14 Commits

Author SHA1 Message Date
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
6cdc830544 Missed a couple wanrings. Build example dll 2017-10-12 16:14:16 -07:00
990c8d4be6 Enable warnings, clang edition. 2017-10-12 16:08:08 -07:00
6fa00223ad Enable all warnings, turn the noisy ones back off, fix the others. 2017-10-12 15:39:31 -07:00
39ff0bf3e4 Ask to join (#4)
This adds a new callback `joinRequest` and a new API function `Discord_Respond` to reply to it.
2017-10-12 13:06:55 -07:00
25b6f1dcde Miscellaneous Unity Fixes (#3)
* Use simplified attribute names and ensure function calls are made with CallingConvention.Cdecl

* Remove unused imports
2017-09-28 08:15:15 -07:00
e7cdfaa64e I knew I shouldn't have checked in this dll 2017-09-21 14:04:46 -07:00
a5a56bcf68 Also do registering on OSX and Linux. 2017-09-14 08:59:32 -07:00
24 changed files with 613 additions and 117 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.

9
build.py Normal file → Executable file
View File

@ -1,3 +1,5 @@
#!/usr/bin/env python
import click import click
import os import os
import subprocess import subprocess
@ -45,7 +47,7 @@ def build_lib(build_name, generator, options):
def create_archive(): def create_archive():
archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc.zip') archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc-%s.zip' % sys.platform)
archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED) archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED)
archive_src_base_path = os.path.join(SCRIPT_PATH, 'builds', 'install') archive_src_base_path = os.path.join(SCRIPT_PATH, 'builds', 'install')
archive_dst_base_path = 'discord-rpc' archive_dst_base_path = 'discord-rpc'
@ -64,6 +66,8 @@ def main(clean):
if clean: if clean:
shutil.rmtree('builds', ignore_errors=True) shutil.rmtree('builds', ignore_errors=True)
mkdir_p('builds')
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
generator32 = 'Visual Studio 14 2015' generator32 = 'Visual Studio 14 2015'
generator64 = 'Visual Studio 14 2015 Win64' generator64 = 'Visual Studio 14 2015 Win64'
@ -79,6 +83,9 @@ def main(clean):
shutil.copy(src_dll, dst_dll) shutil.copy(src_dll, dst_dll)
dst_dll = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc', 'Binaries', 'ThirdParty', 'discordrpcLibrary', 'Win64', 'discord-rpc.dll') dst_dll = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc', 'Binaries', 'ThirdParty', 'discordrpcLibrary', 'Win64', 'discord-rpc.dll')
shutil.copy(src_dll, dst_dll) shutil.copy(src_dll, dst_dll)
elif sys.platform == 'darwin':
build_lib('osx-static', None, {})
build_lib('osx-dynamic', None, {'BUILD_DYNAMIC_LIB': True})
create_archive() create_archive()

View File

@ -10,7 +10,7 @@ By committing to an RPC-only integration, you decide to forego the work our libr
## Application Protocol Registration ## Application Protocol Registration
One thing that cannot be explicitly done over RPC is registering an application protocol for your game. If you choose to do an RPC-only implementation, you will have to register your application protocol yourself in the format of `discord-[your_app_id]://`. You can use `Discord_Register()` from `src/discord-register.cpp` as a good example of how to properly register an application protocol for use with Discord. One thing that cannot be explicitly done over RPC is registering an application protocol for your game. If you choose to do an RPC-only implementation, you will have to register your application protocol yourself in the format of `discord-[your_app_id]://`. You can use `Discord_Register()` as a good(?) example of how to properly register an application protocol for use with Discord. For OSX and Linux it is probably simpler to handle the protocol registration as part of your install/packaging.
## New RPC Command ## New RPC Command

View File

@ -1,8 +1,16 @@
using System.Collections; using UnityEngine;
using System.Collections.Generic;
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;
@ -10,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;
@ -47,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();
} }
@ -73,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

@ -2,21 +2,24 @@
public class DiscordRpc public class DiscordRpc
{ {
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ReadyCallback(); public delegate void ReadyCallback();
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisconnectedCallback(int errorCode, string message); public delegate void DisconnectedCallback(int errorCode, string message);
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ErrorCallback(int errorCode, string message); public delegate void ErrorCallback(int errorCode, string message);
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void JoinCallback(string secret); public delegate void JoinCallback(string secret);
[UnmanagedFunctionPointerAttribute(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,16 +50,34 @@ public class DiscordRpc
public bool instance; public bool instance;
} }
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize")] [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)]
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);
[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown")] [DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)]
public static extern void Shutdown(); public static extern void Shutdown();
[DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks")] [DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)]
public static extern void RunCallbacks(); public static extern void RunCallbacks();
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence")] [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

@ -1,5 +1,13 @@
include_directories(${PROJECT_SOURCE_DIR}/include) include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(send-presence send-presence.c) add_executable(
send-presence
MACOSX_BUNDLE
send-presence.c
)
set_target_properties(send-presence PROPERTIES
MACOSX_BUNDLE_BUNDLE_NAME "Send Presence"
MACOSX_BUNDLE_GUI_IDENTIFIER "com.discordapp.examples.send-presence"
)
target_link_libraries(send-presence discord-rpc) target_link_libraries(send-presence discord-rpc)
install( install(
@ -7,4 +15,7 @@ install(
RUNTIME RUNTIME
DESTINATION "bin" DESTINATION "bin"
CONFIGURATIONS Release CONFIGURATIONS Release
BUNDLE
DESTINATION "bin"
CONFIGURATIONS Release
) )

View File

@ -15,6 +15,21 @@ static const char* APPLICATION_ID = "345229890980937739";
static int FrustrationLevel = 0; static int FrustrationLevel = 0;
static int64_t StartTime; static int64_t StartTime;
static int prompt(char* line, size_t size)
{
int res;
char* nl;
printf("\n> ");
fflush(stdout);
res = fgets(line, (int)size, stdin) ? 1 : 0;
line[size - 1] = 0;
nl = strchr(line, '\n');
if (nl) {
*nl = 0;
}
return res;
}
static void updateDiscordPresence() static void updateDiscordPresence()
{ {
char buffer[256]; char buffer[256];
@ -62,19 +77,37 @@ static void handleDiscordSpectate(const char* secret)
printf("\nDiscord: spectate (%s)\n", secret); printf("\nDiscord: spectate (%s)\n", secret);
} }
static int prompt(char* line, size_t size) static void handleDiscordJoinRequest(const DiscordJoinRequest* request)
{ {
int res; int response = -1;
char* nl; char yn[4];
printf("\n> "); printf("\nDiscord: join request from %s - %s - %s\n",
fflush(stdout); request->username,
res = fgets(line, (int)size, stdin) ? 1 : 0; request->avatar,
line[size - 1] = 0; request->userId);
nl = strchr(line, '\n'); do {
if (nl) { printf("Accept? (y/n)");
*nl = 0; if (!prompt(yn, sizeof(yn))) {
break;
}
if (!yn[0]) {
continue;
}
if (yn[0] == 'y') {
response = DISCORD_REPLY_YES;
break;
}
if (yn[0] == 'n') {
response = DISCORD_REPLY_NO;
break;
}
} while (1);
if (response != -1) {
Discord_Respond(request->userId, response);
} }
return res;
} }
static void discordInit() static void discordInit()
@ -86,6 +119,7 @@ static void discordInit()
handlers.errored = handleDiscordError; handlers.errored = handleDiscordError;
handlers.joinGame = handleDiscordJoin; handlers.joinGame = handleDiscordJoin;
handlers.spectateGame = handleDiscordSpectate; handlers.spectateGame = handleDiscordSpectate;
handlers.joinRequest = handleDiscordJoinRequest;
Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL); Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL);
} }

View File

@ -41,30 +41,43 @@ typedef struct DiscordRichPresence {
int8_t instance; int8_t instance;
} DiscordRichPresence; } DiscordRichPresence;
typedef struct DiscordJoinRequest {
const char* userId;
const char* username;
const char* avatar;
} DiscordJoinRequest;
typedef struct DiscordEventHandlers { typedef struct DiscordEventHandlers {
void (*ready)(); void (*ready)();
void (*disconnected)(int errorCode, const char* message); void (*disconnected)(int errorCode, const char* message);
void (*errored)(int errorCode, const char* message); void (*errored)(int errorCode, const char* message);
void (*joinGame)(const char* joinSecret); void (*joinGame)(const char* joinSecret);
void (*spectateGame)(const char* spectateSecret); void (*spectateGame)(const char* spectateSecret);
void (*joinRequest)(const DiscordJoinRequest* request);
} DiscordEventHandlers; } DiscordEventHandlers;
#define DISCORD_REPLY_NO 0
#define DISCORD_REPLY_YES 1
#define DISCORD_REPLY_IGNORE 2
DISCORD_EXPORT void Discord_Initialize(const char* applicationId, DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers, DiscordEventHandlers* handlers,
int autoRegister, int autoRegister,
const char* optionalSteamId); const char* optionalSteamId);
DISCORD_EXPORT void Discord_Shutdown(); DISCORD_EXPORT void Discord_Shutdown(void);
/* checks for incoming messages, dispatches callbacks */ /* checks for incoming messages, dispatches callbacks */
DISCORD_EXPORT void Discord_RunCallbacks(); DISCORD_EXPORT void Discord_RunCallbacks(void);
/* If you disable the lib starting its own io thread, you'll need to call this from your own */ /* If you disable the lib starting its own io thread, you'll need to call this from your own */
#ifdef DISCORD_DISABLE_IO_THREAD #ifdef DISCORD_DISABLE_IO_THREAD
DISCORD_EXPORT void Discord_UpdateConnection(); DISCORD_EXPORT void Discord_UpdateConnection(void);
#endif #endif
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */
#endif #endif

View File

@ -6,32 +6,76 @@ option(BUILD_DYNAMIC_LIB "Build library as a DLL" OFF)
set(BASE_RPC_SRC set(BASE_RPC_SRC
${PROJECT_SOURCE_DIR}/include/discord-rpc.h ${PROJECT_SOURCE_DIR}/include/discord-rpc.h
discord-rpc.cpp discord-rpc.cpp
discord-register.h discord_register.h
discord-register.cpp
rpc_connection.h rpc_connection.h
rpc_connection.cpp rpc_connection.cpp
serialization.h serialization.h
serialization.cpp serialization.cpp
connection.h connection.h
backoff.h backoff.h
msg_queue.h
) )
if (${BUILD_DYNAMIC_LIB}) if (${BUILD_DYNAMIC_LIB})
set(RPC_LIBRARY_TYPE SHARED) set(RPC_LIBRARY_TYPE SHARED)
set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp) if(WIN32)
set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp)
endif(WIN32)
else(${BUILD_DYNAMIC_LIB}) else(${BUILD_DYNAMIC_LIB})
set(RPC_LIBRARY_TYPE STATIC) set(RPC_LIBRARY_TYPE STATIC)
endif(${BUILD_DYNAMIC_LIB}) endif(${BUILD_DYNAMIC_LIB})
if(WIN32) if(WIN32)
add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC} connection_win.cpp) add_definitions(-DDISCORD_WINDOWS)
target_compile_options(discord-rpc PRIVATE /W4) set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp)
add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC})
target_compile_options(discord-rpc PRIVATE /EHsc
/Wall
/wd4100 # unreferenced formal parameter
/wd4514 # unreferenced inline
/wd4625 # copy constructor deleted
/wd5026 # move constructor deleted
/wd4626 # move assignment operator deleted
/wd4668 # not defined preprocessor macro
/wd4710 # function not inlined
/wd4711 # function was inlined
/wd4820 # structure padding
/wd4946 # reinterpret_cast used between related classes
/wd5027 # move assignment operator was implicitly defined as deleted
)
endif(WIN32) endif(WIN32)
if(UNIX) if(UNIX)
add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC} connection_unix.cpp) set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_unix.cpp)
if (APPLE)
add_definitions(-DDISCORD_OSX)
set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_osx.m)
else (APPLE)
add_definitions(-DDISCORD_LINUX)
set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_linux.cpp)
endif(APPLE)
add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC})
target_link_libraries(discord-rpc PUBLIC pthread) target_link_libraries(discord-rpc PUBLIC pthread)
target_compile_options(discord-rpc PRIVATE -g -Wall -std=c++14) target_compile_options(discord-rpc PRIVATE
-g
-Weverything
-Wno-unknown-pragmas # pragma push thing doesn't work on clang
-Wno-old-style-cast # it's fine
-Wno-c++98-compat # that was almost 2 decades ago
-Wno-c++98-compat-pedantic
-Wno-missing-noreturn
-Wno-padded # structure padding
-Wno-covered-switch-default
-Wno-exit-time-destructors # not sure about these
-Wno-global-constructors
)
target_compile_options(discord-rpc PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-std=c++14>)
if (APPLE)
target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
endif (APPLE)
endif(UNIX) endif(UNIX)
target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include) target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include)

View File

@ -20,7 +20,7 @@ struct Backoff {
, maxAmount(max) , maxAmount(max)
, current(min) , current(min)
, fails(0) , fails(0)
, randGenerator(time(0)) , randGenerator((uint64_t)time(0))
{ {
} }

View File

@ -111,7 +111,7 @@ bool BaseConnection::Read(void* data, size_t length)
return false; return false;
} }
int res = recv(self->sock, data, length, MsgFlags); int res = (int)recv(self->sock, data, length, MsgFlags);
if (res < 0) { if (res < 0) {
if (errno == EAGAIN) { if (errno == EAGAIN) {
return false; return false;

View File

@ -9,7 +9,7 @@
int GetProcessId() int GetProcessId()
{ {
return ::GetCurrentProcessId(); return (int)::GetCurrentProcessId();
} }
struct BaseConnectionWin : public BaseConnection { struct BaseConnectionWin : public BaseConnection {

View File

@ -1,7 +1,8 @@
#include "discord-rpc.h" #include "discord-rpc.h"
#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"
@ -16,6 +17,7 @@
constexpr size_t MaxMessageSize{16 * 1024}; constexpr size_t MaxMessageSize{16 * 1024};
constexpr size_t MessageQueueSize{8}; constexpr size_t MessageQueueSize{8};
constexpr size_t JoinQueueSize{8};
struct QueuedMessage { struct QueuedMessage {
size_t length; size_t length;
@ -30,6 +32,12 @@ struct QueuedMessage {
} }
}; };
struct JoinRequest {
char userId[24];
char username[48];
char avatar[128];
};
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};
@ -45,10 +53,9 @@ static int LastDisconnectErrorCode{0};
static char LastDisconnectErrorMessage[256]; static char LastDisconnectErrorMessage[256];
static std::mutex PresenceMutex; static std::mutex PresenceMutex;
static QueuedMessage QueuedPresence{}; static QueuedMessage QueuedPresence{};
static QueuedMessage SendQueue[MessageQueueSize]{}; static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
static std::atomic_uint SendQueueNextAdd{0}; static MsgQueue<JoinRequest, JoinQueueSize> JoinAskQueue;
static std::atomic_uint SendQueueNextSend{0};
static std::atomic_uint SendQueuePendingSends{0};
// 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
static Backoff ReconnectTimeMs(500, 60 * 1000); static Backoff ReconnectTimeMs(500, 60 * 1000);
@ -69,31 +76,11 @@ static void UpdateReconnectTime()
std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()}; std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()};
} }
static QueuedMessage* SendQueueGetNextAddMessage() #ifdef DISCORD_DISABLE_IO_THREAD
{ extern "C" DISCORD_EXPORT void Discord_UpdateConnection(void)
// if we are not connected, let's not batch up stale messages for later #else
if (!Connection || !Connection->IsOpen()) { static void Discord_UpdateConnection(void)
return nullptr; #endif
}
// if we are falling behind, bail
if (SendQueuePendingSends.load() >= MessageQueueSize) {
return nullptr;
}
auto index = (SendQueueNextAdd++) % MessageQueueSize;
return &SendQueue[index];
}
static QueuedMessage* SendQueueGetNextSendMessage()
{
auto index = (SendQueueNextSend++) % MessageQueueSize;
return &SendQueue[index];
}
static void SendQueueCommitMessage()
{
SendQueuePendingSends++;
}
extern "C" void Discord_UpdateConnection()
{ {
if (!Connection) { if (!Connection) {
return; return;
@ -134,7 +121,7 @@ extern "C" void Discord_UpdateConnection()
continue; continue;
} }
if (strcmp(evtName, "GAME_JOIN") == 0) { if (strcmp(evtName, "ACTIVITY_JOIN") == 0) {
auto data = GetObjMember(&message, "data"); auto data = GetObjMember(&message, "data");
auto secret = GetStrMember(data, "secret"); auto secret = GetStrMember(data, "secret");
if (secret) { if (secret) {
@ -142,7 +129,7 @@ extern "C" void Discord_UpdateConnection()
WasJoinGame.store(true); WasJoinGame.store(true);
} }
} }
else if (strcmp(evtName, "GAME_SPECTATE") == 0) { else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) {
auto data = GetObjMember(&message, "data"); auto data = GetObjMember(&message, "data");
auto secret = GetStrMember(data, "secret"); auto secret = GetStrMember(data, "secret");
if (secret) { if (secret) {
@ -150,6 +137,25 @@ extern "C" void Discord_UpdateConnection()
WasSpectateGame.store(true); WasSpectateGame.store(true);
} }
} }
else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0) {
auto data = GetObjMember(&message, "data");
auto user = GetObjMember(data, "user");
auto userId = GetStrMember(user, "id");
auto username = GetStrMember(user, "username");
auto avatar = GetStrMember(user, "avatar");
auto joinReq = JoinAskQueue.GetNextAddMessage();
if (userId && username && joinReq) {
StringCopy(joinReq->userId, userId);
StringCopy(joinReq->username, username);
if (avatar) {
StringCopy(joinReq->avatar, avatar);
}
else {
joinReq->avatar[0] = 0;
}
JoinAskQueue.CommitAdd();
}
}
} }
} }
@ -168,16 +174,16 @@ extern "C" void Discord_UpdateConnection()
} }
} }
while (SendQueuePendingSends.load()) { while (SendQueue.HavePendingSends()) {
auto qmessage = SendQueueGetNextSendMessage(); auto qmessage = SendQueue.GetNextSendMessage();
Connection->Write(qmessage->buffer, qmessage->length); Connection->Write(qmessage->buffer, qmessage->length);
--SendQueuePendingSends; SendQueue.CommitSend();
} }
} }
} }
#ifndef DISCORD_DISABLE_IO_THREAD #ifndef DISCORD_DISABLE_IO_THREAD
void DiscordRpcIo() static void DiscordRpcIo(void)
{ {
const std::chrono::duration<int64_t, std::milli> maxWait{500LL}; const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
@ -190,30 +196,30 @@ void DiscordRpcIo()
} }
#endif #endif
void SignalIOActivity() static void SignalIOActivity()
{ {
#ifndef DISCORD_DISABLE_IO_THREAD #ifndef DISCORD_DISABLE_IO_THREAD
WaitForIOActivity.notify_all(); WaitForIOActivity.notify_all();
#endif #endif
} }
bool RegisterForEvent(const char* evtName) static bool RegisterForEvent(const char* evtName)
{ {
auto qmessage = SendQueueGetNextAddMessage(); auto qmessage = SendQueue.GetNextAddMessage();
if (qmessage) { if (qmessage) {
qmessage->length = qmessage->length =
JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName); JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
SendQueueCommitMessage(); SendQueue.CommitAdd();
SignalIOActivity(); SignalIOActivity();
return true; return true;
} }
return false; return false;
} }
extern "C" 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)
{ {
if (autoRegister) { if (autoRegister) {
if (optionalSteamId && optionalSteamId[0]) { if (optionalSteamId && optionalSteamId[0]) {
@ -243,11 +249,15 @@ extern "C" void Discord_Initialize(const char* applicationId,
ReconnectTimeMs.reset(); ReconnectTimeMs.reset();
if (Handlers.joinGame) { if (Handlers.joinGame) {
RegisterForEvent("GAME_JOIN"); RegisterForEvent("ACTIVITY_JOIN");
} }
if (Handlers.spectateGame) { if (Handlers.spectateGame) {
RegisterForEvent("GAME_SPECTATE"); RegisterForEvent("ACTIVITY_SPECTATE");
}
if (Handlers.joinRequest) {
RegisterForEvent("ACTIVITY_JOIN_REQUEST");
} }
}; };
Connection->onDisconnect = [](int err, const char* message) { Connection->onDisconnect = [](int err, const char* message) {
@ -263,7 +273,7 @@ extern "C" void Discord_Initialize(const char* applicationId,
#endif #endif
} }
extern "C" void Discord_Shutdown() extern "C" DISCORD_EXPORT void Discord_Shutdown()
{ {
if (!Connection) { if (!Connection) {
return; return;
@ -281,7 +291,7 @@ extern "C" void Discord_Shutdown()
RpcConnection::Destroy(Connection); RpcConnection::Destroy(Connection);
} }
extern "C" 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(
@ -290,7 +300,22 @@ extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence)
SignalIOActivity(); SignalIOActivity();
} }
extern "C" void Discord_RunCallbacks() 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 (!Connection || !Connection->IsOpen()) {
return;
}
auto qmessage = SendQueue.GetNextAddMessage();
if (qmessage) {
qmessage->length =
JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++);
SendQueue.CommitAdd();
SignalIOActivity();
}
}
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
@ -326,6 +351,20 @@ extern "C" void Discord_RunCallbacks()
Handlers.spectateGame(SpectateGameSecret); Handlers.spectateGame(SpectateGameSecret);
} }
// Right now this batches up any requests and sends them all in a burst; I could imagine a world
// where the implementer would rather sequentially accept/reject each one before the next invite
// is sent. I left it this way because I could also imagine wanting to process these all and
// maybe show them in one common dialog and/or start fetching the avatars in parallel, and if
// not it should be trivial for the implementer to make a queue themselves.
while (JoinAskQueue.HavePendingSends()) {
auto req = JoinAskQueue.GetNextSendMessage();
if (Handlers.joinRequest) {
DiscordJoinRequest djr{req->userId, req->username, req->avatar};
Handlers.joinRequest(&djr);
}
JoinAskQueue.CommitSend();
}
if (!isConnected) { if (!isConnected) {
// if we are not connected, disconnect message last // if we are not connected, disconnect message last
if (wasDisconnected && Handlers.disconnected) { if (wasDisconnected && Handlers.disconnected) {

View File

@ -1,4 +1,12 @@
#pragma once #pragma once
#ifdef __cplusplus
extern "C" {
#endif
void Discord_Register(const char* applicationId, const char* command); void Discord_Register(const char* applicationId, const char* command);
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,96 @@
#include "discord-rpc.h"
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
bool Mkdir(const char* path)
{
int result = mkdir(path, 0755);
if (result == 0) {
return true;
}
if (errno == EEXIST) {
return true;
}
return false;
}
// we want to register games so we can run them from Discord client as discord-<appid>://
extern "C" void Discord_Register(const char* applicationId, const char* command)
{
// Add a desktop file and update some mime handlers so that xdg-open does the right thing.
const char* home = getenv("HOME");
if (!home) {
return;
}
char exePath[1024];
if (!command || !command[0]) {
if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) {
return;
}
command = exePath;
}
const char* destopFileFormat = "[Desktop Entry]\n"
"Name=Game %s\n"
"Exec=%s %%u\n" // note: it really wants that %u in there
"Type=Application\n"
"NoDisplay=true\n"
"Categories=Discord;Games;\n"
"MimeType=x-scheme-handler/discord-%s;\n";
char desktopFile[2048];
int fileLen = snprintf(
desktopFile, sizeof(desktopFile), destopFileFormat, applicationId, command, applicationId);
if (fileLen <= 0) {
return;
}
char desktopFilename[256];
snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId);
char desktopFilePath[1024];
snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home);
if (!Mkdir(desktopFilePath)) {
return;
}
strcat(desktopFilePath, "/share");
if (!Mkdir(desktopFilePath)) {
return;
}
strcat(desktopFilePath, "/applications");
if (!Mkdir(desktopFilePath)) {
return;
}
strcat(desktopFilePath, desktopFilename);
FILE* fp = fopen(desktopFilePath, "w");
if (fp) {
fwrite(desktopFile, 1, fileLen, fp);
fclose(fp);
}
else {
return;
}
char xdgMimeCommand[1024];
snprintf(xdgMimeCommand,
sizeof(xdgMimeCommand),
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
applicationId,
applicationId);
system(xdgMimeCommand);
}
extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
{
char command[256];
sprintf(command, "xdg-open steam://run/%s", steamId);
Discord_Register(applicationId, command);
}

View File

@ -0,0 +1,97 @@
#include <stdio.h>
#include <sys/stat.h>
#import <AppKit/AppKit.h>
#include "discord_register.h"
static bool Mkdir(const char* path)
{
int result = mkdir(path, 0755);
if (result == 0) {
return true;
}
if (errno == EEXIST) {
return true;
}
return false;
}
static void RegisterCommand(const char* applicationId, const char* command)
{
// There does not appear to be a way to register arbitrary commands on OSX, so instead we'll save the command
// to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open
// the command therein (will pass to js's window.open, so requires a url-like thing)
const char* home = getenv("HOME");
if (!home) {
return;
}
char path[2048];
sprintf(path, "%s/Library/Application Support/discord", home);
Mkdir(path);
strcat(path, "/games");
Mkdir(path);
strcat(path, "/");
strcat(path, applicationId);
strcat(path, ".json");
FILE* f = fopen(path, "w");
if (f) {
char jsonBuffer[2048];
int len = snprintf(jsonBuffer, sizeof(jsonBuffer), "{\"command\": \"%s\"}", command);
fwrite(jsonBuffer, (size_t)len, 1, f);
fclose(f);
}
}
static void RegisterURL(const char* applicationId)
{
char url[256];
snprintf(url, sizeof(url), "discord-%s", applicationId);
CFStringRef cfURL = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8);
NSString* myBundleId = [[NSBundle mainBundle] bundleIdentifier];
if (!myBundleId) {
fprintf(stderr, "No bundle id found\n");
return;
}
NSURL* myURL = [[NSBundle mainBundle] bundleURL];
if (!myURL) {
fprintf(stderr, "No bundle url found\n");
return;
}
OSStatus status = LSSetDefaultHandlerForURLScheme(cfURL, (__bridge CFStringRef)myBundleId);
if (status != noErr) {
fprintf(stderr, "Error in LSSetDefaultHandlerForURLScheme: %d\n", (int)status);
return;
}
status = LSRegisterURL((__bridge CFURLRef)myURL, true);
if (status != noErr) {
fprintf(stderr, "Error in LSRegisterURL: %d\n", (int)status);
}
}
void Discord_Register(const char* applicationId, const char* command)
{
if (command) {
RegisterCommand(applicationId, command);
}
else {
// raii lite
void* pool = [[NSAutoreleasePool alloc] init];
RegisterURL(applicationId);
[(id)pool drain];
}
}
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
{
char command[256];
sprintf(command, "steam://run/%s", steamId);
Discord_Register(applicationId, command);
}

View File

@ -1,7 +1,6 @@
#include "discord-rpc.h" #include "discord-rpc.h"
#include <stdio.h> #include <stdio.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define NOMCX #define NOMCX
#define NOSERVICE #define NOSERVICE
@ -10,9 +9,7 @@
#include <Psapi.h> #include <Psapi.h>
#include <Strsafe.h> #include <Strsafe.h>
#pragma comment(lib, "Psapi.lib") #pragma comment(lib, "Psapi.lib")
#endif
#ifdef _WIN32
void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command) void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
{ {
// https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx // https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx
@ -20,9 +17,8 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
// Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions. // Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions.
wchar_t exeFilePath[MAX_PATH]; wchar_t exeFilePath[MAX_PATH];
int exeLen = GetModuleFileNameExW(GetCurrentProcess(), nullptr, exeFilePath, MAX_PATH); DWORD exeLen = GetModuleFileNameExW(GetCurrentProcess(), nullptr, exeFilePath, MAX_PATH);
wchar_t openCommand[1024]; wchar_t openCommand[1024];
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
if (command && command[0]) { if (command && command[0]) {
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command); StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
@ -49,14 +45,14 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
} }
DWORD len; DWORD len;
LSTATUS result; LSTATUS result;
len = lstrlenW(protocolDescription) + 1; len = (DWORD)lstrlenW(protocolDescription) + 1;
result = result =
RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t)); RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t));
if (FAILED(result)) { if (FAILED(result)) {
fprintf(stderr, "Error writing description\n"); fprintf(stderr, "Error writing description\n");
} }
len = lstrlenW(protocolDescription) + 1; len = (DWORD)lstrlenW(protocolDescription) + 1;
result = RegSetKeyValueW(key, nullptr, L"URL Protocol", REG_SZ, &urlProtocol, sizeof(wchar_t)); result = RegSetKeyValueW(key, nullptr, L"URL Protocol", REG_SZ, &urlProtocol, sizeof(wchar_t));
if (FAILED(result)) { if (FAILED(result)) {
fprintf(stderr, "Error writing description\n"); fprintf(stderr, "Error writing description\n");
@ -68,7 +64,7 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
fprintf(stderr, "Error writing icon\n"); fprintf(stderr, "Error writing icon\n");
} }
len = lstrlenW(openCommand) + 1; len = (DWORD)lstrlenW(openCommand) + 1;
result = RegSetKeyValueW( result = RegSetKeyValueW(
key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t)); key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t));
if (FAILED(result)) { if (FAILED(result)) {
@ -76,12 +72,9 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
} }
RegCloseKey(key); RegCloseKey(key);
} }
#endif
void Discord_Register(const char* applicationId, const char* command) extern "C" void Discord_Register(const char* applicationId, const char* command)
{ {
#ifdef _WIN32
wchar_t appId[32]; wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
@ -94,12 +87,10 @@ void Discord_Register(const char* applicationId, const char* command)
} }
Discord_RegisterW(appId, wcommand); Discord_RegisterW(appId, wcommand);
#endif
} }
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
{ {
#ifdef _WIN32
wchar_t appId[32]; wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
@ -133,5 +124,4 @@ void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://run/%s", steamPath, wSteamId); StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://run/%s", steamPath, wSteamId);
Discord_RegisterW(appId, command); Discord_RegisterW(appId, command);
#endif
} }

36
src/msg_queue.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <atomic>
// A simple queue. No locks, but only works with a single thread as producer and a single thread as
// a consumer. Mutex up as needed.
template <typename ElementType, size_t QueueSize>
class MsgQueue {
ElementType queue_[QueueSize]{};
std::atomic_uint nextAdd_{0};
std::atomic_uint nextSend_{0};
std::atomic_uint pendingSends_{0};
public:
MsgQueue() {}
ElementType* GetNextAddMessage()
{
// if we are falling behind, bail
if (pendingSends_.load() >= QueueSize) {
return nullptr;
}
auto index = (nextAdd_++) % QueueSize;
return &queue_[index];
}
void CommitAdd() { ++pendingSends_; }
bool HavePendingSends() const { return pendingSends_.load() != 0; }
ElementType* GetNextSendMessage()
{
auto index = (nextSend_++) % QueueSize;
return &queue_[index];
}
void CommitSend() { --pendingSends_; }
};

View File

@ -129,6 +129,7 @@ bool RpcConnection::Read(JsonDocument& message)
break; break;
case Opcode::Pong: case Opcode::Pong:
break; break;
case Opcode::Handshake:
default: default:
// something bad happened // something bad happened
lastErrorCode = (int)ErrorCode::ReadCorrupt; lastErrorCode = (int)ErrorCode::ReadCorrupt;

View File

@ -72,7 +72,7 @@ void WriteOptionalString(JsonWriter& w, T& k, const char* value)
} }
} }
void JsonWriteNonce(JsonWriter& writer, int nonce) static void JsonWriteNonce(JsonWriter& writer, int nonce)
{ {
WriteKey(writer, "nonce"); WriteKey(writer, "nonce");
char nonceBuffer[32]; char nonceBuffer[32];
@ -197,3 +197,32 @@ size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const cha
return writer.Size(); return writer.Size();
} }
size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce)
{
JsonWriter writer(dest, maxLen);
{
WriteObject obj(writer);
WriteKey(writer, "cmd");
if (reply == DISCORD_REPLY_YES) {
writer.String("SEND_ACTIVITY_JOIN_INVITE");
}
else {
writer.String("CLOSE_ACTIVITY_JOIN_REQUEST");
}
WriteKey(writer, "args");
{
WriteObject args(writer);
WriteKey(writer, "user_id");
writer.String(userId);
}
JsonWriteNonce(writer, nonce);
}
return writer.Size();
}

View File

@ -2,10 +2,20 @@
#include <stdint.h> #include <stdint.h>
#pragma warning(push)
#pragma warning(disable : 4061) // enum is not explicitly handled by a case label
#pragma warning(disable : 4365) // signed/unsigned mismatch
#pragma warning(disable : 4464) // relative include path contains
#pragma warning(disable : 4668) // is not defined as a preprocessor macro
#pragma warning(disable : 6313) // Incorrect operator
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/writer.h" #include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h" #include "rapidjson/stringbuffer.h"
#pragma warning(pop)
// if only there was a standard library function for this // if only there was a standard library function for this
template <size_t Len> template <size_t Len>
inline size_t StringCopy(char (&dest)[Len], const char* src) inline size_t StringCopy(char (&dest)[Len], const char* src)
@ -33,6 +43,8 @@ size_t JsonWriteRichPresenceObj(char* dest,
const DiscordRichPresence* presence); const DiscordRichPresence* presence);
size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName); size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce);
// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need // I want to use as few allocations as I can get away with, and to do that with RapidJson, you need
// to supply some of your own allocators for stuff rather than use the defaults // to supply some of your own allocators for stuff rather than use the defaults
@ -109,7 +121,7 @@ public:
} }
} }
void Flush() {} void Flush() {}
size_t GetSize() const { return current_ - buffer_; } size_t GetSize() const { return (size_t)(current_ - buffer_); }
}; };
using MallocAllocator = rapidjson::CrtAllocator; using MallocAllocator = rapidjson::CrtAllocator;