// ==UserScript==
// @name V2EX内容过滤器
// @namespace http://tampermonkey.net/
// @version 1.4
// @description 屏蔽V2EX标题包含关键词的内容,并记录被屏蔽的内容
// @author vitahuang
// @match https://www.v2ex.com/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 存储当前页面被屏蔽的项目
let blockedItems = [];
// 屏蔽计数提示元素
let counterElement = null;
// 默认屏蔽词列表
const DEFAULT_BLOCK_KEYWORDS = [
// 示例:"凡人修仙传"
];
// 获取存储的屏蔽词列表
function getBlockKeywords() {
return GM_getValue('blockKeywords', DEFAULT_BLOCK_KEYWORDS);
}
// 保存屏蔽词列表
function saveBlockKeywords(keywords) {
GM_setValue('blockKeywords', keywords);
}
// 创建并更新屏蔽计数提示
function createOrUpdateCounter() {
// 如果还没有创建计数器元素
if (!counterElement) {
counterElement = document.createElement('div');
counterElement.id = 'blocked-counter';
counterElement.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: rgba(44, 62, 80, 0.9);
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: 13px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
z-index: 99999;
transition: all 0.3s ease;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
opacity: 0.8;
`;
// 添加图标
const icon = document.createElement('span');
icon.innerHTML = '🔒';
counterElement.appendChild(icon);
// 添加文本容器
const textSpan = document.createElement('span');
textSpan.id = 'counter-text';
counterElement.appendChild(textSpan);
// 点击计数器可以查看被屏蔽内容
counterElement.addEventListener('click', showBlockedItems);
// 鼠标悬停时增加透明度
counterElement.addEventListener('mouseover', () => {
counterElement.style.opacity = '1';
counterElement.style.transform = 'scale(1.05)';
});
// 鼠标离开时恢复透明度
counterElement.addEventListener('mouseout', () => {
counterElement.style.opacity = '0.8';
counterElement.style.transform = 'scale(1)';
});
document.body.appendChild(counterElement);
}
// 更新计数器文本
const textSpan = document.getElementById('counter-text');
if (blockedItems.length === 1) {
textSpan.textContent = `已屏蔽 1 条内容`;
} else {
textSpan.textContent = `已屏蔽 ${blockedItems.length} 条内容`;
}
// 根据是否有屏蔽内容显示或隐藏计数器
if (blockedItems.length === 0) {
counterElement.style.display = 'none';
} else {
counterElement.style.display = 'flex';
}
}
// 检查标题是否包含屏蔽词
function shouldBlock(titleElement) {
if (!titleElement) return false;
const titleText = titleElement.textContent.trim().toLowerCase();
const keywords = getBlockKeywords().map(k => k.trim().toLowerCase());
return keywords.some(keyword =>
keyword && titleText.includes(keyword)
);
}
// 屏蔽元素样式
const BLOCK_STYLES = `
display: none !important;
`;
// 屏蔽符合条件的内容并记录
function blockContent() {
// 针对特定网站的选择器
const containerSelector = '.cell.item';
const titleSelector = '.item_title a.topic-link';
document.querySelectorAll(containerSelector).forEach(container => {
const titleElement = container.querySelector(titleSelector);
if (titleElement && shouldBlock(titleElement)) {
// 提取标题和链接
const title = titleElement.textContent.trim();
const href = titleElement.href;
const keyword = getMatchedKeyword(titleElement);
// 检查是否已记录,避免重复
const isAlreadyRecorded = blockedItems.some(
item => item.href === href
);
if (!isAlreadyRecorded) {
blockedItems.push({
title,
href,
keyword,
time: new Date().toLocaleTimeString()
});
// 更新计数器
createOrUpdateCounter();
}
// 屏蔽内容
container.style.cssText += BLOCK_STYLES;
container.setAttribute('data-blocked-by', 'content-filter-with-tags');
}
});
}
// 获取匹配的关键词
function getMatchedKeyword(titleElement) {
const titleText = titleElement.textContent.trim().toLowerCase();
const keywords = getBlockKeywords();
return keywords.find(keyword =>
titleText.includes(keyword.toLowerCase())
) || '未知关键词';
}
// 创建标签式关键词管理界面
function createKeywordManagerUI() {
// 移除已存在的界面(如果有)
const existingUI = document.getElementById('keyword-manager-ui');
if (existingUI) {
existingUI.remove();
}
// 创建遮罩层
const overlay = document.createElement('div');
overlay.id = 'keyword-manager-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 999999;
backdrop-filter: blur(3px);
`;
// 创建主容器
const container = document.createElement('div');
container.id = 'keyword-manager-ui';
container.style.cssText = `
background: #fff;
width: 90%;
max-width: 700px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
overflow: hidden;
`;
// 创建标题栏
const header = document.createElement('div');
header.style.cssText = `
background: #2c3e50;
color: white;
padding: 18px 24px;
font-size: 18px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
`;
header.textContent = '关键词管理';
// 关闭按钮
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '×';
closeBtn.style.cssText = `
background: transparent;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.2s;
`;
closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255,255,255,0.2)';
closeBtn.onmouseout = () => closeBtn.style.background = 'transparent';
closeBtn.onclick = () => overlay.remove();
header.appendChild(closeBtn);
// 内容区域
const content = document.createElement('div');
content.style.cssText = `
padding: 24px;
`;
// 添加关键词区域
const addKeywordContainer = document.createElement('div');
addKeywordContainer.style.cssText = `
display: flex;
gap: 10px;
margin-bottom: 24px;
align-items: center;
`;
// 输入框
const keywordInput = document.createElement('input');
keywordInput.type = 'text';
keywordInput.placeholder = '输入新的关键词...';
keywordInput.style.cssText = `
flex-grow: 1;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
`;
// 新增按钮
const addBtn = document.createElement('button');
addBtn.textContent = '新增';
addBtn.style.cssText = `
padding: 10px 20px;
border: none;
border-radius: 6px;
background: #3498db;
color: white;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
`;
addBtn.onmouseover = () => addBtn.style.background = '#2980b9';
addBtn.onmouseout = () => addBtn.style.background = '#3498db';
addKeywordContainer.appendChild(keywordInput);
addKeywordContainer.appendChild(addBtn);
content.appendChild(addKeywordContainer);
// 已添加关键词区域标题
const tagsTitle = document.createElement('h3');
tagsTitle.textContent = '已添加的关键词';
tagsTitle.style.cssText = `
margin: 0 0 16px 0;
font-size: 16px;
color: #333;
`;
content.appendChild(tagsTitle);
// 标签容器
const tagsContainer = document.createElement('div');
tagsContainer.id = 'keywords-tags-container';
tagsContainer.style.cssText = `
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
min-height: 40px;
`;
content.appendChild(tagsContainer);
// 无关键词时的提示
const emptyHint = document.createElement('div');
emptyHint.id = 'empty-keywords-hint';
emptyHint.textContent = '暂无关键词,添加一个开始使用吧';
emptyHint.style.cssText = `
color: #999;
font-size: 14px;
padding: 10px 0;
`;
content.appendChild(emptyHint);
// 底部按钮区域
const footer = document.createElement('div');
footer.style.cssText = `
padding: 16px 24px;
background: #f9f9f9;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
`;
// 清空按钮
const clearAllBtn = document.createElement('button');
clearAllBtn.textContent = '清空所有';
clearAllBtn.style.cssText = `
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
transition: all 0.2s;
`;
clearAllBtn.onmouseover = () => clearAllBtn.style.background = '#e8e8e8';
clearAllBtn.onmouseout = () => clearAllBtn.style.background = '#f5f5f5';
// 保存按钮
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存设置';
saveBtn.style.cssText = `
padding: 8px 20px;
border: none;
border-radius: 4px;
background: #3498db;
color: white;
cursor: pointer;
transition: all 0.2s;
`;
saveBtn.onmouseover = () => saveBtn.style.background = '#2980b9';
saveBtn.onmouseout = () => saveBtn.style.background = '#3498db';
footer.appendChild(clearAllBtn);
footer.appendChild(saveBtn);
// 组装界面
container.appendChild(header);
container.appendChild(content);
container.appendChild(footer);
overlay.appendChild(container);
document.body.appendChild(overlay);
// 加载现有关键词并显示为标签
let currentKeywords = [...getBlockKeywords()];
updateKeywordTags();
// 更新标签显示
function updateKeywordTags() {
// 清空现有标签
tagsContainer.innerHTML = '';
// 显示或隐藏空提示
emptyHint.style.display = currentKeywords.length === 0 ? 'block' : 'none';
// 添加所有关键词标签
currentKeywords.forEach((keyword, index) => {
const tag = document.createElement('div');
tag.style.cssText = `
background: #f1f5f9;
color: #333;
padding: 6px 12px;
border-radius: 20px;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
`;
// 关键词文本
const tagText = document.createElement('span');
tagText.textContent = keyword;
tag.appendChild(tagText);
// 删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.innerHTML = '×';
deleteBtn.style.cssText = `
background: transparent;
border: none;
color: #999;
cursor: pointer;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 12px;
transition: all 0.2s;
`;
deleteBtn.onmouseover = () => {
deleteBtn.style.color = '#e74c3c';
tag.style.background = '#fef2f2';
};
deleteBtn.onmouseout = () => {
deleteBtn.style.color = '#999';
tag.style.background = '#f1f5f9';
};
// 删除确认
deleteBtn.onclick = () => {
if (confirm(`确定要删除关键词"${keyword}"吗?`)) {
currentKeywords.splice(index, 1);
updateKeywordTags();
}
};
tag.appendChild(deleteBtn);
tagsContainer.appendChild(tag);
});
}
// 添加关键词
function addKeyword() {
const keyword = keywordInput.value.trim();
if (!keyword) {
alert('请输入关键词');
return;
}
if (currentKeywords.includes(keyword)) {
alert('该关键词已存在');
keywordInput.value = '';
return;
}
currentKeywords.push(keyword);
keywordInput.value = '';
updateKeywordTags();
// 自动聚焦输入框
keywordInput.focus();
}
// 绑定添加按钮事件
addBtn.addEventListener('click', addKeyword);
// 绑定回车键添加关键词
keywordInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
addKeyword();
}
});
// 清空所有关键词
clearAllBtn.addEventListener('click', () => {
if (currentKeywords.length === 0) {
alert('当前没有关键词可清空');
return;
}
if (confirm(`确定要清空所有(${currentKeywords.length}个)关键词吗?`)) {
currentKeywords = [];
updateKeywordTags();
}
});
// 保存关键词设置
saveBtn.addEventListener('click', () => {
// 过滤空关键词
const filteredKeywords = currentKeywords.filter(k => k.trim().length > 0);
saveBlockKeywords(filteredKeywords);
// 显示保存成功提示
const successMsg = document.createElement('div');
successMsg.style.cssText = `
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #2ecc71;
color: white;
padding: 10px 20px;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 1000000;
`;
successMsg.textContent = `已保存 ${filteredKeywords.length} 个关键词`;
document.body.appendChild(successMsg);
// 关闭提示和配置界面
setTimeout(() => {
successMsg.remove();
overlay.remove();
location.reload(); // 刷新页面应用更改
}, 1500);
});
// 自动聚焦输入框
keywordInput.focus();
}
// 显示被屏蔽的内容列表
function showBlockedItems() {
// 移除已存在的界面(如果有)
const existingUI = document.getElementById('blocked-items-ui');
if (existingUI) {
existingUI.remove();
}
// 如果没有被屏蔽的内容
if (blockedItems.length === 0) {
alert('当前页面没有被屏蔽的内容');
return;
}
// 创建遮罩层
const overlay = document.createElement('div');
overlay.id = 'blocked-items-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 999999;
backdrop-filter: blur(3px);
`;
// 创建主容器
const container = document.createElement('div');
container.id = 'blocked-items-ui';
container.style.cssText = `
background: #fff;
width: 90%;
max-width: 700px;
max-height: 80vh;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
overflow: hidden;
display: flex;
flex-direction: column;
`;
// 创建标题栏
const header = document.createElement('div');
header.style.cssText = `
background: #2c3e50;
color: white;
padding: 18px 24px;
font-size: 18px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
`;
header.textContent = `被屏蔽的内容 (共 ${blockedItems.length} 项)`;
// 关闭按钮
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '×';
closeBtn.style.cssText = `
background: transparent;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.2s;
`;
closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255,255,255,0.2)';
closeBtn.onmouseout = () => closeBtn.style.background = 'transparent';
closeBtn.onclick = () => overlay.remove();
header.appendChild(closeBtn);
// 内容区域(带滚动)
const content = document.createElement('div');
content.style.cssText = `
padding: 16px;
overflow-y: auto;
flex-grow: 1;
`;
// 添加被屏蔽的项目
blockedItems.forEach((item, index) => {
const itemContainer = document.createElement('div');
itemContainer.style.cssText = `
padding: 16px;
border-bottom: 1px solid #eee;
${index === blockedItems.length - 1 ? 'border-bottom: none;' : ''}
transition: background 0.2s;
`;
itemContainer.onmouseover = () => itemContainer.style.background = '#f9f9f9';
itemContainer.onmouseout = () => itemContainer.style.background = 'transparent';
// 标题和链接
const titleLink = document.createElement('a');
titleLink.href = item.href;
titleLink.target = '_blank'; // 在新标签页打开
titleLink.textContent = item.title;
titleLink.style.cssText = `
color: #3498db;
text-decoration: none;
font-size: 16px;
display: block;
margin-bottom: 8px;
word-break: break-word;
`;
titleLink.onmouseover = () => titleLink.style.textDecoration = 'underline';
titleLink.onmouseout = () => titleLink.style.textDecoration = 'none';
// 附加信息(关键词)
const info = document.createElement('div');
info.style.cssText = `
font-size: 13px;
color: #777;
display: flex;
justify-content: space-between;
`;
const keywordSpan = document.createElement('span');
keywordSpan.innerHTML = `屏蔽原因: <span style="color: #e74c3c;">${item.keyword}</span>`;
const timeSpan = document.createElement('span');
timeSpan.textContent = item.time;
info.appendChild(keywordSpan);
info.appendChild(timeSpan);
itemContainer.appendChild(titleLink);
itemContainer.appendChild(info);
content.appendChild(itemContainer);
});
// 底部按钮区域
const footer = document.createElement('div');
footer.style.cssText = `
padding: 12px 24px;
background: #f9f9f9;
border-top: 1px solid #eee;
text-align: right;
`;
const closeBtnFooter = document.createElement('button');
closeBtnFooter.textContent = '关闭';
closeBtnFooter.style.cssText = `
padding: 8px 20px;
border: none;
border-radius: 4px;
background: #3498db;
color: white;
cursor: pointer;
transition: all 0.2s;
`;
closeBtnFooter.onmouseover = () => closeBtnFooter.style.background = '#2980b9';
closeBtnFooter.onmouseout = () => closeBtnFooter.style.background = '#3498db';
closeBtnFooter.onclick = () => overlay.remove();
footer.appendChild(closeBtnFooter);
// 组装界面
container.appendChild(header);
container.appendChild(content);
container.appendChild(footer);
overlay.appendChild(container);
document.body.appendChild(overlay);
}
// 初始化计数器
function initCounter() {
createOrUpdateCounter();
}
// 注册(不可用)油猴菜单命令
GM_registerMenuCommand('关键词管理', createKeywordManagerUI);
GM_registerMenuCommand('被屏蔽内容', showBlockedItems);
// 页面加载完成后执行初始化
window.addEventListener('load', () => {
initCounter();
blockContent();
});
// 监听页面动态内容变化
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.addedNodes.length) {
blockContent();
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();