From 8994ba262697e2517cf0447f8f458a3a5f8cd25a Mon Sep 17 00:00:00 2001
From: Salanto <support@salanto.de>
Date: Thu, 8 Jul 2021 23:18:49 +0200
Subject: [PATCH] Implement new advertiser

I want to die.
---
 bin/config_sample/config.ini     |  12 ++++
 core/core.pro                    |   6 +-
 core/include/config_manager.h    |  16 +++++
 core/include/http_advertiser.h   | 105 +++++++++++++++++++++++++++++++
 core/include/server.h            |  22 +++++++
 core/src/commands/moderation.cpp |   3 +
 core/src/config_manager.cpp      |  15 +++++
 core/src/http_advertiser.cpp     |  69 ++++++++++++++++++++
 core/src/server.cpp              |  15 +++++
 9 files changed, 261 insertions(+), 2 deletions(-)
 create mode 100644 core/include/http_advertiser.h
 create mode 100644 core/src/http_advertiser.cpp

diff --git a/bin/config_sample/config.ini b/bin/config_sample/config.ini
index c3c4621..6a21d4d 100644
--- a/bin/config_sample/config.ini
+++ b/bin/config_sample/config.ini
@@ -60,6 +60,18 @@ message_floodguard=250
 ; The URL of the server's remote repository, sent to the client during their initial handshake. Used by WebAO users for custom content.
 asset_url=
 
+[ModernAdvertiser]
+; Options for the HTTP based Masterserver.
+
+; Whether or not the server should appear on the master server.
+advertise=false
+
+; Wether the advertiser prints additional debug info 
+debug=false
+
+; The IP address of the master server. Unless something happens to the default one, you shouldn't change this.
+ms_ip=https://ms3.oldmud0.workers.dev/servers
+
 [Dice]
 ; The maximum number of sides dice can be rolled with.
 max_value=100
diff --git a/core/core.pro b/core/core.pro
index ea3e4ec..26f72c2 100644
--- a/core/core.pro
+++ b/core/core.pro
@@ -45,7 +45,8 @@ SOURCES += \
     src/server.cpp \
     src/testimony_recorder.cpp \
     src/ws_client.cpp \
-    src/ws_proxy.cpp
+    src/ws_proxy.cpp \
+    src/http_advertiser.cpp
 
 HEADERS += include/advertiser.h \
     include/aoclient.h \
@@ -58,4 +59,5 @@ HEADERS += include/advertiser.h \
     include/logger.h \
     include/server.h \
     include/ws_client.h \
-    include/ws_proxy.h
+    include/ws_proxy.h \
+    include/http_advertiser.h
diff --git a/core/include/config_manager.h b/core/include/config_manager.h
index 852e28b..05ee05c 100644
--- a/core/include/config_manager.h
+++ b/core/include/config_manager.h
@@ -302,6 +302,22 @@ class ConfigManager {
      */
     static QStringList gimpList();
 
+    /**
+     * @brief advertise_server HTTP advertiser is not constructed if this is false.
+     */
+    static bool advertiseHTTPServer();
+
+    /**
+     * @brief advertise_debug Allows HTTP advertiser to print debug info.
+     */
+    static bool advertiserHTTPDebug();
+
+    /**
+     * @brief advertiser_ip IP or URL of the masterserver
+     */
+    static QUrl advertiserHTTPIP();
+
+
     /**
      * @brief Sets the server's authorization type.
      *
diff --git a/core/include/http_advertiser.h b/core/include/http_advertiser.h
new file mode 100644
index 0000000..96b44df
--- /dev/null
+++ b/core/include/http_advertiser.h
@@ -0,0 +1,105 @@
+//////////////////////////////////////////////////////////////////////////////////////
+//    akashi - a server for Attorney Online 2                                       //
+//    Copyright (C) 2020  scatterflower                                             //
+//                                                                                  //
+//    This program is free software: you can redistribute it and/or modify          //
+//    it under the terms of the GNU Affero General Public License as                //
+//    published by the Free Software Foundation, either version 3 of the            //
+//    License, or (at your option) any later version.                               //
+//                                                                                  //
+//    This program is distributed in the hope that it will be useful,               //
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of                //
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 //
+//    GNU Affero General Public License for more details.                           //
+//                                                                                  //
+//    You should have received a copy of the GNU Affero General Public License      //
+//    along with this program.  If not, see <https://www.gnu.org/licenses/>.        //
+//////////////////////////////////////////////////////////////////////////////////////
+#ifndef HTTP_ADVERTISER_H
+#define HTTP_ADVERTISER_H
+
+#include <QtNetwork>
+#include <QObject>
+
+
+/**
+ * @brief Represents the advertiser of the server. Sends current server information to masterserver.
+ */
+class HTTPAdvertiser : public QObject
+{
+    Q_OBJECT
+public:
+    /**
+     * @brief Constructor for the HTTP_Advertiser class.
+     */
+    explicit HTTPAdvertiser();
+
+public slots:
+
+    /**
+     * @brief msAdvertiseServer Establishes a connection with masterserver to
+     *        register or update the listing on the masterserver.
+     */
+    void msAdvertiseServer();
+
+    /**
+     * @brief msRequestFinished Reads the information send as a reply for further
+     *        error handling.
+     */
+    void msRequestFinished(QNetworkReply *reply);
+
+    /**
+     * @brief setAdvertiserSettings Configures the values being advertised to masterserver.
+     * @param f_name Servername.
+     * @param f_description Serverdescription.
+     * @param f_port Client port.
+     * @param f_ws_port Optional Websocket proxy port.
+     * @param f_players Maximum amount of clients.
+     * @param f_master_url URL of the advertisement target.
+     */
+    void setAdvertiserSettings(QString f_name, QString f_description, int f_port, int f_ws_port, int f_players, QUrl f_master_url, bool f_debug);
+
+private:
+
+    /**
+     * @brief m_manager NetworkAccessManager for HTTP Advertiser.
+     */
+    QNetworkAccessManager* m_manager;
+
+    /**
+     * @brief m_name Current name of the server.
+     */
+    QString m_name;
+
+    /**
+     * @brief m_description Current description of the server.
+     */
+    QString m_description;
+
+    /**
+     * @brief m_port Current port for AO2-Clients.
+     */
+    int m_port;
+
+    /**
+     * @brief m_ws_port Websocket proxy port for WebAO-Clients.
+     */
+    int m_ws_port;
+
+    /**
+     * @brief m_players Maximum number of connected clients.
+     */
+    int m_players;
+
+    /**
+     * @brief m_masterserver URL of the masterserver being advertised to.
+     */
+    QUrl m_masterserver;
+
+    /**
+     * @brief m_debug If debug information is displayed in console.
+     */
+    bool m_debug;
+};
+
+#endif // HTTP_ADVERTISER_H
diff --git a/core/include/server.h b/core/include/server.h
index 05e1f48..72a79c8 100644
--- a/core/include/server.h
+++ b/core/include/server.h
@@ -25,6 +25,7 @@
 #include "include/db_manager.h"
 #include "include/discord.h"
 #include "include/config_manager.h"
+#include "include/http_advertiser.h"
 
 #include <QCoreApplication>
 #include <QDebug>
@@ -227,6 +228,17 @@ class Server : public QObject {
      */
     void reloadRequest(QString p_name, QString p_desc);
 
+    /**
+     * @brief Sends all necessary info for the new advertiser.
+     * @param f_name Servername.
+     * @param f_description Serverdescription.
+     * @param f_port Client port.
+     * @param f_ws_port Optional Websocket proxy port.
+     * @param f_players Maximum amount of clients.
+     * @param f_master_url URL of the advertisement target.
+     */
+    void reloadHTTPRequest(QString f_name, QString f_description, int f_port, int f_ws_port, int f_players, QUrl f_master_url, bool f_debug);
+
     /**
      * @brief Sends a modcall webhook request, emitted by AOClient::pktModcall.
      *
@@ -255,6 +267,16 @@ class Server : public QObject {
      */
     Discord* discord;
 
+    /**
+     * @brief Handles HTTP server advertising.
+     */
+    HTTPAdvertiser* httpAdvertiser;
+
+    /**
+     * @brief Advertises the server in a regular intervall.
+     */
+    QTimer* httpAdvertiserTimer;
+
     /**
      * @brief The port through which the server will accept TCP connections.
      */
diff --git a/core/src/commands/moderation.cpp b/core/src/commands/moderation.cpp
index 1582151..3ac965f 100644
--- a/core/src/commands/moderation.cpp
+++ b/core/src/commands/moderation.cpp
@@ -412,6 +412,9 @@ void AOClient::cmdReload(int argc, QStringList argv)
 {
     ConfigManager::reloadSettings();
     emit server->reloadRequest(ConfigManager::serverName(), ConfigManager::serverDescription());
+    emit server->reloadHTTPRequest(ConfigManager::serverName(),ConfigManager::serverDescription(),ConfigManager::serverPort(),
+                                   ConfigManager::webaoPort(),ConfigManager::maxPlayers(),ConfigManager::advertiserHTTPIP(),
+                                   ConfigManager::advertiserHTTPDebug());
     sendServerMessage("Reloaded configurations");
 }
 
diff --git a/core/src/config_manager.cpp b/core/src/config_manager.cpp
index 76244c7..bbe2759 100644
--- a/core/src/config_manager.cpp
+++ b/core/src/config_manager.cpp
@@ -368,6 +368,21 @@ QStringList ConfigManager::gimpList()
     return m_commands->gimps;
 }
 
+bool ConfigManager::advertiseHTTPServer()
+{
+    return m_settings->value("ModernAdvertiser/advertise","true").toBool();
+}
+
+bool ConfigManager::advertiserHTTPDebug()
+{
+    return m_settings->value("ModernAdvertiser/debug","true").toBool();
+}
+
+QUrl ConfigManager::advertiserHTTPIP()
+{
+    return m_settings->value("ModernAdvertiser/ms_ip","").toUrl();
+}
+
 void ConfigManager::setMotd(const QString f_motd)
 {
     m_settings->setValue("Options/motd", f_motd);
diff --git a/core/src/http_advertiser.cpp b/core/src/http_advertiser.cpp
new file mode 100644
index 0000000..93c12ab
--- /dev/null
+++ b/core/src/http_advertiser.cpp
@@ -0,0 +1,69 @@
+#include "include/http_advertiser.h"
+
+HTTPAdvertiser::HTTPAdvertiser()
+{
+    m_manager = new QNetworkAccessManager();
+    connect(m_manager, &QNetworkAccessManager::finished,
+            this, &HTTPAdvertiser::msRequestFinished);
+}
+
+void HTTPAdvertiser::msAdvertiseServer()
+{
+    if (m_masterserver.isValid()) {
+
+        QNetworkRequest request((QUrl (m_masterserver)));
+        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+        QJsonObject json;
+        json["port"] = m_port;
+        if (!(m_ws_port == -1)) {
+            json["ws_port"] = m_ws_port;
+        }
+
+        json["players"] = m_players;
+        json["name"] = m_name;
+
+        if (!(m_description.isEmpty())) {
+        json["description"] = m_description;
+        }
+
+        m_manager->post(request, QJsonDocument(json).toJson());
+
+        if (m_debug)
+            qDebug().noquote() << "Advertised Server";
+        return;
+    }
+    if (m_debug)
+        qWarning().noquote() << "Unable to advertise. Masterserver URL '" + m_masterserver.toString() + "' is not valid.";
+    return;
+
+}
+
+void HTTPAdvertiser::msRequestFinished(QNetworkReply *reply)
+{
+    if (m_debug) {
+        QJsonDocument json = QJsonDocument::fromJson(reply->readAll());
+        if (json.isNull()) {
+            qCritical().noquote() << "Invalid JSON response from" << reply->url();
+            reply->deleteLater();
+            return;
+        }
+
+        qDebug().noquote() << "Got valid response from" << reply->url();
+        qDebug() << json;
+    }
+    reply->deleteLater();
+}
+
+void HTTPAdvertiser::setAdvertiserSettings(QString f_name, QString f_description, int f_port, int f_ws_port, int f_players, QUrl f_master_url, bool f_debug)
+{
+    m_name = f_name;
+    m_description = f_description;
+    m_port = f_port;
+    m_ws_port = f_ws_port;
+    m_players = f_players;
+    m_masterserver = f_master_url;
+    m_debug = f_debug;
+
+    msAdvertiseServer();
+}
diff --git a/core/src/server.cpp b/core/src/server.cpp
index 635e0a9..cc9cbf6 100644
--- a/core/src/server.cpp
+++ b/core/src/server.cpp
@@ -29,6 +29,7 @@ Server::Server(int p_port, int p_ws_port, QObject* parent) :
     timer = new QTimer();
 
     db_manager = new DBManager();
+
 }
 
 void Server::start()
@@ -57,6 +58,20 @@ void Server::start()
                 discord, &Discord::onModcallWebhookRequested);
     }
 
+    if (ConfigManager::advertiseHTTPServer()) {
+        httpAdvertiserTimer = new QTimer(this);
+        httpAdvertiser = new HTTPAdvertiser();
+
+        connect(httpAdvertiserTimer, &QTimer::timeout,
+                httpAdvertiser, &HTTPAdvertiser::msAdvertiseServer);
+        connect(this, &Server::reloadHTTPRequest,
+                httpAdvertiser, &HTTPAdvertiser::setAdvertiserSettings);
+
+        emit reloadHTTPRequest(ConfigManager::serverName(),ConfigManager::serverDescription(),ConfigManager::serverPort(),ConfigManager::webaoPort(),
+                               ConfigManager::maxPlayers(),ConfigManager::advertiserHTTPIP(),ConfigManager::advertiserHTTPDebug());
+        httpAdvertiserTimer->start(300000);
+    }
+
     proxy = new WSProxy(port, ws_port, this);
     if(ws_port != -1)
         proxy->start();