From 35c887383ad1f9e6ed1238632288d84e38c7fd97 Mon Sep 17 00:00:00 2001 From: Dennis Heinrich Date: Sat, 16 Nov 2024 16:48:50 +0100 Subject: [PATCH] Extracted the logic from the main file into a separate service --- source/Main.ts | 96 ++------------ source/Services/DiscordEmbed.ts | 117 ++++++++++++++++++ ...ServerStatsFeed.ts => ServerStatusFeed.ts} | 29 ++++- 3 files changed, 152 insertions(+), 90 deletions(-) create mode 100644 source/Services/DiscordEmbed.ts rename source/Services/{ServerStatsFeed.ts => ServerStatusFeed.ts} (85%) diff --git a/source/Main.ts b/source/Main.ts index d7e89b6..220fa0d 100644 --- a/source/Main.ts +++ b/source/Main.ts @@ -1,95 +1,23 @@ -import {Client, EmbedBuilder, IntentsBitField, Snowflake, TextChannel} from 'discord.js'; +import {Client, IntentsBitField} from 'discord.js'; import Configuration from "./Services/Configuration"; import Logging from "./Services/Logging"; -import ServerStatsFeed from "./Services/ServerStatsFeed"; +import DiscordService from "./Services/DiscordEmbed"; const appLogger = Logging.getLogger(); const appConfig: Configuration = new Configuration(); -const appClient = new Client({ +const discordClient = new Client({ intents: [IntentsBitField.Flags.Guilds, IntentsBitField.Flags.GuildMessages] }); -appClient.login(appConfig.discord.botToken); /** - * Delete all messages in a text channel - * @param textChannel + * Start the discord client and log in + * After that create a new DiscordService instance to start the server stats feed */ -function deleteAllMessages(textChannel: TextChannel) { - appLogger.info(`Deleting all messages in channel ${textChannel.id}`); - textChannel.messages.fetch().then(messages => { - messages.forEach(message => { - message.delete(); - }); - }); -} - -/** - * Send server stats embed in a channel - * @param serverStats - */ -async function generateEmbedFromStatusFeed(serverStats: ServerStatsFeed): Promise { - let embed = new EmbedBuilder(); - embed.setTitle('Server Status'); - if (!serverStats.isOnline()) { - embed.setDescription('Der Server ist aktuell offline.'); - } else if (serverStats.isFetching()) { - embed.setDescription('Der Serverstatus wird aktuell abgefragt...'); - } else { - embed.setDescription(`Der Server ist aktuell ${serverStats.isOnline() ? 'online' : 'offline'}`); - embed.setTimestamp(); - embed.setThumbnail(Configuration.getConfiguration().application.serverMapUrl); - - let playerListString: string = ''; - if(serverStats.getPlayerList().length === 0) { - playerListString = 'Keine Spieler online'; - } else { - playerListString = serverStats.getPlayerList().map(p => p.username).join(', '); - } - - // @ts-ignore - embed.addFields( - {name: 'Name:', value: serverStats.getServerName()}, - {name: 'Passwort:', value: appConfig.application.serverPassword}, - {name: 'Uhrzeit im Spiel:', value: serverStats.getServerTime()}, - { - name: `Spieler online (${serverStats.getPlayerCount()}/${serverStats.getMaxPlayerCount()}):`, - value: playerListString - }, - ); - } - return embed; -} - -appClient.on('ready', () => { - appLogger.info(`Discord client ready. Logged in as ${appClient.user?.username}!`); - const serverStats = new ServerStatsFeed(); - - // Fetch channel and delete all messages - appClient.channels.fetch(appConfig.discord.channelId as Snowflake).then(channel => { - let textChannel = channel as TextChannel; - deleteAllMessages(textChannel); - }); - - // Start fetching server stats - (async () => { - await serverStats.updateServerFeed(); - let firstMessageId: any = null; - setInterval(async () => { - await serverStats.updateServerFeed(); - appClient.channels.fetch(appConfig.discord.channelId as Snowflake).then(async channel => { - generateEmbedFromStatusFeed(serverStats).then(embedMessage => { - console.log(embedMessage); - if (firstMessageId !== null) { - (channel as TextChannel).messages.fetch(firstMessageId).then(message => { - message.edit({embeds: [embedMessage]}); - }); - } else { - (channel as TextChannel).send({embeds: [embedMessage]}).then(message => { - firstMessageId = message.id; - }) - } - }); - }) - }, appConfig.application.updateIntervalSeconds * 1000); - })(); +discordClient.login(appConfig.discord.botToken).then(() => { + appLogger.info(`Login successful to discord with token`); }); + +discordClient.on('ready', () => { + appLogger.info(`Discord client ready. Logged in as ${discordClient.user?.username}!`); + new DiscordService(discordClient); +}); \ No newline at end of file diff --git a/source/Services/DiscordEmbed.ts b/source/Services/DiscordEmbed.ts new file mode 100644 index 0000000..b2cf5cd --- /dev/null +++ b/source/Services/DiscordEmbed.ts @@ -0,0 +1,117 @@ +import {Client, EmbedBuilder, Snowflake, TextChannel} from "discord.js"; +import Configuration from "./Configuration"; +import ServerStatusFeed from "./ServerStatusFeed"; +import {Logger} from "winston"; +import Logging from "./Logging"; + +export default class DiscordEmbed { + private appLogger: Logger; + private discordAppClient: Client; + private appConfiguration: Configuration; + private serverStatsFeed: ServerStatusFeed; + private firstMessageId: Snowflake | null = null; + + public constructor(discordAppClient: Client) { + this.appLogger = Logging.getLogger(); + this.discordAppClient = discordAppClient; + this.appConfiguration = new Configuration(); + this.serverStatsFeed = new ServerStatusFeed(); + + (async () => { + // Delete all messages in the channel + await this.deleteAllMessages(); + // Start the update loop, which updates the discord embed every x seconds itself + await this.updateDiscordEmbed(); + })(); + } + + /** + * Update the discord embed with the server status, player list and server time + * This method is called every x seconds to update the discord embed. + * @private + */ + private async updateDiscordEmbed(): Promise { + try { + await this.serverStatsFeed.updateServerFeed(); + if(this.serverStatsFeed.isFetching()) { + this.appLogger.info('Server status feed is still fetching, try again...'); + setTimeout(() => { + this.updateDiscordEmbed(); + }, 1000); + return; + } + this.discordAppClient.channels.fetch(this.appConfiguration.discord.channelId as Snowflake).then(async channel => { + this.generateEmbedFromStatusFeed(this.serverStatsFeed).then(embedMessage => { + if (this.firstMessageId !== null) { + (channel as TextChannel).messages.fetch(this.firstMessageId).then(message => { + message.edit({embeds: [embedMessage]}); + }); + } else { + (channel as TextChannel).send({embeds: [embedMessage]}).then(message => { + this.firstMessageId = message.id; + }) + } + }); + }); + } catch (exception) { + this.appLogger.error(exception); + } + + setTimeout(() => { + this.updateDiscordEmbed(); + }, this.appConfiguration.application.updateIntervalSeconds * 1000); + } + + /** + * Delete all messages in a text channel to clear the channel + * @private + */ + private async deleteAllMessages(): Promise { + let textChannel = this.discordAppClient.channels.cache.get(this.appConfiguration.discord.channelId as Snowflake) as TextChannel; + this.appLogger.info(`Deleting all messages in discord text channel ${textChannel.id}`); + textChannel.messages.fetch().then(messages => { + messages.forEach(message => { + message.delete(); + }); + }); + return true; + } + + /** + * Send server stats embed in a channel + * @param serverStats + */ + private async generateEmbedFromStatusFeed(serverStats: ServerStatusFeed): Promise { + let embed = new EmbedBuilder(); + embed.setTitle('Server Status'); + if (!serverStats.isOnline()) { + embed.setDescription('Der Server ist aktuell offline.'); + } else if (serverStats.isFetching()) { + embed.setDescription('Der Serverstatus wird aktuell abgefragt...'); + } else { + embed.setDescription(`Der Server ist aktuell ${serverStats.isOnline() ? 'online' : 'offline'}`); + embed.setTimestamp(); + embed.setThumbnail(this.appConfiguration.application.serverMapUrl); + + let playerListString: string = ''; + if(serverStats.getPlayerList().length === 0) { + playerListString = 'Keine Spieler online'; + } else { + playerListString = serverStats.getPlayerList().map(p => p.username).join(', '); + } + + // @ts-ignore + embed.addFields( + {name: 'Name:', value: serverStats.getServerName()}, + {name: 'Passwort:', value: this.appConfiguration.application.serverPassword}, + {name: 'Uhrzeit im Spiel:', value: serverStats.getServerTime()}, + { + name: `Spieler online (${serverStats.getPlayerCount()}/${serverStats.getMaxPlayerCount()}):`, + value: playerListString + }, + ); + } + this.appLogger.debug(embed); + return embed; + } +} \ No newline at end of file diff --git a/source/Services/ServerStatsFeed.ts b/source/Services/ServerStatusFeed.ts similarity index 85% rename from source/Services/ServerStatsFeed.ts rename to source/Services/ServerStatusFeed.ts index 976bbd7..e5f7942 100644 --- a/source/Services/ServerStatsFeed.ts +++ b/source/Services/ServerStatusFeed.ts @@ -7,7 +7,7 @@ import IPlayer from "../Interfaces/Feed/IPlayer"; export const CONNECTION_REFUSED = 'ECONNREFUSED'; export const NOT_FOUND = 'ENOTFOUND'; -export default class ServerStatsFeed { +export default class ServerStatusFeed { private _serverStats: ServerStats | null = null; private _isOnline: boolean = false; private _isFetching: boolean = false; @@ -15,10 +15,19 @@ export default class ServerStatsFeed { constructor() { } + /** + * Returns the fetching status of the server stats feed + * @returns {boolean} The fetching status of the server stats feed + */ public isFetching(): boolean { return this._isFetching; } + /** + * Get the server stats object + * @returns {ServerStats | null} The server stats object or null if the server is offline or fetching + * @private + */ private getServerStats(): ServerStats | null { if(this._isOnline && !this._isFetching && this._serverStats) { return this._serverStats; @@ -26,9 +35,13 @@ export default class ServerStatsFeed { return null; } + /** + * Update the server feed from the server status feed url + * @returns {Promise} The server stats object or null if the fetch failed + */ public async updateServerFeed(): Promise { this._isFetching = true; - Logging.getLogger().info(`Fetching server stats from feed url`); + Logging.getLogger().info(`Fetching server status from feed url`); await fetch(Configuration.getConfiguration().application.serverStatsUrl) .then( r => r.text() @@ -39,7 +52,7 @@ export default class ServerStatsFeed { // Parse the XML response const parsedFeed = new XMLParser({ignoreAttributes: false, attributeNamePrefix: ''}).parse(response) as ServerStats; - Logging.getLogger().info(`Server stats received`); + Logging.getLogger().info(`Server status feed successful received`); this._serverStats = parsedFeed; } ).catch( @@ -50,13 +63,13 @@ export default class ServerStatsFeed { // Handle different error codes switch (reason.cause.code) { case CONNECTION_REFUSED: - Logging.getLogger().error(`Connection refused to server stats feed`); + Logging.getLogger().error(`Connection refused to server status feed`); break; case NOT_FOUND: - Logging.getLogger().error(`Server stats feed not found`); + Logging.getLogger().error(`Server status feed not found`); break; default: - Logging.getLogger().error(`Error fetching server stats`); + Logging.getLogger().error(`Error fetching server status feed`); break; } return null; @@ -68,6 +81,10 @@ export default class ServerStatsFeed { return this._serverStats; } + /** + * Returns the online status of the server + * @returns {boolean} The online status of the server + */ public isOnline(): boolean { return this._isOnline; }