const { getSetting } = require("./settings"); const badgeCatalogs = new Map(); const CACHE_MS = 30 * 60 * 1000; function fallbackBadges(badges = {}) { return Object.entries(badges).map(([label]) => ({ label, image: null })); } async function badgeRequest(path) { const clientId = String(getSetting("twitch_client_id", "") || "").trim(); const token = String(getSetting("twitch_bot_oauth", "") || "").trim().replace(/^oauth:/i, ""); if (!clientId || !token) throw new Error("Twitch badge credentials are unavailable."); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 2500); try { const response = await fetch(`https://api.twitch.tv/helix/chat/badges${path}`, { headers: { "Client-Id": clientId, Authorization: `Bearer ${token}` }, signal: controller.signal }); if (!response.ok) throw new Error(`Twitch badge request returned HTTP ${response.status}.`); return response.json(); } finally { clearTimeout(timeout); } } function indexCatalog(payload) { const catalog = new Map(); for (const set of payload?.data || []) { for (const version of set.versions || []) { catalog.set(`${set.set_id}/${version.id}`, { label: version.title || set.set_id, image: version.image_url_2x || version.image_url_1x || version.image_url_4x || null }); } } return catalog; } async function loadCatalog(roomId) { const key = String(roomId || "global"); const cached = badgeCatalogs.get(key); if (cached && cached.expiresAt > Date.now()) return cached.promise; const promise = Promise.allSettled([ badgeRequest("/global"), roomId ? badgeRequest(`?broadcaster_id=${encodeURIComponent(roomId)}`) : Promise.resolve({ data: [] }) ]).then((results) => { const catalog = new Map(); for (const result of results) { if (result.status !== "fulfilled") continue; for (const [id, badge] of indexCatalog(result.value)) catalog.set(id, badge); } return catalog; }); badgeCatalogs.set(key, { expiresAt: Date.now() + CACHE_MS, promise }); return promise; } async function resolveTwitchBadges(roomId, badges = {}) { const entries = Object.entries(badges || {}); if (!entries.length) return []; try { const catalog = await loadCatalog(roomId); return entries.map(([setId, version]) => catalog.get(`${setId}/${version}`) || { label: setId, image: null }); } catch { return fallbackBadges(badges); } } module.exports = { resolveTwitchBadges };