feat: purge status channel on startup for clean dashboard

- Add purgeChannel() to Channel interface and DiscordChannel
- Uses bulkDelete for recent messages, individual delete for old ones
- Called once at startup before creating fresh dashboard messages
This commit is contained in:
Eyejoker
2026-03-14 00:52:41 +09:00
parent d06a835163
commit 7a726c335d
3 changed files with 63 additions and 1 deletions

View File

@@ -494,6 +494,53 @@ export class DiscordChannel implements Channel {
return result;
}
async purgeChannel(jid: string): Promise<number> {
if (!this.client) return 0;
let deleted = 0;
try {
const channelId = jid.replace(/^dc:/, '');
const channel = await this.client.channels.fetch(channelId);
if (!channel || !('bulkDelete' in channel)) return 0;
const tc = channel as TextChannel;
// Fetch and delete in batches (bulkDelete handles up to 100, only < 14 days old)
let hasMore = true;
while (hasMore) {
const messages = await tc.messages.fetch({ limit: 100 });
if (messages.size === 0) break;
// Separate into bulk-deletable (< 14 days) and old messages
const twoWeeksAgo = Date.now() - 14 * 24 * 60 * 60 * 1000;
const recent = messages.filter(
(m) => m.createdTimestamp > twoWeeksAgo,
);
const old = messages.filter(
(m) => m.createdTimestamp <= twoWeeksAgo,
);
if (recent.size >= 2) {
await tc.bulkDelete(recent);
deleted += recent.size;
} else if (recent.size === 1) {
await recent.first()!.delete();
deleted += 1;
}
for (const [, msg] of old) {
await msg.delete();
deleted += 1;
}
hasMore = messages.size === 100;
}
logger.info({ jid, deleted }, 'Purged channel messages');
} catch (err) {
logger.error({ jid, err, deleted }, 'Failed to purge channel messages');
}
return deleted;
}
async editMessage(
jid: string,
messageId: string,

View File

@@ -756,7 +756,10 @@ async function buildUsageContent(): Promise<string> {
lines.push(`• 업타임: ${formatElapsed(os.uptime() * 1000)}`);
return lines.join('\n') + `\n\n_${new Date().toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' })}_`;
return (
lines.join('\n') +
`\n\n_${new Date().toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' })}_`
);
}
// ── Dashboard Lifecycle ─────────────────────────────────────────
@@ -1115,6 +1118,16 @@ async function main(): Promise<void> {
});
queue.setProcessMessagesFn(processGroupMessages);
recoverPendingMessages();
// Purge old messages in status channel before creating fresh dashboards
if (STATUS_CHANNEL_ID) {
const statusJid = `dc:${STATUS_CHANNEL_ID}`;
const ch = channels.find(
(c) => c.name.startsWith('discord') && c.isConnected() && c.purgeChannel,
);
if (ch?.purgeChannel) {
await ch.purgeChannel(statusJid);
}
}
await startStatusDashboard();
await startUsageDashboard();
startMessageLoop().catch((err) => {

View File

@@ -88,6 +88,8 @@ export interface Channel {
syncGroups?(force: boolean): Promise<void>;
// Optional: get channel metadata (position, category) for ordering.
getChannelMeta?(jids: string[]): Promise<Map<string, ChannelMeta>>;
// Optional: delete all messages in a channel (used for dashboard cleanup).
purgeChannel?(jid: string): Promise<number>;
}
// Callback type that channels use to deliver inbound messages