Extracted the logic from the main file into a separate service

This commit is contained in:
Dennis Heinrich 2024-11-16 16:48:50 +01:00
parent 5d2f9cfb88
commit 35c887383a
3 changed files with 152 additions and 90 deletions

View file

@ -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 Configuration from "./Services/Configuration";
import Logging from "./Services/Logging"; import Logging from "./Services/Logging";
import ServerStatsFeed from "./Services/ServerStatsFeed"; import DiscordService from "./Services/DiscordEmbed";
const appLogger = Logging.getLogger(); const appLogger = Logging.getLogger();
const appConfig: Configuration = new Configuration(); const appConfig: Configuration = new Configuration();
const appClient = new Client({ const discordClient = new Client({
intents: [IntentsBitField.Flags.Guilds, IntentsBitField.Flags.GuildMessages] intents: [IntentsBitField.Flags.Guilds, IntentsBitField.Flags.GuildMessages]
}); });
appClient.login(appConfig.discord.botToken);
/** /**
* Delete all messages in a text channel * Start the discord client and log in
* @param textChannel * After that create a new DiscordService instance to start the server stats feed
*/ */
function deleteAllMessages(textChannel: TextChannel) { discordClient.login(appConfig.discord.botToken).then(() => {
appLogger.info(`Deleting all messages in channel ${textChannel.id}`); appLogger.info(`Login successful to discord with token`);
textChannel.messages.fetch().then(messages => { });
messages.forEach(message => {
message.delete(); discordClient.on('ready', () => {
}); appLogger.info(`Discord client ready. Logged in as ${discordClient.user?.username}!`);
}); new DiscordService(discordClient);
}
/**
* Send server stats embed in a channel
* @param serverStats
*/
async function generateEmbedFromStatusFeed(serverStats: ServerStatsFeed): Promise<EmbedBuilder> {
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);
})();
}); });

View file

@ -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<void> {
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<boolean> {
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<EmbedBuilder> {
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;
}
}

View file

@ -7,7 +7,7 @@ import IPlayer from "../Interfaces/Feed/IPlayer";
export const CONNECTION_REFUSED = 'ECONNREFUSED'; export const CONNECTION_REFUSED = 'ECONNREFUSED';
export const NOT_FOUND = 'ENOTFOUND'; export const NOT_FOUND = 'ENOTFOUND';
export default class ServerStatsFeed { export default class ServerStatusFeed {
private _serverStats: ServerStats | null = null; private _serverStats: ServerStats | null = null;
private _isOnline: boolean = false; private _isOnline: boolean = false;
private _isFetching: boolean = false; private _isFetching: boolean = false;
@ -15,10 +15,19 @@ export default class ServerStatsFeed {
constructor() { constructor() {
} }
/**
* Returns the fetching status of the server stats feed
* @returns {boolean} The fetching status of the server stats feed
*/
public isFetching(): boolean { public isFetching(): boolean {
return this._isFetching; 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 { private getServerStats(): ServerStats | null {
if(this._isOnline && !this._isFetching && this._serverStats) { if(this._isOnline && !this._isFetching && this._serverStats) {
return this._serverStats; return this._serverStats;
@ -26,9 +35,13 @@ export default class ServerStatsFeed {
return null; return null;
} }
/**
* Update the server feed from the server status feed url
* @returns {Promise<ServerStats | null>} The server stats object or null if the fetch failed
*/
public async updateServerFeed(): Promise<ServerStats|null> { public async updateServerFeed(): Promise<ServerStats|null> {
this._isFetching = true; 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) await fetch(Configuration.getConfiguration().application.serverStatsUrl)
.then( .then(
r => r.text() r => r.text()
@ -39,7 +52,7 @@ export default class ServerStatsFeed {
// Parse the XML response // Parse the XML response
const parsedFeed = new XMLParser({ignoreAttributes: false, attributeNamePrefix: ''}).parse(response) as ServerStats; 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; this._serverStats = parsedFeed;
} }
).catch( ).catch(
@ -50,13 +63,13 @@ export default class ServerStatsFeed {
// Handle different error codes // Handle different error codes
switch (reason.cause.code) { switch (reason.cause.code) {
case CONNECTION_REFUSED: case CONNECTION_REFUSED:
Logging.getLogger().error(`Connection refused to server stats feed`); Logging.getLogger().error(`Connection refused to server status feed`);
break; break;
case NOT_FOUND: case NOT_FOUND:
Logging.getLogger().error(`Server stats feed not found`); Logging.getLogger().error(`Server status feed not found`);
break; break;
default: default:
Logging.getLogger().error(`Error fetching server stats`); Logging.getLogger().error(`Error fetching server status feed`);
break; break;
} }
return null; return null;
@ -68,6 +81,10 @@ export default class ServerStatsFeed {
return this._serverStats; return this._serverStats;
} }
/**
* Returns the online status of the server
* @returns {boolean} The online status of the server
*/
public isOnline(): boolean { public isOnline(): boolean {
return this._isOnline; return this._isOnline;
} }