const { exec } = require('child_process'); const { promisify } = require('util'); const fs = require('fs').promises; const path = require('path'); const execAsync = promisify(exec); /** * Manages YouTube cookies lifecycle using Camoufox stealth extraction. * Auto-refresh when expired, validates periodically. */ class CookiesManager { constructor() { this.cookiesPath = path.join(__dirname, '../../youtube-cookies.txt'); this.pythonPath = process.env.PYTHON_PATH || 'python3'; this.extractScript = path.join(__dirname, '../python/extract_cookies.py'); this.validateScript = path.join(__dirname, '../python/validate_cookies.py'); this.lastRefresh = null; this.isValid = false; // Refresh cookies every 14 days (YouTube cookies typically last 2-4 weeks) this.refreshIntervalDays = 14; // Check interval (every 12 hours) this.checkIntervalMs = 12 * 60 * 60 * 1000; } /** * Initialize cookies manager. * Check if cookies exist, validate them, refresh if needed. */ async init() { console.log('🔧 Initializing cookies manager...'); // Check if cookies file exists try { await fs.access(this.cookiesPath); console.log('✅ Cookies file exists'); // Validate cookies const valid = await this.validate(); if (!valid) { console.log('⚠️ Cookies invalid, refreshing...'); await this.refresh(); } else { console.log('✅ Cookies valid'); } } catch (err) { console.log('📝 No cookies found, generating fresh cookies...'); await this.refresh(); } // Setup periodic validation (every 12 hours) setInterval(() => { this.checkAndRefresh().catch(err => { console.error('Auto-check failed:', err.message); }); }, this.checkIntervalMs); console.log('✅ Cookies manager ready'); } /** * Validate cookies using Python script. * @returns {Promise} True if cookies are valid */ async validate() { try { const { stdout, stderr } = await execAsync( `${this.pythonPath} ${this.validateScript} ${this.cookiesPath}`, { timeout: 60000 } ); // Check for validation success in output this.isValid = stdout.includes('Cookies are valid'); if (stderr && !stderr.includes('DeprecationWarning')) { console.warn('Validation stderr:', stderr.trim()); } return this.isValid; } catch (err) { console.error('Validation failed:', err.message); this.isValid = false; return false; } } /** * Refresh cookies using Camoufox extraction. * @returns {Promise} True if refresh succeeded */ async refresh() { console.log('🔄 Refreshing YouTube cookies with Camoufox...'); try { const { stdout, stderr } = await execAsync( `${this.pythonPath} ${this.extractScript} ${this.cookiesPath}`, { timeout: 120000 } // 2 min timeout ); console.log(stdout.trim()); if (stderr && !stderr.includes('DeprecationWarning')) { console.warn('Camoufox stderr:', stderr.trim()); } // Verify file was created try { await fs.access(this.cookiesPath); this.lastRefresh = Date.now(); this.isValid = true; console.log('✅ Cookies refreshed successfully'); return true; } catch { console.error('❌ Cookies file not created'); this.isValid = false; return false; } } catch (err) { console.error('❌ Failed to refresh cookies:', err.message); this.isValid = false; return false; } } /** * Check cookies age and validity, refresh if needed. */ async checkAndRefresh() { console.log('🔍 Checking cookies status...'); // Check file age try { const stats = await fs.stat(this.cookiesPath); const ageMs = Date.now() - stats.mtimeMs; const ageDays = ageMs / (1000 * 60 * 60 * 24); console.log(` Age: ${ageDays.toFixed(1)} days`); // Refresh if too old if (ageDays >= this.refreshIntervalDays) { console.log(` Age threshold (${this.refreshIntervalDays} days) reached, refreshing...`); await this.refresh(); return; } } catch { // File doesn't exist console.log(' Cookies file missing, refreshing...'); await this.refresh(); return; } // Validate cookies const valid = await this.validate(); if (!valid) { console.log(' Cookies invalid, refreshing...'); await this.refresh(); } else { console.log(' Cookies OK ✅'); } } /** * Get path to cookies file. * @returns {string} Cookies file path */ getCookiesPath() { return this.cookiesPath; } /** * Get cookies status. * @returns {object} Status object */ getStatus() { return { valid: this.isValid, path: this.cookiesPath, lastRefresh: this.lastRefresh, refreshIntervalDays: this.refreshIntervalDays }; } } // Export singleton module.exports = new CookiesManager();