discord integration example working
This commit is contained in:
6
.cell/cell.toml
Normal file
6
.cell/cell.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[compilation]
|
||||
LDFLAGS = "-Ldiscord_social_sdk/lib/release"
|
||||
CFLAGS = "-Idiscord_social_sdk/include"
|
||||
|
||||
[compilation.macOS]
|
||||
LDFLAGS = "-ldiscord_partner_sdk -Wl,-rpath,@loader_path"
|
||||
602
discord.cpp
Normal file
602
discord.cpp
Normal file
@@ -0,0 +1,602 @@
|
||||
// Discord Social SDK bindings for Cell
|
||||
#include "cell.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "cdiscord.h"
|
||||
|
||||
// Global state
|
||||
static Discord_Client *discord_client = NULL;
|
||||
static JSContext *stored_js_ctx = NULL;
|
||||
static uint64_t discord_application_id = 0;
|
||||
|
||||
// Stored JS callbacks
|
||||
static JSValue js_status_cb = JS_UNINITIALIZED;
|
||||
static JSValue js_log_cb = JS_UNINITIALIZED;
|
||||
static JSValue js_auth_cb = JS_UNINITIALIZED;
|
||||
static JSValue js_token_cb = JS_UNINITIALIZED;
|
||||
static JSValue js_update_token_cb = JS_UNINITIALIZED;
|
||||
static JSValue js_presence_cb = JS_UNINITIALIZED;
|
||||
|
||||
// Stored code verifier for OAuth flow
|
||||
static Discord_AuthorizationCodeVerifier *stored_verifier = NULL;
|
||||
|
||||
// Helper: Discord_String to JS string
|
||||
static JSValue dstr_to_js(JSContext *js, Discord_String *str) {
|
||||
if (!str || !str->ptr || str->size == 0) return JS_NULL;
|
||||
return JS_NewStringLen(js, (const char*)str->ptr, str->size);
|
||||
}
|
||||
|
||||
// Helper: JS string to Discord_String
|
||||
static Discord_String js_to_dstr(JSContext *js, JSValue val) {
|
||||
Discord_String str = {0};
|
||||
if (JS_IsNull(val)) return str;
|
||||
size_t len;
|
||||
const char *cstr = JS_ToCStringLen(js, &len, val);
|
||||
if (cstr) {
|
||||
str.ptr = (uint8_t*)Discord_Alloc(len + 1);
|
||||
if (str.ptr) {
|
||||
memcpy(str.ptr, cstr, len);
|
||||
str.ptr[len] = 0;
|
||||
str.size = len;
|
||||
}
|
||||
JS_FreeCString(js, cstr);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// Helper: Free Discord_String
|
||||
static void free_dstr(Discord_String *str) {
|
||||
if (str && str->ptr) { Discord_Free(str->ptr); str->ptr = NULL; str->size = 0; }
|
||||
}
|
||||
|
||||
// Helper: JS value (string or number) to uint64_t
|
||||
static uint64_t js_to_u64(JSContext *js, JSValueConst v) {
|
||||
if (JS_IsString(v)) {
|
||||
uint64_t out = 0;
|
||||
const char *s = JS_ToCString(js, v);
|
||||
if (s) {
|
||||
out = strtoull(s, NULL, 10);
|
||||
JS_FreeCString(js, s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return (uint64_t)js2number(js, v);
|
||||
}
|
||||
|
||||
// Helper: free stored JS callback value
|
||||
static void free_cb(JSContext *js, JSValue *cb) {
|
||||
if (!JS_IsUninitialized(*cb)) {
|
||||
JS_FreeValue(js, *cb);
|
||||
*cb = JS_UNINITIALIZED;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: UserHandle to JS object
|
||||
static JSValue user_to_js(JSContext *js, Discord_UserHandle *h) {
|
||||
if (!h) return JS_NULL;
|
||||
JSValue u = JS_NewObject(js);
|
||||
{
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%llu", (unsigned long long)Discord_UserHandle_Id(h));
|
||||
JS_SetPropertyStr(js, u, "id", JS_NewString(js, buf));
|
||||
}
|
||||
Discord_String s;
|
||||
Discord_UserHandle_Username(h, &s);
|
||||
JS_SetPropertyStr(js, u, "username", dstr_to_js(js, &s));
|
||||
Discord_UserHandle_DisplayName(h, &s);
|
||||
JS_SetPropertyStr(js, u, "display_name", dstr_to_js(js, &s));
|
||||
if (Discord_UserHandle_GlobalName(h, &s))
|
||||
JS_SetPropertyStr(js, u, "global_name", dstr_to_js(js, &s));
|
||||
Discord_UserHandle_AvatarUrl(h, Discord_UserHandle_AvatarType_Png, Discord_UserHandle_AvatarType_Png, &s);
|
||||
JS_SetPropertyStr(js, u, "avatar_url", dstr_to_js(js, &s));
|
||||
Discord_StatusType st = Discord_UserHandle_Status(h);
|
||||
const char *ss = "unknown";
|
||||
if (st == Discord_StatusType_Online) ss = "online";
|
||||
else if (st == Discord_StatusType_Offline) ss = "offline";
|
||||
else if (st == Discord_StatusType_Idle) ss = "idle";
|
||||
else if (st == Discord_StatusType_Dnd) ss = "dnd";
|
||||
JS_SetPropertyStr(js, u, "status", JS_NewString(js, ss));
|
||||
JS_SetPropertyStr(js, u, "is_provisional", JS_NewBool(js, Discord_UserHandle_IsProvisional(h)));
|
||||
return u;
|
||||
}
|
||||
|
||||
// Helper: RelationshipHandle to JS object
|
||||
static JSValue rel_to_js(JSContext *js, Discord_RelationshipHandle *h) {
|
||||
if (!h) return JS_NULL;
|
||||
JSValue r = JS_NewObject(js);
|
||||
{
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%llu", (unsigned long long)Discord_RelationshipHandle_Id(h));
|
||||
JS_SetPropertyStr(js, r, "id", JS_NewString(js, buf));
|
||||
}
|
||||
Discord_RelationshipType t = Discord_RelationshipHandle_DiscordRelationshipType(h);
|
||||
const char *ts = "none";
|
||||
if (t == Discord_RelationshipType_Friend) ts = "friend";
|
||||
else if (t == Discord_RelationshipType_Blocked) ts = "blocked";
|
||||
else if (t == Discord_RelationshipType_PendingIncoming) ts = "pending_incoming";
|
||||
else if (t == Discord_RelationshipType_PendingOutgoing) ts = "pending_outgoing";
|
||||
JS_SetPropertyStr(js, r, "type", JS_NewString(js, ts));
|
||||
Discord_UserHandle uh;
|
||||
if (Discord_RelationshipHandle_User(h, &uh))
|
||||
JS_SetPropertyStr(js, r, "user", user_to_js(js, &uh));
|
||||
return r;
|
||||
}
|
||||
|
||||
// Callback: Status changed
|
||||
static void cb_status(Discord_Client_Status status, Discord_Client_Error error, int32_t detail, void* ud) {
|
||||
if (!stored_js_ctx || JS_IsUninitialized(js_status_cb)) return;
|
||||
JSContext *js = stored_js_ctx;
|
||||
const char *ss = "unknown";
|
||||
if (status == Discord_Client_Status_Disconnected) ss = "disconnected";
|
||||
else if (status == Discord_Client_Status_Connecting) ss = "connecting";
|
||||
else if (status == Discord_Client_Status_Connected) ss = "connected";
|
||||
else if (status == Discord_Client_Status_Ready) ss = "ready";
|
||||
else if (status == Discord_Client_Status_Reconnecting) ss = "reconnecting";
|
||||
const char *es = "none";
|
||||
if (error == Discord_Client_Error_ConnectionFailed) es = "connection_failed";
|
||||
else if (error == Discord_Client_Error_UnexpectedClose) es = "unexpected_close";
|
||||
JSValue args[3] = { JS_NewString(js, ss), JS_NewString(js, es), JS_NewInt32(js, detail) };
|
||||
JSValue res = JS_Call(js, js_status_cb, JS_NULL, 3, args);
|
||||
JS_FreeValue(js, args[0]); JS_FreeValue(js, args[1]); JS_FreeValue(js, args[2]); JS_FreeValue(js, res);
|
||||
}
|
||||
|
||||
// Callback: Log
|
||||
static void cb_log(Discord_String msg, Discord_LoggingSeverity sev, void* ud) {
|
||||
if (!stored_js_ctx || JS_IsUninitialized(js_log_cb)) return;
|
||||
JSContext *js = stored_js_ctx;
|
||||
const char *ss = "info";
|
||||
if (sev == Discord_LoggingSeverity_Verbose) ss = "verbose";
|
||||
else if (sev == Discord_LoggingSeverity_Warning) ss = "warning";
|
||||
else if (sev == Discord_LoggingSeverity_Error) ss = "error";
|
||||
JSValue args[2] = { dstr_to_js(js, &msg), JS_NewString(js, ss) };
|
||||
JSValue res = JS_Call(js, js_log_cb, JS_NULL, 2, args);
|
||||
JS_FreeValue(js, args[0]); JS_FreeValue(js, args[1]); JS_FreeValue(js, res);
|
||||
}
|
||||
|
||||
// Callback: Authorization
|
||||
static void cb_auth(Discord_ClientResult* result, Discord_String code, Discord_String uri, void* ud) {
|
||||
if (!stored_js_ctx || JS_IsUninitialized(js_auth_cb)) return;
|
||||
JSContext *js = stored_js_ctx;
|
||||
bool ok = Discord_ClientResult_Successful(result);
|
||||
JSValue args[4];
|
||||
args[0] = JS_NewBool(js, ok);
|
||||
if (ok) {
|
||||
args[1] = dstr_to_js(js, &code);
|
||||
args[2] = dstr_to_js(js, &uri);
|
||||
args[3] = JS_NULL;
|
||||
} else {
|
||||
args[1] = JS_NULL; args[2] = JS_NULL;
|
||||
Discord_String e; Discord_ClientResult_Error(result, &e);
|
||||
args[3] = dstr_to_js(js, &e);
|
||||
}
|
||||
JSValue res = JS_Call(js, js_auth_cb, JS_NULL, 4, args);
|
||||
for (int i = 0; i < 4; i++) JS_FreeValue(js, args[i]);
|
||||
JS_FreeValue(js, res);
|
||||
}
|
||||
|
||||
// Callback: Token exchange
|
||||
static void cb_token(Discord_ClientResult* result, Discord_String access, Discord_String refresh,
|
||||
Discord_AuthorizationTokenType type, int32_t expires, Discord_String scope, void* ud) {
|
||||
if (!stored_js_ctx || JS_IsUninitialized(js_token_cb)) return;
|
||||
JSContext *js = stored_js_ctx;
|
||||
bool ok = Discord_ClientResult_Successful(result);
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, obj, "success", JS_NewBool(js, ok));
|
||||
if (ok) {
|
||||
JS_SetPropertyStr(js, obj, "access_token", dstr_to_js(js, &access));
|
||||
JS_SetPropertyStr(js, obj, "refresh_token", dstr_to_js(js, &refresh));
|
||||
JS_SetPropertyStr(js, obj, "token_type", JS_NewString(js, type == Discord_AuthorizationTokenType_Bearer ? "bearer" : "user"));
|
||||
JS_SetPropertyStr(js, obj, "expires_in", JS_NewInt32(js, expires));
|
||||
JS_SetPropertyStr(js, obj, "scope", dstr_to_js(js, &scope));
|
||||
} else {
|
||||
Discord_String e; Discord_ClientResult_Error(result, &e);
|
||||
JS_SetPropertyStr(js, obj, "error", dstr_to_js(js, &e));
|
||||
}
|
||||
JSValue args[1] = { obj };
|
||||
JSValue res = JS_Call(js, js_token_cb, JS_NULL, 1, args);
|
||||
JS_FreeValue(js, obj); JS_FreeValue(js, res);
|
||||
}
|
||||
|
||||
// Callback: Update token
|
||||
static void cb_update_token(Discord_ClientResult* result, void* ud) {
|
||||
if (!stored_js_ctx || JS_IsUninitialized(js_update_token_cb)) return;
|
||||
JSContext *js = stored_js_ctx;
|
||||
bool ok = Discord_ClientResult_Successful(result);
|
||||
JSValue args[2];
|
||||
args[0] = JS_NewBool(js, ok);
|
||||
if (!ok) { Discord_String e; Discord_ClientResult_Error(result, &e); args[1] = dstr_to_js(js, &e); }
|
||||
else args[1] = JS_NULL;
|
||||
JSValue res = JS_Call(js, js_update_token_cb, JS_NULL, 2, args);
|
||||
JS_FreeValue(js, args[0]); JS_FreeValue(js, args[1]); JS_FreeValue(js, res);
|
||||
}
|
||||
|
||||
// Callback: Rich presence
|
||||
static void cb_presence(Discord_ClientResult* result, void* ud) {
|
||||
if (!stored_js_ctx || JS_IsUninitialized(js_presence_cb)) return;
|
||||
JSContext *js = stored_js_ctx;
|
||||
bool ok = Discord_ClientResult_Successful(result);
|
||||
JSValue args[2];
|
||||
args[0] = JS_NewBool(js, ok);
|
||||
if (!ok) { Discord_String e; Discord_ClientResult_Error(result, &e); args[1] = dstr_to_js(js, &e); }
|
||||
else args[1] = JS_NULL;
|
||||
JSValue res = JS_Call(js, js_presence_cb, JS_NULL, 2, args);
|
||||
JS_FreeValue(js, args[0]); JS_FreeValue(js, args[1]); JS_FreeValue(js, res);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
// ============================================================================
|
||||
// CORE API
|
||||
// ============================================================================
|
||||
|
||||
// discord.init(application_id) - Initialize the Discord client
|
||||
JSC_CCALL(discord_init,
|
||||
if (discord_client) return JS_ThrowInternalError(js, "Discord client already initialized");
|
||||
uint64_t app_id = 0;
|
||||
if (argc > 0) app_id = js_to_u64(js, argv[0]);
|
||||
if (app_id == 0) return JS_ThrowTypeError(js, "Application ID is required");
|
||||
discord_application_id = app_id;
|
||||
stored_js_ctx = js;
|
||||
discord_client = (Discord_Client*)Discord_Alloc(sizeof(Discord_Client));
|
||||
if (!discord_client) return JS_ThrowOutOfMemory(js);
|
||||
Discord_Client_Init(discord_client);
|
||||
Discord_Client_SetApplicationId(discord_client, app_id);
|
||||
return JS_TRUE;
|
||||
)
|
||||
|
||||
// discord.run_callbacks() - Process Discord callbacks
|
||||
JSC_CCALL(discord_run_callbacks,
|
||||
Discord_RunCallbacks();
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// discord.shutdown() - Disconnect and clean up
|
||||
JSC_CCALL(discord_shutdown,
|
||||
if (discord_client) {
|
||||
Discord_Client_Disconnect(discord_client);
|
||||
Discord_Client_Drop(discord_client);
|
||||
discord_client = NULL;
|
||||
}
|
||||
if (stored_verifier) {
|
||||
Discord_AuthorizationCodeVerifier_Drop(stored_verifier);
|
||||
Discord_Free(stored_verifier);
|
||||
stored_verifier = NULL;
|
||||
}
|
||||
free_cb(js, &js_status_cb);
|
||||
free_cb(js, &js_log_cb);
|
||||
free_cb(js, &js_auth_cb);
|
||||
free_cb(js, &js_token_cb);
|
||||
free_cb(js, &js_update_token_cb);
|
||||
free_cb(js, &js_presence_cb);
|
||||
stored_js_ctx = NULL;
|
||||
discord_application_id = 0;
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// discord.get_status() - Get current connection status
|
||||
JSC_CCALL(discord_get_status,
|
||||
if (!discord_client) return JS_NewString(js, "not_initialized");
|
||||
Discord_Client_Status st = Discord_Client_GetStatus(discord_client);
|
||||
const char *s = "unknown";
|
||||
if (st == Discord_Client_Status_Disconnected) s = "disconnected";
|
||||
else if (st == Discord_Client_Status_Connecting) s = "connecting";
|
||||
else if (st == Discord_Client_Status_Connected) s = "connected";
|
||||
else if (st == Discord_Client_Status_Ready) s = "ready";
|
||||
else if (st == Discord_Client_Status_Reconnecting) s = "reconnecting";
|
||||
return JS_NewString(js, s);
|
||||
)
|
||||
|
||||
// discord.is_authenticated()
|
||||
JSC_CCALL(discord_is_authenticated,
|
||||
if (!discord_client) return JS_FALSE;
|
||||
return JS_NewBool(js, Discord_Client_IsAuthenticated(discord_client));
|
||||
)
|
||||
|
||||
// discord.connect()
|
||||
JSC_CCALL(discord_connect,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
Discord_Client_Connect(discord_client);
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// discord.disconnect()
|
||||
JSC_CCALL(discord_disconnect,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
Discord_Client_Disconnect(discord_client);
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// CALLBACK REGISTRATION
|
||||
// ============================================================================
|
||||
|
||||
// discord.on_status_changed(callback)
|
||||
JSC_CCALL(discord_on_status_changed,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
if (!JS_IsUninitialized(js_status_cb)) JS_FreeValue(js, js_status_cb);
|
||||
if (JS_IsFunction(js, argv[0])) {
|
||||
js_status_cb = JS_DupValue(js, argv[0]);
|
||||
Discord_Client_SetStatusChangedCallback(discord_client, cb_status, NULL, NULL);
|
||||
} else {
|
||||
js_status_cb = JS_UNINITIALIZED;
|
||||
Discord_Client_SetStatusChangedCallback(discord_client, NULL, NULL, NULL);
|
||||
}
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// discord.on_log(callback, min_severity)
|
||||
JSC_CCALL(discord_on_log,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
if (!JS_IsUninitialized(js_log_cb)) JS_FreeValue(js, js_log_cb);
|
||||
if (JS_IsFunction(js, argv[0])) {
|
||||
js_log_cb = JS_DupValue(js, argv[0]);
|
||||
Discord_LoggingSeverity sev = Discord_LoggingSeverity_Info;
|
||||
if (argc > 1 && JS_IsString(argv[1])) {
|
||||
const char *s = JS_ToCString(js, argv[1]);
|
||||
if (strcmp(s, "verbose") == 0) sev = Discord_LoggingSeverity_Verbose;
|
||||
else if (strcmp(s, "warning") == 0) sev = Discord_LoggingSeverity_Warning;
|
||||
else if (strcmp(s, "error") == 0) sev = Discord_LoggingSeverity_Error;
|
||||
JS_FreeCString(js, s);
|
||||
}
|
||||
Discord_Client_AddLogCallback(discord_client, cb_log, NULL, NULL, sev);
|
||||
} else js_log_cb = JS_UNINITIALIZED;
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// AUTHENTICATION API
|
||||
// ============================================================================
|
||||
|
||||
// discord.get_default_scopes()
|
||||
JSC_CCALL(discord_get_default_scopes,
|
||||
Discord_String scopes;
|
||||
Discord_Client_GetDefaultPresenceScopes(&scopes);
|
||||
return dstr_to_js(js, &scopes);
|
||||
)
|
||||
|
||||
// discord.authorize(callback)
|
||||
JSC_CCALL(discord_authorize,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
if (!JS_IsUninitialized(js_auth_cb)) JS_FreeValue(js, js_auth_cb);
|
||||
js_auth_cb = JS_DupValue(js, argv[0]);
|
||||
if (stored_verifier) { Discord_AuthorizationCodeVerifier_Drop(stored_verifier); Discord_Free(stored_verifier); }
|
||||
stored_verifier = (Discord_AuthorizationCodeVerifier*)Discord_Alloc(sizeof(Discord_AuthorizationCodeVerifier));
|
||||
Discord_Client_CreateAuthorizationCodeVerifier(discord_client, stored_verifier);
|
||||
Discord_AuthorizationCodeChallenge challenge;
|
||||
Discord_AuthorizationCodeVerifier_Challenge(stored_verifier, &challenge);
|
||||
Discord_AuthorizationArgs args;
|
||||
Discord_AuthorizationArgs_Init(&args);
|
||||
Discord_AuthorizationArgs_SetClientId(&args, discord_application_id);
|
||||
Discord_String scopes;
|
||||
Discord_Client_GetDefaultPresenceScopes(&scopes);
|
||||
Discord_AuthorizationArgs_SetScopes(&args, scopes);
|
||||
Discord_AuthorizationArgs_SetCodeChallenge(&args, &challenge);
|
||||
Discord_Client_Authorize(discord_client, &args, cb_auth, NULL, NULL);
|
||||
Discord_AuthorizationArgs_Drop(&args);
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// discord.get_token(code, redirect_uri, callback)
|
||||
JSC_CCALL(discord_get_token,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
if (!stored_verifier) return JS_ThrowInternalError(js, "No code verifier - call authorize first");
|
||||
Discord_String code = js_to_dstr(js, argv[0]);
|
||||
Discord_String uri = js_to_dstr(js, argv[1]);
|
||||
if (!JS_IsUninitialized(js_token_cb)) JS_FreeValue(js, js_token_cb);
|
||||
js_token_cb = JS_DupValue(js, argv[2]);
|
||||
Discord_String verifier;
|
||||
Discord_AuthorizationCodeVerifier_Verifier(stored_verifier, &verifier);
|
||||
Discord_Client_GetToken(discord_client, discord_application_id, code, verifier, uri, cb_token, NULL, NULL);
|
||||
free_dstr(&code); free_dstr(&uri);
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// discord.update_token(token_type, token, callback)
|
||||
JSC_CCALL(discord_update_token,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
const char *ts = JS_ToCString(js, argv[0]);
|
||||
Discord_AuthorizationTokenType tt = Discord_AuthorizationTokenType_Bearer;
|
||||
if (ts && strcmp(ts, "user") == 0) tt = Discord_AuthorizationTokenType_User;
|
||||
JS_FreeCString(js, ts);
|
||||
Discord_String token = js_to_dstr(js, argv[1]);
|
||||
if (!JS_IsUninitialized(js_update_token_cb)) JS_FreeValue(js, js_update_token_cb);
|
||||
js_update_token_cb = JS_DupValue(js, argv[2]);
|
||||
Discord_Client_UpdateToken(discord_client, tt, token, cb_update_token, NULL, NULL);
|
||||
free_dstr(&token);
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// discord.refresh_token(refresh_token, callback)
|
||||
JSC_CCALL(discord_refresh_token,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
Discord_String rt = js_to_dstr(js, argv[0]);
|
||||
if (!JS_IsUninitialized(js_token_cb)) JS_FreeValue(js, js_token_cb);
|
||||
js_token_cb = JS_DupValue(js, argv[1]);
|
||||
Discord_Client_RefreshToken(discord_client, discord_application_id, rt, cb_token, NULL, NULL);
|
||||
free_dstr(&rt);
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// USER API
|
||||
// ============================================================================
|
||||
|
||||
// discord.get_current_user()
|
||||
JSC_CCALL(discord_get_current_user,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
Discord_UserHandle user;
|
||||
if (!Discord_Client_GetCurrentUserV2(discord_client, &user)) return JS_NULL;
|
||||
return user_to_js(js, &user);
|
||||
)
|
||||
|
||||
// discord.get_user(user_id)
|
||||
JSC_CCALL(discord_get_user,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
uint64_t uid = 0;
|
||||
uid = js_to_u64(js, argv[0]);
|
||||
Discord_UserHandle user;
|
||||
if (!Discord_Client_GetUser(discord_client, uid, &user)) return JS_NULL;
|
||||
return user_to_js(js, &user);
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// RELATIONSHIPS API
|
||||
// ============================================================================
|
||||
|
||||
// discord.get_relationships()
|
||||
JSC_CCALL(discord_get_relationships,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
Discord_RelationshipHandleSpan rels;
|
||||
Discord_Client_GetRelationships(discord_client, &rels);
|
||||
JSValue arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < rels.size; i++)
|
||||
JS_SetPropertyUint32(js, arr, i, rel_to_js(js, &rels.ptr[i]));
|
||||
return arr;
|
||||
)
|
||||
|
||||
// discord.get_friends_count()
|
||||
JSC_CCALL(discord_get_friends_count,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
Discord_RelationshipHandleSpan rels;
|
||||
Discord_Client_GetRelationships(discord_client, &rels);
|
||||
return JS_NewUint32(js, (uint32_t)rels.size);
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// RICH PRESENCE API
|
||||
// ============================================================================
|
||||
|
||||
// discord.update_rich_presence(activity, callback)
|
||||
JSC_CCALL(discord_update_rich_presence,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
Discord_Activity activity;
|
||||
Discord_Activity_Init(&activity);
|
||||
JSValue obj = argv[0];
|
||||
// Type
|
||||
JSValue tv = JS_GetPropertyStr(js, obj, "type");
|
||||
if (!JS_IsNull(tv)) {
|
||||
const char *ts = JS_ToCString(js, tv);
|
||||
Discord_ActivityTypes t = Discord_ActivityTypes_Playing;
|
||||
if (ts) {
|
||||
if (strcmp(ts, "streaming") == 0) t = Discord_ActivityTypes_Streaming;
|
||||
else if (strcmp(ts, "listening") == 0) t = Discord_ActivityTypes_Listening;
|
||||
else if (strcmp(ts, "watching") == 0) t = Discord_ActivityTypes_Watching;
|
||||
else if (strcmp(ts, "competing") == 0) t = Discord_ActivityTypes_Competing;
|
||||
JS_FreeCString(js, ts);
|
||||
}
|
||||
Discord_Activity_SetType(&activity, t);
|
||||
}
|
||||
JS_FreeValue(js, tv);
|
||||
// State
|
||||
JSValue sv = JS_GetPropertyStr(js, obj, "state");
|
||||
if (JS_IsString(sv)) { Discord_String s = js_to_dstr(js, sv); Discord_Activity_SetState(&activity, &s); free_dstr(&s); }
|
||||
JS_FreeValue(js, sv);
|
||||
// Details
|
||||
JSValue dv = JS_GetPropertyStr(js, obj, "details");
|
||||
if (JS_IsString(dv)) { Discord_String s = js_to_dstr(js, dv); Discord_Activity_SetDetails(&activity, &s); free_dstr(&s); }
|
||||
JS_FreeValue(js, dv);
|
||||
// Assets
|
||||
JSValue li = JS_GetPropertyStr(js, obj, "large_image");
|
||||
JSValue lt = JS_GetPropertyStr(js, obj, "large_text");
|
||||
JSValue si = JS_GetPropertyStr(js, obj, "small_image");
|
||||
JSValue st = JS_GetPropertyStr(js, obj, "small_text");
|
||||
if (JS_IsString(li) || JS_IsString(si)) {
|
||||
Discord_ActivityAssets assets;
|
||||
Discord_ActivityAssets_Init(&assets);
|
||||
if (JS_IsString(li)) { Discord_String s = js_to_dstr(js, li); Discord_ActivityAssets_SetLargeImage(&assets, &s); free_dstr(&s); }
|
||||
if (JS_IsString(lt)) { Discord_String s = js_to_dstr(js, lt); Discord_ActivityAssets_SetLargeText(&assets, &s); free_dstr(&s); }
|
||||
if (JS_IsString(si)) { Discord_String s = js_to_dstr(js, si); Discord_ActivityAssets_SetSmallImage(&assets, &s); free_dstr(&s); }
|
||||
if (JS_IsString(st)) { Discord_String s = js_to_dstr(js, st); Discord_ActivityAssets_SetSmallText(&assets, &s); free_dstr(&s); }
|
||||
Discord_Activity_SetAssets(&activity, &assets);
|
||||
Discord_ActivityAssets_Drop(&assets);
|
||||
}
|
||||
JS_FreeValue(js, li); JS_FreeValue(js, lt); JS_FreeValue(js, si); JS_FreeValue(js, st);
|
||||
// Timestamps
|
||||
JSValue tsv = JS_GetPropertyStr(js, obj, "start_timestamp");
|
||||
JSValue tev = JS_GetPropertyStr(js, obj, "end_timestamp");
|
||||
if (!JS_IsNull(tsv) || !JS_IsNull(tev)) {
|
||||
Discord_ActivityTimestamps ts;
|
||||
Discord_ActivityTimestamps_Init(&ts);
|
||||
if (!JS_IsNull(tsv)) Discord_ActivityTimestamps_SetStart(&ts, (uint64_t)js2number(js, tsv));
|
||||
if (!JS_IsNull(tev)) Discord_ActivityTimestamps_SetEnd(&ts, (uint64_t)js2number(js, tev));
|
||||
Discord_Activity_SetTimestamps(&activity, &ts);
|
||||
Discord_ActivityTimestamps_Drop(&ts);
|
||||
}
|
||||
JS_FreeValue(js, tsv); JS_FreeValue(js, tev);
|
||||
// Party
|
||||
JSValue piv = JS_GetPropertyStr(js, obj, "party_id");
|
||||
if (JS_IsString(piv)) {
|
||||
Discord_ActivityParty party;
|
||||
Discord_ActivityParty_Init(&party);
|
||||
Discord_String s = js_to_dstr(js, piv);
|
||||
Discord_ActivityParty_SetId(&party, s);
|
||||
free_dstr(&s);
|
||||
JSValue psv = JS_GetPropertyStr(js, obj, "party_size");
|
||||
JSValue pmv = JS_GetPropertyStr(js, obj, "party_max");
|
||||
if (!JS_IsNull(psv)) Discord_ActivityParty_SetCurrentSize(&party, (int32_t)js2number(js, psv));
|
||||
if (!JS_IsNull(pmv)) Discord_ActivityParty_SetMaxSize(&party, (int32_t)js2number(js, pmv));
|
||||
JS_FreeValue(js, psv); JS_FreeValue(js, pmv);
|
||||
Discord_Activity_SetParty(&activity, &party);
|
||||
Discord_ActivityParty_Drop(&party);
|
||||
}
|
||||
JS_FreeValue(js, piv);
|
||||
// Secrets
|
||||
JSValue jsv = JS_GetPropertyStr(js, obj, "join_secret");
|
||||
if (JS_IsString(jsv)) {
|
||||
Discord_ActivitySecrets secrets;
|
||||
Discord_ActivitySecrets_Init(&secrets);
|
||||
Discord_String s = js_to_dstr(js, jsv);
|
||||
Discord_ActivitySecrets_SetJoin(&secrets, s);
|
||||
free_dstr(&s);
|
||||
Discord_Activity_SetSecrets(&activity, &secrets);
|
||||
Discord_ActivitySecrets_Drop(&secrets);
|
||||
}
|
||||
JS_FreeValue(js, jsv);
|
||||
// Callback
|
||||
if (argc > 1 && JS_IsFunction(js, argv[1])) {
|
||||
if (!JS_IsUninitialized(js_presence_cb)) JS_FreeValue(js, js_presence_cb);
|
||||
js_presence_cb = JS_DupValue(js, argv[1]);
|
||||
}
|
||||
Discord_Client_UpdateRichPresence(discord_client, &activity, cb_presence, NULL, NULL);
|
||||
Discord_Activity_Drop(&activity);
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// discord.clear_rich_presence()
|
||||
JSC_CCALL(discord_clear_rich_presence,
|
||||
if (!discord_client) return JS_ThrowInternalError(js, "Discord client not initialized");
|
||||
Discord_Client_ClearRichPresence(discord_client);
|
||||
return JS_NULL;
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// FUNCTION LIST AND MODULE EXPORT
|
||||
// ============================================================================
|
||||
|
||||
static const JSCFunctionListEntry js_discord_funcs[] = {
|
||||
MIST_FUNC_DEF(discord, init, 1),
|
||||
MIST_FUNC_DEF(discord, run_callbacks, 0),
|
||||
MIST_FUNC_DEF(discord, shutdown, 0),
|
||||
MIST_FUNC_DEF(discord, get_status, 0),
|
||||
MIST_FUNC_DEF(discord, is_authenticated, 0),
|
||||
MIST_FUNC_DEF(discord, connect, 0),
|
||||
MIST_FUNC_DEF(discord, disconnect, 0),
|
||||
MIST_FUNC_DEF(discord, on_status_changed, 1),
|
||||
MIST_FUNC_DEF(discord, on_log, 2),
|
||||
MIST_FUNC_DEF(discord, get_default_scopes, 0),
|
||||
MIST_FUNC_DEF(discord, authorize, 1),
|
||||
MIST_FUNC_DEF(discord, get_token, 3),
|
||||
MIST_FUNC_DEF(discord, update_token, 3),
|
||||
MIST_FUNC_DEF(discord, refresh_token, 2),
|
||||
MIST_FUNC_DEF(discord, get_current_user, 0),
|
||||
MIST_FUNC_DEF(discord, get_user, 1),
|
||||
MIST_FUNC_DEF(discord, get_relationships, 0),
|
||||
MIST_FUNC_DEF(discord, get_friends_count, 0),
|
||||
MIST_FUNC_DEF(discord, update_rich_presence, 2),
|
||||
MIST_FUNC_DEF(discord, clear_rich_presence, 0),
|
||||
};
|
||||
|
||||
CELL_USE_FUNCS(js_discord_funcs)
|
||||
|
||||
} // extern "C"
|
||||
182
examples/discord_example.ce
Normal file
182
examples/discord_example.ce
Normal file
@@ -0,0 +1,182 @@
|
||||
// Discord Social SDK Example
|
||||
// Demonstrates: Authentication, Status Monitoring, Friends List, Rich Presence
|
||||
//
|
||||
// Configuration is loaded from config.cm
|
||||
// Get your own app at: https://discord.com/developers/applications
|
||||
|
||||
var discord = use("discord")
|
||||
var config = use("tests/config")
|
||||
var time = use('time')
|
||||
|
||||
// ============================================================================
|
||||
// CONFIGURATION - Loaded from config.cm (or override here)
|
||||
// ============================================================================
|
||||
def APPLICATION_ID = config.APPLICATION_ID
|
||||
def CALLBACK_INTERVAL = config.CALLBACK_INTERVAL
|
||||
|
||||
// ============================================================================
|
||||
// STATE
|
||||
// ============================================================================
|
||||
var authenticated = false
|
||||
var ready = false
|
||||
var access_token = null
|
||||
var refresh_token = null
|
||||
|
||||
// ============================================================================
|
||||
// DISCORD CALLBACKS
|
||||
// ============================================================================
|
||||
|
||||
function on_status_changed(status, error, detail) {
|
||||
log.console(`Discord status: ${status}`)
|
||||
|
||||
if (status == "ready") {
|
||||
ready = true
|
||||
log.console("Discord client is ready!")
|
||||
on_ready()
|
||||
} else if (error != "none") {
|
||||
log.console(`Discord error: ${error} (detail: ${detail})`)
|
||||
}
|
||||
}
|
||||
|
||||
function on_log(message, severity) {
|
||||
log.console(`[Discord ${severity}] ${message}`)
|
||||
}
|
||||
|
||||
function on_ready() {
|
||||
// Get and display current user info
|
||||
var user = discord.get_current_user()
|
||||
if (user) {
|
||||
log.console(`Logged in as: ${user.display_name} (@${user.username})`)
|
||||
log.console(`User ID: ${user.id}`)
|
||||
log.console(`Status: ${user.status}`)
|
||||
if (user.avatar_url) log.console(`Avatar: ${user.avatar_url}`)
|
||||
}
|
||||
|
||||
// Get friends count
|
||||
var friends_count = discord.get_friends_count()
|
||||
log.console(`Friends count: ${friends_count}`)
|
||||
|
||||
// Get full relationships list
|
||||
var relationships = discord.get_relationships()
|
||||
log.console(`Total relationships: ${relationships.length}`)
|
||||
for (var i = 0; i < relationships.length && i < 5; i++) {
|
||||
var rel = relationships[i]
|
||||
if (rel.user) {
|
||||
log.console(` - ${rel.user.display_name} (${rel.type})`)
|
||||
}
|
||||
}
|
||||
if (relationships.length > 5) {
|
||||
log.console(` ... and ${relationships.length - 5} more`)
|
||||
}
|
||||
|
||||
// Set rich presence
|
||||
set_rich_presence()
|
||||
}
|
||||
|
||||
function set_rich_presence() {
|
||||
log.console("Setting rich presence...")
|
||||
|
||||
discord.update_rich_presence({
|
||||
type: "playing",
|
||||
state: "In Main Menu",
|
||||
details: "Exploring the game",
|
||||
start_timestamp: time.number()
|
||||
}, function(success, error) {
|
||||
if (success) {
|
||||
log.console("Rich presence updated successfully!")
|
||||
} else {
|
||||
log.console(`Rich presence update failed: ${error}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// AUTHENTICATION FLOW
|
||||
// ============================================================================
|
||||
|
||||
function start_auth() {
|
||||
log.console("Starting Discord authorization...")
|
||||
log.console("A browser window should open for Discord login.")
|
||||
|
||||
discord.authorize(function(success, code, redirect_uri, error) {
|
||||
if (success) {
|
||||
log.console("Authorization successful! Exchanging code for token...")
|
||||
exchange_token(code, redirect_uri)
|
||||
} else {
|
||||
log.console(`Authorization failed: ${error}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function exchange_token(code, redirect_uri) {
|
||||
discord.get_token(code, redirect_uri, function(result) {
|
||||
if (result.success) {
|
||||
log.console("Token received!")
|
||||
access_token = result.access_token
|
||||
refresh_token = result.refresh_token
|
||||
log.console(`Token expires in: ${result.expires_in} seconds`)
|
||||
|
||||
// Update the client with the token and connect
|
||||
update_and_connect(result.access_token)
|
||||
} else {
|
||||
log.console(`Token exchange failed: ${result.error}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function update_and_connect(token) {
|
||||
log.console("Updating token and connecting...")
|
||||
|
||||
discord.update_token("bearer", token, function(success, error) {
|
||||
if (success) {
|
||||
log.console("Token updated, connecting to Discord...")
|
||||
authenticated = true
|
||||
discord.connect()
|
||||
} else {
|
||||
log.console(`Token update failed: ${error}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN LOOP - Process Discord callbacks
|
||||
// ============================================================================
|
||||
|
||||
function discord_tick() {
|
||||
discord.run_callbacks()
|
||||
$_.delay(discord_tick, CALLBACK_INTERVAL)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// INITIALIZATION
|
||||
// ============================================================================
|
||||
|
||||
function main() {
|
||||
log.console("=== Discord Social SDK Example ===")
|
||||
log.console(`Application ID: ${APPLICATION_ID}`)
|
||||
|
||||
// Initialize Discord client
|
||||
var init_result = discord.init(APPLICATION_ID)
|
||||
if (!init_result) {
|
||||
log.console("Failed to initialize Discord client")
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
log.console("Discord client initialized")
|
||||
|
||||
// Set up callbacks
|
||||
discord.on_status_changed(on_status_changed)
|
||||
discord.on_log(on_log, "info")
|
||||
|
||||
// Start the callback processing loop
|
||||
discord_tick()
|
||||
|
||||
// Start authentication
|
||||
start_auth()
|
||||
|
||||
log.console("Waiting for Discord events...")
|
||||
log.console("Press Ctrl+C to exit")
|
||||
}
|
||||
|
||||
// Run main
|
||||
main()
|
||||
15
tests/config.cm
Normal file
15
tests/config.cm
Normal file
@@ -0,0 +1,15 @@
|
||||
// Discord Application Configuration
|
||||
// Configure these values for your Discord application
|
||||
// Get yours at: https://discord.com/developers/applications
|
||||
|
||||
return {
|
||||
// Tangle Tart application
|
||||
APPLICATION_ID: "1446585686789586975",
|
||||
PUBLIC_KEY: "18ce78765f6d57a6d25af9b088271c96f8d7357723d246080ae58e49c71e79bc",
|
||||
|
||||
// OAuth2 redirect URI (configured in Discord Developer Portal)
|
||||
REDIRECT_URI: "http://127.0.0.1/callback",
|
||||
|
||||
// Callback processing interval (ms) - ~60fps
|
||||
CALLBACK_INTERVAL: 16
|
||||
}
|
||||
96
tests/discord.cm
Normal file
96
tests/discord.cm
Normal file
@@ -0,0 +1,96 @@
|
||||
// Discord SDK synchronous tests
|
||||
// These tests check the basic API without requiring authentication
|
||||
// For full integration tests with callbacks, see discord_integration.ce
|
||||
|
||||
var discord = use('discord')
|
||||
|
||||
// Test application ID (Tangle Tart)
|
||||
def TEST_APP_ID = "1446585686789586975"
|
||||
|
||||
return {
|
||||
// Test that the module loads correctly
|
||||
test_module_loads: function() {
|
||||
if (typeof discord != 'object') throw "Discord module should be an object"
|
||||
if (typeof discord.init != 'function') throw "discord.init should be a function"
|
||||
if (typeof discord.run_callbacks != 'function') throw "discord.run_callbacks should be a function"
|
||||
if (typeof discord.shutdown != 'function') throw "discord.shutdown should be a function"
|
||||
},
|
||||
|
||||
// Test initialization
|
||||
test_init: function() {
|
||||
var result = discord.init(TEST_APP_ID)
|
||||
if (!result) throw "discord.init should return true on success"
|
||||
},
|
||||
|
||||
// Test status before connection
|
||||
test_status_before_connect: function() {
|
||||
var status = discord.get_status()
|
||||
// Should be disconnected since we haven't connected yet
|
||||
if (status != "disconnected" && status != "not_initialized") {
|
||||
throw `Expected disconnected status, got: ${status}`
|
||||
}
|
||||
},
|
||||
|
||||
// Test is_authenticated before auth
|
||||
test_not_authenticated: function() {
|
||||
var auth = discord.is_authenticated()
|
||||
if (auth) throw "Should not be authenticated before auth flow"
|
||||
},
|
||||
|
||||
// Test get_default_scopes
|
||||
test_get_default_scopes: function() {
|
||||
var scopes = discord.get_default_scopes()
|
||||
if (!scopes || scopes.length == 0) throw "Default scopes should not be empty"
|
||||
log.console(`Default scopes: ${scopes}`)
|
||||
},
|
||||
|
||||
// Test get_current_user before auth (should return null)
|
||||
test_get_user_before_auth: function() {
|
||||
var user = discord.get_current_user()
|
||||
// Should be null since we're not authenticated
|
||||
if (user != null) {
|
||||
log.console("Note: User returned before auth - may be cached from previous session")
|
||||
}
|
||||
},
|
||||
|
||||
// Test get_relationships before auth
|
||||
test_get_relationships_before_auth: function() {
|
||||
var rels = discord.get_relationships()
|
||||
if (!Array.isArray(rels)) throw "get_relationships should return an array"
|
||||
log.console(`Relationships count: ${rels.length}`)
|
||||
},
|
||||
|
||||
// Test callback registration
|
||||
test_callback_registration: function() {
|
||||
// These should not throw
|
||||
discord.on_status_changed(function(status, error, detail) {
|
||||
log.console(`Status: ${status}`)
|
||||
})
|
||||
discord.on_log(function(msg, severity) {
|
||||
log.console(`[${severity}] ${msg}`)
|
||||
}, "info")
|
||||
|
||||
// Clear callbacks
|
||||
discord.on_status_changed(null)
|
||||
discord.on_log(null)
|
||||
},
|
||||
|
||||
// Test run_callbacks (should not throw even without connection)
|
||||
test_run_callbacks: function() {
|
||||
discord.run_callbacks()
|
||||
},
|
||||
|
||||
// Test clear_rich_presence (should not throw)
|
||||
test_clear_rich_presence: function() {
|
||||
discord.clear_rich_presence()
|
||||
},
|
||||
|
||||
// Test shutdown
|
||||
test_shutdown: function() {
|
||||
discord.shutdown()
|
||||
var status = discord.get_status()
|
||||
if (status != "not_initialized") {
|
||||
throw `Expected not_initialized after shutdown, got: ${status}`
|
||||
}
|
||||
}
|
||||
}
|
||||
166
tests/discord_integration.ce
Normal file
166
tests/discord_integration.ce
Normal file
@@ -0,0 +1,166 @@
|
||||
// Discord SDK Integration Test (Actor-based)
|
||||
// This test runs the full Discord integration flow with callbacks
|
||||
// It sends results back to the test runner via $_.parent
|
||||
//
|
||||
// To run standalone: cell discord_integration
|
||||
// As part of test suite: cell test
|
||||
|
||||
var discord = use('discord')
|
||||
var time = use('time')
|
||||
|
||||
// Test application ID (Tangle Tart)
|
||||
def TEST_APP_ID = "1446585686789586975"
|
||||
def CALLBACK_INTERVAL = 50
|
||||
def TEST_TIMEOUT = 10000 // 10 seconds
|
||||
|
||||
var test_start = time.number()
|
||||
var test_passed = false
|
||||
var test_error = null
|
||||
var status_received = false
|
||||
var connected = false
|
||||
|
||||
// Report test result to parent (test runner)
|
||||
function report_result(passed, error) {
|
||||
if ($_.parent) {
|
||||
$_.send($_.parent, {
|
||||
type: "test_result",
|
||||
passed: passed,
|
||||
error: error
|
||||
})
|
||||
} else {
|
||||
// Running standalone
|
||||
if (passed) {
|
||||
log.console("TEST PASSED")
|
||||
} else {
|
||||
log.console(`TEST FAILED: ${error}`)
|
||||
}
|
||||
}
|
||||
$_.stop()
|
||||
}
|
||||
|
||||
// Status change handler
|
||||
function on_status(status, error, detail) {
|
||||
log.console(`[Test] Status changed: ${status} (error: ${error}, detail: ${detail})`)
|
||||
status_received = true
|
||||
|
||||
if (status == "ready") {
|
||||
connected = true
|
||||
// Run post-connection tests
|
||||
run_connected_tests()
|
||||
} else if (error != "none") {
|
||||
// Connection error - this is expected if Discord isn't running
|
||||
log.console(`[Test] Connection error (expected if Discord app not running): ${error}`)
|
||||
// Still pass the test - we verified the callback system works
|
||||
report_result(true, null)
|
||||
}
|
||||
}
|
||||
|
||||
// Log handler
|
||||
function on_log(msg, severity) {
|
||||
log.console(`[Discord ${severity}] ${msg}`)
|
||||
}
|
||||
|
||||
// Tests that run after connection
|
||||
function run_connected_tests() {
|
||||
log.console("[Test] Running connected tests...")
|
||||
|
||||
try {
|
||||
// Test get_current_user
|
||||
var user = discord.get_current_user()
|
||||
if (user) {
|
||||
log.console(`[Test] Current user: ${user.display_name} (@${user.username})`)
|
||||
log.console(`[Test] User ID: ${user.id}`)
|
||||
} else {
|
||||
log.console("[Test] No user (not authenticated)")
|
||||
}
|
||||
|
||||
// Test get_relationships
|
||||
var rels = discord.get_relationships()
|
||||
log.console(`[Test] Relationships: ${rels.length}`)
|
||||
|
||||
// Test get_friends_count
|
||||
var count = discord.get_friends_count()
|
||||
log.console(`[Test] Friends count: ${count}`)
|
||||
|
||||
// Test update_rich_presence
|
||||
discord.update_rich_presence({
|
||||
type: "playing",
|
||||
state: "Running Tests",
|
||||
details: "Discord SDK Integration Test"
|
||||
}, function(success, error) {
|
||||
if (success) {
|
||||
log.console("[Test] Rich presence updated")
|
||||
} else {
|
||||
log.console(`[Test] Rich presence failed: ${error}`)
|
||||
}
|
||||
})
|
||||
|
||||
// All tests passed
|
||||
test_passed = true
|
||||
|
||||
// Give time for presence update callback
|
||||
$_.delay(function() {
|
||||
discord.clear_rich_presence()
|
||||
discord.shutdown()
|
||||
report_result(true, null)
|
||||
}, 1000)
|
||||
|
||||
} catch (e) {
|
||||
report_result(false, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
// Callback tick
|
||||
function tick() {
|
||||
discord.run_callbacks()
|
||||
|
||||
// Check timeout
|
||||
var elapsed = time.number() - test_start
|
||||
if (elapsed > TEST_TIMEOUT) {
|
||||
if (!status_received) {
|
||||
// No status callback received - SDK might not be working
|
||||
report_result(false, "Timeout: No status callback received")
|
||||
} else if (!connected) {
|
||||
// Status received but not connected - Discord app probably not running
|
||||
// This is acceptable for CI environments
|
||||
log.console("[Test] Timeout waiting for connection (Discord app may not be running)")
|
||||
discord.shutdown()
|
||||
report_result(true, null) // Pass - callbacks work, just no Discord app
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
$_.delay(tick, CALLBACK_INTERVAL)
|
||||
}
|
||||
|
||||
// Main test
|
||||
function main() {
|
||||
log.console("=== Discord SDK Integration Test ===")
|
||||
log.console(`Application ID: ${TEST_APP_ID}`)
|
||||
|
||||
try {
|
||||
// Initialize
|
||||
var init_result = discord.init(TEST_APP_ID)
|
||||
if (!init_result) {
|
||||
report_result(false, "discord.init failed")
|
||||
return
|
||||
}
|
||||
log.console("[Test] Discord initialized")
|
||||
|
||||
// Set up callbacks
|
||||
discord.on_status_changed(on_status)
|
||||
discord.on_log(on_log, "info")
|
||||
|
||||
// Start callback loop
|
||||
tick()
|
||||
|
||||
// Try to connect (will trigger status callbacks)
|
||||
discord.connect()
|
||||
log.console("[Test] Connection initiated, waiting for callbacks...")
|
||||
|
||||
} catch (e) {
|
||||
report_result(false, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user