V2EX内容过滤器

屏蔽V2EX标题包含关键词的内容,并记录被屏蔽的内容

当前为 2025-08-18 提交的版本,查看 最新版本

// ==UserScript==
// @name         V2EX内容过滤器
// @namespace    http://tampermonkey.net/
// @version      1.3
// @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-counter');
            }
        });
    }

    // 获取匹配的关键词
    function getMatchedKeyword(titleElement) {
        const titleText = titleElement.textContent.trim().toLowerCase();
        const keywords = getBlockKeywords();

        return keywords.find(keyword =>
            titleText.includes(keyword.toLowerCase())
        ) || '未知关键词';
    }

    // 创建美观的配置界面
    function createConfigUI() {
        // 移除已存在的配置界面(如果有)
        const existingUI = document.getElementById('keyword-filter-ui');
        if (existingUI) {
            existingUI.remove();
        }

        // 创建遮罩层
        const overlay = document.createElement('div');
        overlay.id = 'keyword-filter-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-filter-ui';
        container.style.cssText = `
            background: #fff;
            width: 90%;
            max-width: 600px;
            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 description = document.createElement('p');
        description.style.cssText = `
            margin: 0 0 16px 0;
            color: #555;
            line-height: 1.5;
        `;
        description.textContent = '请输入要屏蔽的关键词,每个关键词单独一行:';
        content.appendChild(description);

        // 文本输入区域
        const textarea = document.createElement('textarea');
        textarea.style.cssText = `
            width: 100%;
            height: 200px;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-family: inherit;
            font-size: 14px;
            resize: vertical;
            box-sizing: border-box;
            margin-bottom: 16px;
            line-height: 1.6;
        `;
        textarea.placeholder = '例如:\n凡人修仙传\n广告\n推广';
        textarea.value = getBlockKeywords().join('\n');
        content.appendChild(textarea);

        // 关键词数量统计
        const countInfo = document.createElement('div');
        countInfo.id = 'keyword-count-info';
        countInfo.style.cssText = `
            color: #666;
            font-size: 13px;
            margin-bottom: 20px;
            text-align: right;
        `;
        countInfo.textContent = `当前关键词数量: ${getBlockKeywords().length}`;
        content.appendChild(countInfo);

        // 监听输入变化,更新计数
        textarea.addEventListener('input', () => {
            const count = textarea.value
                .split('\n')
                .map(k => k.trim())
                .filter(k => k.length > 0)
                .length;
            countInfo.textContent = `当前关键词数量: ${count}`;
        });

        // 按钮区域
        const buttons = document.createElement('div');
        buttons.style.cssText = `
            display: flex;
            justify-content: flex-end;
            gap: 12px;
        `;

        // 清空按钮
        const clearBtn = document.createElement('button');
        clearBtn.textContent = '清空';
        clearBtn.style.cssText = `
            padding: 8px 16px;
            border: 1px solid #ddd;
            border-radius: 4px;
            background: #f5f5f5;
            cursor: pointer;
            transition: all 0.2s;
        `;
        clearBtn.onmouseover = () => clearBtn.style.background = '#e8e8e8';
        clearBtn.onmouseout = () => clearBtn.style.background = '#f5f5f5';
        clearBtn.onclick = () => {
            if (confirm('确定要清空所有屏蔽词吗?')) {
                textarea.value = '';
                countInfo.textContent = '当前关键词数量: 0';
            }
        };

        // 保存按钮
        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';
        saveBtn.onclick = () => {
            // 处理输入,正确处理换行并过滤空行
            const newKeywords = textarea.value
                .split('\n')
                .map(k => k.trim())
                .filter(k => k.length > 0);

            saveBlockKeywords(newKeywords);

            // 显示保存成功提示
            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 = `已保存 ${newKeywords.length} 个屏蔽词`;
            document.body.appendChild(successMsg);

            // 关闭提示和配置界面
            setTimeout(() => {
                successMsg.remove();
                overlay.remove();
                location.reload(); // 刷新页面应用更改
            }, 1500);
        };

        buttons.appendChild(clearBtn);
        buttons.appendChild(saveBtn);
        content.appendChild(buttons);

        // 组装界面
        container.appendChild(header);
        container.appendChild(content);
        overlay.appendChild(container);
        document.body.appendChild(overlay);
    }

    // 显示被屏蔽的内容列表
    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 showKeywords() {
        const keywords = getBlockKeywords();
        if (keywords.length === 0) {
            alert('当前没有设置任何屏蔽词。');
            return;
        }

        alert(`当前屏蔽词列表 (共 ${keywords.length} 个):\n\n${keywords.join('\n')}`);
    }

    // 初始化计数器
    function initCounter() {
        createOrUpdateCounter();
    }

    // 注册(不可用)油猴菜单命令
    GM_registerMenuCommand('设置屏蔽词', createConfigUI);
    GM_registerMenuCommand('查看屏蔽词', showKeywords);
    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
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址