X一键屏蔽

在X用户资料页面添加直接屏蔽按钮,支持已屏蔽状态检测

// ==UserScript==
// @name         X一键屏蔽
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  在X用户资料页面添加直接屏蔽按钮,支持已屏蔽状态检测
// @author       DeepSeek
// @match        https://twitter.com/*
// @match        https://x.com/*
// @icon         https://abs.twimg.com/favicons/twitter.ico
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const config = {
        checkInterval: 1000,
        maxRetryCount: 3,
        retryDelay: 500
    };

    // 全局变量跟踪当前状态
    let isCurrentlyOnProfilePage = false;
    let observer = null;

    function init() {
        const isProfile = isProfilePage();
        const isOwnProfile = isOwnProfilePage();

        // 如果是自己的资料页面,不执行任何操作
        if (isOwnProfile) {
            if (isCurrentlyOnProfilePage) {
                console.log('检测到自己的资料页面,移除屏蔽按钮');
                removeBlockButton();
                isCurrentlyOnProfilePage = false;
            }
            return;
        }

        // 如果页面状态没有变化,不需要重新初始化
        if (isProfile === isCurrentlyOnProfilePage) {
            if (isProfile) {
                // 已经在资料页,确保按钮存在
                ensureBlockButton();
            }
            return;
        }

        // 更新状态
        isCurrentlyOnProfilePage = isProfile;

        if (isProfile) {
            console.log('检测到其他用户资料页面,初始化屏蔽按钮');
            addBlockButton();
        } else {
            console.log('不在用户资料页面,停止脚本操作');
            removeBlockButton();
        }
    }

    function isProfilePage() {
        const path = window.location.pathname;
        // 更精确的用户资料页面检测
        const isUserProfile = /^\/([^/]+)$/.test(path) &&
                             path !== '/' &&
                             !path.includes('home') &&
                             !path.includes('explore') &&
                             !path.includes('notifications') &&
                             !path.includes('messages') &&
                             !path.includes('compose');

        return isUserProfile;
    }

    function isOwnProfilePage() {
        if (!isProfilePage()) return false;

        // 方法1: 检查是否有编辑资料按钮(自己的页面才有)
        const editProfileSelectors = [
            '[data-testid="editProfileButton"]',
            '[aria-label="编辑资料"]',
            'a[href*="/settings/profile"]',
            'div[role="button"]:contains("编辑资料")'
        ];

        for (const selector of editProfileSelectors) {
            try {
                const element = document.querySelector(selector);
                if (element) {
                    console.log('检测到编辑资料按钮,确认是自己的页面');
                    return true;
                }
            } catch (e) {
                // 忽略选择器错误
            }
        }

        // 方法2: 检查是否有关注按钮(自己的页面没有关注按钮)
        const followButton = document.querySelector('[data-testid*="follow"]');
        if (!followButton) {
            console.log('未找到关注按钮,可能是自己的页面');

            // 进一步确认:检查是否有更多操作按钮
            const moreButton = document.querySelector('[data-testid="userActions"]');
            if (!moreButton) {
                console.log('确认是自己的资料页面(无关注和更多按钮)');
                return true;
            }
        }

        // 方法3: 检查URL路径是否包含已知的非用户页面
        const path = window.location.pathname;
        if (path.includes('/settings') || path.includes('/account')) {
            return true;
        }

        return false;
    }

    function ensureBlockButton() {
        if (!document.getElementById('x-block-btn')) {
            addBlockButton();
        }
    }

    function removeBlockButton() {
        const blockBtn = document.getElementById('x-block-btn');
        if (blockBtn) {
            blockBtn.remove();
            console.log('移除屏蔽按钮');
        }
    }

    // 添加缺失的 findActionButtons 函数
    function findActionButtons() {
        // 主要选择器:包含关注按钮的容器
        const mainSelector = 'div[data-testid="placementTracking"]';
        const actionContainer = document.querySelector(mainSelector);

        if (actionContainer) {
            console.log('找到操作按钮区域');
            return actionContainer;
        }

        // 备用选择器
        const backupSelectors = [
            'div[class*="profile"] > div:last-child > div:last-child',
            'main section > div:last-child > div:last-child'
        ];

        for (const selector of backupSelectors) {
            const element = document.querySelector(selector);
            if (element && element.querySelector('[data-testid*="follow"]')) {
                return element;
            }
        }

        console.log('未找到操作按钮区域');
        return null;
    }

    function addBlockButton() {
        if (document.getElementById('x-block-btn')) return;

        // 如果不是资料页面或者是自己的页面,不添加按钮
        if (!isProfilePage() || isOwnProfilePage()) {
            console.log('不在用户资料页面或是自己的页面,跳过添加按钮');
            return;
        }

        const actionButtons = findActionButtons();
        if (!actionButtons) {
            setTimeout(addBlockButton, config.checkInterval);
            return;
        }

        const blockBtn = createBlockButton();
        actionButtons.appendChild(blockBtn);
        console.log('屏蔽按钮已成功添加到操作区域');

        // 检查是否已屏蔽
        checkBlockStatus(blockBtn);
    }

    function createBlockButton() {
        const btn = document.createElement('div');
        btn.id = 'x-block-btn';
        btn.innerHTML = `
            <div role="button" tabindex="0" style="
                margin-left: 12px;
                min-width: 80px;
                padding: 0 16px;
                height: 36px;
                border: 1px solid rgb(83, 100, 113);
                border-radius: 18px;
                display: flex;
                align-items: center;
                justify-content: center;
                font-weight: bold;
                font-size: 14px;
                color: rgb(239, 243, 244);
                background-color: rgba(0, 0, 0, 0.9);
                cursor: pointer;
                transition: background-color 0.2s;
            ">
                屏蔽
            </div>
        `;

        // 先添加点击事件,checkBlockStatus 会决定是否移除
        btn.addEventListener('click', handleBlockClick);

        return btn;
    }

    function checkBlockStatus(blockBtn) {
        // 如果不是资料页面或者是自己的页面,不检查状态
        if (!isProfilePage() || isOwnProfilePage()) {
            return false;
        }

        console.log('检查屏蔽状态...');

        // 方法1: 检查关注按钮状态(已屏蔽的用户无法关注)
        const followButton = document.querySelector('[data-testid*="follow"]');
        if (followButton) {
            const isDisabled = followButton.disabled || followButton.getAttribute('aria-disabled') === 'true';
            const buttonText = (followButton.textContent || '').toLowerCase();

            console.log('关注按钮状态:', { isDisabled, buttonText });

            if (isDisabled || buttonText.includes('unblock') || buttonText.includes('取消屏蔽')) {
                setButtonBlocked(blockBtn);
                return true;
            }
        }

        // 方法2: 检查页面中的屏蔽状态提示
        const blockIndicators = [
            'span',
            'div',
            '[data-testid*="block"]'
        ];

        for (const selector of blockIndicators) {
            try {
                const elements = document.querySelectorAll(selector);
                for (const element of elements) {
                    const text = (element.textContent || element.innerText || '').toLowerCase();
                    if (text.includes('已屏蔽') || text.includes('blocked') ||
                        text.includes('unblock') || text.includes('取消屏蔽')) {
                        console.log('找到屏蔽指示器:', text);
                        setButtonBlocked(blockBtn);
                        return true;
                    }
                }
            } catch (e) {
                // 忽略选择器错误
            }
        }

        // 方法3: 检查更多菜单中的选项
        try {
            const moreMenuItems = document.querySelectorAll('[role="menuitem"]');
            for (const item of moreMenuItems) {
                const text = (item.textContent || item.innerText || '').toLowerCase();
                if (text.includes('取消屏蔽') || text.includes('unblock')) {
                    console.log('找到取消屏蔽菜单项:', text);
                    setButtonBlocked(blockBtn);
                    return true;
                }
            }
        } catch (e) {
            console.log('检查菜单项时出错:', e);
        }

        console.log('用户未被屏蔽,按钮保持可点击状态');
        return false;
    }

    function setButtonBlocked(blockBtn) {
        const innerDiv = blockBtn.querySelector('div');
        innerDiv.textContent = '已屏蔽';
        innerDiv.style.borderColor = 'rgb(103, 193, 103)';
        innerDiv.style.color = 'rgb(103, 193, 103)';
        innerDiv.style.cursor = 'default';
        innerDiv.style.opacity = '0.7';

        // 移除点击事件
        blockBtn.onclick = null;
        blockBtn.removeEventListener('click', handleBlockClick);
        console.log('用户已被屏蔽,按钮状态已更新');
    }

    async function handleBlockClick(event) {
        // 防止事件冒泡
        event.stopPropagation();

        // 如果不是资料页面或者是自己的页面,不执行操作
        if (!isProfilePage() || isOwnProfilePage()) {
            console.log('不在用户资料页面或是自己的页面,取消屏蔽操作');
            return;
        }

        const blockBtn = document.getElementById('x-block-btn');
        if (blockBtn.getAttribute('data-processing') === 'true') return;

        blockBtn.setAttribute('data-processing', 'true');
        updateButtonState(blockBtn, 'processing');

        try {
            await performBlockAction();

            // 屏蔽成功后更新按钮状态
            setButtonBlocked(blockBtn);

        } catch (error) {
            console.error('屏蔽操作失败:', error);
            updateButtonState(blockBtn, 'error');
            setTimeout(() => updateButtonState(blockBtn, 'normal'), 2000);
        } finally {
            blockBtn.removeAttribute('data-processing');
        }
    }

    async function performBlockAction() {
        console.log('开始执行屏蔽操作...');

        // 1. 点击更多按钮
        const moreBtn = findMoreButton();
        if (!moreBtn) throw new Error('找不到更多按钮');
        console.log('找到更多按钮,点击中...');
        moreBtn.click();
        await wait(1500);

        // 2. 查找屏蔽选项
        const blockOption = findBlockOption();
        if (!blockOption) throw new Error('找不到屏蔽选项');
        console.log('找到屏蔽选项,点击中...');
        blockOption.click();
        await wait(1500);

        // 3. 处理确认弹窗
        const confirmBtn = findConfirmButton();
        if (confirmBtn) {
            console.log('找到确认按钮,点击中...');
            confirmBtn.click();
            await wait(1000);
        } else {
            console.log('未找到确认按钮,可能不需要确认');
        }

        console.log('屏蔽操作完成');
    }

    function findMoreButton() {
        const moreButtonSelectors = [
            '[data-testid="userActions"]',
            '[aria-label="更多"]',
            'div[role="button"][aria-haspopup="menu"]',
            'svg[viewBox="0 0 24 24"]'
        ];

        for (const selector of moreButtonSelectors) {
            const element = document.querySelector(selector);
            if (element) {
                const button = element.closest('[role="button"]') || element;
                console.log('找到更多按钮:', button);
                return button;
            }
        }

        console.log('未找到更多按钮');
        return null;
    }

    function findBlockOption() {
        const blockOptionSelectors = [
            '[role="menuitem"][data-testid="block"]',
            'div[role="menuitem"]',
            'span',
            'button'
        ];

        for (const selector of blockOptionSelectors) {
            const elements = document.querySelectorAll(selector);
            for (const element of elements) {
                const text = (element.textContent || element.innerText || '').toLowerCase();
                if (text.includes('屏蔽') || text.includes('block')) {
                    console.log('找到屏蔽选项:', element, text);
                    return element.closest('[role="menuitem"]') || element;
                }
            }
        }

        console.log('未找到屏蔽选项');
        return null;
    }

    function findConfirmButton() {
        const confirmSelectors = [
            '[data-testid="confirmationSheetConfirm"]',
            'div[role="button"][data-testid*="confirm"]',
            'span',
            'button'
        ];

        for (const selector of confirmSelectors) {
            const elements = document.querySelectorAll(selector);
            for (const element of elements) {
                const text = (element.textContent || element.innerText || '').toLowerCase();
                if (text.includes('确认') || text.includes('confirm')) {
                    console.log('找到确认按钮:', element, text);
                    return element.closest('[role="button"]') || element;
                }
            }
        }

        console.log('未找到确认按钮');
        return null;
    }

    function updateButtonState(button, state) {
        const innerDiv = button.querySelector('div');
        switch (state) {
            case 'processing':
                innerDiv.textContent = '屏蔽中...';
                innerDiv.style.opacity = '0.7';
                break;
            case 'error':
                innerDiv.textContent = '失败';
                innerDiv.style.borderColor = 'rgb(193, 103, 103)';
                innerDiv.style.color = 'rgb(193, 103, 103)';
                break;
            default:
                innerDiv.textContent = '屏蔽';
                innerDiv.style.opacity = '1';
                innerDiv.style.borderColor = 'rgb(83, 100, 113)';
                innerDiv.style.color = 'rgb(239, 243, 244)';
        }
    }

    function wait(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 初始化
    let initialized = false;
    function startObserver() {
        if (initialized) return;

        initialized = true;
        init();

        // 监听URL变化
        let lastUrl = location.href;
        setInterval(() => {
            if (location.href !== lastUrl) {
                lastUrl = location.href;
                init();
            }
        }, config.checkInterval);

        // 监听DOM变化,但只在资料页面时进行深度监听
        observer = new MutationObserver((mutations) => {
            if (isProfilePage() && !isOwnProfilePage()) {
                init();
            }
        });

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

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startObserver);
    } else {
        startObserver();
    }
})();