13 Commits

Author SHA1 Message Date
7dc663a170 Static link crt 2017-11-09 14:24:30 -08:00
f872b4e49c fix event names and add ACTIVITY_JOIN_REQUEST (#10)
* fix event names and add ACTIVITY_JOIN_REQUEST

* Update hard-mode.md

* fix typo
2017-11-09 13:32:17 -08:00
ca5d70a5f9 Add more -Wflags 2017-11-09 13:08:05 -08:00
ee9c504d1c Change -Weverything to -Wall for more compilers 2017-11-09 13:08:05 -08:00
127eadcb89 Added VS2015 C++ redist dependency info 2017-11-07 09:59:04 -08:00
a7808a20ed Fix some sizes on join request strings. 2017-11-03 13:40:30 -07:00
3bdb88d918 Unity ajt fix 2017-11-02 11:59:45 -07:00
aa79c70bf9 Adding MIT license. 2017-11-01 15:25:14 -07:00
a089aab53e Update unity. 2017-10-17 13:34:03 -07:00
dafd85c39f Rename avatarUrl -> avatar for API consistency 2017-10-17 13:34:03 -07:00
b1d6a7c0fc Normalize white-space 2017-10-17 09:37:56 -07:00
e4b3ef63b7 Added request to join functionality 2017-10-17 09:37:56 -07:00
86ca320cb9 These should have been extern C. 2017-10-13 10:02:12 -07:00
12 changed files with 162 additions and 34 deletions

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright 2017 Discord, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -24,6 +24,8 @@ function.
Download a release package, extract it, add `/include` to your compile includes, `/lib` to your Download a release package, extract it, add `/include` to your compile includes, `/lib` to your
linker paths, and link with `discord-rpc`. linker paths, and link with `discord-rpc`.
Note that the release packages were compiled using Visual Studio 2015, so the [Visual C++ Redistributable for VS2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) will be a requirement for your game. If you wish to avoid this dependency, you should compile the libraries yourself using whatever dependencies are already in your game.
### From repo ### From repo
There's a [CMake](https://cmake.org/download/) file that should be able to generate the lib for There's a [CMake](https://cmake.org/download/) file that should be able to generate the lib for

View File

@ -56,7 +56,9 @@ Below is a full example of a `SET_ACTIVITY` command. Field restrictions like siz
## New RPC Events ## New RPC Events
The two new RPC events for Rich Presence power the ability to join and spectate your friends' games. First is the `GAME_JOIN` event: The three new RPC events for Rich Presence power the ability to join and spectate your friends' games.
First is the `ACTIVITY_JOIN` event:
```json ```json
{ {
@ -64,11 +66,11 @@ The two new RPC events for Rich Presence power the ability to join and spectate
"data": { "data": {
"secret": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f" "secret": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f"
}, },
"evnt": "GAME_JOIN" "evnt": "ACTIVITY_JOIN"
} }
``` ```
And second is the `GAME_SPECTATE` event: Second is the `ACTIVITY_SPECTATE` event:
```json ```json
{ {
@ -76,7 +78,25 @@ And second is the `GAME_SPECTATE` event:
"data": { "data": {
"secret": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0" "secret": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0"
}, },
"evnt": "GAME_SPECTATE" "evnt": "ACTIVITY_SPECTATE"
}
```
And third is the `ACTIVITY_JOIN_REQUEST` event:
```json
{
"cmd": "DISPATCH",
"data": {
"user": {
"id": "53908232506183680",
"username": "Mason",
"discriminator": "1337",
"avatar": "a_bab14f271d565501444b2ca3be944b25"
},
"secret": "e459ca99273f59909dd16ed97865f3ad"
},
"evnt": "ACTIVITY_JOIN_REQUEST"
} }
``` ```
@ -85,7 +105,7 @@ In order to receive these events, you need to [subscribe](https://discordapp.com
```json ```json
{ {
"nonce": "be9a6de3-31d0-4767-a8e9-4818c5690015", "nonce": "be9a6de3-31d0-4767-a8e9-4818c5690015",
"evt": "GAME_JOIN", "evt": "ACTIVITY_JOIN",
"cmd": "SUBSCRIBE" "cmd": "SUBSCRIBE"
} }
``` ```
@ -93,7 +113,15 @@ In order to receive these events, you need to [subscribe](https://discordapp.com
```json ```json
{ {
"nonce": "ae9qdde3-31d0-8989-a8e9-dnakwy174he", "nonce": "ae9qdde3-31d0-8989-a8e9-dnakwy174he",
"evt": "GAME_SPECTATE", "evt": "ACTIVITY_SPECTATE",
"cmd": "SUBSCRIBE"
}
```
```json
{
"nonce": "5dc0c062-98c6-47a0-8922-bbb52e9d6afa",
"evt": "ACTIVITY_JOIN_REQUEST",
"cmd": "SUBSCRIBE" "cmd": "SUBSCRIBE"
} }
``` ```

View File

@ -1,6 +1,16 @@
using UnityEngine; using UnityEngine;
public class DiscordController : MonoBehaviour { [System.Serializable]
public class DiscordJoinEvent : UnityEngine.Events.UnityEvent<string> { }
[System.Serializable]
public class DiscordSpectateEvent : UnityEngine.Events.UnityEvent<string> { }
[System.Serializable]
public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.JoinRequest> { }
public class DiscordController : MonoBehaviour
{
public DiscordRpc.RichPresence presence; public DiscordRpc.RichPresence presence;
public string applicationId; public string applicationId;
public string optionalSteamId; public string optionalSteamId;
@ -8,6 +18,9 @@ public class DiscordController : MonoBehaviour {
public int clickCounter; public int clickCounter;
public UnityEngine.Events.UnityEvent onConnect; public UnityEngine.Events.UnityEvent onConnect;
public UnityEngine.Events.UnityEvent onDisconnect; public UnityEngine.Events.UnityEvent onDisconnect;
public DiscordJoinEvent onJoin;
public DiscordJoinEvent onSpectate;
public DiscordJoinRequestEvent onJoinRequest;
DiscordRpc.EventHandlers handlers; DiscordRpc.EventHandlers handlers;
@ -45,18 +58,29 @@ public class DiscordController : MonoBehaviour {
{ {
++callbackCalls; ++callbackCalls;
Debug.Log(string.Format("Discord: join ({0})", secret)); Debug.Log(string.Format("Discord: join ({0})", secret));
onJoin.Invoke(secret);
} }
public void SpectateCallback(string secret) public void SpectateCallback(string secret)
{ {
++callbackCalls; ++callbackCalls;
Debug.Log(string.Format("Discord: spectate ({0})", secret)); Debug.Log(string.Format("Discord: spectate ({0})", secret));
onSpectate.Invoke(secret);
} }
void Start () { public void RequestCallback(DiscordRpc.JoinRequest request)
{
++callbackCalls;
Debug.Log(string.Format("Discord: join request {0}: {1}", request.username, request.userId));
onJoinRequest.Invoke(request);
} }
void Update () { void Start()
{
}
void Update()
{
DiscordRpc.RunCallbacks(); DiscordRpc.RunCallbacks();
} }
@ -71,6 +95,7 @@ public class DiscordController : MonoBehaviour {
handlers.errorCallback += ErrorCallback; handlers.errorCallback += ErrorCallback;
handlers.joinCallback += JoinCallback; handlers.joinCallback += JoinCallback;
handlers.spectateCallback += SpectateCallback; handlers.spectateCallback += SpectateCallback;
handlers.requestCallback += RequestCallback;
DiscordRpc.Initialize(applicationId, ref handlers, true, optionalSteamId); DiscordRpc.Initialize(applicationId, ref handlers, true, optionalSteamId);
} }

View File

@ -17,6 +17,9 @@ public class DiscordRpc
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SpectateCallback(string secret); public delegate void SpectateCallback(string secret);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void RequestCallback(JoinRequest request);
public struct EventHandlers public struct EventHandlers
{ {
public ReadyCallback readyCallback; public ReadyCallback readyCallback;
@ -24,6 +27,7 @@ public class DiscordRpc
public ErrorCallback errorCallback; public ErrorCallback errorCallback;
public JoinCallback joinCallback; public JoinCallback joinCallback;
public SpectateCallback spectateCallback; public SpectateCallback spectateCallback;
public RequestCallback requestCallback;
} }
[System.Serializable] [System.Serializable]
@ -46,6 +50,21 @@ public class DiscordRpc
public bool instance; public bool instance;
} }
[System.Serializable]
public struct JoinRequest
{
public string userId;
public string username;
public string avatar;
}
public enum Reply
{
No = 0,
Yes = 1,
Ignore = 2
}
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)] [DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)]
public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId); public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);
@ -57,5 +76,8 @@ public class DiscordRpc
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)] [DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void UpdatePresence(ref RichPresence presence); public static extern void UpdatePresence(ref RichPresence presence);
[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
public static extern void Respond(string userId, Reply reply);
} }

View File

@ -703,6 +703,21 @@ MonoBehaviour:
m_CallState: 2 m_CallState: 2
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral, m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null PublicKeyToken=null
onJoin:
m_PersistentCalls:
m_Calls: []
m_TypeName: DiscordJoinEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
onSpectate:
m_PersistentCalls:
m_Calls: []
m_TypeName: DiscordJoinEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
onJoinRequest:
m_PersistentCalls:
m_Calls: []
m_TypeName: DiscordJoinRequestEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
--- !u!4 &1929635630 --- !u!4 &1929635630
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@ -1 +1 @@
m_EditorVersion: 2017.1.0f3 m_EditorVersion: 2017.1.1f1

View File

@ -83,7 +83,7 @@ static void handleDiscordJoinRequest(const DiscordJoinRequest* request)
char yn[4]; char yn[4];
printf("\nDiscord: join request from %s - %s - %s\n", printf("\nDiscord: join request from %s - %s - %s\n",
request->username, request->username,
request->avatarUrl, request->avatar,
request->userId); request->userId);
do { do {
printf("Accept? (y/n)"); printf("Accept? (y/n)");

View File

@ -42,9 +42,9 @@ typedef struct DiscordRichPresence {
} DiscordRichPresence; } DiscordRichPresence;
typedef struct DiscordJoinRequest { typedef struct DiscordJoinRequest {
char userId[24]; const char* userId;
char username[48]; const char* username;
char avatarUrl[128]; const char* avatar;
} DiscordJoinRequest; } DiscordJoinRequest;
typedef struct DiscordEventHandlers { typedef struct DiscordEventHandlers {

View File

@ -30,6 +30,7 @@ if(WIN32)
set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp) set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp)
add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC}) add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC})
target_compile_options(discord-rpc PRIVATE /EHsc target_compile_options(discord-rpc PRIVATE /EHsc
/MT
/Wall /Wall
/wd4100 # unreferenced formal parameter /wd4100 # unreferenced formal parameter
/wd4514 # unreferenced inline /wd4514 # unreferenced inline
@ -60,7 +61,10 @@ if(UNIX)
target_link_libraries(discord-rpc PUBLIC pthread) target_link_libraries(discord-rpc PUBLIC pthread)
target_compile_options(discord-rpc PRIVATE target_compile_options(discord-rpc PRIVATE
-g -g
-Weverything -Wall
-Wextra
-Wpedantic
-Werror
-Wno-unknown-pragmas # pragma push thing doesn't work on clang -Wno-unknown-pragmas # pragma push thing doesn't work on clang
-Wno-old-style-cast # it's fine -Wno-old-style-cast # it's fine
-Wno-c++98-compat # that was almost 2 decades ago -Wno-c++98-compat # that was almost 2 decades ago

View File

@ -2,9 +2,9 @@
#include "backoff.h" #include "backoff.h"
#include "discord_register.h" #include "discord_register.h"
#include "msg_queue.h"
#include "rpc_connection.h" #include "rpc_connection.h"
#include "serialization.h" #include "serialization.h"
#include "msg_queue.h"
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
@ -32,6 +32,18 @@ struct QueuedMessage {
} }
}; };
struct JoinRequest {
// snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null
// terminator = 21
char userId[22];
// 32 unicode glyphs is max name size => 4 bytes per glyph in the worst case, +1 for null
// terminator = 129
char username[130];
// optional 'a_' + md5 hex digest (32 bytes) + null terminator = 35
char avatar[36];
// +1 on each because: it's even / I'm paranoid
};
static RpcConnection* Connection{nullptr}; static RpcConnection* Connection{nullptr};
static DiscordEventHandlers Handlers{}; static DiscordEventHandlers Handlers{};
static std::atomic_bool WasJustConnected{false}; static std::atomic_bool WasJustConnected{false};
@ -48,7 +60,7 @@ static char LastDisconnectErrorMessage[256];
static std::mutex PresenceMutex; static std::mutex PresenceMutex;
static QueuedMessage QueuedPresence{}; static QueuedMessage QueuedPresence{};
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue; static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
static MsgQueue<DiscordJoinRequest, JoinQueueSize> JoinAskQueue; static MsgQueue<JoinRequest, JoinQueueSize> JoinAskQueue;
// We want to auto connect, and retry on failure, but not as fast as possible. This does expoential // We want to auto connect, and retry on failure, but not as fast as possible. This does expoential
// backoff from 0.5 seconds to 1 minute // backoff from 0.5 seconds to 1 minute
@ -71,7 +83,7 @@ static void UpdateReconnectTime()
} }
#ifdef DISCORD_DISABLE_IO_THREAD #ifdef DISCORD_DISABLE_IO_THREAD
DISCORD_EXPORT void Discord_UpdateConnection(void) extern "C" DISCORD_EXPORT void Discord_UpdateConnection(void)
#else #else
static void Discord_UpdateConnection(void) static void Discord_UpdateConnection(void)
#endif #endif
@ -136,16 +148,16 @@ static void Discord_UpdateConnection(void)
auto user = GetObjMember(data, "user"); auto user = GetObjMember(data, "user");
auto userId = GetStrMember(user, "id"); auto userId = GetStrMember(user, "id");
auto username = GetStrMember(user, "username"); auto username = GetStrMember(user, "username");
auto avatarUrl = GetStrMember(user, "avatar"); auto avatar = GetStrMember(user, "avatar");
auto joinReq = JoinAskQueue.GetNextAddMessage(); auto joinReq = JoinAskQueue.GetNextAddMessage();
if (userId && username && joinReq) { if (userId && username && joinReq) {
StringCopy(joinReq->userId, userId); StringCopy(joinReq->userId, userId);
StringCopy(joinReq->username, username); StringCopy(joinReq->username, username);
if (avatarUrl) { if (avatar) {
StringCopy(joinReq->avatarUrl, avatarUrl); StringCopy(joinReq->avatar, avatar);
} }
else { else {
joinReq->avatarUrl[0] = 0; joinReq->avatar[0] = 0;
} }
JoinAskQueue.CommitAdd(); JoinAskQueue.CommitAdd();
} }
@ -210,7 +222,7 @@ static bool RegisterForEvent(const char* evtName)
return false; return false;
} }
DISCORD_EXPORT void Discord_Initialize(const char* applicationId, extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers, DiscordEventHandlers* handlers,
int autoRegister, int autoRegister,
const char* optionalSteamId) const char* optionalSteamId)
@ -267,7 +279,7 @@ DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
#endif #endif
} }
DISCORD_EXPORT void Discord_Shutdown() extern "C" DISCORD_EXPORT void Discord_Shutdown()
{ {
if (!Connection) { if (!Connection) {
return; return;
@ -285,7 +297,7 @@ DISCORD_EXPORT void Discord_Shutdown()
RpcConnection::Destroy(Connection); RpcConnection::Destroy(Connection);
} }
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence) extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence)
{ {
PresenceMutex.lock(); PresenceMutex.lock();
QueuedPresence.length = JsonWriteRichPresenceObj( QueuedPresence.length = JsonWriteRichPresenceObj(
@ -294,7 +306,7 @@ DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence)
SignalIOActivity(); SignalIOActivity();
} }
DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int reply) extern "C" DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int reply)
{ {
// if we are not connected, let's not batch up stale messages for later // if we are not connected, let's not batch up stale messages for later
if (!Connection || !Connection->IsOpen()) { if (!Connection || !Connection->IsOpen()) {
@ -309,7 +321,7 @@ DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int
} }
} }
DISCORD_EXPORT void Discord_RunCallbacks() extern "C" DISCORD_EXPORT void Discord_RunCallbacks()
{ {
// Note on some weirdness: internally we might connect, get other signals, disconnect any number // Note on some weirdness: internally we might connect, get other signals, disconnect any number
// of times inbetween calls here. Externally, we want the sequence to seem sane, so any other // of times inbetween calls here. Externally, we want the sequence to seem sane, so any other
@ -353,7 +365,8 @@ DISCORD_EXPORT void Discord_RunCallbacks()
while (JoinAskQueue.HavePendingSends()) { while (JoinAskQueue.HavePendingSends()) {
auto req = JoinAskQueue.GetNextSendMessage(); auto req = JoinAskQueue.GetNextSendMessage();
if (Handlers.joinRequest) { if (Handlers.joinRequest) {
Handlers.joinRequest(req); DiscordJoinRequest djr{req->userId, req->username, req->avatar};
Handlers.joinRequest(&djr);
} }
JoinAskQueue.CommitSend(); JoinAskQueue.CommitSend();
} }