Discussion A review on B站弹幕查询器(查发布者) by (Deleted user 1479207) was reported 05.06.2025 for Spam

(Deleted user 1479207) said:

脚本已被删,有些评论连带着不可见,在这完整还原下整个过程,附全过程截图,方便吃瓜群众

2025.05.31本人下载此脚本使用,发现不支持分P视频,于是自己手动添加了分P支持,并本地使用,此时仅是简单修改,没有上传

使用中又发现搜索弹幕时界面卡死,查看代码得知是不支持异步导致的,随后自己加了异步。
但是在测试过程又中发现命中弹幕结果较多时速度过慢,查看代码得知只有单线程,所以性能上不去,干脆自己重新写了代码,用Worker实现多线程,并加了缓存逻辑提升哈希计算速度,为防止内存泄露又写了缓存自动管理代码,还加了一些安全校验防止用户被XSS攻击,还有很多,比如日志什么的就不说了,后面我会贴代码出来
到此为止整个主体部分就都是我自己写的了

接下来的UI部分我直接丢给AI配合修改完成的,其中包括调整窗口大小,显示进度条,显示toast通知,状态反馈,取消按钮,悬浮窗移动功能
但是后来才发现AI生成的悬浮窗移动功能函数中有23行与PyHaoCoder相同,应该也正是因为这23行代码导致的脚本被删
我的整个代码文件共1602行,懂代码的查看diff都能看出来,整个文件中总共有23行悬浮窗移动的代码相同,哈希是标准库,所以我回复说98%的代码都是我自己的

2025.06.01发布前看到反馈区PyHaoCoder说“拿走的吱一声,有条件的赞助一下,方便后续更新~”,所以特地注册个账号和作者打招呼说拿走爆改了
因为整个架构升级和完全重新设计,并且本地调试时同时安装了PyHaoCoder的版本用于对比性能,版本号定为2.0用于区分,并特地在脚本说明里标注了原作者PyHaoCoder才发布

2025.06.04半夜,PyHaoCoder在这之前没有任何回应和通知,直接举报说 “盗版,未标注原作者出处”
那23行悬浮窗移动代码和他相同我从始至终都没有否认,也怪自己当时没检查,后来才发现。其实这几行当时让AI重新写一个就好了,这么简单的效果AI一句话的事。其他代码全都是自己写的,因为这么简单的一个UI效果被DMCA不值当。对于这个DMCA的结果我是认同的,但是应该把PyHaoCoder对自己人品和技术的展示完整贴出来看看

因为我收到举报提醒,让我回复证据给管理员,我给管理员沟通说他无脑举报,是因为我在脚本说明里加粗标注了他的名字,他还一直抓着多次回复我说没有标

接下来就更搞笑了,他在反馈区挂我,给我打差评,并且全是迷惑发言,是想用自己精彩表现网暴自己吗。说我直接拷贝他代码只是改了UI???核心代码都是他的???并且又说我不标出处???
我并没有说那23行悬浮窗移动代码不是他的,倒是到他嘴里,我1602行代码去掉23行还剩我写的1579行,就成我都是拷贝他的了
后面我把代码贴出来大家就能看到,并且附上BeyondCompare代码对比图,不用懂代码也能看懂,哪行一样一目了然。不止核心代码,请问除了那23行悬浮窗移动代码哪个是他的。证据不甩脸上就死鸭子嘴硬,在那抛开事实不谈泼脏水,演都不演了,直接靠瞎编极限夸大事实骗取不懂代码的人同情,别有用心

前面说了,我发布之初就特地在脚本说明里标注了原作者,给他说了也不看,只管自己在那一个劲说我没标,还出言“但凡标注出处我也不多说什么”,装睡的人叫不醒
后来我直接截图发出来,截图里我脚本说明加粗标的他名字,打脸太快,他一时语塞,在那憋了半小时又改口说“什么标没标名字的,不标链接就是没标”,随后什么出生之类的词就招呼上来了。先试图抬高自己装圣人显得多么大度,然后被戳穿就不装了开始骂人。也没人逼你当圣人,你多余说那句“但凡标注出处我也不多说什么”,结果叽里咕噜在那不停回复我,还开小号反复举报我,纯小丑,把自己架太高摔的也惨

真是一句话一个槽点,帽子扣的离谱,也是喜剧效果拉满。后面又经我多次提醒,他知道核心代码不是他的,从之前说我“只改了UI”又改口“就UI是抄他的”了。还有脸说让我“从零开始完整开发”,就你那23行悬浮窗移动代码(可能还是用DeepSeek写的),有多少用处你自己心里没点数,你那点代码,和“零开始完整开发”区别就是“从0.01开始开发”是吧
除了各种扣帽子泼脏水,就是各种人身攻击,只管自己无脑输出,根本不看别人说什么,开发者的逻辑思维呢,回复我时一口一个出生、废物、BB,你的素质应该在截图里遗臭万年

2025.06.05代码直接放在这了,MIT许可证,多线程和异步框架我都搭好了,注释也很完善,自己改改加功能很方便,随便你们怎么用,记得别带他那23行,免得被碰瓷说你代码全是他的。鉴于他睚眦必报的作风(开小号举报,到处泼脏水),魔怔人没完没了,我也没必要再理,上述只是还原整个过程,就当供大家茶余饭后吃瓜消遣吧,按照之前说的,脚本被删,大号已经销了。不过感觉看来仁慈并不能感化他,那收回之前说的,特地用临时邮箱注册上来,回敬你个差评再退出登录。再也看不见你精彩的国粹了,再也不见

// ==UserScript==
// @name B站弹幕查询器(查发布者)(chaoguan暴力加强版)
// @namespace chaoguan
// @version 2.0
// @description 通过B站视频查询弹幕并查找指定用户
// @author chaoguan
// @icon https://www.bilibili.com/favicon.ico
// @match https://www.bilibili.com/video/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect api.bilibili.com
// @license MIT
// ==/UserScript==

/**
* 工具函数集合
* 包含各种辅助函数,用于数据验证、错误处理等
*/
const utils = {
/**
* 验证B站视频ID格式是否正确
* @param {string} bvid - B站视频ID,格式为BV开头的12位字符串
* @returns {boolean} 返回验证结果,true表示格式正确
*/
validateBvid(bvid) {
return /^BV[a-zA-Z0-9]{10}$/.test(bvid);
},

/**
* 验证数字是否有效
* @param {number} num - 要验证的数字
* @returns {boolean} 返回验证结果,true表示是有效的数字
*/
validateNumber(num) {
return !isNaN(parseFloat(num)) && isFinite(num);
},

/**
* 处理错误信息,移除敏感信息
* @param {Error} error - 原始错误对象
* @returns {Error} 返回处理后的安全错误对象,不包含敏感信息
*/
sanitizeError(error) {
const safeError = new Error('操作失败');
safeError.originalError = error;
return safeError;
}
};

(function () {
'use strict';

// 定时器间隔时间(单位:毫秒)
const intervalTime = 1000; // 1秒

// ==================== 获取视频CID ====================
/**
* 获取B站视频的CID
* 通过API请求获取视频的CID,用于后续获取弹幕数据
* 如果视频有多个分P,会根据URL中的p参数选择对应的分P
*/
function fetchCID() {
try {
const bvid = location.href.split('/')[4].split('?')[0];
if (!bvid || !utils.validateBvid(bvid)) {
throw new Error('无效的视频ID');
}

const url = `https://api.bilibili.com/x/player/pagelist?bvid=${encodeURIComponent(bvid)}&jsonp=jsonp`;

GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Referer': 'https://www.bilibili.com',
'Origin': 'https://www.bilibili.com',
'X-Requested-With': 'XMLHttpRequest'
},
onload: function (response) {
try {
const pageContent = response.responseText;
const data = JSON.parse(pageContent);

if (!data || !data.data || !Array.isArray(data.data)) {
throw new Error('无效的响应数据');
}

const urlParams = new URLSearchParams(window.location.search);
const pValue = urlParams.get('p');
const index = getValidPageIndex(pValue, data.data.length);

const cid = data.data[index]?.cid || data.data[0].cid;
if (!cid || !utils.validateNumber(cid)) {
throw new Error('无法获取视频CID');
}

window._cid = cid;
console.log('获取视频CID:', window._cid, '使用分P索引:', index);

if (!window._url || location.href !== _url) {
window._url = location.href;
}
} catch (error) {
console.error('处理视频信息时发生错误:', error);
alert('获取视频信息失败');
}
},
onerror: function (err) {
console.error('获取视频CID失败:', err);
alert('获取视频信息失败,请检查网络连接');
}
});
} catch (error) {
console.error('初始化视频信息时发生错误:', error);
alert('初始化失败');
}
}

/**
* 验证并获取有效的分P索引
* @param {string} pValue - URL中的p参数值
* @param {number} totalPages - 总分P数
* @returns {number} - 返回有效的分P索引
*/
function getValidPageIndex(pValue, totalPages) {
if (!pValue) return 0;

const index = parseInt(pValue);
if (isNaN(index) || index < 1) {
console.warn('无效的分P参数:', pValue);
return 0;
}

const validIndex = index - 1;
if (validIndex >= totalPages) {
console.warn(`分P索引超出范围: ${index} > ${totalPages}`);
return 0;
}

return validIndex;
}

/**
* 隐藏结果表格
* 当切换视频或开始新的搜索时调用
*/
function hideTable() {
const container = document.getElementById('resultContainer');
if (container) {
container.style.display = "none";
}
}

let urlCheckInterval;

/**
* 启动URL检查定时器
* 定期检查URL变化,当URL变化时重新获取CID
*/
function startTimer() {
urlCheckInterval = setInterval(() => {
if (window._url && location.href !== window._url) {
fetchCID();
hideTable();
}
}, intervalTime);
}

// 添加资源清理函数
function cleanupWorkerResources() {
// 清理定时器
if (urlCheckInterval) {
clearInterval(urlCheckInterval);
}
// 清理事件监听器
if (window._dragEventHandlers) {
const header = document.querySelector('.bili-parser-header');
if (header) {
header.removeEventListener('mousedown', window._dragEventHandlers.handleMouseDown);
}
document.removeEventListener('mousemove', window._dragEventHandlers.handleMouseMove);
document.removeEventListener('mouseup', window._dragEventHandlers.handleMouseUp);
delete window._dragEventHandlers;
}

// 清理结果容器的事件监听器
const resultContainer = document.getElementById('resultContainer');
if (resultContainer && resultContainer._timeJumpListener) {
resultContainer.removeEventListener('click', resultContainer._timeJumpListener);
delete resultContainer._timeJumpListener;
}

// 清理搜索按钮的事件监听器
const searchButton = document.getElementById('startSearch');
if (searchButton && searchButton._clickListener) {
searchButton.removeEventListener('click', searchButton._clickListener);
delete searchButton._clickListener;
}

// 清理输入框的事件监听器
const keywordInput = document.getElementById('keywordInput');
if (keywordInput && keywordInput._keypressListener) {
keywordInput.removeEventListener('keypress', keywordInput._keypressListener);
delete keywordInput._keypressListener;
}

// 清理Worker实例
cleanupWorkers();

// 释放 Blob URL
if (workerUrl) {
try {
URL.revokeObjectURL(workerUrl);
} catch (e) {
console.error('释放 Blob URL 失败:', e);
}
}
}

// 首次初始化
fetchCID();

// 启动定时器
startTimer();

// 添加页面卸载事件监听
window.addEventListener('unload', () => {
cleanupWorkerResources();
// 建议添加对DOM元素的清理
const container = document.querySelector('.bili-parser-container');
if (container) {
container.remove();
}
});
})();

(function () {
'use strict';

// ==================== 哈希转换模块 ====================
/**
* Worker线程代码
* 用于在后台线程中执行哈希转换计算
* 包含CRC32计算和哈希转换的核心算法
*/
const hashWorkerCode = `
const CRCPOLYNOMIAL = 0xEDB88320;
const crctable = new Array(256);

function create_table() {
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;
}
}

function crc32(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;
}

function crc32lastindex(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;
}

function getcrcindex(t) {
for (let i = 0; i < 256; i++) {
if (crctable[i] >>> 24 == t) return i;
}
return -1;
}

function deepCheck(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];
}

function singleConvert(input) {
let ht = parseInt('0x' + input) ^ 0xffffffff,
snum, i, lastindex, deepCheckData;
const index = new Array(4);

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];
}

create_table();

self.onmessage = function(e) {
const { hash, batchId } = e.data;
const result = singleConvert(hash);
self.postMessage({ result, batchId });
};
`;

// 创建Blob URL
const blob = new Blob([hashWorkerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);

// 创建Worker池
const WORKER_COUNT = Math.max(1, (navigator.hardwareConcurrency || 4) - 1);
let workers = [];

// 清理旧的Worker实例
function cleanupWorkers() {
workers.forEach(worker => {
try {
worker.terminate();
} catch (e) {
console.error('终止Worker失败:', e);
}
});
workers = [];
}

// 初始化Worker池
function initializeWorkers() {
// 建议添加Worker状态检查
if (workers.some(worker => worker.terminated)) {
cleanupWorkers();
}

// 建议添加Worker数量限制
if (workers.length >= WORKER_COUNT) {
return;
}

try {
workers = Array.from({ length: WORKER_COUNT }, () => {
const worker = new Worker(workerUrl);
worker.onerror = (error) => handleWorkerError(worker, error);
return worker;
});
} catch (error) {
console.error('初始化 Worker 失败:', error);
cleanupWorkers(); // 确保在失败时清理
throw error;
}
}

// 添加 Worker 错误处理函数
function handleWorkerError(worker, error) {
console.error('Worker 错误:', error);
// 尝试重新创建 Worker
try {
const index = workers.indexOf(worker);
if (index !== -1) {
worker.terminate();
workers[index] = new Worker(workerUrl);
workers[index].onerror = (error) => handleWorkerError(workers[index], error);
console.log('Worker 已重新创建');
}
} catch (e) {
console.error('重新创建 Worker 失败:', e);
}
}

// 添加全局取消标志
let globalCancelled = false;

// 修改 Worker 处理函数
async function processWithWorker(worker, task) {
if (globalCancelled) {
return { result: -1 };
}

// 使用统一的缓存
const cachedResult = globalCache.get(task.hash);
if (cachedResult !== -1) {
return { result: cachedResult };
}

return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error('Worker 处理超时'));
}, 30000);

worker.onmessage = function(e) {
clearTimeout(timeoutId);
if (globalCancelled) {
resolve({ result: -1 });
return;
}
// 将结果存入统一缓存
globalCache.put(task.hash, e.data.result);
resolve(e.data);
};

worker.onerror = function(error) {
clearTimeout(timeoutId);
reject(error);
};

worker.postMessage(task);
});
}

// 添加任务队列管理
class WorkerTaskQueue {
constructor(workers) {
this.workers = workers;
this.taskQueue = [];
this.workerStatus = new Map(workers.map(worker => [worker, false]));
this.maxConcurrentTasks = workers.length; // 最大并发任务数
this.activeTasks = 0;
}

async addTask(task) {
return new Promise((resolve, reject) => {
this.taskQueue.push({ task, resolve, reject });
this.processTasks(); // 尝试处理任务
});
}

async processTasks() {
// 如果当前活跃任务数达到最大并发数,则不再处理新任务
if (this.activeTasks >= this.maxConcurrentTasks) {
return;
}

// 找到空闲的worker
const availableWorker = Array.from(this.workerStatus.entries())
.find(([_, isBusy]) => !isBusy)?.[0];

if (!availableWorker || this.taskQueue.length === 0) {
return;
}

const { task, resolve, reject } = this.taskQueue.shift();
this.workerStatus.set(availableWorker, true);
this.activeTasks++;

try {
const result = await processWithWorker(availableWorker, task);
resolve(result);
} catch (error) {
reject(error);
} finally {
this.workerStatus.set(availableWorker, false);
this.activeTasks--;
// 处理下一个任务
this.processTasks();
}
}
}

// 修改批量处理函数
async function batchConvert(hashArray) {
return new Promise((resolve) => {
const results = new Array(hashArray.length);
let completedCount = 0;
let hasError = false;

// 创建任务队列
const taskQueue = new WorkerTaskQueue(workers);

// 分片大小,可以根据实际情况调整
const CHUNK_SIZE = 100;

// 将数组分成多个小片
const chunks = [];
for (let i = 0; i < hashArray.length; i += CHUNK_SIZE) {
chunks.push(hashArray.slice(i, i + CHUNK_SIZE));
}

// 处理每个分片
const processChunk = async (chunk, chunkIndex) => {
const startIndex = chunkIndex * CHUNK_SIZE;

// 为分片中的每个任务创建Promise
const chunkPromises = chunk.map((hash, index) => {
const task = {
hash,
batchId: startIndex + index
};

return taskQueue.addTask(task)
.then(result => {
if (globalCancelled) return;

results[task.batchId] = result.result;
completedCount++;

// 更新进度
const progress = (completedCount / hashArray.length) * 100;
updateProgress(completedCount, hashArray.length, progress);
})
.catch(error => {
console.error('处理任务失败:', error);
hasError = true;
});
});

// 等待当前分片的所有任务完成
await Promise.all(chunkPromises);
};

// 串行处理所有分片
const processAllChunks = async () => {
for (let i = 0; i < chunks.length; i++) {
if (globalCancelled) break;
await processChunk(chunks[i], i);
}

if (hasError) {
console.error('部分任务处理失败');
}
resolve(results);
};

// 开始处理
processAllChunks().catch(error => {
console.error('批量处理失败:', error);
resolve(results);
});
});
}

/**
* 更新进度显示
* 更新进度条和文本显示,包括处理数量和百分比
*
* @param {number} current - 当前已处理的数量
* @param {number} total - 需要处理的总数量
* @param {number} percentage - 完成百分比(0-100)
* @throws {Error} 当找不到进度显示相关元素时抛出错误
*/
function updateProgress(current, total, percentage) {
const progressContainer = document.getElementById('progressContainer');
const progressText = document.getElementById('progressText');
const progressBarInner = document.getElementById('progressBarInner');

if (!progressContainer || !progressText || !progressBarInner) {
throw new Error('找不到进度显示相关元素');
}

try {
progressContainer.style.display = 'block';
progressText.querySelector('span').textContent = `处理中: ${escapeHtml(String(current))}/${escapeHtml(String(total))}`;
progressBarInner.style.width = `${Math.min(100, Math.max(0, percentage))}%`;
} catch (error) {
console.error('更新进度显示时发生错误:', error);
}
}

// 隐藏进度显示
function hideProgress() {
const progressContainer = document.getElementById('progressContainer');
if (progressContainer) {
try {
progressContainer.style.display = 'none';
} catch (error) {
console.error('隐藏进度显示时发生错误:', error);
}
}
}

// 添加 HTML 转义函数
function escapeHtml(str) {
if (typeof str !== 'string') {
return String(str);
}
return str.replace(/[&<>"']/g, char => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[char]));
}

/**
* 主处理函数
* 处理弹幕数据的获取、解析和转换流程
*
* @param {string} keyword - 搜索关键词,用于过滤弹幕内容
* @returns {Promise} 返回处理完成的Promise
*
* @throws {Error} 当获取视频信息失败时抛出错误
* @throws {Error} 当获取弹幕数据失败时抛出错误
* @throws {Error} 当弹幕数据格式错误时抛出错误
*
* 处理流程:
* 1. 获取视频CID
* 2. 获取弹幕数据
* 3. 解析XML数据
* 4. 转换哈希值
* 5. 显示结果
*/
async function main(keyword) {
if (window._isProcessing) {
showToast('已有请求正在处理中', 'warning');
return;
}

window._isProcessing = true;
globalCancelled = false;

// 显示取消按钮
const cancelButton = document.getElementById('cancelButton');
if (cancelButton) {
cancelButton.style.display = 'block';
cancelButton.onclick = () => {
globalCancelled = true;
window._isProcessing = false;
hideProgress();
cleanupWorkerResources();
// 清理所有Worker
cleanupWorkers();
// 重新初始化Worker
initializeWorkers();
};
}

try {
const cid = getVideoCID();
if (!cid || !utils.validateNumber(cid)) {
throw new Error('获取视频信息失败,请刷新页面重试');
}

updateProgress(0, 0, 0);
const progressText = document.getElementById('progressText');
if (!progressText) {
throw new Error('找不到进度文本元素');
}
progressText.querySelector('span').textContent = '正在获取弹幕...';

const response = await new Promise((resolve, reject) => {
const xhr = GM_xmlhttpRequest({
method: 'GET',
url: `https://api.bilibili.com/x/v1/dm/list.so?oid=${encodeURIComponent(cid)}`,
headers: {
'Referer': 'https://www.bilibili.com',
'Origin': 'https://www.bilibili.com'
},
onload: resolve,
onerror: reject
});

// 添加取消处理
if (cancelButton) {
cancelButton.onclick = () => {
xhr.abort();
globalCancelled = true;
window._isProcessing = false;
hideProgress();
cleanupWorkerResources();
cleanupWorkers();
initializeWorkers();
};
}
});

if (globalCancelled) {
return;
}

if (!response || !response.responseText) {
throw new Error('获取弹幕数据失败');
}

const parser = new DOMParser();
const xmlDoc = parser.parseFromString(response.responseText, "text/xml");

// 检查XML解析错误
const parserError = xmlDoc.querySelector('parsererror');
if (parserError) {
throw new Error('弹幕数据格式错误');
}

const ds = xmlDoc.getElementsByTagName('d');
progressText.querySelector('span').textContent = '正在处理弹幕...';

const comments = [];
for (let d of ds) {
if (globalCancelled) {
return;
}

try {
const text = d.textContent;
if (keyword && !text.includes(keyword)) continue;

const p = d.getAttribute('p')?.split(',') || [];
if (p.length < 7) {
console.warn('弹幕数据格式异常:', p);
continue;
}

const ts = parseInt(p[4]);
if (!utils.validateNumber(ts)) {
console.warn('无效的时间戳:', p[4]);
continue;
}

comments.push({
second: p[0],
hash: p[6],
ts: ts * 1000,
text: text
});
} catch (error) {
console.warn('处理单条弹幕时发生错误:', error);
continue;
}
}

if (globalCancelled) {
return;
}

const totalComments = comments.length;
if (totalComments === 0) {
showToast('没有找到匹配的弹幕', 'warning');
hideProgress();
return;
}

const midBatch = await batchConvert(comments.map(comment => comment.hash));

if (globalCancelled) {
return;
}

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 && comment.mid !== -1);

showResults(results);
} catch (err) {
if (globalCancelled) {
console.log('操作已取消');
return;
}
console.error('处理弹幕时发生错误:', err);
const safeError = utils.sanitizeError(err);
if (err.status === 404) {
showToast('视频不存在或已被删除', 'error');
} else if (err.status === 403) {
showToast('没有权限访问该视频', 'error');
} else {
showToast('获取弹幕失败', 'error');
}
} finally {
window._isProcessing = false;
globalCancelled = false;
hideProgress();
cleanupWorkerResources();
// 隐藏取消按钮
if (cancelButton) {
cancelButton.style.display = 'none';
}
}
}

// ==================== 油猴脚本主逻辑 ====================
/**
* 创建用户界面
* 初始化并添加所有必要的UI元素,包括搜索框、进度条、结果表格等
*
* 主要功能:
* - 创建搜索框和按钮
* - 创建进度显示区域
* - 创建结果表格容器
* - 添加拖拽功能
* - 设置样式和布局
*
* @throws {Error} 当DOM操作失败时抛出错误
*/
function createUI() {
// 清理可能存在的旧实例
const oldContainer = document.querySelector('.bili-parser-container');
if (oldContainer) {
oldContainer.remove();
}

const style = `
`;

const html = `

B站弹幕查询器 By: @chaoguan



开始搜索

处理中: 0/0
取消
`;

document.body.insertAdjacentHTML('afterbegin', style + html);
document.body.insertAdjacentHTML('afterbegin', '
');

// 添加悬浮窗移动功能
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;

// 提取事件处理函数
function handleMouseDown(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';
}

function handleMouseMove(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`;
}

function handleMouseUp() {
isDragging = false;
document.body.style.userSelect = '';
document.body.style.webkitUserSelect = '';
}

// 添加事件监听器
header.addEventListener('mousedown', handleMouseDown);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);

// 将事件处理函数保存到window对象上,以便后续清理
window._dragEventHandlers = {
handleMouseDown,
handleMouseMove,
handleMouseUp
};
}

// 获取视频CID
function getVideoCID() {
if (window._cid) {
return window._cid
}
}

// 显示结果
function showResults(comments) {
const container = document.getElementById('resultContainer');
if (!container) {
console.error('找不到结果容器元素');
return;
}

try {
// 计算合适的容器高度
const windowHeight = window.innerHeight;
const containerTop = container.getBoundingClientRect().top;
const padding = 60; // 增加上下padding的总和
const minHeight = 200; // 最小高度
const maxHeight = 350; // 降低最大高度限制
const calculatedHeight = Math.min(windowHeight - containerTop - padding, maxHeight);

// 设置容器高度,确保不小于最小高度
const finalHeight = Math.max(calculatedHeight, minHeight);
container.style.maxHeight = `${finalHeight}px`;
container.style.height = `${finalHeight}px`;

let html = `


`;

comments.forEach(comment => {
if (!comment || typeof comment !== 'object') {
console.warn('无效的弹幕数据:', comment);
return;
}

const text = comment.text?.length > 40 ?
comment.text.substring(0, 40) + '...' :
comment.text || '';

const escapedText = escapeHtml(text);
const escapedMid = escapeHtml(comment.mid);
const escapedDate = escapeHtml(comment.date);
const escapedSecond = escapeHtml(comment.second);

html += `

`;
});

html += `
用户MID时间内容
${escapedMid}${escapedDate}${escapedText}

共 ${escapeHtml(String(comments.length))} 条数据
`;

container.innerHTML = html;
container.style.display = "block";

// 移除旧的事件监听器
const oldListener = container._timeJumpListener;
if (oldListener) {
container.removeEventListener('click', oldListener);
}

// 添加新的事件监听器
const newListener = function(e) {
if (e.target.classList.contains('time-jump')) {
const second = parseFloat(e.target.getAttribute('data-second'));
if (isNaN(second)) {
console.error('无效的时间戳:', e.target.getAttribute('data-second'));
return;
}
const video = document.querySelector('video');
if (video) {
video.currentTime = second;
video.pause();
}
}
};

container.addEventListener('click', newListener);
container._timeJumpListener = newListener;

} catch (error) {
console.error('显示结果时发生错误:', error);
container.innerHTML = '
显示结果时发生错误
';
}
}

// 初始化
function init() {
// 初始化Worker池
initializeWorkers();

createUI();

const searchButton = document.getElementById('startSearch');
const keywordInput = document.getElementById('keywordInput');

const startSearch = () => {
const keyword = keywordInput.value.trim();
main(keyword || undefined);
};

// 添加搜索按钮点击事件
searchButton.addEventListener('click', startSearch);
// 保存事件监听器引用以便后续清理
searchButton._clickListener = startSearch;

// 定义统一的回车事件处理函数
const handleEnterKey = (e) => {
if (e.key === 'Enter') {
startSearch();
}
};

// 添加输入框回车事件
keywordInput.addEventListener('keypress', handleEnterKey);
// 保存事件监听器引用以便后续清理
keywordInput._keypressListener = handleEnterKey;
}

// 启动脚本
init();

function monitorMemoryUsage() {
if (window.performance && window.performance.memory) {
const memory = window.performance.memory;
const usedHeap = memory.usedJSHeapSize / 1024 / 1024;
const totalHeap = memory.totalJSHeapSize / 1024 / 1024;

// 当内存使用超过80%时进行清理
if (usedHeap > totalHeap * 0.8) {
console.warn('内存使用率过高,执行智能清理');
// 使用 globalCache 的智能清理方法
globalCache.smartCleanup();
}
}
}

window.addEventListener('error', (event) => {
console.error('全局错误:', event.error);
cleanupWorkerResources();
});

// 监控缓存性能
function monitorCachePerformance() {
const hitRate = globalCache.getHitRate();
const memoryUsage = window.performance.memory.usedJSHeapSize / window.performance.memory.totalJSHeapSize;

if (memoryUsage > 0.8 || hitRate < 0.3) {
// 如果内存使用率高或命中率低,清理缓存
globalCache.clear();
} else if (memoryUsage > 0.6) {
// 如果内存使用率中等,减少缓存容量
globalCache.currentCapacity = Math.floor(globalCache.currentCapacity * 0.8);
}
}
})();

/**
* 自适应缓存类
* 根据内存使用情况和命中率动态调整缓存大小
*
* 主要特性:
* - 动态容量调整:根据内存使用情况自动调整缓存大小
* - 智能清理机制:基于访问频率和时间的智能清理
* - 访问频率统计:记录每个缓存项的访问次数
* - 紧急情况处理:内存压力大时进行紧急清理
*
* @property {number} baseCapacity - 基础缓存容量
* @property {number} currentCapacity - 当前缓存容量
* @property {number} minCapacity - 最小缓存容量
* @property {number} maxCapacity - 最大缓存容量
*/
class AdaptiveCache {
constructor() {
this.baseCapacity = 10000;
this.currentCapacity = this.baseCapacity;
this.minCapacity = 1000;
this.maxCapacity = 50000;
this.cache = new Map();
this.hitCount = 0;
this.missCount = 0;
this.lastAccess = new Map();
this.accessCount = new Map();
this.memoryThreshold = 0.8; // 内存使用率阈值
this.cleanupThreshold = 0.7; // 清理阈值
this.growthFactor = 1.1; // 更温和的增长因子
this.shrinkFactor = 0.9; // 更温和的收缩因子
this.emergencyThreshold = 0.9; // 紧急阈值
this.emergencyCleanupRatio = 0.5; // 紧急清理比例
this.adaptiveFactors = {
highHitRate: 1.2, // 高命中率增长因子
lowHitRate: 0.8, // 低命中率收缩因子
normalGrowth: 1.1, // 正常增长因子
normalShrink: 0.9 // 正常收缩因子
};
this.lastAdjustmentTime = Date.now();
this.adjustmentInterval = 1000; // 调整间隔(毫秒)
this.operationCount = 0;
this.adjustmentThreshold = 100; // 操作次数阈值
}

get(key) {
this.operationCount++;
const now = Date.now();
this.lastAccess.set(key, now);
this.accessCount.set(key, (this.accessCount.get(key) || 0) + 1);

if (!this.cache.has(key)) {
this.missCount++;
this._checkAndAdjustCapacity();
return -1;
}

this.hitCount++;
const value = this.cache.get(key);
this.lastAccess.set(key, now);
this._checkAndAdjustCapacity();
return value;
}

put(key, value) {
this.operationCount++;
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.currentCapacity) {
const oldestKey = this.getOldestKey();
if (oldestKey) {
this.cache.delete(oldestKey);
this.lastAccess.delete(oldestKey);
this.accessCount.delete(oldestKey);
}
}
this.cache.set(key, value);
this.lastAccess.set(key, Date.now());
this.accessCount.set(key, 1);
this._checkAndAdjustCapacity();
}

_checkAndAdjustCapacity() {
// 检查是否需要调整容量
if (this.operationCount < this.adjustmentThreshold) {
return;
}

const now = Date.now();
if (now - this.lastAdjustmentTime < this.adjustmentInterval) {
return;
}

this._adjustCapacity();
this.operationCount = 0;
this.lastAdjustmentTime = now;
}

_adjustCapacity() {
const hitRate = this.getHitRate();
const memoryUsage = this.getMemoryUsage();

// 根据内存使用情况调整
if (memoryUsage > this.emergencyThreshold) {
// 紧急情况:大幅减少容量
this.currentCapacity = Math.max(
this.minCapacity,
Math.floor(this.currentCapacity * 0.5)
);
this.emergencyCleanup();
} else if (memoryUsage > this.memoryThreshold) {
// 内存压力大:适度减少容量
this.currentCapacity = Math.max(
this.minCapacity,
Math.floor(this.currentCapacity * this.shrinkFactor)
);
this.smartCleanup();
} else {
// 根据命中率调整
if (hitRate > 0.8) {
// 高命中率:增加容量
this.currentCapacity = Math.min(
this.maxCapacity,
Math.floor(this.currentCapacity * this.adaptiveFactors.highHitRate)
);
} else if (hitRate < 0.3) {
// 低命中率:减少容量
this.currentCapacity = Math.max(
this.minCapacity,
Math.floor(this.currentCapacity * this.adaptiveFactors.lowHitRate)
);
this.smartCleanup();
}
}

// 确保容量在合理范围内
this.currentCapacity = Math.max(
this.minCapacity,
Math.min(this.maxCapacity, this.currentCapacity)
);
}

getOldestKey() {
let oldestKey = null;
let oldestTime = Infinity;

for (const [key, time] of this.lastAccess) {
if (time < oldestTime) {
oldestTime = time;
oldestKey = key;
}
}
return oldestKey;
}

getHitRate() {
const total = this.hitCount + this.missCount;
return total === 0 ? 0 : this.hitCount / total;
}

getMemoryUsage() {
if (window.performance && window.performance.memory) {
return window.performance.memory.usedJSHeapSize / window.performance.memory.totalJSHeapSize;
}
return 0;
}

smartCleanup() {
const now = Date.now();
const items = Array.from(this.cache.entries()).map(([key, value]) => ({
key,
value,
lastAccess: this.lastAccess.get(key),
accessCount: this.accessCount.get(key)
}));

// 按访问频率和最后访问时间排序
items.sort((a, b) => {
const timeWeight = 0.7;
const frequencyWeight = 0.3;

const timeScore = (now - a.lastAccess) / (now - b.lastAccess);
const frequencyScore = b.accessCount / a.accessCount;

return (timeWeight * timeScore + frequencyWeight * frequencyScore);
});

// 保留前80%的项
const keepCount = Math.floor(items.length * 0.8);
const toRemove = items.slice(keepCount);

for (const item of toRemove) {
this.cache.delete(item.key);
this.lastAccess.delete(item.key);
this.accessCount.delete(item.key);
}
}

emergencyCleanup() {
if (this.getMemoryUsage() > this.emergencyThreshold) {
const items = Array.from(this.cache.entries());
items.sort((a, b) => this._getLastAccess(b) - this._getLastAccess(a));
const keepCount = Math.floor(items.length * (1 - this.emergencyCleanupRatio));
items.slice(keepCount).forEach(([key]) => {
this.cache.delete(key);
this.lastAccess.delete(key);
this.accessCount.delete(key);
});
}
}

clear() {
this.cache.clear();
this.lastAccess.clear();
this.accessCount.clear();
this.hitCount = 0;
this.missCount = 0;
}

_getLastAccess(key) {
return this.lastAccess.get(key) || 0;
}
}

// 使用统一的缓存实例
const globalCache = new AdaptiveCache();


// 添加提示函数
function showToast(message, type = 'info', duration = 3000) {
const toast = document.getElementById('biliToast');
const container = document.querySelector('.bili-parser-container');
if (!toast || !container) return;

// 获取工具窗口的位置
const containerRect = container.getBoundingClientRect();

// 计算toast的位置
const toastLeft = containerRect.left + (containerRect.width / 2);
const toastTop = containerRect.top - 50; // 在工具窗口上方50px处

// 设置toast的位置
toast.style.left = `${toastLeft}px`;
toast.style.top = `${toastTop}px`;
toast.style.transform = 'translateX(-50%)';

// 移除所有类型类
toast.classList.remove('error', 'success', 'warning');
// 添加新的类型类
if (type !== 'info') {
toast.classList.add(type);
}

toast.textContent = message;
toast.classList.add('show');

// 清除之前的定时器
if (toast._timeout) {
clearTimeout(toast._timeout);
}

// 设置新的定时器
toast._timeout = setTimeout(() => {
toast.classList.remove('show');
}, duration);
}

This report has been dismissed by a moderator.