Discord Rich Presence

Overview

The discord utility of LabyMod allows users to utilize the Discord Rich Presence feature. The user can then share his current server and game (for example the specific lobby he is on) using Discord.

example of rpc ui in discord

How it works

LabyMod is directly connected to the Discord API and serves as a bridge between the Minecraft server and Discord. The communication is done using the plugin message channel LMC with the key discord_rpc.

overview of rpc infrastructure

Requirements

The "secrets" explained

  • spectateSecret: This is the secret that a user needs to spectate a game.
  • joinSecret: This is the secret that a user needs to join a game
  • matchSecret: An id that is unique for each game on your server. Discord uses this to determine whether people are playing together

Other clients will use the spectateSecret and the joinSecret to spectate or join their friends game, so they need to be unique for each user and should be valid only once (to prevent spamming)

It is okay to just use UUID's as the secrets or other randomly generated strings.

Send the secrets to the client

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// The LabyMod client sends a "INFO" message in the "LMC" channel on join
if ( messageKey.equals( "INFO" ) ) {
   JsonObject obj = new JsonObject();

   // Enter your server IP here (The client uses this domain to join the server)
   String domain = ".play.example.com";

   // Add all secrets
   addSecret( obj, "hasMatchSecret", "matchSecret", user.getGameServerId(), domain );
   addSecret( obj, "hasSpectateSecret", "spectateSecret", user.getSpectateSecret(), domain );
   addSecret( obj, "hasJoinSecret", "joinSecret", user.getJoinSecret(), domain );

   LabyModPlugin.getInstance().sendServerMessage( player, "discord_rpc", obj );
}
/**
 * Add a secret key to the json object
 * @param jsonObject json object
 * @param hasKey has key name
 * @param key key name
 * @param secret the secret key
 * @return json object
 */
public JsonObject addSecret( JsonObject jsonObject, String hasKey, String key, UUID secret, String domain) {
    jsonObject.addProperty( hasKey, true );
    jsonObject.addProperty( key, secret.toString() + ":" + domain );
    return jsonObject;
}

Listen on incoming secret keys (User redeems a key)

Discord is distributing the secrets to the friends of the users, and when a friend chooses to spectate his friend through Discord, he is submitting that secret to the gameserver himself to authenticate itself. This means the server has to remember the active secrets it sends out so it can match them to the player and action they correspond to, so the friend can either join the game, the party or start spectating.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// User wants to join a friend -> he sends the secret to the server using the pluginchannel as before
if ( messageKey.equals( "discord_rpc" ) ) {
    JsonObject obj = jsonElement.getAsJsonObject();

    // Redeem cooldown (To prevent abuse) [optional]
    if ( user.getLastDiscordSecretRedeem() + 1000 > System.currentTimeMillis() ) {
        return;
    }
    user.setLastDiscordSecretRedeem( System.currentTimeMillis() );

    // User has spectate secret
    if ( obj.has( "spectateSecret" ) ) {
        // Do make sure there is proper error handling in place. Client may send secrets that are not in UUID format!
        UUID spectateSecret = UUID.fromString( obj.get( "spectateSecret" ).getAsString() );

        // Get corresponding player, it may be necessary to replace this with a database on multiserver networks
        User secretOwner = null;
        for ( User user : <ALL_USERS_ON_NETWORK> ) {
            UUID secret = user.getSpectateSecret();
            if ( secret != null && secret.equals( spectateSecret ) ) {
                secretOwner = user;
                break;
            }
        }

        // Move player to secret owner
        if ( secretOwner != null && secretOwner.isOnline() ) {
            // TODO: Move the 'user' to the 'secretOwner' on the server

            // Create new secret (To prevent abuse)
            secretOwner.createNewSpectateSecret();

            // Send the new secret key to the owner (To prevent abuse)
            String domain = "play.example.com";
            LabyModPlugin.getInstance().sendServerMessage( secretOwner, "discord_rpc", addSecret( new JsonObject(), "hasSpectateSecret", "spectateSecret", secretOwner.getSpectateSecret(), domain ) );
        }
    }

    // User has join secret for the party
    if ( obj.has( "joinSecret" ) ) {
        UUID joinSecret = UUID.fromString( obj.get( "joinSecret" ).getAsString() );

        // Get secret owner
        User secretOwner = null;
        for ( User user : <ALL_USERS_ON_NETWORK> ) {
            UUID secret = user.getJoinSecret();
            if ( secret != null && secret.equals( joinSecret ) ) {
                secretOwner = user;
                break;
            }
        }

        // Move player to secret owner
        if ( secretOwner != null && secretOwner.isOnline() ) {
            // TODO: Move the 'user' to the 'secretOwner' on the server

            // Create new secret (To prevent abuse)
            secretOwner.createNewJoinSecret();

            // Send the new secret key to the owner (To prevent abuse)
            String domain = "play.example.com";
            LabyModPlugin.getInstance().sendServerMessage( secretOwner, "discord_rpc", addSecret( new JsonObject(), "hasJoinSecret", "joinSecret", secretOwner.getJoinSecret(), domain ) );

            // TODO: Add 'user' to party of 'secretOwner'
        }
    }
}

Display the party in Discord

You can send also a party invitation to other Discord users with the party information.

discord parties

Method to update the party info:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
private void updatePartyInfo( Player player, boolean hasParty, UUID partyLeaderUUID, int partySize, int maxPartyMembers ) {
    String domain = "play.example.com";

    // Only for discord rpc users (Just set an flag to true when a LabyMod user joins the network) [optional]
    if ( !user.isDiscordRPCActive() )
        return;

    // Create party json object
    JsonObject obj = new JsonObject();
    obj.addProperty( "hasParty", hasParty );

    if ( hasParty ) {
        obj.addProperty( "partyId", partyLeaderUUID.toString() + ":" + domain );
        obj.addProperty( "party_size", partySize );
        obj.addProperty( "party_max", maxPartyMembers );
    }

    // Send to user
    LabyModPlugin.getInstance().sendServerMessage( player, "discord_rpc", obj );
}