#define DISCORDPP_IMPLEMENTATION #include "discordpp.h" #include "com_leohabrom_discord_DiscordBridge.h" #include #include #include std::shared_ptr 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(); g_client->SetApplicationId(static_cast(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(start)); hasTimestamps = true; } jlong end = env->GetLongField(jAct, env->GetFieldID(cls, "endTimestamp", "J")); if (end != -1) { ts.SetEnd(static_cast(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(); } }