Twitter Image Saver

Script to download tweet images in one click.

// ==UserScript==
// @name        Twitter Image Saver
// @author      aloneunix
// @namespace   https://twitter.com/aloneunix
// @description Script to download tweet images in one click.
// @include     https://twitter.com/*
// @exclude     https://twitter.com/settings/*
// @grant       GM_download
// @version     1.0
// @noframes
// ==/UserScript==
var TwitterImageSaver;
(function (TwitterImageSaver) {
    function cutSize(url) {  // TODO: rename
        if (url.indexOf(":") != url.lastIndexOf(":")) {
            url = url.substr(0, url.lastIndexOf(':'));
        }
        return url;
    }
    function GetOrigUrl(url) {
        return cutSize(url) + ':orig';
    }
    function GetFileExtension(url) {
        url = cutSize(url);
        return url.substring(url.lastIndexOf(".")+1);
    }
    var Gallery;
    (function (Gallery) {
        var galleryTweetContainer = document.getElementsByClassName("GalleryTweet")[0];
        var observer = new MutationObserver(function (mutations) {
            mutations.forEach(function (mutation) {
                if (mutation.type == "childList") {
                    if (galleryTweetContainer.children.length !== 0) {
                        var tweet = galleryTweetContainer.getElementsByClassName("tweet")[0];
                        new TweetTools(tweet);
                    }
                }
            });
        });
        observer.observe(galleryTweetContainer, {
            childList: true
        });
    })(Gallery || (Gallery = {}));
    var TweetTools = (function () {
        function TweetTools(tweet) {
            this.dlButton = document.createElement("button");
            var t = this;
            t.tweet = tweet;
            t.GetMedia();
            t.actionFooter = tweet.getElementsByClassName("ProfileTweet-actionList")[0];

            var dlContainer = document.createElement("div");
            dlContainer.className = "ProfileTweet-action ProfileTweet-action--reply tis-new-tab";
            dlContainer.appendChild(t.dlButton);
            if (t.actionFooter.lastElementChild.classList.contains("ProfileTweet-action--more")) {
                t.actionFooter.insertBefore(dlContainer, t.actionFooter.lastElementChild);
            } else {
                t.actionFooter.appendChild(dlContainer);
            }
            t.dlButton.className = "ProfileTweet-actionButton u-textUserColorHover js-actionButton js-tooltip";
            t.dlButton.title = "Download";

            if (t.downloadableMedia.length == 1) {
                t.dlButton.onclick = function () {
                    GM_download({url: t.downloadableMedia[0], name: t.GetFilename(t.downloadableMedia[0])});
                };
            }
            else {
            	// download only current image if it's open in gallery
            	var galleryImageConainer = document.getElementsByClassName("Gallery-media")[0];
            	if (galleryImageConainer.children.length !== 0) {
                    var image = galleryImageConainer.children[0];
                    var image_orig_src = GetOrigUrl(image.src)
                    // get index of opened image
                    var i = (function () {
                        for (var i=0; i<t.downloadableMedia.length; i++)
                            if (t.downloadableMedia[i] === image_orig_src) {
                                return i;
                            }
                    }());
                    t.dlButton.onclick = function () {
                        GM_download({url: t.downloadableMedia[i], name: t.GetFilename(t.downloadableMedia[i], i+1)});
                    };

                } else {
                    t.dlButton.onclick = function () {
                        t.downloadableMedia.forEach(function (mediaUrl, i) {
                            GM_download({url: mediaUrl, name: t.GetFilename(mediaUrl, i+1)});
                        });
                    };
                }
            }
            var dlIcon = document.createElement("span");
            dlIcon.className = "Icon Icon--medium Icon--download";

            var iconContainer = document.createElement("div");
            iconContainer.className = "IconContainer";
            iconContainer.appendChild(dlIcon);
            t.dlButton.appendChild(iconContainer);

            tweet.className += " has-tis-tools";
        }
        TweetTools.prototype.GetFilename = function (url, index) {
            var t = this;
            var username = t.tweet.getAttribute("data-screen-name");
            var tweet_id = t.tweet.getAttribute("data-tweet-id");
            var ts = t.tweet.getElementsByClassName("_timestamp")[0].getAttribute("data-time-ms");
            var formatted_ts = new Date(parseInt(ts)).toISOString().slice(0,10);
            var extension = GetFileExtension(url);
            var _index = index !== undefined ? "_"+index : "";
            var filename = `${username}_${formatted_ts}_${tweet_id}${_index}.${extension}`;
            return filename;
        };
        TweetTools.prototype.GetMedia = function () {
            var t = this;
            var media = t.tweet.getElementsByClassName("AdaptiveMedia")[0];
            t.downloadableMedia = [];
            var images = media.getElementsByTagName("img");
            [].forEach.call(images, function (image) {
                var origImg = GetOrigUrl(image.src);
                t.downloadableMedia.push(origImg);
            });
        };
        return TweetTools;
    }());
    function initTools() {
        var tweets = document.querySelectorAll(":not(.GalleryTweet) > .tweet.has-cards:not(.has-tis-tools):not(.cards-forward):not([data-card2-type])");
        tweets = Array.from(tweets).filter(function(tweet) {return tweet.querySelector(".AdaptiveMedia:not(.is-video)");});
        [].forEach.call(tweets, function (tweet) {
            new TweetTools(tweet);
        });
    }
    var timelineObserver = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            if (mutation.type == "childList") {
                initTools();
            }
        });
    });
    var overlayObserver = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            if (mutation.type == "childList") {
                var overlayContainer = document.getElementsByClassName("PermalinkOverlay-body")[0];
                if (overlayContainer.children.length !== 0) {
                    timelineObserver.disconnect();
                    initTools();
                    var overlayStreams = overlayContainer.getElementsByClassName(".stream-items");
                    [].forEach.call(overlayStreams, function (stream) {
                        timelineObserver.observe(stream, {
                            childList: true
                        });
                    });
                }
            }
        });
    });
    var bodyObserver = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            if (mutation.type == "attributes") {
                timelineObserver.disconnect();
                overlayObserver.disconnect();
                initTools();
                if (document.body.classList.contains("overlay-enabled")) {
                    var overlayContainer = document.getElementsByClassName("PermalinkOverlay-body")[0];
                    overlayObserver.observe(overlayContainer, {
                        childList: true
                    });
                    [].forEach.call(overlayContainer.getElementsByClassName("stream-items"), function (stream) {
                        timelineObserver.observe(stream, {
                            childList: true
                        });
                    });
                }
                else {
                    timelineObserver.observe(document.getElementById("stream-items-id"), {
                        childList: true
                    });
                }
            }
        });
    });
    function initBodyObserver() {
        bodyObserver.observe(document.body, {
            attributes: true
        });
    }
    initBodyObserver();
})(TwitterImageSaver || (TwitterImageSaver = {}));