// ==UserScript== // @name 哔哩哔哩视频页面常驻显示AV/BV号[已完全重构,支持显示分P标题] // @namespace ckylin-bilibili-display-video-id // @version 1.8 // @description 始终在哔哩哔哩视频页面标题下方显示当前视频号,默认显示AV号,右键切换为BV号,单击弹窗可复制链接 // @author CKylinMC // @match https://www.bilibili.com/video* // @match https://www.bilibili.com/medialist/play/* // @resource cktools https://greasyfork.org/scripts/429720-cktools/code/CKTools.js?version=967994 // @resource popjs https://cdn.jsdelivr.net/gh/CKylinMC/PopNotify.js@master/PopNotify.js // @resource popcss https://cdn.jsdelivr.net/gh/CKylinMC/PopNotify.js@master/PopNotify.css // @grant unsafeWindow // @grant GM_setValue // @grant GM_getValue // @grant GM_getResourceText // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @license GPL-3.0-only // @downloadURL none // ==/UserScript== (function () { function applyResource(){ if(document.querySelector("#cktools"))return; const cktools = document.createElement("script"); cktools.id = "cktools"; cktools.innerHTML = GM_getResourceText("cktools"); document.head.appendChild(cktools); if(document.querySelector("#popjs"))return; const popjs = document.createElement("script"); popjs.id = "popjs"; popjs.innerHTML = GM_getResourceText("popjs"); document.head.appendChild(popjs); if(document.querySelector("#popcss"))return; const popcss = document.createElement("style"); popcss.id = "cktools"; popcss.innerHTML = GM_getResourceText("popcss"); document.head.appendChild(popcss); const popcsspatch = document.createElement("style"); popcsspatch.id = "popcsspatchforbilibilievolved"; popcsspatch.innerHTML=` div.popNotifyUnitFrame{z-index:110000!important;} `; document.head.appendChild(popcsspatch); } applyResource(); const wait = (t) => new Promise(r => setTimeout(r, t)); const waitForPageVisible = async () => { return document.hidden && new Promise(r => document.addEventListener("visibilitychange", r)) } const log = (...m) => console.log('[ShowAV]', ...m); const getAPI = (bvid) => fetch('https://api.bilibili.com/x/web-interface/view?bvid=' + bvid).then(raw => raw.json()); const getAidAPI = (aid) => fetch('https://api.bilibili.com/x/web-interface/view?aid=' + aid).then(raw => raw.json()); const config = { showAv: true, defaultAv: true, showPn: true, firstTimeLoad: true, showInNewLine: false, showCid: false, showCate: false, vduration: 0 }; const menuId = { showAv: -1, defaultAv: -1, showInNewLine:-1, showPn: -1, showCate:-1, showCid: -1, }; let infos = {}; async function initScript(flag = false) { if (menuId.showAv != -1) GM_unregisterMenuCommand(menuId.showAv); if (menuId.defaultAv != -1) GM_unregisterMenuCommand(menuId.defaultAv); if (menuId.showPn != -1) GM_unregisterMenuCommand(menuId.showPn); if (menuId.showInNewLine != -1) GM_unregisterMenuCommand(menuId.showInNewLine); if (menuId.showCid != -1) GM_unregisterMenuCommand(menuId.showCid); if (menuId.showCate != -1) GM_unregisterMenuCommand(menuId.showCate); if (!(await GM_getValue("inited"))) { await GM_setValue("showAv", true); await GM_setValue("defaultAv", true); await GM_setValue("showPn", true); await GM_setValue("showInNewLine", false); await GM_setValue("showCid", false); await GM_setValue("showCate", false); await GM_setValue("inited", true); } if ((await GM_getValue("showAv"))) { config.showAv = true; menuId.showAv = GM_registerMenuCommand("隐藏视频编号[当前显示]", async () => { await GM_setValue("showAv", false); initScript(true); }); } else { config.showAv = false; menuId.showAv = GM_registerMenuCommand("显示视频编号[当前隐藏]", async () => { await GM_setValue("showAv", true); initScript(true); }); } if ((await GM_getValue("defaultAv"))) { config.defaultAv = true; menuId.defaultAv = GM_registerMenuCommand("默认显示BV号[当前显示av号]", async () => { await GM_setValue("defaultAv", false); initScript(true); }); } else { config.defaultAv = false; menuId.defaultAv = GM_registerMenuCommand("默认显示av号[当前显示BV号]", async () => { await GM_setValue("defaultAv", true); initScript(true); }); } if ((await GM_getValue("showPn"))) { config.showPn = true; menuId.showPn = GM_registerMenuCommand("隐藏视频分P信息[当前显示]", async () => { await GM_setValue("showPn", false); initScript(true); }); } else { config.showPn = false; menuId.showPn = GM_registerMenuCommand("显示视频分P信息[当前隐藏]", async () => { await GM_setValue("showPn", true); initScript(true); }); } if ((await GM_getValue("showCate"))) { config.showCate = true; menuId.showCate = GM_registerMenuCommand("隐藏视频分区信息[当前显示]", async () => { await GM_setValue("showCate", false); initScript(true); }); } else { config.showCate = false; menuId.showCate = GM_registerMenuCommand("显示视频分区信息[当前隐藏]", async () => { await GM_setValue("showCate", true); initScript(true); }); } if ((await GM_getValue("showCid"))) { config.showCid = true; menuId.showCid = GM_registerMenuCommand("隐藏视频CID信息[当前显示]", async () => { await GM_setValue("showCid", false); initScript(true); }); } else { config.showCid = false; menuId.showCid = GM_registerMenuCommand("显示视频CID信息[当前隐藏]", async () => { await GM_setValue("showCid", true); initScript(true); }); } if ((await GM_getValue("showInNewLine"))) { config.showInNewLine = true; menuId.showInNewLine = GM_registerMenuCommand("显示模式: 换行 [点击切换]", async () => { await GM_setValue("showInNewLine", false); let old = document.querySelector("#bilibiliShowInfos") if(old)old.remove(); initScript(true); }); } else { config.showInNewLine = false; menuId.showInNewLine = GM_registerMenuCommand("显示模式: 附加 [点击切换]", async () => { await GM_setValue("showInNewLine", true); let old = document.querySelector("#bilibiliShowInfos") if(old)old.remove(); initScript(true); }); } tryInject(flag); } async function playerReady() { let i = 150; while (--i > 0) { await wait(100); if (!('player' in unsafeWindow)) continue; if (!('isInitialized' in unsafeWindow.player)) continue; if (!unsafeWindow.player.isInitialized()) continue; break; } if (i < 0) return false; await waitForPageVisible(); while (1) { await wait(200); if (document.querySelector(".bilibili-player-video-control-wrap")) return true; } } async function waitForDom(q) { let i = 50; let dom; while (--i >= 0) { if (dom = document.querySelector(q)) break; await wait(100); } return dom; } function getUrlParam(key) { return (new URL(location.href)).searchParams.get(key); } function getOrNew(id, parent,) { let marginDirection = config.showInNewLine ? "Right" : "Left"; let target = document.querySelector("#" + id); if (!target) { target = document.createElement("span"); target.id = id; target.style['margin'+marginDirection] = "16px"; parent.appendChild(target); } return target; } async function getPlayerSeeks() { const video = await waitForDom(".bilibili-player-video video"); let seconds = 0; if (video) { seconds = Math.floor(video.currentTime); } if (seconds == 0) { let fromParam = getUrlParam("t") || 0; return fromParam; } else return seconds; } async function registerVideoChangeHandler() { const video = await waitForDom(".bilibili-player-video video"); if (!video) return; const observer = new MutationObserver(async e => { if (e[0].target.src) { tryInject(true); } }); observer.observe(video, {attribute: true, attributes: true, childList: false}); } function getPageFromCid(cid, infos) { if (!cid || !infos || !infos.pages) return 1; let pages = infos.pages; if (pages.length == 1) return 1; let page; for (page of pages) { if (!page) continue; if (page.cid == cid) return page.page; } return 1; } async function tryInject(flag) { if (flag && !config.showAv && !config.showPn) return log('Terminated because no option is enabled.'); if (!(await playerReady())) return log('Can not load player in time.'); if (config.firstTimeLoad) { registerVideoChangeHandler(); config.firstTimeLoad = false; } if (location.pathname.startsWith("/medialist")) { let aid = unsafeWindow.aid; if (!aid) { console.log("SHOWAV", "Variable 'aid' is not available from unsafeWindow."); let activeVideo = await waitForDom(".player-auxiliary-playlist-item-active"); aid = activeVideo.getAttribute("data-aid"); //console.log("SHOWAV",activeVideo); } console.log("SHOWAV", aid); let apidata = await getAidAPI(aid); //console.log("SHOWAV",apidata); infos = apidata.data; } else { if (flag) infos = (await getAPI(unsafeWindow.bvid)).data; else infos = unsafeWindow.vd; } infos.p = getUrlParam("p") || getPageFromCid(unsafeWindow.cid, infos); const av_infobar = await waitForDom(".video-data"); if (!av_infobar) return log('Can not load info-bar in time.'); let av_root; if(config.showInNewLine){ av_root = getOrNew("bilibiliShowInfos",av_infobar.parentElement); }else{ let rootel = document.querySelector("#bilibiliShowInfos"); if(!rootel){ rootel = document.createElement("span"); rootel.id = "bilibiliShowInfos"; av_infobar.appendChild(rootel); } av_root = rootel; } //const av_root = getOrNew("bilibiliShowInfos",av_infobar); //const av_root = av_infobar; const cate_span = getOrNew("bilibiliShowCate", av_root); if (config.showCate) { cate_span.style.textOverflow = "ellipsis"; cate_span.style.whiteSpace = "nowarp"; cate_span.style.overflow = "hidden"; cate_span.title = "分区:"+infos.tname; cate_span.innerText = "分区:"+infos.tname; } else cate_span.remove(); const av_span = getOrNew("bilibiliShowAV", av_root); if (config.showAv) { if (config.defaultAv) av_span.innerText = 'av' + infos.aid; else av_span.innerText = infos.bvid; av_span.style.overflow = "hidden"; av_span.oncontextmenu = e => { if (e.target.innerText.startsWith('av')) e.target.innerText = infos.bvid; else av_span.innerText = 'av' + infos.aid; e.preventDefault(); } const video = await waitForDom("video"); if (video) { config.vduration = Math.floor(video.duration); } const avspanHC = new CKTools.HoldClick(av_span); avspanHC.onclick(async e=>{ let url = new URL(location.protocol + "//" + location.hostname + "/video/" + e.target.innerText); infos.p == 1 || url.searchParams.append("p", infos.p); let t = await getPlayerSeeks(); if (t && t != "0" && t != ("" + config.vduration)) url.searchParams.append("t", t); copy(url); popNotify.success("完整地址复制成功", url); }); avspanHC.onhold(async e=>{ let url = new URL(location.protocol + "//" + location.hostname + "/video/" + e.target.innerText); infos.p == 1 || url.searchParams.append("p", infos.p); let vidurl = new URL(url); let t = await getPlayerSeeks(); if (t && t != "0" && t != ("" + config.vduration)) url.searchParams.append("t", t); CKTools.modal.alertModal("高级复制",` 点击输入框可以快速复制
当前地址
含视频进度地址(仅在播放时提供)
快速分享
快速分享(含视频进度)
MarkDown格式
BBCode格式


AV号
BV号
资源CID
`,"关闭"); }); } else av_span.remove(); const cid_span = getOrNew("bilibiliShowCID", av_root); if (config.showCid) { cid_span.style.textOverflow = "ellipsis"; cid_span.style.whiteSpace = "nowarp"; cid_span.style.overflow = "hidden"; cid_span.title = "CID:"+infos.cid; cid_span.innerText = "CID:"+infos.cid; const cidspanHC = new CKTools.HoldClick(cid_span); cidspanHC.onclick(()=>{ copy(currentPageName); popNotify.success("CID复制成功", infos.cid); }); cidspanHC.onhold(()=>{ CKTools.modal.alertModal("CID信息",` `,"关闭"); }); } else cid_span.remove(); const pn_span = getOrNew("bilibiliShowPN", av_root); if (config.showPn) { const videoData = infos; if (!videoData) return; let part = { part: 'P' + infos.p } try { part = videoData.pages[infos.p - 1]; } catch (e) { part = videoData.pages[0]; } let currentPageName = part.part.length ? `《${part.part}》` : ''; let currentPageNum; let delimiters; if (videoData.videos != 1) { currentPageNum = `P ${infos.p}/${videoData.videos}`; delimiters = ["\n", " "]; } else { currentPageNum = ""; delimiters = ["", ""]; } pn_span.style.textOverflow = "ellipsis"; pn_span.style.whiteSpace = "nowarp"; pn_span.style.overflow = "hidden"; pn_span.title = currentPageNum + delimiters[0] + currentPageName pn_span.innerText = currentPageNum + delimiters[1] + currentPageName; const pnspanHC = new CKTools.HoldClick(pn_span); pnspanHC.onclick(()=>{ copy(currentPageName); popNotify.success("分P标题复制成功", currentPageName); }); pnspanHC.onhold(()=>{ CKTools.modal.alertModal("分P标题",` `,"关闭"); }); } else pn_span.remove(); log('infos',infos); } const copy = function copy(text) { if (!navigator.clipboard) { prompt('请手动复制',text); return; } navigator.clipboard.writeText(text).then(function() { log('Copy OK'); }, function(err) { log('Auto Copy Failed:',err); prompt('请手动复制',text); }); } unsafeWindow.showav_fastcopy = (el)=>{ copy(el.value); popNotify.success("复制成功", el.value); } initScript(false); })();