Make Twitter Great Again

为Twitter增加两个按钮,快速让Twitter算法知道你“不感兴趣的推文“ From Chrome Extension "Make Twitter Great Again" not interesting this post

当前为 2024-03-12 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Make Twitter Great Again
// @namespace    https://github.com/androidcn/userscripts/
// @version      2024-03-11
// @description  为Twitter增加两个按钮,快速让Twitter算法知道你“不感兴趣的推文“ From Chrome Extension "Make Twitter Great Again" not interesting this post
// @author       theopenprojects.io
// @match        https://twitter.com/home
// @icon         https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @grant        none
// @license      MIT
// ==/UserScript==

const silencePath = 'path[d="M18 6.59V1.2L8.71 7H5.5C4.12 7 3 8.12 3 9.5v5C3 15.88 4.12 17 5.5 17h2.09l-2.3 2.29 1.42 1.42 15.5-15.5-1.42-1.42L18 6.59zm-8 8V8.55l6-3.75v3.79l-6 6zM5 9.5c0-.28.22-.5.5-.5H8v6H5.5c-.28 0-.5-.22-.5-.5v-5zm6.5 9.24l1.45-1.45L16 19.2V14l2 .02v8.78l-6.5-4.06z"]'
const shitPath = 'path[d="M9.5 7c.828 0 1.5 1.119 1.5 2.5S10.328 12 9.5 12 8 10.881 8 9.5 8.672 7 9.5 7zm5 0c.828 0 1.5 1.119 1.5 2.5s-.672 2.5-1.5 2.5S13 10.881 13 9.5 13.672 7 14.5 7zM12 22.25C6.348 22.25 1.75 17.652 1.75 12S6.348 1.75 12 1.75 22.25 6.348 22.25 12 17.652 22.25 12 22.25zm0-18.5c-4.549 0-8.25 3.701-8.25 8.25s3.701 8.25 8.25 8.25 8.25-3.701 8.25-8.25S16.549 3.75 12 3.75zM8.947 17.322l-1.896-.638C7.101 16.534 8.322 13 12 13s4.898 3.533 4.949 3.684l-1.897.633c-.031-.09-.828-2.316-3.051-2.316s-3.021 2.227-3.053 2.322z"]'
const moreProfilePath = 'path[d="M3 12c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2zm9 2c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm7 0c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"]'

const sleep = ms => new Promise(r => setTimeout(r, ms));

const waitForElm = (selector) => {
    return new Promise((resolve, reject) => {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                const elm = mutation.target.querySelector(selector);
                if (elm) {
                    observer.disconnect();
                    resolve(elm);
                }
            });
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true,
        });
    });
};

const setTabStatusToBody = () => {
    const body = document.querySelector('body')
    if (!body || !document.querySelectorAll('[role="tablist"] [role="tab"]')) return
    if (document.URL !== 'https://twitter.com/home') return;

    if (!Array.from(document.querySelectorAll('[role="tablist"] [role="tab"][data-index]')).length > 0) Array.from(document.querySelectorAll('[role="tablist"] [role="tab"]')).map((el, idx) => el.closest('div').setAttribute('data-index', idx + 1))

    const selectedIndexElm = document.querySelector('[role="tablist"] [role="tab"][aria-selected="true"]')
    if (selectedIndexElm) {
        const parent = selectedIndexElm.closest('div')
        if (parent) {
            body.setAttribute('data-make-twitter-great-again', parent.getAttribute('data-index'))
        }
    }
}

const createShityBtn = (tweet) => {
    if (!tweet || tweet.querySelector('.shitBtn')) return;
    if (document.URL !== 'https://twitter.com/home') return;

    const button = document.createElement('button');
    tweet.setAttribute('data-shit', true);
    button.classList.add('shitBtn');
    button.innerHTML = '💩';

    const navAction = tweet.querySelector('div[role="group"][id*="id__"]');
    if (navAction) navAction.appendChild(button);
}

const createSilenceBtn = tweet => {
    if (!tweet || tweet.querySelector('.silenceBtn')) return;
    if (document.URL !== 'https://twitter.com/home') return;

    const button = document.createElement('button');
    tweet.setAttribute('data-silence', true);
    button.classList.add('silenceBtn');
    button.innerHTML = '🤫';

    const navAction = tweet.querySelector('div[role="group"][id*="id__"]');
    if (navAction) navAction.appendChild(button);
}

const handleBtnClick = async (e, selector) => {
    const tweet = e.target.closest('article');
    document.querySelector('body').setAttribute('data-pop-open', true)

    const btnDropdown = tweet.querySelector('[aria-haspopup="menu"][role="button"][data-testid="caret"]');
    if (!btnDropdown) return

    btnDropdown.click();

    await sleep(5);
    const dropdown = document.querySelector('[data-testid="Dropdown"]');
    if (!dropdown) return;

    const item = dropdown.querySelector(selector)
    if (item) item.closest('[role]').click()

    btnDropdown.click();

    if (document.querySelector('body').getAttribute('data-make-twitter-great-again') !== "1" || document.querySelector('body').getAttribute('data-make-twitter-great-again') !== "2") tweet.remove()
}

const handleProfileBtnClick = async (e, selector) => {
    const userActions = document.querySelector('[role="main"] [data-testid="userActions"]')
    if (!userActions) return

    document.querySelector('body').setAttribute('data-pop-open', true)

    const path = userActions.querySelector(moreProfilePath);
    if (!path) return
    const btnDropdown = path.closest('div[dir]')
    if (!btnDropdown) return

    btnDropdown.click();

    await sleep(5);
    const dropdown = document.querySelector('[data-testid="Dropdown"]');
    if (!dropdown) return;

    const item = dropdown.querySelector(selector)
    if (item) item.closest('[role]').click()

    dropdown.remove()
}

const addBtnToTweets = () => {
    const tweets = document.querySelectorAll('[role="region"] article:not([data-shit]):not([data-silence])');
    if (tweets && tweets.length > 0) tweets.forEach(tweet => {
        createShityBtn(tweet)
        createSilenceBtn(tweet)
    });
}

const addProfileSilenceBtn = () => {
    const userActions = document.querySelector('[role="main"] [data-testid="userActions"]')
    if (!userActions) return

    const contentActions = userActions.parentElement
    if (!contentActions || contentActions.getAttribute('data-silence') === 'true') return

    const button = document.createElement('button');
    contentActions.setAttribute('data-silence', true);
    button.classList.add('profileSilence');
    button.innerHTML = '🤫';

    contentActions.insertBefore(button, contentActions.firstChild);
}

const isProfile = () => {
    if (document.querySelector('head meta[content*="twitter://user?screen_name="]')) {
        document.querySelector('body').setAttribute('data-profile', true)

        addProfileSilenceBtn()
    }
    else document.querySelector('body').removeAttribute('data-profile')
}

const observeTweets = () => {
    const observer = new MutationObserver((mutations) => {
        setTabStatusToBody();
        isProfile()
        if (!document.querySelector('[data-make-twitter-great-again] [role="group"] > div > [role="menu"]')) document.querySelector('body').removeAttribute('data-pop-open')
        mutations.forEach(() => addBtnToTweets());
    });
    observer.observe(document.body, {
        childList: true,
        subtree: true,
    });
    return observer;
}

(async () => {
    await waitForElm('[role="region"] article');

    setTabStatusToBody();
    isProfile();
    addBtnToTweets();

    document.addEventListener('click', (e) => {
        if (e.target.classList.contains('shitBtn')) handleBtnClick(e, shitPath);
        if (e.target.classList.contains('silenceBtn')) handleBtnClick(e, silencePath);
        if (e.target.classList.contains('profileSilence')) handleProfileBtnClick(e, silencePath);
    })

    const tweetsObserver = observeTweets();
    window.addEventListener('beforeunload', () => {
        tweetsObserver.disconnect();
    });
})();