Bilibili 直播高亮指定用户的弹幕
// ==UserScript==
// @name Bilibili 直播高亮指定用户的弹幕
// @name:en Bilibili Live Follow User Danmaku Highlight
// @description 高亮指定用户的弹幕.
// @version 0.2.0
// @author Yiero
// @match https://live.bilibili.com/*
// @license GPL-3
// @namespace https://github.com/AliubYiero/TamperMonkeyScripts
// @grant GM_addStyle
// @grant GM_addElement
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
/* ==UserConfig==
配置项:
userListContent:
title: 指定用户列表
description: '多个用户换行, 一行为一个用户'
type: textarea
==/UserConfig== */
const getStorageUser = () => {
const userListContent = GM_getValue("\u914D\u7F6E\u9879.userListContent", "");
const userIdList = [];
userListContent.split("\n").forEach((user) => {
user = user.trim();
const userUid = user.match(/^\d+/);
if (userUid) {
userIdList.push(Number(userUid[0]));
}
});
return userIdList;
};
const addStyle = (userIdList, styleNode) => {
if (styleNode) {
styleNode.remove();
}
const selectorList = userIdList.map((userId) => `.danmaku-item[data-uid="${userId}"]`);
const styleContent = selectorList.join(", ") + " { background: #FFE1C7 !important; border-radius: 5px; margin: 8px 0; }";
return GM_addStyle(styleContent);
};
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 addStorageUser = (appendText) => {
const userListContent = GM_getValue("\u914D\u7F6E\u9879.userListContent", "").trim();
GM_setValue("\u914D\u7F6E\u9879.userListContent", userListContent + "\n" + appendText);
};
const addQuickAddMenu = async (styleNode) => {
const danmakuButtonListContainer = await elementWaiter(".danmaku-menu .none-select");
const dataIndex = Object.keys(danmakuButtonListContainer.dataset).find((content) => content.startsWith("v"));
if (!dataIndex) {
return;
}
const quickAddButton = GM_addElement(danmakuButtonListContainer, "div", {
class: "quick-add-this-guy go-space",
[`data-${dataIndex}`]: ""
});
quickAddButton.innerHTML = `<a data-${dataIndex} class="clickable bili-link pointer"><span data-${dataIndex}>\u5FEB\u6377\u6DFB\u52A0\u9AD8\u4EAE\u7528\u6237</span></a>`;
quickAddButton.addEventListener("click", async () => {
const danmakuMenuContainer = await elementWaiter(".danmaku-menu", {
delayPerSecond: 0
});
const nicknameContainer = danmakuMenuContainer.querySelector(".common-nickname-wrapper span");
if (!nicknameContainer) {
console.error("\u83B7\u53D6\u6307\u5B9A\u7528\u6237\u6635\u79F0\u5931\u8D25");
return;
}
const nickname = nicknameContainer.textContent;
if (!nickname) {
console.error("\u83B7\u53D6\u6307\u5B9A\u7528\u6237\u6635\u79F0\u5931\u8D25");
return;
}
const sendDanmakuContainer = document.querySelector(`[data-uname="${nickname}"]`);
if (!sendDanmakuContainer) {
console.error("\u83B7\u53D6\u6307\u5B9A\u7528\u6237\u53D1\u9001\u7684\u5F39\u5E55\u5931\u8D25");
return;
}
const uid = sendDanmakuContainer.dataset.uid;
if (!uid) {
console.error("\u83B7\u53D6\u6307\u5B9A\u7528\u6237 uid \u5931\u8D25");
return;
}
addStorageUser(`${uid} // ${nickname}`);
const userIdList = getStorageUser();
addStyle(userIdList, styleNode);
document.body.click();
});
};
(async () => {
const userIdList = getStorageUser();
const styleNode = addStyle(userIdList);
await addQuickAddMenu(styleNode);
})();