Compare commits

...

14 commits
0.1.5 ... main

Author SHA1 Message Date
ed492e9045
Use newer docker compose command
Some checks failed
Docker Build / build-and-push (push) Has been cancelled
2025-10-31 23:54:01 +01:00
24f65956ab Local development with webserver and demo files
Some checks failed
Docker Build / build-and-push (push) Has been cancelled
2025-07-10 17:25:29 +02:00
4e6668c2e5 Increased bot version to 0.1.8 2024-12-12 12:39:05 +01:00
696e8f1749 Added truncate for servers with long list of mods (discord max length 1024 chars) and feed parse optimization 2024-12-12 12:37:32 +01:00
a28796751a Updated the README and uploaded the social banner for this repository (Affinity Photo) 2024-12-11 03:17:38 +00:00
58451f6fd0
Removed the header image of the repository 2024-12-11 04:13:16 +01:00
87a3cb158c
Titelbild aktualisiert 2024-12-11 04:12:14 +01:00
9337105ca7
Update README.md 2024-12-11 04:01:34 +01:00
1e35792419 Changed the image names for correct language association 2024-12-08 02:44:47 +01:00
bcc98514e6
Merge pull request #15 from cloudmaker97/change-time-display 2024-12-08 02:32:00 +01:00
885461f316 Changed version to 0.1.7 (temporarily bugfix) 2024-12-08 02:29:52 +01:00
f4a872546a Removed the display of the game server month due to incorrect values, this must me reimplemented when more details about this is available #2 2024-12-08 02:29:17 +01:00
9fa2db35be
Merge pull request #14 from cloudmaker97/update-info 2024-12-01 12:35:30 +01:00
c2380609b2 Implemented version checking for the discord bot and increased version to 0.1.6 2024-12-01 12:34:50 +01:00
18 changed files with 112 additions and 61 deletions

View file

@ -1,31 +1,30 @@
# Farming Simulator 25 - Discord Bot # Farming Simulator 25 - Discord Bot
This bot periodically updates a Discord channel with stats from a Farming Simulator 25 server. This bot periodically updates a Discord channel with stats from a Farming Simulator 25 server.
It posts the server name, password, time, and player count. Written in Node.js, it uses the It posts the server name, password, time, and player count. Written in Node.js, it uses the
discord.js library to interact with Discord and fetches server stats via the XML feed 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. (accessible through the server's web interface). The update interval is configurable.
## Screenshots ## Screenshots
<details> <details>
<summary>Discord embed in english</summary> <summary>Discord embed in english</summary>
![discord_en.png](misc%2Fimages%2Fdiscord_en.png) ![discord_en.png](misc%2Fimages%2Freadme%2Fdiscord_en.png)
</details> </details>
<details> <details>
<summary>Discord embed in german</summary> <summary>Discord embed in german</summary>
![discord_de.png](misc%2Fimages%2Fdiscord_de.png) ![discord_de.png](misc%2Fimages%2Freadme%2Fdiscord_de.png)
</details> </details>
<details> <details>
<summary>Terminal output (NodeJS)</summary> <summary>Terminal output (NodeJS)</summary>
![bot_terminal.png](misc%2Fimages%2Fbot_terminal.png) ![bot_terminal.png](misc%2Fimages%2Freadme%2Fbot_terminal.png)
</details> </details>
@ -57,8 +56,8 @@ discord.js library to interact with Discord and fetches server stats via the XML
1. Clone the repository to your server 1. Clone the repository to your server
2. Locate the configuration files: 2. Locate the configuration files:
- Use either - Use either
- `config.example-de.json` (for German) - `config.example-de.json` (for German)
- `config.example-en.json` (for English) - `config.example-en.json` (for English)
- Rename the chosen file to `config.json`. - Rename the chosen file to `config.json`.
3. Open `config.json` and fill in the required fields: 3. Open `config.json` and fill in the required fields:
@ -73,21 +72,27 @@ discord.js library to interact with Discord and fetches server stats via the XML
1. Navigate to the root directory of the cloned repository. 1. Navigate to the root directory of the cloned repository.
2. Build and start the container: 2. Build and start the container:
```bash ```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. 3. The bot should now be running and posting server stats to the specified Discord channel.
### Option 2: Run Without Docker (Using Node.js) ### Option 2: Run Without Docker (Using Node.js)
1. Navigate to the root directory of the cloned repository. 1. Navigate to the root directory of the cloned repository.
2. Install dependencies: 2. Install dependencies:
```bash ```bash
npm install npm install
``` ```
3. Start the bot: 3. Start the bot:
```bash ```bash
npm start npm start
``` ```
4. The bot should now be running and posting server stats to the specified Discord channel. 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. - Note: Closing the terminal will stop the bot. Use a process manager like [PM2](https://pm2.io/) to keep it running.

View file

@ -1,8 +1,8 @@
{ {
"application": { "application": {
"serverPassword": "TypeMyServerPasswordHere", "serverPassword": "TypeMyServerPasswordHere",
"serverStatsUrl": "http://1.1.1.1:8080/feed/dedicated-server-stats.xml", "serverStatsUrl": "http://127.0.0.1:8080/feed/dedicated-server-stats.xml",
"serverMapUrl": "http://1.1.1.1:8080/feed/dedicated-server-stats-map.jpg", "serverMapUrl": "http://127.0.0.1:8080/feed/dedicated-server-stats-map.jpg",
"updateIntervalSeconds": 30 "updateIntervalSeconds": 30
}, },
"discord": { "discord": {

View file

@ -1,8 +1,8 @@
{ {
"application": { "application": {
"serverPassword": "TypeMyServerPasswordHere", "serverPassword": "TypeMyServerPasswordHere",
"serverStatsUrl": "http://1.1.1.1:8080/feed/dedicated-server-stats.xml", "serverStatsUrl": "http://127.0.0.1:8080/feed/dedicated-server-stats.xml",
"serverMapUrl": "http://1.1.1.1:8080/feed/dedicated-server-stats-map.jpg", "serverMapUrl": "http://127.0.0.1:8080/feed/dedicated-server-stats-map.jpg",
"updateIntervalSeconds": 30 "updateIntervalSeconds": 30
}, },
"discord": { "discord": {

View file

@ -1,5 +1,3 @@
version: "2"
services: services:
ls25bot: ls25bot:
build: build:
@ -24,4 +22,14 @@ services:
sleep 7200 # Each 2 hours sleep 7200 # Each 2 hours
echo "Restarting ls25bot container at $(date)" echo "Restarting ls25bot container at $(date)"
docker restart ls25bot docker restart ls25bot
done 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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View file

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View file

@ -1,6 +1,6 @@
{ {
"name": "ls25-discord-bot", "name": "ls25-discord-bot",
"version": "0.1.5", "version": "0.1.8",
"description": "A simple discord bot for farming simulator 25", "description": "A simple discord bot for farming simulator 25",
"main": "source/Main.ts", "main": "source/Main.ts",
"scripts": { "scripts": {

View file

@ -2,6 +2,7 @@ import {Client, IntentsBitField} from 'discord.js';
import Configuration from "./Services/Configuration"; import Configuration from "./Services/Configuration";
import Logging from "./Services/Logging"; import Logging from "./Services/Logging";
import DiscordService from "./Services/DiscordEmbed"; import DiscordService from "./Services/DiscordEmbed";
import VersionChecker from './Services/VersionChecker';
// Create a new logger instance and configuration instance // Create a new logger instance and configuration instance
const appLogger = Logging.getLogger(); const appLogger = Logging.getLogger();
@ -20,6 +21,24 @@ if(!appConfig.isConfigurationValid()) {
process.exit(1); 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 * Create a new discord client instance
*/ */

View file

@ -92,6 +92,16 @@ export default class DiscordEmbed {
return true; 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 * Send server stats embed in a channel
* @param serverStats * @param serverStats
@ -100,8 +110,6 @@ export default class DiscordEmbed {
let embed = new EmbedBuilder(); let embed = new EmbedBuilder();
let config = this.appConfiguration; let config = this.appConfiguration;
serverStats.getServerMonth();
embed.setTitle(config.translation.discordEmbed.title); embed.setTitle(config.translation.discordEmbed.title);
if (!serverStats.isOnline()) { if (!serverStats.isOnline()) {
embed.setColor(0xCA0000); embed.setColor(0xCA0000);
@ -115,6 +123,8 @@ export default class DiscordEmbed {
embed.setThumbnail(config.application.serverMapUrl); embed.setThumbnail(config.application.serverMapUrl);
let playerListString: string; let playerListString: string;
let playerListTitleString = `${config.translation.discordEmbed.titlePlayerCount} (${serverStats.getPlayerCount()??0}/${serverStats.getMaxPlayerCount()??0}):`;
if(serverStats.getPlayerList().length === 0) { if(serverStats.getPlayerList().length === 0) {
playerListString = config.translation.discordEmbed.noPlayersOnline; playerListString = config.translation.discordEmbed.noPlayersOnline;
} else { } else {
@ -129,7 +139,7 @@ export default class DiscordEmbed {
let serverMods = serverStats.getServerMods(); let serverMods = serverStats.getServerMods();
let serverModsText = "-/-"; let serverModsText = "-/-";
if(serverMods.length > 0) { if(serverMods.length > 0) {
serverModsText = serverMods.map(mod => `${mod.name}`).join(', '); serverModsText = await this.truncateText(serverMods.map(mod => `${mod.name}`).join(', '));
} }
// @ts-ignore // @ts-ignore
@ -140,12 +150,11 @@ export default class DiscordEmbed {
{name: config.translation.discordEmbed.titleServerMap, value: serverStats.getServerMap()}, {name: config.translation.discordEmbed.titleServerMap, value: serverStats.getServerMap()},
{name: config.translation.discordEmbed.titleServerMods, value: serverModsText}, {name: config.translation.discordEmbed.titleServerMods, value: serverModsText},
{ {
name: `${config.translation.discordEmbed.titlePlayerCount} (${serverStats.getPlayerCount()}/${serverStats.getMaxPlayerCount()}):`, name: playerListTitleString,
value: playerListString value: playerListString
}, },
); );
} }
this.appLogger.debug(embed);
return embed; return embed;
} }
} }

View file

@ -137,37 +137,6 @@ 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 the server time in the format HH:MM
* @returns {string} The server time in the format HH:MM * @returns {string} The server time in the format HH:MM
@ -187,23 +156,23 @@ export default class ServerStatusFeed {
if(minutesString.length === 1) { if(minutesString.length === 1) {
minutesString = `0${minutesString}`; minutesString = `0${minutesString}`;
} }
return `${hoursString}:${minutesString} (${this.getServerMonth()})`; return `${hoursString}:${minutesString}`;
} }
/** /**
* Returns the server player count * Returns the server player count
* @returns {number} The server player count * @returns {number | null | undefined} The server player count
*/ */
public getPlayerCount(): number { public getPlayerCount(): number | null | undefined {
return <number>this.getServerStats()?.Server.Slots.numUsed; return <number>this.getServerStats()?.Server?.Slots?.numUsed;
} }
/** /**
* Returns the server player count * Returns the server player count
* @returns {number} The server player count * @returns {number | null | undefined} The server player count
*/ */
public getMaxPlayerCount(): number { public getMaxPlayerCount(): number | null | undefined {
return <number>this.getServerStats()?.Server.Slots.capacity; return <number>this.getServerStats()?.Server?.Slots?.capacity;
} }
/** /**

View file

@ -0,0 +1,41 @@
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;
}
}