├── .clang-format ├── .gitignore ├── .gitmodules ├── EasyRP.ico ├── EasyRP.rc ├── README.md ├── config.cpp ├── config.hpp ├── config.ini ├── discord.cpp ├── discord.hpp ├── main.cpp ├── meson.build └── readme.txt /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 90 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 67 | Priority: 3 68 | - Regex: '.*' 69 | Priority: 1 70 | IncludeIsMainRegex: '(Test)?$' 71 | IndentCaseLabels: false 72 | IndentPPDirectives: None 73 | IndentWidth: 4 74 | IndentWrappedFunctionNames: false 75 | JavaScriptQuotes: Leave 76 | JavaScriptWrapImports: true 77 | KeepEmptyLinesAtTheStartOfBlocks: true 78 | MacroBlockBegin: '' 79 | MacroBlockEnd: '' 80 | MaxEmptyLinesToKeep: 1 81 | NamespaceIndentation: None 82 | ObjCBlockIndentWidth: 2 83 | ObjCSpaceAfterProperty: false 84 | ObjCSpaceBeforeProtocolList: true 85 | PenaltyBreakAssignment: 2 86 | PenaltyBreakBeforeFirstCallParameter: 19 87 | PenaltyBreakComment: 300 88 | PenaltyBreakFirstLessLess: 120 89 | PenaltyBreakString: 1000 90 | PenaltyExcessCharacter: 1000000 91 | PenaltyReturnTypeOnItsOwnLine: 60 92 | PointerAlignment: Right 93 | RawStringFormats: 94 | - Delimiter: pb 95 | Language: TextProto 96 | BasedOnStyle: google 97 | ReflowComments: true 98 | SortIncludes: true 99 | SortUsingDeclarations: true 100 | SpaceAfterCStyleCast: false 101 | SpaceAfterTemplateKeyword: true 102 | SpaceBeforeAssignmentOperators: true 103 | SpaceBeforeParens: ControlStatements 104 | SpaceInEmptyParentheses: false 105 | SpacesBeforeTrailingComments: 1 106 | SpacesInAngles: false 107 | SpacesInContainerLiterals: true 108 | SpacesInCStyleCastParentheses: false 109 | SpacesInParentheses: false 110 | SpacesInSquareBrackets: false 111 | Standard: Cpp11 112 | TabWidth: 4 113 | UseTab: Never 114 | ... 115 | 116 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # EasyRP specific files 2 | build*/ 3 | libdiscord-rpc.a 4 | discord-rpc.dll 5 | discord-rpc.lib 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "discord-rpc"] 2 | path = discord-rpc 3 | url = https://github.com/discordapp/discord-rpc 4 | -------------------------------------------------------------------------------- /EasyRP.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pizzabelly/EasyRP/be829db22573b97c1158fd73609c570241821f2b/EasyRP.ico -------------------------------------------------------------------------------- /EasyRP.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "EasyRP.ico" 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyRP (Custom Discord Rich Presence) 2 | 3 | EasyRP is a small program to use the discord-rpc, to make a custom rich presence/game on discord. 4 | using just an easy config file. 5 | 6 | **Usage** 7 | - First you need to register a Rich Presence application with discord 8 | - Go here https://discordapp.com/developers/applications/me 9 | - Make a new application **The name of the app will be the main name for the rich presence** 10 | - Enable rich presence for your app and add some assets 11 | - Download the latest release of EasyRP from here https://github.com/Pizzabelly/EasyRP/releases 12 | - if you are on Windows you also need [Visual C++ Redistributable](https://www.microsoft.com/en-US/download/details.aspx?id=48145) 13 | - Edit the config file with the information from your newly registered app 14 | - Run the [EasyRP executable](https://github.com/Pizzabelly/EasyRP/releases) (it should open a cmd window) 15 | - It *should* report errors from your config file (if there are any) 16 | - Discord should show the game on your profile 17 | - if not, add the exe as a game on discord and the file path should change to your presence 18 | 19 | - You can edit the config any time while the program is running to change the presence (make sure to save the file) 20 | 21 | **Timestamps** 22 | The Start and End timestamps are in epoch/unix time. 23 | Your desired values can be found [here](https://www.epochconverter.com/). 24 | For elapsed time set only the StartTimestamp. For remaining time set both. 25 | Though discord seems to only care about hours/minutes/seconds. 26 | As it doesnt go above 24hrs either way ¯\\\_(ツ)\_/¯ 27 | 28 | **Building** 29 | To build EasyRP from source you need the following 30 | - any c++ compiler (cl, g++, clang++, etc) 31 | - Meson 32 | - Ninja 33 | - CMake (for discord-rpc library) 34 | 35 | Build discord-rpc 36 | - ``` git clone https://github.com/Pizzabelly/EasyRP --recurse-submodules ``` 37 | - ``` cd EasyRP/discord-rpc ``` 38 | - ``` mkdir build && cd build ``` 39 | - ``` cmake .. -DENABLE_IO_THREAD=OFF ``` -DENABLE_IO_THREAD option will prevent a link error (see [#49](https://github.com/Pizzabelly/EasyRP/issues/49)) 40 | 41 | Now depending on your platform building the discord-rpc library will be different 42 | - Unix: ``` make ``` 43 | - Windows (visual studio): ``` msbuild /p:Configuration=Release ``` 44 | - Windows (mingw): ``` mingw32-make.exe ``` 45 | Now the library should be under src/[lib]discord-rpc.[lib|a], move that to the root of the EasyRP repo 46 | 47 | To build EasyRP (in the root of the repo) 48 | - ``` meson --buildtype=release build ``` 49 | - ``` ninja -C build ``` 50 | 51 | Now the EasyRP executable will be under the build directory! 52 | -------------------------------------------------------------------------------- /config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "discord.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define CONFIG_PATH "config.ini" 9 | 10 | template T setVar(T val, config_t *c) { 11 | c->changed = true; 12 | return val; 13 | } 14 | 15 | // check and set the global presence config 16 | void config_t::update() { 17 | // open config file 18 | std::ifstream config_file(CONFIG_PATH); 19 | if (config_file.fail()) { 20 | printf("config file not found\n"); 21 | Shutdown(1); 22 | } 23 | // "parse" config file 24 | // this is super specific and is NOT proper ini parsing 25 | // but it works, saves memory and avoids massive dependencies 26 | for (std::string line; std::getline(config_file, line);) { 27 | // if line is ini comment (;), whitespace, or '[', skip it 28 | char first = line.front(); 29 | if (first == ';' || first == ' ' || first == '[') 30 | continue; 31 | 32 | std::istringstream line_stream; 33 | line_stream.str(line); 34 | 35 | std::string key; 36 | if (std::getline(line_stream, key, '=')) { 37 | key.erase(std::remove_if(key.begin(), key.end(), ::isspace), key.end()); 38 | std::string value; 39 | if (!std::getline(line_stream, value)) 40 | value = ""; 41 | if (isspace(value.front()) && isspace(value.back())) 42 | value.erase(0, 1); 43 | if (key == "ClientID" && value.compare(this->client_id) != 0) { 44 | this->client_id = setVar(value, this); 45 | } else if (key == "State" && value.compare(this->state) != 0) { 46 | this->state = setVar(value, this); 47 | } else if (key == "Details" && value.compare(this->details) != 0) { 48 | this->details = setVar(value, this); 49 | } else if (key == "LargeImage" && value.compare(this->large_img.key) != 0) { 50 | this->large_img.key = setVar(value, this); 51 | } else if (key == "SmallImage" && value.compare(this->small_img.key) != 0) { 52 | this->small_img.key = setVar(value, this); 53 | } else if (key == "LargeImageTooltip" && 54 | value.compare(this->large_img.text) != 0) { 55 | this->large_img.text = setVar(value, this); 56 | } else if (key == "SmallImageTooltip" && 57 | value.compare(this->small_img.text) != 0) { 58 | this->small_img.text = setVar(value, this); 59 | } 60 | 61 | // special conditions for timestamps to avoid bad values 62 | else if (key == "StartTimestamp") { 63 | long long num_value = std::strtoll(value.c_str(), NULL, 10); 64 | if (num_value != this->start_time) 65 | this->start_time = setVar(num_value, this); 66 | } else if (key == "EndTimestamp") { 67 | long long num_value = std::strtoll(value.c_str(), NULL, 10); 68 | if (num_value != this->end_time) 69 | this->end_time = setVar(num_value, this); 70 | } 71 | } 72 | } 73 | } 74 | 75 | // print values for the current settings from the config file 76 | void config_t::print() { 77 | printf("\nCurrent Presence (%s) :", this->client_id.c_str()); 78 | printf("\nState: %s", this->state.c_str()); 79 | printf("\nDetails: %s", this->details.c_str()); 80 | printf("\nLarge Image: '%s' with toolip, '%s'", this->large_img.key.c_str(), 81 | this->large_img.text.c_str()); 82 | printf("\nSmall Image: '%s' with toolip, '%s'", this->small_img.key.c_str(), 83 | this->small_img.text.c_str()); 84 | printf("\nStart Time: %lld", this->start_time); 85 | printf("\nEnd Time: %lld\n", this->end_time); 86 | } 87 | -------------------------------------------------------------------------------- /config.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct pimage_t { 4 | std::string key; 5 | std::string text; 6 | }; 7 | 8 | // struct type to hold info about the games configuration 9 | struct config_t { 10 | // id for the discord developer app 11 | std::string client_id; 12 | 13 | // text to show for now playing 14 | std::string details; 15 | std::string state; 16 | 17 | // images to show for now playing 18 | pimage_t small_img; 19 | pimage_t large_img; 20 | 21 | // timestamps for game; note: these usually have to be within about 24 hours 22 | // :( 23 | long long start_time = 0; 24 | long long end_time = 0; 25 | 26 | // true if presence needs to be updated 27 | bool changed = true; 28 | 29 | void update(); 30 | void print(); 31 | }; 32 | 33 | extern struct config_t config; 34 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | ;######################################################################## 2 | ;# ______ _____ _____ # 3 | ;# | ____| | __ \| __ \ # 4 | ;# | |__ __ _ ___ _ _| |__) | |__) | # 5 | ;# | __| / _` / __| | | | _ /| ___/ # 6 | ;# | |___| (_| \__ \ |_| | | \ \| | # 7 | ;# |______\__,_|___/\__, |_| \_\_| # 8 | ;# __/ | # 9 | ;# |___/ https://github.com/Pizzabelly/EasyRP # 10 | ;# # 11 | ;# # 12 | ;# Optional Settings: SmallImage, LargeImageTooltip, SmallImageTooltip, # 13 | ;# StartTimestamp, EndTimestamp # 14 | ;# *ALL OTHERS ARE REQUIRED* # 15 | ;# # 16 | ;# timestamps are in unix time -> https://www.epochconverter.com/ # 17 | ;# # 18 | ;######################################################################## 19 | 20 | [Identifiers] 21 | ClientID=123456789012345678 22 | 23 | [State] 24 | State= 25 | Details= 26 | StartTimestamp= 27 | EndTimestamp= 28 | 29 | [Images] 30 | LargeImage= 31 | LargeImageTooltip= 32 | SmallImage= 33 | SmallImageTooltip= 34 | -------------------------------------------------------------------------------- /discord.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define DISCORD_DISABLE_IO_THREAD 9 | #include "discord_rpc.h" 10 | 11 | // shutdown discord-rpc 12 | void Shutdown(int sig) { 13 | printf("\nshutting down...\n"); 14 | Discord_Shutdown(); 15 | getchar(); 16 | exit(sig); 17 | } 18 | 19 | // handle discord ready event 20 | void handleDiscordReady(const DiscordUser *u) { 21 | printf("\nDisplaying Presence for %s#%s\n", u->username, u->discriminator); 22 | } 23 | 24 | // handle discord disconnected event 25 | void handleDiscordDisconnected(int errcode, const char *message) { 26 | printf("\nDiscord: disconnected (%d: %s)\n", errcode, message); 27 | } 28 | 29 | // handle discord error event 30 | void handleDiscordError(int errcode, const char *message) { 31 | printf("\nDiscord: error (%d: %s)\n", errcode, message); 32 | Shutdown(1); 33 | } 34 | 35 | // update discord rich presence 36 | void updatePresence(config_t *c) { 37 | // set required variables 38 | DiscordRichPresence discordPresence; 39 | memset(&discordPresence, 0, sizeof(discordPresence)); 40 | 41 | // make sure required parameters are set, if not dont update untill they are 42 | // corrected 43 | if (c->state.length() < 1 || c->state.length() > 128) { 44 | printf("\nState parameter is too long or not set\n"); 45 | return; 46 | } 47 | if (c->details.length() < 1 || c->details.length() > 128) { 48 | printf("\nDetails parameter is too long or not set\n"); 49 | return; 50 | } 51 | if (c->large_img.key.length() < 1 || c->large_img.key.length() > 100) { 52 | printf("\nLargeImage parameter not set\n"); 53 | return; 54 | } 55 | 56 | discordPresence.state = c->state.c_str(); 57 | discordPresence.largeImageKey = c->large_img.key.c_str(); 58 | char buffer[256]; 59 | sprintf(buffer, "%s", c->details.c_str()); 60 | discordPresence.details = buffer; 61 | 62 | if (c->start_time == LLONG_MAX || c->start_time == LLONG_MIN || 63 | c->end_time == LLONG_MAX || c->end_time == LLONG_MIN) { 64 | printf("wew!, one (or both) of your timestamps is WAY too big"); 65 | return; 66 | } 67 | 68 | if (c->start_time >= 0 && c->start_time != 0LL) 69 | discordPresence.startTimestamp = (int64_t)c->start_time; 70 | if (c->end_time >= 0 && c->end_time != 0LL) 71 | discordPresence.endTimestamp = (int64_t)c->end_time; 72 | 73 | // dont set optional variables if they are not defined in the config 74 | if (c->small_img.key.length() >= 1) 75 | discordPresence.smallImageKey = c->small_img.key.c_str(); 76 | if (c->small_img.text.length() >= 1) 77 | discordPresence.smallImageText = c->small_img.text.c_str(); 78 | if (c->large_img.text.length() >= 1) 79 | discordPresence.largeImageText = c->large_img.text.c_str(); 80 | 81 | // actaully update the presence 82 | Discord_UpdatePresence(&discordPresence); 83 | } 84 | 85 | void refreshDiscord() { 86 | // manually handle this 87 | Discord_UpdateConnection(); 88 | 89 | // callbacks 90 | Discord_RunCallbacks(); 91 | } 92 | 93 | // initialize discord rich presence 94 | void initDiscord(std::string client_id) { 95 | DiscordEventHandlers handlers; 96 | memset(&handlers, 0, sizeof(handlers)); 97 | handlers.ready = handleDiscordReady; 98 | handlers.errored = handleDiscordError; 99 | handlers.disconnected = handleDiscordDisconnected; 100 | if (client_id.length() < 1 || client_id.compare("123456789012345678") == 0) { 101 | printf("ClientID not correct (or not set).\nUnless by god you somehow " 102 | "got 123456789012345678 as your clientid please change this to " 103 | "the one you registered on the website"); 104 | Shutdown(1); 105 | } 106 | Discord_Initialize(client_id.c_str(), &handlers, 1, NULL); 107 | } 108 | -------------------------------------------------------------------------------- /discord.hpp: -------------------------------------------------------------------------------- 1 | void updatePresence(config_t *c); 2 | 3 | void initDiscord(std::string client_id); 4 | 5 | void refreshDiscord(); 6 | 7 | void Shutdown(int sig); 8 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "discord.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | int main(void) { 8 | // define when to shutdown 9 | signal(SIGINT, Shutdown); 10 | signal(SIGTERM, Shutdown); 11 | #ifdef SIGBREAK 12 | signal(SIGBREAK, Shutdown); 13 | #endif 14 | 15 | // main config instance 16 | config_t config; 17 | config.update(); 18 | 19 | // start discord-rpc 20 | initDiscord(config.client_id); 21 | 22 | // loop to keep program running also to check for updated config 23 | while (true) { 24 | config.update(); 25 | if (config.changed) { 26 | // print and set variables for the presence 27 | config.print(); 28 | updatePresence(&config); 29 | config.changed = false; 30 | } 31 | refreshDiscord(); 32 | std::this_thread::sleep_for(std::chrono::seconds(1)); 33 | } 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('EasyRP', 'cpp', 2 | version : '3.0', 3 | default_options : ['warning_level=3', 'cpp_std=c++14', 'b_ndebug=if-release']) 4 | 5 | compiler = meson.get_compiler('cpp') 6 | 7 | if host_machine.system() == 'windows' 8 | windows = import('windows') 9 | win_resources = windows.compile_resources('EasyRP.rc') 10 | discordrpc = compiler.find_library('discord-rpc', dirs: meson.source_root()) 11 | src = ['main.cpp', 'config.cpp', 'discord.cpp', win_resources] 12 | else 13 | discordrpc = compiler.find_library('libdiscord-rpc', dirs: meson.source_root()) 14 | src = ['main.cpp', 'config.cpp', 'discord.cpp'] 15 | endif 16 | 17 | rpc_inc = include_directories('discord-rpc/include') 18 | 19 | exe = executable('easyrp', src, 20 | include_directories : rpc_inc, 21 | dependencies : [discordrpc], 22 | install : false) 23 | 24 | test('basic', exe) 25 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | EasyRP (Custom Discord Rich Presence) https://github.com/Pizzabelly/EasyRP 2 | 1. First you need to register a Rich Presence application with discord 3 | - Go here https://discordapp.com/developers/applications/me 4 | - Make a new application **The name of the app will be the main name for the rich presence** 5 | - Enable rich presence for your app and add some assets 6 | 2 Download the latest release of EasyRP from here https://github.com/Pizzabelly/EasyRP/releases 7 | 3 Edit the config file with the information from your newly registered app 8 | 4 Run easyrp (it should open a cmd window) 9 | - It *should* report errors from your config file (if there are any) 10 | 5 Discord should show the game on your profile 11 | - if not, add the exe as a game on discord and the file path should change to your presence 12 | 13 | You can edit the config any time while the program is running to change the presence (make sure to save the file) 14 | --------------------------------------------------------------------------------