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
This commit is contained in:
2026-09-30 23:45:38 +02:00
parent 0ecbf00ca2
commit bf5d61ffca
5 changed files with 45 additions and 5 deletions

View File

@@ -6,5 +6,9 @@
"clientID": "",
"clientSecret": ""
},
"lfmKey": ""
"lfmKey": "",
"vrctl": {
"relayUrl": "",
"relaySecret": ""
}
}

View File

@@ -1,5 +1,6 @@
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';
@@ -19,15 +20,17 @@ module.exports = {
for (const eventId of eventIds) {
try {
const { data } = await axios.get(`https://vrc.tl/api/v1/events/${eventId}`);
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 performersById = new Map((data.performers ?? []).map(performer => [performer.id, performer.name]));
const lineup = [...event.eventSlots]
const lineup = [...(event.eventSlots ?? [])]
.sort((a, b) => a.start - b.start)
.flatMap(slot => [...slot.performers]
.flatMap(slot => [...(slot.performers ?? [])]
.sort((a, b) => a.order - b.order)
.map(slotPerformer => performersById.get(slotPerformer.performerId)))
.filter(Boolean);

10
relay/README.md Normal file
View File

@@ -0,0 +1,10 @@
# vrc.tl relay
Cloudflare Worker that proxies vrc.tl event lookups (vrc.tl blocks the VPS IP range).
Only serves `GET /events/<id>` and requires an `x-relay-secret` header.
cd relay
npx wrangler deploy
npx wrangler secret put RELAY_SECRET
Set `vrctl.relayUrl` and `vrctl.relaySecret` in the bot's config.json.

20
relay/src/index.js Normal file
View File

@@ -0,0 +1,20 @@
// Minimal relay for vrc.tl event lookups. Only GET /events/<digits>, requires secret header.
export default {
async fetch(request, env) {
if (request.method !== 'GET') return new Response('Method not allowed', { status: 405 });
if (!env.RELAY_SECRET || request.headers.get('x-relay-secret') !== env.RELAY_SECRET) {
return new Response('Unauthorized', { status: 401 });
}
const match = new URL(request.url).pathname.match(/^\/events\/(\d{1,10})$/);
if (!match) return new Response('Not found', { status: 404 });
const upstream = await fetch(`https://vrc.tl/api/v1/events/${match[1]}`, {
headers: { accept: 'application/json' },
cf: { cacheTtl: 300, cacheEverything: true },
});
return new Response(upstream.body, {
status: upstream.status,
headers: { 'content-type': upstream.headers.get('content-type') ?? 'application/json' },
});
},
};

3
relay/wrangler.toml Normal file
View File

@@ -0,0 +1,3 @@
name = "vrctl-relay"
main = "src/index.js"
compatibility_date = "2026-09-01"