Files
general-purpose-bot/events/vrcTlEvents.js
Minz bf5d61ffca Fetch vrc.tl events via Cloudflare Worker relay
vrc.tl returns 403 for the VPS IP range. Route lookups through a relay
Worker (relay/), configured via vrctl.relayUrl/relaySecret in config.json.
Also handle events without eventSlots/performers instead of crashing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrxnVXujrZqNrENxJZfVkU
2026-09-30 23:45:38 +02:00

79 lines
2.9 KiB
JavaScript

const { Events } = require('discord.js');
const axios = require('axios').default;
const { vrctl } = require(process.env.CONFIG_FILE ? `../${process.env.CONFIG_FILE}` : '../config.json');
const SWARM_EVENTS_CHANNEL_ID = '1536067515115905155';
const VRC_CROSSPOST_CHANNEL_ID = '1438261670916133055';
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(`${vrctl.relayUrl}/events/${eventId}`, {
headers: { 'x-relay-secret': vrctl.relaySecret },
});
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}`);
}
const reminderPrompt = `\nReact with ${REMINDER_EMOTE} to get pinged 1 hour before this starts.`;
const db = await message.client.localDB;
const reply = await message.reply([...lines, reminderPrompt].join('\n'));
await reply.react(REMINDER_EMOTE);
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);
try {
const crosspostChannel = await message.client.channels.fetch(VRC_CROSSPOST_CHANNEL_ID);
const crosspostLines = [...lines, `Link: https://vrc.tl/event/${eventId}`, reminderPrompt];
const crosspost = await crosspostChannel.send(crosspostLines.join('\n'));
await crosspost.react(REMINDER_EMOTE);
db.prepare(`
INSERT INTO vrctl_event_reminders (vrctl_event_id, message_id, channel_id, event_name, event_start)
VALUES (?, ?, ?, ?, ?)
`).run(eventId, crosspost.id, crosspost.channel.id, event.name, event.start);
} catch (crosspostError) {
console.error(`Failed to crosspost vrc.tl event ${eventId} to vrc channel:`, crosspostError);
}
} catch (error) {
console.error(`Failed to fetch vrc.tl event ${eventId}:`, error?.response?.data ?? error);
}
}
},
};