diff --git a/Source/Server/Server.vcxproj b/Source/Server/Server.vcxproj index 15fb1cc6..18c90470 100644 --- a/Source/Server/Server.vcxproj +++ b/Source/Server/Server.vcxproj @@ -193,6 +193,7 @@ copy $(SolutionDir)..\Resources\steam_appid.txt $(OutputPath)steam_appid.txt + @@ -252,6 +253,7 @@ copy $(SolutionDir)..\Resources\steam_appid.txt $(OutputPath)steam_appid.txt + diff --git a/Source/Server/Server.vcxproj.filters b/Source/Server/Server.vcxproj.filters index 9f97e3ee..35050172 100644 --- a/Source/Server/Server.vcxproj.filters +++ b/Source/Server/Server.vcxproj.filters @@ -318,6 +318,9 @@ Server\WebUIService\Handlers + + Server\WebUIService\Handlers + @@ -489,6 +492,9 @@ Server\WebUIService\Handlers + + Server\WebUIService\Handlers + diff --git a/Source/Server/Server/GameService/GameClient.cpp b/Source/Server/Server/GameService/GameClient.cpp index 493f9183..b301255a 100644 --- a/Source/Server/Server/GameService/GameClient.cpp +++ b/Source/Server/Server/GameService/GameClient.cpp @@ -18,6 +18,7 @@ #include "Platform/Platform.h" #include "Core/Utils/Logging.h" +#include "Core/Utils/Strings.h" #include "Core/Network/NetConnection.h" #include "Config/BuildConfig.h" @@ -127,4 +128,28 @@ bool GameClient::HandleMessage(const Frpg2ReliableUdpMessage& Message) std::string GameClient::GetName() { return Connection->GetName(); +} + +void GameClient::SendTextMessage(const std::string& TextMessage) +{ + Frpg2RequestMessage::ManagementTextMessage Message; + Message.set_push_message_id(Frpg2RequestMessage::PushID_ManagementTextMessage); + Message.set_unknown_2(TextMessage); + Message.set_unknown_4(0); + Message.set_unknown_5(0); + + // Date makes no difference, just hard-code for now. + Frpg2PlayerData::DateTime* DateTime = Message.mutable_unknown_3(); + DateTime->set_year(2021); + DateTime->set_month(1); + DateTime->set_day(1); + DateTime->set_hours(0); + DateTime->set_minutes(0); + DateTime->set_seconds(0); + DateTime->set_tzdiff(0); + + if (!MessageStream->Send(&Message)) + { + WarningS(GetName().c_str(), "Failed to send game client text message."); + } } \ No newline at end of file diff --git a/Source/Server/Server/GameService/GameClient.h b/Source/Server/Server/GameService/GameClient.h index 1a355673..f96f1257 100644 --- a/Source/Server/Server/GameService/GameClient.h +++ b/Source/Server/Server/GameService/GameClient.h @@ -39,6 +39,9 @@ class GameClient double GetConnectionDuration() { return GetSeconds() - ConnectTime; } + // Sends a text message displayed at the top of the users screen. + void SendTextMessage(const std::string& Message); + public: std::shared_ptr Connection; diff --git a/Source/Server/Server/WebUIService/Handlers/MessageHandler.cpp b/Source/Server/Server/WebUIService/Handlers/MessageHandler.cpp new file mode 100644 index 00000000..6090ff87 --- /dev/null +++ b/Source/Server/Server/WebUIService/Handlers/MessageHandler.cpp @@ -0,0 +1,71 @@ +/* + * Dark Souls 3 - Open Server + * Copyright (C) 2021 Tim Leonard + * + * This program is free software; licensed under the MIT license. + * You should have received a copy of the license along with this program. + * If not, see . + */ + +#include "Server/Server.h" +#include "Server/GameService/GameService.h" +#include "Server/GameService/GameClient.h" +#include "Server/WebUIService/Handlers/MessageHandler.h" +#include "Server/Core/Network/NetConnection.h" + +#include "Core/Utils/Logging.h" +#include "Core/Utils/Strings.h" + +MessageHandler::MessageHandler(WebUIService* InService) + : WebUIHandler(InService) +{ +} + +void MessageHandler::Register(CivetServer* Server) +{ + Server->addHandler("/message", this); +} + +bool MessageHandler::handlePost(CivetServer* Server, struct mg_connection* Connection) +{ + if (!Service->IsAuthenticated(Connection)) + { + mg_send_http_error(Connection, 401, "Token invalid."); + return true; + } + + nlohmann::json json; + if (!ReadJson(Server, Connection, json) || + !json.contains("playerId") || + !json.contains("message")) + { + mg_send_http_error(Connection, 400, "Malformed body."); + return true; + } + + uint32_t playerId = json["playerId"]; + std::string message = json["message"]; + + std::shared_ptr Game = Service->GetServer()->GetService(); + if (playerId == 0) + { + LogS("WebUI", "Sending message to all players: %s", message.c_str()); + for (auto Client : Game->GetClients()) + { + Client->SendTextMessage(message); + } + } + else + { + if (std::shared_ptr Client = Game->FindClientByPlayerId(playerId)) + { + LogS("WebUI", "Sending message to %s: %s", Client->GetName().c_str(), message.c_str()); + Client->SendTextMessage(message); + } + } + + nlohmann::json responseJson; + RespondJson(Connection, responseJson); + + return true; +} diff --git a/Source/Server/Server/WebUIService/Handlers/MessageHandler.h b/Source/Server/Server/WebUIService/Handlers/MessageHandler.h new file mode 100644 index 00000000..ffcfe17e --- /dev/null +++ b/Source/Server/Server/WebUIService/Handlers/MessageHandler.h @@ -0,0 +1,32 @@ +/* + * Dark Souls 3 - Open Server + * Copyright (C) 2021 Tim Leonard + * + * This program is free software; licensed under the MIT license. + * You should have received a copy of the license along with this program. + * If not, see . + */ + +#pragma once + +#include "Server/WebUIService/Handlers/WebUIHandler.h" +#include "Server/GameService/PlayerState.h" + +#include + +// /message +// +// POST - Sends a message to a player (or all players) + +class MessageHandler : public WebUIHandler +{ +public: + MessageHandler(WebUIService* InService); + + virtual bool handlePost(CivetServer* Server, struct mg_connection* Connection) override; + + virtual void Register(CivetServer* Server) override; + +protected: + +}; \ No newline at end of file diff --git a/Source/Server/Server/WebUIService/WebUIService.cpp b/Source/Server/Server/WebUIService/WebUIService.cpp index 3022317e..58d48a9f 100644 --- a/Source/Server/Server/WebUIService/WebUIService.cpp +++ b/Source/Server/Server/WebUIService/WebUIService.cpp @@ -13,6 +13,7 @@ #include "Server/WebUIService/Handlers/PlayersHandler.h" #include "Server/WebUIService/Handlers/StatisticsHandler.h" #include "Server/WebUIService/Handlers/SettingsHandler.h" +#include "Server/WebUIService/Handlers/MessageHandler.h" #include "Server/Server.h" #include "Core/Utils/Logging.h" @@ -29,6 +30,7 @@ WebUIService::WebUIService(Server* OwningServer) Handlers.push_back(std::make_shared(this)); Handlers.push_back(std::make_shared(this)); Handlers.push_back(std::make_shared(this)); + Handlers.push_back(std::make_shared(this)); } WebUIService::~WebUIService() diff --git a/Source/WebUI/Static/index.html b/Source/WebUI/Static/index.html index 26eb692f..9b4e400b 100644 --- a/Source/WebUI/Static/index.html +++ b/Source/WebUI/Static/index.html @@ -22,6 +22,7 @@ +

Login

@@ -43,6 +44,24 @@

Login

+ + + +

Send Message

+
+

+ This message will be sent and displayed at the top of the players screen for around 15 seconds. +

+
+ + +
+
+
+ + +
+
@@ -114,29 +133,34 @@

Login

-
- - - - - - - - - - - - - - - - - - - - -
Steam IdCharacter NameSoul LevelSoulsSoul MemoryDeath CountMultiplayer CountCovenantStatusLocationPlay TimeConnection TimeOptions
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
Steam IdCharacter NameSoul LevelSoulsSoul MemoryDeath CountMultiplayer CountCovenantStatusLocationPlay TimeConnection TimeOptions
+
diff --git a/Source/WebUI/Static/js/main.js b/Source/WebUI/Static/js/main.js index 8dca696f..f1630959 100644 --- a/Source/WebUI/Static/js/main.js +++ b/Source/WebUI/Static/js/main.js @@ -184,11 +184,13 @@ function reauthenticate() } dialog.showModal(); - button.addEventListener('click', function() + var handler = function() { + button.removeEventListener('client', handler); button.disabled = true; authenticate(usernameBox.value, passwordBox.value); - }); + }; + button.addEventListener('click', handler); } // Starts async loading data to populate the page data. @@ -259,6 +261,89 @@ function banUser(playerId) }); } +// Sends a message to a given user. +function sendUserMessageInternal(playerId, message) +{ + console.log("Sending to user ("+playerId+"): "+message); + + fetch("/message", + { + method: 'post', + headers: + { + "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", + "Auth-Token": getAuthToken() + }, + body: JSON.stringify({ + "playerId": playerId, + "message": message + }) + }) + .catch(function (error) + { + console.log('Request failed'); + reauthenticate(); + }); +} + +function sendUserMessage(playerId) +{ + var dialog = document.querySelector("#send-message-dialog"); + var sendButton = document.querySelector('#send-message-button'); + var cancelButton = document.querySelector('#cancel-send-message-button'); + var messageBox = document.querySelector('#send-message-text'); + + if (!dialog.showModal) + { + dialogPolyfill.registerDialog(dialog); + } + dialog.showModal(); + + var sendHandler = function() + { + sendButton.removeEventListener('click', sendHandler); + sendUserMessageInternal(playerId, messageBox.value); + dialog.close(); + }; + var cancelHandler = function() + { + cancelButton.removeEventListener('click', cancelHandler); + dialog.close(); + }; + + sendButton.addEventListener('click', sendHandler); + cancelButton.addEventListener('click', cancelHandler); +} + +function sendMessageToAllUsers() +{ + var dialog = document.querySelector("#send-message-dialog"); + var sendButton = document.querySelector('#send-message-button'); + var cancelButton = document.querySelector('#cancel-send-message-button'); + var messageBox = document.querySelector('#send-message-text'); + + if (!dialog.showModal) + { + dialogPolyfill.registerDialog(dialog); + } + dialog.showModal(); + + var sendHandler = function() + { + sendButton.removeEventListener('click', sendHandler); + sendUserMessageInternal(0, messageBox.value); + dialog.close(); + }; + var cancelHandler = function() + { + cancelButton.removeEventListener('click', cancelHandler); + dialog.close(); + }; + + sendButton.addEventListener('click', sendHandler); + cancelButton.addEventListener('click', cancelHandler); +} + // Retrieves data from the server to update the statistics tab. function refreshStatisticsTab() { @@ -377,6 +462,9 @@ function refreshPlayersTab() + `;