- // ==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
- // ==/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>${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';
- }
- }
- }
-
- // 主逻辑
- 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({
- 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) => ({
- 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();
- })();