UNPKG

newpipe-extractor-js

Version:
378 lines 17 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.YoutubeSignatureDecryptor = void 0; const node_fetch_1 = __importDefault(require("node-fetch")); // Enhanced YouTube Signature Decryptor - Based on yt-dlp and NewPipeExtractor implementations // Handles deobfuscation of YouTube streaming URLs with comprehensive signature decryption class YoutubeSignatureDecryptor { constructor() { this.playerCache = new Map(); this.signatureFunctionCache = new Map(); this.throttlingFunctionCache = new Map(); this.cachedSignatureTimestamp = null; this.USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; } async decryptSignature(cipher, playerResponse) { try { console.log('[SigDecrypt] Starting signature decryption...'); // Parse cipher parameters const cipherParams = this.parseCipherString(cipher); if (!cipherParams.s || !cipherParams.url || !cipherParams.sp) { console.log('[SigDecrypt] Missing required cipher parameters'); return null; } console.log(`[SigDecrypt] Cipher params: sp=${cipherParams.sp}, sig_length=${cipherParams.s.length}`); // Get player JavaScript code const playerJsCode = await this.getPlayerJsCode(playerResponse); if (!playerJsCode) { console.log('[SigDecrypt] Failed to get player JS code'); return null; } // Extract and execute signature function const decryptedSignature = await this.executeSignatureDecryption(cipherParams.s, playerJsCode); if (!decryptedSignature) { console.log('[SigDecrypt] Failed to decrypt signature'); return null; } // Construct final URL const finalUrl = `${cipherParams.url}&${cipherParams.sp}=${encodeURIComponent(decryptedSignature)}`; console.log('[SigDecrypt] Signature decryption successful'); return finalUrl; } catch (error) { console.error(`[SigDecrypt] Error: ${error.message}`); return null; } } async deobfuscateSignature(obfuscatedSignature, playerResponse) { try { const playerJsCode = await this.getPlayerJsCode(playerResponse); if (!playerJsCode) { throw new Error('Could not get player JS code'); } return await this.executeSignatureDecryption(obfuscatedSignature, playerJsCode) || obfuscatedSignature; } catch (error) { console.warn('Failed to deobfuscate signature:', error); return obfuscatedSignature; } } async getSignatureTimestamp(playerResponse) { if (this.cachedSignatureTimestamp !== null) { return this.cachedSignatureTimestamp; } try { const playerJsCode = await this.getPlayerJsCode(playerResponse); if (!playerJsCode) { throw new Error('Could not get player JS code'); } const match = YoutubeSignatureDecryptor.STS_REGEX.exec(playerJsCode); if (match && match[1]) { this.cachedSignatureTimestamp = parseInt(match[1]); return this.cachedSignatureTimestamp; } throw new Error('Could not extract signature timestamp'); } catch (error) { console.warn('Failed to get signature timestamp:', error.message); return 0; // Return 0 as fallback } } async deobfuscateThrottlingParameter(url, playerResponse) { const match = YoutubeSignatureDecryptor.THROTTLING_PARAM_REGEX.exec(url); if (!match || !match[1]) { return url; // No throttling parameter found } const obfuscatedParam = match[1]; try { // Check cache first const cacheKey = this.generateCacheKey(obfuscatedParam); if (this.throttlingFunctionCache.has(cacheKey)) { const cachedParam = this.throttlingFunctionCache.get(cacheKey); return url.replace(`n=${obfuscatedParam}`, `n=${cachedParam}`); } // Get player code and attempt throttling deobfuscation const playerJsCode = await this.getPlayerJsCode(playerResponse); if (!playerJsCode) { return url; // Return original URL if can't get player code } // For now, return original parameter as throttling deobfuscation is complex // This could be enhanced in the future with full throttling parameter decryption console.log('[SigDecrypt] Throttling parameter deobfuscation not fully implemented'); return url; } catch (error) { console.warn('Failed to deobfuscate throttling parameter:', error.message); return url; // Return original URL on error } } // Enhanced cipher string parsing parseCipherString(cipherStr) { const params = {}; const pairs = cipherStr.split('&'); for (const pair of pairs) { const [key, value] = pair.split('='); if (key && value) { params[key] = decodeURIComponent(value); } } return params; } // Enhanced player JavaScript code retrieval async getPlayerJsCode(playerResponse) { try { // Try to extract player URL from player response first let playerJsUrl = null; if (playerResponse) { // Look for player URL in various places const possiblePaths = [ 'player.web.web.jsUrl', 'player.web.jsUrl', 'playerJsUrl', 'jsUrl' ]; for (const path of possiblePaths) { const url = this.getNestedValue(playerResponse, path); if (url) { playerJsUrl = url.startsWith('//') ? `https:${url}` : url; break; } } } // Fallback: get player URL from YouTube main page if (!playerJsUrl) { console.log('[SigDecrypt] Getting player URL from YouTube main page...'); playerJsUrl = await this.extractPlayerUrlFromMainPage(); } if (!playerJsUrl) { throw new Error('Could not find player JS URL'); } console.log(`[SigDecrypt] Player JS URL: ${playerJsUrl.substring(0, 50)}...`); // Check cache first if (this.playerCache.has(playerJsUrl)) { console.log('[SigDecrypt] Using cached player JS'); return this.playerCache.get(playerJsUrl); } // Download player JavaScript const response = await (0, node_fetch_1.default)(playerJsUrl, { headers: { 'User-Agent': this.USER_AGENT }, timeout: 30000 }); if (!response.ok) { throw new Error(`Failed to download player code: ${response.status}`); } const playerJs = await response.text(); // Cache the player code this.playerCache.set(playerJsUrl, playerJs); // Clean old cache entries (keep last 3) if (this.playerCache.size > 3) { const firstKey = this.playerCache.keys().next().value; if (firstKey) { this.playerCache.delete(firstKey); } } console.log(`[SigDecrypt] Downloaded player JS: ${playerJs.length} characters`); return playerJs; } catch (error) { console.error(`[SigDecrypt] Failed to get player JS: ${error.message}`); return null; } } // Enhanced player URL extraction from YouTube main page async extractPlayerUrlFromMainPage() { try { const response = await (0, node_fetch_1.default)('https://www.youtube.com/', { headers: { 'User-Agent': this.USER_AGENT }, timeout: 15000 }); if (!response.ok) { throw new Error(`Failed to fetch YouTube main page: ${response.status}`); } const pageContent = await response.text(); // Multiple patterns to find player URL const patterns = [ /"jsUrl":"([^"]+)"/, /ytplayer\.config\s*=\s*.*?"jsUrl"\s*:\s*"([^"]+)"/, /"WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH":"[^"]*","PLAYER_JS_URL":"([^"]+)"/ ]; for (const pattern of patterns) { const match = pageContent.match(pattern); if (match && match[1]) { let url = match[1].replace(/\\u0026/g, '&').replace(/\\/g, ''); if (url.startsWith('//')) { url = 'https:' + url; } else if (url.startsWith('/')) { url = 'https://www.youtube.com' + url; } return url; } } throw new Error('Player URL not found in main page'); } catch (error) { console.error(`[SigDecrypt] Failed to extract player URL: ${error.message}`); return null; } } // Enhanced signature decryption execution async executeSignatureDecryption(encryptedSignature, playerJsCode) { try { // Check cache first const cacheKey = this.generateCacheKey(playerJsCode); if (this.signatureFunctionCache.has(cacheKey)) { const cachedFunction = this.signatureFunctionCache.get(cacheKey); return this.applySignatureTransforms(encryptedSignature, cachedFunction); } console.log('[SigDecrypt] Extracting signature function from player JS...'); // Extract signature function name const functionName = this.extractSignatureFunctionName(playerJsCode); if (!functionName) { throw new Error('Could not find signature function name'); } console.log(`[SigDecrypt] Found signature function: ${functionName}`); // Extract transformation plan const transformPlan = this.extractTransformationPlan(functionName, playerJsCode); if (!transformPlan || transformPlan.length === 0) { throw new Error('Could not extract transformation plan'); } console.log(`[SigDecrypt] Found ${transformPlan.length} transformation operations`); // Cache the transformation plan this.signatureFunctionCache.set(cacheKey, transformPlan); // Apply transformations return this.applySignatureTransforms(encryptedSignature, transformPlan); } catch (error) { console.error(`[SigDecrypt] Signature function extraction failed: ${error.message}`); return null; } } // Enhanced signature function name extraction extractSignatureFunctionName(playerJs) { for (const regex of YoutubeSignatureDecryptor.FUNCTION_REGEXES) { const match = regex.exec(playerJs); if (match && match[1]) { return match[1]; } } return undefined; } // Enhanced transformation plan extraction extractTransformationPlan(functionName, playerJs) { try { // Find the function definition const escapedName = functionName.replace(/\$/g, '\\$'); const functionPattern = new RegExp(escapedName + '\\s*=\\s*function\\s*\\([^)]*\\)\\s*\\{([^}]+)\\}'); const functionMatch = playerJs.match(functionPattern); if (!functionMatch) { throw new Error(`Function ${functionName} not found`); } const functionBody = functionMatch[1]; // Extract transformation statements const statements = functionBody.split(';').map(s => s.trim()).filter(s => s); const transformations = []; for (const statement of statements) { // Parse different transformation types if (statement.includes('.reverse()')) { transformations.push({ type: 'reverse' }); } else if (statement.includes('.splice(')) { const spliceMatch = statement.match(/\.splice\(\s*(\d+)\s*\)/); if (spliceMatch) { transformations.push({ type: 'splice', parameter: parseInt(spliceMatch[1]) }); } } else { // Look for swap operations const swapMatch = statement.match(/\[(\d+)\].*?\[.*?(\d+).*?\]/); if (swapMatch) { transformations.push({ type: 'swap', parameter: parseInt(swapMatch[2]) }); } } } return transformations; } catch (error) { console.error(`[SigDecrypt] Transform plan extraction failed: ${error.message}`); return null; } } // Enhanced signature transformation application applySignatureTransforms(signature, transformPlan) { try { let sigArray = signature.split(''); for (const transform of transformPlan) { switch (transform.type) { case 'reverse': sigArray.reverse(); break; case 'splice': sigArray = sigArray.slice(transform.parameter); break; case 'swap': if (sigArray.length > transform.parameter) { const temp = sigArray[0]; sigArray[0] = sigArray[transform.parameter % sigArray.length]; sigArray[transform.parameter % sigArray.length] = temp; } break; } } const result = sigArray.join(''); console.log(`[SigDecrypt] Applied ${transformPlan.length} transformations`); return result; } catch (error) { console.error(`[SigDecrypt] Transform application failed: ${error.message}`); return signature; // Return original on failure } } // Helper method to get nested object values getNestedValue(obj, path) { return path.split('.').reduce((current, key) => current && current[key], obj); } // Generate cache key from string generateCacheKey(input) { // Simple hash function for caching let hash = 0; for (let i = 0; i < Math.min(input.length, 1000); i++) { const char = input.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return hash.toString(); } // Clear all caches clearCache() { this.playerCache.clear(); this.signatureFunctionCache.clear(); this.throttlingFunctionCache.clear(); this.cachedSignatureTimestamp = null; console.log('[SigDecrypt] Cache cleared'); } } exports.YoutubeSignatureDecryptor = YoutubeSignatureDecryptor; // Enhanced regex patterns for finding deobfuscation function (based on yt-dlp) YoutubeSignatureDecryptor.FUNCTION_REGEXES = [ /\bm=([a-zA-Z0-9$]{2,})\(decodeURIComponent\(h\.s\)\)/, /\bc&&\(c=([a-zA-Z0-9$]{2,})\(decodeURIComponent\(c\)\)/, /(?:\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\s*=\s*function\(\s*a\s*\)\s*\{\s*a\s*=\s*a\.split\(\s*""\s*\)/, /([\w$]+)\s*=\s*function\((\w+)\)\{\s*\2=\s*\2\.split\(""\)\s*;/, /\.sig\|\|([a-zA-Z0-9$]+)\(/, /yt\.akamaized\.net\/\)\s*\|\|\s*.*?\s*c\s*&&\s*d\.set\([^,]+\s*,\s*(?:encodeURIComponent\s*\()?\s*([a-zA-Z0-9$]+)\(/, /\bc\s*&&\s*d\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*([a-zA-Z0-9$]+)\(/, /\bc\s*&&\s*d\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*([a-zA-Z0-9$]+)\(/ ]; YoutubeSignatureDecryptor.STS_REGEX = /signatureTimestamp[=:](\d+)/; YoutubeSignatureDecryptor.THROTTLING_PARAM_REGEX = /[&?]n=([^&]+)/; //# sourceMappingURL=YoutubeSignatureDecryptor.js.map