Greasy Fork is available in English.
在视频标题下方插入链接,点击可以通过访问B站API查看视频详情
// ==UserScript==
// @name Bilibili Video Detail
// @name:zh-CN 哔哩哔哩视频详情
// @namespace http://tampermonkey.net/
// @version 1.5
// @description Insert a hyperlink under video title named "info" to get the video details by BV code through Bilibili API.
// @description:zh-CN 在视频标题下方插入链接,点击可以通过访问B站API查看视频详情
// @author philimao
// @match https://www.bilibili.com/video/*
// @match https://www.bilibili.com/opus/*
// @match https://t.bilibili.com/*
// @icon https://www.google.com/s2/favicons?domain=bilibili.com
// @grant unsafeWindow
// @license MIT
// ==/UserScript==
(async function () {
// for video
async function playerReady() {
let counter = 0;
return new Promise((resolve) => {
function wait() {
if (
unsafeWindow.player &&
unsafeWindow.player.isInitialized &&
unsafeWindow.player.isInitialized()
) {
return resolve();
}
if (counter === 10000) {
console.log("[BiliDetail] Player Timeout, Increase Interval");
}
counter++;
const timeout = counter < 10000 ? 500 : 2000;
if (counter % 10000 === 0)
console.log("[BiliDetail] Waiting", counter, timeout);
setTimeout(wait, timeout);
}
setTimeout(wait, 3000);
});
}
// for dynamic
async function titleReady(titleClass) {
let counter = 0;
return new Promise((resolve, reject) => {
const id = setInterval(() => {
if (document.querySelector(titleClass)) resolve(clearInterval(id));
if (counter > 50) {
reject(clearInterval(id));
}
counter++;
}, 200);
});
}
function setSessionSpeed(speed) {
const data = JSON.parse(
window.sessionStorage.getItem("bilibili_player_settings")
);
data.video_status.videospeed = speed;
window.sessionStorage.setItem(
"bilibili_player_settings",
JSON.stringify(data)
);
}
function injectSpeedIcon() {
const video = document.querySelector("video");
const playerStyle = document.querySelector(".bpx-player-container") ? 0 : 1;
let ul;
if (playerStyle) ul = document.querySelector("ul.bilibili-player-video-btn-speed-menu");
else ul = document.querySelector("ul.bpx-player-ctrl-playbackrate-menu");
if (!ul) return console.log("[BiliDetail] Speed Icon Not Detected");
let x3 = playerStyle ?
document.querySelector(".bilibili-player-video-btn-speed-menu-list[data-value='3']") :
document.querySelector(".bpx-player-ctrl-playbackrate-menu-item[data-value='3']");
if (x3) return;
const sp3 = document.createElement("li");
sp3.innerText = "3.0x";
if (playerStyle) sp3.className = "bilibili-player-video-btn-speed-menu-list ";
else sp3.className = "bpx-player-ctrl-playbackrate-menu-item ";
sp3.setAttribute("data-value", "3");
sp3.addEventListener("click", () => {
if (playerStyle) sp3.classList.add("bilibili-player-active");
else sp3.classList.add("bpx-state-active");
video.playbackRate = 3;
setSessionSpeed(3);
// showSpeed.innerText = "3.0x";
});
const sp4 = document.createElement("li");
sp4.innerText = "4.0x";
if (playerStyle) sp4.className = "bilibili-player-video-btn-speed-menu-list ";
else sp4.className = "bpx-player-ctrl-playbackrate-menu-item ";
sp4.setAttribute("data-value", "4");
sp4.addEventListener("click", () => {
if (playerStyle) sp4.classList.add("bilibili-player-active");
else sp4.classList.add("bpx-state-active");
video.playbackRate = 4;
setSessionSpeed(4);
// showSpeed.innerText = "4.0x";
});
ul.insertBefore(sp3, ul.firstElementChild);
ul.insertBefore(sp4, ul.firstElementChild);
document
.querySelectorAll(".bilibili-player-video-btn-speed-menu-list")
.forEach((el) =>
el.addEventListener("click", () => {
document
.querySelectorAll(
playerStyle ?
".bilibili-player-video-btn-speed-menu-list.bilibili-player-active" :
".bpx-player-ctrl-playbackrate-menu-item.bpx-state-active"
)
.forEach((active) => {
// 切回1.0x时有问题
if (active && active.innerHTML !== el.innerHTML) {
if (playerStyle) active.classList.remove("bilibili-player-active");
else active.classList.remove("bpx-state-active");
}
});
})
);
}
function registerPageObserver() {
const target = unsafeWindow.__INITIAL_STATE__;
let videoData = target.videoData;
const desc = Object.getOwnPropertyDescriptor(target, "videoData");
const vueHook = desc.set;
Object.defineProperty(target, "videoData", {
get: desc.get || (() => videoData),
set(data) {
videoData = data;
injectSpeedIcon();
if (vueHook) vueHook.call(this, data);
},
enumerable: true,
configurable: true,
});
}
const info = document.createElement("a");
info.id = "custom_info";
info.innerText = "info";
info.style.height = "16px";
info.style.color = "#999";
info.style.marginLeft = "12px";
info.target = "_blank";
let titleClass, isVideo;
if (window.location.href.includes("video")) {
const match = window.location.href.match(/BV[^?#&\/]*/);
if (match) {
info.href =
"https://api.bilibili.com/x/web-interface/view?bvid=" + match[0];
titleClass = ".video-info-detail-list";
isVideo = true;
info.style.paddingBottom = "3px";
}
} else {
const match = window.location.href
.split("spm_id_from")[0]
.split("tab")[0]
.match(/\d+/);
if (match) {
info.href =
"https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=" +
match[0];
if (window.location.href.includes("opus")) titleClass = ".opus-module-author__pub"
else titleClass = ".bili-dyn-time";
}
}
if (titleClass) {
if (isVideo) await playerReady();
else await titleReady(titleClass);
const bvid = unsafeWindow?.__INITIAL_STATE__?.bvid;
if (bvid) info.innerText = bvid;
document.querySelector(titleClass).appendChild(info);
console.log("[BiliDetail] Custom Info Icon Injected.", bvid);
if (isVideo) {
injectSpeedIcon();
registerPageObserver();
console.log("[BiliDetail] Speed Control Injected.");
const observer = new MutationObserver((mutationList, observer) => {
console.log(
"[BiliDetail] New Custom Info Icon Injected",
bvid
);
info.innerText = bvid;
info.href =
"https://api.bilibili.com/x/web-interface/view?bvid=" +
bvid;
document.querySelector(titleClass).appendChild(info);
});
observer.observe(document.querySelector(".video-info").children[0], {
attributes: true,
});
}
}
})();