BlockXhihu

Block some fucking annoying content on Zhihu

// ==UserScript==
// @name         BlockXhihu
// @name:zh-CN   黑白名单过滤某乎首页推荐
// @version      1.0.0
// @author       Sh1hanmax
// @description  Block some fucking annoying content on Zhihu
// @match        *://www.zhihu.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @grant        GM_addStyle
// @run-at       document-start
// @namespace    https://gf.qytechs.cn/scripts/4122051
// @license      GPL-3.0 License
// ==/UserScript==

// ref: https://github.com/XIU2/UserScript 知乎增强

'use strict';

// 默认正则表达式
const DEFAULT_ALLOW_REGEX = 'gpt|vllm|模型|ORPO|PPO|eepseek|rl|编码|attention|llm|gemm|rag|megatron|deepspeed|dp|动态规划';
const DEFAULT_BLOCK_REGEX = '$^';

// 初始化配置
function initConfig() {
    try {
        // 如果没有设置过屏蔽正则,则使用默认配置
        if (GM_getValue('blockRegex') === undefined) {
            GM_setValue('blockRegex', DEFAULT_BLOCK_REGEX);
            console.log('[知乎标题筛选] 初始化屏蔽正则表达式:', DEFAULT_BLOCK_REGEX);
        }

        // 如果没有设置过允许正则,则使用默认配置
        if (GM_getValue('allowRegex') === undefined) {
            GM_setValue('allowRegex', DEFAULT_ALLOW_REGEX);
            console.log('[知乎标题筛选] 初始化允许正则表达式:', DEFAULT_ALLOW_REGEX);
        }
    } catch (e) {
        console.error('[知乎标题筛选] 初始化配置失败:', e);
    }
}

// 获取保存的正则表达式
function getRegex(type) {
    try {
        return GM_getValue(type === 'allow' ? 'allowRegex' : 'blockRegex',
                          type === 'allow' ? DEFAULT_ALLOW_REGEX : DEFAULT_BLOCK_REGEX);
    } catch (e) {
        console.error(`[知乎标题筛选] 获取${type === 'allow' ? '允许' : '屏蔽'}正则表达式失败:`, e);
        return type === 'allow' ? DEFAULT_ALLOW_REGEX : DEFAULT_BLOCK_REGEX;
    }
}

// 设置正则表达式菜单
function setupRegexMenu() {
    try {
        // 允许正则表达式菜单
        GM_registerMenuCommand('设置允许正则表达式', () => {
            const currentRegex = getRegex('allow');
            const newRegex = prompt('请输入允许正则表达式\n(例如: .*(Python|编程).* 将只保留包含"Python"或"编程"的标题):', currentRegex);

            if (newRegex === null) return; // 用户取消

            try {
                new RegExp(newRegex);
                GM_setValue('allowRegex', newRegex);
                GM_notification({
                    text: '允许正则表达式已更新,刷新页面生效',
                    timeout: 3000,
                    onclick: () => location.reload()
                });
            } catch (e) {
                GM_notification({
                    text: '无效的正则表达式!',
                    timeout: 3000
                });
            }
        });

        // 屏蔽正则表达式菜单
        GM_registerMenuCommand('设置屏蔽正则表达式', () => {
            const currentRegex = getRegex('block');
            const newRegex = prompt('请输入屏蔽正则表达式\n(例如: .*(视频|直播).* 将屏蔽包含"视频"或"直播"的标题):', currentRegex);

            if (newRegex === null) return; // 用户取消

            try {
                new RegExp(newRegex);
                GM_setValue('blockRegex', newRegex);
                GM_notification({
                    text: '屏蔽正则表达式已更新,刷新页面生效',
                    timeout: 3000,
                    onclick: () => location.reload()
                });
            } catch (e) {
                GM_notification({
                    text: '无效的正则表达式!',
                    timeout: 3000
                });
            }
        });
    } catch (e) {
        console.error('[知乎标题筛选] 注册(不可用)菜单命令失败:', e);
    }
}

// 使用正则表达式过滤标题
function filterByRegex(titles) {
    try {
        const allowRegex = new RegExp(getRegex('allow'), 'i');
        const blockRegex = new RegExp(getRegex('block'), 'i');

        console.log('[知乎标题筛选] 使用允许正则:', allowRegex);
        console.log('[知乎标题筛选] 使用屏蔽正则:', blockRegex);

        return titles.map(title => {
            const lowerTitle = title.toLowerCase();
            const isAllowed = allowRegex.test(lowerTitle);
            const isBlocked = blockRegex.test(lowerTitle);

            // 优先判断屏蔽规则,然后是允许规则
            if (isBlocked) {
                console.log('[知乎标题筛选] 屏蔽标题:', title);
                return false; // 命中屏蔽规则,隐藏
            }
            if (!isAllowed) {
                console.log('[知乎标题筛选] 不允许标题:', title);
            }
            return isAllowed; // 根据允许规则决定是否显示
        });
    } catch (e) {
        console.error('[知乎标题筛选] 正则匹配失败:', e);
        return titles.map(() => true); // 出错时默认显示所有内容
    }
}

// Mock API 响应
function mockFilterAPI(titles) {
    console.log('[知乎标题筛选] 准备过滤的标题列表:', titles);
    return new Promise((resolve) => {
        setTimeout(() => {
            const results = filterByRegex(titles);
            console.log('[知乎标题筛选] 正则匹配结果:', results);
            resolve(results);
        }, 100);
    });
}

// 获取标题文本
function getTitleText(item) {
    // 尝试多种可能的标题选择器
    const selectors = [
        'h2.ContentItem-title meta[itemprop="name"]',
        'meta[itemprop="headline"]',
        'h2.ContentItem-title a',
        '.ContentItem-title a',
        '.RichText[itemprop="text"]'
    ];

    for (const selector of selectors) {
        const el = item.querySelector(selector);
        if (el) {
            // 根据元素类型获取文本
            if (el.tagName === 'META') {
                return el.content;
            } else if (el.tagName === 'A') {
                return el.textContent.trim();
            } else {
                return el.innerText.trim();
            }
        }
    }

    console.warn('[知乎标题筛选] 未找到标题元素:', item);
    return '';
}

// 批量过滤标题
async function filterTitles(items) {
    console.log('[知乎标题筛选] 开始处理新的内容组, 数量:', items.length);

    // 提取所有标题
    const titles = items.map(item => getTitleText(item));
    console.log('[知乎标题筛选] 提取到的标题:', titles);

    // 调用过滤API
    const results = await mockFilterAPI(titles);

    // 根据API返回结果隐藏内容
    items.forEach((item, index) => {
        if (!results[index]) {
            item.classList.add('zhihu-filter-hidden');
            console.log('[知乎标题筛选] 已屏蔽:', titles[index]);

            // 获取标题和链接,添加到侧边栏
            const titleInfo = getTitleAndUrl(item);
            if (titleInfo) {
                addToSidebar(titleInfo.title, titleInfo.url);
            }
        } else {
            item.classList.remove('zhihu-filter-hidden');
            console.log('[知乎标题筛选] 已保留:', titles[index]);
        }
    });
}

// 等待首屏内容加载完成
function waitForContent() {
    return new Promise(resolve => {
        const maxAttempts = 50; // 最多等待5秒
        let attempts = 0;

        const check = () => {
            const items = document.querySelectorAll('.Card.TopstoryItem.TopstoryItem-isRecommend');
            if (items.length > 0) {
                // 确保内容已经完全加载
                const titles = Array.from(items).map(item => getTitleText(item));
                if (titles.every(title => title)) {
                    resolve(items);
                    return;
                }
            }

            attempts++;
            if (attempts >= maxAttempts) {
                console.warn('[知乎标题筛选] 等待内容加载超时');
                resolve(document.querySelectorAll('.Card.TopstoryItem.TopstoryItem-isRecommend'));
                return;
            }

            setTimeout(check, 100);
        };
        check();
    });
}

// 监听新内容加载
function observeNewContent() {
    console.log('[知乎标题筛选] 开始监听新内容加载');
    const observer = new MutationObserver(mutations => {
        const newItems = [];

        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.nodeType !== 1) continue;

                if (node.classList && node.classList.contains('TopstoryItem-isRecommend')) {
                    newItems.push(node);
                } else {
                    // 检查子元素
                    const items = node.querySelectorAll('.Card.TopstoryItem.TopstoryItem-isRecommend');
                    if (items.length > 0) {
                        newItems.push(...items);
                    }
                }
            }
        }

        if (newItems.length > 0) {
            console.log('[知乎标题筛选] 检测到新的推荐内容:', newItems.length, '条');
            filterTitles(newItems);
        }
    });

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

// 主函数
async function main() {
    console.log('[知乎标题筛选] 脚本开始执行');

    // 检查必要的API是否可用
    if (typeof GM_getValue === 'undefined' ||
        typeof GM_setValue === 'undefined' ||
        typeof GM_registerMenuCommand === 'undefined' ||
        typeof GM_notification === 'undefined') {
        console.error('[知乎标题筛选] 必要的GM API未找到,脚本可能无法正常工作');
        return;
    }

    // 初始化配置
    initConfig();

    setupRegexMenu();

    if (location.pathname === '/') {
        console.log('[知乎标题筛选] 当前在首页,等待内容加载');

        // 等待首屏内容加载
        const items = await waitForContent();
        console.log('[知乎标题筛选] 找到已加载的内容:', items.length, '条');
        filterTitles(Array.from(items));

        // 监听后续内容
        observeNewContent();
    } else {
        console.log('[知乎标题筛选] 当前不在首页,不执行过滤');
    }
}

// URL变化时重新执行
function handleUrlChange() {
    console.log('[知乎标题筛选] URL发生变化');
    if (location.pathname === '/') {
        console.log('[知乎标题筛选] 切换到首页,准备重新执行');
        setTimeout(main, 500);
    }
}

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

    .zhihu-filter-sidebar-toggle {
        position: fixed;
        left: 24px;
        top: 0;
        height: 50px;
        padding: 0 16px;
        color: #8491a5;
        font-size: 15px;
        line-height: 50px;
        cursor: pointer;
        transition: color 0.25s ease-in;
        user-select: none;
        display: inline-block;
        background: #fff;
        z-index: 101;
    }

    .zhihu-filter-sidebar-toggle:hover {
        color: #191b1f;
    }

    .zhihu-filter-sidebar-toggle::after {
        content: "";
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 3px;
        background: #056de8;
        opacity: 0;
        transition: opacity 0.25s ease-in;
    }

    .zhihu-filter-sidebar-toggle.active {
        color: #191b1f;
    }

    .zhihu-filter-sidebar-toggle.active::after {
        opacity: 1;
    }

    .zhihu-filter-sidebar {
        position: fixed;
        left: 24px;
        top: 52px;
        width: 300px;
        height: calc(100vh - 52px);
        background: #fff;
        background: var(--GBK99A);
        border-radius: 2px;
        box-shadow: 0 1px 3px rgba(0, 0, 0, .1);
        padding: 12px;
        overflow-y: auto;
        z-index: 100;
        font-size: 14px;
        transition: opacity 0.3s ease, transform 0.3s ease;
        opacity: 1;
        transform: translateX(0);
    }

    .zhihu-filter-sidebar.collapsed {
        opacity: 0;
        transform: translateX(-100%);
        pointer-events: none;
    }

    .zhihu-filter-input-container {
        display: flex;
        align-items: center;
        gap: 8px;
        margin: 8px 0;
        padding: 12px;
        background: #f6f6f6;
        border-radius: 4px;
    }

    .zhihu-filter-input {
        flex: 1;
        height: 32px;
        padding: 6px 12px;
        border: 1px solid #ddd;
        border-radius: 4px;
        font-size: 14px;
        line-height: 20px;
    }

    .zhihu-filter-btn {
        height: 32px;
        width: 32px;
        padding: 0;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        transition: all 0.3s;
        font-size: 16px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .zhihu-filter-allow-btn {
        background: #4CAF50;
    }

    .zhihu-filter-allow-btn:hover {
        background: #388E3C;
    }

    .zhihu-filter-block-btn {
        background: #F44336;
    }

    .zhihu-filter-block-btn:hover {
        background: #D32F2F;
    }

    .zhihu-filter-sidebar-item {
        padding: 8px 12px;
        margin-bottom: 8px;
        cursor: pointer;
        border-radius: 4px;
        transition: all 0.2s;
        color: #444;
        line-height: 1.6;
    }

    .zhihu-filter-sidebar-item:hover {
        background-color: #f6f6f6;
        color: #175199;
    }

    .zhihu-filter-sidebar-content {
        margin-top: 4px;
    }
`);

// 创建侧边栏
function createSidebar() {
    const sidebar = document.createElement('div');
    sidebar.className = 'zhihu-filter-sidebar';

    // 创建切换按钮
    const toggle = document.createElement('div');
    toggle.className = 'zhihu-filter-sidebar-toggle active';
    toggle.textContent = '内容筛选';

    // 直接添加到body
    document.body.appendChild(toggle);

    // 创建输入框容器
    const inputContainer = document.createElement('div');
    inputContainer.className = 'zhihu-filter-input-container';

    const input = document.createElement('input');
    input.className = 'zhihu-filter-input';
    input.placeholder = '输入关键词';

    const allowButton = document.createElement('button');
    allowButton.className = 'zhihu-filter-btn zhihu-filter-allow-btn';
    allowButton.textContent = '⊕';

    const blockButton = document.createElement('button');
    blockButton.className = 'zhihu-filter-btn zhihu-filter-block-btn';
    blockButton.textContent = '⊖';

    inputContainer.appendChild(input);
    inputContainer.appendChild(allowButton);
    inputContainer.appendChild(blockButton);

    // 创建内容区域
    const content = document.createElement('div');
    content.className = 'zhihu-filter-sidebar-content';

    sidebar.appendChild(inputContainer);
    sidebar.appendChild(content);

    // 更新切换按钮事件
    toggle.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();
        sidebar.classList.toggle('collapsed');
        toggle.classList.toggle('active');
    });

    // 按钮动画函数
    const showSuccessAnimation = (button, originalText) => {
        button.textContent = '✓';
        setTimeout(() => {
            button.textContent = originalText;
        }, 1000);
    };

    // 添加Allow按钮事件
    allowButton.addEventListener('click', () => {
        const keyword = input.value.trim();
        if (keyword) {
            const currentAllowRegex = getRegex('allow');
            const newAllowRegex = currentAllowRegex ? `${currentAllowRegex}|${keyword}` : keyword;
            GM_setValue('allowRegex', newAllowRegex);
            input.value = '';
            showSuccessAnimation(allowButton, '⊕');
        }
    });

    // 添加Block按钮事件
    blockButton.addEventListener('click', () => {
        const keyword = input.value.trim();
        if (keyword) {
            const currentBlockRegex = getRegex('block');
            const newBlockRegex = currentBlockRegex ? `${currentBlockRegex}|${keyword}` : keyword;
            GM_setValue('blockRegex', newBlockRegex);
            input.value = '';
            showSuccessAnimation(blockButton, '⊖');
        }
    });

    // 添加输入框回车事件
    input.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            allowButton.click(); // 默认回车触发允许按钮
        }
    });

    document.body.appendChild(sidebar);
    return sidebar;
}

// 添加被屏蔽的标题到侧边栏
function addToSidebar(title, url) {
    const sidebar = document.querySelector('.zhihu-filter-sidebar') || createSidebar();
    const content = sidebar.querySelector('.zhihu-filter-sidebar-content');

    // 检查是否已存在相同的标题
    if (content.querySelector(`[data-url="${url}"]`)) {
        return;
    }

    const item = document.createElement('div');
    item.className = 'zhihu-filter-sidebar-item';
    item.setAttribute('data-url', url);
    item.textContent = title;
    item.title = title; // 添加完整标题作为提示

    item.addEventListener('click', () => {
        window.open(url, '_blank');
    });

    content.appendChild(item);
}

// 获取标题和链接
function getTitleAndUrl(item) {
    const titleElement = item.querySelector('h2.ContentItem-title a, .ContentItem-title a');
    if (!titleElement) return null;

    return {
        title: titleElement.textContent.trim(),
        url: titleElement.href
    };
}

// 监听 URL 变化
if (window.onurlchange === undefined) {
    // 如果浏览器不支持 onurlchange,使用 pushState 和 replaceState 的重写来监听
    history.pushState = ( f => function pushState(){
        var ret = f.apply(this, arguments);
        window.dispatchEvent(new Event('pushstate'));
        window.dispatchEvent(new Event('urlchange'));
        return ret;
    })(history.pushState);

    history.replaceState = ( f => function replaceState(){
        var ret = f.apply(this, arguments);
        window.dispatchEvent(new Event('replacestate'));
        window.dispatchEvent(new Event('urlchange'));
        return ret;
    })(history.replaceState);

    window.addEventListener('popstate', () => {
        window.dispatchEvent(new Event('urlchange'));
    });
}

// 监听 URL 变化事件
window.addEventListener('urlchange', handleUrlChange);

// 等待 DOM 加载完成后执行
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', main);
} else {
    main();
}

QingJ © 2025

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