'use strict'; const DEFAULT_API_BASE = 'https://api.bgm.tv'; let apiBase = DEFAULT_API_BASE; // Set by runSync from settings.bgmApiBase (sync is locked/serial, so a module-level value is safe). function setApiBase(base) { apiBase = (typeof base === 'string' && base.trim()) ? base.trim().replace(/\/+$/, '') : DEFAULT_API_BASE; } const sleep = (ms) => new Promise(r => setTimeout(r, ms)); async function bgmFetch(path, token) { const res = await fetch(apiBase + path, { headers: { 'Accept': 'application/json', 'User-Agent': 'BangumiVaultDocker/1.0', ...(token ? { 'Authorization': 'Bearer ' + String(token).trim() } : {}) } }); if (!res.ok) { const t = await res.text().catch(() => res.statusText); throw new Error(`HTTP ${res.status}: ${String(t).slice(0, 200)}`); } return res.json(); } async function fetchMe(token) { return bgmFetch('/v0/me', token); } async function fetchCollectionAll(username, st, ct, token, onPage) { const limit = 50; let offset = 0; let total = Infinity; let page = 0; const out = []; while (offset < total) { const path = `/v0/users/${encodeURIComponent(username)}/collections?subject_type=${st}&type=${ct}&limit=${limit}&offset=${offset}`; const js = await bgmFetch(path, token); const data = Array.isArray(js) ? js : (js.data || []); total = Number.isFinite(js.total) ? js.total : (offset + data.length + (data.length === limit ? limit : 0)); out.push(...data.map(x => ({ ...x, __fallback: { subject_type: st, collection_type: ct } }))); page++; if (onPage) onPage(page, out.length); if (!data.length || data.length < limit) break; offset += limit; await sleep(110); } return out; } async function fetchCollectionRecent(username, st, ct, token, cutoff, onPage) { const limit = 50; let offset = 0; let total = Infinity; let page = 0; const out = []; const cutoffMs = cutoff.getTime(); while (offset < total) { const path = `/v0/users/${encodeURIComponent(username)}/collections?subject_type=${st}&type=${ct}&limit=${limit}&offset=${offset}`; const js = await bgmFetch(path, token); const data = Array.isArray(js) ? js : (js.data || []); total = Number.isFinite(js.total) ? js.total : (offset + data.length + (data.length === limit ? limit : 0)); const decorated = data.map(x => ({ ...x, __fallback: { subject_type: st, collection_type: ct } })); const recent = decorated.filter(x => { const t = Date.parse(x.updated_at || x.updatedAt || ''); return !Number.isFinite(t) || t >= cutoffMs; }); out.push(...recent); page++; const stopByTime = data.length > 0 && recent.length === 0 && data.every(x => { const t = Date.parse(x.updated_at || x.updatedAt || ''); return Number.isFinite(t) && t < cutoffMs; }); if (onPage) onPage(page, out.length, stopByTime); if (!data.length || data.length < limit || stopByTime) break; offset += limit; await sleep(80); } return out; } async function fetchSubjectDetail(subjectId, token) { try { return await bgmFetch(`/v0/subjects/${encodeURIComponent(subjectId)}`, token || ''); } catch { return null; } } module.exports = { DEFAULT_API_BASE, setApiBase, sleep, bgmFetch, fetchMe, fetchCollectionAll, fetchCollectionRecent, fetchSubjectDetail };