const { ActivityType } = require('discord.js'); const deployCommands = require('../deploy-commands'); const api = require('../api'); module.exports = { name: 'clientReady', once: true, async execute(client) { const guildIds = client.guilds.cache.map(guild => guild.id); if (guildIds.length > 0) { // Deploy commands for all guilds in parallel, but only log a single summary try { await Promise.all(guildIds.map(id => deployCommands(id))); console.log(`🔁 Refreshed application commands for ${guildIds.length} guild(s)`); } catch (e) { console.error('Error refreshing application commands:', e && e.message ? e.message : e); } } // Reconcile invites for all guilds to detect invites deleted while bot was offline console.log('🔄 Reconciling invites for offline changes...'); let totalReconciled = 0; for (const guildId of guildIds) { try { const guild = client.guilds.cache.get(guildId); if (!guild) continue; // Fetch current invites from Discord const discordInvites = await guild.invites.fetch(); const currentInvites = Array.from(discordInvites.values()); // Reconcile with database const reconciled = await api.reconcileInvites(guildId, currentInvites); totalReconciled += reconciled; } catch (e) { console.error(`Failed to reconcile invites for guild ${guildId}:`, e && e.message ? e.message : e); } } if (totalReconciled > 0) { console.log(`✅ Invite reconciliation complete: removed ${totalReconciled} stale invites`); } else { console.log('✅ Invite reconciliation complete: no stale invites found'); } // Reconcile reaction roles: ensure stored message IDs still exist, remove stale configs console.log('🔄 Reconciling reaction roles (initial check)...'); try { for (const guildId of guildIds) { try { const rrList = await api.listReactionRoles(guildId) || []; for (const rr of rrList) { if (!rr.message_id) continue; // not posted yet try { const guild = client.guilds.cache.get(guildId); if (!guild) continue; const channel = await guild.channels.fetch(rr.channel_id || rr.channelId).catch(() => null); if (!channel) { // channel missing -> delete RR await api.deleteReactionRole(guildId, rr.id); continue; } const msg = await channel.messages.fetch(rr.message_id).catch(() => null); if (!msg) { // message missing -> delete RR await api.deleteReactionRole(guildId, rr.id); continue; } } catch (inner) { // ignore per-item errors } } } catch (e) { // ignore guild-level errors } } console.log('✅ Reaction role initial reconciliation complete'); } catch (e) { console.error('Failed reaction role reconciliation:', e && e.message ? e.message : e); } // Periodic reconciliation every 10 minutes setInterval(async () => { try { for (const guildId of client.guilds.cache.map(g => g.id)) { const rrList = await api.listReactionRoles(guildId) || []; for (const rr of rrList) { if (!rr.message_id) continue; try { const guild = client.guilds.cache.get(guildId); if (!guild) continue; const channel = await guild.channels.fetch(rr.channel_id || rr.channelId).catch(() => null); if (!channel) { await api.deleteReactionRole(guildId, rr.id); continue; } const msg = await channel.messages.fetch(rr.message_id).catch(() => null); if (!msg) { await api.deleteReactionRole(guildId, rr.id); continue; } } catch (e) { // ignore } } } } catch (e) { // ignore } }, 10 * 60 * 1000); const activities = [ { name: 'Watch EhChad Live!', type: ActivityType.Streaming, url: 'https://twitch.tv/ehchad' }, { name: 'Follow EhChad!', type: ActivityType.Streaming, url: 'https://twitch.tv/ehchad' }, { name: '/help', type: ActivityType.Streaming, url: 'https://twitch.tv/ehchad' }, { name: 'EhChadServices', type: ActivityType.Streaming, url: 'https://twitch.tv/ehchad' }, ]; let activityIndex = 0; setInterval(() => { const activity = activities[activityIndex]; client.user.setActivity(activity.name, { type: activity.type, url: activity.url }); activityIndex = (activityIndex + 1) % activities.length; }, 3000); // Signal that startup is complete console.log('✅ ECS - Full Stack Bot Online!'); }, };