/* ===================================================================== lib/tiktok-live.js — Automatische Live-Erkennung für TikTok. 1:1 portiert aus cloudflare-worker/src/lib/tiktok-live.js (das "cf"-Fetch-Sonderfeld von Cloudflare Workers entfällt, hat in Node keine Entsprechung/Wirkung). ===================================================================== */ const LIVE_STATUS_RUNNING = 2; const BROWSER_HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "de-DE,de;q=0.9,en;q=0.8", "Cache-Control": "no-cache", Pragma: "no-cache", }; function extractScriptJson(html, id) { const marker = `", open); if (end === -1) return null; try { return JSON.parse(html.slice(open + 1, end)); } catch { return null; } } export async function checkTikTokLive(username) { const handle = String(username || "").replace(/^@/, "").trim(); if (!handle) return { ok: false, reason: "kein-benutzername" }; let res; try { res = await fetch(`https://www.tiktok.com/@${encodeURIComponent(handle)}/live`, { headers: BROWSER_HEADERS, redirect: "follow", }); } catch (err) { return { ok: false, reason: `netzwerkfehler: ${String(err).slice(0, 120)}` }; } if (!res.ok) return { ok: false, reason: `http-${res.status}` }; const html = await res.text(); if (html.length < 5000) return { ok: false, reason: "seite-zu-klein-vermutlich-blockiert" }; const sigi = extractScriptJson(html, "SIGI_STATE"); let info = sigi?.LiveRoom?.liveRoomUserInfo; if (!info) { const universal = extractScriptJson(html, "__UNIVERSAL_DATA_FOR_REHYDRATION__"); info = universal?.__DEFAULT_SCOPE__?.["webapp.live-detail"]?.liveRoomUserInfo; } if (!info) return { ok: false, reason: "struktur-nicht-gefunden" }; const status = info.liveRoom?.status; if (typeof status !== "number") return { ok: false, reason: "kein-status-feld" }; return { ok: true, live: status === LIVE_STATUS_RUNNING, rawStatus: status, title: info.liveRoom?.title || null, roomId: info.user?.roomId ? String(info.user.roomId) : null, followers: info.stats?.followerCount ?? null, }; }