Twitterᴾˡᵘˢ (View Twitter Origin Images)

Enhance the Twitter user experience. The main features include replacing images with the original quality and getting the original image URL by right-clicking, and hiding posts that contain specific hashtags or exceed a set number.

As of 14/03/2023. See the latest version.

// ==UserScript==
// @name        Twitterᴾˡᵘˢ (View Twitter Origin Images)
// @name:zh-TW  Twitterᴾˡᵘˢ (檢視Twitter原始圖檔)
// @name:zh-CN  Twitterᴾˡᵘˢ (检视Twitter原始图档)
// @namespace   https://greasyfork.org
// @version     0.1.0
// @description Enhance the Twitter user experience. The main features include replacing images with the original quality and getting the original image URL by right-clicking, and hiding posts that contain specific hashtags or exceed a set number.
// @description:zh-TW  加強使用推特的體驗。主要功能包含圖片更換為原始畫質並從右鍵取得原始圖片網址、當貼文包含指定hashtag或超過指定數量則隱藏該貼文。
// @description:zh-CN  加强使用推特的体验。主要功能包含图片更换为原始画质并从右键取得原始图片网址、当贴文包含指定hashtag或超过指定数量则隐藏该贴文。
// @author      Pixmi
// @icon        https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @match       https://twitter.com/*
// @match       https://pbs.twimg.com/media/*
// @license     MIT
// @grant       GM_setValue
// @grant       GM_getValue
// ==/UserScript==

(function () {
    'use strict';
    // Hide the post if the hashtag exceeds the set number. (If set to 0, it will not be enabled)
    GM_setValue('MAX_HASHTAGS', 20);
    // Hide the post if it contains the following hashtag. (Please include "#" and separate using commas)
    GM_setValue('OUT_HASHTAGS', ['#tag1', '#tag2'])

    let URL = window.location.href;
    // If browsing an image URL, change it to obtain the original quality.
    if (URL.match(/https:\/\/pbs\.twimg\.com\/media\/([a-zA-Z0-9\-\_]+)(\?format=|.)(jpg|jpeg|png)/) && !URL.includes('?name=orig')) {
        if (URL.indexOf('?format=') > 0) {
            URL = URL.replace('?format=', '.');
        }
        if (URL.match(/\&name=(\w+)/)) {
            URL = URL.replace(/\&name=(\w+)/g, '?name=orig');
        } else {
            URL = `${URL}?name=orig`;
        }
        window.location.replace(URL);
    }
    // If browsing tweets, activate the observer.
    if (URL.includes('twitter.com')) {
        const rootmatch = document.evaluate('//div[@id="react-root"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        const rootnode = rootmatch.singleNodeValue;
        const MAX_HASHTAGS = GM_getValue('MAX_HASHTAGS');
        const OUT_HASHTAGS = GM_getValue('OUT_HASHTAGS');
        if (rootnode) {
            let callback = function (mutationsList, observer) {
                for (let mutation of mutationsList) {
                    let target = mutation.target;
                    // image class name
                    if (target.className.includes('css-1dbjc4n')) {
                        // hashtag check
                        let hashtags = Array.from(target.querySelectorAll('.css-901oao > .r-18u37iz'), tag => tag.textContent);
                        let hideCheck = [];
                        if (hashtags.length && target.nodeName == 'ARTICLE') {
                            // MAX_HASHTAGS must be greater than 0.
                            if (MAX_HASHTAGS > 0) hideCheck.push(hashtags.length >= MAX_HASHTAGS);
                            hideCheck.push(hashtags.some(tag => OUT_HASHTAGS.find(item => item == tag)));
                            if (hideCheck.some(item => item === true)) {
                                target.closest('.css-1dbjc4n.r-1igl3o0').style.display = 'none';
                                target.remove();
                                continue;
                            }
                        }
                        let images = target.getElementsByTagName('img');
                        for (let i = 0; i < images.length; i++) {
                            let imgSrc = images[i].src;
                            if (imgSrc.includes('https://pbs.twimg.com/media/') && !imgSrc.match(/name=orig/)) {
                                images[i].src = imgSrc.replace('?format=', '.').replace(/&name=(\w+)/g, '?name=orig');
                            }
                        }
                    }
                }
            };
            const observeConfig = {
                attributes: true,
                childList: true,
                subtree: true
            };
            const observer = new MutationObserver(callback);

            observer.observe(document.body, observeConfig);
        }
    }
})();