Merge pull request #18 from GreenLeaf-Gaming/bot_presence

Add bot playing status with online player count
This commit is contained in:
Dennis Heinrich 2025-07-10 17:04:51 +02:00 committed by GitHub
commit 4a18bd53e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 151 additions and 139 deletions

3
.gitignore vendored
View file

@ -5,4 +5,5 @@
config.json config.json
config.prod.json config.prod.json
config.test.json config.test.json
config.dev.json config.dev.json
.DS_Store

View file

@ -1,160 +1,171 @@
import {Client, EmbedBuilder, Snowflake, TextChannel} from "discord.js"; import { ActivityType, Client, EmbedBuilder, PresenceUpdateStatus, Snowflake, TextChannel } from "discord.js";
import Configuration from "./Configuration"; import Configuration from "./Configuration";
import ServerStatusFeed from "./ServerStatusFeed"; import ServerStatusFeed from "./ServerStatusFeed";
import {Logger} from "winston"; import { Logger } from "winston";
import Logging from "./Logging"; import Logging from "./Logging";
export default class DiscordEmbed { export default class DiscordEmbed {
private appLogger: Logger; private appLogger: Logger;
private discordAppClient: Client; private discordAppClient: Client;
private appConfiguration: Configuration; private appConfiguration: Configuration;
private serverStatsFeed: ServerStatusFeed; private serverStatsFeed: ServerStatusFeed;
private firstMessageId: Snowflake | null = null; private firstMessageId: Snowflake | null = null;
public constructor(discordAppClient: Client) { public constructor(discordAppClient: Client) {
this.appLogger = Logging.getLogger(); this.appLogger = Logging.getLogger();
this.discordAppClient = discordAppClient; this.discordAppClient = discordAppClient;
this.appConfiguration = new Configuration(); this.appConfiguration = new Configuration();
this.serverStatsFeed = new ServerStatusFeed(); this.serverStatsFeed = new ServerStatusFeed();
(async () => { (async () => {
// Delete all messages in the channel // Delete all messages in the channel
await this.deleteAllMessages(); await this.deleteAllMessages();
// Start the update loop, which updates the discord embed every x seconds itself // Start the update loop, which updates the discord embed every x seconds itself
await this.updateDiscordEmbed(); 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 => {
/**
* Send the initial message to the channel (if the first message id is not set) or
* the message is meanwhile deleted
* @param embedMessage
*/
let sendInitialMessage = (embedMessage: EmbedBuilder) => {
// noinspection JSAnnotator
(channel as TextChannel).send({embeds: [embedMessage]}).then(message => {
this.firstMessageId = message.id;
});
};
this.generateEmbedFromStatusFeed(this.serverStatsFeed).then(embedMessage => {
if (this.firstMessageId !== null) {
(channel as TextChannel).messages.fetch(this.firstMessageId).then(message => {
this.appLogger.info(`Message found, editing message with new embed`);
message.edit({embeds: [embedMessage]});
}).catch(() => {
this.appLogger.warn('Message not found, sending new message');
sendInitialMessage(embedMessage);
});
} else {
this.appLogger.info(`No message found, sending new message`);
sendInitialMessage(embedMessage);
}
});
});
} catch (exception) {
this.appLogger.error(exception);
}
/**
* 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(() => { setTimeout(() => {
this.updateDiscordEmbed(); this.updateDiscordEmbed();
}, this.appConfiguration.application.updateIntervalSeconds * 1000); }, 1000);
} return;
}
this.discordAppClient.user?.setActivity({
name: `with ${this.serverStatsFeed.getPlayerCount() ?? 0} players`,
type: ActivityType.Playing,
});
/** this.discordAppClient.channels.fetch(this.appConfiguration.discord.channelId as Snowflake).then(async (channel) => {
* Delete all messages in a text channel to clear the channel /**
* @private * Send the initial message to the channel (if the first message id is not set) or
*/ * the message is meanwhile deleted
private async deleteAllMessages(): Promise<boolean> { * @param embedMessage
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}`); let sendInitialMessage = (embedMessage: EmbedBuilder) => {
textChannel.messages.fetch().then(messages => { // noinspection JSAnnotator
messages.forEach(message => { (channel as TextChannel).send({ embeds: [embedMessage] }).then((message) => {
message.delete(); this.firstMessageId = message.id;
}); });
};
this.generateEmbedFromStatusFeed(this.serverStatsFeed).then((embedMessage) => {
if (this.firstMessageId !== null) {
(channel as TextChannel).messages
.fetch(this.firstMessageId)
.then((message) => {
this.appLogger.info(`Message found, editing message with new embed`);
message.edit({ embeds: [embedMessage] });
})
.catch(() => {
this.appLogger.warn("Message not found, sending new message");
sendInitialMessage(embedMessage);
});
} else {
this.appLogger.info(`No message found, sending new message`);
sendInitialMessage(embedMessage);
}
}); });
return true; });
} catch (exception) {
this.appLogger.error(exception);
} }
/** setTimeout(() => {
* Truncates a string at a given length this.updateDiscordEmbed();
* @param text The input text to truncate }, this.appConfiguration.application.updateIntervalSeconds * 1000);
* @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 * Delete all messages in a text channel to clear the channel
* @param serverStats * @private
*/ */
private async generateEmbedFromStatusFeed(serverStats: ServerStatusFeed): Promise<EmbedBuilder> { private async deleteAllMessages(): Promise<boolean> {
let embed = new EmbedBuilder(); let textChannel = this.discordAppClient.channels.cache.get(this.appConfiguration.discord.channelId as Snowflake) as TextChannel;
let config = this.appConfiguration; this.appLogger.info(`Deleting all messages in discord text channel ${textChannel.id}`);
textChannel.messages.fetch().then((messages) => {
messages.forEach((message) => {
message.delete();
});
});
return true;
}
embed.setTitle(config.translation.discordEmbed.title); /**
if (!serverStats.isOnline()) { * Truncates a string at a given length
embed.setColor(0xCA0000); * @param text The input text to truncate
embed.setDescription(config.translation.discordEmbed.descriptionOffline); * @param maxLength The allowed characters until truncation
} else if (serverStats.isFetching()) { * @returns The truncated string
embed.setDescription(config.translation.discordEmbed.descriptionUnknown); */
} else { private async truncateText(text: string, maxLength = 1024): Promise<string> {
embed.setColor(0x00CA00); return text.length > maxLength ? text.slice(0, maxLength - 3) + "..." : text;
embed.setDescription(config.translation.discordEmbed.descriptionOnline); }
embed.setTimestamp(new Date());
embed.setThumbnail(config.application.serverMapUrl);
let playerListString: string; /**
let playerListTitleString = `${config.translation.discordEmbed.titlePlayerCount} (${serverStats.getPlayerCount()??0}/${serverStats.getMaxPlayerCount()??0}):`; * Send server stats embed in a channel
* @param serverStats
*/
private async generateEmbedFromStatusFeed(serverStats: ServerStatusFeed): Promise<EmbedBuilder> {
let embed = new EmbedBuilder();
let config = this.appConfiguration;
if(serverStats.getPlayerList().length === 0) { embed.setTitle(config.translation.discordEmbed.title);
playerListString = config.translation.discordEmbed.noPlayersOnline; if (!serverStats.isOnline()) {
} else { embed.setColor(0xca0000);
playerListString = serverStats.getPlayerList().map(p => p.username).join(', '); embed.setDescription(config.translation.discordEmbed.descriptionOffline);
} } else if (serverStats.isFetching()) {
embed.setDescription(config.translation.discordEmbed.descriptionUnknown);
} else {
embed.setColor(0x00ca00);
embed.setDescription(config.translation.discordEmbed.descriptionOnline);
embed.setTimestamp(new Date());
embed.setThumbnail(config.application.serverMapUrl);
let serverPassword = config.application.serverPassword; let playerListString: string;
if(config.application.serverPassword == "") { let playerListTitleString = `${config.translation.discordEmbed.titlePlayerCount} (${serverStats.getPlayerCount() ?? 0}/${serverStats.getMaxPlayerCount() ?? 0}):`;
serverPassword = "-/-";
}
let serverMods = serverStats.getServerMods(); if (serverStats.getPlayerList().length === 0) {
let serverModsText = "-/-"; playerListString = config.translation.discordEmbed.noPlayersOnline;
if(serverMods.length > 0) { } else {
serverModsText = await this.truncateText(serverMods.map(mod => `${mod.name}`).join(', ')); playerListString = serverStats
} .getPlayerList()
.map((p) => p.username)
.join(", ");
}
// @ts-ignore let serverPassword = config.application.serverPassword;
embed.addFields( if (config.application.serverPassword == "") {
{name: config.translation.discordEmbed.titleServerName, value: serverStats.getServerName()}, serverPassword = "-/-";
{name: config.translation.discordEmbed.titleServerPassword, value: serverPassword}, }
{name: config.translation.discordEmbed.titleServerTime, value: serverStats.getServerTime()},
{name: config.translation.discordEmbed.titleServerMap, value: serverStats.getServerMap()}, let serverMods = serverStats.getServerMods();
{name: config.translation.discordEmbed.titleServerMods, value: serverModsText}, let serverModsText = "-/-";
{ if (serverMods.length > 0) {
name: playerListTitleString, serverModsText = await this.truncateText(serverMods.map((mod) => `${mod.name}`).join(", "));
value: playerListString }
},
); // @ts-ignore
embed.addFields(
{ name: config.translation.discordEmbed.titleServerName, value: serverStats.getServerName() },
{ name: config.translation.discordEmbed.titleServerPassword, value: serverPassword },
{ name: config.translation.discordEmbed.titleServerTime, value: serverStats.getServerTime() },
{ name: config.translation.discordEmbed.titleServerMap, value: serverStats.getServerMap() },
{ name: config.translation.discordEmbed.titleServerMods, value: serverModsText },
{
name: playerListTitleString,
value: playerListString,
} }
return embed; );
} }
} return embed;
}
}