Skip to content

Commit 76bbdf6

Browse files
committed
Added Discord RPC support
- Added Allow debug logs to cmakelist instead of debug runs - Added Discord rpc SDK supporting buttons and type - Added Discord RPC handling - Added Discord RPC Events for each stremio site - Added DiscordRPC settings toggle to disable RPC - Changed App image to a cleaner one
1 parent a67eeba commit 76bbdf6

File tree

12 files changed

+209
-6
lines changed

12 files changed

+209
-6
lines changed

CMakeLists.txt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@ project(stremio VERSION "5.0.17")
55
set(CMAKE_CXX_STANDARD 20)
66
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
77

8+
option(DEBUG_LOG "Allow debug logs" ON)
9+
810
# Locate MPV
911
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
1012
# 64-bit architecture
1113
set(MPV_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/libmpv/x86_64/include")
1214
set(MPV_LIBRARY "${CMAKE_CURRENT_SOURCE_DIR}/deps/libmpv/x86_64/mpv.lib")
1315
set(MPV_DLL "${CMAKE_CURRENT_SOURCE_DIR}/deps/libmpv/x86_64/libmpv-2.dll")
16+
17+
set(DISCORD_LIB "${CMAKE_CURRENT_SOURCE_DIR}/deps/discord-rpc/win64-static/lib/discord-rpc.lib")
18+
set(DISCORD_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/discord-rpc/win64-static/include")
1419
else()
1520
# 32-bit architecture
1621
set(MPV_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/libmpv/i686/include")
1722
set(MPV_LIBRARY "${CMAKE_CURRENT_SOURCE_DIR}/deps/libmpv/i686/mpv.lib")
1823
set(MPV_DLL "${CMAKE_CURRENT_SOURCE_DIR}/deps/libmpv/i686/libmpv-2.dll")
19-
endif()
2024

25+
set(DISCORD_LIB "${CMAKE_CURRENT_SOURCE_DIR}/deps/discord-rpc/win32-static/lib/discord-rpc.lib")
26+
set(DISCORD_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/discord-rpc/win32-static/include")
27+
endif()
2128

29+
include_directories(${DISCORD_INCLUDE_DIR})
2230
include_directories(${MPV_INCLUDE_DIR})
2331

2432
find_package(OpenSSL REQUIRED)
@@ -54,6 +62,8 @@ set(SOURCES
5462
src/resource.h
5563
src/utils/extensions.cpp
5664
src/utils/extensions.h
65+
src/utils/discord.cpp
66+
src/utils/discord.h
5767
)
5868

5969
add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
@@ -72,9 +82,10 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
7282
OpenSSL::Crypto
7383
CURL::libcurl
7484
${MPV_LIBRARY}
85+
${DISCORD_LIB}
7586
)
7687

77-
target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CONFIG:Debug>:DEBUG_BUILD>)
88+
target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG_LOG)
7889

7990
# Copy MPV DLL
8091
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD

images/stremio2.ico

-18.9 KB
Binary file not shown.

images/stremio2.png

-10.6 KB
Loading

src/core/globals.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ HWND g_trayHwnd = nullptr;
6767
bool g_pauseOnMinimize = true;
6868
bool g_pauseOnLostFocus = false;
6969
bool g_allowZoom = false;
70+
bool g_isRpcOn = true;
7071

7172
// Tray sizes
7273
int g_tray_itemH = 31;

src/core/globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ extern HWND g_trayHwnd;
111111
extern bool g_pauseOnMinimize;
112112
extern bool g_pauseOnLostFocus;
113113
extern bool g_allowZoom;
114+
extern bool g_isRpcOn;
114115

115116
// Tray sizes
116117
extern int g_tray_itemH;

src/main.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <iostream>
88
#include <sstream>
99
#include <VersionHelpers.h>
10+
#include "discord_rpc.h"
1011

1112
#include "ui/mainwindow.h"
1213
#include "core/globals.h"
@@ -20,6 +21,7 @@
2021
#include "updater/updater.h"
2122
#include "utils/helpers.h"
2223
#include "utils/config.h"
24+
#include "utils/discord.h"
2325
// This started as 1-week project so please don't take the code to seriously
2426
int main(int argc, char* argv[])
2527
{
@@ -85,6 +87,9 @@ int main(int argc, char* argv[])
8587
// Load config
8688
LoadSettings();
8789

90+
// Initialize Discord RPC
91+
InitializeDiscord();
92+
8893
// Updater
8994
g_updaterThread=std::thread(RunAutoUpdaterOnce);
9095
g_updaterThread.detach();
@@ -154,6 +159,9 @@ int main(int argc, char* argv[])
154159
while(GetMessage(&msg, nullptr, 0, 0)) {
155160
TranslateMessage(&msg);
156161
DispatchMessage(&msg);
162+
163+
// Run Discord RPC callbacks
164+
Discord_RunCallbacks();
157165
}
158166

159167
if(g_darkBrush){

src/ui/mainwindow.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "../ui/splash.h"
1616
#include "../webview/webview.h"
1717
#include "../updater/updater.h"
18+
#include "../utils/discord.h"
1819

1920
// Single-instance
2021
bool FocusExistingInstance(const std::wstring &protocolArg)
@@ -124,7 +125,7 @@ void SendToJS(const std::string &eventName, const nlohmann::json &eventData)
124125
std::wstring wpayload(payload.begin(), payload.end());
125126
g_webview->PostWebMessageAsString(wpayload.c_str());
126127

127-
#ifdef DEBUG_BUILD
128+
#ifdef DEBUG_LOG
128129
std::cout << "[Native->JS] " << payload << "\n";
129130
#endif
130131
}
@@ -226,6 +227,8 @@ void HandleEvent(const std::string &ev, std::vector<std::string> &args)
226227
} else {
227228
g_webview->Navigate(uri.c_str());
228229
}
230+
} else if (ev == "activity") {
231+
SetDiscordPresenceFromArgs(args);
229232
} else {
230233
std::cout<<"Unknown event="<<ev<<"\n";
231234
}
@@ -235,7 +238,7 @@ void HandleEvent(const std::string &ev, std::vector<std::string> &args)
235238
void HandleInboundJSON(const std::string &msg)
236239
{
237240
try {
238-
#ifdef DEBUG_BUILD
241+
#ifdef DEBUG_LOG
239242
std::cout << "[JS -> NATIVE]: " << msg << std::endl;
240243
#endif
241244

src/utils/config.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ void LoadSettings()
2929
g_allowZoom = GetPrivateProfileIntW(L"General", L"AllowZoom", 0, iniPath.c_str());
3030
g_pauseOnMinimize = (GetPrivateProfileIntW(L"General", L"PauseOnMinimize", 1, iniPath.c_str()) == 1);
3131
g_pauseOnLostFocus = (GetPrivateProfileIntW(L"General", L"PauseOnLostFocus", 0, iniPath.c_str()) == 1);
32+
g_isRpcOn = (GetPrivateProfileIntW(L"General", L"DiscordRPC", 1, iniPath.c_str()) == 1);
3233
//Mpv
3334
wchar_t voBuffer[32];
3435
GetPrivateProfileStringW(L"MPV", L"VideoOutput", L"gpu-next", voBuffer, 32, iniPath.c_str());
@@ -47,12 +48,14 @@ void SaveSettings()
4748
const wchar_t* pauseMinVal = g_pauseOnMinimize ? L"1" : L"0";
4849
const wchar_t* pauseFocVal = g_pauseOnLostFocus ? L"1" : L"0";
4950
const wchar_t* allowZoomVal = g_allowZoom ? L"1" : L"0";
51+
const wchar_t* rpcVal = g_isRpcOn ? L"1" : L"0";
5052

5153
WritePrivateProfileStringW(L"General", L"CloseOnExit", closeVal, iniPath.c_str());
5254
WritePrivateProfileStringW(L"General", L"UseDarkTheme", darkVal, iniPath.c_str());
5355
WritePrivateProfileStringW(L"General", L"PauseOnMinimize", pauseMinVal, iniPath.c_str());
5456
WritePrivateProfileStringW(L"General", L"PauseOnLostFocus", pauseFocVal, iniPath.c_str());
5557
WritePrivateProfileStringW(L"General", L"AllowZoom", allowZoomVal, iniPath.c_str());
58+
WritePrivateProfileStringW(L"General", L"DiscordRPC", rpcVal, iniPath.c_str());
5659
WriteIntToIni(L"MPV", L"InitialVolume", g_currentVolume, iniPath);
5760
}
5861

src/utils/crashlog.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "../utils/helpers.h"
1010
#include <gdiplus.h>
1111
#include <sstream>
12+
#include "discord_rpc.h"
1213

1314
#include "config.h"
1415

@@ -64,4 +65,6 @@ void Cleanup()
6465
if(g_gdiplusToken) {
6566
Gdiplus::GdiplusShutdown(g_gdiplusToken);
6667
}
68+
69+
Discord_Shutdown();
6770
}

src/utils/discord.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#include <iostream>
2+
#include "../core/globals.h"
3+
#include "crashlog.h"
4+
#include "discord_rpc.h"
5+
6+
void Discord_Ready(const DiscordUser* user) {
7+
std::cout << "[DISCORD]: Connected to Discord user: " + std::string(user->username);
8+
}
9+
10+
void Discord_Disconnected(int errorCode, const char* message) {
11+
std::cout << "[DISCORD]: Disconnected (" + std::to_string(errorCode) + "): " + std::string(message);
12+
}
13+
14+
void Discord_Error(int errorCode, const char* message) {
15+
std::cout << "[DISCORD]: Error (" + std::to_string(errorCode) + "): " + std::string(message);
16+
AppendToCrashLog("[DISCORD]: Error (" + std::to_string(errorCode) + "): " + std::string(message));
17+
}
18+
19+
void InitializeDiscord()
20+
{
21+
DiscordEventHandlers handlers{};
22+
memset(&handlers, 0, sizeof(handlers));
23+
handlers.ready = Discord_Ready;
24+
handlers.disconnected = Discord_Disconnected;
25+
handlers.errored = Discord_Error;
26+
27+
Discord_Initialize("1361448446862692492", &handlers, 1, nullptr);
28+
}
29+
30+
// Encapsulated presence setters
31+
32+
static void SetDiscordWatchingPresence(
33+
const std::vector<std::string>& args
34+
) {
35+
// expects:
36+
// 0: generic "watching" identifier
37+
// 1: type (movie, series)
38+
// 2: title
39+
// 3: season
40+
// 4: episode
41+
// 5: episode name
42+
// 6: episode thumbnail (small image) (Optional)
43+
// 7: show/movie image (large image)
44+
// 8: elapsed seconds
45+
// 9: duration seconds
46+
// 10: isPaused ("yes" or "no") (Optional)
47+
// 11: more detail button link (imdb link) (Optional)
48+
// 12: watch on stremio button link (stremio link) (Optional)
49+
DiscordRichPresence discordPresence{};
50+
memset(&discordPresence, 0, sizeof(discordPresence));
51+
52+
discordPresence.type = DISCORD_ACTIVITY_TYPE_WATCHING;
53+
54+
// Common fields (required)
55+
discordPresence.details = args[2].c_str(); // Title
56+
discordPresence.largeImageKey = args[7].c_str();
57+
discordPresence.largeImageText = args[2].c_str();
58+
59+
// Handle paused state (optional)
60+
bool isPaused = (!args[10].empty() && args[10] == "yes");
61+
62+
if (isPaused) {
63+
discordPresence.state = "Paused";
64+
discordPresence.startTimestamp = 0;
65+
discordPresence.endTimestamp = 0;
66+
} else {
67+
std::time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
68+
int elapsedSeconds = std::stoi(args[8]);
69+
int durationSeconds = std::stoi(args[9]);
70+
71+
discordPresence.startTimestamp = currentTime - elapsedSeconds;
72+
discordPresence.endTimestamp = currentTime + (durationSeconds - elapsedSeconds);
73+
74+
if (args[1] == "series") {
75+
// Series-specific fields
76+
std::string state = args[5] + " (S" + args[3] + "-E" + args[4] + ")";
77+
discordPresence.state = state.c_str();
78+
79+
if (!args[6].empty()) {
80+
discordPresence.smallImageKey = args[6].c_str();
81+
discordPresence.smallImageText = args[5].c_str();
82+
}
83+
} else {
84+
discordPresence.state = "Enjoying a Movie";
85+
}
86+
}
87+
88+
// Buttons setup (optional)
89+
if (!args[11].empty()) {
90+
discordPresence.button1Label = "More Details";
91+
discordPresence.button1Url = args[11].c_str();
92+
}
93+
94+
if (!args[12].empty()) {
95+
discordPresence.button2Label = "Watch on Stremio";
96+
discordPresence.button2Url = args[12].c_str();
97+
}
98+
99+
Discord_UpdatePresence(&discordPresence);
100+
}
101+
102+
static void SetDiscordMetaDetailPresence(const std::vector<std::string>& args) {
103+
// args structure:
104+
// 0: embed type ("meta-detail")
105+
// 1: type ("movie" or "series")
106+
// 2: title
107+
// 3: image URL
108+
109+
DiscordRichPresence discordPresence{};
110+
memset(&discordPresence, 0, sizeof(discordPresence));
111+
112+
discordPresence.type = DISCORD_ACTIVITY_TYPE_WATCHING;
113+
discordPresence.details = args[2].c_str(); // Title (show/movie)
114+
discordPresence.largeImageKey = args[3].c_str();
115+
discordPresence.largeImageText = args[2].c_str();
116+
117+
// Engaging state
118+
discordPresence.state = args[1] == "movie"
119+
? "Exploring a Movie"
120+
: "Exploring a Series";
121+
122+
Discord_UpdatePresence(&discordPresence);
123+
}
124+
125+
static void SetDiscordDiscoverPresence(const char *const details, const char *const state) {
126+
std::cout << "[DISCORD]: Setting discover Presence";
127+
128+
DiscordRichPresence discordPresence{};
129+
memset(&discordPresence, 0, sizeof(discordPresence));
130+
discordPresence.type = DISCORD_ACTIVITY_TYPE_WATCHING;
131+
discordPresence.state = state;
132+
discordPresence.details = details;
133+
discordPresence.largeImageKey = "https://raw.githubusercontent.com/Stremio/stremio-web/refs/heads/development/images/icon.png";
134+
discordPresence.largeImageText = "Stremio";
135+
Discord_UpdatePresence(&discordPresence);
136+
}
137+
138+
void SetDiscordPresenceFromArgs(const std::vector<std::string>& args) {
139+
if (!g_isRpcOn || args.empty()) {
140+
return;
141+
}
142+
143+
const std::string& embedType = args[0];
144+
if (embedType == "watching" && args.size() >= 12) {
145+
SetDiscordWatchingPresence(args);
146+
} else if (embedType == "meta-detail" && args.size() >= 4) {
147+
SetDiscordMetaDetailPresence(args);
148+
} else if (embedType == "board") {
149+
SetDiscordDiscoverPresence("Resuming Favorites", "On Board");
150+
} else if (embedType == "discover") {
151+
SetDiscordDiscoverPresence("Finding New Gems", "In Discover");
152+
} else if (embedType == "library") {
153+
SetDiscordDiscoverPresence("Revisiting Old Favorites", "In Library");
154+
} else if (embedType == "calendar") {
155+
SetDiscordDiscoverPresence("Planning My Next Binge", "On Calendar");
156+
} else if (embedType == "addons") {
157+
SetDiscordDiscoverPresence("Exploring Add-ons", "In Add-ons");
158+
} else if (embedType == "settings") {
159+
SetDiscordDiscoverPresence("Tuning Preferences", "In Settings");
160+
} else if (embedType == "search") {
161+
SetDiscordDiscoverPresence("Searching for Shows & Movies", "In Search");
162+
} else if (embedType == "clear") {
163+
Discord_ClearPresence();
164+
}
165+
// Add more presence types here...
166+
}

0 commit comments

Comments
 (0)