匹配范围
*://*.bilibili.com/*bilibili-video-detail用途 读取单个哔哩哔哩视频的公开元数据,包括标题、作者、分区、发布时间、时长、互动统计、分 P 信息、封面、简介和付费标记。 执行前提 需要在 bilibili.com 页面执行。脚本通过页面环境请求公开接口,接口可能因为风控、地域、权限或视频状态返回失败。 参数 bvid:可选,视频 BV 号,例如 BV1rz7569EWw。 url:可选,视频 URL…
*://*.bilibili.com/*dom.read1const input = (typeof params === "object" && params !== null) ? params : {};2const host = String(location.hostname || "").toLowerCase();3 4if (!/(^|\.)bilibili\.com$/.test(host)) {5 throw new Error("请在 bilibili.com 页面执行。");6}7 8function pickString(value, field) {9 if (value === undefined || value === null || value === "") return "";10 if (typeof value !== "string") {11 throw new Error(field + " 必须是字符串。");12 }13 return value.trim();14}15 16function extractBvid(value) {17 const text = String(value || "");18 const match = text.match(/(?:\/video\/)?(BV[0-9A-Za-z]{10,})/);19 return match ? match[1] : "";20}21 22function parsePage(value) {23 if (value === undefined || value === null || value === "") return null;24 const number = Number(value);25 if (!Number.isInteger(number) || number < 1) {26 throw new Error("page 必须是从 1 开始的整数。");27 }28 return number;29}30 31function formatDuration(seconds) {32 const total = Number(seconds);33 if (!Number.isFinite(total) || total < 0) return "";34 const value = Math.floor(total);35 const hours = Math.floor(value / 3600);36 const minutes = Math.floor((value % 3600) / 60);37 const secs = value % 60;38 const pad = (item) => String(item).padStart(2, "0");39 return hours > 040 ? hours + ":" + pad(minutes) + ":" + pad(secs)41 : minutes + ":" + pad(secs);42}43 44function toIsoTime(seconds) {45 const value = Number(seconds);46 if (!Number.isFinite(value) || value <= 0) return null;47 return new Date(value * 1000).toISOString();48}49 50const explicitBvid = pickString(input.bvid, "bvid");51const explicitUrl = pickString(input.url, "url");52const page = parsePage(input.page);53const bvid = explicitBvid || extractBvid(explicitUrl) || extractBvid(location.href);54 55if (!bvid) {56 throw new Error("请提供 bvid、url,或在哔哩哔哩视频页面执行。");57}58 59const apiUrl = "https://api.bilibili.com/x/web-interface/view?bvid=" + encodeURIComponent(bvid);60const response = await fetch(apiUrl, { credentials: "include" });61 62if (!response.ok) {63 throw new Error("读取视频详情失败,接口状态码:" + response.status + "。");64}65 66const payload = await response.json();67 68if (!payload || payload.code !== 0 || !payload.data) {69 const message = payload && payload.message ? payload.message : "未知错误";70 throw new Error("读取视频详情失败:" + message + "。");71}72 73const data = payload.data;74const pages = Array.isArray(data.pages) ? data.pages : [];75const parts = pages.map((item) => ({76 page: Number(item.page),77 cid: item.cid,78 title: item.part || "",79 duration_seconds: Number(item.duration || 0),80 duration: formatDuration(item.duration),81 url: "https://www.bilibili.com/video/" + (data.bvid || bvid) + "?p=" + Number(item.page)82}));83const selectedPart = page === null ? null : parts.find((item) => item.page === page);84 85if (page !== null && !selectedPart) {86 throw new Error("page 超出分 P 范围。");87}88 89const rights = data.rights && typeof data.rights === "object" ? data.rights : {};90let paymentType = null;91 92if (Number(rights.pay) === 1) {93 paymentType = "pay";94} else if (Number(rights.ugc_pay) === 1) {95 paymentType = "ugc_pay";96} else if (Number(rights.arc_pay) === 1) {97 paymentType = "arc_pay";98}99 100const durationSeconds = selectedPart101 ? selectedPart.duration_seconds102 : Number(data.duration || 0);103 104return {105 bvid: data.bvid || bvid,106 aid: data.aid,107 title: selectedPart ? selectedPart.title : (data.title || ""),108 series_title: data.title || "",109 author: data.owner && data.owner.name ? data.owner.name : "",110 mid: data.owner && data.owner.mid ? data.owner.mid : null,111 category: data.tname || "",112 publish_time: toIsoTime(data.pubdate),113 duration_seconds: durationSeconds,114 duration: formatDuration(durationSeconds),115 stat: {116 view: data.stat ? data.stat.view : 0,117 danmaku: data.stat ? data.stat.danmaku : 0,118 reply: data.stat ? data.stat.reply : 0,119 like: data.stat ? data.stat.like : 0,120 coin: data.stat ? data.stat.coin : 0,121 favorite: data.stat ? data.stat.favorite : 0,122 share: data.stat ? data.stat.share : 0123 },124 parts,125 cid: selectedPart ? selectedPart.cid : data.cid,126 page: selectedPart ? selectedPart.page : null,127 thumbnail: data.pic || "",128 description: data.desc || "",129 requires_payment: Boolean(paymentType),130 payment_type: paymentType,131 pay_preview: Boolean(Number(rights.ugc_pay_preview) === 1 || Number(rights.pay_free_watch) === 1),132 redirect_url: data.redirect_url || "",133 url: "https://www.bilibili.com/video/" + (data.bvid || bvid)134};