v0.5.114: add Douyu streamers with fan enrichment and live probe.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+15
-1
@@ -4364,7 +4364,14 @@ function buildStreamerMetaLine(row) {
|
||||
const parts = [];
|
||||
if (uniqueId) {
|
||||
parts.push({
|
||||
label: platform === "douyin" ? "抖音号" : platform === "bilibili" ? "UID" : "账号",
|
||||
label:
|
||||
platform === "douyin"
|
||||
? "抖音号"
|
||||
: platform === "bilibili"
|
||||
? "UID"
|
||||
: platform === "douyu"
|
||||
? "房间号"
|
||||
: "账号",
|
||||
value: String(uniqueId),
|
||||
account: true,
|
||||
});
|
||||
@@ -4373,6 +4380,13 @@ function buildStreamerMetaLine(row) {
|
||||
if (Number.isFinite(Number(row.following_count))) {
|
||||
parts.push({ label: "关注", value: formatStreamerCount(row.following_count) });
|
||||
}
|
||||
} else if (platform === "douyu") {
|
||||
if (Number.isFinite(Number(row.following_count))) {
|
||||
parts.push({ label: "关注", value: formatStreamerCount(row.following_count) });
|
||||
}
|
||||
if (Number.isFinite(Number(row.total_favorited))) {
|
||||
parts.push({ label: "播放", value: formatStreamerCount(row.total_favorited) });
|
||||
}
|
||||
} else if (Number.isFinite(Number(row.total_favorited))) {
|
||||
parts.push({ label: "获赞", value: formatStreamerCount(row.total_favorited) });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Local defaults; production export overwrites via export_relations_site.py. */
|
||||
var SITE_VERSION = "0.5.112";
|
||||
var SITE_VERSION = "0.5.114";
|
||||
var ABILITY_VIDEO_BASE = "";
|
||||
var STATIC_ASSET_BASE = "";
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>DOTA2 上分帝</title>
|
||||
<link rel="icon" href="/ui-icon/dota2_logo.png" type="image/png" />
|
||||
<link rel="stylesheet" href="/style.css?v=0.5.112" />
|
||||
<script src="/mobile-gate.js?v=0.5.112"></script>
|
||||
<link rel="stylesheet" href="/style.css?v=0.5.114" />
|
||||
<script src="/mobile-gate.js?v=0.5.114"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="sr-only">DOTA2 上分帝</h1>
|
||||
@@ -178,8 +178,8 @@
|
||||
|
||||
<section class="detail" id="detail" aria-live="polite"></section>
|
||||
|
||||
<script src="/config.js?v=0.5.112"></script>
|
||||
<script src="/router.js?v=0.5.112"></script>
|
||||
<script src="/app.js?v=0.5.112"></script>
|
||||
<script src="/config.js?v=0.5.114"></script>
|
||||
<script src="/router.js?v=0.5.114"></script>
|
||||
<script src="/app.js?v=0.5.114"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user