1 Commits

Author SHA1 Message Date
John Alanbrook
816dd664c2 initial discord attempt 2025-07-17 20:46:42 -05:00
5 changed files with 470 additions and 1 deletions

View File

@@ -1,7 +1,7 @@
project('cell', ['c', 'cpp'],
version: '0.9.3',
meson_version: '>=1.4',
default_options : [ 'cpp_std=c++11'])
default_options : [ 'cpp_std=c++17'])
libtype = get_option('default_library')
@@ -302,6 +302,37 @@ else
message('Storefront: ' + storefront)
endif
# Discord SDK integration
if host_machine.system() != 'emscripten'
discord_sdk_path = meson.current_source_dir() / 'discord_social_sdk'
if host_machine.system() == 'darwin'
discord_lib_path = discord_sdk_path / 'lib' / 'release' / 'libdiscord_partner_sdk.dylib'
elif host_machine.system() == 'linux'
discord_lib_path = discord_sdk_path / 'lib' / 'release' / 'libdiscord_partner_sdk.so'
elif host_machine.system() == 'windows'
discord_lib_path = discord_sdk_path / 'lib' / 'release' / 'discord_partner_sdk.lib'
else
discord_lib_path = ''
endif
if fs.exists(discord_lib_path)
discord_dep = declare_dependency(
include_directories: include_directories('discord_social_sdk/include'),
link_args: [discord_lib_path]
)
deps += discord_dep
src += 'qjs_discord.cpp'
message('Discord SDK enabled')
else
add_project_arguments('-NDISCORD', language: ['c', 'cpp'])
message('Discord SDK not found at: ' + discord_lib_path)
endif
else
add_project_arguments('-NDISCORD', language: ['c', 'cpp'])
message('Discord SDK disabled for Emscripten')
endif
link_args = link
sources = []
src += [

View File

@@ -60,6 +60,9 @@
#ifndef NSTEAM
#include "qjs_steam.h"
#endif
#ifndef NDISCORD
#include "qjs_discord.h"
#endif
#include <signal.h>
@@ -1682,6 +1685,10 @@ void ffi_load(JSContext *js)
arrput(rt->module_registry, MISTLINE(steam));
#endif
#ifndef NDISCORD
arrput(rt->module_registry, MISTLINE(discord));
#endif
JSValue globalThis = JS_GetGlobalObject(js);
JSValue prosp = JS_NewObject(js);

344
source/qjs_discord.cpp Normal file
View File

@@ -0,0 +1,344 @@
#ifndef NDISCORD
// Include C headers first to get types
#include "qjs_discord.h"
#include "jsffi.h"
#include "qjs_blob.h"
// C++ headers
#define DISCORDPP_IMPLEMENTATION
#include <optional>
#include <discordpp.h>
#include <string.h>
#include <memory>
// Discord client wrapper
static std::shared_ptr<discordpp::Client> discord_client = nullptr;
static bool discord_initialized = false;
static bool discord_ready = false;
extern "C" {
// DISCORD INITIALIZATION
JSC_CCALL(discord_init,
if (discord_initialized) {
return JS_NewBool(js, true);
}
try {
discord_client = std::make_shared<discordpp::Client>();
discord_initialized = true;
return JS_NewBool(js, true);
} catch (const std::exception& e) {
return JS_ThrowInternalError(js, "Failed to initialize Discord client: %s", e.what());
}
)
JSC_CCALL(discord_shutdown,
if (discord_initialized && discord_client) {
discord_client.reset();
discord_initialized = false;
discord_ready = false;
}
return JS_NULL;
)
JSC_CCALL(discord_run_callbacks,
if (discord_initialized && discord_client) {
discordpp::RunCallbacks();
}
return JS_NULL;
)
// LOGGING CALLBACKS
static JSValue js_log_callback = JS_NULL;
static JSContext *js_log_context = nullptr;
static void discord_log_callback(std::string message, discordpp::LoggingSeverity severity) {
if (!JS_IsNull(js_log_callback) && js_log_context) {
const char* severity_str;
switch (severity) {
case discordpp::LoggingSeverity::Verbose: severity_str = "verbose"; break;
case discordpp::LoggingSeverity::Info: severity_str = "info"; break;
case discordpp::LoggingSeverity::Warning: severity_str = "warning"; break;
case discordpp::LoggingSeverity::Error: severity_str = "error"; break;
default: severity_str = "unknown"; break;
}
JSValue args[2];
args[0] = JS_NewString(js_log_context, message.c_str());
args[1] = JS_NewString(js_log_context, severity_str);
JSValue ret = JS_Call(js_log_context, js_log_callback, JS_NULL, 2, args);
JS_FreeValue(js_log_context, args[0]);
JS_FreeValue(js_log_context, args[1]);
if (JS_IsException(ret)) {
JS_GetException(js_log_context);
}
JS_FreeValue(js_log_context, ret);
}
}
JSC_CCALL(discord_add_log_callback,
if (!discord_client) return JS_ThrowInternalError(js, "Discord not initialized");
if (!JS_IsNull(js_log_callback)) {
JS_FreeValue(js, js_log_callback);
}
js_log_callback = JS_DupValue(js, argv[0]);
js_log_context = js;
discordpp::LoggingSeverity severity_level = discordpp::LoggingSeverity::Info;
if (argc > 1) {
int32_t severity_int;
JS_ToInt32(js, &severity_int, argv[1]);
severity_level = (discordpp::LoggingSeverity)severity_int;
}
discord_client->AddLogCallback(discord_log_callback, severity_level);
return JS_NULL;
)
// STATUS CALLBACKS
static JSValue js_status_callback = JS_NULL;
static JSContext *js_status_context = nullptr;
static void discord_status_callback(discordpp::Client::Status status, discordpp::Client::Error error, int32_t errorDetail) {
if (!JS_IsNull(js_status_callback) && js_status_context) {
std::string status_str_cpp = discordpp::Client::StatusToString(status);
std::string error_str_cpp = discordpp::Client::ErrorToString(error);
const char* status_str = status_str_cpp.c_str();
const char* error_str = error_str_cpp.c_str();
JSValue args[3];
args[0] = JS_NewString(js_status_context, status_str);
args[1] = JS_NewString(js_status_context, error_str);
args[2] = JS_NewInt32(js_status_context, errorDetail);
JSValue ret = JS_Call(js_status_context, js_status_callback, JS_NULL, 3, args);
JS_FreeValue(js_status_context, args[0]);
JS_FreeValue(js_status_context, args[1]);
JS_FreeValue(js_status_context, args[2]);
if (JS_IsException(ret)) {
JS_GetException(js_status_context);
}
JS_FreeValue(js_status_context, ret);
discord_ready = (status == discordpp::Client::Status::Ready);
}
}
JSC_CCALL(discord_set_status_callback,
if (!discord_client) return JS_ThrowInternalError(js, "Discord not initialized");
if (!JS_IsNull(js_status_callback)) {
JS_FreeValue(js, js_status_callback);
}
js_status_callback = JS_DupValue(js, argv[0]);
js_status_context = js;
discord_client->SetStatusChangedCallback(discord_status_callback);
return JS_NULL;
)
// CONNECTIONS (simplified)
JSC_CCALL(discord_connect,
if (!discord_client) return JS_ThrowInternalError(js, "Discord not initialized");
try {
discord_client->Connect();
return JS_NULL;
} catch (const std::exception& e) {
return JS_ThrowInternalError(js, "Connect failed: %s", e.what());
}
)
// RELATIONSHIPS (Friends)
JSC_CCALL(discord_get_relationships,
if (!discord_client) return JS_ThrowInternalError(js, "Discord not initialized");
if (!discord_ready) return JS_ThrowInternalError(js, "Discord not ready");
try {
auto relationships = discord_client->GetRelationships();
JSValue arr = JS_NewArray(js);
int index = 0;
for (const auto& rel : relationships) {
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "id", JS_NewString(js, std::to_string(rel.Id()).c_str()));
auto user_opt = rel.User();
if (user_opt.has_value()) {
auto user = user_opt.value();
JS_SetPropertyStr(js, obj, "username", JS_NewString(js, user.Username().c_str()));
JS_SetPropertyStr(js, obj, "display_name", JS_NewString(js, user.DisplayName().c_str()));
auto avatar_opt = user.Avatar();
if (avatar_opt.has_value()) {
JS_SetPropertyStr(js, obj, "avatar", JS_NewString(js, avatar_opt.value().c_str()));
} else {
JS_SetPropertyStr(js, obj, "avatar", JS_NewString(js, ""));
}
JS_SetPropertyStr(js, obj, "status", JS_NewInt32(js, (int)user.Status()));
}
JS_SetPropertyUint32(js, arr, index++, obj);
}
return arr;
} catch (const std::exception& e) {
return JS_ThrowInternalError(js, "Get relationships failed: %s", e.what());
}
)
// RICH PRESENCE (simplified)
JSC_CCALL(discord_update_rich_presence,
if (!discord_client) return JS_ThrowInternalError(js, "Discord not initialized");
if (!discord_ready) return JS_ThrowInternalError(js, "Discord not ready");
JSValue activity_obj = argv[0];
JSValue callback = argv[1];
try {
discordpp::Activity activity;
// Get activity type
JSValue type_val = JS_GetPropertyStr(js, activity_obj, "type");
if (!JS_IsNull(type_val)) {
int32_t type;
JS_ToInt32(js, &type, type_val);
activity.SetType((discordpp::ActivityTypes)type);
}
JS_FreeValue(js, type_val);
// Get state
JSValue state_val = JS_GetPropertyStr(js, activity_obj, "state");
if (!JS_IsNull(state_val)) {
const char *state = JS_ToCString(js, state_val);
if (state) {
activity.SetState(state);
JS_FreeCString(js, state);
}
}
JS_FreeValue(js, state_val);
// Get details
JSValue details_val = JS_GetPropertyStr(js, activity_obj, "details");
if (!JS_IsNull(details_val)) {
const char *details = JS_ToCString(js, details_val);
if (details) {
activity.SetDetails(details);
JS_FreeCString(js, details);
}
}
JS_FreeValue(js, details_val);
// Store callback for later use
static JSValue presence_callback = JS_NULL;
static JSContext *presence_context = nullptr;
if (!JS_IsNull(presence_callback)) {
JS_FreeValue(presence_context, presence_callback);
}
presence_callback = JS_DupValue(js, callback);
presence_context = js;
discord_client->UpdateRichPresence(activity, [](discordpp::ClientResult result) {
if (presence_context && !JS_IsNull(presence_callback)) {
JSValue args[1];
args[0] = JS_NewBool(presence_context, result.Successful());
JSValue ret = JS_Call(presence_context, presence_callback, JS_NULL, 1, args);
JS_FreeValue(presence_context, args[0]);
if (JS_IsException(ret)) {
JS_GetException(presence_context);
}
JS_FreeValue(presence_context, ret);
}
});
return JS_NULL;
} catch (const std::exception& e) {
return JS_ThrowInternalError(js, "Update rich presence failed: %s", e.what());
}
)
JSC_CCALL(discord_clear_rich_presence,
if (!discord_client) return JS_ThrowInternalError(js, "Discord not initialized");
if (!discord_ready) return JS_ThrowInternalError(js, "Discord not ready");
try {
discord_client->ClearRichPresence();
return JS_NULL;
} catch (const std::exception& e) {
return JS_ThrowInternalError(js, "Clear rich presence failed: %s", e.what());
}
)
// UTILITIES
JSC_CCALL(discord_get_default_presence_scopes,
std::string scopes = discordpp::Client::GetDefaultPresenceScopes();
return JS_NewString(js, scopes.c_str());
)
// Get SDK version
JSC_CCALL(discord_get_version,
return JS_NewString(js, "Discord Social SDK C++ v1.0");
)
// Check if ready
JSC_CCALL(discord_is_ready,
return JS_NewBool(js, discord_ready);
)
// Activity Types Constants
JSC_CCALL(discord_activity_types,
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "Playing", JS_NewInt32(js, (int)discordpp::ActivityTypes::Playing));
JS_SetPropertyStr(js, obj, "Streaming", JS_NewInt32(js, (int)discordpp::ActivityTypes::Streaming));
JS_SetPropertyStr(js, obj, "Listening", JS_NewInt32(js, (int)discordpp::ActivityTypes::Listening));
JS_SetPropertyStr(js, obj, "Watching", JS_NewInt32(js, (int)discordpp::ActivityTypes::Watching));
JS_SetPropertyStr(js, obj, "Competing", JS_NewInt32(js, (int)discordpp::ActivityTypes::Competing));
return obj;
)
// Function list for Discord module - simplified set
static const JSCFunctionListEntry js_discord_funcs[] = {
// Core functions
JS_CFUNC_DEF("init", 0, js_discord_init),
JS_CFUNC_DEF("shutdown", 0, js_discord_shutdown),
JS_CFUNC_DEF("run_callbacks", 0, js_discord_run_callbacks),
// Callbacks
JS_CFUNC_DEF("add_log_callback", 2, js_discord_add_log_callback),
JS_CFUNC_DEF("set_status_callback", 1, js_discord_set_status_callback),
// Connection
JS_CFUNC_DEF("connect", 0, js_discord_connect),
// Relationships
JS_CFUNC_DEF("get_relationships", 0, js_discord_get_relationships),
// Rich Presence
JS_CFUNC_DEF("update_rich_presence", 2, js_discord_update_rich_presence),
JS_CFUNC_DEF("clear_rich_presence", 0, js_discord_clear_rich_presence),
// Utilities
JS_CFUNC_DEF("get_default_presence_scopes", 0, js_discord_get_default_presence_scopes),
JS_CFUNC_DEF("get_version", 0, js_discord_get_version),
JS_CFUNC_DEF("is_ready", 0, js_discord_is_ready),
JS_CFUNC_DEF("activity_types", 0, js_discord_activity_types),
};
JSValue js_discord_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_discord_funcs, sizeof(js_discord_funcs) / sizeof(js_discord_funcs[0]));
return mod;
}
} // extern "C"
#endif // !NDISCORD

16
source/qjs_discord.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef QJS_DISCORD_H
#define QJS_DISCORD_H
#include "quickjs.h"
#ifdef __cplusplus
extern "C" {
#endif
JSValue js_discord_use(JSContext *js);
#ifdef __cplusplus
}
#endif
#endif // QJS_DISCORD_H

71
tests/discord.ce Normal file
View File

@@ -0,0 +1,71 @@
// Test Discord integration
var discord = use('discord');
var time = use('time');
log.console("Testing Discord integration...");
// Test basic initialization
if (discord.init()) {
log.console("✓ Discord initialized successfully");
// Test version info
log.console("Discord SDK version: " + discord.get_version());
// Test activity types
var types = discord.activity_types();
log.console("Activity types available:", types);
// Test status callback
discord.set_status_callback((status, error, detail) => {
log.console("Discord status changed:", status, error, detail);
if (status === "Ready") {
log.console("✓ Discord is ready! Setting rich presence...");
// Set rich presence
var activity = {
type: types.Playing,
state: "Testing Prosperon Engine",
details: "Running Discord Integration Test"
};
discord.update_rich_presence(activity, (success) => {
if (success) {
log.console("✓ Rich presence updated successfully!");
log.console("Check your Discord profile - you should see the activity");
log.console("Waiting 10 seconds so you can see the rich presence...");
// Wait 10 seconds so user can see the rich presence
time.delay(() => {
log.console("Clearing rich presence and shutting down...");
discord.clear_rich_presence();
discord.shutdown();
log.console("✓ Discord test completed successfully");
}, 10);
} else {
log.console("✗ Failed to set rich presence");
discord.shutdown();
}
});
}
});
// Test log callback
discord.add_log_callback((message, severity) => {
log.console("[Discord " + severity + "] " + message);
}, 2); // Info level
// Test default scopes
log.console("Default presence scopes: " + discord.get_default_presence_scopes());
log.console("✓ Discord module functions loaded successfully");
log.console("Attempting to connect to Discord...");
// Connect to Discord
discord.connect();
} else {
log.console("✗ Failed to initialize Discord");
}
log.console("Discord integration test started - waiting for connection...");