// ==UserScript== // @name ytdl link // @namespace https://blog.maple3142.net/ // @version 0.3.1 // @description Show youtube download link on the left side without external service. // @author maple3142 // @require https://code.jquery.com/jquery-3.2.1.slim.min.js // @match https://www.youtube.com/* // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function() { 'use strict' const xhrget=url=>new Promise((res,rej)=>{ const xhr = new XMLHttpRequest() xhr.open('HEAD', url); xhr.onreadystatechange = ()=>{ if (xhr.readyState === xhr.DONE) { res(xhr.responseText) } }; xhr.onerror=rej xhr.send() }) // type "__YTDL_LINK_DECSIG.toString().split('\n').join('')" in console to get fallback const fallback = function anonymous(a) {var gL={pk:function(a){a.reverse()},k9:function(a,b){a.splice(0,b)},TA:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}};;a=a.split("");gL.k9(a,3);gL.TA(a,44);gL.k9(a,2);gL.TA(a,34);return a.join("")} const getdecsig= () => { let path='/yts/jsbin/player-vfl4qvcOS/zh_TW/base.js' if(ytplayer.config && ytplayer.config.assets.js){ path=ytplayer.config.assets.js } return xhrget('https://www.youtube.com' + path) .then(data => { const fnname = /\"signature\"\),.+?\.set\(.+?,(.+?)\(/.exec(data)[1] const [_, argname, fnbody] = new RegExp(fnname + '=function\\((.+?)\\){(.+?)}').exec(data) //console.log(fnbody) const helpername = /;(.+?)\..+?\(/.exec(fnbody)[1] //console.log(helpername) const helper = new RegExp('var ' + helpername + '={[\\s\\S]+?};').exec(data)[0] //console.log(helper) return new Function([argname], helper + ';' + fnbody) }).catch(e => fallback) .then(fn=>unsafeWindow.__YTDL_LINK_DECSIG=fn) } const parseQuery = s => Object.assign( ...s .split('&') .map(x => x.split('=')) .map(p => ({ [p[0]]: decodeURIComponent(p[1]) })) ) const getVideo = id =>fetch(`https://www.youtube.com/get_video_info?video_id=${id}`) .then(r=>r.text()) .then(async data => { const obj = parseQuery(data) if (obj.status === 'fail') { throw obj } const decsig = await getdecsig(id) let stream = [] if(obj.url_encoded_fmt_stream_map){ stream=obj.url_encoded_fmt_stream_map.split(',').map(parseQuery) if (stream[0].sp && stream[0].sp.includes('signature')) { stream = stream .map(x => ({ ...x, s: decsig(x.s) })) .map(x => ({ ...x, url: x.url + `&signature=${x.s}` })) } } let adaptive = [] if (obj.adaptive_fmts) { adaptive = obj.adaptive_fmts.split(',').map(parseQuery) if (adaptive[0].sp && adaptive[0].sp.includes('signature')) { adaptive = adaptive .map(x => ({ ...x, s: decsig(x.s) })) .map(x => ({ ...x, url: x.url + `&signature=${x.s}` })) } } return { stream, adaptive } }) const $box=$('