Merge branch 'master' into pterodactyl-🥚

This commit is contained in:
Smith
2023-08-05 22:29:23 +02:00
21 changed files with 827 additions and 2851 deletions

View File

@@ -16,4 +16,4 @@ jobs:
run: flyctl apps create ${{ github.repository_owner }}-gsw --org personal
- name: 💽 Create fly.io volume
run: flyctl volumes create gsw_data --size 1 --region ${{ secrets.FLY_REGION }} --app ${{ github.repository_owner }}-gsw
run: flyctl volumes create gsw_data --size 1 --region ${{ secrets.FLY_REGION }} --app ${{ github.repository_owner }}-gsw --yes

View File

@@ -1,4 +1,4 @@
# <img src="https://i.imgur.com/2Ok3pxv.png" width="40" align="left"> Game Server Watcher
# <img src="https://github.com/a-sync/game-server-watcher/assets/14183614/66a8ffa8-c547-4f9c-b312-4c855da80c20" width="40" align="left"> Game Server Watcher
A simple discord/telegram bot that can be hosted on a free service to monitor your game servers and players in style. 😎
# About the Project
@@ -9,18 +9,8 @@ The main goals of this repo:
1. should be able to host on a free service (_target atm. is fly.io (node.js 16)_)
1. graciously add more features based on community feedback via [discord](https://discord.gg/4tsbftsGJz) and [github](https://github.com/a-sync/game-server-watcher/discussions/new?category=ideas-requests)
<details>
<summary><h1>Screenshots</h1></summary>
### Discord
![discord](https://user-images.githubusercontent.com/14183614/162092529-e1645b44-2650-4893-8123-7ba187b1f51c.png)
### Telegram
![telegram](https://user-images.githubusercontent.com/14183614/162092488-f28bd60c-88bf-4b1e-a31e-d7dca51d8c28.png)
### GSW Control Panel
![gsw control panel](https://user-images.githubusercontent.com/14183614/167517240-a4d02cb2-8037-44c0-abaa-c92901357e35.png)
</details>
# Screenshots
<img src="https://user-images.githubusercontent.com/14183614/162092529-e1645b44-2650-4893-8123-7ba187b1f51c.png" height="360"> <img src="https://user-images.githubusercontent.com/14183614/162092488-f28bd60c-88bf-4b1e-a31e-d7dca51d8c28.png" height="360"> <img src="https://user-images.githubusercontent.com/14183614/167517240-a4d02cb2-8037-44c0-abaa-c92901357e35.png" height="360">
# Project Status
The code itself is stable and continuously tested/deployed from the master branch.
@@ -37,14 +27,14 @@ The project is in a very early stage. More detailed customization options and fe
* refresh on reaction
* watched players (notify when a watched player enters/leaves the server)
* detect when the server goes offline, notify when player number crosses a threshold
* bot commands (reinit message, cleanup, start/stop, configure)
* bot commands (reinit message, cleanup, start/stop, post server status, configure)
* more integrations: slack, ms teams, twillio (email, sms)
* ~~web ui to manage & configure the servers and bots~~
* put custom information in the channel name or bot status (online status indicator, number of players, map)
* github action workflows to deploy to other cloud providers (aws, linode, atlantic, vultr, pikapods, okteto, ibm cloud etc.)
* SQL, JSON or object store database support (postgres, redis etc.)
* run as stateless serverless function (aws lambda, azure function, heroku, vercel, fly.io machines etc.)
* [pterodactyl](https://pterodactyl.io) egg release
* pterodactyl egg release
# Getting Started
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See [deployment](#deployment) for notes on how to deploy the project on a live system.

8
TODO Normal file
View File

@@ -0,0 +1,8 @@
// move loop inside watcher instance
// flush game server specific (d/t) data
// response normalization: https://github.com/GameServerManagers/LinuxGSM/blob/master/lgsm/functions/query_gamedig.sh
//v3
// refactor config structure
// introduce storage layer and add free postgresql support
// support gamedig udpListenPort config

View File

@@ -3,7 +3,7 @@
"description": "A simple discord/telegram bot that can be hosted on a free service to monitor your game servers and players in style. 😎",
"website": "https://github.com/a-sync/game-server-watcher",
"repository": "https://github.com/a-sync/game-server-watcher",
"logo": "https://i.imgur.com/2Ok3pxv.png",
"logo": "https://github.com/a-sync/game-server-watcher/assets/14183614/66a8ffa8-c547-4f9c-b312-4c855da80c20",
"env": {
"SECRET": {
"description": "Admin secret",

View File

@@ -1,46 +1,31 @@
app = "game-server-watcher"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
primary_region = "ams"
[env]
DBG=0
REFRESH_TIME_MINUTES=2
DATA_PATH="/data/"
PRIMARY_REGION = "ams"
DBG = "0"
DATA_PATH = "/data/"
REFRESH_TIME_MINUTES = "2"
[mounts]
source="gsw_data"
destination="/data"
source = "gsw_data"
destination = "/data"
[experimental]
auto_rollback = true
allowed_public_ports = []
# cmd = ["node", "--max-old-space-size=180", "server.js"]
[[services]]
http_checks = []
[http_service]
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[http_service.concurrency]
type = "requests"
soft_limit = 50
hard_limit = 60
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
[[http_service.checks]]
grace_period = "10s"
interval = "30s"
method = "GET"
timeout = "5s"
path = "/"

3034
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "game-server-watcher",
"version": "2.0.0",
"version": "2.1.0",
"description": "A simple discord/telegram bot that can be hosted on a free service to monitor your game servers and players in style 😎",
"main": "src/server",
"scripts": {
@@ -23,7 +23,7 @@
"license": "AGPL-3.0-or-later",
"dependencies": {
"@commonify/lowdb": "^3.0.0",
"axios": "^0.27.2",
"axios": "^1.4.0",
"discord.js": "^14.7.1",
"dotenv": "^16.0.3",
"gamedig": "^4.0.6",
@@ -31,11 +31,11 @@
"mustache": "^4.2.0"
},
"devDependencies": {
"@types/gamedig": "^4.0.0",
"@types/gamedig": "^4.0.1",
"@types/node": "^16.9.0",
"eslint": "^8.34.0",
"nodemon": "^2.0.20",
"typescript": "^4.9.5"
"nodemon": "^3.0.1",
"typescript": "^5.1.6"
},
"engines": {
"node": ">=16.9.0"

BIN
public/bg0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 KiB

BIN
public/bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

BIN
public/bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

BIN
public/bg3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

BIN
public/bg4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

BIN
public/bg5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,188 @@
{
"title": "Game Server",
"type": "object",
"required": [
"name",
"type",
"host",
"port"
],
"properties": {
"name": {
"title": "Name",
"type": "string",
"minLength": 1,
"options": {
"grid_columns": 12
}
},
"type": {
"title": "Gamedig type",
"description": "Look for the <i>GameDig Type ID</i> in the <a href=\"https://github.com/gamedig/node-gamedig#games-list\">games list</a>.",
"type": "string",
"minLength": 1,
"options": {
"grid_columns": 3
}
},
"host": {
"title": "Host name or IP",
"type": "string",
"pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9])$",
"options": {
"grid_columns": 3,
"error_messages": {
"en": {
"error_pattern": "Value must be a valid host name or IP"
}
}
}
},
"port": {
"title": "Port number",
"type": "integer",
"minimum": 1,
"maximum": 65535,
"format": "number",
"options": {
"grid_columns": 3
}
},
"appId": {
"title": "Steam App ID",
"description": "Look for the <i>AppID</i> in the <a href=\"https://steamdb.info/apps/\">apps list</a>.",
"type": "integer",
"minimum": 1,
"format": "number",
"options": {
"grid_columns": 3
}
},
"discord": {
"type": "array",
"title": "Discord channels",
"description": "The simplest way to get a discord channel ID is to enable developer mode in settings, then right clicking on the channel name will give you the <b>Copy ID</b> option.",
"minItems": 0,
"uniqueItems": true,
"items": {
"type": "object",
"title": "Channel",
"required": [
"channelId"
],
"properties": {
"channelId": {
"title": "Channel ID",
"type": "string"
}
}
},
"format": "table",
"options": {
"grid_columns": 12
}
},
"telegram": {
"type": "array",
"title": "Telegram chats",
"description": "The simplest way to get a telegram chat ID is to invite <a target=\"_blank\" href=\"https://telegram.me/getidsbot\">@getidsbot</a> and use <code>/start</code> to make it post (among other data) the chat ID.",
"minItems": 0,
"uniqueItems": true,
"items": {
"type": "object",
"title": "Chat",
"required": [
"chatId"
],
"properties": {
"chatId": {
"type": "string",
"title": "Chat ID"
}
}
},
"format": "table",
"options": {
"grid_columns": 12
}
},
"graphHistoryHours": {
"title": "Graph history time span (hours)",
"type": "integer",
"default": 12,
"minimum": 1,
"maximum": 24,
"format": "range",
"options": {
"grid_columns": 12
}
},
"timezoneOffset": {
"title": "Time zone offset of the server",
"default": 0,
"type": "number",
"minimum": -12,
"maximum": 14,
"format": "range",
"options": {
"grid_columns": 12
}
},
"givenPortOnly": {
"title": "givenPortOnly",
"description": "Only attempt to query server on given port.",
"type": "boolean",
"options": {
"grid_columns": 3
}
},
"requestRules": {
"title": "requestRules (Valve)",
"description": "Additional server info may be fetched into the <code>raw</code> field.",
"type": "boolean",
"options": {
"grid_columns": 3
}
},
"teamspeakQueryPort": {
"title": "teamspeakQueryPort (TeamSpeak 3)",
"type": "integer",
"minimum": 1,
"maximum": 65535,
"format": "number",
"options": {
"grid_columns": 3
}
},
"guildId": {
"title": "guildId (Discord)",
"type": "string",
"options": {
"grid_columns": 3
}
},
"login": {
"title": "login (Nadeo)",
"type": "string",
"options": {
"grid_columns": 3
}
},
"password": {
"title": "password (Nadeo)",
"type": "string",
"options": {
"grid_columns": 3
}
},
"token": {
"title": "token (Terraria)",
"type": "string",
"options": {
"grid_columns": 3
}
}
},
"format": "grid-strict",
"headerTemplate": "{{#self.name}}{{self.name}}{{/self.name}}{{^self.name}}{{self.type}}:{{self.host}}:{{self.port}}{{/self.name}}"
}

View File

@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://i.imgur.com/2Ok3pxv.png" rel="icon" type="image/png">
<link href="favicon.png" rel="icon" type="image/png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- <link href="https://cdn.jsdelivr.net/npm/@forevolve/bootstrap-dark@latest/dist/css/toggle-bootstrap-dark.min.css" rel="stylesheet"> -->
@@ -38,7 +38,7 @@
<body>
<nav id="top-menu" class="navbar navbar-expand-lg navbar-dark invisible">
<span class="navbar-brand mb-0 h1">
<img src="https://i.imgur.com/2Ok3pxv.png" width="30" height="30" class="d-inline-block align-middle mr-sm-1 mr-md-2 mr-lg-3" alt="">
<img src="favicon.png" width="30" height="30" class="d-inline-block align-middle mr-sm-1 mr-md-2 mr-lg-3" alt="">
<span>GSW Control Panel</span>
</span>
<ul class="navbar-nav mr-auto">

View File

@@ -12,7 +12,7 @@ body {
background-attachment: fixed;
background-repeat: no-repeat;
background-size: cover;
background-image: url('https://i.imgur.com/lTly23N.png');
background-image: url('bg0.png');
}
body>nav {
@@ -61,13 +61,6 @@ body>nav {
vertical-align: top;
}
#config-form input[type=checkbox] {
vertical-align: middle;
zoom: 2;
cursor: pointer;
margin-right: 0.25rem !important;
}
#config-form .btn-group {
display: inline-flex !important;
margin-bottom: 0.25rem;
@@ -86,14 +79,62 @@ body>nav {
margin: 0 0 0.75rem 0;
}
#config-form .json-editor-btn-add {
#config-form .json-editor-btn-add,
#config-form .json-editor-btn-save,
#config-form .json-editor-btn-file-import {
background-color: darkgreen;
}
#config-form .json-editor-btn-add span {
display: none;
}
#config-form .json-editor-btn-delete {
#config-form .json-editor-btn-delete,
#config-form .json-editor-btn-cancel {
background-color: darkred;
}
#config-form .json-editor-btn-delete span,
#config-form .json-editor-btn-cancel span {
display: none;
}
#config-form .json-editor-btn-copy,
#config-form .json-editor-btn-file-export {
background-color: darkblue;
}
#config-form .json-editor-btn-copy span {
display: none;
}
#config-form .je-edit-json--textarea {
width: auto !important;
height: auto !important;
min-width: 300px;
min-height: 300px;
}
#config-form .je-modal {
box-shadow: rgba(0,0,0,0.5) 3px 3px 6px 2px !important;
border: 1px solid rgba(0,0,0,0.25) !important;
}
#config-form .property-selector {
min-width: max-content;
max-height: none !important;
padding: 5px !important;
}
#config-form .property-selector .form-check-input {
cursor: pointer;
margin-left: -0.85rem !important;
}
#config-form .property-selector .form-check-label {
cursor: pointer;
padding-left: 5px !important;
}
footer>a>svg {
fill: #c6cbd1;
}
@@ -124,4 +165,4 @@ footer>a>svg {
#bye kbd {
box-shadow: 0 0.125rem 0.25rem rgb(255, 255, 255, 0.75) !important;
}
}

View File

@@ -2,7 +2,7 @@ let configEditor;
$(async () => {
const gswFeatures = await fetchApi('GET', 'features');
if (gswFeatures) {
await setRandomBg().catch(() => {});
await setRandomBg().catch(() => { });
setInterval(setRandomBg, 60000);
let unsavedChanges = false;
@@ -33,21 +33,22 @@ $(async () => {
"iconlib": "fontawesome5",
"object_layout": "normal",
"template": "mustache",
"compact": 0,
"show_errors": "always",
"required_by_default": 0,
"no_additional_properties": 1,
"display_required_only": 0,
"remove_empty_properties": 1,
"keep_oneof_values": 0,
"ajax": 0,
"display_required_only": 1,
"show_opt_in": 0,
"remove_empty_properties": 0,
"use_default_values": 1,
"ajax": 1,
"ajaxCredentials": 0,
"show_opt_in": 1,
"disable_edit_json": 0,
"disable_collapse": 1,
"disable_properties": 1,
"disable_properties": 0,
"disable_array_add": 0,
"disable_array_reorder": 1,
"disable_array_delete": 0,
"disable_array_reorder": 1,
"enable_array_copy": 1,
"array_controls_top": 0,
"disable_array_delete_all_rows": 1,
@@ -61,148 +62,15 @@ $(async () => {
"uniqueItems": true,
"format": "tabs-top",
"items": {
"title": "Game Server",
"type": "object",
"required": [
"name",
"type",
"host",
"port"
],
"properties": {
"name": {
"title": "Name",
"type": "string",
"options": {
"grid_columns": 12
}
},
"type": {
"title": "Gamedig type",
"description": "Look for the <i>GameDig Type ID</i> in the <a href=\"https://github.com/gamedig/node-gamedig#games-list\">games list</a>.",
"type": "string",
"options": {
"grid_columns": 6
}
},
"appId": {
"title": "Steam App ID",
"description": "Look for the <i>AppID</i> in the <a href=\"https://steamdb.info/apps/\">apps list</a>.",
"type": "integer",
"format": "number",
"options": {
"grid_columns": 6
}
},
"host": {
"title": "Host name or IP",
"type": "string",
"options": {
"grid_columns": 6
}
},
"port": {
"title": "Port number",
"type": "integer",
"minimum": 1,
"maximum": 65535,
"format": "number",
"options": {
"grid_columns": 6
}
},
"updateIntervalMinutes": {
"title": "Update interval (minutes) [NOT IMPLEMENTED]",
"type": "integer",
"default": 5,
"minimum": 1,
"maximum": 60,
"format": "range",
"options": {
"grid_columns": 12
}
},
"graphHistoryHours": {
"title": "Graph history time span (hours)",
"type": "integer",
"default": 12,
"minimum": 1,
"maximum": 24,
"format": "range",
"options": {
"grid_columns": 12
}
},
"timezoneOffset": {
"title": "Time zone offset of the server",
"default": 0,
"type": "number",
"minimum": -12,
"maximum": 14,
"format": "range",
"options": {
"grid_columns": 6,
"grid_break": true
}
},
"discord": {
"type": "array",
"title": "Discord channels",
"description": "The simplest way to get a discord channel ID is to enable developer mode in settings, then right clicking on the channel name will give you the <b>Copy ID</b> option.",
"minItems": 0,
"uniqueItems": true,
"items": {
"type": "object",
"title": "Channel",
"required": [
"channelId"
],
"properties": {
"channelId": {
"title": "Channel ID",
"type": "string"
}
}
},
"format": "table",
"options": {
"grid_columns": 12
}
},
"telegram": {
"type": "array",
"title": "Telegram chats",
"description": "The simplest way to get a telegram chat ID is to invite <a target=\"_blank\" href=\"https://telegram.me/getidsbot\">@getidsbot</a> and use <code>/start</code> to make it post (among other data) the chat ID.",
"minItems": 0,
"uniqueItems": true,
"items": {
"type": "object",
"title": "Chat",
"required": [
"chatId"
],
"properties": {
"chatId": {
"type": "string",
"title": "Chat ID"
}
}
},
"format": "table",
"options": {
"grid_columns": 12
}
}
},
"format": "grid",
"headerTemplate": "{{#self.name}}{{self.name}}{{/self.name}}{{^self.name}}{{self.type}}:{{self.host}}:{{self.port}}{{/self.name}}"
"$ref": "game-server-config.schema.json"
}
},
startval: gswConfig.config
});
configEditor.on('ready', () => {
addExportButton();
addExportImportButtons();
$('#config-form').submit(e => {
e.preventDefault();
});
@@ -295,13 +163,15 @@ function checkForChanges(formCurrVal) {
}
};
function addExportButton() {
function addExportImportButtons() {
const button_holder = configEditor.root.theme.getHeaderButtonHolder();
const exportBtn = configEditor.root.getButton('Export', 'save', 'Export As JSON File');
const exportBtn = configEditor.root.getButton('Export', 'file-export', 'Export as JSON file');
const importBtn = configEditor.root.getButton('Import', 'file-import', 'Import from JSON file');
button_holder.appendChild(exportBtn);
button_holder.appendChild(importBtn);
configEditor.root.header.parentNode.insertBefore(button_holder, configEditor.root.header.nextSibling);
exportBtn.addEventListener('click', e => {
exportBtn.onclick = e => {
e.preventDefault();
const dt = (new Date()).toISOString().slice(0, 19).replace(/\D/g, '');
const filename = dt + '-gsw.config.json';
@@ -322,7 +192,28 @@ function addExportButton() {
'cancelable': false
}));
}
}, false);
};
const fileSelector = document.createElement('input');
fileSelector.type = 'file';
fileSelector.accept = 'application/json';
fileSelector.onchange = e => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsText(file, 'UTF-8');
reader.onload = readerEvent => {
const content = JSON.parse(readerEvent.target.result);
configEditor.setValue(content);
};
};
importBtn.onclick = e => {
e.preventDefault();
fileSelector.click();
};
}
function logout() {
@@ -513,11 +404,11 @@ async function generateBearerToken(secret) {
function setRandomBg() {
const bgs = [
'https://i.imgur.com/bDzhwG5.png',
'https://i.imgur.com/rA8JXuI.png',
'https://i.imgur.com/pstAPIw.png',
'https://i.imgur.com/gQD3xfo.png',
'https://i.imgur.com/iKTfM8z.png'
'bg1.png',
'bg2.png',
'bg3.png',
'bg4.png',
'bg5.png'
];
const bgImg = new Image();

View File

@@ -1,5 +1,5 @@
import axios from 'axios';
import { Player, query, QueryResult } from 'gamedig';
import { Player, query, QueryResult, Type } from 'gamedig';
import { Low, JSONFile } from '@commonify/lowdb';
import ipRegex from './lib/ipregex';
import getIP from './lib/getip';
@@ -48,6 +48,28 @@ interface qRes extends QueryResult {
numplayers: number;
}
export interface QueryOptions {
type: Type;
host: string;
port: number;
maxAttempts?: number;
socketTimeout?: number;
attemptTimeout?: number;
givenPortOnly?: boolean;
ipfamily?: 0 | 4 | 6 | undefined;
debug?: boolean;
requestRules?: boolean;
// Discord
guildId?: string;
// Nadeo
login?: string;
password?: string;
// Teamspeak 3
teamspeakQueryPort?: number;
// Terraria
token?: string;
}
export class GameServer {
public ip: string;
public info?: Info;
@@ -70,7 +92,7 @@ export class GameServer {
if (DBG) console.log('gs.up', this.config.host, this.config.port);
let info = await this.gamedig();
if (DBG) console.log('gs.gamedig', Object.assign({}, info, { players: info?.players.length }));
if (DBG) console.log('gs.gamedig %j', Object.assign({}, info, { players: info?.players.length }));
if (!info && STEAM_WEB_API_KEY && this.config.appId) {
info = await this.steam();
if (DBG) console.log('gs.steam', info);
@@ -78,7 +100,7 @@ export class GameServer {
if (info) {
if (info.players.length > 0 && DBG) {
console.log('gs.players.0', info.players[0]);
console.log('gs.players.0 %j', info.players[0]);
}
this.online = true;
@@ -94,10 +116,17 @@ export class GameServer {
async gamedig(): Promise<Info | null> {
try {
const res = await query({
type: this.config.type,
host: this.config.host,
port: this.config.port,
type: this.config.type,
}) as qRes;
givenPortOnly: this.config.givenPortOnly,
requestRules: this.config.requestRules,
guildId: this.config.guildId,
login: this.config.login,
password: this.config.password,
teamspeakQueryPort: this.config.teamspeakQueryPort,
token: this.config.token
} as QueryOptions) as qRes;
const raw = res.raw as { game: string; folder: string; };
const game = raw.game || raw.folder || this.config.type;

View File

@@ -1,7 +1,8 @@
import fs from 'fs';
import crypto from 'crypto';
import { createServer } from 'http';
import { URL } from 'url';
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import { createServer } from 'node:http';
import { URL } from 'node:url';
import 'dotenv/config';
@@ -30,22 +31,28 @@ interface ApiResponse {
}
}
const EXT_MIME: Record<string, string> = {
'html': 'text/html',
'css': 'text/css',
'js': 'application/javascript',
'json': 'application/json',
'png': 'image/png'
};
createServer(async (req, res) => {
if (DBG) console.log('DBG: %j %j', (new Date()), req.url);
const reqUrl = new URL(req.url || '', 'http://localhost');
const p = reqUrl.pathname === '/' ? 'index.html' : reqUrl.pathname.slice(1);
const p = reqUrl.pathname === '/' ? 'index.html' : path.normalize(reqUrl.pathname).slice(1);
const ext = path.extname(p).slice(1);
if (['index.html', 'main.css', 'main.js'].includes(p)) {
if (ext in EXT_MIME && !p.includes('/') && !p.includes('\\')) {
if (SECRET !== '') {
let ct = 'text/html';
if (p === 'main.css') ct = 'text/css';
else if (p === 'main.js') ct = 'text/javascript';
res.writeHead(200, {
'Content-Type': ct,
'Content-Type': EXT_MIME[ext] || 'plain/text',
'Cache-Control': 'max-age=' + String(CACHE_MAX_AGE)
});
fs.createReadStream('./public/' + p).pipe(res);
fs.createReadStream(path.resolve('./public/', p)).pipe(res);
} else {
res.end('Configure the `SECRET` env var to enable the web UI!');
}

View File

@@ -20,15 +20,28 @@ interface TelegramConfig {
export interface GameServerConfig {
name: string;
type: Type;
host: string;
port: number;
appId?: number;//0
updateIntervalMinutes?: number;//5
graphHistoryHours?: number;//12
timezoneOffset?: number;//0
discord?: DiscordConfig[],
discord?: DiscordConfig[];
telegram?: TelegramConfig[];
// node-gamedig stuff
type: Type;
host: string;
port: number;
givenPortOnly?: boolean;
requestRules?: boolean;
// * Discord
guildId?: string;
// * Nadeo
login?: string;
password?: string;
// * Teamspeak 3
teamspeakQueryPort?: number;
// * Terraria
token?: string;
}
const adapter = new JSONFile<GameServerConfig[]>(DATA_PATH + 'default.config.json');