From 3100d13fc08cf04f831cd8167e98394c22389f59 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Mon, 6 Jan 2025 14:06:41 +1000 Subject: discord: Improve game image handling and UI presentation Updates the Discord Rich Presence implementation to use Tinfoil's media server for game images and improves the overall presentation. Technical changes: - Switch to Tinfoil media server for game images - Add proper title ID handling and display - Improve error handling for image requests - Increase network timeout to 10 seconds - Cache URL to prevent string destruction - Update Discord client ID - Clean up code formatting and organization UI/UX improvements: - Remove title ID from game state display - Update default text to be more descriptive - Improve error logging for failed image fetches - Maintain image persistence between updates This change provides better game image support and a cleaner Discord presence display while improving reliability of the integration. --- src/citron/discord_impl.cpp | 166 +++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 81 deletions(-) (limited to 'src/citron/discord_impl.cpp') diff --git a/src/citron/discord_impl.cpp b/src/citron/discord_impl.cpp index 6391fdae4..f1607de44 100644 --- a/src/citron/discord_impl.cpp +++ b/src/citron/discord_impl.cpp @@ -2,116 +2,120 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include + #include #include + #include + #include #include + #include +#include "citron/discord_impl.h" + +#include "citron/uisettings.h" + #include "common/common_types.h" + #include "common/string_util.h" + #include "core/core.h" + #include "core/loader/loader.h" -#include "citron/discord_impl.h" -#include "citron/uisettings.h" namespace DiscordRPC { -DiscordImpl::DiscordImpl(Core::System& system_) : system{system_} { - DiscordEventHandlers handlers{}; - // The number is the client ID for citron, it's used for images and the - // application name - Discord_Initialize("712465656758665259", &handlers, 1, nullptr); +DiscordImpl::DiscordImpl(Core::System & system_): system { +system_ +} { +DiscordEventHandlers handlers {}; +// The number is the client ID for citron, it's used for images and the +// application name +Discord_Initialize("1322413013248118888", & handlers, 1, nullptr); } DiscordImpl::~DiscordImpl() { - Discord_ClearPresence(); - Discord_Shutdown(); +Discord_ClearPresence(); +Discord_Shutdown(); } void DiscordImpl::Pause() { - Discord_ClearPresence(); +Discord_ClearPresence(); } -std::string DiscordImpl::GetGameString(const std::string& title) { - // Convert to lowercase - std::string icon_name = Common::ToLower(title); - - // Replace spaces with dashes - std::replace(icon_name.begin(), icon_name.end(), ' ', '-'); - - // Remove non-alphanumeric characters but keep dashes - std::erase_if(icon_name, [](char c) { return !std::isalnum(c) && c != '-'; }); - - // Remove dashes from the start and end of the string - icon_name.erase(icon_name.begin(), std::find_if(icon_name.begin(), icon_name.end(), - [](int ch) { return ch != '-'; })); - icon_name.erase( - std::find_if(icon_name.rbegin(), icon_name.rend(), [](int ch) { return ch != '-'; }).base(), - icon_name.end()); +void DiscordImpl::UpdateGameStatus(bool use_default) { +const std::string default_text = "Citron Is A Homebrew Emulator For The Nintendo Switch"; +const std::string default_image = "citron_logo"; +const std::string tinfoil_base_url = "https://tinfoil.media/ti/"; +s64 start_time = std::chrono::duration_cast < std::chrono::seconds > ( +std::chrono::system_clock::now().time_since_epoch()) +.count(); +DiscordRichPresence presence {}; + +// Store the URL string to prevent it from being destroyed +if (!game_title_id.empty()) { +game_url = fmt::format("{}{}/128/128", tinfoil_base_url, game_title_id); +// Make sure the string stays alive for the duration of the presence +cached_url = game_url; +presence.largeImageKey = cached_url.c_str(); +} else { +presence.largeImageKey = cached_url.c_str(); +} - // Remove double dashes - icon_name.erase(std::unique(icon_name.begin(), icon_name.end(), - [](char a, char b) { return a == '-' && b == '-'; }), - icon_name.end()); +presence.largeImageText = game_title.c_str(); +presence.smallImageKey = default_image.c_str(); +presence.smallImageText = default_text.c_str(); +// Remove title ID from display, only show game title +presence.state = game_title.c_str(); +presence.details = "Currently in game"; +presence.startTimestamp = start_time; +Discord_UpdatePresence( & presence); +} - return icon_name; +void DiscordImpl::Update() { +const std::string default_text = "Citron Is A Homebrew Emulator For The Nintendo Switch"; +const std::string default_image = "citron_logo"; + +if (system.IsPoweredOn()) { +system.GetAppLoader().ReadTitle(game_title); +system.GetAppLoader().ReadProgramId(program_id); +game_title_id = fmt::format("{:016X}", program_id); + +fmt::print("Title ID: {}\n", game_title_id); + +QNetworkAccessManager manager; +QNetworkRequest request; +request.setUrl(QUrl(QString::fromStdString( +fmt::format("https://tinfoil.media/ti/{}/128/128", game_title_id)))); +request.setTransferTimeout(10000); +QNetworkReply * reply = manager.head(request); +QEventLoop request_event_loop; +QObject::connect(reply, & QNetworkReply::finished, & request_event_loop, & QEventLoop::quit); +request_event_loop.exec(); + +if (reply -> error()) { +fmt::print("Failed to fetch game image: {} ({})\n", reply -> errorString().toStdString(), +program_id); } -void DiscordImpl::UpdateGameStatus(bool use_default) { - const std::string default_text = "citron is an emulator for the Nintendo Switch"; - const std::string default_image = "citron_logo"; - const std::string url = use_default ? default_image : game_url; - s64 start_time = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - DiscordRichPresence presence{}; - - presence.largeImageKey = url.c_str(); - presence.largeImageText = game_title.c_str(); - presence.smallImageKey = default_image.c_str(); - presence.smallImageText = default_text.c_str(); - presence.state = game_title.c_str(); - presence.details = "Currently in game"; - presence.startTimestamp = start_time; - Discord_UpdatePresence(&presence); +UpdateGameStatus(reply -> error()); +reply -> deleteLater(); +return; } -void DiscordImpl::Update() { - const std::string default_text = "citron is an emulator for the Nintendo Switch"; - const std::string default_image = "citron_logo"; - - if (system.IsPoweredOn()) { - system.GetAppLoader().ReadTitle(game_title); - - // Used to format Icon URL for citron website game compatibility page - std::string icon_name = GetGameString(game_title); - game_url = fmt::format("https://citron-emu.org/images/game/boxart/{}.png", icon_name); - - QNetworkAccessManager manager; - QNetworkRequest request; - request.setUrl(QUrl(QString::fromStdString(game_url))); - request.setTransferTimeout(3000); - QNetworkReply* reply = manager.head(request); - QEventLoop request_event_loop; - QObject::connect(reply, &QNetworkReply::finished, &request_event_loop, &QEventLoop::quit); - request_event_loop.exec(); - UpdateGameStatus(reply->error()); - return; - } - - s64 start_time = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - DiscordRichPresence presence{}; - presence.largeImageKey = default_image.c_str(); - presence.largeImageText = default_text.c_str(); - presence.details = "Currently not in game"; - presence.startTimestamp = start_time; - Discord_UpdatePresence(&presence); +s64 start_time = std::chrono::duration_cast < std::chrono::seconds > ( +std::chrono::system_clock::now().time_since_epoch()) +.count(); + +DiscordRichPresence presence {}; +presence.largeImageKey = default_image.c_str(); +presence.largeImageText = default_text.c_str(); +presence.details = "Currently not in game"; +presence.startTimestamp = start_time; +Discord_UpdatePresence( & presence); } } // namespace DiscordRPC -- cgit v1.2.3