Compare commits
No commits in common. "main" and "0.1.5" have entirely different histories.
15
README.md
|
|
@ -5,26 +5,27 @@ It posts the server name, password, time, and player count. Written in Node.js,
|
|||
discord.js library to interact with Discord and fetches server stats via the XML feed
|
||||
(accessible through the server's web interface). The update interval is configurable.
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
<details>
|
||||
<summary>Discord embed in english</summary>
|
||||
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Discord embed in german</summary>
|
||||
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Terminal output (NodeJS)</summary>
|
||||
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
|
|
@ -72,27 +73,21 @@ discord.js library to interact with Discord and fetches server stats via the XML
|
|||
|
||||
1. Navigate to the root directory of the cloned repository.
|
||||
2. Build and start the container:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
3. The bot should now be running and posting server stats to the specified Discord channel.
|
||||
|
||||
### Option 2: Run Without Docker (Using Node.js)
|
||||
|
||||
1. Navigate to the root directory of the cloned repository.
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Start the bot:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
4. The bot should now be running and posting server stats to the specified Discord channel.
|
||||
- Note: Closing the terminal will stop the bot. Use a process manager like [PM2](https://pm2.io/) to keep it running.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"application": {
|
||||
"serverPassword": "TypeMyServerPasswordHere",
|
||||
"serverStatsUrl": "http://127.0.0.1:8080/feed/dedicated-server-stats.xml",
|
||||
"serverMapUrl": "http://127.0.0.1:8080/feed/dedicated-server-stats-map.jpg",
|
||||
"serverStatsUrl": "http://1.1.1.1:8080/feed/dedicated-server-stats.xml",
|
||||
"serverMapUrl": "http://1.1.1.1:8080/feed/dedicated-server-stats-map.jpg",
|
||||
"updateIntervalSeconds": 30
|
||||
},
|
||||
"discord": {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"application": {
|
||||
"serverPassword": "TypeMyServerPasswordHere",
|
||||
"serverStatsUrl": "http://127.0.0.1:8080/feed/dedicated-server-stats.xml",
|
||||
"serverMapUrl": "http://127.0.0.1:8080/feed/dedicated-server-stats-map.jpg",
|
||||
"serverStatsUrl": "http://1.1.1.1:8080/feed/dedicated-server-stats.xml",
|
||||
"serverMapUrl": "http://1.1.1.1:8080/feed/dedicated-server-stats-map.jpg",
|
||||
"updateIntervalSeconds": 30
|
||||
},
|
||||
"discord": {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
version: "2"
|
||||
|
||||
services:
|
||||
ls25bot:
|
||||
build:
|
||||
|
|
@ -23,13 +25,3 @@ services:
|
|||
echo "Restarting ls25bot container at $(date)"
|
||||
docker restart ls25bot
|
||||
done
|
||||
|
||||
# This is used for development purposes only
|
||||
webserver:
|
||||
image: nginx:alpine
|
||||
container_name: ls25bot-webserver
|
||||
restart: always
|
||||
volumes:
|
||||
- ./misc/files:/usr/share/nginx/html
|
||||
ports:
|
||||
- "8080:80"
|
||||
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ls25-discord-bot",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.5",
|
||||
"description": "A simple discord bot for farming simulator 25",
|
||||
"main": "source/Main.ts",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import {Client, IntentsBitField} from 'discord.js';
|
|||
import Configuration from "./Services/Configuration";
|
||||
import Logging from "./Services/Logging";
|
||||
import DiscordService from "./Services/DiscordEmbed";
|
||||
import VersionChecker from './Services/VersionChecker';
|
||||
|
||||
// Create a new logger instance and configuration instance
|
||||
const appLogger = Logging.getLogger();
|
||||
|
|
@ -21,24 +20,6 @@ if(!appConfig.isConfigurationValid()) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the version of the bot and log if it is up to date
|
||||
*/
|
||||
const versionChecker = new VersionChecker();
|
||||
versionChecker.checkVersionIsUpdated().then((isUpToDate: boolean): void => {
|
||||
if (!isUpToDate) {
|
||||
appLogger.warn(`====================================================`);
|
||||
appLogger.warn(`====================================================`);
|
||||
appLogger.warn(`The bot is not up to date. Please update it soon.`);
|
||||
appLogger.warn(`Use the command 'git pull && docker compose up -d --build' to update the bot.`);
|
||||
appLogger.warn(`====================================================`);
|
||||
appLogger.warn(`====================================================`);
|
||||
|
||||
} else {
|
||||
appLogger.info(`The bot is up to date. No update needed.`);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a new discord client instance
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -92,16 +92,6 @@ export default class DiscordEmbed {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates a string at a given length
|
||||
* @param text The input text to truncate
|
||||
* @param maxLength The allowed characters until truncation
|
||||
* @returns The truncated string
|
||||
*/
|
||||
private async truncateText(text: string, maxLength = 1024): Promise<string> {
|
||||
return text.length > maxLength ? text.slice(0, maxLength - 3) + '...' : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send server stats embed in a channel
|
||||
* @param serverStats
|
||||
|
|
@ -110,6 +100,8 @@ export default class DiscordEmbed {
|
|||
let embed = new EmbedBuilder();
|
||||
let config = this.appConfiguration;
|
||||
|
||||
serverStats.getServerMonth();
|
||||
|
||||
embed.setTitle(config.translation.discordEmbed.title);
|
||||
if (!serverStats.isOnline()) {
|
||||
embed.setColor(0xCA0000);
|
||||
|
|
@ -123,8 +115,6 @@ export default class DiscordEmbed {
|
|||
embed.setThumbnail(config.application.serverMapUrl);
|
||||
|
||||
let playerListString: string;
|
||||
let playerListTitleString = `${config.translation.discordEmbed.titlePlayerCount} (${serverStats.getPlayerCount()??0}/${serverStats.getMaxPlayerCount()??0}):`;
|
||||
|
||||
if(serverStats.getPlayerList().length === 0) {
|
||||
playerListString = config.translation.discordEmbed.noPlayersOnline;
|
||||
} else {
|
||||
|
|
@ -139,7 +129,7 @@ export default class DiscordEmbed {
|
|||
let serverMods = serverStats.getServerMods();
|
||||
let serverModsText = "-/-";
|
||||
if(serverMods.length > 0) {
|
||||
serverModsText = await this.truncateText(serverMods.map(mod => `${mod.name}`).join(', '));
|
||||
serverModsText = serverMods.map(mod => `${mod.name}`).join(', ');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
|
|
@ -150,11 +140,12 @@ export default class DiscordEmbed {
|
|||
{name: config.translation.discordEmbed.titleServerMap, value: serverStats.getServerMap()},
|
||||
{name: config.translation.discordEmbed.titleServerMods, value: serverModsText},
|
||||
{
|
||||
name: playerListTitleString,
|
||||
name: `${config.translation.discordEmbed.titlePlayerCount} (${serverStats.getPlayerCount()}/${serverStats.getMaxPlayerCount()}):`,
|
||||
value: playerListString
|
||||
},
|
||||
);
|
||||
}
|
||||
this.appLogger.debug(embed);
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
|
|
@ -137,6 +137,37 @@ export default class ServerStatusFeed {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server month as a string
|
||||
* @returns {string} The server month as a string
|
||||
*/
|
||||
public getServerMonth(): string {
|
||||
let config: IConfiguration = Configuration.getConfiguration();
|
||||
let dayTime = this.getServerStats()?.Server.dayTime;
|
||||
if (dayTime === undefined) {
|
||||
return "Error";
|
||||
}
|
||||
let month = dayTime / (60 * 60 * 1000 * 24);
|
||||
month = month % 1;
|
||||
month = month * 12;
|
||||
month = Math.floor(month);
|
||||
let months = [
|
||||
config.translation.common.monthJanuary,
|
||||
config.translation.common.monthFebruary,
|
||||
config.translation.common.monthMarch,
|
||||
config.translation.common.monthApril,
|
||||
config.translation.common.monthMay,
|
||||
config.translation.common.monthJune,
|
||||
config.translation.common.monthJuly,
|
||||
config.translation.common.monthAugust,
|
||||
config.translation.common.monthSeptember,
|
||||
config.translation.common.monthOctober,
|
||||
config.translation.common.monthNovember,
|
||||
config.translation.common.monthDecember
|
||||
]
|
||||
return months[month-1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server time in the format HH:MM
|
||||
* @returns {string} The server time in the format HH:MM
|
||||
|
|
@ -156,23 +187,23 @@ export default class ServerStatusFeed {
|
|||
if(minutesString.length === 1) {
|
||||
minutesString = `0${minutesString}`;
|
||||
}
|
||||
return `${hoursString}:${minutesString}`;
|
||||
return `${hoursString}:${minutesString} (${this.getServerMonth()})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server player count
|
||||
* @returns {number | null | undefined} The server player count
|
||||
* @returns {number} The server player count
|
||||
*/
|
||||
public getPlayerCount(): number | null | undefined {
|
||||
return <number>this.getServerStats()?.Server?.Slots?.numUsed;
|
||||
public getPlayerCount(): number {
|
||||
return <number>this.getServerStats()?.Server.Slots.numUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server player count
|
||||
* @returns {number | null | undefined} The server player count
|
||||
* @returns {number} The server player count
|
||||
*/
|
||||
public getMaxPlayerCount(): number | null | undefined {
|
||||
return <number>this.getServerStats()?.Server?.Slots?.capacity;
|
||||
public getMaxPlayerCount(): number {
|
||||
return <number>this.getServerStats()?.Server.Slots.capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
export default class VersionChecker {
|
||||
private readonly localPackageVersion: string;
|
||||
private readonly versionUrl: string = "https://raw.githubusercontent.com/cloudmaker97/FS25-Discord-Bot/refs/heads/main/package.json";
|
||||
|
||||
constructor() {
|
||||
this.localPackageVersion = require('../../package.json').version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the version of the bot is up to date
|
||||
*/
|
||||
public async checkVersionIsUpdated(): Promise<boolean> {
|
||||
const latestVersion = await this.getLatestReleasedVersion();
|
||||
return this.isNewerVersion(latestVersion, this.localPackageVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest released version of the bot from the github repository
|
||||
*/
|
||||
public async getLatestReleasedVersion(): Promise<string> {
|
||||
const response = await fetch(this.versionUrl);
|
||||
const latestPackage = await response.text();
|
||||
const latestVersion = JSON.parse(latestPackage)?.version;
|
||||
return latestVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the latest version is newer than the current version
|
||||
*/
|
||||
public isNewerVersion(latestVersion: string, currentVersion: string) {
|
||||
const v1Parts: number[] = latestVersion.split('.').map(Number);
|
||||
const v2Parts: number[] = currentVersion.split('.').map(Number);
|
||||
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
|
||||
const part1 = v1Parts[i] || 0;
|
||||
const part2 = v2Parts[i] || 0;
|
||||
if (part1 > part2) return false;
|
||||
if (part1 < part2) return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||