Add vrctl parse and reminder
This commit is contained in:
62
events/vrcTlEvents.js
Normal file
62
events/vrcTlEvents.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
20
migrations/01_vrctl_event_reminders.js
Normal file
20
migrations/01_vrctl_event_reminders.js
Normal 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
55
timers/vrcTlReminder.js
Normal 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user