Greasy Fork

X/Twitter 一键屏蔽用户

在推特推文右下方添加一键屏蔽按钮

// ==UserScript==
// @name         X/Twitter 一键屏蔽用户
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  在推特推文右下方添加一键屏蔽按钮
// @author       @Maige
// @match        https://twitter.com/*
// @match        https://x.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 配置参数
    const CONFIG = {
        buttonColor: 'rgb(83, 100, 113)',
        hoverColor: 'rgb(244, 33, 46)',
        timeouts: {
            init: 1500,
            menuAppear: 300,
            confirmAppear: 300,
            closeMenu: 300
        },
        selectors: {
            tweet: 'article[data-testid="tweet"]',
            actionBar: '[role="group"]',
            userLink: 'a[role="link"][href*="/"]',
            moreButton: '[data-testid="caret"]',
            menuItem: '[role="menuitem"]',
            confirmButton: 'div[role="button"]'
        }
    };

    // 定义屏蔽按钮的SVG图标,保持Twitter风格
    const blockIconSVG = `
        <svg viewBox="0 0 24 24" aria-hidden="true" class="r-4qtqp9 r-yyyyoo r-1xvli5t r-dnmrzs r-bnwqim r-1plcrui r-lrvibr r-1hdv0qi">
            <g>
                <path d="M12 3.75C7.99 3.75 4.75 7 4.75 11s3.24 7.25 7.25 7.25S19.25 15 19.25 11 16.01 3.75 12 3.75zm0 12.5c-2.9 0-5.25-2.35-5.25-5.25S9.1 5.75 12 5.75 17.25 8.1 17.25 11s-2.35 5.25-5.25 5.25z"></path>
                <path d="M12 2.75c-4.55 0-8.25 3.7-8.25 8.25s3.7 8.25 8.25 8.25 8.25-3.7 8.25-8.25S16.55 2.75 12 2.75zm0 14.5c-3.45 0-6.25-2.8-6.25-6.25S8.55 4.75 12 4.75s6.25 2.8 6.25 6.25-2.8 6.25-6.25 6.25z"></path>
                <path d="M18.35 4.35l-14 14 1.4 1.4 14-14-1.4-1.4z"></path>
            </g>
        </svg>
    `;

    // 监听DOM变化,为新加载的推文添加屏蔽按钮
    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            if (mutation.addedNodes && mutation.addedNodes.length > 0) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1) { // 元素节点
                        const tweets = node.querySelectorAll(CONFIG.selectors.tweet);
                        if (tweets.length) tweets.forEach(addBlockButton);
                    }
                }
            }
        }
    });

    // 为已存在的推文添加屏蔽按钮
    function initExistingTweets() {
        const tweets = document.querySelectorAll(CONFIG.selectors.tweet);
        if (tweets.length) tweets.forEach(addBlockButton);
    }

    // 添加屏蔽按钮到推文
    function addBlockButton(tweet) {
        // 检查是否已经添加了屏蔽按钮
        if (tweet.querySelector('.one-click-block-btn')) return;

        // 查找推文的操作区域(右下角的分享、喜欢等按钮区域)
        const actionBar = tweet.querySelector(CONFIG.selectors.actionBar);
        if (!actionBar) return;

        // 创建屏蔽按钮
        const blockButton = document.createElement('div');
        blockButton.className = 'one-click-block-btn';
        blockButton.innerHTML = blockIconSVG;
        blockButton.title = '屏蔽此用户';
        blockButton.style.cssText = `
            cursor: pointer;
            display: inline-flex;
            margin-left: 8px;
            color: ${CONFIG.buttonColor};
        `;

        // 添加悬停效果
        blockButton.addEventListener('mouseover', () => blockButton.style.color = CONFIG.hoverColor);
        blockButton.addEventListener('mouseout', () => blockButton.style.color = CONFIG.buttonColor);

        // 添加点击事件
        blockButton.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            
            // 获取用户信息
            const userLink = tweet.querySelector(CONFIG.selectors.userLink);
            if (!userLink) return;
            
            const username = userLink.href.split('/').filter(Boolean).pop();
            if (!username) return;
            
            // 屏蔽用户
            blockUser(username, tweet);
        });

        // 将按钮添加到操作栏
        actionBar.appendChild(blockButton);
    }

    // 屏蔽用户的函数
    function blockUser(username, tweetElement) {
        // 查找用户的更多操作按钮
        const moreButton = tweetElement.querySelector(CONFIG.selectors.moreButton);
        if (!moreButton) {
            console.error(`无法找到用户 @${username} 的更多操作按钮`);
            return;
        }
        
        // 点击更多按钮
        moreButton.click();
        
        // 等待菜单出现并点击屏蔽选项
        setTimeout(() => {
            const blockOption = Array.from(document.querySelectorAll(CONFIG.selectors.menuItem))
                .find(item => item.textContent.includes('屏蔽') || item.textContent.includes('Block'));
            
            if (!blockOption) {
                document.body.click(); // 关闭菜单
                return;
            }
            
            // 点击屏蔽选项
            blockOption.click();
            
            // 等待确认对话框出现并确认
            setTimeout(() => {
                const confirmButton = Array.from(document.querySelectorAll(CONFIG.selectors.confirmButton))
                    .find(btn => btn.textContent.includes('屏蔽') || btn.textContent.includes('Block'));
                
                if (!confirmButton) {
                    document.body.click(); // 关闭菜单
                    return;
                }
                
                // 点击确认按钮
                confirmButton.click();
                console.log(`已屏蔽用户: @${username}`);
                
                // 关闭菜单
                setTimeout(() => document.body.click(), CONFIG.timeouts.closeMenu);
            }, CONFIG.timeouts.confirmAppear);
        }, CONFIG.timeouts.menuAppear);
    }

    // 初始化
    function init() {
        // 开始观察DOM变化
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
        
        // 处理已存在的推文
        setTimeout(initExistingTweets, CONFIG.timeouts.init);
        
        // 处理页面内导航(Twitter是SPA)
        let lastUrl = location.href;
        new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                setTimeout(initExistingTweets, CONFIG.timeouts.init);
            }
        }).observe(document, {subtree: true, childList: true});
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();