Compare commits
12 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed492e9045 | |||
| 24f65956ab | |||
| 4e6668c2e5 | |||
| 696e8f1749 | |||
| a28796751a | |||
| 58451f6fd0 | |||
| 87a3cb158c | |||
| 9337105ca7 | |||
| 1e35792419 | |||
| bcc98514e6 | |||
| 885461f316 | |||
| f4a872546a |
25
README.md
|
|
@ -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>
|
||||||
|
|
||||||

|

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

|

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

|

|
||||||
|
|
||||||
</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.
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
BIN
misc/files/feed/dedicated-server-stats-map.jpg
Normal file
|
After 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 |
BIN
misc/images/socials/GitHub Repository Header.afphoto
Normal file
BIN
misc/images/socials/GitHub Repository Header.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "ls25-discord-bot",
|
"name": "ls25-discord-bot",
|
||||||
"version": "0.1.6",
|
"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": {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||