UNPKG

newpipe-extractor-js

Version:
347 lines 13.6 kB
"use strict"; // Advanced Throttling Parameter Decryptor for NewPipe Extractor JS // Based on yt-dlp's n-parameter handling // Handles YouTube's anti-bot throttling mechanisms Object.defineProperty(exports, "__esModule", { value: true }); exports.throttlingDecryptor = exports.ThrottlingDecryptor = void 0; /** * Advanced Throttling Parameter Decryptor * Handles YouTube's n-parameter obfuscation used to prevent bot access */ class ThrottlingDecryptor { // Transformation helper function patterns (currently unused but kept for future enhancements) // private static readonly TRANSFORM_PATTERNS = [ // // Reverse operation // /\.reverse\(\)/g, // // Splice operations // /\.splice\(\s*(\d+)\s*,\s*(\d+)\s*\)/g, // // Character/array manipulation // /\[(\d+)\]\s*=\s*\[(\d+)\]/g // ]; constructor() { this.cache = new Map(); this.CACHE_TIMEOUT = 30 * 60 * 1000; // 30 minutes this.MAX_CACHE_SIZE = 10; // Clean up cache periodically setInterval(() => this.cleanupCache(), 5 * 60 * 1000); // Every 5 minutes } /** * Decrypt throttling parameter from URL */ async decryptThrottlingParameter(url, playerJsCode) { try { console.log('[ThrottlingDecryptor] Processing throttling parameter...'); // Extract n parameter from URL const nParam = this.extractNParameter(url); if (!nParam) { console.log('[ThrottlingDecryptor] No n parameter found in URL'); return url; } console.log(`[ThrottlingDecryptor] Found n parameter: ${nParam}`); // Get player JS code if not provided if (!playerJsCode) { console.log('[ThrottlingDecryptor] No player JS provided, skipping n-parameter decryption'); return url; } // Check cache first const cacheKey = this.generateCacheKey(playerJsCode); const cached = this.cache.get(cacheKey); if (cached && this.isCacheValid(cached)) { console.log('[ThrottlingDecryptor] Using cached throttling function'); const decryptedParam = this.applyThrottlingOperations(nParam, cached.operations); return this.replaceNParameter(url, decryptedParam); } // Extract and parse throttling function const throttlingFunction = this.extractThrottlingFunction(playerJsCode); if (!throttlingFunction) { console.log('[ThrottlingDecryptor] No throttling function found'); return url; } console.log('[ThrottlingDecryptor] Found throttling function'); // Parse operations from function const operations = this.parseThrottlingOperations(throttlingFunction); if (operations.length === 0) { console.log('[ThrottlingDecryptor] No operations found in throttling function'); return url; } console.log(`[ThrottlingDecryptor] Parsed ${operations.length} throttling operations`); // Cache the operations this.cache.set(cacheKey, { operations, timestamp: Date.now(), playerJsUrl: '', success: true }); // Apply operations to decrypt n parameter const decryptedParam = this.applyThrottlingOperations(nParam, operations); const decryptedUrl = this.replaceNParameter(url, decryptedParam); console.log(`[ThrottlingDecryptor] Successfully decrypted n parameter`); return decryptedUrl; } catch (error) { console.error(`[ThrottlingDecryptor] Error: ${error.message}`); return url; // Return original URL on error } } /** * Extract n parameter from URL */ extractNParameter(url) { const nMatch = url.match(/[?&]n=([^&]+)/); return nMatch ? decodeURIComponent(nMatch[1]) : null; } /** * Replace n parameter in URL */ replaceNParameter(url, newValue) { return url.replace(/([?&])n=([^&]+)/, `$1n=${encodeURIComponent(newValue)}`); } /** * Extract throttling function from player JS */ extractThrottlingFunction(playerJs) { for (const pattern of ThrottlingDecryptor.N_FUNCTION_PATTERNS) { const match = pattern.exec(playerJs); if (match) { const functionName = match[1]; // Extract the full function const functionPattern = new RegExp(`${this.escapeRegex(functionName)}\\s*=\\s*function\\([^)]*\\)\\s*\\{[^}]*\\}`, 'g'); const funcMatch = functionPattern.exec(playerJs); if (funcMatch) { return funcMatch[0]; } // Try alternative extraction method const altPattern = new RegExp(`function\\s+${this.escapeRegex(functionName)}\\([^)]*\\)\\s*\\{[^}]*\\}`, 'g'); const altMatch = altPattern.exec(playerJs); if (altMatch) { return altMatch[0]; } } } return null; } /** * Parse throttling operations from function code */ parseThrottlingOperations(functionCode) { const operations = []; try { // Look for reverse operations if (functionCode.includes('.reverse()')) { operations.push({ type: 'reverse', params: [] }); } // Look for splice operations const spliceMatches = functionCode.matchAll(/\.splice\(\s*(\d+)\s*,\s*(\d+)\s*\)/g); for (const match of spliceMatches) { operations.push({ type: 'splice', params: [parseInt(match[1]), parseInt(match[2])] }); } // Look for swap operations const swapMatches = functionCode.matchAll(/\[(\d+)\]\s*=\s*\[(\d+)\]/g); for (const match of swapMatches) { operations.push({ type: 'swap', params: [parseInt(match[1]), parseInt(match[2])] }); } // Look for complex transformations if (functionCode.includes('split') && functionCode.includes('join')) { operations.push({ type: 'transform', params: [] }); } } catch (error) { console.error(`[ThrottlingDecryptor] Error parsing operations: ${error.message}`); } return operations; } /** * Apply throttling operations to decrypt parameter */ applyThrottlingOperations(nParam, operations) { try { let result = nParam.split(''); for (const operation of operations) { switch (operation.type) { case 'reverse': result = result.reverse(); break; case 'splice': if (operation.params.length >= 2) { const [start, deleteCount] = operation.params; result.splice(start, deleteCount); } break; case 'swap': if (operation.params.length >= 2) { const [i, j] = operation.params; if (i < result.length && j < result.length) { [result[i], result[j]] = [result[j], result[i]]; } } break; case 'transform': // Apply basic character transformation result = this.applyCharacterTransform(result); break; case 'shuffle': // Apply deterministic shuffle result = this.applyDeterministicShuffle(result); break; } } return result.join(''); } catch (error) { console.error(`[ThrottlingDecryptor] Error applying operations: ${error.message}`); return nParam; // Return original on error } } /** * Apply character transformation */ applyCharacterTransform(chars) { // Basic transformation based on character codes return chars.map(char => { const code = char.charCodeAt(0); if (code >= 48 && code <= 57) { // 0-9 return String.fromCharCode(((code - 48 + 5) % 10) + 48); } else if (code >= 65 && code <= 90) { // A-Z return String.fromCharCode(((code - 65 + 13) % 26) + 65); } else if (code >= 97 && code <= 122) { // a-z return String.fromCharCode(((code - 97 + 13) % 26) + 97); } return char; }); } /** * Apply deterministic shuffle */ applyDeterministicShuffle(chars) { const result = [...chars]; const length = result.length; // Simple deterministic shuffle algorithm for (let i = 0; i < length; i++) { const j = (i * 7 + 3) % length; [result[i], result[j]] = [result[j], result[i]]; } return result; } /** * Generate cache key from player JS */ generateCacheKey(playerJs) { // Use a hash of relevant parts of the player JS const relevantParts = playerJs.substring(0, 1000) + playerJs.substring(playerJs.length - 1000); return this.simpleHash(relevantParts); } /** * Simple hash function for cache keys */ simpleHash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return hash.toString(36); } /** * Check if cache entry is still valid */ isCacheValid(entry) { return (Date.now() - entry.timestamp) < this.CACHE_TIMEOUT; } /** * Clean up expired cache entries */ cleanupCache() { // const now = Date.now(); // Currently unused for (const [key, entry] of this.cache.entries()) { if (!this.isCacheValid(entry)) { this.cache.delete(key); } } // Limit cache size if (this.cache.size > this.MAX_CACHE_SIZE) { const entries = Array.from(this.cache.entries()); entries.sort((a, b) => a[1].timestamp - b[1].timestamp); const toDelete = entries.slice(0, entries.length - this.MAX_CACHE_SIZE); for (const [key] of toDelete) { this.cache.delete(key); } } } /** * Escape regex special characters */ escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /** * Clear all cached data */ clearCache() { this.cache.clear(); console.log('[ThrottlingDecryptor] Cache cleared'); } /** * Get cache statistics */ getCacheStats() { return { size: this.cache.size, entries: Array.from(this.cache.entries()).map(([key, entry]) => ({ key, timestamp: entry.timestamp, success: entry.success })) }; } /** * Test throttling decryption with known parameters */ async testDecryption(testCases) { const results = []; for (const testCase of testCases) { try { const mockUrl = `https://example.com/video?n=${encodeURIComponent(testCase.input)}`; const result = await this.decryptThrottlingParameter(mockUrl); const extractedParam = this.extractNParameter(result); results.push({ input: testCase.input, output: extractedParam || '', success: !testCase.expected || extractedParam === testCase.expected }); } catch (error) { results.push({ input: testCase.input, output: '', success: false }); } } return results; } } exports.ThrottlingDecryptor = ThrottlingDecryptor; // Common n-parameter function patterns found in YouTube player JS ThrottlingDecryptor.N_FUNCTION_PATTERNS = [ // Pattern for main n-function /([a-zA-Z_$][\w$]*)\s*=\s*function\([a-zA-Z_$][\w$]*\)\s*\{\s*[a-zA-Z_$][\w$]*\s*=\s*[a-zA-Z_$][\w$]*\.split\(\s*['"]{2}\s*\)/, // Alternative patterns /([a-zA-Z_$][\w$]*)\s*=\s*function\([a-zA-Z_$][\w$]*\)\s*\{\s*var\s+[a-zA-Z_$][\w$]*\s*=\s*[a-zA-Z_$][\w$]*\.split\(/, /function\s*([a-zA-Z_$][\w$]*)\([a-zA-Z_$][\w$]*\)\s*\{\s*[a-zA-Z_$][\w$]*\s*=\s*[a-zA-Z_$][\w$]*\.split\(/, // Compact patterns /([a-zA-Z_$][\w$]*)\s*:\s*function\([a-zA-Z_$][\w$]*\)\s*\{\s*[a-zA-Z_$][\w$]*\s*=\s*[a-zA-Z_$][\w$]*\.split\(/ ]; /** * Singleton instance of throttling decryptor */ exports.throttlingDecryptor = new ThrottlingDecryptor(); //# sourceMappingURL=ThrottlingDecryptor.js.map