v0.5.114: add Douyu streamers with fan enrichment and live probe.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
* Probe logic is a JS port of fetch_streamer_live.py:
|
||||
* - Bilibili: api.live.bilibili.com Room/get_info; data.live_status === 1 is
|
||||
* live (0 offline, 2 replay counts as offline).
|
||||
* - Douyu: www.douyu.com/betard/<room_id>; room.show_status === 1 is live
|
||||
* (2 offline); room.videoLoop === 1 (carousel) counts as offline.
|
||||
* - Douyin: warm up cookies (www.douyin.com + live.douyin.com), then GET
|
||||
* live.douyin.com/<web_rid> with a browser UA and parse the escaped JSON in
|
||||
* the SSR pace chunks: roomStore.roomInfo.room.status (2 live / 4 offline);
|
||||
@@ -44,6 +46,7 @@ const DOUYIN_HOME = "https://www.douyin.com/";
|
||||
const DOUYIN_LIVE_HOME = "https://live.douyin.com/";
|
||||
const BILIBILI_INFO_URL =
|
||||
"https://api.live.bilibili.com/room/v1/Room/get_info?room_id=";
|
||||
const DOUYU_BETARD_URL = "https://www.douyu.com/betard/";
|
||||
|
||||
// Escaped JSON inside the SSR pace chunks: \"roomStore\":{\"roomInfo\":{\"room\":{
|
||||
const DOUYIN_ROOMSTORE_RE = /\\"roomStore\\":\s*\{\\"roomInfo\\":\s*\{\\"room\\":\s*\{/;
|
||||
@@ -162,7 +165,25 @@ export async function probeBilibiliRoom(roomId) {
|
||||
return data.live_status === 1;
|
||||
}
|
||||
|
||||
/** First path segment of the live room URL (douyin web_rid / bilibili room id). */
|
||||
/** show_status: 1 live, 2 offline; videoLoop carousel counts as offline. */
|
||||
export async function probeDouyuRoom(roomId) {
|
||||
const res = await fetchWithTimeout(DOUYU_BETARD_URL + roomId, {
|
||||
headers: {
|
||||
"User-Agent": BROWSER_UA,
|
||||
Accept: "application/json",
|
||||
Referer: "https://www.douyu.com/",
|
||||
},
|
||||
});
|
||||
const payload = await res.json();
|
||||
const room = payload && payload.room;
|
||||
if (!room || typeof room !== "object") {
|
||||
throw new Error("douyu betard returned no room");
|
||||
}
|
||||
if (Number(room.videoLoop || 0) === 1) return false;
|
||||
return Number(room.show_status) === 1;
|
||||
}
|
||||
|
||||
/** First path segment of the live room URL (douyin/bilibili/douyu room id). */
|
||||
export function roomRefFromUrl(liveUrl) {
|
||||
let path = "";
|
||||
try {
|
||||
@@ -180,6 +201,7 @@ export function livePlatformFromUrl(liveUrl, fallback = "") {
|
||||
const host = new URL(String(liveUrl).trim()).hostname.toLowerCase();
|
||||
if (host.includes("bilibili.com")) return "bilibili";
|
||||
if (host.includes("douyin.com")) return "douyin";
|
||||
if (host.includes("douyu.com")) return "douyu";
|
||||
} catch {
|
||||
/* keep fallback */
|
||||
}
|
||||
@@ -245,6 +267,7 @@ async function probeAll(targets) {
|
||||
let isLive;
|
||||
if (t.platform === "douyin") isLive = await probeDouyinRoom(t.ref, jar);
|
||||
else if (t.platform === "bilibili") isLive = await probeBilibiliRoom(t.ref);
|
||||
else if (t.platform === "douyu") isLive = await probeDouyuRoom(t.ref);
|
||||
else throw new Error(`unsupported platform ${t.platform}`);
|
||||
results.set(t.id, { is_live: isLive });
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user