const REMINDER_EMOTE = '🔔'; const REMINDER_LEAD_SECONDS = 1 * 60 * 60; module.exports = { timeout: 60000, immediate: true, name: 'VRC.TL Event Reminders', async tick(client, timer) { try { const db = await client.localDB; const nowUnix = Math.floor(Date.now() / 1000); const dueReminders = db.prepare(` SELECT id, message_id, channel_id, event_name, event_start FROM vrctl_event_reminders WHERE reminder_sent = 0 AND event_start - ? <= ? `).all(REMINDER_LEAD_SECONDS, nowUnix); for (const reminder of dueReminders) { const markSent = db.prepare(`UPDATE vrctl_event_reminders SET reminder_sent = 1 WHERE id = ?`); if (reminder.event_start <= nowUnix) { // Event already started (e.g. bot was down) - don't send a stale reminder. markSent.run(reminder.id); continue; } try { const channel = await client.channels.fetch(reminder.channel_id); // force: true bypasses the client's message cache, which the reaction // sweeper (see main.js) periodically empties - a cached message here // would silently report zero reactions. const message = await channel.messages.fetch({ message: reminder.message_id, force: true }); const reaction = message.reactions.cache.get(REMINDER_EMOTE); const users = reaction ? await reaction.users.fetch() : new Map(); const mentions = [...users.values()] .filter(user => !user.bot) .map(user => `<@${user.id}>`); if (mentions.length > 0) { await channel.send({ content: `⏰ **${reminder.event_name}** starts ! ${mentions.join(' ')}`, reply: { messageReference: reminder.message_id }, allowedMentions: { parse: [], users: mentions.map(m => m.slice(2, -1)) } }); console.log(`[TIMER] Sent vrc.tl reminder for "${reminder.event_name}" to ${mentions.length} user(s)`); } else { console.log(`[TIMER] No reactions found for vrc.tl reminder "${reminder.event_name}" (message ${reminder.message_id}), skipping ping`); } markSent.run(reminder.id); } catch (error) { console.error(`[TIMER] Failed to send vrc.tl reminder for message ${reminder.message_id}:`, error); } } } catch (error) { console.error('[TIMER] VRC.TL Event Reminders error:', error); } }, };