bilibili(b站/哔哩哔哩)根据弹幕查询发送者信息
"use strict";// ==UserScript==// @name Bilibili弹幕查询发送者// @namespace https://github.com/qianjiachun// @version 2025.03.24.01// @icon https://static.hdslb.com/mobile/img/512.png// @description bilibili(b站/哔哩哔哩)根据弹幕查询发送者信息// @author 小淳// @match *://www.bilibili.com/video/*// @match *://www.bilibili.com/festival/*// @match *://www.bilibili.com/bangumi/play/*// @match *://www.bilibili.com/cheese/play/*// @grant unsafeWindow// @grant GM_xmlhttpRequest// @require https://lib.baomitu.com/protobufjs/6.11.2/protobuf.min.js// @connect bilibili.com// @run-at document-start// @license MIT// ==/UserScript==unsafeWindow.requestHookList = [];unsafeWindow.requestHookCallback = function (xhr) {if (xhr.responseURL.includes("/seg.so")) {let data = new Uint8Array(xhr.response);protobuf.loadFromString("dm", protoStr).then(root => {let dmList = root.lookupType("dm.dmList").decode(data);handleDanmakuList(dmList.list);})}};var originalOpen = XMLHttpRequest.prototype.open;var originalSend = XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open = function () {this._url = arguments[1];originalOpen.apply(this, arguments);};XMLHttpRequest.prototype.send = function () {var self = this;this.addEventListener("load", function () {if (self.readyState === 4 && self.status === 200) {unsafeWindow.requestHookList.push(self);unsafeWindow.requestHookCallback(self);}});originalSend.apply(this, arguments);};function init() {init_Router();}function initStyles() {let style = document.createElement("style");style.appendChild(document.createTextNode(`.senderinfo__wrap { width: 280px; min-height: 110px; height: auto; z-index: 1; background-color: white; border-radius: 8px; box-shadow: 0 0 30px 2px rgb(0 0 0 / 10%); position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); max-height: 300px; box-sizing: border-box; padding: 5px; overflow: auto;}.senderinfo__card { margin-bottom: 5px; margin-top: 5px;}.senderinfo__github { width: 16px; height: 16px; position: absolute;}.senderinfo__close { margin-right: 5px; margin-top: 5px; cursor: pointer; position: absolute; margin-left: 260px; margin-top: 0px;}.senderinfo__avatar { width: 100%; height: 70px; overflow: hidden; text-align: center;}.senderinfo__img-loding { width: 70px; height: 70px; border-radius: 50%; background-color: rgb(225,232,238); display: inline-block;}.senderinfo__avatar img { width: 70px; height: 70px; border-radius: 50%;}.senderinfo__user { text-align: center; margin-top: 10px;}.senderinfo__name { font-size: 16px; font-weight: bold; color: black;}.senderinfo__name-loading { width: 100px; height: 16px; background-color: rgb(225,232,238); display: inline-block;}.senderinfo__level { line-height: 17px; margin-left: 5px; position: absolute; color: #99a2aa;}.senderinfo__sign { color: #99a2aa; word-break: break-all; word-wrap: break-word; margin-top: 10px; text-align: center; line-height: 12px;}.senderinfo__sign-loading { width: 150px; height: 16px; background-color: rgb(225,232,238); display: inline-block;}.senderinfo__wrap::-webkit-scrollbar { width: 4px; }.senderinfo__wrap::-webkit-scrollbar-thumb { border-radius: 10px; box-shadow: inset 0 0 5px rgba(0,0,0,0.2); background: rgba(0,0,0,0.2);}.senderinfo__wrap::-webkit-scrollbar-track { box-shadow: inset 0 0 5px rgba(0,0,0,0.2); border-radius: 0; background: rgba(0,0,0,0.1);}`));document.head.appendChild(style);}let allDanmaku = {}const DOM_MENU_MAIN = ".player-auxiliary-context-menu-container"const DOM_MENU_BANGUMI = ".bpx-player-contextmenu.bpx-player-active"const DOM_MENU_CHEESE = ".bpx-player-contextmenu.bpx-player-active"function formatSeconds(value) {var secondTime = parseInt(value / 1000); // 秒var minuteTime = 0; // 分if (secondTime > 60) {minuteTime = parseInt(secondTime / 60);secondTime = parseInt(secondTime % 60);}var r###lt = "" + (parseInt(secondTime) < 10 ? "0" + parseInt(secondTime) : parseInt(secondTime));// if (minuteTime > 0) {r###lt = "" + (parseInt(minuteTime) < 10 ? "0" + parseInt(minuteTime) : parseInt(minuteTime)) + ":" + r###lt;// }return r###lt;}function toSecond(e) {var time = e;var len = time.split(':')let min = "";let hour = "";let sec = "";if (len.length == 3) {hour = time.split(':')[0];min = time.split(':')[1];sec = time.split(':')[2];return Number(hour * 3600) + Number(min * 60) + Number(sec);}if (len.length == 2) {min = time.split(':')[0];sec = time.split(':')[1];return Number(min * 60) + Number(sec);}if (len.length == 1) {sec = time.split(':')[0];return Number(sec);}// var hour = time.split(':')[0];// var min = time.split(':')[1];// var sec = time.split(':')[2];// return Number(hour*3600) + Number(min*60) + Number(sec);}function getStrMiddle(str, before, after) {let m = str.match(new RegExp(before + '(.*?)' + after));return m ? m[1] : false;}let protoStr = `syntax = "proto3";package dm;message dmList{repeated dmItem list=1;}message dmItem{int64 id = 1;int32 progress = 2;int32 mode = 3;int32 fontsize = 4;uint32 color = 5;string midHash = 6;string content = 7;int64 ctime = 8;int32 weight = 9;string action = 10;int32 pool = 11;string idStr = 12;}`;let videoCid = "";function initPkg_CollectAllDanmaku() {initPkg_CollectAllDanmaku_Dom();initPkg_CollectAllDanmaku_Func();}function initPkg_CollectAllDanmaku_Dom() {}function initPkg_CollectAllDanmaku_Func() {collectAllDanmaku(1);}function collectAllDanmaku(page) {if (page > 30) {// 熔断return;}fetch(`https://api.bilibili.com/x/v2/dm/web/seg.so?type=1&oid=${videoCid}&segment_index=${page}`).then(response => {return response.arrayBuffer();}).then(ret => {let data = new Uint8Array(ret);protobuf.loadFromString("dm", protoStr).then(root => {let dmList = root.lookupType("dm.dmList").decode(data);handleDanmakuList(dmList.list);})if (ret.byteLength > 0) {collectAllDanmaku(page + 1);}}).catch(err => {console.log(err);})}function handleDanmakuList(list) {for (let i = 0; i < list.length; i++) {let item = list[i];let content = item.content;let progress = "progress" in item ? item.progress : 0;let keyName = `${content}|${parseInt(progress / 1000)}`;if (keyName in allDanmaku) {if (allDanmaku[keyName].includes(item.midHash)) continueallDanmaku[keyName].push(item.midHash);} else {allDanmaku[keyName] = [item.midHash];}}}async function refreshAllDanmaku() {let route = getRoute();switch (route) {case 0:// 在普通页面videoCid = getVideoCid_Main();initPkg_CollectAllDanmaku();break;case 1:// 在番剧页面videoCid = getVideoCid_Bangumi();initPkg_CollectAllDanmaku();break;case 2:// 在课程页面videoCid = await getVideoCid_Cheese();initPkg_CollectAllDanmaku();break;default:videoCid = getVideoCid_Main();initPkg_CollectAllDanmaku();break;}}function initPkg_Main() {initPkg_Main_Dom();initPkg_Main_Func();}function initPkg_Main_Dom() {}function initPkg_Main_Func() {let selectedDom = null;document.getElementById("danmukuBox").addEventListener("contextmenu", (e) => {let path = e.path || (e.composedPath && e.composedPath());setTimeout(() => {selectedDom = getSelectedDom(path);let dom = document.querySelector(DOM_MENU_MAIN) || document.querySelector(DOM_MENU_BANGUMI) || document.querySelector(DOM_MENU_CHEESE);if (dom) {if (dom.querySelector("#query-sender")) {return;}removeSenderInfoWrap();let ul = dom.querySelector("ul");let li = document.createElement("li");li.id = "query-sender";li.className = "context-line context-menu-function";li.innerHTML = `<a style="color:#444" class="context-menu-a js-action" href="javascript:void(0);" data-disabled="0">查看发送者</a>`;if (ul) {ul.appendChild(li);} else {dom.appendChild(li);}li.addEventListener("click", () => {if (selectedDom) {renderSenderInfoWrap();showSelectedInfo(selectedDom);}})}}, 0);}, true)}function getSelectedDom(path) {let ret = null;for (let i = 0; i < path.length; i++) {if (path[i].className && (path[i].className.includes("danmaku-info-row") || path[i].className.includes("dm-info-row"))) {ret = path[i];break;}}return ret;}function showSelectedInfo(dom) {let domTime = dom.getElementsByClassName("danmaku-info-time")[0];let domContent = dom.getElementsByClassName("danmaku-info-danmaku")[0];let progress = domTime ? domTime.innerText :dom.getElementsByClassName("dm-info-time")[0].innerText;let content = domContent ? domContent.title : dom.getElementsByClassName("dm-info-dm")[0].title;let keyName = `${content}|${toSecond(progress)}`;let uidList = [];if (keyName in allDanmaku) {for (let i = 0; i < allDanmaku[keyName].length; i++) {let uhash = allDanmaku[keyName][i];let list = uhash2uid(uhash);uidList.push(...list);}renderSenderInfoCard(uidList);}}function renderSenderInfoWrap() {removeSenderInfoWrap();let div = document.createElement("div");div.className = "senderinfo__wrap";div.innerHTML = `<div class="senderinfo__close">X</div><a title="点个Star吧~" href="https://github.com/qianjiachun/bilibili-danmaku-tracker" target="_blank" class="senderinfo__github"><svg t="1639304975096" class="icon" viewBox="0 0 #### ####" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2323" width="16" height="16"><path d="M512 42.666667A464.64 464.64 0 0 0 42.666667 502.186667 460.373333 460.373333 0 0 0 363.52 938.666667c23.466667 4.266667 32-9.813333 32-22.186667v-78.08c-130.56 27.733333-158.293333-61.44-158.293333-61.44a122.026667 122.026667 0 0 0-52.053334-67.413333c-42.666667-28.16 3.413333-27.733333 3.413334-27.733334a98.56 98.56 0 0 1 71.68 47.36 101.12 101.12 0 0 0 136.533333 37.973334 99.413333 99.413333 0 0 1 29.866667-61.44c-104.106667-11.52-213.333333-50.773333-213.333334-226.986667a177.066667 177.066667 0 0 1 47.36-124.16 161.28 161.28 0 0 1 4.693334-121.173333s39.68-12.373333 128 46.933333a455.68 455.68 0 0 1 234.666666 0c89.6-59.306667 128-46.933333 128-46.933333a161.28 161.28 0 0 1 4.693334 121.173333A177.066667 177.066667 0 0 1 810.666667 477.866667c0 176.64-110.08 215.466667-213.333334 226.986666a106.666667 106.666667 0 0 1 32 85.333334v125.866666c0 14.933333 8.533333 26.88 32 22.186667A460.8 460.8 0 0 0 981.333333 502.186667 464.64 464.64 0 0 0 512 42.666667" p-id="2324"></path></svg></a><div style="display:flex;justify-content:center;">请先左键选中弹幕再右键查询</div><div class="senderinfo__content"><div class="senderinfo__loading"><div class="senderinfo__card"><div class="senderinfo__avatar"><div class="senderinfo__img-loding"></div></div><div class="senderinfo__user"><span class="senderinfo__name-loading"></span></div><div class="senderinfo__sign"><span class="senderinfo__sign-loading"></span></div></div></div></div>`let b = document.getElementsByClassName("bui-collapse-wrap")[0];b.insertBefore(div, b.childNodes[0]);document.getElementsByClassName("senderinfo__close")[0].addEventListener("click", () => {div.remove();})}function renderSenderInfoCard(uidList) {let domCard = document.getElementsByClassName("senderinfo__content")[0];if (!domCard) {return;}const uidSet = new Set(uidList);function noPersonMatch(uid) {if (uidSet.size === 1) {domCard.innerHTML += `<h1><br><br><br>匹配不到发送者,可能已经删号也可能是超过10位uid</h1>`;}uidSet.delete(uid);}let domLoading = document.getElementsByClassName("senderinfo__loading")[0];for (let i = 0; i < uidList.length; i++) {let uid = uidList[i];// fetch(`https://api.bilibili.com/x/space/acc/info?mid=${uid}&token=&platform=web&jsonp=jsonp`)// .then(res => res.json())// .then(ret => {// const {data} = ret;// domLoading.style.display = "none";// let head = data.face;// let name = data.name;// let sign = data.sign// // 此时arr[0]为名字 arr[1]为签名// let html = `// <div class="senderinfo__card">// <div class="senderinfo__avatar">// <a href="https://space.bilibili.com/${uid}" target="_blank"><img src="${head}" /></a>// </div>// <div class="senderinfo__user">// <a href="https://space.bilibili.com/${uid}" target="_blank"><span class="senderinfo__name">${name}</span></a>// </div>// <div class="senderinfo__sign">${sign}</div>// </div>// `// domCard.innerHTML += html;// })GM_xmlhttpRequest({method: "GET",url: "https://m.bilibili.com/space/" + uid,headers: {"cookie": document.cookie,"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/105.0.0.0"},responseType: "text",onload: function (response) {domLoading.style.display = "none";let ret = response.response;let parser = new DOMParser();let doc = parser.parseFromString(ret, "text/html");let name;let headImg;let head;if (doc) {name = String(getStrMiddle(ret, `content="哔哩哔哩`, "的个人空间"));headImg = doc.querySelector(".m-space-info")?.querySelector(".face")?.querySelector("img");head = String(headImg?.src || "");}if (!doc || !headImg || !name || name === "" || name === "false") return noPersonMatch(uid);let sign = String(doc.querySelector(".desc").querySelector(".content").innerHTML);let html = `<div class="senderinfo__card"><div class="senderinfo__avatar"><a href="https://space.bilibili.com/${uid}" target="_blank"><img src="${head}" /></a></div><div class="senderinfo__user"><a href="https://space.bilibili.com/${uid}" target="_blank"><span class="senderinfo__name">${name}</span></a></div><div class="senderinfo__sign">${sign}</div></div>`;domCard.innerHTML += html;},onerror: function (error) {noPersonMatch(uid);console.log(error);}});}}function removeSenderInfoWrap() {let domWrapList = document.getElementsByClassName("senderinfo__wrap");if (domWrapList.length > 0) {domWrapList[0].remove();}}function make_crc32_cracker() {var POLY = 0xedb88320;var crc32_table = new Uint32Array(256);function make_table() {for (var i = 0; i < 256; i++) {var crc = i;for (var _ = 0; _ < 8; _++) {if (crc & 1) {crc = ((crc >>> 1) ^ POLY) >>> 0;} else {crc = crc >>> 1;}}crc32_table[i] = crc;}}make_table();function update_crc(by, crc) {return ((crc >>> 8) ^ crc32_table[(crc & 0xff) ^ by]) >>> 0;}function compute(arr, init) {var crc = init || 0;for (var i = 0; i < arr.length; i++) {crc = update_crc(arr[i], crc);}return crc;}function make_rainbow(N) {var rainbow = new Uint32Array(N);for (var i = 0; i < N; i++) {var arr = [].slice.call(i.toString()).map(Number);rainbow[i] = compute(arr);}return rainbow;}var rainbow_0 = make_rainbow(100000);var five_zeros = Array(5).fill(0);var rainbow_1 = rainbow_0.map(function (crc) {return compute(five_zeros, crc);});var rainbow_pos = new Uint32Array(65537);var rainbow_hash = new Uint32Array(200000);function make_hash() {for (var i = 0; i < rainbow_0.length; i++) {rainbow_pos[rainbow_0[i] >>> 16]++;}for (var i = 1; i <= 65536; i++) {rainbow_pos[i] += rainbow_pos[i - 1];}for (var i = 0; i <= rainbow_0.length; i++) {var po = --rainbow_pos[rainbow_0[i] >>> 16];rainbow_hash[po << 1] = rainbow_0[i];rainbow_hash[po << 1 | 1] = i;}}function lookup(crc) {var r###lts = [];var first = rainbow_pos[crc >>> 16],last = rainbow_pos[1 + (crc >>> 16)];for (var i = first; i < last; i++) {if (rainbow_hash[i << 1] == crc)r###lts.push(rainbow_hash[i << 1 | 1]);}return r###lts;}make_hash();function crack(maincrc, max_digit) {var r###lts = [];maincrc = (~maincrc) >>> 0;var basecrc = 0xffffffff;for (var ndigits = 1; ndigits <= max_digit; ndigits++) {basecrc = update_crc(0x30, basecrc);if (ndigits < 6) {var first_uid = Math.pow(10, ndigits - 1),last_uid = Math.pow(10, ndigits);for (var uid = first_uid; uid < last_uid; uid++) {if (maincrc == ((basecrc ^ rainbow_0[uid]) >>> 0)) {r###lts.push(uid);}}} else {var first_prefix = Math.pow(10, ndigits - 6);var last_prefix = Math.pow(10, ndigits - 5);for (var prefix = first_prefix; prefix < last_prefix; prefix++) {var rem = (maincrc ^ basecrc ^ rainbow_1[prefix]) >>> 0;var items = lookup(rem);items.forEach(function (z) {r###lts.push(prefix * 100000 + z);})}}}return r###lts;}return {crack: crack};}function uhash2uid(uidhash, max_digit = 10) {let _crc32_cracker = null;_crc32_cracker = _crc32_cracker || make_crc32_cracker();return _crc32_cracker.crack(parseInt(uidhash, 16), max_digit);}function getVideoCid_Bangumi() {return String(unsafeWindow.__INITIAL_STATE__.epInfo.cid);}function getVideoCid_Cheese() {// let episodes = unsafeWindow.PlayerAgent.getEpisodes();// let _id = unsafeWindow.$('li.on.list-box-li').index();// return String(episodes[_id].cid);// let cid = "";// while (cid === "") {// if (window.bpNC_1) {// console.log(window.bpNC_1)// cid = window.bpNC_1.config.cid;// }// }return new Promise(resolve => {let timer = setInterval(() => {if (unsafeWindow.bpNC_1) {clearInterval(timer);resolve(unsafeWindow.bpNC_1.config.cid);}}, 1000);});// return cid;}function getVideoCid_Main() {let cidMap = unsafeWindow.__INITIAL_STATE__.cidMap;let keys = Object.keys(cidMap);if (keys.length > 0) {let cids = cidMap[keys[0]].cids;let cidsKeys = Object.keys(cids);if (cidsKeys.length > 0) {return String(cids[cidsKeys[0]]);} else {return "";}} else {return "";}}protobuf.loadFromString = (name, protoStr) => {const Root = protobuf.Root;const fetchFunc = Root.prototype.fetch;Root.prototype.fetch = (_, cb) => cb(null, protoStr);const root = new Root().load(name);Root.prototype.fetch = fetchFunc;return root;};function init_Router() {// refreshAllDanmaku();initPkg_Main();}function getRoute() {// 规定 0是默认页面 1是番剧bangumi页面 2是cheese课程页面let ret = 0;let url = String(location.href);if (url.includes("bangumi/play")) {// 在番剧页面ret = 1;} else if (url.includes("cheese/play")) {// 在课程页面ret = 2;}return ret;}const _historyWrap = function (type) {const orig = history[type];const e = new Event(type);return function () {const rv = orig.apply(this, arguments);e.arguments = arguments;window.dispatchEvent(e);return rv;};};history.pushState = _historyWrap('pushState');history.replaceState = _historyWrap('replaceState');window.addEventListener('pushState', refreshAllDanmaku);window.addEventListener('replaceState', refreshAllDanmaku);window.addEventListener('hashchange', refreshAllDanmaku);window.addEventListener('popstate', refreshAllDanmaku);(async function () {let timer = setInterval(() => {let dom = document.getElementById("danmukuBox");if (dom) {clearInterval(timer);initStyles();init();}}, 500);})();