Twitter Original Images Extractor

Extract original size images for Twitter.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @author   Jimmy Chin
// @name     Twitter Original Images Extractor
// @version  1.0.11
// @include       https://twitter.com*
// @description Extract original size images for Twitter.
// @namespace https://greasyfork.org/users/241557
// ==/UserScript==

javascript:(function(){
    const SELECTOR = {
        ARTICLE_CLASS: "css-1dbjc4n r-18u37iz r-1ny4l3l r-1udh08x r-1qhn6m8 r-i023vh",
        BUTTON_BAR_CLASS_ON_TIMELINE: ".css-1dbjc4n.r-1kbdv8c.r-18u37iz.r-1wtj0ep.r-1s2bzr4.r-hzcoqn",
        BUTTON_BAR_CLASS_ON_ARTICLE_IN_DIM_BACKGROUND: ".css-1dbjc4n.r-1oszu61.r-1ila09b.r-rull8r.r-qklmqi.r-1kfrmmb.r-1efd50x.r-5kkj8d.r-1kbdv8c.r-18u37iz.r-h3s6tt.r-1wtj0ep.r-3qxfft.r-s1qlax",
        BUTTON_BAR_CLASS_ON_ARTICLE_IN_DEFAULT_BACKGROUND: ".css-1dbjc4n.r-1oszu61.r-j5o65s.r-rull8r.r-qklmqi.r-1dgieki.r-1efd50x.r-5kkj8d.r-1kbdv8c.r-18u37iz.r-h3s6tt.r-1wtj0ep.r-3qxfft.r-s1qlax",
        BUTTON_BAR_CLASS_ON_ARTICLE_IN_LIGHT_OUT_BACKGROUND: ".css-1dbjc4n.r-1oszu61.r-1igl3o0.r-rull8r.r-qklmqi.r-2sztyj.r-1efd50x.r-5kkj8d.r-1kbdv8c.r-18u37iz.r-h3s6tt.r-1wtj0ep.r-3qxfft.r-s1qlax",
        TIME_CLASS_ON_ARTICLE: "css-4rbku5.css-18t94o4.css-901oao.css-16my406.r-111h2gw.r-1loqt21.r-poiln3.r-bcqeeo.r-qvutc0"
    }
    const ATTRIBUTE = {
        BUTTON_CLASS: "css-1dbjc4n r-18u37iz r-1h0z5md r-3qxfft r-h4g966 r-rjfia"
    }
    const BACKGROUND_COLOR = {
        DEFAULT: "rgb(255, 255, 255)",
        DIM: "rgb(21, 32, 43)",
        LIGHTS_OUT: "rgb(0, 0, 0)"
    }
    const PATTERN = {
        IMG: /^https:\/\/pbs.twimg.com\/media\//
    }

    init();

    function init() {
        getTweetData();
        setDOMObserver();
    }

    function setDOMObserver() {
        const DOMObserver = new MutationObserver(() => getTweetData());

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

    function getTweetData() {
        const tweets = document.querySelectorAll("article");
        if (!isEmpty(tweets)) {
            tweets.forEach(tweet => {
                setTimeout(() => {
                    if (!hasButtonBeenCreated(tweet)) {
                        if (isArticle(tweet)) {
                            createButtonForArticle(tweet);
                        } else {
                            createButtonForTimeline(tweet);
                        }
                    }
                }, 300);
            });
        }
    }

    function isArticle(tweet) {
        if (!isEmpty(tweet)) {
            return tweet.getAttribute("class") == SELECTOR.ARTICLE_CLASS;
        }
    }

    function hasButtonBeenCreated(tweet) {
        if (isArticle(tweet)) {
            return document.querySelector("#buttonForArticle");
        } else {
            return document.querySelector(`#button${getTweetIdFromTweet(tweet)}`);
        }
    }

    function createButtonForArticle(tweet) {
        if (!isEmpty(tweet)) {
            const buttonBar = document.querySelector(getButtonBarClassOnArticle());
            if (!isEmpty(buttonBar)) {
                const button = document.createElement("div");
                const lastButton = buttonBar.lastChild;
                const buttonWidth = lastButton.clientWidth;
                const buttonHeight = lastButton.clientHeight;
                button.setAttribute("class", ATTRIBUTE.BUTTON_CLASS);
                button.innerHTML = getButtonInnerHtml(buttonWidth, buttonHeight);
                button.id = "buttonForArticle";
                buttonBar.appendChild(button);

                button.addEventListener("click", () => {
                    const imgs = getImageFromTweet(tweet);
                    if (!isEmpty(imgs)) {
                        imgs.forEach(url => window.open(url));
                    }
                });
            }
        }
    }

    function createButtonForTimeline(tweet) {
        if (!isEmpty(tweet)) {
            const buttonBar = tweet.querySelector(SELECTOR.BUTTON_BAR_CLASS_ON_TIMELINE);
            if (!isEmpty(buttonBar)) {
                const button = document.createElement("div");
                const buttonCss = buttonBar.firstChild.getAttribute("class");
                const lastButton = buttonBar.lastChild;
                const buttonWidth = lastButton.clientWidth;
                const buttonHeight = lastButton.clientHeight;
                lastButton.setAttribute("class", buttonCss)
                button.setAttribute("class", buttonCss);
                button.innerHTML = getButtonInnerHtml(buttonWidth, buttonHeight);
                button.id = `button${getTweetIdFromTweet(tweet)}`;
                buttonBar.appendChild(button);

                button.addEventListener("click", () => {
                    const imgs = getImageFromTweet(tweet);
                    if (!isEmpty(imgs)) {
                        imgs.forEach(url => window.open(url));
                    }
                });
            }
        }
    }

    function getTweetIdFromTweet(tweet) {
        let result = null;
        if (!isEmpty(tweet)) {
            const timeObj = tweet.querySelector("time");
            if (!isEmpty(timeObj)) {
                const href = timeObj.parentNode.href;
                const lastSlashIndex = href.lastIndexOf("/");
                result = href.substr(lastSlashIndex + 1);
            }
        }
        return result;
    }

    function getImageFromTweet(tweet) {
        let result = null;
        if (!isEmpty(tweet)) {
            const imgs = [...tweet.querySelectorAll("img")];

            if (!isEmpty(imgs)) {
                const srcArray = [];

                imgs.filter(img => img.src.match(PATTERN.IMG))
                    .forEach(img => srcArray.push(img.src));
                result = transImageUrlToOrig(srcArray);
            }
        }

        return result;
    }

    function transImageUrlToOrig(imgs) {
        const result = [];
        if (!isEmpty(imgs)) {
            imgs.forEach(img => {
                /* get the "name" param index of the query string*/
                const nameParamKeyOfQueryString = img.indexOf("name=");
                const origImageUrl = img.substr(0, nameParamKeyOfQueryString) + "name=orig";
                result.push(origImageUrl);
            });
        }
        return result;
    }

    function isEmpty(data) {
        let result = false;
        if (data != undefined && data != null) {
            if (Array.isArray(data)) {
                if (data.length == 0) {
                    result = true;
                }
            } else if (typeof data == "string") {
                if (data.trim() == "") {
                    result = true;
                }
            }
        } else {
            result = true;
        }

        return result;
    }

    function getBGColor() {
        return document.body.style.backgroundColor;
    }

    function getButtonInnerHtml(buttonWidth, buttonHeight) {
        let result = null;
        switch (getBGColor()) {
            case BACKGROUND_COLOR.DEFAULT:
                result = getDefaultButtonInnerHtml(buttonWidth, buttonHeight);
                break;
            case BACKGROUND_COLOR.DIM:
                result = getDimButtonInnerHtml(buttonWidth, buttonHeight);
                break;
            case BACKGROUND_COLOR.LIGHTS_OUT:
                result = getLightsOutButtonInnerHtml(buttonWidth, buttonHeight);
                break;
        }
        return result;
    }

    function getButtonBarClassOnArticle() {
        let result = null;
        switch (getBGColor()) {
            case BACKGROUND_COLOR.DEFAULT:
                result = SELECTOR.BUTTON_BAR_CLASS_ON_ARTICLE_IN_DEFAULT_BACKGROUND;
                break;
            case BACKGROUND_COLOR.DIM:
                result = SELECTOR.BUTTON_BAR_CLASS_ON_ARTICLE_IN_DIM_BACKGROUND;
                break;
            case BACKGROUND_COLOR.LIGHTS_OUT:
                result = SELECTOR.BUTTON_BAR_CLASS_ON_ARTICLE_IN_LIGHT_OUT_BACKGROUND;
                break;
        }
        return result;
    }

    function getDefaultButtonInnerHtml(buttonWidth, buttonHeight) {
        return `<a>
                    <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
                        width="${buttonWidth}" height="${buttonHeight}"
                        viewBox="0 0 172 172"
                        style=" fill:#000000;"><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z" fill="none"></path><g fill="#657786"><path d="M99.33,37.84c-1.73344,0.215 -3.02344,1.69313 -3.01,3.44v24.295c-39.60031,1.20938 -60.9525,22.17188 -71.4875,43c-10.68281,21.11031 -11.05906,41.69656 -11.0725,42.355v0.215v0.215c-0.05375,1.89469 1.43781,3.49375 3.3325,3.5475c1.89469,0.05375 3.49375,-1.43781 3.5475,-3.3325v-0.215c0.02688,-0.3225 0.79281,-10.73656 10.6425,-21.8225c9.675,-10.87094 28.55469,-21.83594 65.0375,-22.575v23.7575c0,1.31688 0.7525,2.52625 1.94844,3.10406c1.19594,0.57781 2.60688,0.41656 3.64156,-0.41656l55.04,-44.72c0.81969,-0.65844 1.29,-1.63937 1.29,-2.6875c0,-1.04812 -0.47031,-2.02906 -1.29,-2.6875l-55.04,-44.72c-0.72562,-0.59125 -1.65281,-0.86 -2.58,-0.7525zM103.2,48.4825l46.1175,37.5175l-46.1175,37.5175v-20.3175c0,-1.89469 -1.54531,-3.44 -3.44,-3.44c-40.43344,0 -62.22906,12.42969 -73.6375,25.2625c-0.43,0.48375 -0.67187,0.91375 -1.075,1.3975c1.45125,-4.73 3.35938,-9.7825 5.9125,-14.835c10.03781,-19.83375 29.19969,-39.345 68.8,-39.345c1.89469,0 3.44,-1.54531 3.44,-3.44z"></path></g></g>
                    </svg>
                    <div class="css-1dbjc4n r-sdzlij r-1p0dtai r-xoduu5 r-1d2f490 r-xf4iuw r-1ny4l3l r-u8s1d r-zchlnj r-ipm5af r-o7ynqc r-6416eg"></div>
                <a>`;
    }

    function getDimButtonInnerHtml(buttonWidth, buttonHeight) {
        return `<a>
                    <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
                        width="${buttonWidth}" height="${buttonHeight}"
                        viewBox="0 0 172 172"
                        style=" fill:#000000;"><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z" fill="none"></path><g fill="#8899a6"><path d="M99.33,37.84c-1.73344,0.215 -3.02344,1.69313 -3.01,3.44v24.295c-39.60031,1.20938 -60.9525,22.17188 -71.4875,43c-10.68281,21.11031 -11.05906,41.69656 -11.0725,42.355v0.215v0.215c-0.05375,1.89469 1.43781,3.49375 3.3325,3.5475c1.89469,0.05375 3.49375,-1.43781 3.5475,-3.3325v-0.215c0.02688,-0.3225 0.79281,-10.73656 10.6425,-21.8225c9.675,-10.87094 28.55469,-21.83594 65.0375,-22.575v23.7575c0,1.31688 0.7525,2.52625 1.94844,3.10406c1.19594,0.57781 2.60688,0.41656 3.64156,-0.41656l55.04,-44.72c0.81969,-0.65844 1.29,-1.63937 1.29,-2.6875c0,-1.04812 -0.47031,-2.02906 -1.29,-2.6875l-55.04,-44.72c-0.72562,-0.59125 -1.65281,-0.86 -2.58,-0.7525zM103.2,48.4825l46.1175,37.5175l-46.1175,37.5175v-20.3175c0,-1.89469 -1.54531,-3.44 -3.44,-3.44c-40.43344,0 -62.22906,12.42969 -73.6375,25.2625c-0.43,0.48375 -0.67187,0.91375 -1.075,1.3975c1.45125,-4.73 3.35938,-9.7825 5.9125,-14.835c10.03781,-19.83375 29.19969,-39.345 68.8,-39.345c1.89469,0 3.44,-1.54531 3.44,-3.44z"></path></g></g>
                    </svg>
                    <div class="css-1dbjc4n r-sdzlij r-1p0dtai r-xoduu5 r-1d2f490 r-xf4iuw r-1ny4l3l r-u8s1d r-zchlnj r-ipm5af r-o7ynqc r-6416eg"></div>
                <a>`;
    }

    function getLightsOutButtonInnerHtml(buttonWidth, buttonHeight) {
        return `<a>
                    <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
                        width="${buttonWidth}" height="${buttonHeight}"
                        viewBox="0 0 172 172"
                        style=" fill:#000000;"><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z" fill="none"></path><g fill="#6e767d"><path d="M99.33,37.84c-1.73344,0.215 -3.02344,1.69313 -3.01,3.44v24.295c-39.60031,1.20938 -60.9525,22.17188 -71.4875,43c-10.68281,21.11031 -11.05906,41.69656 -11.0725,42.355v0.215v0.215c-0.05375,1.89469 1.43781,3.49375 3.3325,3.5475c1.89469,0.05375 3.49375,-1.43781 3.5475,-3.3325v-0.215c0.02688,-0.3225 0.79281,-10.73656 10.6425,-21.8225c9.675,-10.87094 28.55469,-21.83594 65.0375,-22.575v23.7575c0,1.31688 0.7525,2.52625 1.94844,3.10406c1.19594,0.57781 2.60688,0.41656 3.64156,-0.41656l55.04,-44.72c0.81969,-0.65844 1.29,-1.63937 1.29,-2.6875c0,-1.04812 -0.47031,-2.02906 -1.29,-2.6875l-55.04,-44.72c-0.72562,-0.59125 -1.65281,-0.86 -2.58,-0.7525zM103.2,48.4825l46.1175,37.5175l-46.1175,37.5175v-20.3175c0,-1.89469 -1.54531,-3.44 -3.44,-3.44c-40.43344,0 -62.22906,12.42969 -73.6375,25.2625c-0.43,0.48375 -0.67187,0.91375 -1.075,1.3975c1.45125,-4.73 3.35938,-9.7825 5.9125,-14.835c10.03781,-19.83375 29.19969,-39.345 68.8,-39.345c1.89469,0 3.44,-1.54531 3.44,-3.44z"></path></g></g>
                    </svg>
                    <div class="css-1dbjc4n r-sdzlij r-1p0dtai r-xoduu5 r-1d2f490 r-xf4iuw r-1ny4l3l r-u8s1d r-zchlnj r-ipm5af r-o7ynqc r-6416eg"></div>
                <a>`;
    }
})();