MB-X Bilibili Pipeline
6 days ago 5c75bc6b3fe4eda9b1210574230d22249ecf949f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* Source-controlled visible-page extractor used by exactly one supported
 * playwright.evaluate call. It never fetches, navigates, or reads storage.
 */
async function projectInfoCollectVisibleDynamicNodes(options = {}) {
  const settleMs = Number(options.page_internal_settle_timeout_ms || 15000);
  if (!Number.isInteger(settleMs) || settleMs !== 15000) throw new Error("E_RUNTIME_CONTRACT");
  const started = Date.now();
  while ((!document.body || document.readyState === "loading") && Date.now() - started < settleMs) {
    await new Promise((resolve) => setTimeout(resolve, 100));
  }
  const normalize = (value) => String(value || "").replace(/\s+/g, " ").trim();
  const href = (node, selector) => node.querySelector(selector)?.href || null;
  const idFrom = (url, expression) => {
    const match = String(url || "").match(expression);
    return match ? match[1] : null;
  };
  const nodes = Array.from(document.querySelectorAll("[data-dynamic-id], .bili-dyn-list__item")).slice(0, 50);
  const cards = [];
  const unparsed_nodes = [];
  nodes.forEach((node, position) => {
    const dynamicId = node.getAttribute("data-dynamic-id");
    const opusUrl = href(node, 'a[href*="/opus/"]');
    const videoUrl = href(node, 'a[href*="/video/BV"]');
    const sourceUrl = opusUrl || videoUrl || href(node, "a[href]");
    const opusId = idFrom(opusUrl, /\/opus\/([A-Za-z0-9_-]+)/);
    const bvid = idFrom(videoUrl, /\/video\/(BV[0-9A-Za-z]{10})/i);
    const publishedAt = node.querySelector("time")?.getAttribute("datetime") || null;
    const bodyText = normalize(node.querySelector(".bili-rich-text, .dyn-card-opus__desc, .bili-dyn-card-video__desc")?.textContent);
    const title = normalize(node.querySelector(".bili-dyn-title, .bili-dyn-card-video__title")?.textContent) || bodyText.slice(0, 80);
    const images = Array.from(node.querySelectorAll('img[src^="https://"], img[src^="http://"]')).slice(0, 20).map((image) => image.currentSrc || image.src);
    const contentType = bvid ? "video" : images.length ? "image" : bodyText ? "text" : null;
    if ((!dynamicId && !opusId && !bvid) || !publishedAt || !sourceUrl || !contentType) {
      const material = JSON.stringify({position, dynamicId, opusId, bvid, publishedAt, sourceUrl, contentType, text: normalize(node.textContent).slice(0, 256)});
      unparsed_nodes.push({position, reason_code: !dynamicId && !opusId && !bvid ? "IDENTITY_MISSING" : !publishedAt ? "PUBLISHED_AT_MISSING" : !sourceUrl ? "SOURCE_URL_INVALID" : "CONTENT_TYPE_UNKNOWN", fingerprint_material: material});
      return;
    }
    cards.push({position, dynamic_id: dynamicId, opus_id: opusId, bvid, published_at: publishedAt, content_type: contentType, source_url: sourceUrl, title: title || "Bilibili dynamic", body_text: bodyText, body_complete: contentType === "text", duration_seconds: bvid ? Number(node.getAttribute("data-duration-seconds") || 0) : null, artifact_urls: images});
  });
  const bodyText = normalize(document.body?.innerText);
  const endText = ["没有更多动态了", "已经到底了"].find((text) => bodyText.includes(text)) || null;
  const profileLink = document.querySelector('a[href^="https://space.bilibili.com/"], a[href^="//space.bilibili.com/"]');
  const profileUrl = profileLink ? new URL(profileLink.href, location.href).href.replace(/\/$/, "") : null;
  return {
    schema_version: 1,
    ready_state: document.readyState,
    visibility_state: document.visibilityState,
    final_url: String(location.href),
    page_title: String(document.title || ""),
    creator: {uid: profileUrl ? idFrom(profileUrl, /space\.bilibili\.com\/(\d+)/) : null, name: normalize(profileLink?.textContent), profile_url: profileUrl},
    cards,
    unparsed_nodes,
    terminal_marker_text: endText,
    limit_hit: nodes.length === 50 ? "CARD_PER_OBSERVATION_LIMIT" : "NONE"
  };
}