推特(x.com)网页端清理脚本

隐藏/删除推特时间线中的推荐内容和侧边栏推荐

// ==UserScript==
// @name         推特(x.com)网页端清理脚本
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  隐藏/删除推特时间线中的推荐内容和侧边栏推荐
// @author       You
// @match        https://x.com/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    let isExecuting = false;
    let processedElements = new Set();
    let lastScrollTop = 0;
    let scrollCheckInterval = null;

    //匹配 x.com/abc123 个人主页,也匹配了平台主页 x.com/home
    let userPattern = /^\/([^\/?#]+)$/;

    //匹配 x.com/abc123/1234567890 ,不匹配 x.com/abc123/with_replies
    let statusPattern = /^\/[^\/?#]+\/status\/\d+$/;

    function 隐藏status发现更多之后的推荐() {
        console.log('执行/status/页清理操作,隐藏"发现更多"之后的推荐');
        const targetSpan = Array.from(document.querySelectorAll('span'))
            .find(span => span.textContent.includes('发现更多'));
        if (!targetSpan) return false;

        const outerDiv = targetSpan.closest('div[style*="translateY"]');
        if (!outerDiv) return false;

        // 获取目标元素的translateY值作为参考点
        const style = outerDiv.getAttribute('style');
        const translateYMatch = style.match(/translateY\(([^)]+)\)/);
        if (!translateYMatch) return false;

        const targetTranslateY = parseFloat(translateYMatch[1].replace('px', ''));

        // 查找并隐藏所有translateY值大于目标值的div中的推荐内容
        let 隐藏计数 = 0;
        const allDivs = document.querySelectorAll('div[style*="translateY"]');

        allDivs.forEach(div => {
            const divStyle = div.getAttribute('style');
            const divTranslateYMatch = divStyle.match(/translateY\(([^)]+)\)/);
            if (divTranslateYMatch) {
                const divTranslateY = parseFloat(divTranslateYMatch[1].replace('px', ''));
                if (divTranslateY > targetTranslateY) {
                    const targetChild = div.querySelector('div > div > article > div > div > div:nth-child(2)');
                    if (targetChild && targetChild.offsetParent !== null) { // 检查元素是否可见
                        targetChild.style.display = 'none';
                        隐藏计数++;
                    }
                }
            }
        });

        if (隐藏计数 > 0) {
            console.log(`已隐藏 ${隐藏计数} 个推荐内容`);
            return true;
        }

        return false;
    }

    function 隐藏主页时间线中的转发(用户名){
        const tweetContainers = document.querySelectorAll('div[data-testid="cellInnerDiv"]');
        let hiddenCount = 0;

        tweetContainers.forEach(container => {
            const isRetweet = container.querySelector('[data-testid="socialContext"]');
            if (!isRetweet) return;

            const userAvatar = container.querySelector(`[data-testid="UserAvatar-Container-${用户名}"]`);
            if (!userAvatar) {
                // 隐藏而不是删除,避免破坏布局
                container.style.display = 'none';
                container.setAttribute('data-hidden', 'true');
                hiddenCount++;
            }
        });

        if (hiddenCount > 0) {
            console.log(`隐藏了 ${hiddenCount} 个非目标用户转发`);
        }
    }

    function 删除主页时间线中的推荐(){
        const buttons = document.querySelectorAll('button.css-175oi2r.r-1mmae3n.r-3pj75a.r-1loqt21.r-o7ynqc.r-6416eg.r-1ny4l3l');
        let modifiedCount = 0;

        buttons.forEach(button => {
            if (processedElements.has(button)) return;

            button.innerHTML = '';
            const span = document.createElement('span');
            span.textContent = '不值得关注';
            button.appendChild(span);

            processedElements.add(button);
            modifiedCount++;
        });

        if (modifiedCount > 0) {
            console.log(`修改了 ${modifiedCount} 个推荐按钮`);
        }
    }

    function 删除主页时间线中的引用() {
        const parentDivs = document.querySelectorAll('div.css-175oi2r.r-9aw3ui.r-1s2bzr4');
        let modifiedCount = 0;

        parentDivs.forEach(parentDiv => {
            if (processedElements.has(parentDiv)) return;

            // 直接操作父div的子节点,避免层级过深的选择器
            // 清空所有子节点
            parentDiv.innerHTML = '';

            // 创建新的提示信息
            const newSpan = document.createElement('span');
            newSpan.textContent = '引用已删除';
            newSpan.style.cssText = 'color: #666; font-style: italic; padding: 8px; display: block;';

            parentDiv.appendChild(newSpan);
            modifiedCount++;
            processedElements.add(parentDiv);
        });

        if (modifiedCount > 0) {
            console.log(`修改了 ${modifiedCount} 个引用节点`);
        }
    }
    function 隐藏主页侧边栏你可能会喜欢() {
        const aside = document.querySelector('aside[aria-label="推荐关注"][role="complementary"]');
        if (aside && !processedElements.has(aside)) {
            // 隐藏而不是删除,避免布局抖动和重新渲染
            //aside.style.opacity = '0.5';
            //aside.style.pointerEvents = 'none';

            // 或者完全隐藏
            aside.style.display = 'none';

            processedElements.add(aside);
            console.log('已隐藏主页侧边栏推荐内容');
        }
    }

    function 执行主页清理操作(用户名) {
        console.log('执行主页清理操作');
        隐藏主页侧边栏你可能会喜欢();
        隐藏主页时间线中的转发(用户名);
        删除主页时间线中的推荐();
        删除主页时间线中的引用();
    }

    function 执行主逻辑() {
        if (isExecuting) return;
        isExecuting = true;

        const currentPath = window.location.pathname;
        console.log('执行主逻辑,当前路径:', currentPath);
        let match;


        if (statusPattern.test(currentPath)) {
            console.log('检测到status路径');
            setTimeout(() => {
                隐藏status发现更多之后的推荐;
                isExecuting = false;
            }, 1000);
        } else if ((match = currentPath.match(userPattern))) {
            console.log('检测到主页路径');
            const 用户名 = match[1];
            console.log('用户名:', 用户名);
            setTimeout(() => {
                执行主页清理操作(用户名);
                isExecuting = false;
            }, 1000);
        } else {
            console.log('检测到普通路径,无动作');
            isExecuting = false;
        }
    }

    function 处理URL变化() {
        console.log('URL发生变化,重新执行主逻辑');
        processedElements.clear();
        执行主逻辑();
    }

    function setup监听URL变化() {
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        history.pushState = function(...args) {
            originalPushState.apply(this, args);
            setTimeout(处理URL变化, 100);
        };

        history.replaceState = function(...args) {
            originalReplaceState.apply(this, args);
            setTimeout(处理URL变化, 100);
        };

        window.addEventListener('popstate', () => setTimeout(处理URL变化, 100));
        window.addEventListener('hashchange', () => setTimeout(处理URL变化, 100));
    }

    // 新增:滚动检测函数
    function 启动滚动检测() {
        if (scrollCheckInterval) clearInterval(scrollCheckInterval);

        scrollCheckInterval = setInterval(() => {
            const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop;

            // 检测到用户滚动并且滚动距离变化较大
            if (Math.abs(currentScrollTop - lastScrollTop) > 100) {
                lastScrollTop = currentScrollTop;

                const currentPath = window.location.pathname;
                const userPattern = /^\/([^\/?#]+)$/;

                if (userPattern.test(currentPath)) {
                    console.log('检测到滚动,执行清理操作');
                    执行主页清理操作();
                }
            }
        }, 1000); // 每秒检查一次
    }

    function init() {
        setup监听URL变化();
        setTimeout(() => {
            执行主逻辑();
            启动滚动检测(); // 启动滚动检测
        }, 2000);
    }

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

    // 简化MutationObserver,只用于检测主要结构变化
    const observer = new MutationObserver((mutations) => {
        const hasSignificantChanges = mutations.some(mutation => {
            return mutation.addedNodes.length > 0 &&
                   Array.from(mutation.addedNodes).some(node =>
                       node.nodeType === 1 && (
                           node.querySelector('article') ||
                           node.querySelector('[data-testid="tweet"]')
                       )
                   );
        });

        if (hasSignificantChanges) {
            console.log('检测到DOM重大变化,执行清理操作');
            const currentPath = window.location.pathname;
            const userPattern = /^\/([^\/?#]+)$/;

            if (userPattern.test(currentPath)) {
                执行主页清理操作();
            }else if(statusPattern.test(currentPath)){
                隐藏status发现更多之后的推荐();
            }else{console.log(currentPath)}
        }
    });

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