refactor: enhance Discord service update logic and improve configuration handling

This commit is contained in:
Shrev Dev
2025-10-21 12:18:16 -05:00
parent 35d86f8ec3
commit bc0fcadda3
3 changed files with 90 additions and 12 deletions
+17 -1
View File
@@ -74,6 +74,11 @@ export class ConfigStorage {
}
private save(): void {
if (process.env.JEST_WORKER_ID) {
this.forceSave();
return;
}
// Debounce saves to prevent excessive writes
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
@@ -85,6 +90,11 @@ export class ConfigStorage {
}
private forceSave(): void {
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
this.saveTimeout = null;
}
try {
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), 'utf-8');
this.logger.info('Saved configuration to storage');
@@ -218,6 +228,9 @@ export class ConfigStorage {
public setChannelId(guildId: string, channelId: string): void {
const config = this.getGuildConfig(guildId);
if (config.channelId === channelId) {
return;
}
config.channelId = channelId;
this.save();
}
@@ -240,7 +253,10 @@ export class ConfigStorage {
public setMessageIds(guildId: string, ids: string[]): void {
const config = this.getGuildConfig(guildId);
config.messageIds = ids;
if (config.messageIds.length === ids.length && config.messageIds.every((id, index) => id === ids[index])) {
return;
}
config.messageIds = [...ids];
this.save();
}
+3 -3
View File
@@ -3,6 +3,7 @@ import { configStorage } from './config/storage';
import { UptimeKumaService } from './services/uptime-kuma.service';
import { DiscordService } from './services/discord.service';
import { Logger } from './utils/logger';
import { MonitorStats } from './types/uptime-kuma';
import * as http from 'http';
class UptimeKumaDiscordBot {
@@ -68,7 +69,7 @@ class UptimeKumaDiscordBot {
}
private setupEventListeners(): void {
this.uptimeKuma.on('monitorsUpdated', (monitors: any) => {
this.uptimeKuma.on('monitorsUpdated', (monitors: MonitorStats[]) => {
this.discord.updateMonitorStatus(monitors).catch((error: Error) => {
this.logger.error(`Failed to update Discord status: ${error.message}`);
});
@@ -120,8 +121,7 @@ class UptimeKumaDiscordBot {
});
};
// Use a default interval; guilds can have different intervals but we'll use a common update cycle
const defaultInterval = parseInt(process.env.UPDATE_INTERVAL || '60', 10) * 1000;
const defaultInterval = config.bot.updateInterval;
this.updateInterval = setInterval(updateFn, defaultInterval);
this.logger.info(`Update interval set to ${defaultInterval / 1000} seconds`);
+70 -8
View File
@@ -13,6 +13,12 @@ export class DiscordService {
private maxMonitorsPerEmbed = 20;
private commandsService: CommandsService;
private uptimeKumaService: UptimeKumaService | null = null;
private latestMonitors: MonitorStats[] = [];
private isUpdateInProgress = false;
private pendingUpdate = false;
private forceNextUpdate = false;
private lastUpdateTimes: Map<string, number> = new Map();
private hasLoggedNoGuilds = false;
constructor() {
this.client = new Client({
@@ -167,22 +173,67 @@ export class DiscordService {
this.channels.set(guildId, textChannel);
configStorage.setMessageIds(guildId, []);
configStorage.setChannelId(guildId, channelId);
this.lastUpdateTimes.delete(guildId);
this.logger.info(`Changed status channel for guild ${guildId} to: ${textChannel.name}`);
if (this.uptimeKumaService) {
const monitors = this.uptimeKumaService.getMonitorStats();
await this.updateMonitorStatus(monitors, { force: true }).catch((error: any) => {
this.logger.error(`Failed to run immediate update for guild ${guildId}: ${error.message}`);
});
}
} catch (error: any) {
throw new Error(`Failed to set channel: ${error.message}`);
}
}
public async updateMonitorStatus(monitors: MonitorStats[]): Promise<void> {
const guildIds = configStorage.getAllGuildIds();
if (guildIds.length === 0) {
this.logger.warn('No guilds configured, skipping update');
public async updateMonitorStatus(monitors: MonitorStats[], options: { force?: boolean } = {}): Promise<void> {
this.latestMonitors = monitors;
if (options.force) {
this.forceNextUpdate = true;
}
if (this.isUpdateInProgress) {
this.pendingUpdate = true;
return;
}
await this.processPendingUpdates();
}
private async processPendingUpdates(): Promise<void> {
this.isUpdateInProgress = true;
try {
do {
this.pendingUpdate = false;
const monitorsSnapshot = [...this.latestMonitors];
const force = this.forceNextUpdate;
this.forceNextUpdate = false;
await this.performUpdate(monitorsSnapshot, force);
} while (this.pendingUpdate);
} finally {
this.isUpdateInProgress = false;
}
}
private async performUpdate(monitors: MonitorStats[], force: boolean): Promise<void> {
const guildIds = configStorage.getAllGuildIds();
if (guildIds.length === 0) {
if (!this.hasLoggedNoGuilds) {
this.logger.warn('No guilds configured, skipping update');
this.hasLoggedNoGuilds = true;
}
return;
}
this.hasLoggedNoGuilds = false;
const totalMonitors = monitors.length;
const now = Date.now();
for (const guildId of guildIds) {
// Skip if guild doesn't actually exist in config (shouldn't happen but safety check)
if (!configStorage.guildExists(guildId)) {
continue;
}
@@ -192,6 +243,14 @@ export class DiscordService {
continue;
}
const interval = configStorage.getUpdateInterval(guildId);
const lastUpdate = this.lastUpdateTimes.get(guildId) ?? 0;
const dueForUpdate = force || now - lastUpdate >= interval;
if (!dueForUpdate) {
continue;
}
try {
const trackedIds = configStorage.getMonitorIds(guildId);
const filteredMonitors = trackedIds.length === 0
@@ -202,14 +261,16 @@ export class DiscordService {
continue;
}
const embeds = this.createEmbeds(guildId, filteredMonitors, monitors.length);
const embeds = this.createEmbeds(guildId, filteredMonitors, totalMonitors);
const messageIds = configStorage.getMessageIds(guildId);
if (messageIds.length === 0) {
await this.createNewMessages(guildId, channel, embeds);
} else {
await this.updateExistingMessages(guildId, channel, embeds);
}
this.lastUpdateTimes.set(guildId, Date.now());
} catch (error: any) {
this.logger.error(`Failed to update monitor status for guild ${guildId}: ${error.message}`);
}
@@ -365,6 +426,7 @@ export class DiscordService {
// Don't create new messages here - let the main flow handle it
// Just clear the message IDs so next update will create new ones
configStorage.setMessageIds(guildId, []);
this.lastUpdateTimes.delete(guildId);
return;
}
}