Youtube download link inserter

Inserts download links to YouTube

// ==UserScript==
// @name        Youtube download link inserter
// @match       https://www.youtube.com/watch?v=*
// @icon        https://s.ytimg.com/yts/img/favicon_32-vfl8NGn4k.png
// @icon64      https://s.ytimg.com/yts/img/favicon_144-vflWmzoXw.png
// @version     2.7
// @description Inserts download links to YouTube
// @author      TheSeven, RoundRobin
// @namespace https://greasyfork.org/users/97514
// ==/UserScript==

var extension = {
    "video/mp4": ".mp4",
    "video/webm": ".mkv",
    "audio/mp4": ".m4a",
    "audio/webm": ".mkv",
};

function makeButton(container, background, info, size, rate, url) {
    var a = document.createElement("a");
    a.style.display = "block";
    a.style.cssFloat = "left";
    a.style.background = background;
    a.style.border = "1px solid #888";
    a.style.boxShadow = "1px 1px 2px rgba(0, 0, 0, 0.2)";
    a.style.padding = "2px";
    a.style.margin = "2px";
    a.style.color = "#333";
    a.style.textDecoration = "none";
    container.appendChild(a);
    var mime = info.mimeType.split(";", 2);
    var format = mime[0].trim();
    var codecs = mime[1].split("\"", 2)[1].trim().split(",");
    if (info.video) {
        a.appendChild(document.createTextNode("V: " + codecs[0].split(".", 1)[0].trim() + " " + info.video.qualityLabel));
        a.appendChild(document.createElement("br"));
    }
    if (info.audio) {
        a.appendChild(document.createTextNode("A: " + codecs[codecs.length - 1].split(".", 1)[0].trim()));
        a.appendChild(document.createElement("br"));
    }
    if (rate) {
        a.appendChild(document.createTextNode(Math.round(rate / 1000 * 8) + " kbit/s"));
        a.appendChild(document.createElement("br"));
    }
    var dlindicator = document.createElement("strong");
    dlindicator.style.display = "none";
    dlindicator.appendChild(document.createTextNode("DL "));
    var dlprogress = document.createTextNode("");
    dlindicator.appendChild(dlprogress);
    a.appendChild(dlindicator);
    a.title = mime;
    if (info.video) a.title += "\nVideo: " + info.video.quality + " " + info.video.width + "x" + info.video.height;
    if (info.video && info.video.fps) a.title += ", " + info.video.fps + " FPS";
    if (size) a.title += "\nSize: " + (Math.round(size / 10000) / 100) + " MB";
    a.href = url;
    a.download = ytplayer.config.args.title + extension[format];
    var xhr = false;
    a.onclick = function() {
        if (xhr) {
            xhr.abort();
            xhr = false;
            dlindicator.style.display = "none";
            return false;
        }
        dlindicator.style.display = "inline";
        xhr = new XMLHttpRequest();
        xhr.open("GET", a.href);
        xhr.responseType = "blob";
        xhr.onprogress = function(e) {
            dlprogress.nodeValue = (Math.round(1000 * e.loaded / e.total) / 10).toFixed(1) + "%";
        };
        xhr.onload = function(e) {
            if (xhr.status > 299) {
                dlprogress.nodeValue = "Error " + xhr.status;
                return;
            }
            var dl = document.createElement("a");
            dl.style.display = "none";
            dl.href = URL.createObjectURL(e.target.response);
            dl.download = a.download;
            container.appendChild(dl);
            setTimeout(function() {
                dl.click();
                container.removeChild(dl);
                setTimeout(function() {
                    URL.revokeObjectURL(dl.href);
                    xhr = false;
                    dlindicator.style.display = "none";
                }, 1000);
            }, 1);
        };
        xhr.send();
        return false;
    };
}

function populate()
{
    var outerkey, muxstreams, muxurlkey, muxmetakey, substreams, subsizekey, subratekey;
    var clonedConfig = Object.assign({}, ytplayer.config);
    clonedConfig.args = Object.assign({}, ytplayer.config.args);
    clonedConfig.args.adaptive_fmts = null;
    var player = yt.player.Application.create(document.createElement("div"), clonedConfig);
    player.dispose();
    if (!Object.keys(player).some(key1 => {
        var o1 = player[key1];
        if (!o1 || typeof(o1) != "object" || Array.isArray(o1)) return;
        return Object.keys(o1).some(key2 => {
            var o2 = o1[key2];
            if (!o2 || typeof(o2) != "object" || Array.isArray(o2)) return;
            return Object.keys(o2).some(key3 => {
                var o3 = o2[key3];
                if (!o3 || !Array.isArray(o3)) return;
                if (!o3[0] || typeof(o3[0]) != "object") return;
                return Object.keys(o3[0]).some(key4 => {
                    var o4 = o3[0][key4];
                    if (typeof(o4) != "string" || o4.indexOf("https://") || o4.indexOf("googlevideo.com/videoplayback?") < 0) return;
                    return Object.keys(o3[0]).some(key5 => {
                        var o5 = o3[0][key5];
                        if (!o5 || typeof(o5) != "object" || o5.id === undefined) return;
                        outerkey = key1;
                        muxstreams = o3;
                        muxurlkey = key4;
                        muxmetakey = key5;
                        return true;
                    });
                });
            });
        });
    })) return console.error("Couldn't find muxed streams");
    player = yt.player.Application.create(document.createElement("div"), ytplayer.config);
    player.dispose();
    if (!Object.keys(player[outerkey]).some(key1 => {
        var o1 = player[outerkey][key1];
        if (!o1 || typeof(o1) != "object" || Array.isArray(o1)) return;
        return Object.keys(o1).some(key2 => {
            var o2 = o1[key2];
            if (!o2 || typeof(o2) != "object" || Array.isArray(o2)) return;
            return Object.keys(o2).some(key3 => {
                var o3 = o2[key3];
                if (!o3 || typeof(o3) != "object" || Array.isArray(o3)) return;
                return Object.keys(o3).some(key4 => {
                    var o4 = o3[key4];
                    if (!o4 || typeof(o4) != "object" || Array.isArray(o4)) return;
                    if (!o4.info || typeof(o4.info) != "object" || o4.info.id != key4) return;
                    return Object.keys(o4).some(key5 => {
                        var o5 = o4[key5];
                        if (typeof(o5) != "number" || key5 == "lastModified") return;
                        return Object.keys(o4.info).some(key6 => {
                            var o6 = o4.info[key6];
                            if (typeof(o6) != "number" || o6 < 1000) return;
                            substreams = o3;
                            subsizekey = key5;
                            subratekey = key6;
                            return true;
                        });
                    });
                });
            });
        });
    })) return console.error("Couldn't find individual streams");

    var detailbox = document.getElementById("action-panel-details");
    var container = document.createElement("div");
    container.className = "yt-card yt-card-has-padding";
    container.style.color = "#333";
    detailbox.parentNode.insertBefore(container, detailbox);
    var title = document.createElement("strong");
    title.appendChild(document.createTextNode("Download links:"));
    container.appendChild(title);
    var muxedContainer = document.createElement("div");
    container.appendChild(muxedContainer);
    var videoContainer = document.createElement("div");
    videoContainer.style.clear = "both";
    container.appendChild(videoContainer);
    var audioContainer = document.createElement("div");
    audioContainer.style.clear = "both";
    container.appendChild(audioContainer);
    var tailContainer = document.createElement("div");
    tailContainer.style.clear = "both";
    container.appendChild(tailContainer);

    muxstreams.forEach(function(s) {
        makeButton(muxedContainer, "#cfc", s[muxmetakey], 0, 0, s[muxurlkey]);
    });
    Object.keys(substreams).forEach(function(fid) {
        console.log("Stream " + fid);
        var s = substreams[fid];
        Object.keys(s).some(key1 => {
            var o1 = s[key1];
            if (!o1 || typeof(o1) != "object" || Array.isArray(o1)) return;
            return Object.keys(o1).some(key2 => {
                var o2 = o1[key2];
                if (!o2 || typeof(o2) != "object" || Array.isArray(o2)) return;
                return Object.keys(o2).some(key3 => {
                    var o3 = o2[key3];
                    if (typeof(o3) != "string" || o3.indexOf("https://") || o3.indexOf("googlevideo.com/videoplayback?") < 0) return;
                    if (o3.indexOf("&signature=") < 0)
                        Object.keys(o2).some(key4 => {
                            var o4 = o2[key4];
                            if (!o4 || typeof(o4) != "object" || Array.isArray(o4)) return;
                            Object.keys(o4).forEach(key5 => {
                                if (key5 == "keepalive" || key5 == "mime") return;
                                o3 += "&" + key5 + "=" + o4[key5];
                            });
                            return true;
                        });
                    console.log(o2);
                    console.log(o3);
                    if (s.info.video) makeButton(videoContainer, "#ccf", s.info, s[subsizekey], s.info[subratekey], o3);
                    if (s.info.audio) makeButton(audioContainer, "#fcc", s.info, s[subsizekey], s.info[subratekey], o3);
                    return true;
                });
            });
        });
    });
}

window.addEventListener("spfdone", function(){setTimeout(populate,100)});
populate();