init: library wrapper done
This commit is contained in:
347
DiscordBridge.cpp
Normal file
347
DiscordBridge.cpp
Normal file
@@ -0,0 +1,347 @@
|
||||
#define DISCORDPP_IMPLEMENTATION
|
||||
#include "discordpp.h"
|
||||
#include "DiscordBridge.h"
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
std::shared_ptr<discordpp::Client> g_client;
|
||||
JavaVM *g_jvm = nullptr;
|
||||
// Helper to check if a Java string field is set and return it
|
||||
std::string getJString(JNIEnv* env, jobject obj, const char* fieldName) {
|
||||
jclass objClass = env->GetObjectClass(obj);
|
||||
jfieldID fid = env->GetFieldID(objClass, fieldName, "Ljava/lang/String;");
|
||||
jstring jstr = (jstring)env->GetObjectField(obj, fid);
|
||||
|
||||
if (!jstr) return "";
|
||||
|
||||
// 1. Call String.getBytes("UTF-8")
|
||||
jclass stringClass = env->FindClass("java/lang/String");
|
||||
jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
|
||||
jstring utf8 = env->NewStringUTF("UTF-8");
|
||||
jbyteArray array = (jbyteArray)env->CallObjectMethod(jstr, getBytes, utf8);
|
||||
|
||||
// 2. Convert jbyteArray to std::string
|
||||
jsize len = env->GetArrayLength(array);
|
||||
jbyte* bytes = env->GetByteArrayElements(array, nullptr);
|
||||
std::string result((char*)bytes, len);
|
||||
|
||||
// 3. Cleanup
|
||||
env->ReleaseByteArrayElements(array, bytes, JNI_ABORT);
|
||||
env->DeleteLocalRef(utf8);
|
||||
env->DeleteLocalRef(array);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void sendToJavaThreadSafe(jobject globalObj, const std::string &message)
|
||||
{
|
||||
if (g_jvm == nullptr)
|
||||
return;
|
||||
|
||||
JNIEnv *env;
|
||||
bool attached = false;
|
||||
jint res = g_jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
||||
|
||||
if (res == JNI_EDETACHED)
|
||||
{
|
||||
if (g_jvm->AttachCurrentThread((void **)&env, nullptr) != 0)
|
||||
return;
|
||||
attached = true;
|
||||
}
|
||||
|
||||
jclass cls = env->GetObjectClass(globalObj);
|
||||
jmethodID mid = env->GetMethodID(cls, "onMessage", "(Ljava/lang/String;)V");
|
||||
|
||||
if (mid != nullptr)
|
||||
{
|
||||
jstring jmsg = env->NewStringUTF(message.c_str());
|
||||
env->CallVoidMethod(globalObj, mid, jmsg);
|
||||
env->DeleteLocalRef(jmsg);
|
||||
}
|
||||
|
||||
if (attached)
|
||||
g_jvm->DetachCurrentThread();
|
||||
}
|
||||
extern "C"
|
||||
{
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
|
||||
{
|
||||
g_jvm = vm;
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_leohabrom_discord_DiscordBridge_init(JNIEnv *env, jobject obj, jlong appId)
|
||||
{
|
||||
g_client = std::make_shared<discordpp::Client>();
|
||||
g_client->SetApplicationId(static_cast<uint64_t>(appId));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_leohabrom_discord_DiscordBridge_update(JNIEnv *env, jobject obj, jobject jAct)
|
||||
{
|
||||
if (!g_client)
|
||||
{
|
||||
return;
|
||||
}
|
||||
jobject globalObj = env->NewGlobalRef(obj);
|
||||
|
||||
discordpp::Activity activity;
|
||||
jclass cls = env->GetObjectClass(jAct);
|
||||
|
||||
// 1. Basic Info & URLs
|
||||
std::string name = getJString(env, jAct, "name");
|
||||
if (!name.empty())
|
||||
activity.SetName(name);
|
||||
|
||||
std::string state = getJString(env, jAct, "state");
|
||||
if (!state.empty())
|
||||
{
|
||||
activity.SetState(state);
|
||||
std::string stateUrl = getJString(env, jAct, "stateUrl");
|
||||
if (!stateUrl.empty())
|
||||
activity.SetStateUrl(stateUrl);
|
||||
}
|
||||
|
||||
std::string details = getJString(env, jAct, "details");
|
||||
if (!details.empty())
|
||||
{
|
||||
activity.SetDetails(details);
|
||||
std::string detailsUrl = getJString(env, jAct, "detailsUrl");
|
||||
if (!detailsUrl.empty())
|
||||
activity.SetDetailsUrl(detailsUrl);
|
||||
}
|
||||
|
||||
// 2. Enums (Type, Status Display, Platform)
|
||||
jint jType = env->GetIntField(jAct, env->GetFieldID(cls, "type", "I"));
|
||||
switch (jType)
|
||||
{
|
||||
case 0:
|
||||
activity.SetType(discordpp::ActivityTypes::Playing);
|
||||
break;
|
||||
// case 1: activity.SetType(discordpp::ActivityTypes::Streaming); break;
|
||||
case 2:
|
||||
activity.SetType(discordpp::ActivityTypes::Listening);
|
||||
break;
|
||||
case 3:
|
||||
activity.SetType(discordpp::ActivityTypes::Watching);
|
||||
break;
|
||||
case 4:
|
||||
activity.SetType(discordpp::ActivityTypes::Competing);
|
||||
break;
|
||||
// case 5: activity.SetType(discordpp::ActivityTypes::CustomStatus); break;
|
||||
// case 6: activity.SetType(discordpp::ActivityTypes::HangStatus); break;
|
||||
// If type is -1 or any other value, default to Playing
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
jint jStatus = env->GetIntField(jAct, env->GetFieldID(cls, "status", "I"));
|
||||
switch (jStatus)
|
||||
{
|
||||
case 0:
|
||||
activity.SetStatusDisplayType(discordpp::StatusDisplayTypes::Name);
|
||||
break;
|
||||
case 1:
|
||||
activity.SetStatusDisplayType(discordpp::StatusDisplayTypes::State);
|
||||
break;
|
||||
case 2:
|
||||
activity.SetStatusDisplayType(discordpp::StatusDisplayTypes::Details);
|
||||
break;
|
||||
default:
|
||||
break; // Don't set if -1
|
||||
}
|
||||
|
||||
jint jPlatform = env->GetIntField(jAct, env->GetFieldID(cls, "platform", "I"));
|
||||
switch (jPlatform)
|
||||
{
|
||||
case 0:
|
||||
activity.SetSupportedPlatforms(discordpp::ActivityGamePlatforms::Android);
|
||||
break;
|
||||
case 1:
|
||||
activity.SetSupportedPlatforms(discordpp::ActivityGamePlatforms::Desktop);
|
||||
break;
|
||||
case 2:
|
||||
activity.SetSupportedPlatforms(discordpp::ActivityGamePlatforms::Embedded);
|
||||
break;
|
||||
case 3:
|
||||
activity.SetSupportedPlatforms(discordpp::ActivityGamePlatforms::IOS);
|
||||
break;
|
||||
case 4:
|
||||
activity.SetSupportedPlatforms(discordpp::ActivityGamePlatforms::PS4);
|
||||
break;
|
||||
case 5:
|
||||
activity.SetSupportedPlatforms(discordpp::ActivityGamePlatforms::PS5);
|
||||
break;
|
||||
case 6:
|
||||
activity.SetSupportedPlatforms(discordpp::ActivityGamePlatforms::Samsung);
|
||||
break;
|
||||
case 7:
|
||||
activity.SetSupportedPlatforms(discordpp::ActivityGamePlatforms::Xbox);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 3. Assets
|
||||
discordpp::ActivityAssets assets;
|
||||
bool hasAnyAsset = false;
|
||||
|
||||
// Handle Large Image Group
|
||||
std::string li = getJString(env, jAct, "largeImage");
|
||||
if (!li.empty())
|
||||
{
|
||||
assets.SetLargeImage(li);
|
||||
hasAnyAsset = true;
|
||||
|
||||
// Only set metadata if the image itself exists
|
||||
std::string lt = getJString(env, jAct, "largeText");
|
||||
if (!lt.empty())
|
||||
assets.SetLargeText(lt);
|
||||
|
||||
std::string lu = getJString(env, jAct, "largeUrl");
|
||||
if (!lu.empty())
|
||||
assets.SetLargeUrl(lu);
|
||||
}
|
||||
|
||||
// Handle Small Image Group (Independent of Large Image)
|
||||
std::string si = getJString(env, jAct, "smallImage");
|
||||
if (!si.empty())
|
||||
{
|
||||
assets.SetSmallImage(si);
|
||||
hasAnyAsset = true;
|
||||
|
||||
// Only set metadata if the image itself exists
|
||||
std::string st = getJString(env, jAct, "smallText");
|
||||
if (!st.empty())
|
||||
assets.SetSmallText(st);
|
||||
|
||||
std::string su = getJString(env, jAct, "smallUrl");
|
||||
if (!su.empty())
|
||||
assets.SetSmallUrl(su);
|
||||
}
|
||||
|
||||
std::string ci = getJString(env, jAct, "inviteCoverImage");
|
||||
if (!ci.empty())
|
||||
{
|
||||
assets.SetInviteCoverImage(ci);
|
||||
hasAnyAsset = true;
|
||||
}
|
||||
|
||||
// Only apply assets to the activity if at least one image was set
|
||||
if (hasAnyAsset)
|
||||
{
|
||||
activity.SetAssets(assets);
|
||||
}
|
||||
|
||||
// 4. Party & Secrets Logic (Independent & Optional)
|
||||
discordpp::ActivityParty party;
|
||||
|
||||
// Party ID
|
||||
std::string pId = getJString(env, jAct, "partyId");
|
||||
if (!pId.empty())
|
||||
{
|
||||
party.SetId(pId);
|
||||
|
||||
// Current Size
|
||||
jint pCur = env->GetIntField(jAct, env->GetFieldID(cls, "partyCurrentSize", "I"));
|
||||
if (pCur != -1)
|
||||
{
|
||||
party.SetCurrentSize(pCur);
|
||||
}
|
||||
|
||||
// Max Size
|
||||
jint pMax = env->GetIntField(jAct, env->GetFieldID(cls, "partyMaxSize", "I"));
|
||||
if (pMax != -1)
|
||||
{
|
||||
party.SetMaxSize(pMax);
|
||||
}
|
||||
|
||||
// Privacy
|
||||
jint pPriv = env->GetIntField(jAct, env->GetFieldID(cls, "partyPrivacy", "I"));
|
||||
if (pPriv != -1)
|
||||
{
|
||||
// 0 = Private, 1 = Public (based on your Java constants)
|
||||
if (pPriv == 0)
|
||||
party.SetPrivacy(discordpp::ActivityPartyPrivacy::Private);
|
||||
else if (pPriv == 1)
|
||||
party.SetPrivacy(discordpp::ActivityPartyPrivacy::Public);
|
||||
}
|
||||
|
||||
activity.SetParty(party);
|
||||
}
|
||||
|
||||
// 5. Timestamps Logic (Independent & Optional)
|
||||
discordpp::ActivityTimestamps ts;
|
||||
bool hasTimestamps = false;
|
||||
|
||||
jlong start = env->GetLongField(jAct, env->GetFieldID(cls, "startTimestamp", "J"));
|
||||
if (start != -1)
|
||||
{
|
||||
ts.SetStart(static_cast<int64_t>(start));
|
||||
hasTimestamps = true;
|
||||
}
|
||||
|
||||
jlong end = env->GetLongField(jAct, env->GetFieldID(cls, "endTimestamp", "J"));
|
||||
if (end != -1)
|
||||
{
|
||||
ts.SetEnd(static_cast<int64_t>(end));
|
||||
hasTimestamps = true;
|
||||
}
|
||||
|
||||
if (hasTimestamps)
|
||||
{
|
||||
activity.SetTimestamps(ts);
|
||||
}
|
||||
|
||||
// 6. Buttons
|
||||
bool hasButton = false;
|
||||
|
||||
std::string b1Label = getJString(env, jAct, "firstButtonLabel");
|
||||
std::string b1Url = getJString(env, jAct, "firstButtonUrl");
|
||||
|
||||
if (!b1Label.empty() && !b1Url.empty())
|
||||
{
|
||||
discordpp::ActivityButton b1;
|
||||
b1.SetLabel(b1Label);
|
||||
b1.SetUrl(b1Url);
|
||||
hasButton = true;
|
||||
activity.AddButton(b1);
|
||||
}
|
||||
|
||||
std::string b2Label = getJString(env, jAct, "secondButtonLabel");
|
||||
std::string b2Url = getJString(env, jAct, "secondButtonUrl");
|
||||
|
||||
if (!b2Label.empty() && !b2Url.empty())
|
||||
{
|
||||
discordpp::ActivityButton b2;
|
||||
b2.SetLabel(b2Label);
|
||||
b2.SetUrl(b2Url);
|
||||
hasButton = true;
|
||||
activity.AddButton(b2);
|
||||
}
|
||||
|
||||
// Join Secret (Independent)
|
||||
std::string joinSec = getJString(env, jAct, "joinSecret");
|
||||
if (!joinSec.empty() && !hasButton)
|
||||
{
|
||||
discordpp::ActivitySecrets secrets;
|
||||
secrets.SetJoin(joinSec);
|
||||
activity.SetSecrets(secrets);
|
||||
}
|
||||
|
||||
g_client->UpdateRichPresence(activity, [globalObj](const discordpp::ClientResult &r)
|
||||
{
|
||||
std::cout << r.ToString() << std::endl;
|
||||
if (r.Successful()) sendToJavaThreadSafe(globalObj, "Update Sent");
|
||||
else sendToJavaThreadSafe(globalObj, "Update Failed");
|
||||
JNIEnv* t_env;
|
||||
if (g_jvm->GetEnv((void**)&t_env, JNI_VERSION_1_6) == JNI_OK) {
|
||||
t_env->DeleteGlobalRef(globalObj);
|
||||
} });
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_leohabrom_discord_DiscordBridge_runCallbacks(JNIEnv *env, jobject obj)
|
||||
{
|
||||
discordpp::RunCallbacks();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user