Content Control

Block or filter content on Zhihu, Xiaohongshu, Bilibili, and Weibo based on keywords.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Content Control
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Block or filter content on Zhihu, Xiaohongshu, Bilibili, and Weibo based on keywords.
// @author       Your Name
// @match        https://www.zhihu.com/*
// @match        https://www.xiaohongshu.com/*
// @match        https://www.bilibili.com/*
// @match        https://www.bilibili.com/?*
// @match        https://www.bilibili.com/v/*
// @match        https://search.bilibili.com/*
// @match        https://weibo.com/*
// @match        https://www.weibo.com/*
// @match        https://s.weibo.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Constants and Storage ---
    const BLOCK_STORAGE_KEY = 'keyword_blocker_words';
    const SHOW_STORAGE_KEY = 'showlist_keywords';
    const MODE_STORAGE_KEY = 'content_control_mode'; // 'block' or 'show'
    const DISABLED_SITES_KEY = 'content_control_disabled_sites';

    const DEFAULT_BLOCK_KEYWORDS = ['男','女'];
    const DEFAULT_SHOW_KEYWORDS = ['AI'];

    // --- Utility Functions ---
    function loadFromStorage(key, defaultValue) {
        try {
            const saved = localStorage.getItem(key);
            return saved ? JSON.parse(saved) : defaultValue;
        } catch (e) {
            console.error(`Failed to load from ${key}:`, e);
            return defaultValue;
        }
    }

    function saveToStorage(key, value) {
        localStorage.setItem(key, JSON.stringify(value));
    }

    // --- State Management ---
    let blockKeywords = loadFromStorage(BLOCK_STORAGE_KEY, [...DEFAULT_BLOCK_KEYWORDS]);
    let showKeywords = loadFromStorage(SHOW_STORAGE_KEY, [...DEFAULT_SHOW_KEYWORDS]);
    let currentMode = loadFromStorage(MODE_STORAGE_KEY, 'block');
    let disabledSites = loadFromStorage(DISABLED_SITES_KEY, []);

    // --- Site Configuration ---
    function getCurrentSite() {
        const hostname = window.location.hostname;
        if (hostname.includes('zhihu.com')) return 'zhihu';
        if (hostname.includes('xiaohongshu.com')) return 'xiaohongshu';
        if (hostname.includes('bilibili.com')) return 'bilibili';
        if (hostname.includes('weibo.com')) return 'weibo';
        return 'unknown';
    }

    const siteConfigs = {
        zhihu: {
            containerSelector: '.ContentItem',
            cardSelector: '.Card',
            titleSelector: '.ContentItem-title a',
        },
        xiaohongshu: {
            containerSelector: 'section.note-item',
            cardSelector: 'section.note-item',
            titleSelector: 'a.title, .title',
        },
        bilibili: {
            containerSelector: '.bili-feed-card, .bili-video-card',
            cardSelector: '.bili-feed-card, .bili-video-card',
            titleSelector: '.bili-video-card__info--tit, .bili-video-card__info--tit a, .bili-video-card__wrap .bili-video-card__info--tit',
        },
        weibo: {
            containerSelector: '.wbpro-scroller-item',
            cardSelector: '.wbpro-scroller-item',
            titleSelector: '.wbpro-feed-content .detail_wbtext_4CRf9',
        }
    };

    // --- UI ---
    function createManagementUI() {
        const style = document.createElement('style');
        style.textContent = `
            #content-control-toggle {
                position: fixed; left: 20px; top: 50%; transform: translateY(-50%); z-index: 10000;
                background: #1890ff; color: white; border: none; border-radius: 6px; padding: 12px 8px;
                cursor: pointer; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.15);
                writing-mode: vertical-lr; text-orientation: mixed; transition: all 0.3s ease;
            }
            #content-control-toggle:hover {
                background: #40a9ff;
                transform: translateY(-50%) scale(1.05);
            }
            #content-control-panel {
                position: fixed; left: -350px; top: 50%; transform: translateY(-50%); z-index: 9999;
                width: 320px; max-height: 70vh; background: white; border: 1px solid #d9d9d9;
                border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.15); transition: left 0.3s ease;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            }
            #content-control-panel.show {
                left: 20px;
            }
            .cc-header {
                padding: 16px; border-bottom: 1px solid #f0f0f0; background: #fafafa;
                border-radius: 8px 8px 0 0;
            }
            .cc-header h3 {
                margin: 0; font-size: 16px; font-weight: 500; color: #262626;
            }
            .cc-mode-switch {
                margin-top: 10px;
            }
            .cc-mode-switch label {
                margin-right: 15px; font-size: 14px; color: #595959;
            }
            .cc-input-group {
                padding: 16px; display: flex; gap: 8px;
            }
            #cc-keyword-input {
                flex: 1; padding: 8px 12px; border: 1px solid #d9d9d9; border-radius: 4px;
                font-size: 14px; outline: none;
            }
            #cc-keyword-input:focus {
                border-color: #1890ff; box-shadow: 0 0 0 2px rgba(24,144,255,0.2);
            }
            #cc-add-keyword {
                padding: 8px 16px; background: #1890ff; color: white; border: none;
                border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.3s ease;
            }
            #cc-add-keyword:hover {
                background: #40a9ff;
            }
            #cc-keyword-list {
                list-style: none; margin: 0; padding: 0; max-height: calc(70vh - 200px);
                overflow-y: auto;
            }
            #cc-keyword-list li {
                display: flex; justify-content: space-between; align-items: center;
                padding: 12px 16px; border-bottom: 1px solid #f0f0f0;
            }
            #cc-keyword-list li:hover {
                background: #f5f5f5;
            }
            .cc-delete-keyword {
                padding: 4px 8px; background: #ff4d4f; color: white; border: none;
                border-radius: 3px; cursor: pointer; font-size: 12px;
            }
            .cc-delete-keyword:hover {
                background: #ff7875;
            }
            .cc-footer {
                padding: 12px 16px; background: #f9f9f9; border-top: 1px solid #f0f0f0;
                font-size: 12px; color: #666; border-radius: 0 0 8px 8px;
            }
        `;
        document.head.appendChild(style);

        const toggleBtn = document.createElement('button');
        toggleBtn.id = 'content-control-toggle';
        toggleBtn.textContent = '内容控制';
        document.body.appendChild(toggleBtn);

        const panel = document.createElement('div');
        panel.id = 'content-control-panel';
        panel.innerHTML = `
            <div class="cc-header">
                <h3>内容控制</h3>
                <div class="cc-mode-switch">
                    <label><input type="radio" name="mode" value="filter"> 筛选</label>
                    <label><input type="radio" name="mode" value="block"> 屏蔽</label>
                </div>
            </div>
            <div class="cc-input-group">
                <input type="text" id="cc-keyword-input" placeholder="输入关键词...">
                <button id="cc-add-keyword">添加</button>
            </div>
            <ul id="cc-keyword-list"></ul>
            <div class="cc-footer">
                <label><input type="checkbox" id="cc-site-disable"> 在当前网站禁用</label>
            </div>
        `;
        document.body.appendChild(panel);

        updatePanelUI();
        addPanelEventListeners();

        toggleBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            panel.classList.toggle('show');
            toggleBtn.style.display = panel.classList.contains('show') ? 'none' : 'block';
        });
        
        document.addEventListener('click', (e) => {
            if (!panel.contains(e.target) && !toggleBtn.contains(e.target)) {
                panel.classList.remove('show');
                toggleBtn.style.display = 'block';
            }
        });
    }

    function updatePanelUI() {
        const isBlockMode = currentMode === 'block';
        const keywords = isBlockMode ? blockKeywords : showKeywords;
        const listElement = document.querySelector('#cc-keyword-list');
        if (listElement) {
            listElement.innerHTML = keywords.map((kw, i) =>
                `<li data-index="${i}">${kw} <button class="cc-delete-keyword">删除</button></li>`).join('');
        }
        const blockRadio = document.querySelector('input[name="mode"][value="block"]');
        const filterRadio = document.querySelector('input[name="mode"][value="filter"]');
        if (blockRadio) blockRadio.checked = isBlockMode;
        if (filterRadio) filterRadio.checked = !isBlockMode;
        
        const inputElement = document.querySelector('#cc-keyword-input');
        if (inputElement) {
            inputElement.placeholder = isBlockMode ? '输入屏蔽词...' : '输入筛选词...';
        }
        
        const disableCheckbox = document.querySelector('#cc-site-disable');
        if (disableCheckbox) {
            disableCheckbox.checked = disabledSites.includes(getCurrentSite());
        }
    }

    function addPanelEventListeners() {
        const modeRadios = document.querySelectorAll('input[name="mode"]');
        modeRadios.forEach(radio => {
            radio.addEventListener('change', (e) => {
                currentMode = e.target.value;
                saveToStorage(MODE_STORAGE_KEY, currentMode);
                updatePanelUI();
                processAllItems();
            });
        });

        const addKeywordBtn = document.getElementById('cc-add-keyword');
        if (addKeywordBtn) {
            addKeywordBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                const input = document.getElementById('cc-keyword-input');
                if (input && input.value.trim()) {
                    const newKeywords = input.value.split(/[,,/]/).map(k => k.trim()).filter(Boolean);
                    if (newKeywords.length > 0) {
                        const keywords = currentMode === 'block' ? blockKeywords : showKeywords;
                        const storageKey = currentMode === 'block' ? BLOCK_STORAGE_KEY : SHOW_STORAGE_KEY;
                        keywords.unshift(...newKeywords);
                        saveToStorage(storageKey, keywords);
                        input.value = '';
                        updatePanelUI();
                        processAllItems();
                    }
                }
            });
        }

        const keywordList = document.getElementById('cc-keyword-list');
        if (keywordList) {
            keywordList.addEventListener('click', (e) => {
                e.stopPropagation();
                if (e.target.classList.contains('cc-delete-keyword')) {
                    const index = parseInt(e.target.parentElement.dataset.index, 10);
                    const keywords = currentMode === 'block' ? blockKeywords : showKeywords;
                    const storageKey = currentMode === 'block' ? BLOCK_STORAGE_KEY : SHOW_STORAGE_KEY;
                    keywords.splice(index, 1);
                    saveToStorage(storageKey, keywords);
                    updatePanelUI();
                    processAllItems();
                }
            });
        }

        const siteDisableCheckbox = document.getElementById('cc-site-disable');
        if (siteDisableCheckbox) {
            siteDisableCheckbox.addEventListener('change', (e) => {
                const site = getCurrentSite();
                if (e.target.checked) {
                    if (!disabledSites.includes(site)) disabledSites.push(site);
                } else {
                    disabledSites = disabledSites.filter(s => s !== site);
                }
                saveToStorage(DISABLED_SITES_KEY, disabledSites);
                processAllItems();
            });
        }
        
        // 添加回车键支持
        const keywordInput = document.getElementById('cc-keyword-input');
        if (keywordInput) {
            keywordInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    e.stopPropagation();
                    document.getElementById('cc-add-keyword').click();
                }
            });
        }
    }

    // --- Content Processing ---
    function processItem(item, config) {
        const titleElement = item.querySelector(config.titleSelector);
        const content = titleElement ? titleElement.innerText : item.innerText;
        const card = item.closest(config.cardSelector);
        if (!card) return;

        if (currentMode === 'block') {
            const shouldBlock = blockKeywords.some(keyword => content.toLowerCase().includes(keyword.toLowerCase()));
            card.style.display = shouldBlock ? 'none' : '';
        } else {
            const shouldShow = showKeywords.some(keyword => content.toLowerCase().includes(keyword.toLowerCase()));
            card.style.display = shouldShow ? '' : 'none';
        }
    }

    function processAllItems() {
        const site = getCurrentSite();
        if (site === 'unknown' || disabledSites.includes(site)) return;
        const config = siteConfigs[site];
        document.querySelectorAll(config.containerSelector).forEach(item => processItem(item, config));
    }

    // --- Initialization ---
    function init() {
        const site = getCurrentSite();
        if (site === 'unknown') return;

        // 确保DOM加载完成后再初始化UI
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                createManagementUI();
                processAllItems();
            });
        } else {
            createManagementUI();
            processAllItems();
        }

        const observer = new MutationObserver(mutations => {
            if (disabledSites.includes(site)) return;
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1) {
                        const config = siteConfigs[site];
                        if (node.matches && node.matches(config.containerSelector)) {
                            processItem(node, config);
                        }
                        if (node.querySelectorAll) {
                            node.querySelectorAll(config.containerSelector).forEach(item => processItem(item, config));
                        }
                    }
                }
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    init();

})();