Implement Discord Rich Presence SDK from Simple RPC
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@ eclipse
|
||||
run
|
||||
|
||||
artifacts
|
||||
src/test/**
|
@@ -0,0 +1,90 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk;
|
||||
|
||||
import com.hypherionmc.craterlib.core.rpcsdk.callbacks.*;
|
||||
import com.sun.jna.Structure;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Class containing references to all available discord event handles.
|
||||
* Registering a handler is optional, and non-assigned handlers will be ignored
|
||||
*/
|
||||
public class DiscordEventHandlers extends Structure {
|
||||
|
||||
// Callback for when the RPC was initialized successfully
|
||||
public ReadyCallback ready;
|
||||
|
||||
// Callback for when the Discord connection was ended
|
||||
public DisconnectedCallback disconnected;
|
||||
|
||||
// Callback for when a Discord Error occurs
|
||||
public ErroredCallback errored;
|
||||
|
||||
// Callback for when a player joins the game
|
||||
public JoinGameCallback joinGame;
|
||||
|
||||
// Callback for when a player spectates the game
|
||||
public SpectateGameCallback spectateGame;
|
||||
|
||||
// Callback for when a players request to join your game
|
||||
public JoinRequestCallback joinRequest;
|
||||
|
||||
/**
|
||||
* DO NOT TOUCH THIS... EVER!
|
||||
*/
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"ready",
|
||||
"disconnected",
|
||||
"errored",
|
||||
"joinGame",
|
||||
"spectateGame",
|
||||
"joinRequest"
|
||||
);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final DiscordEventHandlers handlers;
|
||||
|
||||
public Builder() {
|
||||
this.handlers = new DiscordEventHandlers();
|
||||
}
|
||||
|
||||
public Builder ready(ReadyCallback readyCallback) {
|
||||
handlers.ready = readyCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder disconnected(DisconnectedCallback disconnectedCallback) {
|
||||
handlers.disconnected = disconnectedCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder errored(ErroredCallback erroredCallback) {
|
||||
handlers.errored = erroredCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder joinGame(JoinGameCallback joinGameCallback) {
|
||||
handlers.joinGame = joinGameCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder spectateGame(SpectateGameCallback spectateGameCallback) {
|
||||
handlers.spectateGame = spectateGameCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder joinRequest(JoinRequestCallback joinRequestCallback) {
|
||||
handlers.joinRequest = joinRequestCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DiscordEventHandlers build() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Java Wrapper of the Discord-RPC Library
|
||||
*/
|
||||
public interface DiscordRPC extends Library {
|
||||
|
||||
DiscordRPC INSTANCE = Native.load("discord-rpc", DiscordRPC.class);
|
||||
|
||||
/**
|
||||
* Open a New RPC Connection
|
||||
* @param applicationId The ID of the Application the RPC is tied to
|
||||
* @param handlers Optional Event Callback Handlers
|
||||
* @param autoRegister Auto Register the running game
|
||||
* @param steamId Steam ID of the game
|
||||
*/
|
||||
void Discord_Initialize(@NotNull String applicationId, @Nullable DiscordEventHandlers handlers, boolean autoRegister, @Nullable String steamId);
|
||||
|
||||
/**
|
||||
* Shutdown the RPC instance and disconnect from discord
|
||||
*/
|
||||
void Discord_Shutdown();
|
||||
|
||||
/**
|
||||
* Need to be called manually at least every 2 seconds, to allow RPC updates
|
||||
* and callback handlers to fire
|
||||
*/
|
||||
void Discord_RunCallbacks();
|
||||
|
||||
/**
|
||||
* Not sure about this. Believe it needs to be called manually in some circumstances
|
||||
*/
|
||||
void Discord_UpdateConnection();
|
||||
|
||||
/**
|
||||
* Update the Rich Presence
|
||||
* @param struct Constructed {@link DiscordRichPresence}
|
||||
*/
|
||||
void Discord_UpdatePresence(@Nullable DiscordRichPresence struct);
|
||||
|
||||
/**
|
||||
* Clear the current Rich Presence
|
||||
*/
|
||||
void Discord_ClearPresence();
|
||||
|
||||
/**
|
||||
* Respond to Join/Spectate callback
|
||||
* @param userid The Discord User ID of the user that initiated the request
|
||||
* @param reply Reply to the request. See {@link DiscordReply}
|
||||
*/
|
||||
void Discord_Respond(@NotNull String userid, int reply);
|
||||
|
||||
/**
|
||||
* Replace the already registered {@link DiscordEventHandlers}
|
||||
* @param handlers The new handlers to apply
|
||||
*/
|
||||
void Discord_UpdateHandlers(@Nullable DiscordEventHandlers handlers);
|
||||
|
||||
/**
|
||||
* Register the executable of the application/game
|
||||
* Only applicable when autoRegister is set to false
|
||||
* @param applicationId The Application ID
|
||||
* @param command The Launch command of the game
|
||||
*
|
||||
* NB: THIS DOES NOT WORK WITH MINECRAFT
|
||||
*/
|
||||
void Discord_Register(String applicationId, String command);
|
||||
|
||||
/**
|
||||
* Register the Steam executable of the application/game
|
||||
* @param applicationId The Application ID
|
||||
* @param steamId The Steam ID of the application/game
|
||||
*/
|
||||
void Discord_RegisterSteamGame(String applicationId, String steamId);
|
||||
|
||||
public enum DiscordReply {
|
||||
NO(0),
|
||||
YES(1),
|
||||
IGNORE(2);
|
||||
|
||||
public final int reply;
|
||||
|
||||
DiscordReply(int reply) {
|
||||
this.reply = reply;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,244 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk;
|
||||
|
||||
import com.hypherionmc.craterlib.core.rpcsdk.helpers.RPCButton;
|
||||
import com.sun.jna.Structure;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Class reprenting a Discord RPC activity
|
||||
*/
|
||||
public class DiscordRichPresence extends Structure {
|
||||
|
||||
// First line of text on the RPC
|
||||
public String state;
|
||||
|
||||
// Second line of text on the RPC
|
||||
public String details;
|
||||
|
||||
// Time the activity started in UNIX-Timestamp format
|
||||
public long startTimestamp;
|
||||
|
||||
// Time the activity will end in UNIX-Timestamp format
|
||||
public long endTimestamp;
|
||||
|
||||
// URL or Asset key of the Large Image
|
||||
public String largeImageKey;
|
||||
|
||||
// Hover text to display when hovering the Large Image
|
||||
public String largeImageText;
|
||||
|
||||
// URL or Asset key of the Small Image
|
||||
public String smallImageKey;
|
||||
|
||||
// Hover text to display when hovering the Small Image
|
||||
public String smallImageText;
|
||||
|
||||
// Id of the player's party, lobby, or group.
|
||||
public String partyId;
|
||||
|
||||
// Current size of the player's party, lobby, or group.
|
||||
public int partySize;
|
||||
|
||||
// Maximum size of the player's party, lobby, or group.
|
||||
public int partyMax;
|
||||
|
||||
// Unused
|
||||
public String partyPrivacy;
|
||||
|
||||
// Unused.
|
||||
public String matchSecret;
|
||||
|
||||
// Unique hashed string for chat invitations and Ask to Join.
|
||||
public String joinSecret;
|
||||
|
||||
// Unique hashed string for Spectate button.
|
||||
public String spectateSecret;
|
||||
|
||||
// Label of the First RPC Button
|
||||
public String button_label_1;
|
||||
|
||||
// URL of the First RPC Button
|
||||
public String button_url_1;
|
||||
|
||||
// Label of the Second RPC Button
|
||||
public String button_label_2;
|
||||
|
||||
// URL of the Second RPC Button
|
||||
public String button_url_2;
|
||||
|
||||
// Unused
|
||||
public int instance;
|
||||
|
||||
/**
|
||||
* DO NOT TOUCH THIS... EVER!
|
||||
*/
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"state",
|
||||
"details",
|
||||
"startTimestamp",
|
||||
"endTimestamp",
|
||||
"largeImageKey",
|
||||
"largeImageText",
|
||||
"smallImageKey",
|
||||
"smallImageText",
|
||||
"partyId",
|
||||
"partySize",
|
||||
"partyMax",
|
||||
"partyPrivacy",
|
||||
"matchSecret",
|
||||
"joinSecret",
|
||||
"spectateSecret",
|
||||
"button_label_1",
|
||||
"button_url_1",
|
||||
"button_label_2",
|
||||
"button_url_2",
|
||||
"instance"
|
||||
);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final DiscordRichPresence rpc;
|
||||
|
||||
public Builder(String state) {
|
||||
rpc = new DiscordRichPresence();
|
||||
|
||||
if (state != null && !state.isEmpty()) {
|
||||
rpc.state = state.substring(0, Math.min(state.length(), 128));
|
||||
}
|
||||
}
|
||||
|
||||
public Builder setDetails(String details) {
|
||||
if (details != null && !details.isEmpty()) {
|
||||
rpc.details = details.substring(0, Math.min(details.length(), 128));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setStartTimestamp(long timestamp) {
|
||||
rpc.startTimestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setStartTimestamp(OffsetDateTime timestamp) {
|
||||
rpc.startTimestamp = timestamp.toEpochSecond();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEndTimestamp(long timestamp) {
|
||||
rpc.endTimestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEndTimestamp(OffsetDateTime timestamp) {
|
||||
rpc.endTimestamp = timestamp.toEpochSecond();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLargeImage(String key) {
|
||||
return this.setLargeImage(key, "");
|
||||
}
|
||||
|
||||
public Builder setLargeImage(@NotNull String key, String text) {
|
||||
// Null check used for users blatantly ignoring the NotNull marker
|
||||
if ((text != null && !text.isEmpty()) && key != null) {
|
||||
throw new IllegalArgumentException("Image key cannot be null when assigning a hover text");
|
||||
}
|
||||
|
||||
rpc.largeImageKey = key;
|
||||
rpc.largeImageText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSmallImage(String key) {
|
||||
return this.setSmallImage(key, "");
|
||||
}
|
||||
|
||||
public Builder setSmallImage(@NotNull String key, String text) {
|
||||
// Null check used for users blatantly ignoring the NotNull marker
|
||||
if ((text != null && !text.isEmpty()) && key != null) {
|
||||
throw new IllegalArgumentException("Image key cannot be null when assigning a hover text");
|
||||
}
|
||||
|
||||
rpc.smallImageKey = key;
|
||||
rpc.smallImageText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setParty(String party, int size, int max) {
|
||||
// Buttons are present, ignore
|
||||
if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty()))
|
||||
return this;
|
||||
|
||||
rpc.partyId = party;
|
||||
rpc.partySize = size;
|
||||
rpc.partyMax = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSecrets(String match, String join, String spectate) {
|
||||
// Buttons are present, ignore
|
||||
if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty()))
|
||||
return this;
|
||||
|
||||
rpc.matchSecret = match;
|
||||
rpc.joinSecret = join;
|
||||
rpc.spectateSecret = spectate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSecrets(String join, String spectate) {
|
||||
// Buttons are present, ignore
|
||||
if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty()))
|
||||
return this;
|
||||
|
||||
rpc.joinSecret = join;
|
||||
rpc.spectateSecret = spectate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setInstance(boolean i) {
|
||||
// Buttons are present, ignore
|
||||
if ((rpc.button_label_1 != null && rpc.button_label_1.isEmpty()) || (rpc.button_label_2 != null && rpc.button_label_2.isEmpty()))
|
||||
return this;
|
||||
|
||||
rpc.instance = i ? 1 : 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setButtons(RPCButton button) {
|
||||
return this.setButtons(Collections.singletonList(button));
|
||||
}
|
||||
|
||||
public Builder setButtons(RPCButton button1, RPCButton button2) {
|
||||
return this.setButtons(Arrays.asList(button1, button2));
|
||||
}
|
||||
|
||||
public Builder setButtons(List<RPCButton> rpcButtons) {
|
||||
// Limit to 2 Buttons. Discord Limitation
|
||||
if (rpcButtons != null && !rpcButtons.isEmpty()) {
|
||||
int length = Math.min(rpcButtons.size(), 2);
|
||||
rpc.button_label_1 = rpcButtons.get(0).getLabel();
|
||||
rpc.button_url_1 = rpcButtons.get(0).getUrl();
|
||||
|
||||
if (length == 2) {
|
||||
rpc.button_label_2 = rpcButtons.get(1).getLabel();
|
||||
rpc.button_url_2 = rpcButtons.get(1).getUrl();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public DiscordRichPresence build() {
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk;
|
||||
|
||||
import com.sun.jna.Structure;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Class representing the Discord User
|
||||
*/
|
||||
public class DiscordUser extends Structure {
|
||||
|
||||
// The User ID of the User
|
||||
public String userId;
|
||||
|
||||
// The Username of the User
|
||||
public String username;
|
||||
|
||||
// The unique identifier of the user. Discontinued by Discord
|
||||
@Deprecated
|
||||
public String discriminator;
|
||||
|
||||
// The avatar has of the user
|
||||
public String avatar;
|
||||
|
||||
/**
|
||||
* DO NOT TOUCH THIS... EVER!
|
||||
*/
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"userId",
|
||||
"username",
|
||||
"discriminator",
|
||||
"avatar"
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk.callbacks;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Callback for when the Discord RPC disconnects
|
||||
*/
|
||||
public interface DisconnectedCallback extends Callback {
|
||||
|
||||
/**
|
||||
* Called when RPC disconnected
|
||||
* @param errorCode Error code if any
|
||||
* @param message Details about the disconnection
|
||||
*/
|
||||
void apply(int errorCode, String message);
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk.callbacks;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Callback for when the RPC ran into an error
|
||||
*/
|
||||
public interface ErroredCallback extends Callback {
|
||||
|
||||
/**
|
||||
* Called when an RPC error occurs
|
||||
* @param errorCode Error code if any
|
||||
* @param message Details about the error
|
||||
*/
|
||||
void apply(int errorCode, String message);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk.callbacks;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Callback for when someone was approved to join your game
|
||||
*/
|
||||
public interface JoinGameCallback extends Callback {
|
||||
|
||||
/**
|
||||
* Called when someone joins a game from {@link JoinRequestCallback}
|
||||
* @param joinSecret Secret or Password required to let the player join the game
|
||||
*/
|
||||
void apply(String joinSecret);
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk.callbacks;
|
||||
|
||||
import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser;
|
||||
import com.sun.jna.Callback;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Callback for when someone requests to join your game
|
||||
*/
|
||||
public interface JoinRequestCallback extends Callback {
|
||||
|
||||
/**
|
||||
* Called when someone clicks on the Join Game button
|
||||
* @param user The Discord User trying to join your game
|
||||
* @see DiscordUser
|
||||
*/
|
||||
void apply(DiscordUser user);
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk.callbacks;
|
||||
|
||||
import com.hypherionmc.craterlib.core.rpcsdk.DiscordUser;
|
||||
import com.sun.jna.Callback;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Callback for when the RPC has connected successfully
|
||||
*/
|
||||
public interface ReadyCallback extends Callback {
|
||||
|
||||
/**
|
||||
* Called when the RPC is connected and ready to be used
|
||||
* @param user The user the RPC is displayed on
|
||||
* @see DiscordUser
|
||||
*/
|
||||
void apply(DiscordUser user);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk.callbacks;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Callback for when someone is requesting to spectate your game
|
||||
*/
|
||||
public interface SpectateGameCallback extends Callback {
|
||||
|
||||
/**
|
||||
* Called when joining the game
|
||||
* @param spectateSecret Secret or Password required to let the player spectate
|
||||
*/
|
||||
void apply(String spectateSecret);
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
package com.hypherionmc.craterlib.core.rpcsdk.helpers;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author HypherionSA
|
||||
* Helper class to add Buttons to Discord Rich Presence
|
||||
* This can not be used with Join/Spectate
|
||||
*/
|
||||
public class RPCButton implements Serializable {
|
||||
|
||||
// The label of the button
|
||||
private final String label;
|
||||
|
||||
// The URL the button will open when clicked
|
||||
private final String url;
|
||||
|
||||
protected RPCButton(String label, String url) {
|
||||
this.label = label;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RPC Button
|
||||
* @param label The label of the button
|
||||
* @param url The URL the button will open when clicked
|
||||
* @return The constructed button
|
||||
*/
|
||||
public static RPCButton create(@NotNull String label, @NotNull String url) {
|
||||
// Null check used here for users blatantly ignoring the NotNull marker
|
||||
if (label == null || label.isEmpty() || url == null || url.isEmpty()) {
|
||||
throw new IllegalArgumentException("RPC Buttons require both a label and url");
|
||||
}
|
||||
|
||||
label = label.substring(0, Math.min(label.length(), 31));
|
||||
return new RPCButton(label, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The label assigned to the button
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The URL of the button
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
BIN
Common/src/main/resources/darwin/libdiscord-rpc.dylib
Normal file
BIN
Common/src/main/resources/darwin/libdiscord-rpc.dylib
Normal file
Binary file not shown.
BIN
Common/src/main/resources/linux-x86-64/libdiscord-rpc.so
Normal file
BIN
Common/src/main/resources/linux-x86-64/libdiscord-rpc.so
Normal file
Binary file not shown.
BIN
Common/src/main/resources/win32-x86-64/discord-rpc.dll
Normal file
BIN
Common/src/main/resources/win32-x86-64/discord-rpc.dll
Normal file
Binary file not shown.
BIN
Common/src/main/resources/win32-x86/discord-rpc.dll
Normal file
BIN
Common/src/main/resources/win32-x86/discord-rpc.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user