fix: proxy Bandsintown events through backend to bypass 403, always show ticket link with sold-out badge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-06-08 01:23:00 +02:00
1 parent 914297651e
commit 98c964ecae
3 files changed
+21 -4

No files matched your search

+14
View File
@@ -315,6 +315,20 @@ app.get('/api/itunes/:endpoint', (req, res) => {
}).on('error', e => res.status(502).json({ error: e.message }));
});
app.get('/api/events/:artist', (req, res) => {
const artist = req.params.artist;
const target = `https://rest.bandsintown.com/artists/${encodeURIComponent(artist)}/events?app_id=novasonic`;
https.get(target, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Accept': 'application/json',
}
}, upstream => {
res.setHeader('Content-Type', 'application/json');
upstream.pipe(res);
}).on('error', e => res.status(502).json({ error: e.message }));
});
app.get('/api/health', (req, res) => {
res.json({
status: 'ok', ytdlpVersion,
+5 -2
View File
@@ -1,8 +1,11 @@
import { esc } from '../utils.js';
import { S } from '../state.js';
export async function fetchEvents(artistName) {
const base = S.cfg?.backendUrl;
if(!base) return [];
try {
const r = await fetch(`https://rest.bandsintown.com/artists/${encodeURIComponent(artistName)}/events?app_id=novasonic`);
const r = await fetch(`${base}/api/events/${encodeURIComponent(artistName)}`);
if(!r.ok) return [];
const data = await r.json();
if(!Array.isArray(data)) return [];
@@ -31,7 +34,7 @@ export function openEventsModal(events, artistName) {
<div class="event-venue-name">${esc(venue)}</div>
<div class="event-venue-city">${esc(city)}</div>
</div>
${offer ? `<a class="event-book-btn${soldOut ? ' sold-out' : ''}" href="${esc(offer.url)}" target="_blank" rel="noopener">${soldOut ? 'Ausverkauft' : 'Buchen →'}</a>` : ''}
${offer ? `<a class="event-book-btn" href="${esc(offer.url)}" target="_blank" rel="noopener">${soldOut ? '<span class="event-sold-badge">Ausverkauft</span>' : ''}Tickets →</a>` : ''}
</div>`;
}).join('');
+2 -2
View File
@@ -855,9 +855,9 @@ button:focus{outline:none}
.event-venue{flex:1;min-width:0}
.event-venue-name{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.event-venue-city{font-size:11px;color:var(--text2);margin-top:2px}
.event-book-btn{flex-shrink:0;padding:7px 14px;border-radius:20px;background:var(--accent);color:#fff;font-family:var(--font);font-size:12px;font-weight:700;text-decoration:none;transition:opacity .15s}
.event-book-btn{flex-shrink:0;padding:7px 14px;border-radius:20px;background:var(--accent);color:#fff;font-family:var(--font);font-size:12px;font-weight:700;text-decoration:none;transition:opacity .15s;display:flex;flex-direction:column;align-items:center;gap:2px}
.event-book-btn:hover{opacity:.85}
.event-book-btn.sold-out{background:var(--surface2);color:var(--text3)}
.event-sold-badge{font-size:9px;font-weight:600;opacity:.8;letter-spacing:.3px}
/* ── MISC ── */
.spinner{width:28px;height:28px;border:3px solid var(--surface2);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;margin:50px auto;display:block}