From c2aedb7649b09250485f96b85345afeb94a14eca Mon Sep 17 00:00:00 2001 From: Sleepy Flower Girl Date: Fri, 20 Jul 2018 18:27:43 -0400 Subject: [PATCH] Adds a UI for accepting Discord join requests in Dolphin also did these things fixed crash from joining user that isn't hosting via a direct connection current game stat can now pass to override the current game in config uses ip endpoint from dolphin.org --- Source/Core/Core/NetPlayClient.cpp | 1 + Source/Core/Core/NetPlayClient.h | 1 + Source/Core/Core/NetPlayServer.cpp | 9 +- Source/Core/DolphinQt/CMakeLists.txt | 1 + Source/Core/DolphinQt/DiscordHandler.cpp | 81 +++++++++++++++ Source/Core/DolphinQt/DiscordHandler.h | 43 ++++++++ .../DolphinQt/DiscordJoinRequestDialog.cpp | 90 +++++++++++++++++ .../Core/DolphinQt/DiscordJoinRequestDialog.h | 35 +++++++ Source/Core/DolphinQt/DolphinQt.vcxproj | 3 + Source/Core/DolphinQt/MainWindow.cpp | 11 ++- .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 98 +++++++++++++------ Source/Core/DolphinQt/NetPlay/NetPlayDialog.h | 4 + Source/Core/DolphinQt2/DiscordHandler.cpp | 45 --------- Source/Core/DolphinQt2/DiscordHandler.h | 30 ------ Source/Core/UICommon/DiscordPresence.cpp | 53 +++++++--- Source/Core/UICommon/DiscordPresence.h | 18 +++- 16 files changed, 393 insertions(+), 130 deletions(-) create mode 100644 Source/Core/DolphinQt/DiscordHandler.cpp create mode 100644 Source/Core/DolphinQt/DiscordHandler.h create mode 100644 Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp create mode 100644 Source/Core/DolphinQt/DiscordJoinRequestDialog.h delete mode 100644 Source/Core/DolphinQt2/DiscordHandler.cpp delete mode 100644 Source/Core/DolphinQt2/DiscordHandler.h diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 0630fdd625..f9c093db7f 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -1330,6 +1330,7 @@ void NetPlayClient::OnTraversalStateChanged() Disconnect(); m_dialog->OnTraversalError(m_traversal_client->GetFailureReason()); } + m_dialog->OnTraversalStateChanged(state); } // called from ---NETPLAY--- thread diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index ad9d3ee971..2a76dd5cbe 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -47,6 +47,7 @@ public: virtual void OnConnectionLost() = 0; virtual void OnConnectionError(const std::string& message) = 0; virtual void OnTraversalError(TraversalClient::FailureReason error) = 0; + virtual void OnTraversalStateChanged(TraversalClient::State state) = 0; virtual void OnSaveDataSyncFailure() = 0; virtual bool IsRecording() = 0; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index e49626976c..af4167060f 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -809,8 +809,15 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) void NetPlayServer::OnTraversalStateChanged() { - if (m_dialog && m_traversal_client->GetState() == TraversalClient::Failure) + if (!m_dialog) + return; + + const TraversalClient::State state = m_traversal_client->GetState(); + + if (state == TraversalClient::Failure) m_dialog->OnTraversalError(m_traversal_client->GetFailureReason()); + + m_dialog->OnTraversalStateChanged(state); } // called from ---GUI--- thread diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index b54d09ebf6..86a73c34ed 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(dolphin-emu AboutDialog.cpp CheatsManager.cpp DiscordHandler.cpp + DiscordJoinRequestDialog.cpp FIFO/FIFOPlayerWindow.cpp FIFO/FIFOAnalyzer.cpp HotkeyScheduler.cpp diff --git a/Source/Core/DolphinQt/DiscordHandler.cpp b/Source/Core/DolphinQt/DiscordHandler.cpp new file mode 100644 index 0000000000..fa3f39a8ad --- /dev/null +++ b/Source/Core/DolphinQt/DiscordHandler.cpp @@ -0,0 +1,81 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#ifdef USE_DISCORD_PRESENCE + +#include + +#include + +#include "DolphinQt/DiscordHandler.h" + +#include "Common/Thread.h" + +#include "UICommon/DiscordPresence.h" + +DiscordHandler::DiscordHandler(QWidget* parent) : QObject{parent}, m_parent{parent} +{ + connect(this, &DiscordHandler::JoinRequest, this, &DiscordHandler::ShowNewJoinRequest); +} + +DiscordHandler::~DiscordHandler() +{ + Stop(); +} + +void DiscordHandler::Start() +{ + m_stop_requested.Set(false); + m_thread = std::thread(&DiscordHandler::Run, this); +} + +void DiscordHandler::Stop() +{ + m_stop_requested.Set(true); + + if (m_thread.joinable()) + m_thread.join(); +} + +void DiscordHandler::DiscordJoinRequest(const char* id, const std::string& discord_tag, + const char* avatar) +{ + m_request_dialogs.emplace_front(m_parent, id, discord_tag, avatar); + emit DiscordHandler::JoinRequest(); +} + +void DiscordHandler::DiscordJoin() +{ + emit DiscordHandler::Join(); +} + +void DiscordHandler::ShowNewJoinRequest() +{ + m_request_dialogs.front().show(); + QApplication::alert(nullptr, DiscordJoinRequestDialog::s_max_lifetime_seconds * 1000); +} + +void DiscordHandler::Run() +{ + while (!m_stop_requested.IsSet()) + { + if (m_thread.joinable()) + Discord::CallPendingCallbacks(); + + // close and remove dead requests + for (auto request_dialog = m_request_dialogs.rbegin(); + request_dialog != m_request_dialogs.rend(); ++request_dialog) + { + if (std::time(nullptr) < request_dialog->GetCloseTimestamp()) + continue; + request_dialog->close(); + std::advance(request_dialog, 1); + m_request_dialogs.erase(request_dialog.base()); + } + + Common::SleepCurrentThread(1000 * 2); + } +} + +#endif diff --git a/Source/Core/DolphinQt/DiscordHandler.h b/Source/Core/DolphinQt/DiscordHandler.h new file mode 100644 index 0000000000..4f951ceadd --- /dev/null +++ b/Source/Core/DolphinQt/DiscordHandler.h @@ -0,0 +1,43 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +// Note using a ifdef around this class causes link issues with qt + +#include +#include + +#include + +#include "Common/Flag.h" + +#include "DolphinQt/DiscordJoinRequestDialog.h" + +#include "UICommon/DiscordPresence.h" + +class DiscordHandler : public QObject, public Discord::Handler +{ + Q_OBJECT +public: + explicit DiscordHandler(QWidget* parent); + ~DiscordHandler(); + + void Start(); + void Stop(); + void DiscordJoin() override; + void DiscordJoinRequest(const char* id, const std::string& discord_tag, + const char* avatar) override; + void ShowNewJoinRequest(); +signals: + void Join(); + void JoinRequest(); + +private: + void Run(); + QWidget* m_parent; + Common::Flag m_stop_requested; + std::thread m_thread; + std::list m_request_dialogs; +}; diff --git a/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp b/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp new file mode 100644 index 0000000000..c7177a4a03 --- /dev/null +++ b/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp @@ -0,0 +1,90 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#ifdef USE_DISCORD_PRESENCE + +#include +#include +#include +#include + +#include + +#include "Common/HttpRequest.h" +#include "Common/StringUtil.h" + +#include "DolphinQt/DiscordJoinRequestDialog.h" + +DiscordJoinRequestDialog::DiscordJoinRequestDialog(QWidget* parent, const char* id, + const std::string& discord_tag, + const char* avatar) + : QDialog(parent), m_user_id(id), m_close_timestamp(std::time(nullptr) + s_max_lifetime_seconds) +{ + setWindowTitle(tr("Request to Join Your Party")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + QPixmap avatar_pixmap; + + if (avatar[0] != '\0') + { + const std::string avatar_endpoint = + StringFromFormat("https://cdn.discordapp.com/avatars/%s/%s.png", id, avatar); + + Common::HttpRequest request; + Common::HttpRequest::Response response = request.Get(avatar_endpoint); + + if (response.has_value()) + avatar_pixmap.loadFromData(response->data(), static_cast(response->size()), "png"); + } + + CreateMainLayout(discord_tag, avatar_pixmap); + ConnectWidgets(); +} + +std::time_t DiscordJoinRequestDialog::GetCloseTimestamp() const +{ + return m_close_timestamp; +} + +void DiscordJoinRequestDialog::CreateMainLayout(const std::string& discord_tag, + const QPixmap& avatar) +{ + m_main_layout = new QGridLayout; + + m_invite_button = new QPushButton(QString::fromWCharArray(L"\u2714 Invite")); + m_decline_button = new QPushButton(QString::fromWCharArray(L"\u2716 Decline")); + m_ignore_button = new QPushButton(tr("Ignore")); + + if (!avatar.isNull()) + { + QLabel* picture = new QLabel(); + picture->setPixmap(avatar); + m_main_layout->addWidget(picture, 1, 0, 1, 3, Qt::AlignHCenter); + } + + m_main_layout->addWidget(new QLabel(tr(discord_tag.c_str())), 2, 0, 3, 3, Qt::AlignHCenter); + m_main_layout->addWidget(new QLabel(tr("wants to join your party.")), 4, 0, 4, 3, + Qt::AlignHCenter); + m_main_layout->addWidget(m_invite_button, 8, 0); + m_main_layout->addWidget(m_decline_button, 8, 1); + m_main_layout->addWidget(m_ignore_button, 8, 2); + + setLayout(m_main_layout); +} + +void DiscordJoinRequestDialog::ConnectWidgets() +{ + connect(m_invite_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_YES); }); + connect(m_decline_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_NO); }); + connect(m_ignore_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_IGNORE); }); + connect(this, &QDialog::rejected, this, [this] { Reply(DISCORD_REPLY_IGNORE); }); +} + +void DiscordJoinRequestDialog::Reply(int reply) +{ + Discord_Respond(m_user_id, reply); + close(); +} + +#endif diff --git a/Source/Core/DolphinQt/DiscordJoinRequestDialog.h b/Source/Core/DolphinQt/DiscordJoinRequestDialog.h new file mode 100644 index 0000000000..d2345cf407 --- /dev/null +++ b/Source/Core/DolphinQt/DiscordJoinRequestDialog.h @@ -0,0 +1,35 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class QGridLayout; +class QPixmap; + +class DiscordJoinRequestDialog : public QDialog +{ + Q_OBJECT +public: + explicit DiscordJoinRequestDialog(QWidget* parent, const char* id, const std::string& discord_tag, + const char* avatar); + std::time_t GetCloseTimestamp() const; + + static constexpr std::time_t s_max_lifetime_seconds = 30; + +private: + void CreateMainLayout(const std::string& discord_tag, const QPixmap& avatar); + void ConnectWidgets(); + void Reply(int reply); + + QGridLayout* m_main_layout; + QPushButton* m_invite_button; + QPushButton* m_decline_button; + QPushButton* m_ignore_button; + + const char* const m_user_id; + const std::time_t m_close_timestamp; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 3e5ad13b14..216c0aa2f5 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -108,6 +108,7 @@ + @@ -177,6 +178,7 @@ + @@ -323,6 +325,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 4804b469bc..7f016e5993 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1044,12 +1044,11 @@ void MainWindow::BootWiiSystemMenu() void MainWindow::NetPlayInit() { m_netplay_setup_dialog = new NetPlaySetupDialog(this); -<<<<<<< HEAD:Source/Core/DolphinQt/MainWindow.cpp m_netplay_dialog = new NetPlayDialog; -======= m_netplay_dialog = new NetPlayDialog(this); - m_netplay_discord = new DiscordHandler(); ->>>>>>> Add Discord Join Net Play functionally:Source/Core/DolphinQt2/MainWindow.cpp +#ifdef USE_DISCORD_PRESENCE + m_netplay_discord = new DiscordHandler(this); +#endif connect(m_netplay_dialog, &NetPlayDialog::Boot, this, [this](const QString& path) { StartGame(path); }); @@ -1057,10 +1056,12 @@ void MainWindow::NetPlayInit() connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost); +#ifdef USE_DISCORD_PRESENCE connect(m_netplay_discord, &DiscordHandler::Join, this, &MainWindow::NetPlayJoin); - Discord::InitNetPlayFunctionality([this] { m_netplay_discord->DiscordJoin(); }); + Discord::InitNetPlayFunctionality(*m_netplay_discord); m_netplay_discord->Start(); +#endif } bool MainWindow::NetPlayJoin() diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 5224fb4820..1e18eaea87 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -34,6 +34,7 @@ #include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" +#include "Core/Config/NetplaySettings.h" #include "Core/Config/SYSCONFSettings.h" #include "Core/ConfigLoaders/GameConfigLoader.h" #include "Core/ConfigManager.h" @@ -461,6 +462,55 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) UpdateGUI(); } +void NetPlayDialog::UpdateDiscordPresence() +{ +#ifdef USE_DISCORD_PRESENCE + // both m_current_game and m_player_count need to be set for the status to be displayed correctly + if (m_player_count == 0 || m_current_game.empty()) + return; + + const auto use_default = [this]() { + Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::Empty, "", m_current_game); + }; + + if (g_TraversalClient) + { + const auto host_id = g_TraversalClient->GetHostID(); + if (host_id == decltype(host_id)()) + return use_default(); + + Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::RoomID, + std::string(host_id.begin(), host_id.end()), m_current_game); + } + else if (IsHosting()) + { + if (m_exernal_ip_address.empty()) + { + Common::HttpRequest request; + Common::HttpRequest::Response response = + request.Get("https://ip.dolphin-emu.org/", {{"X-Is-Dolphin", "1"}}); + + if (!response.has_value()) + return use_default(); + m_exernal_ip_address = std::string(response->begin(), response->end()); + } + const int port = Settings::Instance().GetNetPlayServer()->GetPort(); + + Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::IPAddress, + Discord::CreateSecretFromIPAddress(m_exernal_ip_address, port), + m_current_game); + } + else + { + Discord::UpdateDiscordPresence( + m_player_count, Discord::SecretType::IPAddress, + Discord::CreateSecretFromIPAddress(Config::Get(Config::NETPLAY_HOST_CODE), + Config::Get(Config::NETPLAY_HOST_PORT)), + m_current_game); + } +#endif +} + void NetPlayDialog::UpdateGUI() { auto client = Settings::Instance().GetNetPlayClient(); @@ -569,39 +619,10 @@ void NetPlayDialog::UpdateGUI() m_hostcode_action_button->setEnabled(true); } - if (m_old_player_count != player_count) + if (m_old_player_count != m_player_count) { - if (m_use_traversal) - { - const auto host_id = g_TraversalClient->GetHostID(); - Discord::UpdateDiscordPresence(player_count, Discord::SecretType::RoomID, - std::string(host_id.begin(), host_id.end())); - } - else - { - // Temporary soluation - // To Do: Don't rely on a service that Dolphin devs aren't in control of. Ask one of the - // project managers about this. - - Common::HttpRequest request; - Common::HttpRequest::Response response = request.Get("https://www.myexternalip.com/raw"); - - if (!response.has_value()) - return; - - // The response ends with a /n and the - 1 removes that - std::string exernalIPAddress = std::string(response->begin(), response->end() - 1); - std::string port = std::to_string(Settings::Instance().GetNetPlayServer()->GetPort()); - std::string secret; - secret.reserve(exernalIPAddress.length() + 1 + port.length()); - secret += exernalIPAddress; - secret += ':'; - secret += port; - - Discord::UpdateDiscordPresence(player_count, Discord::SecretType::IPAddress, secret); - } - - m_old_player_count = player_count; + UpdateDiscordPresence(); + m_old_player_count = m_player_count; } } @@ -670,6 +691,7 @@ void NetPlayDialog::OnMsgChangeGame(const std::string& title) QueueOnObject(this, [this, qtitle, title] { m_game_button->setText(qtitle); m_current_game = title; + UpdateDiscordPresence(); }); DisplayMessage(tr("Game changed to \"%1\"").arg(qtitle), "magenta"); } @@ -765,6 +787,18 @@ void NetPlayDialog::OnTraversalError(TraversalClient::FailureReason error) }); } +void NetPlayDialog::OnTraversalStateChanged(TraversalClient::State state) +{ + switch (state) + { + case TraversalClient::State::Connected: + case TraversalClient::State::Failure: + UpdateDiscordPresence(); + default: + break; + } +} + void NetPlayDialog::OnSaveDataSyncFailure() { QueueOnObject(this, [this] { SetOptionsEnabled(true); }); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index f8f9926101..acb9d27596 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -51,6 +51,7 @@ public: void OnConnectionLost() override; void OnConnectionError(const std::string& message) override; void OnTraversalError(TraversalClient::FailureReason error) override; + void OnTraversalStateChanged(TraversalClient::State state) override; void OnSaveDataSyncFailure() override; bool IsRecording() override; @@ -73,6 +74,7 @@ private: void OnStart(); void DisplayMessage(const QString& msg, const std::string& color, int duration = OSD::Duration::NORMAL); + void UpdateDiscordPresence(); void UpdateGUI(); void GameStatusChanged(bool running); void SetOptionsEnabled(bool enabled); @@ -113,6 +115,7 @@ private: MD5Dialog* m_md5_dialog; PadMappingDialog* m_pad_mapping; std::string m_current_game; + std::string m_exernal_ip_address; std::string m_nickname; GameListModel* m_game_list_model = nullptr; bool m_use_traversal = false; @@ -120,4 +123,5 @@ private: bool m_got_stop_request = true; int m_buffer_size = 0; int m_player_count = 0; + int m_old_player_count = 0; }; diff --git a/Source/Core/DolphinQt2/DiscordHandler.cpp b/Source/Core/DolphinQt2/DiscordHandler.cpp deleted file mode 100644 index bad505f2de..0000000000 --- a/Source/Core/DolphinQt2/DiscordHandler.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "DolphinQt2/DiscordHandler.h" - -#include "Common/Thread.h" - -#include "UICommon/DiscordPresence.h" - -DiscordHandler::DiscordHandler() = default; - -DiscordHandler::~DiscordHandler() -{ - Stop(); -} - -void DiscordHandler::Start() -{ - m_stop_requested.Set(false); - m_thread = std::thread(&DiscordHandler::Run, this); -} - -void DiscordHandler::Stop() -{ - m_stop_requested.Set(true); - - if (m_thread.joinable()) - m_thread.join(); -} - -void DiscordHandler::DiscordJoin() -{ - emit DiscordHandler::Join(); -} - -void DiscordHandler::Run() -{ - while (!m_stop_requested.IsSet()) - { - Common::SleepCurrentThread(1000 * 2); - - Discord::CallPendingCallbacks(); - } -} diff --git a/Source/Core/DolphinQt2/DiscordHandler.h b/Source/Core/DolphinQt2/DiscordHandler.h deleted file mode 100644 index 1cc6787b46..0000000000 --- a/Source/Core/DolphinQt2/DiscordHandler.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include - -#include - -#include "Common/Flag.h" - -class DiscordHandler : public QObject -{ - Q_OBJECT -public: - explicit DiscordHandler(); - ~DiscordHandler(); - - void Start(); - void Stop(); - void DiscordJoin(); -signals: - void Join(); - -private: - void Run(); - Common::Flag m_stop_requested; - std::thread m_thread; -}; diff --git a/Source/Core/UICommon/DiscordPresence.cpp b/Source/Core/UICommon/DiscordPresence.cpp index afd57d87c7..6f742308c0 100644 --- a/Source/Core/UICommon/DiscordPresence.cpp +++ b/Source/Core/UICommon/DiscordPresence.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "Common/Hash.h" +#include "Common/StringUtil.h" #include "Core/Config/NetplaySettings.h" #include "Core/Config/UISettings.h" @@ -20,7 +21,7 @@ namespace Discord { #ifdef USE_DISCORD_PRESENCE -static JoinFunction join_function = nullptr; +static Handler* event_handler = nullptr; static const char* username = ""; static void HandleDiscordReady(const DiscordUser* user) @@ -28,9 +29,18 @@ static void HandleDiscordReady(const DiscordUser* user) username = user->username; } +static void HandleDiscordJoinRequest(const DiscordUser* user) +{ + if (event_handler == nullptr) + return; + + const std::string discord_tag = StringFromFormat("%s#%s", user->username, user->discriminator); + event_handler->DiscordJoinRequest(user->userId, discord_tag, user->avatar); +} + static void HandleDiscordJoin(const char* join_secret) { - if (join_function == nullptr) + if (event_handler == nullptr) return; if (Config::Get(Config::NETPLAY_NICKNAME) == Config::NETPLAY_NICKNAME.default_value) @@ -38,9 +48,8 @@ static void HandleDiscordJoin(const char* join_secret) std::string secret(join_secret); - size_t offset = 0; - std::string type = secret.substr(offset, secret.find('\n')); - offset += type.length() + 1; + std::string type = secret.substr(0, secret.find('\n')); + size_t offset = type.length() + 1; switch (static_cast(std::stol(type))) { @@ -70,10 +79,12 @@ static void HandleDiscordJoin(const char* join_secret) break; } - join_function(); + event_handler->DiscordJoin(); } #endif +Discord::Handler::~Handler() = default; + void Init() { #ifdef USE_DISCORD_PRESENCE @@ -83,6 +94,7 @@ void Init() DiscordEventHandlers handlers = {}; handlers.ready = HandleDiscordReady; + handlers.joinRequest = HandleDiscordJoinRequest; handlers.joinGame = HandleDiscordJoin; // The number is the client ID for Dolphin, it's used for images and the appication name Discord_Initialize("455712169795780630", &handlers, 1, nullptr); @@ -101,20 +113,22 @@ void CallPendingCallbacks() #endif } -void InitNetPlayFunctionality(const JoinFunction& join) +void InitNetPlayFunctionality(Handler& handler) { #ifdef USE_DISCORD_PRESENCE - join_function = std::move(join); + event_handler = &handler; #endif } -void UpdateDiscordPresence(const int party_size, SecretType type, const std::string& secret) +void UpdateDiscordPresence(const int party_size, SecretType type, const std::string& secret, + const std::string& current_game) { #ifdef USE_DISCORD_PRESENCE if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)) return; - const std::string& title = SConfig::GetInstance().GetTitleDescription(); + const std::string& title = + current_game.empty() ? SConfig::GetInstance().GetTitleDescription() : current_game; DiscordRichPresence discord_presence = {}; discord_presence.largeImageKey = "dolphin_logo"; @@ -139,15 +153,15 @@ void UpdateDiscordPresence(const int party_size, SecretType type, const std::str } } - std::string party_ID; + std::string party_id; std::string secret_final; if (type != SecretType::Empty) { - // Declearing party_ID or secret_final here will deallocate the variable before passing the + // Declearing party_id or secret_final here will deallocate the variable before passing the // values over to Discord_UpdatePresence. const size_t secret_length = secret.length(); - party_ID = std::to_string( + party_id = std::to_string( Common::HashAdler32(reinterpret_cast(secret.c_str()), secret_length)); const std::string secret_type = std::to_string(static_cast(type)); @@ -156,13 +170,24 @@ void UpdateDiscordPresence(const int party_size, SecretType type, const std::str secret_final += '\n'; secret_final += secret; } - discord_presence.partyId = party_ID.c_str(); + discord_presence.partyId = party_id.c_str(); discord_presence.joinSecret = secret_final.c_str(); Discord_UpdatePresence(&discord_presence); #endif } +std::string CreateSecretFromIPAddress(const std::string& ip_address, int port) +{ + const std::string port_string = std::to_string(port); + std::string secret; + secret.reserve(ip_address.length() + 1 + port_string.length()); + secret += ip_address; + secret += ':'; + secret += port_string; + return secret; +} + void Shutdown() { #ifdef USE_DISCORD_PRESENCE diff --git a/Source/Core/UICommon/DiscordPresence.h b/Source/Core/UICommon/DiscordPresence.h index f2e40bb0b6..c7e921e804 100644 --- a/Source/Core/UICommon/DiscordPresence.h +++ b/Source/Core/UICommon/DiscordPresence.h @@ -9,8 +9,19 @@ namespace Discord { using JoinFunction = std::function; +using JoinRequestFunction = + std::function; -enum class SecretType : char +class Handler +{ +public: + virtual ~Handler(); + virtual void DiscordJoin() = 0; + virtual void DiscordJoinRequest(const char* id, const std::string& discord_tag, + const char* avatar) = 0; +}; + +enum class SecretType { Empty, IPAddress, @@ -18,10 +29,11 @@ enum class SecretType : char }; void Init(); -void InitNetPlayFunctionality(const JoinFunction& join); +void InitNetPlayFunctionality(Handler& handler); void CallPendingCallbacks(); void UpdateDiscordPresence(int party_size = 0, SecretType type = SecretType::Empty, - const std::string& secret = {}); + const std::string& secret = {}, const std::string& current_game = {}); +std::string CreateSecretFromIPAddress(const std::string& ip_address, int port); void Shutdown(); void SetDiscordPresenceEnabled(bool enabled); } // namespace Discord