From bc0fcadda3da463e9a104b82f400c87dfa1b768b Mon Sep 17 00:00:00 2001 From: Shrev Dev Date: Tue, 21 Oct 2025 12:18:16 -0500 Subject: [PATCH] refactor: enhance Discord service update logic and improve configuration handling --- src/config/storage.ts | 18 +++++++- src/index.ts | 6 +-- src/services/discord.service.ts | 78 +++++++++++++++++++++++++++++---- 3 files changed, 90 insertions(+), 12 deletions(-) diff --git a/src/config/storage.ts b/src/config/storage.ts index 778dc8f..8961e1d 100644 --- a/src/config/storage.ts +++ b/src/config/storage.ts @@ -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(); } diff --git a/src/index.ts b/src/index.ts index a01ae70..9962420 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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`); diff --git a/src/services/discord.service.ts b/src/services/discord.service.ts index 0d482f0..e13b5c6 100644 --- a/src/services/discord.service.ts +++ b/src/services/discord.service.ts @@ -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 = 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 { - 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 { + this.latestMonitors = monitors; + if (options.force) { + this.forceNextUpdate = true; + } + + if (this.isUpdateInProgress) { + this.pendingUpdate = true; return; } + await this.processPendingUpdates(); + } + + private async processPendingUpdates(): Promise { + 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 { + 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; } }