diff --git a/src/main/java/club/minnced/discord/rpc/DiscordEventHandlers.java b/src/main/java/club/minnced/discord/rpc/DiscordEventHandlers.java new file mode 100644 index 00000000..c0532795 --- /dev/null +++ b/src/main/java/club/minnced/discord/rpc/DiscordEventHandlers.java @@ -0,0 +1,136 @@ +/* + * Copyright 2016 - 2019 Florian Spieß and the java-discord-rpc contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package club.minnced.discord.rpc; + +import com.sun.jna.Callback; +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/* +typedef struct DiscordEventHandlers { + void (*ready)(DiscordUser*); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* request); +} DiscordEventHandlers; + */ +/** + * Struct containing handlers for RPC events + *
Provided handlers can be null. + */ +public class DiscordEventHandlers extends Structure +{ + /** + * Handler function for the ready event + */ + public interface OnReady extends Callback + { + void accept(DiscordUser user); + } + + /** + * Handler function for the exceptional events (error, disconnect) + */ + public interface OnStatus extends Callback + { + void accept(int errorCode, String message); + } + + /** + * Handler function for game update events (joinGame, spectateGame) + */ + public interface OnGameUpdate extends Callback + { + void accept(String secret); + } + + /** + * Handler function for user join requests + */ + public interface OnJoinRequest extends Callback + { + void accept(DiscordUser request); + } + + private static final List FIELD_ORDER = Collections.unmodifiableList(Arrays.asList( + "ready", + "disconnected", + "errored", + "joinGame", + "spectateGame", + "joinRequest" + )); + + /** + * Called when the RPC connection has been established + */ + public OnReady ready; + /** + * Called when the RPC connection has been severed + */ + public OnStatus disconnected; + /** + * Called when an internal error is caught within the SDK + */ + public OnStatus errored; + /** + * Called when the logged in user joined a game + */ + public OnGameUpdate joinGame; + /** + * Called when the logged in user joined to spectate a game + */ + public OnGameUpdate spectateGame; + /** + * Called when another discord user wants to join the game of the logged in user + */ + public OnJoinRequest joinRequest; + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (!(o instanceof DiscordEventHandlers)) + return false; + DiscordEventHandlers that = (DiscordEventHandlers) o; + return Objects.equals(ready, that.ready) + && Objects.equals(disconnected, that.disconnected) + && Objects.equals(errored, that.errored) + && Objects.equals(joinGame, that.joinGame) + && Objects.equals(spectateGame, that.spectateGame) + && Objects.equals(joinRequest, that.joinRequest); + } + + @Override + public int hashCode() + { + return Objects.hash(ready, disconnected, errored, joinGame, spectateGame, joinRequest); + } + + @Override + protected List getFieldOrder() + { + return FIELD_ORDER; + } +} diff --git a/src/main/java/club/minnced/discord/rpc/DiscordRPC.java b/src/main/java/club/minnced/discord/rpc/DiscordRPC.java new file mode 100644 index 00000000..50ad825f --- /dev/null +++ b/src/main/java/club/minnced/discord/rpc/DiscordRPC.java @@ -0,0 +1,169 @@ +/* + * Copyright 2016 - 2019 Florian Spieß and the java-discord-rpc contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package club.minnced.discord.rpc; + +import com.sun.jna.Library; +import com.sun.jna.Native; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Core library binding for the official Discord RPC SDK. + *
Use {@link #INSTANCE} to access this library. + * + *

Supported Architectures

+ * + */ +public interface DiscordRPC extends Library +{ + /** + * Library instance. + */ + DiscordRPC INSTANCE = Native.loadLibrary("discord-rpc", DiscordRPC.class); + + /** + * Used to decline a request via {@link #Discord_Respond(String, int)} + * @see #DISCORD_REPLY_YES + */ + int DISCORD_REPLY_NO = 0; + /** + * Used to accept a request via {@link #Discord_Respond(String, int)} + * @see #DISCORD_REPLY_NO + */ + int DISCORD_REPLY_YES = 1; + /** + * Currently unsused response, treated like NO. + * Used with {@link #Discord_Respond(String, int)} + * @see #DISCORD_REPLY_NO + */ + int DISCORD_REPLY_IGNORE = 2; + + /** + * Initializes the library, supply with application details and event handlers. + * Handlers are only called when the {@link #Discord_RunCallbacks()} method is invoked! + *
Before closing the application it is recommended to call {@link #Discord_Shutdown()} + * + * @param applicationId + * The ID for this RPC application, + * retrieved from the developer dashboard + * @param handlers + * Nullable instance of {@link club.minnced.discord.rpc.DiscordEventHandlers} + * @param autoRegister + * {@code true} to automatically call {@link #Discord_RegisterSteamGame(String, String)} or {@link #Discord_Register(String, String)} + * @param steamId + * Possible steam ID of the running game + */ + void Discord_Initialize(@Nonnull String applicationId, + @Nullable DiscordEventHandlers handlers, + boolean autoRegister, + @Nullable String steamId); + + /** + * Shuts the RPC connection down. + * If not currently connected, this does nothing. + */ + void Discord_Shutdown(); + + /** + * Executes the registered handlers for currently queued events. + *
If this is not called the handlers will not receive any events! + * + *

It is recommended to call this in a 2 second interval + */ + void Discord_RunCallbacks(); + + /** + * Polls events from the RPC pipe and pushes the currently queued presence. + *
This will be performed automatically if the attached binary + * has an enabled IO thread (default) + * + *

If the IO-Thread has been enabled this will not be supported! + */ + void Discord_UpdateConnection(); + + /** + * Updates the currently set presence of the logged in user. + *
Note that the client only updates its presence every 15 seconds + * and queues all additional presence updates. + * + * @param struct + * The new presence to use + * + * @see club.minnced.discord.rpc.DiscordRichPresence + */ + void Discord_UpdatePresence(@Nullable DiscordRichPresence struct); + + /** + * Clears the currently set presence. + */ + void Discord_ClearPresence(); + + /** + * Responds to the given user with the specified reply type. + * + *

Possible Replies

+ * + * + * @param userid + * The id of the user to respond to + * @param reply + * The reply type + * + * @see club.minnced.discord.rpc.DiscordUser#userId DiscordUser.userId + */ + void Discord_Respond(@Nonnull String userid, int reply); + + /** + * Updates the registered event handlers to the provided struct. + * + * @param handlers + * The handlers to update to, or null + */ + void Discord_UpdateHandlers(@Nullable DiscordEventHandlers handlers); + + /** + * Registers the given application so it can be run by the discord client. {@code discord-://} + * + * @param applicationId + * The ID of the application to register + * @param command + * The command for the application startup, or {@code null} to use the + * current executable's path + */ + void Discord_Register(String applicationId, String command); + + /** + * Similar to {@link #Discord_Register(String, String)} but uses the steam + * game's installation path. + * + * @param applicationId + * The ID of the application to register + * @param steamId + * The steam ID for the game + */ + void Discord_RegisterSteamGame(String applicationId, String steamId); +} diff --git a/src/main/java/club/minnced/discord/rpc/DiscordRichPresence.java b/src/main/java/club/minnced/discord/rpc/DiscordRichPresence.java new file mode 100644 index 00000000..35461411 --- /dev/null +++ b/src/main/java/club/minnced/discord/rpc/DiscordRichPresence.java @@ -0,0 +1,229 @@ +/* + * Copyright 2016 - 2019 Florian Spieß and the java-discord-rpc contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package club.minnced.discord.rpc; + +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/* +typedef struct DiscordRichPresence { + const char* state; // max 128 bytes + const char* details; // max 128 bytes + int64_t startTimestamp; + int64_t endTimestamp; + const char* largeImageKey; // max 32 bytes + const char* largeImageText; // max 128 bytes + const char* smallImageKey; // max 32 bytes + const char* smallImageText; // max 128 bytes + const char* partyId; // max 128 bytes + int partySize; + int partyMax; + const char* matchSecret; // max 128 bytes + const char* joinSecret; // max 128 bytes + const char* spectateSecret; // max 128 bytes + int8_t instance; +} DiscordRichPresence; + */ + +/** + * Struct binding for a RichPresence + */ +public class DiscordRichPresence extends Structure +{ + private static final List FIELD_ORDER = Collections.unmodifiableList(Arrays.asList( + "state", + "details", + "startTimestamp", + "endTimestamp", + "largeImageKey", + "largeImageText", + "smallImageKey", + "smallImageText", + "partyId", + "partySize", + "partyMax", + "matchSecret", + "joinSecret", + "spectateSecret", + "instance" + )); + + public DiscordRichPresence(String encoding) { + super(); + setStringEncoding(encoding); + } + + public DiscordRichPresence() { + this("UTF-8"); + } + + /** + * The user's current party status. + *
Example: "Looking to Play", "Playing Solo", "In a Group" + * + *

Maximum: 128 characters + */ + public String state; + + /** + * What the player is currently doing. + *
Example: "Competitive - Captain's Mode", "In Queue", "Unranked PvP" + * + *

Maximum: 128 characters + */ + public String details; + + /** + * Unix timestamp (seconds) for the start of the game. + *
Example: 1507665886 + */ + public long startTimestamp; + + /** + * Unix timestamp (seconds) for the start of the game. + *
Example: 1507665886 + */ + public long endTimestamp; + + /** + * Name of the uploaded image for the large profile artwork. + *
Example: "default" + * + *

Maximum: 32 characters + */ + public String largeImageKey; + + /** + * Tooltip for the largeImageKey. + *
Example: "Blade's Edge Arena", "Numbani", "Danger Zone" + * + *

Maximum: 128 characters + */ + public String largeImageText; + + /** + * Name of the uploaded image for the small profile artwork. + *
Example: "rogue" + * + *

Maximum: 32 characters + */ + public String smallImageKey; + + /** + * Tooltip for the smallImageKey. + *
Example: "Rogue - Level 100" + * + *

Maximum: 128 characters + */ + public String smallImageText; + + /** + * ID of the player's party, lobby, or group. + *
Example: "ae488379-351d-4a4f-ad32-2b9b01c91657" + * + *

Maximum: 128 characters + */ + public String partyId; + + /** + * Current size of the player's party, lobby, or group. + *
Example: 1 + */ + public int partySize; + + /** + * Maximum size of the player's party, lobby, or group. + *
Example: 5 + */ + public int partyMax; + + /** + * Unique hashed string for Spectate and Join. + * Required to enable match interactive buttons in the user's presence. + *
Example: "MmhuZToxMjMxMjM6cWl3amR3MWlqZA==" + * + *

Maximum: 128 characters + */ + public String matchSecret; + + /** + * Unique hashed string for Spectate button. + * This will enable the "Spectate" button on the user's presence if whitelisted. + *
Example: "MTIzNDV8MTIzNDV8MTMyNDU0" + * + *

Maximum: 128 characters + */ + public String joinSecret; + + /** + * Unique hashed string for chat invitations and Ask to Join. + * This will enable the "Ask to Join" button on the user's presence if whitelisted. + *
Example: "MTI4NzM0OjFpMmhuZToxMjMxMjM=" + * + *

Maximum: 128 characters + */ + public String spectateSecret; + + /** + * Marks the matchSecret as a game session with a specific beginning and end. + * Boolean value of 0 or 1. + *
Example: 1 + */ + public byte instance; + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (!(o instanceof DiscordRichPresence)) + return false; + DiscordRichPresence presence = (DiscordRichPresence) o; + return startTimestamp == presence.startTimestamp + && endTimestamp == presence.endTimestamp + && partySize == presence.partySize + && partyMax == presence.partyMax + && instance == presence.instance + && Objects.equals(state, presence.state) + && Objects.equals(details, presence.details) + && Objects.equals(largeImageKey, presence.largeImageKey) + && Objects.equals(largeImageText, presence.largeImageText) + && Objects.equals(smallImageKey, presence.smallImageKey) + && Objects.equals(smallImageText, presence.smallImageText) + && Objects.equals(partyId, presence.partyId) + && Objects.equals(matchSecret, presence.matchSecret) + && Objects.equals(joinSecret, presence.joinSecret) + && Objects.equals(spectateSecret, presence.spectateSecret); + } + + @Override + public int hashCode() + { + return Objects.hash(state, details, startTimestamp, endTimestamp, largeImageKey, largeImageText, smallImageKey, + smallImageText, partyId, partySize, partyMax, matchSecret, joinSecret, spectateSecret, instance); + } + + @Override + protected List getFieldOrder() + { + return FIELD_ORDER; + } +} diff --git a/src/main/java/club/minnced/discord/rpc/DiscordUser.java b/src/main/java/club/minnced/discord/rpc/DiscordUser.java new file mode 100644 index 00000000..7ebd572d --- /dev/null +++ b/src/main/java/club/minnced/discord/rpc/DiscordUser.java @@ -0,0 +1,101 @@ +/* + * Copyright 2016 - 2019 Florian Spieß and the java-discord-rpc contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package club.minnced.discord.rpc; + +import com.sun.jna.Structure; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/* +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordUser; + */ + +/** + * Struct binding for a discord join request. + */ +public class DiscordUser extends Structure +{ + private static final List FIELD_ORDER = Collections.unmodifiableList(Arrays.asList( + "userId", + "username", + "discriminator", + "avatar" + )); + + public DiscordUser(String encoding) { + super(); + setStringEncoding(encoding); + } + + public DiscordUser() { + this("UTF-8"); + } + + /** + * The userId for the user that requests to join + */ + public String userId; + + /** + * The username of the user that requests to join + */ + public String username; + + /** + * The discriminator of the user that requests to join + */ + public String discriminator; + + /** + * The avatar of the user that requests to join + */ + public String avatar; + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (!(o instanceof DiscordUser)) + return false; + DiscordUser that = (DiscordUser) o; + return Objects.equals(userId, that.userId) + && Objects.equals(username, that.username) + && Objects.equals(discriminator, that.discriminator) + && Objects.equals(avatar, that.avatar); + } + + @Override + public int hashCode() + { + return Objects.hash(userId, username, discriminator, avatar); + } + + @Override + protected List getFieldOrder() + { + return FIELD_ORDER; + } +} diff --git a/src/main/java/club/minnced/discord/rpc/package-info.java b/src/main/java/club/minnced/discord/rpc/package-info.java new file mode 100644 index 00000000..c77e41b7 --- /dev/null +++ b/src/main/java/club/minnced/discord/rpc/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2016 - 2019 Florian Spieß and the java-discord-rpc contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Java bindings for the official Discord RPC SDK. + */ +package club.minnced.discord.rpc; diff --git a/src/main/java/me/sdashb/kamiblue/DiscordKBlueRPC.java b/src/main/java/me/sdashb/kamiblue/DiscordKBlueRPC.java new file mode 100644 index 00000000..dde1528d --- /dev/null +++ b/src/main/java/me/sdashb/kamiblue/DiscordKBlueRPC.java @@ -0,0 +1,157 @@ +package me.sdashb.kamiblue; + +import me.zeroeightsix.kami.module.Module; +import me.zeroeightsix.kami.module.ModuleManager; +import me.zeroeightsix.kami.setting.Setting; +import me.zeroeightsix.kami.setting.Settings; + +// discord imports +import club.minnced.discord.rpc.*; +import com.google.common.hash.Hashing; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraftforge.fml.common.FMLLog; + +import java.nio.charset.StandardCharsets; +import java.util.Random; + +/** + * @author cookiedragon234 + * Updated by S-B99 on 28/10/19 + */ +@Module.Info(name = "DiscordRPC", category = Module.Category.MISC, description = "Discord Rich Presence") +public class DiscordKBlueRPC extends Module { + + //1//protected void onEnable() { + //1// + //1// if (isDisabled()) { + //1// this.disable(); + //1// return; + //1// } + //1//} + + private static final String APP_ID = "638403216278683661"; + private static final DiscordKBlueRPC rpc = DiscordKBlueRPC.INSTANCE; + + private static DiscordRichPresence presence = new DiscordRichPresence(); + + private static boolean hasStarted = false; + + public static boolean start() + { + FMLLog.log.info("Starting Discord RPC"); + + if(hasStarted) return false; + + hasStarted = true; + + DiscordEventHandlers handlers = new DiscordEventHandlers(); + + handlers.disconnected = (int var1, String var2) -> + { + System.out.println("Discord RPC disconnected, var1: " + String.valueOf(var1) + ", var2: " + var2); + }; + + rpc.Discord_Initialize(APP_ID, handlers, true, ""); + + presence.startTimestamp = System.currentTimeMillis() / 1000; + presence.details = "Main Menu"; + presence.state = "discord.gg/ncQkFKU"; + //presence.smallImageKey = "backdoored_logo"; + presence.largeImageKey = "backdoored_logo"; + //presence.spectateSecret = String.valueOf(new Random().nextInt((9000000 - 100000) + 1) + 100000); + //presence.joinSecret = String.valueOf(new Random().nextInt((9000000 - 100000) + 1) + 100000); + + rpc.Discord_UpdatePresence(presence); + + new Thread(() -> + { + while(!Thread.currentThread().isInterrupted()) + { + + try + { + // Run callbacks + rpc.Discord_RunCallbacks(); + + String details = ""; + String state = ""; + int players = 0; + int maxPlayers = 0; + + // If we're in singleplayer + if (Globals.mc.isIntegratedServerRunning()) + { + details = "Singleplayer"; + } + else + { + if (Globals.mc.getCurrentServerData() != null) + { + ServerData svr = Globals.mc.getCurrentServerData(); + if (!svr.serverIP.equals("")) + { + // If we're on multiplayer + details = "Multiplayer"; + state = svr.serverIP; + if(svr.populationInfo != null) + { + String[] popInfo = svr.populationInfo.split("/"); + if(popInfo.length > 2) + { + players = Integer.valueOf(popInfo[0]); + maxPlayers = Integer.valueOf(popInfo[1]); + } + } + + if(state.contains("2b2t.org")) + { + try + { + if(Backdoored.lastChat.startsWith("Position in queue: ")) + { + state = state + " " + Integer.parseInt(Backdoored.lastChat.substring(19)) + " in queue"; + } + } catch(Throwable e){ e.printStackTrace(); } + } + } + } + // If we're in the main menu + else + { + details = "Main Menu"; + state = "discord.gg/ncQkFKU"; + } + } + + if(!details.equals(presence.details) || !state.equals(presence.state)) + { + presence.startTimestamp = System.currentTimeMillis() / 1000; + } + + presence.details = details; + presence.state = state; + //presence.partySize = players; + //presence.partyMax = maxPlayers; + + /*if(players > 0) + { + presence.partyId = String.valueOf(new Random().nextInt((9000000 - 100000) + 1) + 100000); + }*/ + + rpc.Discord_UpdatePresence(presence); + } catch(Exception e){e.printStackTrace();} + + try + { + Thread.sleep(5000); + } + catch(InterruptedException e) + { + e.printStackTrace(); + } + } + }, "Discord-RPC-Callback-Handler").start(); + FMLLog.log.info("Discord RPC initialised succesfully"); + return true; + } +} diff --git a/src/main/java/me/sdashb/kamiblue/DiscordRPC.java b/src/main/java/me/sdashb/kamiblue/DiscordRPC.java deleted file mode 100644 index 276b292b..00000000 --- a/src/main/java/me/sdashb/kamiblue/DiscordRPC.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.sdashb.kamiblue; - -import me.zeroeightsix.kami.module.Module; -import me.zeroeightsix.kami.setting.Setting; -import me.zeroeightsix.kami.setting.Settings; - -//import club.minnced.discord.rpc.*; - -/** - * @author 086 - * Updated by S-B99 on 27/10/19 - */ -@Module.Info(name = "DiscordRPC", category = Module.Category.PLAYER, description = "Discord Rich Presence") -public class DiscordRPC extends Module { - -} \ No newline at end of file