知乎问题屏蔽器

自定义关键词屏蔽知乎推荐问题

// ==UserScript==
// @name         知乎问题屏蔽器
// @namespace    zhihu-question-blocker
// @version      1.0.0
// @description  自定义关键词屏蔽知乎推荐问题
// @match        https://www.zhihu.com/*
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// ==/UserScript==

(() => {
    'use strict';

    // 默认屏蔽关键词
    const DEFAULT_KEYWORDS = [
        '如何看待',
        '怎么看',
        '有什么看法',
        '明星',
        '娱乐圈',
        '网红',
        '直播',
        '游戏主播'
    ];

    // 获取用户设置的关键词
    let blockedKeywords = GM_getValue('blockedKeywords', DEFAULT_KEYWORDS);

    // 添加样式
    GM_addStyle(`
        .zhihu-blocker-hidden {
            display: none !important;
        }

        .zhihu-blocker-settings {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 500px;
            max-height: 600px;
            background: #fff;
            border-radius: 8px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
            z-index: 10000;
            padding: 20px;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
        }

        .zhihu-blocker-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9999;
        }

        .zhihu-blocker-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
        }

        .zhihu-blocker-title {
            font-size: 18px;
            font-weight: 600;
            color: #333;
        }

        .zhihu-blocker-close {
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            color: #999;
            padding: 0;
            width: 30px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .zhihu-blocker-close:hover {
            color: #333;
        }

        .zhihu-blocker-input-group {
            margin-bottom: 15px;
        }

        .zhihu-blocker-label {
            display: block;
            margin-bottom: 5px;
            font-weight: 500;
            color: #333;
        }

        .zhihu-blocker-input {
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            box-sizing: border-box;
        }

        .zhihu-blocker-keywords {
            max-height: 300px;
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 10px;
            background: #f9f9f9;
        }

        .zhihu-blocker-keyword-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 5px 8px;
            margin-bottom: 5px;
            background: #fff;
            border-radius: 4px;
            border: 1px solid #eee;
        }

        .zhihu-blocker-keyword-text {
            flex: 1;
            font-size: 14px;
            color: #333;
        }

        .zhihu-blocker-remove-btn {
            background: #ff4757;
            color: white;
            border: none;
            border-radius: 3px;
            padding: 2px 8px;
            font-size: 12px;
            cursor: pointer;
        }

        .zhihu-blocker-remove-btn:hover {
            background: #ff3742;
        }

        .zhihu-blocker-buttons {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            margin-top: 20px;
            padding-top: 15px;
            border-top: 1px solid #eee;
        }

        .zhihu-blocker-btn {
            padding: 8px 16px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
        }

        .zhihu-blocker-btn-primary {
            background: #0084ff;
            color: white;
        }

        .zhihu-blocker-btn-primary:hover {
            background: #0066cc;
        }

        .zhihu-blocker-btn-secondary {
            background: #f5f5f5;
            color: #333;
        }

        .zhihu-blocker-btn-secondary:hover {
            background: #e8e8e8;
        }

        .zhihu-blocker-stats {
            position: fixed;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 8px 12px;
            border-radius: 4px;
            font-size: 12px;
            z-index: 1000;
            display: none;
        }

        /* 左侧快速输入框 */
        .zhihu-blocker-quick-input {
            position: fixed;
            left: -300px;
            top: 50%;
            transform: translateY(-50%);
            width: 300px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border-radius: 0 12px 12px 0;
            box-shadow: 2px 0 20px rgba(0, 0, 0, 0.3);
            z-index: 9998;
            transition: left 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
        }

        .zhihu-blocker-quick-input.show {
            left: 0;
        }

        .zhihu-blocker-quick-tab {
            position: absolute;
            right: -8px;
            top: 50%;
            transform: translateY(-50%);
            width: 16px;
            height: 16px;
            background: radial-gradient(circle, rgba(102, 126, 234, 0.8) 0%, rgba(118, 75, 162, 0.3) 70%, transparent 100%);
            border-radius: 50%;
            cursor: pointer;
            transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }

        .zhihu-blocker-quick-tab:hover {
            width: 32px;
            height: 32px;
            right: -16px;
            opacity: 0.9;
            background: radial-gradient(circle, rgba(102, 126, 234, 1) 0%, rgba(118, 75, 162, 0.8) 60%, rgba(118, 75, 162, 0.3) 80%, transparent 100%);
            box-shadow: 0 0 16px rgba(102, 126, 234, 0.6);
            transform: translateY(-50%) scale(1.1);
        }

        .zhihu-blocker-quick-tab::after {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 8px;
            height: 8px;
            background: rgba(255, 255, 255, 0);
            border-radius: 50%;
            transition: all 0.3s ease;
        }

        .zhihu-blocker-quick-tab:hover::after {
            width: 12px;
            height: 12px;
            background: rgba(255, 255, 255, 0.8);
            box-shadow: 0 0 6px rgba(255, 255, 255, 0.4);
        }

        .zhihu-blocker-quick-content {
            padding: 20px;
            color: white;
        }

        .zhihu-blocker-quick-title {
            font-size: 16px;
            font-weight: 600;
            margin-bottom: 15px;
            text-align: center;
            color: white;
        }

        .zhihu-blocker-quick-input-field {
            width: 100%;
            padding: 10px 12px;
            border: none;
            border-radius: 6px;
            font-size: 14px;
            background: rgba(255, 255, 255, 0.9);
            color: #333;
            margin-bottom: 10px;
            box-sizing: border-box;
            transition: all 0.2s ease;
        }

        .zhihu-blocker-quick-input-field:focus {
            outline: none;
            background: rgba(255, 255, 255, 1);
            box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3);
        }

        .zhihu-blocker-quick-input-field::placeholder {
            color: #999;
        }

        .zhihu-blocker-quick-add-btn {
            width: 100%;
            padding: 10px;
            background: rgba(255, 255, 255, 0.2);
            border: 1px solid rgba(255, 255, 255, 0.3);
            border-radius: 6px;
            color: white;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s ease;
            margin-bottom: 15px;
        }

        .zhihu-blocker-quick-add-btn:hover {
            background: rgba(255, 255, 255, 0.3);
            border-color: rgba(255, 255, 255, 0.5);
        }

        .zhihu-blocker-quick-recent {
            border-top: 1px solid rgba(255, 255, 255, 0.2);
            padding-top: 15px;
        }

        .zhihu-blocker-quick-recent-title {
            font-size: 12px;
            color: rgba(255, 255, 255, 0.8);
            margin-bottom: 8px;
        }

        .zhihu-blocker-quick-keyword {
            display: inline-block;
            background: rgba(255, 255, 255, 0.2);
            color: white;
            padding: 4px 8px;
            border-radius: 12px;
            font-size: 12px;
            margin: 2px 4px 2px 0;
            cursor: pointer;
            transition: all 0.2s ease;
        }

        .zhihu-blocker-quick-keyword:hover {
            background: rgba(255, 255, 255, 0.3);
        }

        .zhihu-blocker-quick-stats {
            text-align: center;
            font-size: 12px;
            color: rgba(255, 255, 255, 0.8);
            margin-top: 10px;
        }
    `);

    // 统计信息
    let blockedCount = 0;

    // 创建统计显示元素
    const statsElement = document.createElement('div');
    statsElement.className = 'zhihu-blocker-stats';
    statsElement.textContent = '已屏蔽: 0 个问题';
    document.body.appendChild(statsElement);

    // 创建快速输入框
    const quickInputPanel = createQuickInputPanel();

    // 更新统计显示
    function updateStats() {
        statsElement.textContent = `已屏蔽: ${blockedCount} 个问题`;
        if (blockedCount > 0) {
            statsElement.style.display = 'block';
            setTimeout(() => {
                statsElement.style.display = 'none';
            }, 3000);
        }

        // 同时更新快速输入框的统计
        if (quickInputPanel && quickInputPanel.updateQuickStats) {
            quickInputPanel.updateQuickStats();
        }
    }

    // 检查文本是否包含屏蔽关键词
    function containsBlockedKeyword(text) {
        if (!text) return false;
        const lowerText = text.toLowerCase();
        return blockedKeywords.some(keyword =>
            lowerText.includes(keyword.toLowerCase())
        );
    }

    // 屏蔽问题元素
    function blockElement(element, reason) {
        element.classList.add('zhihu-blocker-hidden');
        blockedCount++;
        console.log(`[知乎屏蔽器] 屏蔽问题: ${reason}`);
        updateStats();
    }

    // 检查并屏蔽问题
    function checkAndBlockQuestions() {
        // 知乎首页推荐问题选择器
        const selectors = [
            '.ContentItem', // 主要内容项
            '.TopstoryItem', // 推荐内容项
            '.Card', // 卡片式内容
            '[data-zop-feedtype]', // 带有 feed 类型的元素
            '.List-item' // 列表项
        ];

        selectors.forEach(selector => {
            document.querySelectorAll(selector).forEach(item => {
                if (item.classList.contains('zhihu-blocker-hidden')) return;

                // 获取问题标题
                const titleElements = item.querySelectorAll([
                    'h2 a',
                    '.ContentItem-title a',
                    '.QuestionItem-title a',
                    '[data-za-detail-view-element_name="Title"] a',
                    '.RichText',
                    '.ContentItem-title'
                ].join(','));

                let shouldBlock = false;
                let blockReason = '';

                titleElements.forEach(titleEl => {
                    const titleText = titleEl.textContent || titleEl.innerText;
                    if (containsBlockedKeyword(titleText)) {
                        shouldBlock = true;
                        blockReason = titleText.substring(0, 50) + '...';
                    }
                });

                // 检查问题描述
                const descElements = item.querySelectorAll([
                    '.RichText',
                    '.ContentItem-meta',
                    '.QuestionRichText'
                ].join(','));

                descElements.forEach(descEl => {
                    const descText = descEl.textContent || descEl.innerText;
                    if (containsBlockedKeyword(descText)) {
                        shouldBlock = true;
                        if (!blockReason) {
                            blockReason = descText.substring(0, 50) + '...';
                        }
                    }
                });

                if (shouldBlock) {
                    blockElement(item, blockReason);
                }
            });
        });
    }

    // 创建设置界面
    function createSettingsUI() {
        const overlay = document.createElement('div');
        overlay.className = 'zhihu-blocker-overlay';

        const modal = document.createElement('div');
        modal.className = 'zhihu-blocker-settings';

        modal.innerHTML = `
            <div class="zhihu-blocker-header">
                <div class="zhihu-blocker-title">知乎问题屏蔽设置</div>
                <button class="zhihu-blocker-close">×</button>
            </div>

            <div class="zhihu-blocker-input-group">
                <label class="zhihu-blocker-label">添加屏蔽关键词:</label>
                <input type="text" class="zhihu-blocker-input" placeholder="输入关键词后按回车添加" id="keyword-input">
            </div>

            <div class="zhihu-blocker-input-group">
                <label class="zhihu-blocker-label">当前屏蔽关键词:</label>
                <div class="zhihu-blocker-keywords" id="keywords-list"></div>
            </div>

            <div class="zhihu-blocker-buttons">
                <button class="zhihu-blocker-btn zhihu-blocker-btn-secondary" id="reset-btn">重置为默认</button>
                <button class="zhihu-blocker-btn zhihu-blocker-btn-primary" id="save-btn">保存设置</button>
            </div>
        `;

        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        // 渲染关键词列表
        function renderKeywords() {
            const keywordsList = modal.querySelector('#keywords-list');
            keywordsList.innerHTML = '';

            blockedKeywords.forEach((keyword, index) => {
                const item = document.createElement('div');
                item.className = 'zhihu-blocker-keyword-item';
                item.innerHTML = `
                    <span class="zhihu-blocker-keyword-text">${keyword}</span>
                    <button class="zhihu-blocker-remove-btn" data-index="${index}">删除</button>
                `;
                keywordsList.appendChild(item);
            });
        }

        renderKeywords();

        // 事件绑定
        const keywordInput = modal.querySelector('#keyword-input');
        const closeBtn = modal.querySelector('.zhihu-blocker-close');
        const saveBtn = modal.querySelector('#save-btn');
        const resetBtn = modal.querySelector('#reset-btn');

        // 添加关键词
        keywordInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                const keyword = keywordInput.value.trim();
                if (keyword && !blockedKeywords.includes(keyword)) {
                    blockedKeywords.push(keyword);
                    keywordInput.value = '';
                    renderKeywords();
                }
            }
        });

        // 删除关键词
        modal.addEventListener('click', (e) => {
            if (e.target.classList.contains('zhihu-blocker-remove-btn')) {
                const index = parseInt(e.target.dataset.index);
                blockedKeywords.splice(index, 1);
                renderKeywords();
            }
        });

        // 关闭设置
        function closeSettings() {
            document.body.removeChild(overlay);
        }

        closeBtn.addEventListener('click', closeSettings);
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) closeSettings();
        });

        // 保存设置
        saveBtn.addEventListener('click', () => {
            GM_setValue('blockedKeywords', blockedKeywords);
            alert('设置已保存!');
            closeSettings();
            // 重新检查页面
            setTimeout(() => {
                checkAndBlockQuestions();
            }, 100);
        });

        // 重置设置
        resetBtn.addEventListener('click', () => {
            if (confirm('确定要重置为默认设置吗?')) {
                blockedKeywords = [...DEFAULT_KEYWORDS];
                renderKeywords();
            }
        });
    }

    // 创建快速输入面板
    function createQuickInputPanel() {
        const panel = document.createElement('div');
        panel.className = 'zhihu-blocker-quick-input';

        panel.innerHTML = `
            <div class="zhihu-blocker-quick-tab"></div>
            <div class="zhihu-blocker-quick-content">
                <div class="zhihu-blocker-quick-title">快速屏蔽</div>
                <input type="text" class="zhihu-blocker-quick-input-field" placeholder="输入关键词..." id="quick-keyword-input">
                <button class="zhihu-blocker-quick-add-btn" id="quick-add-btn">添加屏蔽词</button>
                <div class="zhihu-blocker-quick-recent">
                    <div class="zhihu-blocker-quick-recent-title">最近添加:</div>
                    <div id="quick-recent-keywords"></div>
                </div>
                <div class="zhihu-blocker-quick-stats" id="quick-stats">
                    已屏蔽: 0 个问题
                </div>
            </div>
        `;

        document.body.appendChild(panel);

        // 获取元素
        const tab = panel.querySelector('.zhihu-blocker-quick-tab');
        const quickInput = panel.querySelector('#quick-keyword-input');
        const quickAddBtn = panel.querySelector('#quick-add-btn');
        const recentKeywords = panel.querySelector('#quick-recent-keywords');
        const quickStats = panel.querySelector('#quick-stats');

        let isExpanded = false;

        // 切换面板显示/隐藏
        function togglePanel() {
            isExpanded = !isExpanded;
            panel.classList.toggle('show', isExpanded);
            if (isExpanded) {
                setTimeout(() => quickInput.focus(), 300);
                updateRecentKeywords();
                updateQuickStats();
            }
        }

        // 更新最近添加的关键词显示
        function updateRecentKeywords() {
            const recentCount = 8; // 显示最近8个
            const recent = blockedKeywords.slice(-recentCount).reverse();
            recentKeywords.innerHTML = '';

            recent.forEach(keyword => {
                const keywordEl = document.createElement('span');
                keywordEl.className = 'zhihu-blocker-quick-keyword';
                keywordEl.textContent = keyword;
                keywordEl.title = `点击删除: ${keyword}`;
                keywordEl.addEventListener('click', () => {
                    if (confirm(`确定要删除关键词 "${keyword}" 吗?`)) {
                        const index = blockedKeywords.indexOf(keyword);
                        if (index > -1) {
                            blockedKeywords.splice(index, 1);
                            GM_setValue('blockedKeywords', blockedKeywords);
                            updateRecentKeywords();
                            // 重新检查页面
                            setTimeout(checkAndBlockQuestions, 100);
                        }
                    }
                });
                recentKeywords.appendChild(keywordEl);
            });
        }

        // 更新快速统计
        function updateQuickStats() {
            quickStats.textContent = `已屏蔽: ${blockedCount} 个问题`;
        }

        // 添加关键词
        function addQuickKeyword() {
            const keyword = quickInput.value.trim();
            if (keyword && !blockedKeywords.includes(keyword)) {
                blockedKeywords.push(keyword);
                GM_setValue('blockedKeywords', blockedKeywords);
                quickInput.value = '';
                updateRecentKeywords();

                // 显示添加成功提示
                const originalText = quickAddBtn.textContent;
                quickAddBtn.textContent = '已添加!';
                quickAddBtn.style.background = 'rgba(76, 175, 80, 0.8)';
                setTimeout(() => {
                    quickAddBtn.textContent = originalText;
                    quickAddBtn.style.background = '';
                }, 1000);

                // 重新检查页面
                setTimeout(checkAndBlockQuestions, 100);
            } else if (blockedKeywords.includes(keyword)) {
                // 显示已存在提示
                const originalText = quickAddBtn.textContent;
                quickAddBtn.textContent = '已存在';
                quickAddBtn.style.background = 'rgba(255, 152, 0, 0.8)';
                setTimeout(() => {
                    quickAddBtn.textContent = originalText;
                    quickAddBtn.style.background = '';
                }, 1000);
            }
        }

        // 事件绑定
        tab.addEventListener('click', togglePanel);
        quickAddBtn.addEventListener('click', addQuickKeyword);

        quickInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                addQuickKeyword();
            }
        });

        // 点击面板外部关闭
        document.addEventListener('click', (e) => {
            if (isExpanded && !panel.contains(e.target)) {
                togglePanel();
            }
        });

        // ESC 键关闭
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape' && isExpanded) {
                togglePanel();
            }
        });

        // 暴露更新方法供外部调用
        panel.updateQuickStats = updateQuickStats;

        return panel;
    }

    // 注册菜单命令
    GM_registerMenuCommand('屏蔽设置', createSettingsUI);

    // 页面加载完成后开始监控
    function init() {
        // 初始检查
        checkAndBlockQuestions();

        // 监控页面变化
        const observer = new MutationObserver((mutations) => {
            let shouldCheck = false;
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    shouldCheck = true;
                }
            });
            if (shouldCheck) {
                setTimeout(checkAndBlockQuestions, 500);
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 定期检查(防止遗漏)
        setInterval(checkAndBlockQuestions, 3000);
    }

    // 等待页面加载
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    console.log('[知乎问题屏蔽器] 已启动,当前屏蔽关键词:', blockedKeywords);
})();