72 lines
2.5 KiB
JavaScript
72 lines
2.5 KiB
JavaScript
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 };
|