Twitter Original Images Extractor

Extract original size images for Twitter.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

Advertisement:

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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>`;
    }
})();