Add vrctl parse and reminder

This commit is contained in:
2026-08-09 20:02:34 +02:00
parent 6f1e8f8087
commit afdba9a6ad
3 changed files with 137 additions and 0 deletions

62
events/vrcTlEvents.js Normal file
View File

@@ -0,0 +1,62 @@
const { Events } = require('discord.js');
const axios = require('axios').default;
const SWARM_EVENTS_CHANNEL_ID = '1536067515115905155';
const VRC_TL_EVENT_REGEX = /https:\/\/vrc\.tl\/event\/(\d+)/g;
const REMINDER_EMOTE = '🔔';
module.exports = {
name: Events.MessageCreate,
async execute(message) {
if (message.author.bot) return;
if (message.channel.id !== SWARM_EVENTS_CHANNEL_ID) return;
const matches = [...message.content.matchAll(VRC_TL_EVENT_REGEX)];
if (matches.length === 0) return;
const eventIds = [...new Set(matches.map(match => match[1]))];
for (const eventId of eventIds) {
try {
const { data } = await axios.get(`https://vrc.tl/api/v1/events/${eventId}`);
const event = data.events?.[0];
if (!event) continue;
const performersById = new Map(data.performers.map(performer => [performer.id, performer.name]));
const lineup = [...event.eventSlots]
.sort((a, b) => a.start - b.start)
.flatMap(slot => [...slot.performers]
.sort((a, b) => a.order - b.order)
.map(slotPerformer => performersById.get(slotPerformer.performerId)))
.filter(Boolean);
const lines = [
`**${event.name}**`,
`<t:${event.start}:F> (<t:${event.start}:R>)`,
];
if (lineup.length > 0) {
lines.push(`DJ Lineup: ${lineup.join(', ')}`);
}
if (event.urls?.vrcGroup) {
lines.push(`Group: ${event.urls.vrcGroup}`);
}
lines.push(`\nReact with ${REMINDER_EMOTE} to get pinged 1 hour before this starts.`);
const reply = await message.reply(lines.join('\n'));
await reply.react(REMINDER_EMOTE);
const db = await message.client.localDB;
db.prepare(`
INSERT INTO vrctl_event_reminders (vrctl_event_id, message_id, channel_id, event_name, event_start)
VALUES (?, ?, ?, ?, ?)
`).run(eventId, reply.id, reply.channel.id, event.name, event.start);
} catch (error) {
console.error(`Failed to fetch vrc.tl event ${eventId}:`, error?.response?.data ?? error);
}
}
},
};

View File

@@ -0,0 +1,20 @@
module.exports = {
up: async ({ context: db }) => {
db.exec(`
CREATE TABLE IF NOT EXISTS vrctl_event_reminders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vrctl_event_id TEXT NOT NULL,
message_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
event_name TEXT,
event_start INTEGER NOT NULL,
reminder_sent INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
db.exec(`CREATE INDEX IF NOT EXISTS idx_vrctl_reminders_pending ON vrctl_event_reminders (reminder_sent, event_start);`);
},
down: async ({ context: db }) => {
db.exec(`DROP TABLE IF EXISTS vrctl_event_reminders;`);
}
};

55
timers/vrcTlReminder.js Normal file
View File

@@ -0,0 +1,55 @@
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);
const message = await channel.messages.fetch(reminder.message_id);
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 <t:${reminder.event_start}:R>! ${mentions.join(' ')}`,
reply: { messageReference: reminder.message_id },
allowedMentions: { parse: [], users: mentions.map(m => m.slice(2, -1)) }
});
}
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);
}
},
};