init: library wrapper done
This commit is contained in:
69
.gitignore
vendored
Normal file
69
.gitignore
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/java,c++
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=java,c++
|
||||||
|
|
||||||
|
### C++ ###
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
### Java ###
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
replay_pid*
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/java,c++
|
||||||
|
|
||||||
|
|
||||||
|
discord.sh
|
||||||
|
|
||||||
|
*.h
|
||||||
107
DiscordActivity.java
Normal file
107
DiscordActivity.java
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package com.leohabrom.discord;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class DiscordActivity {
|
||||||
|
// Basic Info
|
||||||
|
public String name = null;
|
||||||
|
public String state = null;
|
||||||
|
public String stateUrl = null;
|
||||||
|
public String details = null;
|
||||||
|
public String detailsUrl = null;
|
||||||
|
public int type = -1;
|
||||||
|
public int status = -1;
|
||||||
|
public int platform = -1;
|
||||||
|
|
||||||
|
// Assets
|
||||||
|
public String largeImage = null;
|
||||||
|
public String largeText = null;
|
||||||
|
public String largeUrl = null;
|
||||||
|
public String smallImage = null;
|
||||||
|
public String smallText = null;
|
||||||
|
public String smallUrl = null;
|
||||||
|
public String inviteCoverImage = null;
|
||||||
|
|
||||||
|
// Party
|
||||||
|
public String partyId = null;
|
||||||
|
public int partyCurrentSize = -1;
|
||||||
|
public int partyMaxSize = -1;
|
||||||
|
public int partyPrivacy = -1;
|
||||||
|
|
||||||
|
// Timestamps (Unix milliseconds)
|
||||||
|
public long startTimestamp = -1;
|
||||||
|
public long endTimestamp = -1;
|
||||||
|
|
||||||
|
//Buttons
|
||||||
|
public String firstButtonLabel = null;
|
||||||
|
public String firstButtonUrl = null;
|
||||||
|
public String secondButtonLabel = null;
|
||||||
|
public String secondButtonUrl = null;
|
||||||
|
|
||||||
|
public String joinSecret = null;
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
public static final int TYPE_PLAYING = 0;
|
||||||
|
//public static final int TYPE_STREAMING = 1;
|
||||||
|
public static final int TYPE_LISTENING = 2;
|
||||||
|
public static final int TYPE_WATCHING = 3;
|
||||||
|
public static final int TYPE_COMPETING = 4;
|
||||||
|
//public static final int TYPE_CUSTOM = 5;
|
||||||
|
//public static final int TYPE_HANG = 6;
|
||||||
|
|
||||||
|
public static final int STATUS_NAME = 0;
|
||||||
|
public static final int STATUS_STATE = 1;
|
||||||
|
public static final int STATUS_DETAIL = 2;
|
||||||
|
|
||||||
|
public static final int PLATFORM_ANDROID = 0;
|
||||||
|
public static final int PLATFOTM_DESKTOP = 1;
|
||||||
|
public static final int PLATFORM_EMBEDDED = 2;
|
||||||
|
public static final int PLATFORM_IOS = 3;
|
||||||
|
public static final int PLATFORM_PS4 = 4;
|
||||||
|
public static final int PLATFORM_PS5 = 5;
|
||||||
|
public static final int PLATFORM_SAMSUNG = 6;
|
||||||
|
public static final int PLATFORM_XBOX = 7;
|
||||||
|
|
||||||
|
public static final int PARTY_PRIVATE = 0;
|
||||||
|
public static final int PARTY_PUBLIC = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
DiscordActivity that = (DiscordActivity) o;
|
||||||
|
|
||||||
|
return type == that.type &&
|
||||||
|
status == that.status &&
|
||||||
|
// small tolerance for timestamps to account for drift
|
||||||
|
Math.abs(startTimestamp - that.startTimestamp) < 500 &&
|
||||||
|
Math.abs(endTimestamp - that.endTimestamp) < 500 &&
|
||||||
|
Objects.equals(name, that.name) &&
|
||||||
|
Objects.equals(state, that.state) &&
|
||||||
|
Objects.equals(stateUrl, that.stateUrl) &&
|
||||||
|
Objects.equals(details, that.details) &&
|
||||||
|
Objects.equals(detailsUrl, that.detailsUrl) &&
|
||||||
|
Objects.equals(largeImage, that.largeImage) &&
|
||||||
|
Objects.equals(largeUrl, that.largeUrl) &&
|
||||||
|
Objects.equals(largeText, that.largeText) &&
|
||||||
|
Objects.equals(smallImage, that.smallImage) &&
|
||||||
|
Objects.equals(smallUrl, that.smallUrl) &&
|
||||||
|
Objects.equals(smallText, that.smallText) &&
|
||||||
|
Objects.equals(inviteCoverImage, that.inviteCoverImage) &&
|
||||||
|
Objects.equals(platform, that.platform) &&
|
||||||
|
Objects.equals(partyId, that.partyId) &&
|
||||||
|
Objects.equals(partyCurrentSize, that.partyCurrentSize) &&
|
||||||
|
Objects.equals(partyMaxSize, that.partyMaxSize) &&
|
||||||
|
Objects.equals(partyPrivacy, that.partyPrivacy) &&
|
||||||
|
Objects.equals(firstButtonLabel, that.firstButtonLabel) &&
|
||||||
|
Objects.equals(firstButtonUrl, that.firstButtonUrl) &&
|
||||||
|
Objects.equals(secondButtonLabel, that.secondButtonLabel) &&
|
||||||
|
Objects.equals(secondButtonUrl, that.secondButtonUrl) &&
|
||||||
|
Objects.equals(joinSecret, that.joinSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, state, stateUrl, details, detailsUrl, type, status, platform, largeImage, largeText, largeUrl, smallImage, smallText, smallUrl, inviteCoverImage, partyId, partyCurrentSize, partyMaxSize, partyPrivacy, startTimestamp, endTimestamp, firstButtonLabel, firstButtonUrl, secondButtonLabel, secondButtonUrl, joinSecret);
|
||||||
|
}
|
||||||
|
}
|
||||||
265
DiscordActivityBuilder.java
Normal file
265
DiscordActivityBuilder.java
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
package com.leohabrom.discord;
|
||||||
|
|
||||||
|
import com.leohabrom.discord.DiscordActivity;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
public class DiscordActivityBuilder {
|
||||||
|
public enum Type {
|
||||||
|
PLAYING(0),
|
||||||
|
WATCHING(3),
|
||||||
|
LISTENING(2),
|
||||||
|
COMPETING(4);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
Type(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
NAME(0),
|
||||||
|
STATE(1),
|
||||||
|
DETAIL(2);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
Status(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Platform {
|
||||||
|
ANDROID(0),
|
||||||
|
DESKTOP(1),
|
||||||
|
EMBEDDED(2),
|
||||||
|
IOS(3),
|
||||||
|
PS4(4),
|
||||||
|
PS5(5),
|
||||||
|
SAMSUNG(6),
|
||||||
|
XBOX(7);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
Platform(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Privacy {
|
||||||
|
PRIVATE(0),
|
||||||
|
PUBLIC(1);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
Privacy(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Button {
|
||||||
|
FIRST(0),
|
||||||
|
SECOND(1);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
Button(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DiscordActivity activity;
|
||||||
|
|
||||||
|
public DiscordActivityBuilder() {
|
||||||
|
activity = new DiscordActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder details(String details) {
|
||||||
|
activity.details = details;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder details(String details, URI url) {
|
||||||
|
activity.details = details;
|
||||||
|
activity.detailsUrl = url.toString();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder name(String name) {
|
||||||
|
activity.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder state(String state) {
|
||||||
|
activity.state = state;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder state(String state, URI url) {
|
||||||
|
activity.state = state;
|
||||||
|
activity.stateUrl = url.toString();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder type(Type type) {
|
||||||
|
activity.type = type.getValue();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder statusDisplay(Status status) {
|
||||||
|
activity.status = status.getValue();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder platform(Platform platform) {
|
||||||
|
activity.platform = platform.getValue();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder largeImage(String idOrUrl) {
|
||||||
|
activity.largeImage = idOrUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder largeImage(String idOrUrl, String text) {
|
||||||
|
activity.largeImage = idOrUrl;
|
||||||
|
activity.largeText = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder largeImage(String idOrUrl, URI url) {
|
||||||
|
activity.largeImage = idOrUrl;
|
||||||
|
activity.largeUrl = url.toString();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder largeImage(String idOrUrl, String text, URI url) {
|
||||||
|
activity.largeImage = idOrUrl;
|
||||||
|
activity.largeText = text;
|
||||||
|
activity.largeUrl = url.toString();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder smallImage(String idOrUrl) {
|
||||||
|
activity.smallImage = idOrUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder smallImage(String idOrUrl, String text) {
|
||||||
|
activity.smallImage = idOrUrl;
|
||||||
|
activity.smallText = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder smallImage(String idOrUrl, URI url) {
|
||||||
|
activity.smallImage = idOrUrl;
|
||||||
|
activity.smallUrl = url.toString();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder smallImage(String idOrUrl, String text, URI url) {
|
||||||
|
activity.smallImage = idOrUrl;
|
||||||
|
activity.smallText = text;
|
||||||
|
activity.smallUrl = url.toString();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder inviteCoverImage(String idOrUrl) {
|
||||||
|
activity.inviteCoverImage = idOrUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder party(String id) {
|
||||||
|
activity.partyId = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public DiscordActivityBuilder party(String id, int currentSize) {
|
||||||
|
activity.partyId = id;
|
||||||
|
activity.partyCurrentSize = currentSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public DiscordActivityBuilder party(String id, int currentSize, Privacy privacy) {
|
||||||
|
activity.partyId = id;
|
||||||
|
activity.partyCurrentSize = currentSize;
|
||||||
|
activity.partyPrivacy = privacy.getValue();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder party(String id, Privacy privacy) {
|
||||||
|
activity.partyId = id;
|
||||||
|
activity.partyPrivacy = privacy.getValue();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder party(String id, int currentSize, int maxSize, Privacy privacy) {
|
||||||
|
activity.partyId = id;
|
||||||
|
activity.partyCurrentSize = currentSize;
|
||||||
|
activity.partyMaxSize = maxSize;
|
||||||
|
activity.partyPrivacy = privacy.getValue();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder time(long startTime) {
|
||||||
|
activity.startTimestamp = startTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder endTime(long endTime) {
|
||||||
|
activity.startTimestamp = endTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder time(long startTime, long endTime) {
|
||||||
|
activity.startTimestamp = startTime;
|
||||||
|
activity.endTimestamp = endTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder button(Button id, String label, URI url) {
|
||||||
|
switch (id) {
|
||||||
|
case FIRST -> {
|
||||||
|
activity.firstButtonLabel = label;
|
||||||
|
activity.firstButtonUrl = url.toString();
|
||||||
|
}
|
||||||
|
case SECOND -> {
|
||||||
|
activity.secondButtonLabel = label;
|
||||||
|
activity.secondButtonUrl = url.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivityBuilder joinSecret(String secret) {
|
||||||
|
activity.joinSecret = secret;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscordActivity build() {
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
}
|
||||||
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
36
DiscordBridge.java
Normal file
36
DiscordBridge.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package com.leohabrom.discord;
|
||||||
|
import com.leohabrom.discord.DiscordBridgeAdapter;
|
||||||
|
|
||||||
|
public class DiscordBridge {
|
||||||
|
static {
|
||||||
|
System.loadLibrary("discord_bridge");
|
||||||
|
}
|
||||||
|
|
||||||
|
public native void init(long appId);
|
||||||
|
public native void update(DiscordActivity activity);
|
||||||
|
public native void runCallbacks();
|
||||||
|
private DiscordBridgeAdapter adapter = null;
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
Thread heartbeat = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
runCallbacks();
|
||||||
|
Thread.sleep(16);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
});
|
||||||
|
heartbeat.setDaemon(true);
|
||||||
|
heartbeat.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void setAdapter(DiscordBridgeAdapter adapter) {
|
||||||
|
this.adapter = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(String string) {
|
||||||
|
if (adapter!=null) adapter.onMessage(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
DiscordBridgeAdapter.java
Normal file
5
DiscordBridgeAdapter.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package com.leohabrom.discord;
|
||||||
|
|
||||||
|
public interface DiscordBridgeAdapter {
|
||||||
|
void onMessage(String message);
|
||||||
|
}
|
||||||
4
README.md
Normal file
4
README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
```
|
||||||
|
export DISCORD_SDK=<path-to-discord-sdk>
|
||||||
|
./export.sh
|
||||||
|
```
|
||||||
20
export.sh
Executable file
20
export.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
rm *.class
|
||||||
|
|
||||||
|
javac -g -d . DiscordActivity.java DiscordActivityBuilder.java DiscordBridgeAdapter.java DiscordBridge.java
|
||||||
|
|
||||||
|
jar cvf out/lib/DiscordLib.jar com/ DiscordActivity.java DiscordActivityBuilder.java DiscordBridgeAdapter.java DiscordBridge.java
|
||||||
|
javac -h . DiscordActivity.java DiscordActivityBuilder.java DiscordBridgeAdapter.java DiscordBridge.java
|
||||||
|
|
||||||
|
|
||||||
|
g++ -shared -fPIC \
|
||||||
|
-I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \
|
||||||
|
-I"$DISCORD_SDK/include" \
|
||||||
|
DiscordBridge.cpp \
|
||||||
|
-L"$DISCORD_SDK/lib/release" \
|
||||||
|
-ldiscord_partner_sdk \
|
||||||
|
-Wl,-rpath,'$ORIGIN' \
|
||||||
|
-o out/lib/libdiscord_bridge.so
|
||||||
|
|
||||||
|
cp "$DISCORD_SDK/lib/release/libdiscord_partner_sdk.so" out/lib/
|
||||||
|
|
||||||
|
zip out/libraries.zip out/lib/DiscordLib.jar out/lib/libdiscord_bridge.so out/lib/libdiscord_partner_sdk.so
|
||||||
Reference in New Issue
Block a user