From e44fdd39058c28d331a50c6b37c9823544d97c06 Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:06:46 +0200 Subject: [PATCH] feat(moderation): Add websocket protocol messages --- .../Include/GameNetwork/GameSpyOverlay.h | 7 + .../Source/GameNetwork/GameSpyOverlay.cpp | 28 +++ GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../GameNetwork/GeneralsOnline/NGMP_include.h | 1 + .../GeneralsOnline/OnlineServices_Init.h | 25 ++- .../OnlineServices_Moderation.h | 13 ++ .../OnlineServices_RoomsInterface.h | 2 + .../GameEngine/Source/Common/GameEngine.cpp | 2 +- .../GeneralsOnline/NGMP_Helpers.cpp | 26 +++ .../GeneralsOnline/OnlineServices_Auth.cpp | 41 +++- .../OnlineServices_Moderation.cpp | 179 ++++++++++++++++++ .../OnlineServices_RoomsInterface.cpp | 58 ++++++ 12 files changed, 370 insertions(+), 14 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Moderation.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Moderation.cpp diff --git a/Core/GameEngine/Include/GameNetwork/GameSpyOverlay.h b/Core/GameEngine/Include/GameNetwork/GameSpyOverlay.h index 9c717f7040a..e89581f286a 100644 --- a/Core/GameEngine/Include/GameNetwork/GameSpyOverlay.h +++ b/Core/GameEngine/Include/GameNetwork/GameSpyOverlay.h @@ -38,6 +38,13 @@ void ClearGSMessageBoxes(); ///< Tear down any GS message boxes (e.g. in case we have a new one to put up) void GSMessageBoxOk(UnicodeString titleString,UnicodeString bodyString, GameWinMsgBoxFunc okFunc = nullptr); ///< Display a Message box with Ok button and track it void GSMessageBoxOkCancel(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc okFunc, GameWinMsgBoxFunc cancelFunc); ///< Display a Message box with Ok/Cancel buttons and track it +void GSMessageBoxOkCancelWithLabels( + UnicodeString title, + UnicodeString message, + UnicodeString okLabel, + UnicodeString cancelLabel, + GameWinMsgBoxFunc okFunc, + GameWinMsgBoxFunc cancelFunc); ///< Display a tracked Ok/Cancel message box with caller-provided button labels void GSMessageBoxYesNo(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc yesFunc, GameWinMsgBoxFunc noFunc); ///< Display a Message box with Yes/No buttons and track it void RaiseGSMessageBox(); ///< Bring GS message box to the foreground (if we transition screens while a message box is up) diff --git a/Core/GameEngine/Source/GameNetwork/GameSpyOverlay.cpp b/Core/GameEngine/Source/GameNetwork/GameSpyOverlay.cpp index 7dcfb814062..e0dc3edc154 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpyOverlay.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpyOverlay.cpp @@ -30,6 +30,7 @@ #include "Common/AudioEventRTS.h" #include "GameClient/GadgetListBox.h" +#include "GameClient/GadgetPushButton.h" #include "GameClient/GameText.h" #include "GameClient/MessageBox.h" #include "GameClient/ShellHooks.h" @@ -125,6 +126,33 @@ void GSMessageBoxOkCancel(UnicodeString title, UnicodeString message, GameWinMsg cancelFunc = newCancelFunc; } +void GSMessageBoxOkCancelWithLabels( + UnicodeString title, + UnicodeString message, + UnicodeString okLabel, + UnicodeString cancelLabel, + GameWinMsgBoxFunc newOkFunc, + GameWinMsgBoxFunc newCancelFunc) +{ + GSMessageBoxOkCancel(title, message, newOkFunc, newCancelFunc); + if (messageBoxWindow == nullptr) + { + return; + } + + GameWindow* buttonOk = TheWindowManager->winGetWindowFromId(messageBoxWindow, TheNameKeyGenerator->nameToKey("MessageBox.wnd:ButtonOk")); + GameWindow* buttonCancel = TheWindowManager->winGetWindowFromId(messageBoxWindow, TheNameKeyGenerator->nameToKey("MessageBox.wnd:ButtonCancel")); + if (buttonOk != nullptr) + { + GadgetButtonSetText(buttonOk, okLabel); + } + + if (buttonCancel != nullptr) + { + GadgetButtonSetText(buttonCancel, cancelLabel); + } +} + /** * GSMessageBoxYesNo puts up a Yes/No dialog box and saves the * pointers to it and its callbacks. diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 7cb8b0cba9f..f49819dc3f4 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -1150,6 +1150,7 @@ set(GAMEENGINE_SRC Include/GameNetwork/GeneralsOnline/OnlineServices_Auth.h Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h + Include/GameNetwork/GeneralsOnline/OnlineServices_Moderation.h Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h Include/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.h @@ -1193,6 +1194,7 @@ set(GAMEENGINE_SRC Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp + Source/GameNetwork/GeneralsOnline/OnlineServices_Moderation.cpp Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp Source/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.cpp Source/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_include.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_include.h index 8c0c3474788..07d590e64f5 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_include.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_include.h @@ -27,6 +27,7 @@ void NetworkLog(ELogVerbosity logVerbosity, const char* fmt, ...); std::string to_utf8(const std::wstring& wstr); std::wstring from_utf8(const std::string& utf8_str); +std::wstring NormalizeSingleLineText(const std::wstring& text); int RoundUpLatencyToFrameInterval(int latency, int frameInterval); int ConvertMSLatencyToFrames(int ms); diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h index 30cff480bb6..52d6da64417 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h @@ -96,7 +96,10 @@ enum EWebSocketMessageID WS_KEEPALIVE = 42, WS_KEEPALIVE_CLIENT = 43, MATCHMAKING_ACTION_REQUEUE = 44, - MATCHMAKING_ACTION_SETUP_PROGRESS = 45 + MATCHMAKING_ACTION_SETUP_PROGRESS = 45, + MODERATION_NOTICE = 46, + MODERATION_COMMAND = 47, + MODERATION_COMMAND_RESULT = 48 }; enum class EQoSRegions @@ -126,9 +129,16 @@ enum class EGOTearDownReason LOST_CONNECTION = 0, USER_LOGOUT = 1, USER_REQUESTED_SILENT = 2, - AUTH_FAILED = 3 + AUTH_FAILED = 3, + MODERATION_BAN = 4, + MODERATION_KICK = 5 }; +constexpr bool IsModerationTeardownReason(EGOTearDownReason reason) noexcept +{ + return reason == EGOTearDownReason::MODERATION_BAN || reason == EGOTearDownReason::MODERATION_KICK; +} + class WebSocket { public: @@ -505,7 +515,16 @@ class NGMP_OnlineServicesManager std::string& GetMOTD() { return m_strMOTD; } - void SetPendingFullTeardown(EGOTearDownReason reason) { m_bPendingFullTeardown = true; m_teardownReason = reason; } + void SetPendingFullTeardown(EGOTearDownReason reason) + { + if (IsModerationTeardownReason(m_teardownReason) && !IsModerationTeardownReason(reason)) + { + return; + } + + m_bPendingFullTeardown = true; + m_teardownReason = reason; + } bool IsPendingFullTeardown() const { return m_bPendingFullTeardown; } EGOTearDownReason GetTeardownReason() const { return m_teardownReason; } void ConsumePendingFullTeardown() { m_bPendingFullTeardown = false; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Moderation.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Moderation.h new file mode 100644 index 00000000000..072e9c05ebc --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Moderation.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +enum class EOnlineModerationAction +{ + BAN, + KICK +}; + +void ShowLoginBanDialog(const std::string& reason); +void HandleModerationDisconnect(EOnlineModerationAction action, const std::string& reason); +void HandleModerationNotice(const std::string& actionType, const std::string& reason, const std::string& scopeType); diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h index c4b5dc80866..6347fbaf371 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h @@ -212,6 +212,7 @@ class NGMP_OnlineServices_RoomsInterface const std::vector& GetGroupRooms() const { return m_vecRooms; } void OnRosterUpdated(std::unordered_map mapMembers, const RoomSelectionResult& selectionResult); + bool SupportsModerationCommands() const { return m_bSupportsModerationCommands; } int GetCurrentRoomIndex() const { return m_CurrentRoomIndex; } @@ -229,6 +230,7 @@ class NGMP_OnlineServices_RoomsInterface std::optional m_PendingRoomChange; uint64_t m_NextRoomChangeRequestID = 1; bool m_bRoomSelectionResultsSupported = false; + bool m_bSupportsModerationCommands = false; std::function m_RoomChangedCallback = nullptr; void ReportRoomJoinFailure(const std::string& error); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index a5d5ca2e3f8..d39ea9bee76 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -128,7 +128,7 @@ void TearDownGeneralsOnline() EGOTearDownReason teardownReason = NGMP_OnlineServicesManager::GetInstance()->GetTeardownReason(); - if (teardownReason != EGOTearDownReason::USER_REQUESTED_SILENT) + if (teardownReason != EGOTearDownReason::USER_REQUESTED_SILENT && !IsModerationTeardownReason(teardownReason)) { UnicodeString title, body; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp index 38e771c323f..271b6e23805 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp @@ -1,5 +1,6 @@ #include "GameNetwork/GeneralsOnline/NGMP_include.h" #include +#include #include #include #include @@ -25,6 +26,31 @@ std::wstring from_utf8(const std::string& utf8_str) return converter.from_bytes(utf8_str); } +std::wstring NormalizeSingleLineText(const std::wstring& text) +{ + std::wstring normalizedText; + normalizedText.reserve(text.size()); + bool pendingSpace = false; + + for (wchar_t character : text) + { + if (std::iswspace(character) || std::iswcntrl(character)) + { + pendingSpace = !normalizedText.empty(); + continue; + } + + if (pendingSpace) + { + normalizedText += L' '; + pendingSpace = false; + } + normalizedText += character; + } + + return normalizedText; +} + void NetworkLog(ELogVerbosity logVerbosity, const char* fmt, ...) { if (!NGMP_OnlineServicesManager::Settings.Debug_VerboseLogging()) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp index 945b360d10c..9b8e1e8f3b5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Auth.cpp @@ -2,6 +2,7 @@ #include "GameNetwork/GeneralsOnline/HTTP/HTTPManager.h" #include "GameNetwork/GeneralsOnline/HTTP/HTTPRequest.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_Moderation.h" #include "GameNetwork/GeneralsOnline/json.hpp" #include #include @@ -52,6 +53,24 @@ struct AuthResponse NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(AuthResponse, result, session_token, refresh_token, user_id, display_name, ws_uri) }; +namespace +{ + std::string GetBanReason(const std::string& responseBody) + { + nlohmann::json jsonObject = nlohmann::json::parse(responseBody, nullptr, false, true); + if (jsonObject.is_object()) + { + const auto banReason = jsonObject.find("ban_reason"); + if (banReason != jsonObject.end() && banReason->is_string()) + { + return banReason->get_ref(); + } + } + + return std::string(); + } +} + struct MOTDResponse { std::string MOTD; @@ -206,6 +225,16 @@ void NGMP_OnlineServices_AuthInterface::RefreshToken() { if (statusCode >= 400 && statusCode < 500) { + if (statusCode == 423) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AUTH]: Account ban detected during token refresh, tearing down"); + m_tokenCreationTime = -1; + m_nextRefreshRetryTime = -1; + m_currentRefreshAttempt = 0; + HandleModerationDisconnect(EOnlineModerationAction::BAN, GetBanReason(strBody)); + return; + } + OnRefreshTokenFailed(std::format("HTTP {}", statusCode).c_str(), strBody); } else @@ -306,11 +335,7 @@ void NGMP_OnlineServices_AuthInterface::BeginLogin() { if (statusCode == 423) { - ClearGSMessageBoxes(); - GSMessageBoxOk(UnicodeString(L"Account Banned"), UnicodeString(L"You are banned. You can file an appeal in Discord."), []() - { - TheShell->pop(); - }); + ShowLoginBanDialog(GetBanReason(strBody)); return; } else @@ -506,11 +531,7 @@ void NGMP_OnlineServices_AuthInterface::Tick() if (statusCode == 423) { m_bWaitingLogin = false; - ClearGSMessageBoxes(); - GSMessageBoxOk(UnicodeString(L"Account Banned"), UnicodeString(L"You are banned. You can file an appeal in Discord."), []() - { - TheShell->pop(); - }); + ShowLoginBanDialog(GetBanReason(strBody)); return; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Moderation.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Moderation.cpp new file mode 100644 index 00000000000..56e96e8d69b --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Moderation.cpp @@ -0,0 +1,179 @@ +#include "GameNetwork/GeneralsOnline/OnlineServices_Moderation.h" + +#include "GameNetwork/GeneralsOnline/NGMP_include.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_Init.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h" +#include "GameNetwork/GameSpyOverlay.h" + +#include +#include +#include + +namespace +{ + enum class EModerationDialogContext + { + LOGIN, + ACTIVE_SESSION + }; + + void LeaveLoginScreen() + { + TheShell->pop(); + } + + void OpenDiscord() + { + ShellExecuteA(nullptr, "open", "https://discord.playgenerals.online", nullptr, nullptr, SW_SHOWNORMAL); + } + + void OpenDiscordFromLogin() + { + OpenDiscord(); + LeaveLoginScreen(); + } + + std::wstring GetNormalizedReason(const std::string& reason) + { + try + { + return NormalizeSingleLineText(from_utf8(reason)); + } + catch (const std::range_error&) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[MODERATION]: Ignoring an invalid UTF-8 reason"); + return std::wstring(); + } + } + + void AppendReason(std::wstring& message, const std::string& reason) + { + const std::wstring normalizedReason = GetNormalizedReason(reason); + if (!normalizedReason.empty()) + { + message += L"\n\nReason: "; + message += normalizedReason; + } + } + + void ShowModerationDialog( + UnicodeString title, + std::wstring message, + const std::string& reason, + EModerationDialogContext context) + { + AppendReason(message, reason); + message += L"\n\nVisit Discord for more information or support."; + + GameWinMsgBoxFunc discordCallback = OpenDiscord; + GameWinMsgBoxFunc closeCallback = nullptr; + if (context == EModerationDialogContext::LOGIN) + { + discordCallback = OpenDiscordFromLogin; + closeCallback = LeaveLoginScreen; + } + + GSMessageBoxOkCancelWithLabels( + title, + UnicodeString(message.c_str()), + UnicodeString(L"Discord"), + UnicodeString(L"Close"), + discordCallback, + closeCallback); + } + + void ShowBanDialog(const std::string& reason, EModerationDialogContext context) + { + ShowModerationDialog( + UnicodeString(L"Banned"), + L"You have been banned from Generals Online.", + reason, + context); + } + + void ShowKickDialog(const std::string& reason) + { + ShowModerationDialog( + UnicodeString(L"Kicked"), + L"You have been kicked from Generals Online.", + reason, + EModerationDialogContext::ACTIVE_SESSION); + } + + void ShowChatNotice(const std::string& reason, const std::string& scopeType) + { + const std::wstring normalizedReason = GetNormalizedReason(reason); + if (normalizedReason.empty()) + { + return; + } + + UnicodeString message(normalizedReason.c_str()); + const Color color = GameMakeColor(255, 194, 15, 255); + if (scopeType == "lobby") + { + NGMP_OnlineServices_LobbyInterface* lobbyInterface = + NGMP_OnlineServicesManager::GetInterface(); + if (lobbyInterface != nullptr && lobbyInterface->m_OnChatCallback != nullptr) + { + lobbyInterface->m_OnChatCallback(message, color); + } + return; + } + + NGMP_OnlineServices_RoomsInterface* roomsInterface = + NGMP_OnlineServicesManager::GetInterface(); + if (roomsInterface != nullptr && roomsInterface->m_OnChatCallback != nullptr) + { + roomsInterface->m_OnChatCallback(message, color); + } + } +} + +void ShowLoginBanDialog(const std::string& reason) +{ + ShowBanDialog(reason, EModerationDialogContext::LOGIN); +} + +void HandleModerationDisconnect(EOnlineModerationAction action, const std::string& reason) +{ + NGMP_OnlineServicesManager* manager = NGMP_OnlineServicesManager::GetInstance(); + if (manager == nullptr || IsModerationTeardownReason(manager->GetTeardownReason())) + { + return; + } + + switch (action) + { + case EOnlineModerationAction::BAN: + manager->SetPendingFullTeardown(EGOTearDownReason::MODERATION_BAN); + ShowBanDialog(reason, EModerationDialogContext::ACTIVE_SESSION); + break; + + case EOnlineModerationAction::KICK: + manager->SetPendingFullTeardown(EGOTearDownReason::MODERATION_KICK); + ShowKickDialog(reason); + break; + } +} + +void HandleModerationNotice(const std::string& actionType, const std::string& reason, const std::string& scopeType) +{ + if (actionType == "ban") + { + HandleModerationDisconnect(EOnlineModerationAction::BAN, reason); + } + else if (actionType == "kick") + { + HandleModerationDisconnect(EOnlineModerationAction::KICK, reason); + } + else if (actionType == "rate_limit") + { + ShowChatNotice(reason, scopeType); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Unknown moderation notice: %s", actionType.c_str()); + } +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp index a37342657d7..2d0182a58c1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp @@ -2,6 +2,7 @@ #include "GameNetwork/GeneralsOnline/NGMP_include.h" #include "GameNetwork/GeneralsOnline/NetworkPacket.h" #include "GameNetwork/GeneralsOnline/NetworkBitstream.h" +#include "GameNetwork/GeneralsOnline/OnlineServices_Moderation.h" #include "GameNetwork/GeneralsOnline/json.hpp" #include "../OnlineServices_Init.h" #include "../HTTP/HTTPManager.h" @@ -356,6 +357,33 @@ class WebSocketMessageBase NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessageBase, msg_id) }; +class WebSocketMessage_ModerationNotice : public WebSocketMessageBase +{ +public: + std::string action_type; + std::string reason; + std::string scope_type; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(WebSocketMessage_ModerationNotice, msg_id, action_type, reason, scope_type) +}; + +class WebSocketMessage_ModerationCommandResult : public WebSocketMessageBase +{ +public: + uint64_t request_id = 0; + bool success = false; + std::string error_code; + std::string message; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + WebSocketMessage_ModerationCommandResult, + msg_id, + request_id, + success, + error_code, + message) +}; + class WebSocketMessage_NetworkStartSignalling : public WebSocketMessageBase { public: @@ -836,6 +864,33 @@ void WebSocket::Tick() switch (msgID) { + case EWebSocketMessageID::MODERATION_NOTICE: + { + WebSocketMessage_ModerationNotice moderationNotice; + if (JSONGetAsObject(jsonObject, &moderationNotice)) + { + HandleModerationNotice( + moderationNotice.action_type, + moderationNotice.reason, + moderationNotice.scope_type); + } + } + break; + + case EWebSocketMessageID::MODERATION_COMMAND_RESULT: + { + WebSocketMessage_ModerationCommandResult commandResult; + if (JSONGetAsObject(jsonObject, &commandResult)) + { + NetworkLog( + ELogVerbosity::LOG_RELEASE, + "Moderation command %llu %s: %s", + static_cast(commandResult.request_id), + commandResult.success ? "succeeded" : "failed", + commandResult.message.c_str()); + } + } + break; case EWebSocketMessageID::PONG: { @@ -1615,6 +1670,7 @@ void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function c m_EffectiveRoomID.reset(); m_PendingRoomChange.reset(); m_bRoomSelectionResultsSupported = false; + m_bSupportsModerationCommands = false; // Cache our buddies on lobby list NGMP_OnlineServices_SocialInterface* pSocialInterface = @@ -1640,6 +1696,7 @@ void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function c std::vector rooms; nlohmann::json jsonObject = nlohmann::json::parse(strBody); const bool roomSelectionResultsSupported = jsonObject.value("supports_room_selection_results", false); + const bool supportsModerationCommands = jsonObject.value("supports_moderation_commands", false); for (const auto& roomEntryIter : jsonObject["rooms"]) { @@ -1663,6 +1720,7 @@ void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function c m_vecRooms = std::move(rooms); m_bRoomSelectionResultsSupported = roomSelectionResultsSupported; + m_bSupportsModerationCommands = supportsModerationCommands; cb(true); return;