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 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<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);
})();
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);
});

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 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<ServerStats | null>} The server stats object or null if the fetch failed
*/
public async updateServerFeed(): Promise<ServerStats|null> {
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;
}