// ==UserScript== // @name BiliReveal - 哔哩哔哩 Safari 网页版显示 IP 属地 // @version 2.3.1 // @author Codex // @description 我不喜欢 IP 属地,但是你手机都显示了,为什么电脑不显示呢?在哔哩哔哩网页版大部分场景中显示 IP 属地。 // @license MIT // @icon https://www.bilibili.com/favicon.ico // @match https://www.bilibili.com/video/* // @match https://www.bilibili.com/list/* // @match https://www.bilibili.com/bangumi/play/* // @match https://t.bilibili.com/* // @match https://www.bilibili.com/opus/* // @match https://space.bilibili.com/* // @match https://www.bilibili.com/v/topic/detail/* // @match https://www.bilibili.com/cheese/play/* // @match https://www.bilibili.com/festival/* // @match https://www.bilibili.com/blackboard/* // @match https://www.bilibili.com/blackroom/ban/* // @match https://www.bilibili.com/read/* // @match https://manga.bilibili.com/detail/* // @match https://www.bilibili.com/v/topic/detail* // @match https://live.bilibili.com/* // @grant none // @inject-into page // @weight 999 // @run-at document-start // ==/UserScript== (function() { 'use strict'; var _GM = (() => typeof GM != "undefined" ? GM : void 0)(); var _GM_getValue = (() => typeof GM_getValue === "function" ? GM_getValue : typeof _GM?.getValue === "function" ? _GM.getValue.bind(_GM) : void 0)(); var _GM_info = (() => typeof GM_info != "undefined" ? GM_info : _GM?.info)(); var _GM_registerMenuCommand = (() => typeof GM_registerMenuCommand === "function" ? GM_registerMenuCommand : typeof _GM?.registerMenuCommand === "function" ? _GM.registerMenuCommand.bind(_GM) : void 0)(); var _GM_setClipboard = (() => typeof GM_setClipboard === "function" ? GM_setClipboard : typeof _GM?.setClipboard === "function" ? _GM.setClipboard.bind(_GM) : void 0)(); var _GM_setValue = (() => typeof GM_setValue === "function" ? GM_setValue : void 0)(); var _GM_unregisterMenuCommand = (() => typeof GM_unregisterMenuCommand === "function" ? GM_unregisterMenuCommand : typeof _GM?.unregisterMenuCommand === "function" ? _GM.unregisterMenuCommand.bind(_GM) : void 0)(); var _unsafeWindow = (() => typeof unsafeWindow != "undefined" ? unsafeWindow : window)(); var REPLACEMENTS_KEY = "locationReplacements"; var safeJSONParse = (text, defaultValue) => { if (typeof text !== "string") return text ?? defaultValue; try { return JSON.parse(text); } catch { return defaultValue; } }; var parseReplacements = (rawJson) => { const parsed = safeJSONParse(rawJson, {}); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return new Map(); return new Map(Object.entries(parsed)); }; var replacements = new Map(); var DEBUG_MODE_KEY = "bili_reveal_debug_mode"; var LOG_VERSION = 1; var isDebugMode = false; var getStoredValue = async (key, defaultValue) => { try { if (_GM_getValue) return await _GM_getValue(key, defaultValue); if (typeof _GM?.getValue === "function") return await _GM.getValue(key, defaultValue); const localValue = localStorage.getItem(key); return localValue === null ? defaultValue : localValue; } catch { return defaultValue; } }; var setStoredValue = async (key, value) => { if (typeof _GM?.setValue === "function") { await _GM.setValue(key, value); return; } if (_GM_setValue) { await _GM_setValue(key, value); return; } localStorage.setItem(key, typeof value === "string" ? value : JSON.stringify(value)); }; var parseBoolean = (value) => value === true || value === 1 || value === "1" || value === "true"; var memoryLogs = []; var logger = { log: (level, ...args) => { if (!isDebugMode) { if (level === "error") console.error("[BiliReveal]", ...args); return; } (level === "error" ? console.error : level === "warn" ? console.warn : console.log)(`[BiliReveal][${level.toUpperCase()}]`, ...args); try { const MAX_LOGS = 1e3; const logString = `[${new Date().toLocaleTimeString()}] [${level.toUpperCase()}] ${args.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")}`; memoryLogs.push(logString); if (memoryLogs.length > MAX_LOGS) memoryLogs = memoryLogs.slice(-1e3); updateLogMenu(); } catch (e) { console.error("Failed to process log", e); } }, debug: (...args) => logger.log("debug", ...args), info: (...args) => logger.log("info", ...args), warn: (...args) => logger.log("warn", ...args), error: (...args) => logger.log("error", ...args), incrementIpCount: () => { if (ipInjectCount === 0) logger.info("[IP属地插入] 首次成功解析并插入 IP 属地"); ipInjectCount++; updateLogMenu(); } }; var ipInjectCount = 0; var logMenuId; var menuUpdateTimer = null; var updateLogMenu = () => { if (!isDebugMode || typeof _GM_registerMenuCommand !== "function") return; if (menuUpdateTimer) clearTimeout(menuUpdateTimer); menuUpdateTimer = setTimeout(() => { const count = memoryLogs.length; if (logMenuId !== void 0 && typeof _GM_unregisterMenuCommand === "function") try { _GM_unregisterMenuCommand(logMenuId); } catch {} logMenuId = _GM_registerMenuCommand(`📄 复制本页日志 (${count})`, () => { if (memoryLogs.length === 0) return; const text = [ `=== BiliReveal Debug Info v${LOG_VERSION}===`, `Script Version: ${_GM_info?.script?.version || "Unknown"} (Lite: false)`, `Script Handler: ${_GM_info?.scriptHandler || "Unknown"} v${_GM_info?.version || "Unknown"}`, `User Agent: ${navigator.userAgent}`, `URL: ${location.href}`, `Time: ${new Date().toLocaleDateString("sv-SE")}`, `IP Insertions: ${ipInjectCount}`, `=============================`, "" ].join("\n") + memoryLogs.join("\n"); if (_GM_setClipboard) _GM_setClipboard(text, "text"); else if (navigator.clipboard?.writeText) void navigator.clipboard.writeText(text).catch(() => {}); }); }, 100); }; var isElementLoaded = async (selector, root = document) => { const getElement = () => root.querySelector(selector); return new Promise((resolve) => { const element = getElement(); if (element) { logger.debug(`[DOM] 元素就绪 (瞬时): ${selector}`); return resolve(element); } logger.debug(`[DOM] 等待元素渲染: ${selector}`); const observer = new MutationObserver((_) => { const element = getElement(); if (!element) return; logger.debug(`[DOM] 元素就绪 (异步): ${selector}`); resolve(element); observer.disconnect(); }); const target = root === document ? root.documentElement ?? root : root; observer.observe(target, { childList: true, subtree: true }); }); }; var preprocessLocation = (location) => { if (!location || replacements.size === 0) return location; let result = location; for (const [target, replacement] of replacements) if (result.includes(target)) result = result.replaceAll(target, replacement); return result; }; var logLocationDebug = (source, rawLocation, locationString, details = {}) => { console.log("[BiliReveal][IP属地]", source, { raw: rawLocation ?? null, processed: locationString ?? null, ...details }); }; var getLocationString = (replyItem) => { const rawLocation = replyItem?.reply_control?.location; const locationString = preprocessLocation(rawLocation); logLocationDebug("评论字段读取", rawLocation, locationString, { hasReplyItem: !!replyItem, hasReplyControl: !!replyItem?.reply_control, replyKeys: replyItem && typeof replyItem === "object" ? Object.keys(replyItem) : [], replyControlKeys: replyItem?.reply_control && typeof replyItem.reply_control === "object" ? Object.keys(replyItem.reply_control) : [] }); return locationString; }; var Router = class { routes = []; serve(prefix, action, constrait = {}) { if (Array.isArray(prefix)) { prefix.forEach((p) => { this.routes.push({ prefix: p, action, constrait }); }); return; } this.routes.push({ prefix, action, constrait }); } match(url) { for (const { prefix, action, constrait } of this.routes) { if (!url.startsWith(prefix)) continue; if (constrait.endsWith && !url.endsWith(constrait.endsWith)) continue; logger.info(`[Router] 匹配到路由: ${prefix}`, constrait.endsWith ? `(要求以 ${constrait.endsWith} 结尾)` : ""); action(url); break; } } }; var fromError = (error) => error instanceof Error ? error.message : String(error); var registerConfigMenus = () => { if (typeof _GM_registerMenuCommand !== "function") return; _GM_registerMenuCommand("配置文本替换", () => { const currentRules = JSON.stringify(Object.fromEntries(replacements), null, 2); const input = prompt("请输入新的位置替换规则(JSON格式的键值对,例如 {\"旧字符串\": \"新字符串\"}):", currentRules); if (!input) return; try { const parsed = JSON.parse(input); if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("必须是键值对对象格式"); } catch (error) { alert(`JSON 格式错误:${fromError(error)}`); return; } setStoredValue(REPLACEMENTS_KEY, input).then(() => { location.reload(); }).catch((error) => { alert(`更新替换规则失败:${fromError(error)}`); }); }); _GM_registerMenuCommand(isDebugMode ? "🟢 关闭调试模式" : "开启调试模式", () => { setStoredValue(DEBUG_MODE_KEY, !isDebugMode).then(() => { location.reload(); }).catch((error) => { alert(`更新调试模式失败:${fromError(error)}`); }); }); if (isDebugMode) updateLogMenu(); }; var fetchArticleViewInfo = async (cv) => { try { const response = await (await fetch(`https://api.bilibili.com/x/article/viewinfo?id=${cv}`, { credentials: "include" })).json(); const { data } = response; logLocationDebug("专栏接口返回", data?.location, data?.location, { cv, code: response?.code, dataKeys: data && typeof data === "object" ? Object.keys(data) : [] }); return data; } catch (error) { logger.error("获取文章 IP 属地失败:", error); return; } }; var serveNewOpusArticle = async (initialState) => { const basic = initialState?.detail?.basic; if (!basic?.rid_str || basic.article_type !== 0) return; const viewinfo = await fetchArticleViewInfo(basic.rid_str); if (!viewinfo?.location) { logger.warn(`[IP属地解析] 新版专栏 (opus) 未携带 IP 数据 (cv: ${basic.rid_str})`); return; } const authorPub = await isElementLoaded(".opus-module-author__pub"); if (!authorPub) { logger.warn("[article] 未找到 .opus-module-author__pub", `(有 .opus-module-author: ${!!document.querySelector(".opus-module-author")})`); return; } if (authorPub.querySelector(".opus-module-author__pub__bilireveal")) return; logger.incrementIpCount(); const locationEl = document.createElement("span"); locationEl.innerHTML = `${viewinfo.location} · `; locationEl.className = "opus-module-author__pub__bilireveal"; authorPub.insertAdjacentElement("afterbegin", locationEl); logLocationDebug("新版专栏插入成功", viewinfo.location, viewinfo.location, { cv: basic.rid_str }); }; var injectArticleLocation = async (url) => { const match = url.match(/\/cv(\d+)/); const cv = match ? match[1] : void 0; if (!cv) { if (_unsafeWindow.__INITIAL_STATE__) { serveNewOpusArticle(_unsafeWindow.__INITIAL_STATE__); return; } let initialState; Object.defineProperty(_unsafeWindow, "__INITIAL_STATE__", { get: () => initialState, set: (value) => { initialState = value; serveNewOpusArticle(initialState); }, configurable: true }); return; } const viewinfo = await fetchArticleViewInfo(cv); if (!viewinfo?.location) { logger.warn(`[IP属地解析] 专栏文章 未携带 IP 数据 (cv: ${cv})`); return; } const publishText = (await isElementLoaded(".article-detail"))?.querySelector(".publish-text"); if (!publishText) return; if (publishText.parentElement?.querySelector(".article-location-bilireveal")) return; logger.incrementIpCount(); const locationEl = document.createElement("span"); locationEl.textContent = `${viewinfo.location} · `; locationEl.className = "article-location-bilireveal"; publishText.insertAdjacentElement("afterend", locationEl); logLocationDebug("专栏文章插入成功", viewinfo.location, viewinfo.location, { cv }); }; var updateLocationElement = (thisArg) => { const pubDateEl = thisArg?.shadowRoot?.querySelector("#pubdate"); if (!pubDateEl) { console.log("[BiliReveal][Debug] lit-component 未找到 #pubdate", { hasThisArg: !!thisArg, hasShadowRoot: !!thisArg?.shadowRoot }); return; } let locationEl = thisArg.shadowRoot.querySelector("#location"); const locationString = getLocationString(thisArg.data); if (!locationString) { logger.warn("[IP属地解析] lit-component 未携带 IP 数据 (解析为空)"); if (locationEl) locationEl.remove(); return; } if (locationEl) { locationEl.textContent = locationString; return; } logger.incrementIpCount(); locationEl = document.createElement("div"); locationEl.id = "location"; locationEl.textContent = locationString; pubDateEl.insertAdjacentElement("afterend", locationEl); logLocationDebug("lit-component 插入成功", locationString, locationString); }; var patchedActionButtonsRenderers = new WeakSet(); var createPatch = (ActionButtonsRender) => { if (patchedActionButtonsRenderers.has(ActionButtonsRender)) return ActionButtonsRender; const originalUpdate = ActionButtonsRender?.prototype?.update; if (typeof originalUpdate !== "function") { console.log("[BiliReveal][Debug] action-buttons-renderer 没有可 Hook 的 update", { constructorName: ActionButtonsRender?.name || null, prototypeKeys: ActionButtonsRender?.prototype ? Object.keys(ActionButtonsRender.prototype) : [] }); return ActionButtonsRender; } const applyHandler = (target, thisArg, args) => { const result = Reflect.apply(target, thisArg, args); try { updateLocationElement(thisArg); } catch (error) { logger.error("[Hook异常] lit-component 处理失败", error); } return result; }; ActionButtonsRender.prototype.update = new Proxy(originalUpdate, { apply: applyHandler }); patchedActionButtonsRenderers.add(ActionButtonsRender); console.log("[BiliReveal][Debug] action-buttons-renderer Hook 成功", { constructorName: ActionButtonsRender.name || null }); return ActionButtonsRender; }; var hookLit = () => { console.log("[BiliReveal][Debug] 安装 hookLit"); logger.info("[Strategy] 启用 hookLit (Web Components)"); const customElements = _unsafeWindow.customElements; const rendererName = "bili-comment-action-buttons-renderer"; const existingRenderer = typeof customElements?.get === "function" ? customElements.get(rendererName) : void 0; console.log("[BiliReveal][Debug] 检查 action-buttons-renderer", { unsafeWindowType: typeof unsafeWindow, customElementsAvailable: !!customElements, alreadyDefined: !!existingRenderer, domCount: typeof document !== "undefined" && typeof document.querySelectorAll === "function" ? document.querySelectorAll(rendererName).length : null }); if (existingRenderer) createPatch(existingRenderer); if (!customElements || typeof customElements.define !== "function") return; const { define: originalDefine } = customElements; const applyHandler = (target, thisArg, args) => { const [name, classConstructor, ...rest] = args; if (typeof classConstructor !== "function" || name !== rendererName) return Reflect.apply(target, thisArg, args); const PatchActionButtonsRender = createPatch(classConstructor); return Reflect.apply(target, thisArg, [ name, PatchActionButtonsRender, ...rest ]); }; _unsafeWindow.customElements.define = new Proxy(originalDefine, { apply: applyHandler }); }; var injectBBComment = (bbComment, { variation } = { variation: false }) => { const { _createListCon: createListCon, _createSubReplyItem: createSubReplyItem } = bbComment.prototype; const applyHandler = (target, thisArg, args) => { const [item] = args; const result = Reflect.apply(target, thisArg, args); try { const replyTimeRegex = /(.*?)<\/span>/; const location = getLocationString(item); if (!location) { logger.warn("[IP属地解析] vue-legacy 未携带 IP 数据 (解析为空)"); return result; } logger.incrementIpCount(); const injectedResult = variation ? result.replace(/(.*?)<\/span>/, `$1  ${location}`) : result.replace(replyTimeRegex, `$1${location}`); logLocationDebug("vue-legacy 评论插入成功", location, location, { variation }); return injectedResult; } catch (error) { logger.error("[Hook异常] vue-legacy 处理失败", error, `(rpid=${item?.rpid}, hasLocation=${!!item?.reply_control?.location})`); return result; } }; bbComment.prototype._createListCon = new Proxy(createListCon, { apply: applyHandler }); bbComment.prototype._createSubReplyItem = new Proxy(createSubReplyItem, { apply: applyHandler }); }; var hookBBComment = ({ variation } = { variation: false }) => { console.log("[BiliReveal][Debug] 安装 hookBBComment", { variation }); logger.info("[Strategy] 启用 hookBBComment", variation ? "(变体)" : ""); if (_unsafeWindow.bbComment) { injectBBComment(_unsafeWindow.bbComment, { variation }); return; } let bbComment; Object.defineProperty(_unsafeWindow, "bbComment", { get: () => bbComment, set: (value) => { bbComment = value; injectBBComment(value, { variation }); }, configurable: true }); }; var realProxy = _unsafeWindow.Proxy; var vueUnhooked = new WeakSet(); var vueHooked = new WeakMap(); var watchedVNodes = new WeakSet(); var watchedApps = new WeakSet(); var isProxyHookInstalled = false; var isVueApp = (value) => { if (!value || typeof value !== "object") return false; const app = value; return typeof app.uid === "number" && app.uid >= 0 && !!app.vnode; }; var handleVueApp = (app) => { const el = app.vnode.el; if (el) { recordVue(el, app); recordDOM(el, app); watchIsUnmounted(app); return; } vueUnhooked.add(app); watchEl(app.vnode); }; var watchEl = (vnode) => { if (watchedVNodes.has(vnode)) return; watchedVNodes.add(vnode); let value = vnode.el; let hooked = false; Object.defineProperty(vnode, "el", { configurable: true, enumerable: true, get() { return value; }, set(newValue) { value = newValue; const component = this.component; if (!hooked && newValue && component) { hooked = true; recordVue(newValue, component); recordDOM(newValue, component); watchIsUnmounted(component); } } }); }; var watchIsUnmounted = (app) => { if (watchedApps.has(app)) return; watchedApps.add(app); let value = app.isUnmounted; let unhooked = false; Object.defineProperty(app, "isUnmounted", { configurable: true, enumerable: true, get() { return value; }, set(newValue) { value = newValue; if (!unhooked && this.isUnmounted) { unhooked = true; cleanupAppReference(this); } } }); }; var cleanupAppReference = (app) => { const el = app.vnode.el; if (!el) return; const domValue = el.__vue__; const nextDomValue = removeAppFromRecord(domValue, app); if (nextDomValue === void 0) el.__vue__ = void 0; else el.__vue__ = nextDomValue; const nextMapValue = removeAppFromRecord(vueHooked.get(el), app); if (nextMapValue === void 0) vueHooked.delete(el); else vueHooked.set(el, nextMapValue); }; var removeAppFromRecord = (record, app) => { if (!record) return void 0; if (!Array.isArray(record)) return record === app ? void 0 : record; const next = record.filter((item) => item !== app); if (next.length === 0) return void 0; return next.length === 1 ? next[0] : next; }; var appendAppToRecord = (record, app) => { if (!record) return app; if (!Array.isArray(record)) return record === app ? record : [record, app]; return record.includes(app) ? record : [...record, app]; }; var recordVue = (el, app) => { vueUnhooked.delete(app); vueHooked.set(el, appendAppToRecord(vueHooked.get(el), app)); }; var recordDOM = (el, app) => { el.__vue__ = appendAppToRecord(el.__vue__, app); }; var proxyInterceptor = new Proxy(realProxy, { construct: (target, args, newTarget) => { const app = args[0]?._; if (isVueApp(app)) handleVueApp(app); return Reflect.construct(target, args, newTarget); } }); var hookVue3App = () => { if (isProxyHookInstalled) return; _unsafeWindow.Proxy = proxyInterceptor; isProxyHookInstalled = true; }; var extractLocationFromReplyElement = (replyItemEl) => { let replyElement; let locationString; if (replyItemEl.className.startsWith("sub")) { replyElement = replyItemEl; locationString = getLocationString(replyElement?.__vue__.vnode.props.subReply); } else { replyElement = replyItemEl; locationString = getLocationString(replyElement?.__vue__.vnode.props.reply); } return locationString; }; var hasLocationInjected = (replyInfo) => replyInfo.children.length !== 0 && replyInfo.children[0].innerHTML.includes("IP属地"); var insertLocation = (replyItemEl) => { const replyInfo = replyItemEl.className.startsWith("sub") ? replyItemEl.querySelector(".sub-reply-info") : replyItemEl.querySelector(".reply-info"); if (!replyInfo) throw new Error("Can not detect reply info"); const locationString = extractLocationFromReplyElement(replyItemEl); if (!locationString) { logger.warn("[IP属地解析] vue3 未携带 IP 数据 (解析为空)"); return; } if (hasLocationInjected(replyInfo)) return; logger.incrementIpCount(); replyInfo.children[0].innerHTML += `  ${locationString}`; logLocationDebug("vue3 评论插入成功", locationString, locationString); }; var isReplyItem = (el) => el instanceof HTMLDivElement && ["reply-item", "sub-reply-item"].includes(el.className); var observeAndInjectComments = async (root) => { logger.info("[Strategy] 启用 observeAndInjectComments (Vue3)"); hookVue3App(); const targetNode = await isElementLoaded(".reply-list", root); new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type !== "childList") continue; mutation.addedNodes.forEach((node) => { if (!isReplyItem(node)) return; try { insertLocation(node); if (node.className.startsWith("sub")) return; const subReplyListEl = node.querySelector(".sub-reply-list"); if (!subReplyListEl) return; const subReplyList = Array.from(subReplyListEl.children); subReplyList.pop(); subReplyList.forEach(insertLocation); } catch (error) { logger.error("[Hook异常] observeAndInjectComments (Vue3) 处理失败", error); } }); } }).observe(targetNode, { childList: true, subtree: true }); }; var handleOpusRoute = async (url) => { logger.info("[handleOpusRoute] 处理新版专栏:", url); hookLit(); injectArticleLocation(url); }; var findSpaceLocation = (data, visited = new WeakSet()) => { if (!data || typeof data !== "object" || visited.has(data)) return; visited.add(data); if (data.type === "location" && typeof data.title === "string") { return data.title; } for (const key of ["ip_location", "ipLocation", "location"]) { if (typeof data[key] === "string" && data[key]) { return data[key]; } } for (const key of ["space_tag", "space_tag_bottom"]) { if (!Array.isArray(data[key])) continue; const locationTag = data[key].find( (tag) => tag?.type === "location" && typeof tag.title === "string", ); if (locationTag?.title) return locationTag.title; } for (const value of Object.values(data)) { const location = findSpaceLocation(value, visited); if (location) return location; } }; var getSpaceLocationString = (data = _unsafeWindow.__INITIAL_STATE__) => { const location = findSpaceLocation(data) || findSpaceLocation(_unsafeWindow.__BiliUser__); return location ? preprocessLocation(location) : ""; }; var injectSpaceLocation = async (isFreshSpace, data) => { const locationString = getSpaceLocationString(data); if (!locationString) return; const upInfoRootElement = await isElementLoaded( isFreshSpace ? ".upinfo__main" : ".h-inner", ); const upInfoElement = await isElementLoaded( isFreshSpace ? ".upinfo-detail__top" : ".h-basic div", upInfoRootElement, ); if (!upInfoElement) return; let locationElement = upInfoElement.querySelector( ".bili-reveal-space-location", ); if (!locationElement) { locationElement = document.createElement("span"); locationElement.className = "bili-reveal-space-location"; upInfoElement.appendChild(locationElement); logger.incrementIpCount(); } locationElement.textContent = locationString; }; var handleSpaceHomeRoute = async () => { const isFreshSpace = (await isElementLoaded("#biliMainHeader"))?.tagName === "HEADER"; logger.info("[handleSpaceHomeRoute] 是否新版空间:", isFreshSpace); injectSpaceLocation(isFreshSpace); let initialState = _unsafeWindow.__INITIAL_STATE__; if (!initialState) { try { Object.defineProperty(_unsafeWindow, "__INITIAL_STATE__", { configurable: true, get: () => initialState, set: (value) => { initialState = value; injectSpaceLocation(isFreshSpace, value); } }); } catch (error) { logger.warn("[handleSpaceHomeRoute] 无法监听空间页状态:", error); } } (await isElementLoaded(isFreshSpace ? ".nav-tab__item:nth-child(2)" : ".n-dynamic")).addEventListener("click", hookLit, { once: true }); }; var handleDynamicHomeRoute = async () => { const dynBtnText = (await isElementLoaded(".bili-dyn-home--member")).querySelector(".bili-dyn-sidebar__btn")?.textContent; const isNewDyn = dynBtnText ? !dynBtnText.includes("体验新版") : false; logger.info("[handleDynamicHomeRoute] 动态主页是否新版:", isNewDyn, "按钮文字:", dynBtnText); if (isNewDyn) hookLit(); else hookBBComment(); }; var handleDynamicItemRoute = async () => { const isNewDyn = !(await isElementLoaded(".bili-dyn-item")).querySelector(".bili-dyn-item__footer"); logger.info("[handleDynamicItemRoute] 动态详情页是否新版:", isNewDyn); if (isNewDyn) hookLit(); else hookBBComment(); }; var registerRoutes = (router) => { router.serve([ "https://www.bilibili.com/video/", "https://www.bilibili.com/list/", "https://www.bilibili.com/bangumi/play/", "https://www.bilibili.com/cheese/play/", "https://www.bilibili.com/v/topic/detail", "https://manga.bilibili.com/detail/", "https://www.bilibili.com/festival/", "https://live.bilibili.com/" ], hookLit); router.serve("https://www.bilibili.com/blackboard/feed-topic.html", () => hookBBComment()); router.serve("https://www.bilibili.com/blackboard/", () => { hookBBComment({ variation: true }); observeAndInjectComments(); }); router.serve(["https://www.bilibili.com/read/", "https://www.bilibili.com/opus/"], handleOpusRoute); router.serve("https://space.bilibili.com/", hookLit, { endsWith: "dynamic" }); router.serve("https://space.bilibili.com/", handleSpaceHomeRoute); router.serve("https://t.bilibili.com/", handleDynamicHomeRoute, { endsWith: "/" }); router.serve("https://t.bilibili.com/", handleDynamicItemRoute); router.serve("https://www.bilibili.com/blackroom/ban/", () => hookBBComment({ variation: true })); }; var router = new Router(); registerRoutes(router); var initialize = async () => { const settingsPromise = Promise.all([ getStoredValue(REPLACEMENTS_KEY, "{}"), getStoredValue(DEBUG_MODE_KEY, false) ]); var { origin, pathname } = new URL(location.href); var urlWithoutQueryOrHash = `${origin}${pathname}`; console.log("[BiliReveal][Debug] 初始化", { url: urlWithoutQueryOrHash, isDebugMode }); router.match(urlWithoutQueryOrHash); const [rawReplacements, rawDebugMode] = await settingsPromise; replacements = parseReplacements(rawReplacements); isDebugMode = parseBoolean(rawDebugMode); registerConfigMenus(); }; initialize().catch((error) => console.error("[BiliReveal] 初始化失败", error)); })();