您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
通过B站视频查询弹幕并查找指定用户,未经授权禁止修改、分发或商业用途
当前为
// ==UserScript== // @name B站弹幕查询器(查发布者) // @namespace PyHaoCoder // @version 1.0 // @description 通过B站视频查询弹幕并查找指定用户,未经授权禁止修改、分发或商业用途 // @author PyHaoCoder // @icon https://www.bilibili.com/favicon.ico // @match https://www.bilibili.com/video/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect api.bilibili.com // @license Proprietary // @copyright 2025, PyHaoCoder (All Rights Reserved) // ==/UserScript== (function () { 'use strict'; // 定时器间隔时间(单位:毫秒) const intervalTime = 1000; // 5秒 // ==================== 获取视频CID ==================== function fetchCID() { const bvid = location.href.split('/')[4].split('?')[0]; const url = `https://api.bilibili.com/x/player/pagelist?bvid=${bvid}&jsonp=jsonp` GM_xmlhttpRequest({ method: 'GET', url: url, onload: function (response) { // 获取页面内容 const pageContent = response.responseText; const data = JSON.parse(pageContent); if (data.data) { // 获取视频CID window._cid = data.data[0].cid; // 更新全局变量 console.log('获取视频CID:', window._cid); // 更新 _url if (!window._url || location.href !== _url) { window._url = location.href; } } else { console.error('获取视频CID失败'); } }, onerror: function (err) { console.log('获取视频CID失败:' + err.statusText); } }); } // 隐藏表格 function hideTable() { const container = document.getElementById('resultContainer') container.style.display = "none" } // 初始化定时器 function startTimer() { setInterval(() => { // 如果 _url 未定义或当前 URL 不等于 _url,则更新 CID if (window._url && location.href !== window._url) { fetchCID(); hideTable(); } }, intervalTime); } // 首次初始化 fetchCID(); // 启动定时器 startTimer(); })(); (function () { 'use strict'; // ==================== 哈希转换模块 ==================== window.BiliBili_midcrc = function () { 'use strict'; const CRCPOLYNOMIAL = 0xEDB88320; const startTime = new Date().getTime(), crctable = new Array(256), create_table = function () { let crcreg, i, j; for (i = 0; i < 256; ++i) { crcreg = i; for (j = 0; j < 8; ++j) { if ((crcreg & 1) != 0) { crcreg = CRCPOLYNOMIAL ^ (crcreg >>> 1); } else { crcreg >>>= 1; } } crctable[i] = crcreg; } }, crc32 = function (input) { if (typeof (input) != 'string') input = input.toString(); let crcstart = 0xFFFFFFFF, len = input.length, index; for (let i = 0; i < len; ++i) { index = (crcstart ^ input.charCodeAt(i)) & 0xff; crcstart = (crcstart >>> 8) ^ crctable[index]; } return crcstart; }, crc32lastindex = function (input) { if (typeof (input) != 'string') input = input.toString(); let crcstart = 0xFFFFFFFF, len = input.length, index; for (let i = 0; i < len; ++i) { index = (crcstart ^ input.charCodeAt(i)) & 0xff; crcstart = (crcstart >>> 8) ^ crctable[index]; } return index; }, getcrcindex = function (t) { for (let i = 0; i < 256; i++) { if (crctable[i] >>> 24 == t) return i; } return -1; }, deepCheck = function (i, index) { let tc = 0x00, str = '', hash = crc32(i); tc = hash & 0xff ^ index[2]; if (!(tc <= 57 && tc >= 48)) return [0]; str += tc - 48; hash = crctable[index[2]] ^ (hash >>> 8); tc = hash & 0xff ^ index[1]; if (!(tc <= 57 && tc >= 48)) return [0]; str += tc - 48; hash = crctable[index[1]] ^ (hash >>> 8); tc = hash & 0xff ^ index[0]; if (!(tc <= 57 && tc >= 48)) return [0]; str += tc - 48; hash = crctable[index[0]] ^ (hash >>> 8); return [1, str]; }; create_table(); const index = new Array(4); // 单次转换函数 const singleConvert = function (input) { let ht = parseInt('0x' + input) ^ 0xffffffff, snum, i, lastindex, deepCheckData; for (i = 3; i >= 0; i--) { index[3 - i] = getcrcindex(ht >>> (i * 8)); snum = crctable[index[3 - i]]; ht ^= snum >>> ((3 - i) * 8); } for (i = 0; i < 100000000; i++) { lastindex = crc32lastindex(i); if (lastindex == index[3]) { deepCheckData = deepCheck(i, index) if (deepCheckData[0]) break; } } if (i == 100000000) return -1; return i + '' + deepCheckData[1]; }; // 批量转换函数 const batchConvert = function (hashArray) { return hashArray.map(function (hash) { return singleConvert(hash); }); }; return { singleConvert: singleConvert, // 单次转换 batchConvert: batchConvert // 批量转换 }; }; })(); (function () { 'use strict'; // ==================== 油猴脚本主逻辑 ==================== // 创建UI界面 function createUI() { const style = ` <style> .bili-parser-container { position: fixed; top: 70px; right: 20px; z-index: 9999; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.2); width: 300px; cursor: default; transition: transform 0.1s ease-out; /* 平滑复位效果 */ will-change: transform; /* 提前声明变化属性 */ } .bili-parser-header { cursor: move; padding: 10px 0; margin: -10px 0 10px; border-bottom: 1px solid #eee; } #resultContainer { max-height: 400px; /* 设置最大高度 */ overflow-y: auto; /* 添加垂直滚动条 */ margin-top: 0; display: none; } .bili-input { width: 100%; padding: 8px 0; margin: 0 0 8px; border: 1px solid #ddd; box-sizing: border-box; /* 确保宽度包括内边距和边框 */ } #keywordInput.bili-input { padding-left: 8px; padding-right: 8px; } .bili-btn { background: #00a1d6; color: white; border: none; padding: 8px 15px; cursor: pointer; width: 100%; box-sizing: border-box; /* 确保宽度包括内边距和边框 */ } .result-table { width: 100%; border-collapse: collapse; display: block; } .result-table td, .result-table th { border: 1px solid #ddd; padding: 8px; font-size: 12px; } </style> `; const html = ` <div class="bili-parser-container"> <div class="bili-parser-header"><h3>B站弹幕查询器 <span style="float: right;">By: @PyHaoCoder</span></h3></div> <input type="text" class="bili-input" id="keywordInput" placeholder="输入要查找的关键字"> <button class="bili-btn" id="startSearch">开始搜索</button> <div id="resultContainer"></div> </div> `; document.body.insertAdjacentHTML('afterbegin', style + html); // 添加悬浮窗移动功能 addDragFunctionality(); } // ==================== 添加悬浮窗移动功能 ==================== function addDragFunctionality() { const container = document.querySelector('.bili-parser-container'); const header = document.querySelector('.bili-parser-header'); let isDragging = false; let startX, startY, initialX, initialY; header.addEventListener('mousedown', (e) => { isDragging = true; startX = e.clientX; startY = e.clientY; initialX = container.offsetLeft; initialY = container.offsetTop; // 防止文本选中 document.body.style.userSelect = 'none'; document.body.style.webkitUserSelect = 'none'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; // 计算新位置(限制在窗口范围内) const newX = Math.max(0, Math.min(window.innerWidth - container.offsetWidth, initialX + deltaX)); const newY = Math.max(0, Math.min(window.innerHeight - container.offsetHeight, initialY + deltaY)); container.style.left = `${newX}px`; container.style.right = 'auto'; container.style.top = `${newY}px`; }); document.addEventListener('mouseup', () => { isDragging = false; document.body.style.userSelect = ''; document.body.style.webkitUserSelect = ''; }); } // 获取视频CID function getVideoCID() { if (window._cid) { return window._cid } } // 显示结果 function showResults(comments) { const container = document.getElementById('resultContainer'); let html = ` <table class="result-table"> <tr> <th>用户MID</th> <th>时间</th> <th>内容</th> </tr> `; // 显示所有数据 comments.forEach(comment => { // 如果弹幕长度超过 40,则截断并添加“...” const text = comment.text.length > 40 ? comment.text.substring(0, 40) + '...' : comment.text; html += ` <tr> <td><a href="https://space.bilibili.com/${comment.mid}" target="_blank">${comment.mid}</a></td> <td>${comment.date}</td> <td class="time-jump" data-second="${comment.second}" style="cursor: pointer;">${text}</td> </tr> `; }); html += '</table>'; // 添加“共 n 条数据”提示 html += ` <div style="margin-top: 10px; color: #666; text-align: center;"> 共 ${comments.length} 条数据 </div> `; container.innerHTML = html; container.style.marginTop = "10px" container.style.display = "block" // 动态调整表格高度和滚动条 const table = container.querySelector('.result-table'); if (table) { if (table.scrollHeight > 280) { table.style.maxHeight = '280px'; table.style.overflowY = 'auto'; // 添加垂直滚动条 } else { table.style.maxHeight = 'none'; table.style.overflowY = 'visible'; } } // 添加事件监听器 container.addEventListener('click', function (e) { if (e.target.classList.contains('time-jump')) { const second = parseFloat(e.target.getAttribute('data-second')); const video = document.querySelector('video'); if (video) { video.currentTime = second; video.pause(); } } }); } // 主逻辑 async function main(keyword) { const cid = getVideoCID(); if (!cid) { alert('获取视频信息失败,请刷新页面重试'); return; } else { console.log(`开始解析:https://api.bilibili.com/x/v1/dm/list.so?oid=${cid}`) } GM_xmlhttpRequest({ method: 'GET', url: `https://api.bilibili.com/x/v1/dm/list.so?oid=${cid}`, onload: function (response) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(response.responseText, "text/xml"); const ds = xmlDoc.getElementsByTagName('d'); const comments = []; for (let d of ds) { const text = d.textContent; if (keyword && !text.includes(keyword)) continue; const p = d.getAttribute('p').split(','); comments.push({ second: p[0], hash: p[6], ts: parseInt(p[4]) * 1000, text: text }); } // 使用优化后的哈希转换模块 const midcrc = new BiliBili_midcrc(); const midBatch = midcrc.batchConvert(comments.map(comment => comment.hash)) const results = comments.map((comment, idx) => ({ second: comment.second, mid: midBatch[idx], date: new Date(comment.ts).toLocaleString(), text: comment.text })).filter(comment => comment.mid); // 过滤无效结果 console.log('解析结果:', results) showResults(results); }, onerror: function (err) { alert('获取弹幕失败:' + err.statusText); } }); } // 初始化 function init() { createUI(); document.getElementById('startSearch').addEventListener('click', () => { const keyword = document.getElementById('keywordInput').value.trim(); main(keyword || undefined); }); } // 启动脚本 init(); })();