Bilibili 直播弹幕发送时间显示
// ==UserScript==
// @name Bilibili 直播弹幕发送时间显示
// @name:en Bilibili live danmaku timestamp display
// @description 显示直播弹幕的发送时间
// @version 0.2.3
// @author Yiero
// @match https://live.bilibili.com/*
// @run-at document-body
// @license GPL-3
// @namespace https://github.com/AliubYiero/TamperMonkeyScripts
// @grant GM_addStyle
// @grant GM_addElement
// ==/UserScript==
function elementWaiter(selector, config = {}) {
const {
parent = document.body,
timeoutPerSecond = 20,
delayPerSecond = 0.5
} = config;
return new Promise((resolve, reject) => {
const returnElement = (selector2) => {
setTimeout(() => {
const element2 = document.querySelector(selector2);
if (!element2) {
reject(new Error("Void Element"));
return;
}
window.dispatchEvent(new CustomEvent("ElementUpdate", { detail: element2 }));
resolve(element2);
}, delayPerSecond * 1e3);
};
const element = document.querySelector(selector);
if (element) {
returnElement(selector);
return;
}
if (MutationObserver) {
const timer2 = timeoutPerSecond && window.setTimeout(() => {
observer.disconnect();
returnElement(selector);
}, timeoutPerSecond * 1e3);
const observeElementCallback = (mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((addNode) => {
if (addNode.nodeType !== Node.ELEMENT_NODE) {
return;
}
const addedElement = addNode;
const element2 = addedElement.matches(selector) ? addedElement : addedElement.querySelector(selector);
if (element2) {
timer2 && clearTimeout(timer2);
returnElement(selector);
}
});
});
};
const observer = new MutationObserver(observeElementCallback);
observer.observe(parent, {
subtree: true,
childList: true
});
return;
}
const intervalDelay = 100;
let intervalCounter = 0;
const maxIntervalCounter = Math.ceil(timeoutPerSecond * 1e3 / intervalDelay);
const timer = window.setInterval(() => {
if (++intervalCounter > maxIntervalCounter) {
clearInterval(timer);
returnElement(selector);
return;
}
const element2 = parent.querySelector(selector);
if (element2) {
clearInterval(timer);
returnElement(selector);
}
}, intervalDelay);
});
}
const handleDanmakuDisplay = async (callback) => {
const observer = new MutationObserver((records) => {
for (const record of records) {
for (let addedNode of record.addedNodes) {
if (addedNode.nodeType !== Node.ELEMENT_NODE) {
continue;
}
const element = addedNode;
callback(element);
}
}
});
const container = await elementWaiter("#chat-items");
observer.observe(container, {
childList: true
});
};
const danmakuTimeStyle = `/* \u8BA9\u5F39\u5E55\u65F6\u957F\u5C45\u53F3 */
.danmaku-item-right {
display: flex !important;
align-items: flex-end;
}
/* \u5F39\u5E55\u65F6\u957F */
.danmaku-time {
margin-left: auto;
font-size: 10px;
color: #9499a0;
}
/* \u53D1\u9001\u5F39\u5E55\u65F6\u7684\u73B0\u5B9E\u65F6\u957F */
.danmaku-time-real {
display: inherit !important;
}
/* \u53D1\u9001\u5F39\u5E55\u65F6\u7684\u76F4\u64AD\u65F6\u957F */
.danmaku-time-live {
display: none !important;
}
.danmaku-item:hover {
& .danmaku-time {
color: #00aeec;
}
& .danmaku-time-real {
display: none !important;
}
& .danmaku-time-live {
display: inherit !important;
}
}
`;
const addDanmakuTimeStyle = () => {
GM_addStyle(danmakuTimeStyle);
};
(() => {
addDanmakuTimeStyle();
handleDanmakuDisplay((element) => {
const { timestamp, ts } = element.dataset;
if (!timestamp) {
return;
}
const danmakuTextNode = element.querySelector(".danmaku-item-right");
if (!danmakuTextNode) {
return;
}
const sendTime = ts === "0" ? Number(timestamp) : Number(timestamp) * 1e3;
const sendTimeStr = new Date(sendTime).toLocaleTimeString();
GM_addElement(danmakuTextNode, "span", {
textContent: sendTimeStr,
class: "danmaku-time danmaku-time-real"
});
const liveTime = sendTime - __NEPTUNE_IS_MY_WAIFU__.roomInitRes.data.live_time * 1e3;
const liveTimeDate = new Date(liveTime);
const padStart = (num) => String(num).padStart(2, "0");
const liveTimeStr = `${padStart(liveTimeDate.getUTCHours())}:${padStart(liveTimeDate.getUTCMinutes())}:${padStart(liveTimeDate.getUTCSeconds())}`;
GM_addElement(danmakuTextNode, "span", {
textContent: liveTimeStr,
class: "danmaku-time danmaku-time-live"
});
});
})();