Greasy Fork

Greasy Fork is available in English.

Twitter Search Filter

Filter tweets in Twitter search. Visual management, mask hint, full hide, draggable and collapsible panel!

当前为 2025-04-26 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Twitter Search Filter
// @namespace    https://x.com/pollowinworld
// @version      1.0
// @description  Filter tweets in Twitter search. Visual management, mask hint, full hide, draggable and collapsible panel!
// @author       pollowinworld
// @match        https://x.com/search*
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let blockedWords = JSON.parse(localStorage.getItem('blockedWords') || '["AI Alert &"]');
    let filterEnabled = JSON.parse(localStorage.getItem('filterEnabled') || 'true');
    let fullHideEnabled = JSON.parse(localStorage.getItem('fullHideEnabled') || 'false');

    function saveBlockedWords() {
        localStorage.setItem('blockedWords', JSON.stringify(blockedWords));
    }

    function saveFilterStatus() {
        localStorage.setItem('filterEnabled', JSON.stringify(filterEnabled));
    }

    function saveFullHideStatus() {
        localStorage.setItem('fullHideEnabled', JSON.stringify(fullHideEnabled));
    }

    function shouldBlock(tweetText) {
        return blockedWords.find(word => tweetText.toLowerCase().includes(word.toLowerCase()));
    }

    function createMask(article, matchedWord) {
        const mask = document.createElement('div');
        mask.style.position = 'absolute';
        mask.style.top = '0';
        mask.style.left = '0';
        mask.style.width = '100%';
        mask.style.height = '100%';
        mask.style.background = 'rgba(255, 255, 255, 0.9)';
        mask.style.display = 'flex';
        mask.style.flexDirection = 'column';
        mask.style.justifyContent = 'center';
        mask.style.alignItems = 'center';
        mask.style.zIndex = '100';
        mask.innerHTML = `
            <div style="font-weight:bold;margin-bottom:5px;">已屏蔽推文</div>
            <div style="font-size:12px;margin-bottom:10px;">匹配关键词:<b>${matchedWord}</b></div>
            <button style="padding:5px 10px;">显示此推文</button>
        `;
        const button = mask.querySelector('button');
        button.addEventListener('click', () => {
            mask.remove();
        });
        article.style.position = 'relative';
        article.appendChild(mask);
    }

    function filterTweets() {
        if (!filterEnabled) return;
        const tweets = document.querySelectorAll('article div[lang]');
        tweets.forEach(tweet => {
            const textContent = tweet.innerText;
            const matchedWord = shouldBlock(textContent);
            if (matchedWord) {
                const article = tweet.closest('article');
                if (article && !article.dataset.filtered) {
                    article.dataset.filtered = 'true';
                    if (fullHideEnabled) {
                        article.style.display = 'none';
                    } else {
                        createMask(article, matchedWord);
                    }
                }
            }
        });
    }

    function createControlPanel() {
        const panel = document.createElement('div');
        panel.style.position = 'fixed';
        panel.style.top = '100px';
        panel.style.right = '20px';
        panel.style.width = '280px';
        panel.style.background = 'white';
        panel.style.border = '1px solid #ccc';
        panel.style.borderRadius = '8px';
        panel.style.padding = '10px';
        panel.style.zIndex = '9999';
        panel.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
        panel.style.fontSize = '14px';
        panel.style.fontFamily = 'Arial, sans-serif';
        panel.style.cursor = 'move';
        panel.id = 'filter-panel';

        panel.innerHTML = `
            <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:5px;">
                <h3 style="margin:0; font-size:16px;">🛡️ 屏蔽词管理</h3>
                <button id="collapse-btn" style="background:none; border:none; font-size:18px; cursor:pointer;">➖</button>
            </div>
            <div id="panel-body">
                <div id="word-list" style="max-height:120px; overflow:auto; margin-bottom:10px;"></div>
                <input id="new-word" type="text" placeholder="添加屏蔽词" style="width: 100%; box-sizing: border-box; padding:5px; margin-bottom:5px;">
                <button id="add-word" style="width: 100%; padding:5px;">添加屏蔽词</button>
                <button id="toggle-filter" style="width: 100%; padding:5px; margin-top:5px;">${filterEnabled ? '✅ 过滤开启' : '❌ 过滤关闭'}</button>
                <button id="toggle-fullhide" style="width: 100%; padding:5px; margin-top:5px;">${fullHideEnabled ? '✅ 完全隐藏开启' : '❌ 完全隐藏关闭'}</button>
                <button id="export-words" style="width: 100%; padding:5px; margin-top:5px;">📤 导出屏蔽词</button>
                <button id="import-words" style="width: 100%; padding:5px; margin-top:5px;">📥 导入屏蔽词</button>
                <button id="clear-words" style="width: 100%; padding:5px; margin-top:5px; background-color:#f88;">清空所有屏蔽词</button>
            </div>
        `;

        document.body.appendChild(panel);

        const collapseBtn = panel.querySelector('#collapse-btn');
        const panelBody = panel.querySelector('#panel-body');
        let collapsed = false;

        collapseBtn.addEventListener('click', () => {
            collapsed = !collapsed;
            if (collapsed) {
                panelBody.style.display = 'none';
                collapseBtn.textContent = '➕';
                panel.style.width = '60px';
            } else {
                panelBody.style.display = '';
                collapseBtn.textContent = '➖';
                panel.style.width = '280px';
            }
        });

        const wordListDiv = panel.querySelector('#word-list');
        const input = panel.querySelector('#new-word');
        const addButton = panel.querySelector('#add-word');
        const toggleButton = panel.querySelector('#toggle-filter');
        const toggleFullHideButton = panel.querySelector('#toggle-fullhide');
        const exportButton = panel.querySelector('#export-words');
        const importButton = panel.querySelector('#import-words');
        const clearButton = panel.querySelector('#clear-words');

        function refreshWordList() {
            wordListDiv.innerHTML = '';
            blockedWords.forEach((word, index) => {
                const wordItem = document.createElement('div');
                wordItem.style.display = 'flex';
                wordItem.style.justifyContent = 'space-between';
                wordItem.style.marginBottom = '5px';
                wordItem.innerHTML = `
                    <span>${word}</span>
                    <button data-index="${index}" style="background: none; border: none; color: red; cursor: pointer;">✖️</button>
                `;
                wordListDiv.appendChild(wordItem);
            });

            wordListDiv.querySelectorAll('button').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const idx = parseInt(e.target.getAttribute('data-index'));
                    blockedWords.splice(idx, 1);
                    saveBlockedWords();
                    refreshWordList();
                });
            });
        }

        addButton.addEventListener('click', () => {
            const word = input.value.trim();
            if (word && !blockedWords.includes(word)) {
                blockedWords.push(word);
                saveBlockedWords();
                refreshWordList();
                input.value = '';
            }
        });

        toggleButton.addEventListener('click', () => {
            filterEnabled = !filterEnabled;
            saveFilterStatus();
            toggleButton.innerText = filterEnabled ? '✅ 过滤开启' : '❌ 过滤关闭';
        });

        toggleFullHideButton.addEventListener('click', () => {
            fullHideEnabled = !fullHideEnabled;
            saveFullHideStatus();
            toggleFullHideButton.innerText = fullHideEnabled ? '✅ 完全隐藏开启' : '❌ 完全隐藏关闭';
        });

        exportButton.addEventListener('click', () => {
            const blob = new Blob([JSON.stringify(blockedWords, null, 2)], {type: "application/json"});
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'blocked_words.json';
            a.click();
            URL.revokeObjectURL(url);
        });

        importButton.addEventListener('click', () => {
            const inputFile = document.createElement('input');
            inputFile.type = 'file';
            inputFile.accept = 'application/json';
            inputFile.onchange = e => {
                const file = e.target.files[0];
                const reader = new FileReader();
                reader.onload = event => {
                    try {
                        const imported = JSON.parse(event.target.result);
                        if (Array.isArray(imported)) {
                            blockedWords = imported;
                            saveBlockedWords();
                            refreshWordList();
                            alert('✅ 成功导入屏蔽词');
                        } else {
                            alert('❌ 文件格式错误');
                        }
                    } catch (err) {
                        alert('❌ 解析失败');
                    }
                };
                reader.readAsText(file);
            };
            inputFile.click();
        });

        clearButton.addEventListener('click', () => {
            if (confirm('确定要清空所有屏蔽词吗?')) {
                blockedWords = [];
                saveBlockedWords();
                refreshWordList();
            }
        });

        refreshWordList();

        // 拖拽 + 自动靠边吸附
        let isDragging = false;
        let offsetX, offsetY;

        panel.addEventListener('mousedown', function(e) {
            isDragging = true;
            offsetX = e.clientX - panel.offsetLeft;
            offsetY = e.clientY - panel.offsetTop;
            panel.style.transition = 'none';
        });

        document.addEventListener('mousemove', function(e) {
            if (isDragging) {
                panel.style.left = (e.clientX - offsetX) + 'px';
                panel.style.top = (e.clientY - offsetY) + 'px';
                panel.style.right = 'auto';
            }
        });

        document.addEventListener('mouseup', function() {
            if (isDragging) {
                isDragging = false;

                const windowWidth = window.innerWidth;
                const panelRect = panel.getBoundingClientRect();
                const middle = windowWidth / 2;

                if (panelRect.left + panelRect.width / 2 < middle) {
                    // 靠左
                    panel.style.left = '10px';
                    panel.style.right = 'auto';
                } else {
                    // 靠右
                    panel.style.right = '10px';
                    panel.style.left = 'auto';
                }

                panel.style.transition = 'left 0.2s, right 0.2s, top 0.2s'; // 平滑回弹
            }
        });

    }

    createControlPanel();
    setInterval(filterTweets, 1000);
})();