// ==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 = `\u5FEB\u6377\u6DFB\u52A0\u9AD8\u4EAE\u7528\u6237`; 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); })();