From b67b4d5afd2e7c3eb866fec0dad030fcc6719832 Mon Sep 17 00:00:00 2001
From: Denis Freund <info@denis-freund.de>
Date: Mon, 27 Sep 2021 11:17:57 +0200
Subject: [PATCH] add steam gameserver for monitoring

---
 db/patch-add-apikey-monitor.sql |  7 ++++++
 server/database.js              |  1 +
 server/model/monitor.js         | 41 +++++++++++++++++++++++++++++++++
 server/server.js                |  1 +
 src/languages/en.js             |  1 +
 src/pages/EditMonitor.vue       | 24 ++++++++++++++++---
 6 files changed, 72 insertions(+), 3 deletions(-)
 create mode 100644 db/patch-add-apikey-monitor.sql

diff --git a/db/patch-add-apikey-monitor.sql b/db/patch-add-apikey-monitor.sql
new file mode 100644
index 00000000..1a30bdf3
--- /dev/null
+++ b/db/patch-add-apikey-monitor.sql
@@ -0,0 +1,7 @@
+-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
+BEGIN TRANSACTION;
+
+ALTER TABLE monitor
+    ADD apikey VARCHAR(64) default '' not null;
+
+COMMIT;
diff --git a/server/database.js b/server/database.js
index 2f6c1c5f..c0b6bc1d 100644
--- a/server/database.js
+++ b/server/database.js
@@ -46,6 +46,7 @@ class Database {
         "patch-improve-performance.sql": true,
         "patch-2fa.sql": true,
         "patch-add-retry-interval-monitor.sql": true,
+        "patch-add-apikey-monitor.sql": true,
         "patch-incident-table.sql": true,
         "patch-group-table.sql": true,
     }
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 9a80225e..e484ccec 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -59,6 +59,7 @@ class Monitor extends BeanModel {
             weight: this.weight,
             active: this.active,
             type: this.type,
+            apikey: this.apikey,
             interval: this.interval,
             retryInterval: this.retryInterval,
             keyword: this.keyword,
@@ -236,6 +237,46 @@ class Monitor extends BeanModel {
 
                     bean.msg = dnsMessage;
                     bean.status = UP;
+                } else if (this.type === "steam") {
+                    const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
+                    const filter = `addr\\${this.hostname}:${this.port}`;
+
+                    let res = await axios.get(steamApiUrl, {
+                        timeout: this.interval * 1000 * 0.8,
+                        headers: {
+                            "Accept": "*/*",
+                            "User-Agent": "Uptime-Kuma/" + version,
+                        },
+                        httpsAgent: new https.Agent({
+                            maxCachedSessions: 0,      // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
+                            rejectUnauthorized: ! this.getIgnoreTls(),
+                        }),
+                        maxRedirects: this.maxredirects,
+                        validateStatus: (status) => {
+                            return checkStatusCode(status, this.getAcceptedStatuscodes());
+                        },
+                        params: {
+                            filter: filter,
+                            key: this.apikey,
+                        }
+                    });
+
+                    bean.msg = `${res.status} - ${res.statusText}`;
+                    bean.ping = await ping(this.hostname);
+
+                    let data = res.data;
+
+                    // Convert to string for object/array
+                    if (typeof data !== "string") {
+                        data = JSON.stringify(data);
+                    }
+
+                    if (data.includes(`${this.hostname}:${this.port}`)) {
+                        bean.msg += ", server is found";
+                        bean.status = UP;
+                    } else {
+                        throw new Error(bean.msg + ", but server is not found");
+                    }
                 }
 
                 if (this.isUpsideDown()) {
diff --git a/server/server.js b/server/server.js
index f5a8b16e..fb8db1f7 100644
--- a/server/server.js
+++ b/server/server.js
@@ -504,6 +504,7 @@ exports.entryPage = "dashboard";
                 bean.hostname = monitor.hostname;
                 bean.maxretries = monitor.maxretries;
                 bean.port = monitor.port;
+                bean.apikey = monitor.apikey;
                 bean.keyword = monitor.keyword;
                 bean.ignoreTls = monitor.ignoreTls;
                 bean.upsideDown = monitor.upsideDown;
diff --git a/src/languages/en.js b/src/languages/en.js
index 75d8f30c..6b5dadf3 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -178,4 +178,5 @@ export default {
     "Add a monitor": "Add a monitor",
     "Edit Status Page": "Edit Status Page",
     "Go to Dashboard": "Go to Dashboard",
+    steamApiKeyDescription: "For monitoring a Steam Gameserver you need a steam Web-API key. You can register your api key here: https://steamcommunity.com/dev",
 };
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 84231b1a..0a68a34a 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -26,6 +26,9 @@
                                     <option value="dns">
                                         DNS
                                     </option>
+                                    <option value="steam">
+                                        Steam Gameserver
+                                    </option>
                                 </select>
                             </div>
 
@@ -48,17 +51,23 @@
                             </div>
 
                             <!-- TCP Port / Ping / DNS only -->
-                            <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
+                            <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' " class="my-3">
                                 <label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
                                 <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
                             </div>
 
                             <!-- For TCP Port Type -->
-                            <div v-if="monitor.type === 'port' " class="my-3">
+                            <div v-if="monitor.type === 'port'" class="my-3">
                                 <label for="port" class="form-label">{{ $t("Port") }}</label>
                                 <input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
                             </div>
 
+                            <!-- For Steam Query Port Type -->
+                            <div v-if="monitor.type === 'steam' " class="my-3">
+                                <label for="queryport" class="form-label">{{ $t("Query Port") }}</label>
+                                <input id="queryport" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
+                            </div>
+
                             <!-- For DNS Type -->
                             <template v-if="monitor.type === 'dns'">
                                 <div class="my-3">
@@ -93,6 +102,15 @@
                                 </div>
                             </template>
 
+                            <!-- For Steam Type -->
+                            <div class="my-3" v-if="monitor.type === 'steam'">
+                                <label for="steamApiKey" class="form-label">{{ $t("Steam Web-API Key") }}</label>
+                                <input id="steamApiKey" v-model="monitor.apikey" type="text" class="form-control" required>
+                                <div class="form-text">
+                                    {{ $t("steamApiKeyDescription") }}
+                                </div>
+                            </div>
+
                             <div class="my-3">
                                 <label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
                                 <input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1">
@@ -328,7 +346,7 @@ export default {
                     }
                 }
             } else if (this.isEdit) {
-                this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
+                this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {                    
                     if (res.ok) {
                         this.monitor = res.monitor;