// ==UserScript==
// @name B站评论过滤器
// @namespace https://github.com/cmyyx/bili-comment-filter
// @version 0.1.1
// @description 根据关键词和正则表达式过滤B站评论区的评论
// @author Trae AI, 璨梦踏月
// @match *://*.bilibili.com/video/*
// @match *://www.bilibili.com/read/*
// @match *://t.bilibili.com/*
// @match *://www.bilibili.com/opus/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @license GPL-3.0
// ==/UserScript==
(function() {
'use strict';
// 存储过滤规则
const FILTER_KEY = 'bili_comment_filter_rules';
let filterRules = GM_getValue(FILTER_KEY, { keywords: [], regexps: [], hideComments: false, completelyHide: false });
// 保存过滤规则到本地存储
function saveFilterRules() {
GM_setValue(FILTER_KEY, filterRules);
}
// 创建过滤器设置面板
function createFilterPanel() {
// 如果已存在面板则显示
const existingPanel = document.getElementById('bili-comment-filter-panel');
if (existingPanel) {
existingPanel.style.display = 'block';
return;
}
// 创建面板容器
const panel = document.createElement('div');
panel.id = 'bili-comment-filter-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
max-height: 500px;
background-color: white;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 10000;
padding: 20px;
display: flex;
flex-direction: column;
font-family: 'Microsoft YaHei', sans-serif;
`;
// 创建标题
const title = document.createElement('h2');
title.textContent = 'B站评论过滤器设置';
title.style.cssText = `
margin: 0 0 15px 0;
color: #fb7299;
font-size: 18px;
text-align: center;
`;
panel.appendChild(title);
// 创建关闭按钮
const closeBtn = document.createElement('div');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
position: absolute;
top: 10px;
right: 15px;
cursor: pointer;
font-size: 20px;
color: #999;
`;
closeBtn.onclick = function() {
panel.style.display = 'none';
};
panel.appendChild(closeBtn);
// 创建选项卡
const tabContainer = document.createElement('div');
tabContainer.style.cssText = `
display: flex;
margin-bottom: 15px;
border-bottom: 1px solid #eee;
`;
const keywordTab = document.createElement('div');
keywordTab.textContent = '关键词';
keywordTab.className = 'filter-tab active';
keywordTab.dataset.tab = 'keyword';
const regexpTab = document.createElement('div');
regexpTab.textContent = '正则表达式';
regexpTab.className = 'filter-tab';
regexpTab.dataset.tab = 'regexp';
const tabStyle = `
padding: 8px 15px;
cursor: pointer;
margin-right: 5px;
border-radius: 5px 5px 0 0;
`;
keywordTab.style.cssText = tabStyle + 'background-color: #fb7299; color: white;';
regexpTab.style.cssText = tabStyle + 'background-color: #f4f4f4; color: #666;';
tabContainer.appendChild(keywordTab);
tabContainer.appendChild(regexpTab);
panel.appendChild(tabContainer);
// 创建内容区域
const contentContainer = document.createElement('div');
contentContainer.style.cssText = `
flex: 1;
overflow-y: auto;
margin-bottom: 15px;
`;
panel.appendChild(contentContainer);
// 创建关键词内容
const keywordContent = document.createElement('div');
keywordContent.className = 'tab-content';
keywordContent.dataset.content = 'keyword';
keywordContent.style.display = 'block';
// 创建正则表达式内容
const regexpContent = document.createElement('div');
regexpContent.className = 'tab-content';
regexpContent.dataset.content = 'regexp';
regexpContent.style.display = 'none';
contentContainer.appendChild(keywordContent);
contentContainer.appendChild(regexpContent);
// 添加选项卡切换功能
[keywordTab, regexpTab].forEach(tab => {
tab.addEventListener('click', function() {
// 更新选项卡样式
document.querySelectorAll('.filter-tab').forEach(t => {
t.classList.remove('active');
t.style.backgroundColor = '#f4f4f4';
t.style.color = '#666';
});
this.classList.add('active');
this.style.backgroundColor = '#fb7299';
this.style.color = 'white';
// 显示对应内容
document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = 'none';
});
const contentId = this.dataset.tab;
document.querySelector(`.tab-content[data-content="${contentId}"]`).style.display = 'block';
});
});
// 渲染过滤规则列表
function renderFilterRules() {
// 清空内容
keywordContent.innerHTML = '';
regexpContent.innerHTML = '';
// 渲染关键词列表
filterRules.keywords.forEach((keyword, index) => {
const item = createFilterItem(keyword, index, 'keyword');
keywordContent.appendChild(item);
});
// 渲染正则表达式列表
filterRules.regexps.forEach((regexp, index) => {
const item = createFilterItem(regexp, index, 'regexp');
regexpContent.appendChild(item);
});
// 添加空状态提示
if (filterRules.keywords.length === 0) {
const emptyTip = document.createElement('div');
emptyTip.textContent = '暂无关键词过滤规则';
emptyTip.style.cssText = 'text-align: center; color: #999; padding: 20px;';
keywordContent.appendChild(emptyTip);
}
if (filterRules.regexps.length === 0) {
const emptyTip = document.createElement('div');
emptyTip.textContent = '暂无正则表达式过滤规则';
emptyTip.style.cssText = 'text-align: center; color: #999; padding: 20px;';
regexpContent.appendChild(emptyTip);
}
}
// 创建过滤规则项
function createFilterItem(text, index, type) {
const item = document.createElement('div');
item.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
border-bottom: 1px solid #eee;
background-color: #f9f9f9;
margin-bottom: 5px;
border-radius: 4px;
`;
const textSpan = document.createElement('span');
textSpan.textContent = text;
textSpan.style.cssText = `
flex: 1;
word-break: break-all;
`;
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.style.cssText = `
background-color: #fb7299;
color: white;
border: none;
border-radius: 4px;
padding: 3px 8px;
cursor: pointer;
margin-left: 10px;
font-size: 12px;
`;
deleteBtn.onclick = () => {
if (type === 'keyword') {
filterRules.keywords.splice(index, 1);
} else {
filterRules.regexps.splice(index, 1);
}
saveFilterRules();
renderFilterRules();
applyFilters(); // 重新应用过滤
};
item.appendChild(textSpan);
item.appendChild(deleteBtn);
return item;
}
// 创建添加规则区域
const addContainer = document.createElement('div');
addContainer.style.cssText = `
display: flex;
margin-top: 10px;
`;
const input = document.createElement('input');
input.type = 'text';
input.placeholder = '输入要添加的过滤规则';
input.style.cssText = `
flex: 1;
padding: 8px 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 10px;
`;
const addBtn = document.createElement('button');
addBtn.textContent = '添加';
addBtn.style.cssText = `
background-color: #fb7299;
color: white;
border: none;
border-radius: 4px;
padding: 8px 15px;
cursor: pointer;
`;
addBtn.onclick = () => {
const value = input.value.trim();
if (!value) return;
// 判断当前选中的是哪个选项卡
const activeTab = document.querySelector('.filter-tab.active') || keywordTab;
const type = activeTab.dataset.tab;
if (type === 'keyword') {
// 检查是否已存在
if (!filterRules.keywords.includes(value)) {
filterRules.keywords.push(value);
saveFilterRules();
renderFilterRules();
applyFilters(); // 重新应用过滤
}
} else if (type === 'regexp') {
try {
// 验证正则表达式是否有效
new RegExp(value);
if (!filterRules.regexps.includes(value)) {
filterRules.regexps.push(value);
saveFilterRules();
renderFilterRules();
applyFilters(); // 重新应用过滤
}
} catch (e) {
alert('无效的正则表达式!');
}
}
input.value = '';
};
// 回车键添加
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
addBtn.click();
}
});
addContainer.appendChild(input);
addContainer.appendChild(addBtn);
panel.appendChild(addContainer);
// 创建过滤模式设置区域
const settingsContainer = document.createElement('div');
settingsContainer.style.cssText = `
margin-top: 15px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
border: 1px solid #eee;
`;
const settingsTitle = document.createElement('div');
settingsTitle.textContent = '过滤设置';
settingsTitle.style.cssText = `
font-weight: bold;
margin-bottom: 10px;
color: #fb7299;
`;
settingsContainer.appendChild(settingsTitle);
// 创建隐藏评论选项
const hideOptionContainer = document.createElement('div');
hideOptionContainer.style.cssText = `
display: flex;
align-items: center;
margin-bottom: 5px;
`;
const hideCheckbox = document.createElement('input');
hideCheckbox.type = 'checkbox';
hideCheckbox.id = 'hide-comments-checkbox';
hideCheckbox.checked = filterRules.hideComments;
hideCheckbox.style.cssText = `
margin-right: 8px;
`;
const hideLabel = document.createElement('label');
hideLabel.htmlFor = 'hide-comments-checkbox';
hideLabel.textContent = '直接隐藏评论(而非模糊显示)';
hideLabel.style.cssText = `
cursor: pointer;
user-select: none;
`;
hideCheckbox.addEventListener('change', () => {
filterRules.hideComments = hideCheckbox.checked;
// 如果取消隐藏,则同时取消完全隐藏
if (!filterRules.hideComments) {
filterRules.completelyHide = false;
completelyHideCheckbox.checked = false;
}
completelyHideCheckbox.disabled = !filterRules.hideComments;
completelyHideLabel.style.color = filterRules.hideComments ? '#555' : '#bbb'; // 灰色表示禁用
saveFilterRules();
applyFilters(); // 重新应用过滤
});
hideOptionContainer.appendChild(hideCheckbox);
hideOptionContainer.appendChild(hideLabel);
settingsContainer.appendChild(hideOptionContainer);
// 创建完全隐藏评论选项 (嵌套在隐藏选项下)
const completelyHideOptionContainer = document.createElement('div');
completelyHideOptionContainer.style.cssText = `
display: flex; /* 始终显示 */
align-items: center;
margin-bottom: 5px;
margin-left: 20px; /* 缩进以表示从属关系 */
`;
const completelyHideCheckbox = document.createElement('input');
completelyHideCheckbox.type = 'checkbox';
completelyHideCheckbox.id = 'completely-hide-comments-checkbox';
completelyHideCheckbox.checked = filterRules.completelyHide;
completelyHideCheckbox.style.cssText = `
margin-right: 8px;
`;
const completelyHideLabel = document.createElement('label');
completelyHideLabel.htmlFor = 'completely-hide-comments-checkbox';
completelyHideLabel.textContent = '完全隐藏(不显示占位符)';
completelyHideLabel.style.cssText = `
cursor: pointer;
user-select: none;
font-size: 13px; /* 稍小字体 */
color: #555;
`;
completelyHideCheckbox.addEventListener('change', () => {
if (completelyHideCheckbox.disabled) return; // 禁用时不允许更改
filterRules.completelyHide = completelyHideCheckbox.checked;
saveFilterRules();
applyFilters(); // 重新应用过滤
});
// 点击禁用状态的完全隐藏选项时提示
completelyHideOptionContainer.addEventListener('click', (e) => {
if (completelyHideCheckbox.disabled && e.target !== completelyHideCheckbox) { // 避免重复触发checkbox的change事件
alert('请先勾选“直接隐藏评论”选项');
}
});
// 初始化完全隐藏选项的状态
completelyHideCheckbox.disabled = !filterRules.hideComments;
completelyHideLabel.style.color = filterRules.hideComments ? '#555' : '#bbb';
completelyHideOptionContainer.appendChild(completelyHideCheckbox);
completelyHideOptionContainer.appendChild(completelyHideLabel);
// 将完全隐藏选项添加到设置容器中,位于隐藏选项之后
settingsContainer.appendChild(completelyHideOptionContainer);
// 将设置容器添加到主面板
panel.appendChild(settingsContainer);
// 移除重复添加的代码
// panel.appendChild(settingsContainer); // 这行是多余的,已在上面添加
// hideOptionContainer.appendChild(hideLabel); // 这行也是多余的,已在hideOptionContainer内部添加
// settingsContainer.appendChild(hideOptionContainer); // 这行也是多余的,已在上面添加
// 初始化渲染
renderFilterRules();
// 将面板添加到页面
document.body.appendChild(panel);
// 添加样式以标记活动选项卡
const style = document.createElement('style');
style.textContent = `
.filter-tab.active {
background-color: #fb7299 !important;
color: white !important;
}
`;
document.head.appendChild(style);
// 初始化选项卡状态
keywordTab.classList.add('active');
}
// 注册菜单命令
GM_registerMenuCommand('B站评论过滤器设置', createFilterPanel);
// 检查文本是否应该被过滤
function shouldFilter(text) {
console.log('正在检查文本:', text);
console.log('当前过滤规则:', filterRules);
// 检查关键词
for (const keyword of filterRules.keywords) {
if (text.includes(keyword)) {
console.log('匹配到关键词:', keyword);
return true;
}
}
// 检查正则表达式
for (const regexpStr of filterRules.regexps) {
try {
const regexp = new RegExp(regexpStr);
console.log('正在尝试正则表达式:', regexpStr);
if (regexp.test(text)) {
console.log('正则表达式匹配成功:', regexpStr);
return true;
}
} catch (e) {
console.error('无效的正则表达式:', regexpStr, e);
}
}
console.log('文本未匹配任何规则');
return false;
}
// 应用过滤器到评论
function applyFilters() {
// 查找评论元素 - 支持多层Shadow DOM
let commentItems = [];
let threadElements = [];
// 获取评论元素
const commentApp = document.querySelector('#commentapp > bili-comments');
if (commentApp && commentApp.shadowRoot) {
// 获取主评论
const threads = commentApp.shadowRoot.querySelectorAll('#feed > bili-comment-thread-renderer');
threads.forEach(thread => {
if (thread.shadowRoot) {
threadElements.push(thread); // 记录主评论节点
// 主评论内容
const comment = thread.shadowRoot.querySelector('#comment');
if (comment && comment.shadowRoot) {
const richText = comment.shadowRoot.querySelector('#content > bili-rich-text');
if (richText && richText.shadowRoot) {
const span = richText.shadowRoot.querySelector('#contents > span');
if (span) {
commentItems.push({span, thread, container: comment});
}
}
}
// 回复评论
const replies = thread.shadowRoot.querySelector('#replies > bili-comment-replies-renderer');
if (replies && replies.shadowRoot) {
// 已展开的回复
const replyItems = replies.shadowRoot.querySelectorAll('#expander-contents > bili-comment-reply-renderer');
replyItems.forEach(reply => {
if (reply.shadowRoot) {
const replyContent = reply.shadowRoot.querySelector('#main > bili-rich-text');
if (replyContent && replyContent.shadowRoot) {
const replySpan = replyContent.shadowRoot.querySelector('#contents > span');
if (replySpan) {
commentItems.push({span: replySpan, thread: reply, container: reply});
}
}
}
});
}
}
});
}
// 处理评论过滤 - 根据设置决定是隐藏评论还是模糊显示
commentItems.forEach(({span, thread, container}) => {
const commentText = span.textContent || '';
if (shouldFilter(commentText)) {
// 为每个评论元素创建唯一ID
if (!thread.dataset.filterId) {
thread.dataset.filterId = 'filter-' + Math.random().toString(36).substr(2, 9);
}
// 保存原始样式
if (!thread.dataset.originalStyle) {
thread.dataset.originalStyle = 'true';
thread.dataset.originalDisplay = thread.style.display || '';
thread.dataset.originalOpacity = thread.style.opacity || '1';
thread.dataset.originalFilter = thread.style.filter || 'none';
}
// 如果用户已经点击查看过,则不再应用过滤效果
if (thread.dataset.userViewed === 'true') {
thread.style.display = thread.dataset.originalDisplay;
thread.style.opacity = thread.dataset.originalOpacity || '1';
thread.style.filter = thread.dataset.originalFilter || 'none';
return;
}
// 根据设置决定是隐藏、完全隐藏还是模糊显示
if (filterRules.hideComments) {
thread.style.display = 'none'; // 隐藏评论本身
// 检查是否需要完全隐藏(无占位符)
if (!filterRules.completelyHide) {
// 检查占位符是否已存在 (更精确地检查父节点下)
const placeholderExists = thread.parentNode && thread.parentNode.querySelector(`.bili-filter-placeholder[data-filter-id="${thread.dataset.filterId}"]`);
if (!placeholderExists) {
const placeholder = document.createElement('div');
placeholder.className = 'bili-filter-placeholder';
placeholder.dataset.filterId = thread.dataset.filterId;
placeholder.style.cssText = `
padding: 10px;
margin: 5px 0;
background-color: rgba(251, 114, 153, 0.05);
border: 1px dashed rgba(251, 114, 153, 0.3);
border-radius: 4px;
color: #fb7299;
text-align: center;
cursor: pointer;
`;
placeholder.textContent = '已过滤评论,点击查看';
// 添加点击事件,显示原评论
placeholder.addEventListener('click', function() {
if (thread.dataset.filterId) {
thread.style.display = thread.dataset.originalDisplay;
thread.style.opacity = thread.dataset.originalOpacity || '1';
thread.style.filter = thread.dataset.originalFilter || 'none';
thread.dataset.userViewed = 'true';
this.remove(); // 移除占位符
}
});
// 插入到评论前面
if (thread.parentNode) {
thread.parentNode.insertBefore(placeholder, thread);
}
}
} else {
// 如果是完全隐藏,确保移除可能存在的占位符
const existingPlaceholder = thread.parentNode && thread.parentNode.querySelector(`.bili-filter-placeholder[data-filter-id="${thread.dataset.filterId}"]`);
if (existingPlaceholder) {
existingPlaceholder.remove();
}
}
} else {
// 应用模糊效果 或 恢复正常显示 (如果之前是隐藏)
thread.style.display = thread.dataset.originalDisplay;
thread.style.opacity = thread.dataset.originalOpacity || '1'; // 恢复原始透明度
thread.style.filter = 'blur(3px)'; // 应用模糊
thread.style.cursor = 'pointer'; // 鼠标悬停时显示手型光标
// 移除可能存在的占位符 (从隐藏切换到模糊时)
const existingPlaceholder = thread.parentNode && thread.parentNode.querySelector(`.bili-filter-placeholder[data-filter-id="${thread.dataset.filterId}"]`);
if (existingPlaceholder) {
existingPlaceholder.remove();
}
// 添加提示文本(仅对模糊显示的评论)
// 检查容器内是否已有提示
let hint = container.querySelector(`.bili-filter-hint[data-filter-id="${thread.dataset.filterId}"]`);
if (!hint) {
hint = document.createElement('div');
hint.className = 'bili-filter-hint';
hint.dataset.filterId = thread.dataset.filterId;
hint.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(251, 114, 153, 0.1);
color: #fb7299;
padding: 5px 10px;
border-radius: 4px;
font-size: 14px;
pointer-events: none; /* 确保提示不拦截点击 */
z-index: 1000;
text-align: center;
display: none; /* 默认隐藏 */
`;
hint.textContent = '已过滤,悬停查看';
// 确保父元素(container)有相对定位
if (window.getComputedStyle(container).position === 'static') {
container.style.position = 'relative';
}
// 确保提示在容器内
container.style.overflow = 'hidden'; // 可选
container.appendChild(hint);
}
// 添加鼠标悬停和移出事件 (确保事件监听器只添加一次到 thread)
if (!thread.dataset.filterHoverHandler) {
thread.dataset.filterHoverHandler = 'true';
thread.addEventListener('mouseenter', function() {
// 用户未点击查看过才响应悬停
if (this.dataset.userViewed !== 'true' && this.style.filter.includes('blur')) {
this.style.opacity = this.dataset.originalOpacity || '1';
this.style.filter = this.dataset.originalFilter || 'none';
const currentHint = container.querySelector(`.bili-filter-hint[data-filter-id="${this.dataset.filterId}"]`);
if (currentHint) currentHint.style.display = 'none'; // 悬停时隐藏提示
}
});
thread.addEventListener('mouseleave', function() {
// 用户未点击查看过才考虑恢复模糊
if (this.dataset.userViewed !== 'true' && !this.style.filter.includes('blur')) {
// 重新检查是否仍需过滤
const commentText = span.textContent || ''; // 从闭包中获取span
if (shouldFilter(commentText)) { // 检查当前规则
this.style.opacity = '0.6';
this.style.filter = 'blur(3px)';
// 提示逻辑可以根据需要调整
// const currentHint = container.querySelector(`.bili-filter-hint[data-filter-id="${this.dataset.filterId}"]`);
// if (currentHint) currentHint.style.display = 'block';
}
}
});
}
}
} else {
// 如果不需要过滤,恢复原始样式
if (thread.dataset.originalStyle) {
thread.style.display = thread.dataset.originalDisplay;
thread.style.opacity = thread.dataset.originalOpacity || '1';
thread.style.filter = thread.dataset.originalFilter || 'none';
thread.style.cursor = ''; // 恢复默认光标
}
// 移除提示文本和占位符 (确保移除)
if (thread.dataset.filterId) {
// 移除模糊提示 (从container移除)
const hint = container.querySelector(`.bili-filter-hint[data-filter-id="${thread.dataset.filterId}"]`);
if (hint) hint.remove();
// 移除隐藏占位符 (从thread的父节点移除)
const placeholder = thread.parentNode && thread.parentNode.querySelector(`.bili-filter-placeholder[data-filter-id="${thread.dataset.filterId}"]`);
if (placeholder) placeholder.remove();
}
// 如果评论不再需要过滤,重置相关状态
delete thread.dataset.userViewed;
// 检查并移除thread上的监听器标记和样式
if (thread.dataset.filterHoverHandler) {
delete thread.dataset.filterHoverHandler;
thread.style.cursor = ''; // 恢复默认光标
}
}
});
// 返回处理的评论数量,用于调试
return commentItems.length;
}
// 创建观察器以监视DOM变化
function createObserver() {
// 主DOM观察器 - 监视常规DOM变化
const mainObserver = new MutationObserver((mutations) => {
let shouldApply = false;
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// 检查是否添加了评论元素
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
// 检查是否是B站评论组件
if (node.tagName && node.tagName.toLowerCase().includes('bili-')) {
shouldApply = true;
// 如果是B站组件,尝试观察其Shadow DOM
observeShadowDOM(node);
break;
}
// 检查常规评论类名
if (node.classList && (
node.classList.contains('reply-item') ||
node.classList.contains('comment-item') ||
node.classList.contains('list-item') ||
node.classList.contains('reply-wrap') ||
node.classList.contains('comment-wrap')
)) {
shouldApply = true;
break;
}
// 检查子元素
const commentItems = node.querySelectorAll('.reply-item, .comment-item, .list-item, .reply-wrap, .comment-wrap');
if (commentItems.length > 0) {
shouldApply = true;
break;
}
// 检查B站组件
const biliComponents = node.querySelectorAll('bili-comment-list, bili-comments, bili-comment-thread-renderer, bili-comment-renderer, bili-comment-reply-renderer');
if (biliComponents.length > 0) {
shouldApply = true;
// 为每个B站组件添加Shadow DOM观察器
biliComponents.forEach(comp => observeShadowDOM(comp));
break;
}
}
}
}
if (shouldApply) break;
}
if (shouldApply) {
setTimeout(applyFilters, 100); // 短暂延迟以确保DOM完全更新
}
});
// 监视整个文档的变化
mainObserver.observe(document.body, {
childList: true,
subtree: true
});
// 观察Shadow DOM的函数
function observeShadowDOM(element) {
// 如果元素已经有shadowRoot,直接观察
if (element.shadowRoot) {
observeShadowRoot(element.shadowRoot, element);
}
// 监听shadowRoot的创建
const originalAttachShadow = element.attachShadow;
if (originalAttachShadow && !element._attachShadowMonitored) {
element._attachShadowMonitored = true;
element.attachShadow = function() {
const shadowRoot = originalAttachShadow.apply(this, arguments);
observeShadowRoot(shadowRoot, this);
return shadowRoot;
};
}
}
// 观察特定shadowRoot的函数
function observeShadowRoot(shadowRoot, element) {
// 创建Shadow DOM观察器
const shadowObserver = new MutationObserver((mutations) => {
let hasChanges = false;
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
hasChanges = true;
// 检查新添加的节点是否有Shadow DOM
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
// 递归观察新添加节点的Shadow DOM
if (node.tagName && node.tagName.toLowerCase().includes('bili-')) {
observeShadowDOM(node);
}
}
}
}
}
if (hasChanges) {
setTimeout(applyFilters, 100); // 短暂延迟以确保DOM完全更新
}
});
// 观察Shadow DOM的变化
shadowObserver.observe(shadowRoot, {
childList: true,
subtree: true
});
// 立即检查现有的bili组件
const biliComponents = shadowRoot.querySelectorAll('bili-comment-list, bili-comments, bili-comment-thread-renderer, bili-comment-renderer, bili-comment-reply-renderer');
biliComponents.forEach(comp => observeShadowDOM(comp));
}
// 初始扫描页面上已存在的bili组件
const initialBiliComponents = document.querySelectorAll('bili-comment-list, bili-comments, bili-comment-thread-renderer, bili-comment-renderer, bili-comment-reply-renderer');
initialBiliComponents.forEach(comp => observeShadowDOM(comp));
return mainObserver;
}
// 初始化函数
function init() {
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', onDOMReady);
} else {
onDOMReady();
}
}
// DOM加载完成后执行
function onDOMReady() {
// 创建观察器
const observer = createObserver();
// 初始应用过滤器 - 使用多次检查策略确保评论加载后被过滤
const initialCheckTimes = [500, 1500, 3000, 6000];
initialCheckTimes.forEach(delay => {
setTimeout(applyFilters, delay);
});
// 添加定期检查(页面加载后2分钟内每10秒检查一次)
let checkCount = 0;
const intervalId = setInterval(() => {
applyFilters();
checkCount++;
if (checkCount >= 12) clearInterval(intervalId); // 2分钟后停止
}, 10000);
// 使用IntersectionObserver替代滚动事件,提高性能
const intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
applyFilters();
}
});
}, {
rootMargin: '200px', // 提前200px开始观察
threshold: 0.1 // 当10%的元素可见时触发
});
// 监听页面底部区域
function observeBottomArea() {
const commentApp = document.querySelector('#commentapp');
if (commentApp) {
intersectionObserver.observe(commentApp);
}
// 定期检查并观察新出现的评论区域
setTimeout(() => {
const commentElements = document.querySelectorAll('.comment-container, .reply-list, #comment-list');
commentElements.forEach(el => intersectionObserver.observe(el));
}, 2000);
}
observeBottomArea();
// 监听页面动态加载的内容,包括各种交互按钮
document.addEventListener('click', (e) => {
// 监听各种可能触发评论加载的按钮
let target = e.target;
for (let i = 0; i < 5 && target; i++) {
if (target.tagName === 'BUTTON' || target.tagName === 'A' ||
(target.getAttribute && target.getAttribute('role') === 'button')) {
const text = target.textContent || target.innerText || '';
if (/更多|查看|展开|收起|下一页|上一页|回复|评论|点击|加载/.test(text)) {
// 使用多次延迟检查,确保动态加载的内容被过滤
setTimeout(applyFilters, 300);
setTimeout(applyFilters, 800);
setTimeout(applyFilters, 1500);
break;
}
}
target = target.parentElement;
}
// 监听回复展开和其他可能的交互元素
const interactiveSelectors = [
'.reply-expander', '.expand-btn', '.reply-btn', '.comment-btn',
'.load-more', '.show-more', '.view-more', '.btn-more'
];
for (const selector of interactiveSelectors) {
if ((e.target.matches && e.target.matches(selector)) ||
(e.target.closest && e.target.closest(selector))) {
// 使用多次延迟检查
setTimeout(applyFilters, 300);
setTimeout(applyFilters, 800);
setTimeout(applyFilters, 1500);
break;
}
}
});
// 特别监听B站"查看更多"按钮
function monitorViewMoreButtons() {
// 使用定时器定期检查是否存在"查看更多"按钮和分页按钮
setInterval(() => {
try {
// 尝试获取评论区的查看更多按钮
const commentApp = document.querySelector('#commentapp > bili-comments');
if (commentApp && commentApp.shadowRoot) {
const threads = commentApp.shadowRoot.querySelectorAll('#feed > bili-comment-thread-renderer');
threads.forEach(thread => {
if (thread.shadowRoot) {
const replies = thread.shadowRoot.querySelector('#replies > bili-comment-replies-renderer');
if (replies && replies.shadowRoot) {
// 监听查看更多按钮
const viewMoreBtn = replies.shadowRoot.querySelector('#view-more > bili-text-button');
if (viewMoreBtn && !viewMoreBtn.dataset.filterMonitored) {
// 标记已监听,避免重复添加
viewMoreBtn.dataset.filterMonitored = 'true';
viewMoreBtn.addEventListener('click', () => {
// 立即触发一次过滤
setTimeout(applyFilters, 100);
// 再延迟触发几次,确保新加载的内容被过滤
setTimeout(applyFilters, 500);
setTimeout(applyFilters, 1000);
});
}
// 监听分页区域的按钮
const paginationBody = replies.shadowRoot.querySelector('#pagination-body');
if (paginationBody && !paginationBody.dataset.filterMonitored) {
paginationBody.dataset.filterMonitored = 'true';
paginationBody.addEventListener('click', (e) => {
// 使用多次延迟过滤确保分页后的内容被过滤
setTimeout(applyFilters, 100);
setTimeout(applyFilters, 500);
setTimeout(applyFilters, 1000);
});
}
// 监听底部的查看更多按钮
const paginationFoot = replies.shadowRoot.querySelector('#pagination-foot > bili-text-button');
if (paginationFoot && paginationFoot.shadowRoot) {
const footButton = paginationFoot.shadowRoot.querySelector('button');
if (footButton && !footButton.dataset.filterMonitored) {
footButton.dataset.filterMonitored = 'true';
footButton.addEventListener('click', () => {
// 使用多次延迟过滤确保加载的内容被过滤
setTimeout(applyFilters, 100);
setTimeout(applyFilters, 500);
setTimeout(applyFilters, 1000);
});
}
}
}
}
});
}
} catch (e) {
console.error('监听评论区按钮时出错:', e);
}
}, 2000); // 每2秒检查一次
}
monitorViewMoreButtons();
// 监听键盘事件,可能是用户发表评论
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
// 可能是发送评论的快捷键
setTimeout(() => {
applyFilters();
}, 1000);
}
});
// 添加样式
const style = document.createElement('style');
style.textContent = `
.bili-comment-mask {
user-select: none;
overflow: hidden;
z-index: 9999 !important;
pointer-events: auto !important;
display: flex !important;
}
`;
document.head.appendChild(style);
// 定期清理不再存在的评论对应的遮罩
setInterval(() => {
const masks = document.querySelectorAll('.bili-comment-mask');
masks.forEach(mask => {
const filterId = mask.dataset.filterId;
if (filterId) {
// 检查对应的评论元素是否还存在
const commentElement = document.querySelector(`[data-filter-id="${filterId}"]`);
if (!commentElement) {
// 如果评论元素不存在,移除遮罩
mask.remove();
}
}
});
}, 30000); // 每30秒清理一次
}
// 启动脚本
init();
})();