const querystring = require('querystring'); const Cache = require('./cache'); const utils = require('./utils'); const vm = require('vm'); // A shared cache to keep track of html5player js functions. exports.cache = new Cache(); /** * Extract signature deciphering and n parameter transform functions from html5player file. * * @param {string} html5playerfile * @param {Object} options * @returns {Promise>} */ exports.getFunctions = (html5playerfile, options) => exports.cache.getOrSet(html5playerfile, async() => { const body = await utils.exposedMiniget(html5playerfile, options).text(); const functions = exports.extractFunctions(body); if (!functions || !functions.length) { throw Error('Could not extract functions'); } exports.cache.set(html5playerfile, functions); return functions; }); /** * Extracts the actions that should be taken to decipher a signature * and tranform the n parameter * * @param {string} body * @returns {Array.} */ exports.extractFunctions = body => { const functions = []; const extractManipulations = caller => { const functionName = utils.between(caller, `a=a.split("");`, `.`); if (!functionName) return ''; const functionStart = `var ${functionName}={`; const ndx = body.indexOf(functionStart); if (ndx < 0) return ''; const subBody = body.slice(ndx + functionStart.length - 1); return `var ${functionName}=${utils.cutAfterJS(subBody)}`; }; const extractDecipher = () => { const functionName = utils.between(body, `a.set("alr","yes");c&&(c=`, `(decodeURIC`); if (functionName && functionName.length) { const functionStart = `${functionName}=function(a)`; const ndx = body.indexOf(functionStart); if (ndx >= 0) { const subBody = body.slice(ndx + functionStart.length); let functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)}`; functionBody = `${extractManipulations(functionBody)};${functionBody};${functionName}(sig);`; functions.push(functionBody); } } }; const extractNCode = () => { let functionName = utils.between(body, `&&(b=a.get("n"))&&(b=`, `(b)`); if (functionName.includes('[')) functionName = utils.between(body, `var ${functionName.split('[')[0]}=[`, `]`); if (functionName && functionName.length) { const functionStart = `${functionName}=function(a)`; const ndx = body.indexOf(functionStart); if (ndx >= 0) { const subBody = body.slice(ndx + functionStart.length); const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`; functions.push(functionBody); } } }; extractDecipher(); extractNCode(); return functions; }; /** * Apply decipher and n-transform to individual format * * @param {Object} format * @param {vm.Script} decipherScript * @param {vm.Script} nTransformScript */ exports.setDownloadURL = (format, decipherScript, nTransformScript) => { const decipher = url => { const args = querystring.parse(url); if (!args.s || !decipherScript) return args.url; const components = new URL(decodeURIComponent(args.url)); components.searchParams.set(args.sp ? args.sp : 'signature', decipherScript.runInNewContext({ sig: decodeURIComponent(args.s) })); return components.toString(); }; const ncode = url => { const components = new URL(decodeURIComponent(url)); const n = components.searchParams.get('n'); if (!n || !nTransformScript) return url; components.searchParams.set('n', nTransformScript.runInNewContext({ ncode: n })); return components.toString(); }; const cipher = !format.url; const url = format.url || format.signatureCipher || format.cipher; format.url = cipher ? ncode(decipher(url)) : ncode(url); delete format.signatureCipher; delete format.cipher; }; /** * Applies decipher and n parameter transforms to all format URL's. * * @param {Array.} formats * @param {string} html5player * @param {Object} options */ exports.decipherFormats = async(formats, html5player, options) => { let decipheredFormats = {}; let functions = await exports.getFunctions(html5player, options); const decipherScript = functions.length ? new vm.Script(functions[0]) : null; const nTransformScript = functions.length > 1 ? new vm.Script(functions[1]) : null; formats.forEach(format => { exports.setDownloadURL(format, decipherScript, nTransformScript); decipheredFormats[format.url] = format; }); return decipheredFormats; };