您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download videos and audio from YouTube with support DASH formats
// ==UserScript== // @name YouTube Downloader (update) // @namespace https://gf.qytechs.cn/es/users/77045-foxrom // @version 0.10.1 // @description Download videos and audio from YouTube with support DASH formats // @author foxrom // @icon http://youtube.com/favicon.ico // @include http://www.youtube.com* // @include https://www.youtube.com* // @license Creative Commons; http://creativecommons.org/licenses/by/4.0/ // @require http://code.jquery.com/jquery-1.11.0.min.js // @grant GM_xmlhttpRequest // @grant GM_download // ==/UserScript== // Down arrow selector icon is made by Freepik at http://www.freepik.com under Creative Commons 3.0 // src/prototypes.js // ================================================= // These are functions that are references throughout // the script that perform useful tasks var downloadCancelBtn_width = 30; function GM_download2_Init(){ var $container = $("#downloadBtnCont"); var $downloadCancelCont = $("#downloadCancelCont"); if ($downloadCancelCont.length === 0) { $downloadCancelCont = $("<span>", { id:"downloadCancelCont", }); $container.append($downloadCancelCont); }else{ $downloadCancelCont.find("button").click(); $downloadCancelCont.html(""); } } function GM_download2 (url, name) { if (url == null) return; var $downloadCancelBtnLabel_img = $("<img>", { class:"midalign downloadCancelBtnLabel_img", src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACJklEQVQ4T2NkoBAwUqifAcWA5TJSyWwcrAbBdx7mYjN4PgMDh5iW2s7v334cCHnwqB6kBm7AclnxJCN9nbkc7OwMp09f6gh59KQS2ZBJQkJ8GtJix3V11LUuXbp50/3qDQ0UA1bLSrcZ6apXcnNyMHz99p3hzKUbReFPn/eDFE3n4RFTVZA+raOuLHf91sPndx8+1kn59OkdigEgzkoZiXJ9VaUOfm4uho9fvjGcv3kn7sf3X4eVZCRPqylIi9x58vTDrVsPtZO/fXsGcx1GIC4XF03SUZSbI8zLzfj685f/n798+6YkKcb98OXb7zcePNZP/PTpNrLXsMbCQmFhfz1pibVi/DzMIMVP3334ffXVS7PE1x8uoAcuVgMWifG5awuJbRHn42UBG/Dp489rj1+aJn79epmgAUtFBKO1hIQWiXCwM737+ev/59+/f8jzcHM++Pzl6413H3VTP368j9MLy4SECnQFePv5WVkZPv/5w3Dh/af073/+7FPh5jytxMcrcO/L57e3P3zRTv369SVGIK4UEqrX5eZo4GZmZvj+7x/Dxa/fqsPfvm8DKZzLxSUlx8t9Ro2TQ/LGt+9PHvz+q5P+/v1HlGhcxseXpcvBPpWNkZHhws8fk8I/fMpHduocPj4hCWamk9rs7CpXf32/6vPukw5GOljKx5fDzvTXMPjD1xRGBob/6AG2SFycW/Dnj/3fGBn2h7//WI5hADkZi+LcCAA1QdYRLzAxRQAAAABJRU5ErkJggg==" }); var $downloadCancelBtnLabel_img2 = $("<img>", { class:"midalign downloadCancelBtnLabel_img2", src:"data:image/gif;base64,R0lGODlhEAAQAPYAAF6IsP///2CJsaK60ai+1GiPtIChwHWZu5q0zf///6S80oGiwd7m7srX5GmQtZWwy6C50GWNs6zB1uzw9Y2rx5axy5y2zmSMs2GKsZSvynyevtPe6fb4+sHR4HKWuWuStoemxOPq8dvk7dHd6HibvIKiwdji68zZ5tbh6tDc53qdvn6gv8XU4oyqxmqRtrfJ25GuyYqoxYinxMLS4cTT4cDQ36m/1HCVua7D11+IsJeyzO7y9uLp8MfW4/v8/MvY5X2fv9rj7MbV4u3x9maOtHeavJu1zm+VuG2Tt9Xg6pKvyeft8unv9JCtyLbJ2s/b593l7rvM3XmcvYWkw+bs8q/E19Tf6cnW5Pf5+vj6+4+sx26Ut87a5mOLsoupxr3O3rzN3nOXupmzzPL1+Oju8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAHaIAAgoMgIiYlg4kACxIaACEJCSiKggYMCRselwkpghGJBJEcFgsjJyoAGBmfggcNEx0flBiKDhQFlIoCCA+5lAORFb4AJIihCRbDxQAFChAXw9HSqb60iREZ1omqrIPdJCTe0SWI09GBACH5BAkKAAAALAAAAAAQABAAAAdrgACCgwc0NTeDiYozCQkvOTo9GTmDKy8aFy+NOBA7CTswgywJDTIuEjYFIY0JNYMtKTEFiRU8Pjwygy4ws4owPyCKwsMAJSTEgiQlgsbIAMrO0dKDGMTViREZ14kYGRGK38nHguHEJcvTyIEAIfkECQoAAAAsAAAAABAAEAAAB2iAAIKDAggPg4iJAAMJCRUAJRIqiRGCBI0WQEEJJkWDERkYAAUKEBc4Po1GiKKJHkJDNEeKig4URLS0ICImJZAkuQAhjSi/wQyNKcGDCyMnk8u5rYrTgqDVghgZlYjcACTA1sslvtHRgQAh+QQJCgAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCQARAtOUoQRGRiFD0kJUYWZhUhKT1OLhR8wBaaFBzQ1NwAlkIszCQkvsbOHL7Y4q4IuEjaqq0ZQD5+GEEsJTDCMmIUhtgk1lo6QFUwJVDKLiYJNUd6/hoEAIfkECQoAAAAsAAAAABAAEAAAB2iAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4uen4ICCA+IkIsDCQkVACWmhwSpFqAABQoQF6ALTkWFnYMrVlhWvIKTlSAiJiVVPqlGhJkhqShHV1lCW4cMqSkAR1ofiwsjJyqGgQAh+QQJCgAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCSMhREZGIYYGY2ElYebi56fhyWQniSKAKKfpaCLFlAPhl0gXYNGEwkhGYREUywag1wJwSkHNDU3D0kJYIMZQwk8MjPBLx9eXwuETVEyAC/BOKsuEjYFhoEAIfkECQoAAAAsAAAAABAAEAAAB2eAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4ueICImip6CIQkJKJ4kigynKaqKCyMnKqSEK05StgAGQRxPYZaENqccFgIID4KXmQBhXFkzDgOnFYLNgltaSAAEpxa7BQoQF4aBACH5BAkKAAAALAAAAAAQABAAAAdogACCg4SFggJiPUqCJSWGgkZjCUwZACQkgxGEXAmdT4UYGZqCGWQ+IjKGGIUwPzGPhAc0NTewhDOdL7Ykji+dOLuOLhI2BbaFETICx4MlQitdqoUsCQ2vhKGjglNfU0SWmILaj43M5oEAOwAAAAAAAAAAAA==", style:"width:16px;", }); var $downloadCancelBtn = $("<button>", { class:"downloadCancelBtn" }); $downloadCancelBtn.attr({ "label": "Cancelar descarga: " + name, "title": "Cancelar descarga: " + name, }); var $downloadCancelBtnProgress = $("<div>", { class:"downloadCancelBtnProgress" }); $downloadCancelBtn.append($downloadCancelBtnProgress); var $downloadCancelBtnLabel = $("<span>", { class:"downloadCancelBtnLabel", html:$downloadCancelBtnLabel_img, }); $downloadCancelBtn.append($downloadCancelBtnLabel); var $downloadCancelCont = $("#downloadCancelCont"); $downloadCancelCont.append($downloadCancelBtn); var data = { method: 'GET', responseType: 'arraybuffer', onload: function (res) { console.log(name + " > GM_download2 > onload:", "descarga completa ini"); $downloadCancelBtnLabel.append($downloadCancelBtnLabel_img2); var blob = new Blob([res.response], {type: 'application/octet-stream'}); var url = URL.createObjectURL(blob); // blob url var a = document.createElement('a'); a.setAttribute('href', url); a.setAttribute('download', data.name != null ? data.name : 'filename'); document.documentElement.appendChild(a); // call download // a.click() or CLICK the download link can't modify filename in Firefox (why?) // Solution from FileSaver.js, https://github.com/eligrey/FileSaver.js/ var e = new MouseEvent('click'); a.dispatchEvent(e); document.documentElement.removeChild(a); setTimeout(function(){ // reduce memory usage URL.revokeObjectURL(url); if ('close' in blob) blob.close(); // File Blob.close() API, not supported by all the browser right now blob = undefined; }, 1000); if (typeof data.onafterload === 'function') data.onafterload(); // call onload function $downloadCancelBtn.remove(); console.log(name + " > GM_download2 > onload:", "descarga completa fin"); }, onprogress: function(res) { if (res.lengthComputable) { var percentComplete = res.loaded / res.total; percentComplete = percentComplete * downloadCancelBtn_width; $downloadCancelBtnProgress.css("width", percentComplete.toFixed(3)+"px"); console.log(name + " > GM_download2 > onprogress:", percentComplete.toFixed(3) + " de " + downloadCancelBtn_width); if(parseFloat(percentComplete) >= parseFloat(downloadCancelBtn_width)){ $downloadCancelBtnLabel.html($downloadCancelBtnLabel_img2); //$downloadCancelBtn.off('click'); console.log(name + " > GM_download2 > onprogress:", "100% = " + parseFloat(percentComplete) + " - " + parseFloat(downloadCancelBtn_width)); } } else { GM_xhr.abort(); $downloadCancelBtn.remove(); console.log(name + " > GM_download2 > onprogress:", "El tamaño total es desconocida"); } }, }; if (typeof url === 'string') { data.url = url; data.name = name; } else { if (url instanceof Object === false) return; // as documentation, you can only use [url, name, headers, saveAs, onload, onerror] function, but we won't check them // Notice: saveAs is not supported if (url.url == null) return; for (var i in url) { if (i === 'onload') data.onafterload = url.onload; // onload function support else data[i] = url[i]; } } // it returns this GM_xhr, thought not mentioned in documentation var GM_xhr = GM_xmlhttpRequest(data); $downloadCancelBtn.on({ click: function() { GM_xhr.abort(); this.remove(); }, }); return GM_xhr; } // Set JSON localstorage Storage.prototype.setObject = function(key, value) { this.setItem(key, JSON.stringify(value)); }; // Retrieve JSON localstorage Storage.prototype.getObject = function(key) { var value = this.getItem(key); return value && JSON.parse(value); }; // Get the setting from an encoded URL string String.prototype.getSetting = function(setting, index) { index = index*2-1 || 1; var val = false; var regex = new RegExp("(?:\\?|&|^|,)"+setting+"=([^&|,]*)", "g"); var split = this.split(regex); if (split.length > index) { val = split[index].split(",")[0]; } return val; }; String.prototype.setSetting = function(setting, value) { var newString = this; var hasQuestionMark = (newString.indexOf("?") !== -1); if (!hasQuestionMark) { newString += "?"; // Search for setting, delete it if it exists } else { var search = newString.split(setting+"="); if (search.length > 1) { search[1] = search[1].replace(/[^\&]*/, ""); newString = search.join(""); } } // Append the setting on the end var ampersand = (hasQuestionMark) ? "&" : ""; newString += ampersand + setting + "=" + value; // Remove multiple ampersand newString = newString.replace(/&{2,}/g, "&"); return newString; }; // Return the indexes of records with specified value Array.prototype.listIndexOf = function(property, value) { var indexes = []; // If the value exists if (typeof(value) !== "undefined") { value = value.toString(); for (var i = 0; i<this.length; i++) { var str = (this[i][property]) ? this[i][property].toString() : ""; if (str === value) { indexes.push(i); } } } return indexes; }; // Return the records with specified value Array.prototype.listMatches = function(property, value){ var indexes = this.listIndexOf(property, value); var values = []; for (var i = 0; i<indexes.length; i++){ values.push(this[indexes[i]]); } return values; }; // Assert function function assert(condition, message) { var context = "Youtube Downloader - "; if (!condition) { message = message || "Assertion failed"; if (typeof Error !== "undefined") { throw new Error(context + message); } throw message; // Fallback } } // Adds useful prototyping functions for jQuery objects $.fn.extend({ toggleState: function(){ if ($(this).hasClass("disabled")){ $(this).removeClass("disabled"); } else { $(this).addClass("disabled"); } }, onState: function(){ if ($(this).hasClass("disabled")){ $(this).html(""); $(this).removeClass("disabled"); $(this).append($downloadIcon).append($("<span>", { html:"",//Download class:"midalign" })); } }, }); // src/classes/display.js // ================================================= // Generates the display, updates the display, all // things related to the interface can be found here function Display() { // The text colour of the size once loaded this.SIZE_LOADED = "red"; // The text colour of the size when waiting on audio size this.SIZE_WAITING = "green"; // Sprites // Download icon (with cloud) this.$downloadIcon = $("<img>", { class:"midalign downloadIcon", src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAMAAACsjQ8GAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAA81BMVEX///8AAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AgAAY1NEfAAAAT3RSTlMAACKO3/z96KM0ADHF/tpMGb3ZNHL6/fb29vb6orzQRj8/PI7n2MAJaf3a8qGXcQU+l5TT8lIbwuNNHLLjTRyy400csuNNHLLjSxuy4sj+3zONWQAAAAFiS0dEUONuTLwAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgBwMLDTTiSqfFAAAAuUlEQVQoz92S1Q7DMBRD3TEzddytg46ZmTn//zfrqklLpML7/GbnSLF0DcBktliJimx2h9MFwO3xEi35/EAgSHQUCiMS/dlYPMHzfDJFEWlkKJfN5QVBKBSpSESJcuUKOI5DlYok1PQB8m9AvdFstRWg0+31VYDBEJ/njzAaq30xmX4JzObqHRZLhcBqrVVys5UJ7PZMSfpY5HAETmc6kZhzE3K53u5MIDKDkfV4vhifNp6c4WiNZv8GO3e7aQ4nYEYAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA3LTAzVDExOjEzOjUyLTA0OjAwhxoNwQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0wNy0wM1QxMToxMzo1Mi0wNDowMPZHtX0AAAAASUVORK5CYII=" }); // Down select arrow (for dropdown) this.$downArrow = $("<img>", { class:"midalign downArrow", src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QAtQC1ALVZ+kfjAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AcFEAAUWZLqDQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAA2ElEQVRo3u2UQQ6EIAxFP16r5++tJjKbiYkRtUCrkPlvzeK9QgEIIYQQQv6XJCIrgDRrwKKqC4A8oXtW1bRNfrKbyL/B74UnidjkUZIdPGInjzPRUSNU9eC0nBwcbbFzSR53Ux7kJg7PxhwwQMSlPKxiL0XcyqNG6uEIkzxqhR6KMMujRSY4okoerSJBEdXy6JFwjmiSR6+AU0SzPDwmKCL5LXl4PYHGiG55eC5iZYSLPLx/EmOEm7x7gCHCVT4k4CLCXT4soBARIh+OiKwi8gEhhBBCCAnhCzz2dWSNsOWhAAAAAElFTkSuQmCC" }); } Display.prototype = { update: function() { var _this = this; var sizes = qualities.sizes; // Main window var $downloadBtnInfo = $("#downloadBtnInfo"); sizes.getSize($downloadBtnInfo.find("span:eq(0)"), function($span, size) { _this.updateDisplay($span, size, true); }); // Drop down list $lis = $("#options").find("li"); for (var i = 0; i<$lis.length; i++) { sizes.getSize($lis.eq(i), function($li, size) { _this.updateDisplay($li, size); }); } }, // Initialises the display initOptions: function(qualities) { // Fallback for setting to top value var $topEl = false; // Reset this.updateInfo(false); $options = $("<ul>", { id:"options", class:"unselectable", }); // Initialise items in the drop-down list for (i = 0; i<qualities.items.length; i++) { var quality = qualities.items[i]; var display = (quality.hidden) ? "none" : "inherit"; $li = $("<li>", { html : quality.label, itag : quality.itag, style : "display:"+display, }); // Tags - get them and then append them to the $li $tags = this.getTags(quality); for (var j = 0; j<$tags.length; j++) { $li.append($tags[j]); } // Add the $li to the $options $options.append($li); // Add the first as a fallback if (!$topEl) $topEl = $li; // If it matches the set quality, assign it to the info box var sameQuality = (quality.itag === Number(localStorage.selQuality)); var visible = !quality.hidden; if (sameQuality && visible) { $topEl = $li; } } // Update the top panel with the top element this.updateInfo($topEl); // Prepend options if necessary if ($("#options").length === 0 && $options) { $("#downloadBtnCont").append($options); } }, // Updates the display LIST-ITEM updateDisplay: function($li, size, forceNeutralFloat) { var item = qualities.getFromItag($li.attr("itag")); var sizes = qualities.sizes; var _this = this; var color = (item.dash) ? this.SIZE_WAITING : this.SIZE_LOADED; // If the SIZE tag doesn't already exist, add it var extraClass = (forceNeutralFloat) ? " floatNormal" : ""; $spanSize = $li.find("span.size"); // Add it if it doesn't exist if ($spanSize.length === 0) { $spanSize = $("<span>", { style:"color:"+color, class:"size ignoreMouse"+extraClass }); $li.append($spanSize); } $spanSize.html(sizes.formatSize(size)); // If it is of the DASH format if (item.dash) { if (globalProperties.audioSize) { // Let the size be the sum of the size and the audio size size = parseInt(size) + parseInt(globalProperties.audioSize); $li.find("span.size").html(sizes.formatSize(size)); $li.find("span.size").css("color", this.SIZE_LOADED); } else { // Try again in 2 seconds setTimeout(function() { _this.updateDisplay($li, size); }, 2000); } } }, // Returns a jquery element of the download button with a certain text updateDownloadButton: function (text, disabled) { // Create the download button container var $container = this.checkContainer(); // Determine if it is of the disabled class var disabledText = (disabled) ? " disabled" : ""; // Create the button if it doesn't exist var $button = $container.find("#downloadBtn"); if ($button.length === 0) { $button = $("<button>", { id:"downloadBtn", title:"Download", }); $button.append(this.$downloadIcon); $button.append($("<span>", { class:"midalign" })); // Append it to the container $container.append($button); } // Update the properties $button.attr("class", disabledText); $button.find("span").html(text); }, // Update the downloadBtnInfo (top, non drop-down) updateInfo: function ($li) { var $downloadBtnInfo = $("#downloadBtnInfo"); // Add it if it doesn't exist if ($downloadBtnInfo.length === 0) { $downloadBtnInfo = $("<span>", { id:"downloadBtnInfo" }).append(this.$downArrow); // Find the container var $container = this.checkContainer(); // Append it to the container $container.append($downloadBtnInfo); } // If an element was passed, prepend it if ($li) { var item = qualities.getFromItag($li.attr("itag")); $span = $downloadBtnInfo.find("span:eq(0)"); if ($span.length === 0) { $span = $("<span>"); // Prepend the new element $downloadBtnInfo.prepend($span); } // Set the span ATTRIBUTES $span.attr({ "itag": item.itag }); var $child = $span.find("span.text"); if ($child.length === 0) { $child = $("<span>", { class:"text" }); $span.append($child); } // Set the span HTML $child.html(item.label); } }, // Fetch the container if it exists, otherwise make it checkContainer: function() { var $container = $("#downloadBtnCont"); if ($container.length === 0) { $container = $("<span>", { id:"downloadBtnCont", class:"unselectable" }); $("#watch7-subscription-container").append($container); } return $container; }, getTags: function(quality) { $tags = []; $tags.push($("<span>", { class:"tag ignoreMouse", html:quality.type })); var dash = quality.dash; if (dash && dash !== "false") { $tags.push($("<span>", { class:"tag ignoreMouse", html:"DASH" })); } var muted = quality.muted; if (muted && muted !== "false") { $tags.push($("<span>", { class:"tag ignoreMouse", html:"MUTED" })); } return $tags; } }; // src/classes/qualities.js // ================================================= // This class handles the qualities that can be downloaded // This class also manages the the display of qualities (both // the top quality and the list of qualities) function Qualities() { this.items = []; this.sizes = new GetSizes(); this.itags = { 5: { type:"flv" }, 17: { resolution:144, type:"3gpp" }, 18: { resolution:360, type:"mp4" }, 22: { resolution:720, type:"mp4" }, 36: { resolution:180, type:"3gpp" }, 43: { resolution:360, type:"webm" }, 133: { resolution:240, type:"mp4", dash:true }, 134: { resolution:360, type:"mp4", muted:true }, 135: { resolution:480, type:"mp4", dash:true }, 136: { resolution:720, type:"mp4", muted:true }, 137: { resolution:1080, type:"mp4", dash:true }, 140: { audio:true, type:"mp4" }, 160: { resolution:144, type:"mp4", muted:true }, 171: { audio:true, type:"webm", }, 242: { resolution:240, type:"webm", muted:true }, 243: { resolution:360, type:"webm", muted:true }, 244: { resolution:480, type:"webm", dash:true }, 247: { resolution:720, type:"webm", muted:true }, 248: { resolution:1080, type:"webm", muted:true }, 249: { audio:true, type:"webm", }, 250: { audio:true, type:"webm", }, 251: { audio:true, type:"webm", }, 264: { resolution:1440, type:"mp4", dash:true }, 266: { resolution:2160, type:"mp4", dash:true }, 271: { resolution:1440, type:"webm", dash:true }, 278: { resolution:140, type:"webm", muted:true }, 298: { resolution:720, fps:60, type:"mp4", dash:true }, 299: { resolution:1080, fps:60, type:"mp4", dash:true }, 302: { resolution:720, fps:60, type:"webm", muted:true }, 303: { resolution:1080, fps:60, type:"webm", muted:true }, 313: { resolution:2160, type:"webm", dash:true }, }; } Qualities.prototype = { reset: function() { this.items = []; }, initialise: function(callback) { this.reset(); var _this = this; this.getPotentialDash(function(potential) { var split = potential.split(","); // Iterate through each option for (var i = 0; i<split.length; i++) { // Get relevant properties var sect = split[i]; var url = decodeURIComponent(sect.getSetting("url")); var s = sect.getSetting("s"); var type = decodeURIComponent(url.getSetting("mime")); var clen = url.getSetting("clen") || sect.getSetting("clen"); var itag = parseInt(url.getSetting("itag"), 10); var size = false; // Decode the url url = signature.decryptSignature(url, s); // Get data from the ITAG identifier var tag = _this.itags[itag] || {}; // Get the value from the tag var value = _this.getValue(tag); // Get the label from the tag var label = sect.getSetting("quality_label") || _this.getLabel(tag); // If we have content-length, we can find size IMMEDIATELY if (clen) { size = parseInt(clen, 10); } // Get the type from the tag assert(type.split("/").length > 1, "Incorrect type: "+type); var newType = type.split("/")[1].split(",")[0]; if (newType !== tag.type) { console.log("Error with "+itag+", "+newType+"!="+tag.type); console.log(decodeURIComponent(url)); } // Fix the types if (newType === "mp4" && tag.audio) { newType = "m4a"; } if (newType === "mp4" && tag.dash) { newType = "m4v"; } // Append to qualities (if it shouldn't be ignored) var item = { itag : itag, url : url, size : size, type : newType, dash : tag.dash || false, muted: tag.muted || false, label: label, audio: tag.url || false, value: value }; if (_this.checkValid(item)) { _this.items.push(item); // Check if it should be added but HIDDEN } else { if (newType === "m4a") { item.hidden = true; _this.items.push(item); } } _this.checkMP3(item); // If it is the audio url - find the size and update if (newType === "m4a" && tag.audio) { var $li = $("<li>", { url : url, itag : itag, }); _this.sizes.getSize($li, function($li, size) { globalProperties.audioSize = size; }); } } callback(); }); }, getLabel: function(tag) { var label = false; tag = tag || {}; if (tag.resolution) { label = tag.resolution.toString()+"p"; if (tag.fps) { label += tag.fps.toString(); } } else if (tag.audio) { label = "Audio"; } return label; }, getValue: function(tag) { // Base value is the resolution OR 0 var value = tag.resolution || 0; // Multiply if it has an fps tag (high frame rate) if (tag.fps >= 30) { value += 10; } // Multiply if it is mp4 if (tag.type === "mp4" || tag.type === "m4v") { value *= 100; } // Make it negative if it's audio if (tag.audio) { value -= 5; value *= -1; } if (tag.type === "mp3") { value -= 1; } return value; }, sortItems: function() { var _this = this; this.items.sort(_this.sortDescending); }, sortDescending: function(a, b) { if (isNaN(a.value)) a.value = 0; if (isNaN(b.value)) b.value = 0; return Number(b.value) - Number(a.value); }, // Check if the item should be ignored or not checkValid: function(item) { var valid = true; // If it is muted and we are ignoring muted if (settings.get("ignoreMuted") && item.muted) { valid = false; } // If it matches a blacklisted type if (settings.get("ignoreTypes").indexOf(item.type) !== -1) { valid = false; } // If it matches a blacklisted value if (settings.get("ignoreVals").indexOf(item.value) !== -1) { valid = false; } return valid; }, // Get potential inclusive of dash formats from // 2010-2011 getPotentialDash: function(callback) { // Get native potential, within ytplayer var potential = this.getPotential(); var dashmpd = ytplayer.config.args.dashmpd; // We must make a get request, and find the // additional qualities within the file if (dashmpd !== undefined) { console.log("Making dashmpd request..."); var _this = this; Ajax.request({ method:"GET", url:dashmpd, success: function(xhr, text, jqXHR) { var text = (typeof(xhr) === "string") ? jqXHR.responseText : xhr.responseText; // Add the potential from BaseURL tags var add = []; var addPotential = text.split(/\<BaseURL\>([^\<]*)\<\/BaseURL\>/); for (var i = 0; i<Math.floor(addPotential.length/2); i++) { var url = addPotential[i*2 + 1]; add.push(_this.decodeURL(url)); } assert(add.length > 0, "No videos found in dashmpd!"); potential = potential+",url="+add.join(",url="); callback(potential); } }); } else { callback(potential); } }, // Process the forward slashes / and make them into // ampersands (&) and equals (=) decodeURL: function(url) { var split = url.split("videoplayback/"); var host = split[0]; var str = split[1]; str = str.replace(/\/([^\/]*(?:\/|$))/g, "=$1"); str = str.replace(/\//g, "&"); url = host+"videoplayback?"+str; // Encode it return encodeURIComponent(url); }, // Get potential list getPotential: function() { assert(ytplayer !== undefined, "Ytplayer is undefined!"); // Find the valid links and place them in an array var args = ytplayer.config.args; var arr = []; if (args.adaptive_fmts !== undefined && args.adaptive_fmts !== "") { arr.push(args.adaptive_fmts); } if (args.url_encoded_fmt_stream_map !== undefined && args.url_encoded_fmt_stream_map !== "") { arr.push(args.url_encoded_fmt_stream_map); } // Assert that there is a potential var potential = arr.join(","); potential = potential.replace(/([0-9])s=/g, ",s="); // Make potential false if neither found if (arr.length === 0) potential = false; return potential; }, checkPotential: function(potential) { var valid = false; if (potential) { var lengths = this.getPotentialLengths(potential); valid = (lengths.url >= lengths.url && lengths.sig > 1); // Trace out why it isn't valid if (!valid) { var split = potential.split(",") || ""; for (var i = 0; i<split.length; i++) { var splitLengths = this.getPotentialLengths(split[i]); if (splitLengths.url !== 1 || splitLengths.sig !== 1) { console.log("checkPotential"); console.log(split[i]); console.log(splitLengths.url, splitLengths.sig); } } } } // Return if it is valid return valid; }, // Get url and sig lengths from potential list getPotentialLengths: function(potential) { return { url: potential.split("url=").length - 1, sig: decodeURIComponent(potential).split(/(?:(?:&|,|\?|^)s|signature|sig)=/).length - 1 }; }, // Check if MP3 should be added checkMP3: function(item) { if (item.type === "m4a") { // Copy over the properties into // a new object var newItem = {}; for (var key in item) { newItem[key] = item[key]; } newItem.type = "mp3"; newItem.itag += 1; this.items.push(newItem); } }, // Get from ITAG getFromItag: function(itag) { var matches = qualities.items.listMatches("itag", Number(itag)); if (matches.length !== 1) { console.log("ERROR: Found "+matches.length+" with itag: "+itag); } var item = matches[0] || {}; // Return the item obtained from the itag return item; } }; // src/classes/qualities/getSizes.js // ================================================= // Obtains the sizes of each of the urls, adding // the "size" attribute to each li element, and setting // the size in kb/mb/gb etc on each element function GetSizes() { // Number of decimal places to represent the // size as this.SIZE_DP = 1; } GetSizes.prototype = { getSize: function($li, callback) { var item = qualities.getFromItag($li.attr("itag")); var url = item.url; // Attempt to obtain the size from the qualities values var size = item.size; if (size) { callback($li, size); } else { // We must make a cross-domain request to determine the size from the return headers... Ajax.request({ method:"HEAD", url:url, success:function(xhr, text, jqXHR) { var size = Number(Ajax.getResponseHeader(xhr, text, jqXHR, "Content-Length")); // Revert to old header name if (size === 0) { size = Number(Ajax.getResponseHeader(xhr, text, jqXHR, "Content-length")); } item.size = size; callback($li, size); } }); } }, // Takes the input in bytes, and returns a formatted string formatSize: function(size) { size = parseInt(size, 10); var sizes = { GB:Math.pow(1024,3), MB:Math.pow(1024,2), KB:Math.pow(1024,1), }; // Default of 0MB var returnSize = "0MB"; for (var sizeFormat in sizes){ if (sizes.hasOwnProperty(sizeFormat)) { var minSize = sizes[sizeFormat]; if (size > minSize) { returnSize = (size/minSize).toFixed(this.SIZE_DP) + sizeFormat; break; } } } // Return the string of return size return returnSize; } }; // src/classes/signature.js // ================================================= // Gets the signature code from YouTube in order // to be able to correctly decrypt direct urls // USES: ytplayer.config.assets.js function Signature() { // constructor } Signature.prototype = { fetchSignatureScript: function(callback) { var scriptURL = this.getScriptURL(ytplayer.config.assets.js); // If it's only positive, it's wrong if (!/,0,|^0,|,0$|\-/.test(settings.get("signatureCode"))) { settings.set("signatureCode", null); } var _this = this; Ajax.request({ method:"GET", url:scriptURL, success:function(xhr, text, jqXHR) { var text = (typeof(xhr) === "string") ? jqXHR.responseText : xhr.responseText; _this.findSignatureCode(text); callback(); }, error:function(xhr, text, jqXHR) { console.log("Error getting signature script"); } }); }, getScriptURL: function(scriptURL) { var split = scriptURL.split("//"); if (split[0] === "") { split.shift(); scriptURL = window.location.href.split(":")[0] + "://" + split.join("//"); } return scriptURL; }, isInteger: function(n) { return (typeof n === 'number' && n%1 === 0); }, findSignatureCode: function(sourceCode) { // Signature function name var sigCodes = [ this.regMatch(sourceCode, /\.set\s*\("signature"\s*,\s*([a-zA-Z0-9_$][\w$]*)\(/), this.regMatch(sourceCode, /\.sig\s*\|\|\s*([a-zA-Z0-9_$][\w$]*)\(/), this.regMatch(sourceCode, /\.signature\s*=\s*([a-zA-Z_$][\w$]*)\([a-zA-Z_$][\w$]*\)/) ]; var sigFuncName = this.getFirstValid(sigCodes); var binary = []; binary.push(sourceCode); //SaveToDisk(URL.createObjectURL(new Blob(binary, {type: "application/js"})), {title:"hi", type:".js"}); assert(sigFuncName !== null, "Signature function name not found!"); // Regcode (1,2) - used for functionCode var regCodes = [ this.regMatch(sourceCode, sigFuncName + '\\s*=\\s*function' + '\\s*\\([\\w$]*\\)\\s*{[\\w$]*=[\\w$]*\\.split\\(""\\);\n*(.+);return [\\w$]*\\.join'), this.regMatch(sourceCode, 'function \\s*' + sigFuncName + '\\s*\\([\\w$]*\\)\\s*{[\\w$]*=[\\w$]*\\.split\\(""\\);\n*(.+);return [\\w$]*\\.join') ]; var funcCode = this.getFirstValid(regCodes); // Slice function name var sliceFuncName = this.regMatch(sourceCode, /([\w$]*)\s*:\s*function\s*\(\s*[\w$]*\s*,\s*[\w$]*\s*\)\s*{\s*(?:return\s*)?[\w$]*\.(?:slice|splice)\(.+\)\s*}/); // Reverse function name var reverseFuncName = this.regMatch(sourceCode, /([\w$]*)\s*:\s*function\s*\(\s*[\w$]*\s*\)\s*{\s*(?:return\s*)?[\w$]*\.reverse\s*\(\s*\)\s*}/); // Possible methods var methods = { slice: '\\.(?:'+'slice'+(sliceFuncName?'|'+sliceFuncName:'')+ ')\\s*\\(\\s*(?:[a-zA-Z_$][\\w$]*\\s*,)?\\s*([0-9]+)\\s*\\)', reverse: '\\.(?:'+'reverse'+(reverseFuncName?'|'+reverseFuncName:'')+ ')\\s*\\([^\\)]*\\)', swap: '[\\w$]+\\s*\\(\\s*[\\w$]+\\s*,\\s*([0-9]+)\\s*\\)', inline: '[\\w$]+\\[0\\]\\s*=\\s*[\\w$]+\\[([0-9]+)\\s*%\\s*[\\w$]+\\.length\\]' }; var decodeArray = []; var codeLines = funcCode.split(';'); for (var i = 0; i<codeLines.length; i++) { var codeLine = codeLines[i].trim(); if (codeLine.length > 0) { var arrSlice = codeLine.match(methods.slice); var arrReverse = codeLine.match(methods.reverse); // Use slice method if (arrSlice && arrSlice.length >= 2) { var slice = parseInt(arrSlice[1], 10); assert(this.isInteger(slice), "Not integer"); decodeArray.push(-slice); // Reverse } else if (arrReverse && arrReverse.length >= 1) { decodeArray.push(0); // Inline swap } else if (codeLine.indexOf('[0]') >= 0) { // inline swap var nextLine = codeLines[i+1].trim(); var hasLength = (nextLine.indexOf(".length") >= 0); var hasZero = (nextLine.indexOf("[0]") >= 0); if (nextLine && hasLength && hasZero) { var inline = this.regMatch(nextLine, methods.inline); inline = parseInt(inline, 10); decodeArray.push(inline); i += 2; } // Swap } else if (codeLine.indexOf(',') >= 0) { var swap = this.regMatch(codeLine, methods.swap); swap = parseInt(swap, 10); assert(this.isInteger(swap) && swap > 0); decodeArray.push(swap); } } } // Make sure it is a valid signature assert(this.isValidSignatureCode(decodeArray)); globalProperties.signatureCode = decodeArray; }, isValidSignatureCode: function(arr) { var valid = false; var length = arr.length; if (length > 1) { valid = true; // Ensure that every value is an INTEGER for (var i = 0; i<length; i++) { if (!this.isInteger(parseInt(arr[i],10))) { valid = false; } } } return valid; }, regMatch: function(string, regex) { if (typeof(regex) === "string") { regex = new RegExp(regex); } var result = regex.exec(string); if (result) { result = result[1]; } return result; }, getFirstValid: function(arr) { var val = null; for (var i = 0; i<arr.length; i++) { if (arr[i]) { val = arr[i]; break; } } return val; }, decryptSignature: function(url, s) { url = decodeURIComponent(url); var sig = url.getSetting("signature") || url.getSetting("sig"); // Decryption is only required if signature is non-existant AND // there is an encrypted property (s) if (!sig) { assert(s !== "false" && s, "S attribute not found!"); sig = this.decodeSignature(s, globalProperties.signatureCode); url = url.setSetting("signature", sig); } url = url.setSetting("ratebypass", "yes"); assert(url.getSetting("signature"), "URL does not have signature!"); return url; }, decodeSignature: function(s) { var arr = globalProperties.signatureCode; var sigA = s.split(""); for (var i = 0; i<arr.length; i++) { var act = arr[i]; // Determine what sigA should be, based // on polarity of act if (act > 0) { sigA = this.swap(sigA, act); } else if (act === 0) { sigA = sigA.reverse(); } else { sigA = sigA.slice(-act); } } var result = sigA.join(""); return result; }, swap: function(a, b) { var c = a[0]; a[0] = a[b%a.length]; a[b] = c; return a; } }; // src/classes/unique/ajaxclass.js // ================================================= // Isolates the async functions from the modules function AjaxClass() { } AjaxClass.prototype = { request: function(params) { // Setup the request var success = params.success; var error = params.error; params.onerror = function(xhr) { error(xhr); }; params.onload = function(xhr) { if (xhr.readyState === 4 && xhr.status === 200) { success(xhr); } else { console.log(xhr); if (typeof error === "function") error(xhr); } }; // Call the request GM_xmlhttpRequest(params); }, getResponseHeader: function(xhr, text, jqXHR, type) { var value = false; if (typeof xhr.getResponseHeader === "function") { value = xhr.getResponseHeader(type); } else if (xhr.responseHeaders) { var regex = new RegExp(type.split("-")[1]+": (.*)"); var match = regex.exec(xhr.responseHeaders); if (match){ value = match[1]; } } // Return the value return value; } }; // src/classes/unique/css.js // ================================================= // This function adds styling to the page by // injecting CSS into the document (function() { var css = { ".disabled": { "cursor":"default!important", }, ".midalign": { "vertical-align":"middle!important", }, ".unselectable": { "-webkit-user-select":"none", "-moz-user-select":"none", "-ms-user-select":"none", }, "#downloadBtnCont": { "margin-left":"1em", "position":"relative", "display":"inline-block,", }, "#downloadBtn": { "padding":"0 8px 0 5.5px", "height":"24px", "background-color":"green", "color":"white", "font-weight":"normal", "box-shadow":"0 1px 0 rgba(0,0,0,0.05)", "vertical-align":"middle", "font-size":"11px", "border":"solid 1px transparent", "border-radius":"2px 0 0 2px", "cursor":"pointer", "font":"11px Roboto,arial,sans-serif", "-webkit-user-select":"none", "-moz-user-select":"none", "-ms-user-select":"none", "user-select":"none", }, "#downloadBtn.disabled": { "background-color":"gray!important" }, "#downloadBtn:hover": { "background-color":"darkgreen" }, "#downloadBtn span": { "font-size":"12px" }, "#downloadBtn img": { "height":"12px" }, "#downloadBtnInfo": { "cursor":"default", "height":"22px", "line-height":"24px", "padding":"0 6px", "color":"#737373", "font-size":"11px", "text-align":"center", "display":"inline-block", "margin-left":"-2px", "border":"1px solid #ccc", "background-color":"#fafafa", "vertical-align":"middle", "border-radius":"0 2px 2px 0", }, "span.text": { "margin-right":"0.2em", }, "ul#options": { "position":"absolute!important", "background-color":"white", "z-index":"500", "width":"200px", "padding":"0 5px", "cursor":"default", "box-shadow":"0 1px 2px rgba(0,0,0,0.5)", "left":"0", "display":"none", }, "ul#options li": { "line-height":"2em", "padding":" 0 5px", "margin":"0 -5px", }, "ul#options li:hover": { "background-color":"orange", }, "span.size": { "float":"right", }, "span.tag": { "margin":"0.2em", "padding":"0.2em", "background-color":"lightblue", "color":"grey", }, ".floatNormal": { "float":"inherit!important", }, ".ignoreMouse": { "pointer-events":"none", }, "#watch7-user-header": { "overflow":"visible!important", }, "#watch7-content": { "overflow":"visible!important", "z-index":"500!important", }, // Fix the drag-drop events causing ghost image "img": { "pointer-events": "none" }, /* Download sprites */ ".downloadIcon": { "margin-right":"0px" }, ".downArrow": { "margin-bottom":"-13px", "margin-left":"6px", "width":"13px", "transform":"translateY(-50%)" }, "#downloadCancelCont": { "cursor":"default", //"width":"40px", "height":"24px", "overflow":"hidden", "line-height":"24px", "padding":"0 0px", "color":"#737373", "font-size":"11px", "text-align":"center", "display":"inline-block", "margin":"0px", "border":"0px solid #ccc", "background-color":"#fafafa", "vertical-align":"middle", "border-radius":"0 0px 0px 0", "position":"relative", }, ".downloadCancelBtn": { "cursor":"pointer", "width":downloadCancelBtn_width+"px", "height":"24px", "overflow":"hidden", "line-height":"24px", "padding":"0 0px", "color":"#e62117", "font-size":"11px", "text-align":"center", "display":"inline-block", "margin":"0px", "border":"1px solid #ccc", "background-color":"transparent", "vertical-align":"top", "border-radius":"0 2px 2px 0", "position":"relative", }, ".downloadCancelBtnProgress": { "cursor":"default", "width":"0px", "height":"24px", "overflow":"hidden", "line-height":"24px", "padding":"0 0px", "color":"#737373", "font-size":"11px", "text-align":"center", "display":"inline-block", "margin":"0px", "border":"0px solid #ccc", "background-color":"#008000", "vertical-align":"top", "border-radius":"0 0px 0px 0", "position":"absolute", "z-index":"101", }, ".downloadCancelBtnLabel": { "cursor":"pointer", "width":downloadCancelBtn_width+"px", "height":"24px", "overflow":"hidden", "line-height":"24px", "padding":"0 0px", "color":"#e62117", "font-size":"11px", "text-align":"center", "display":"inline-block", "margin":"0px", "border":"0px solid #ccc", "background-color":"transparent", "vertical-align":"top", "border-radius":"0 0px 0px 0", "position":"relative", "z-index":"102", }, }; // Append the CSS to the document var node = document.createElement("style"); var html = ""; for (var key in css) { var props = css[key]; html += key + " {\n"; for (var prop in props) { html += "\t" + prop + ":" + props[prop] + ";\n"; } html += "}\n"; } node.innerHTML = html; document.body.appendChild(node); })(); // src/classes/unique/download.js // ================================================= // Functions that are used to download the video and audio // files function Download() { // Construct } Download.prototype = { // Download the file getVid: function($span, title) { var item = qualities.getFromItag($span.attr("itag")); var type = item.type; var dash = item.dash; title = title || this.getTitle(item.label); var name = title; var url = item.url.setSetting("title", encodeURIComponent(title)); // MP3 change if (type === "mp3") { //name = "MP3 - "+name; type = "m4a"; } // Save to disk this.saveToDisk(url, name+"."+type); // If it requires audio, download it if (dash) { this.handleAudio(name); } // Re-enable the button after 0.5 seconds setTimeout(function() { display.updateDownloadButton("");//Download }, 500); }, getTitle: function(label) { label = (label) ? label : ""; var str = $("title").html().split(" - YouTube")[0]; // Add the label if required if (settings.get("label") && label.toString() !== "Audio") { str += " " + label.toString(); } str = str.replace(/!|\+|\.|\:|\?|\||\\|\//g, "").replace(/\"/g, "'"); return str; }, // Download audio if required handleAudio: function(name) { // Download the audio file this.getVid($("#options").find("li[itag=140]:eq(0)"), "" + name);//AUDIO - // Download the script /* var os = GetOs(); var text = MakeScript(settings.title, type, "m4a", "mp4", os); settings.type = os.scriptType; if (os.os === 'win'){ SaveToDisk(URL.createObjectURL(text), settings); } else { SaveToDisk("https://github.com/Domination9987/YouTube-Downloader/raw/master/muxer/Muxer.zip", settings); }*/ }, getOs: function() { var os = (navigator.appVersion.indexOf("Win") !== -1) ? "win" : "mac"; var scriptType = (os === "win") ? "bat" : "command"; return {os:os, scriptType:scriptType}; }, saveToDisk: function(url, name) { console.log("Trying to download:", name + " > " + url); if (typeof(GM_download) === "undefined") { //this.fallbackSave(url, name); GM_download2(url, name); //alert("Please enable GM_Download if you have videoplayback issues"); } else { //GM_download(url, name); var GM_download_details = { url: url, name: name, //saveAs: true,//no funciona onload: function(res) { console.log(name + " > GM_download > onload:", "descarga completa"); }, onerror: function(download) { if(download.error == "not_whitelisted"){ alert('Modificar la configuracion de Tampermonkey:'+'\n'+ 'En: "Downloads BETA > Download Mode" escoger "Browser API"'+'\n'+ 'En: "Downloads BETA > Whitelisted File Extensions" agregar /\.(3gpp|m4a|m4v)$/'); }; console.log(name + " > GM_download > onerror.error:", download.error); console.log(name + " > GM_download > onerror.details:", download.details); console.log(name + " > GM_download > onerror.details.current:", download.details.current); }, }; GM_download(GM_download_details); } }, // Saves using the old method // NOTE: Does not work for audio or DASH formats // will download as "videoplayback" fallbackSave: function(url, name) { var save = document.createElement('a'); save.target = "_blank"; save.download = name;//true console.log(decodeURIComponent(url)); save.href = url; (document.body || document.documentElement).appendChild(save); save.onclick = function() { (document.body || document.documentElement).removeChild(save); }; save.click(); } }; // src/classes/unique/settings.js // ================================================= // This class handles the settings // Uses localStorage to remember the settings function Settings(defaultSettings) { // Fetch the settings from localStorage this.settings = {}; // Set the default settings for (var key in defaultSettings) { if (defaultSettings.hasOwnProperty(key)) { this.settings[key] = defaultSettings[key]; } } } Settings.prototype = { // Get the value of a property get: function(key) { var value = this.settings[key]; if (Number(value) === value) { value = Number(value); } return value; }, // Set a new property set: function(key, value) { this.settings[key] = value; } }; // src/classes/unique/unsafe.js // ================================================= function Unsafe() { this.id = 0; } Unsafe.prototype = { getVariable: function(name, callback) { var script = "(function() {"+ "setTimeout(function(){"+ "var event = document.createEvent(\"CustomEvent\");"+ "var val = (typeof "+name+" !== 'undefined') ? "+name+" : false;"+ "event.initCustomEvent(\""+name+"\", true, true, {\"passback\":JSON.stringify(val)});"+ "window.dispatchEvent(event);"+ "},100);"+ "})()"; // Inject the script this.injectScript(script, name, function(obj) { var passback = obj.detail.passback || {}; callback(JSON.parse(passback)); }); }, injectScript: function(script, name, callback) { //Listen for the script return var myFunc = function(e) { window.removeEventListener(name, myFunc); callback(e); }; window.addEventListener(name, myFunc); this.id++; //Inject the script var s = document.createElement("script"); s.innerText = script; (document.head||document.documentElement).appendChild(s); s.parentNode.removeChild(s); } }; // src/main.js // ================================================= // Variables // Selected quality localStorage.selQuality = localStorage.selQuality || 298; // Default settings var defaultSettings = { // Ignore muted ignoreMuted:true, // Types that are ignored ignoreTypes:["webm"], // Values that are ignored ignoreVals:[], // Have quality label on download label:true, }; // Volatile properties var globalProperties = { // Size of audio audioSize:false, // Obtained signature pattern signatureCode:false }; // Objects var Ajax = new AjaxClass(); var settings = new Settings(defaultSettings); var signature = new Signature(); var display = new Display(); var qualities = new Qualities(); var download = new Download(); var unsafe = new Unsafe(); var ytplayer = {}; // Run the script ONLY if it's on the top if (window.top === window) { AddEvents(); Program(); } // This function is run on every new page load.... function Program() { // Make sure it is of the correct URL var url = window.location.href; if (!url.match(/watch|embed/)) return; unsafe.getVariable("ytplayer", function(ytp) { // If the old thing is still there, wait a while ytplayer = ytp || {}; if ($("#downloadBtn").length > 0 || !ytplayer.config) { setTimeout(Program, 2000); return; } // Verify that the potential is LOADED, by comparing the // number of SIGNATURES to the number of URLs var potential = qualities.getPotential(); if (!qualities.checkPotential(potential)) { setTimeout(Program, 2000); return; } // Get the signature (required for decrypting) signature.fetchSignatureScript(function() { // Reset the audio size globalProperties.audioSize = false; // Initialise the available qualities qualities.initialise(function() { qualities.sortItems(); // Update the download button, set it to be ENABLED // with text "Download" display.updateDownloadButton("");//Download // Initialise the options & add it to the frame display.initOptions(qualities, $("#downloadBtnInfo")); // Update the display (fetch sizes as well) display.update(); }); }); }); } // Adds events to the window function AddEvents() { // Call the function on page change window.lastURL = window.location.href; setInterval(function() { var newURL = window.location.href; if (newURL !== window.lastURL) { window.lastURL = newURL; $(window).ready(function() { Program(); }); } }, 200); // On download button click $(document).on("click", "#downloadBtn", function() { // Ensure that the button is ENABLED if (!$(this).hasClass("disabled")) { GM_download2_Init(); var $span = $("#downloadBtnInfo span:eq(0)"); $(this).toggleState(); download.getVid($span); } }); // Toggle options on info click $(document).on("click", "#downloadBtnInfo", function() { $("#options").toggle(); }); // On individual option click $(document).on("click", "#options li", function() { // Close the options $("#options").hide(); // Update the relevant settings localStorage.selQuality = Number($(this).attr("itag")); // Update the info display.updateInfo($(this)); // Update the display display.update(); }); // Hide options on document click $(document).click(function(e) { // If it matches the info or is a child of the top info, ignore $el = $(e.target); $parent = $(e.target).parent(); var str = $el.attr("id") + $parent.attr("id") + $parent.parent().attr("id"); str = str || ""; if (str.split("downloadBtnInfo").length > 1) { return; } // Hide the options $("#options").hide(); }); };
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址