UNPKG

crunchyroll-toolkit

Version:

Toolkit Node.js complet pour extraire données d'animés, métadonnées et thumbnails depuis Crunchyroll avec techniques anti-détection 2024/2025

625 lines (624 loc) 27.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.BrowserManager = void 0; const playwright_1 = require("playwright"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); class BrowserManager { constructor(options = {}) { this.options = { headless: true, timeout: 30000, maxRetries: 3, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', locale: 'fr-FR', ...options }; } async initialize() { if (!this.browser) { console.log('🔧 Initialisation UNDETECTED-CHROME pour Playwright...'); // === UNDETECTED-CHROME TECHNIQUE === // 1. Créer un profil utilisateur temporaire réaliste const userDataDir = await this.createRealUserProfile(); // 2. Arguments ultra-furtifs basés sur undetected-chromedriver const stealthArgs = [ // Core anti-détection (basé sur UC) - ARGUMENTS CRITIQUES '--disable-blink-features=AutomationControlled', '--exclude-switches=enable-automation', '--disable-extensions', '--no-sandbox', '--disable-plugins-discovery', '--disable-features=VizDisplayCompositor', // Navigation réaliste '--no-first-run', '--no-service-autorun', '--no-default-browser-check', '--disable-default-apps', '--disable-component-update', // Extensions et services '--disable-extensions-file-access-check', '--disable-extensions-http-throttling', '--disable-component-extensions-with-background-pages', '--disable-background-extensions', // Sécurité adaptée '--disable-web-security', '--disable-site-isolation-trials', '--disable-features=TranslateUI,BlinkGenPropertyTrees', '--disable-ipc-flooding-protection', // Performance réaliste '--memory-pressure-off', '--disable-back-forward-cache', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-background-timer-throttling', // Media/GPU '--disable-gpu-sandbox', '--disable-software-rasterizer', '--disable-dev-shm-usage', // Fenêtre réaliste '--window-size=1366,768', '--disable-infobars', '--disable-notifications', // Réseau optimisé '--aggressive-cache-discard', '--disable-domain-reliability', '--disable-background-networking', // Arguments UNDETECTED-CHROMEDRIVER critiques '--disable-blink-features=AutomationControlled', '--disable-features=VizDisplayCompositor,ScriptStreaming', '--disable-automation', '--disable-save-password-bubble', '--disable-single-click-autofill', '--disable-autofill-keyboard-accessory-view[8]', '--disable-full-form-autofill-ios', '--disable-password-generation', '--disable-password-manager-reauthentication', // Profil utilisateur `--user-data-dir=${userDataDir}` ]; // 3. Configuration Playwright avec profil réel const launchOptions = { headless: this.options.headless, args: stealthArgs, // Utiliser le canal Chrome officiel (plus furtif) channel: 'chrome', // Timeout plus long pour éviter détection timeout: 60000, // Désactiver les DEV tools devtools: false }; console.log(`🚀 Lancement Chrome ${this.options.headless ? 'headless' : 'visible'} avec profil: ${userDataDir}`); // 4. Context ultra-réaliste avec rotation d'identité et profil persistant const identity = this.generateRealisticIdentity(); // Retirer user-data-dir des args pour utiliser launchPersistentContext const finalArgs = stealthArgs.filter(arg => !arg.startsWith('--user-data-dir')); const context = await playwright_1.chromium.launchPersistentContext(userDataDir, { headless: this.options.headless, args: finalArgs, channel: 'chrome', timeout: 60000, devtools: false, // Arguments UNDETECTED spécifiques à Playwright ignoreDefaultArgs: ['--enable-automation', '--enable-blink-features=IdleDetection'], userAgent: identity.userAgent, locale: identity.locale, viewport: identity.viewport, // Headers ultra-réalistes avec variation extraHTTPHeaders: { 'Accept': identity.headers.accept, 'Accept-Language': identity.headers.acceptLanguage, 'Accept-Encoding': 'gzip, deflate, br', 'Cache-Control': 'max-age=0', 'Sec-Ch-Ua': identity.headers.secChUa, 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': identity.headers.secChUaPlatform, 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1', 'User-Agent': identity.userAgent }, // Géolocalisation cohérente avec l'identité geolocation: identity.geolocation, permissions: ['geolocation'], timezoneId: identity.timezone, // Simulation réseau réaliste offline: false, // Bypass CSP pour injection de scripts bypassCSP: true }); // Récupérer la première page du context persistant const pages = context.pages(); this.page = pages.length > 0 ? pages[0] : await context.newPage(); this.page.setDefaultTimeout(this.options.timeout); // Stocker le browser (même si c'est un context persistant) this.browser = context; // 5. Injection de scripts anti-détection ultra-avancés await this.injectUndetectedScripts(); console.log('✅ Browser UNDETECTED initialisé avec succès'); } } async setupHeadlessAntiDetection() { if (!this.page) return; // Script anti-détection ultime niveau militaire await this.page.addInitScript(() => { // === NIVEAU 1: MASQUAGE DES TRACES AUTOMATION === // Suppression complète webdriver Object.defineProperty(navigator, 'webdriver', { get: () => undefined, configurable: true }); // Nettoyage des traces automation delete window.__playwright; delete window.__pw_manual; delete window.__pw_script; delete window.__puppeteer; delete window._phantom; delete window.__nightmare; delete window.__selenium; delete window.callPhantom; delete window._selenium; delete window.__webdriver_script_fn; delete window.__driver_evaluate; delete window.__webdriver_evaluate; delete window.__selenium_evaluate; delete window.__fxdriver_evaluate; delete window.__driver_unwrapped; delete window.__webdriver_unwrapped; delete window.__selenium_unwrapped; delete window.__fxdriver_unwrapped; delete window.__webdriver_script_func; // Suppression attributs automation Chrome if (navigator.webdriver !== undefined) { delete navigator.webdriver; } // === NIVEAU 2: OVERRIDE PROPRIÉTÉS CRITIQUES === // Chrome Runtime masking if ('runtime' in window && 'onConnect' in window.runtime) { delete window.runtime.onConnect; } // User Agent ultra-réaliste avec rotation const realUserAgents = [ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' ]; const selectedUA = realUserAgents[Math.floor(Math.random() * realUserAgents.length)]; Object.defineProperty(navigator, 'userAgent', { get: () => selectedUA, configurable: true }); // Platform cohérent avec l'UA const platform = selectedUA.includes('Windows') ? 'Win32' : selectedUA.includes('Mac') ? 'MacIntel' : 'Linux x86_64'; Object.defineProperty(navigator, 'platform', { get: () => platform, configurable: true }); // 4. Plugins réalistes Object.defineProperty(navigator, 'plugins', { get: () => [ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' }, { name: 'Chromium PDF Plugin', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: 'Portable Document Format' } ], configurable: true }); // 5. Languages réalistes Object.defineProperty(navigator, 'languages', { get: () => ['fr-FR', 'fr', 'en-US', 'en'], configurable: true }); // 6. Hardware concurrency réaliste Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4, configurable: true }); // 7. Memory info réaliste if (navigator.deviceMemory !== undefined) { Object.defineProperty(navigator, 'deviceMemory', { get: () => 8, configurable: true }); } // 8. Screen properties réalistes Object.defineProperty(screen, 'width', { get: () => 1366, configurable: true }); Object.defineProperty(screen, 'height', { get: () => 768, configurable: true }); Object.defineProperty(screen, 'availWidth', { get: () => 1366, configurable: true }); Object.defineProperty(screen, 'availHeight', { get: () => 728, configurable: true }); Object.defineProperty(screen, 'colorDepth', { get: () => 24, configurable: true }); Object.defineProperty(screen, 'pixelDepth', { get: () => 24, configurable: true }); // 9. Timezone cohérent Object.defineProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', { value: function () { return { locale: 'fr-FR', timeZone: 'Europe/Paris', calendar: 'gregory', numberingSystem: 'latn' }; } }); // 10. WebGL vendor réaliste const getParameter = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function (parameter) { if (parameter === 37445) { return 'Intel Inc.'; } if (parameter === 37446) { return 'Intel(R) HD Graphics 620'; } return getParameter.call(this, parameter); }; // 11. Canvas fingerprint randomization const getImageData = CanvasRenderingContext2D.prototype.getImageData; CanvasRenderingContext2D.prototype.getImageData = function (...args) { const imageData = getImageData.apply(this, args); for (let i = 0; i < imageData.data.length; i += 4) { imageData.data[i] += Math.floor(Math.random() * 3) - 1; imageData.data[i + 1] += Math.floor(Math.random() * 3) - 1; imageData.data[i + 2] += Math.floor(Math.random() * 3) - 1; } return imageData; }; // 12. Permissions API réaliste const originalQuery = navigator.permissions.query; navigator.permissions.query = function (parameters) { return originalQuery.call(this, parameters).then(result => { if (parameters.name === 'notifications') { Object.defineProperty(result, 'state', { value: 'default' }); } return result; }); }; // 13. Masquer les propriétés spécifiques au headless Object.defineProperty(navigator, 'maxTouchPoints', { get: () => 0, configurable: true }); // 14. Connection info réaliste Object.defineProperty(navigator, 'connection', { get: () => ({ effectiveType: '4g', rtt: 50, downlink: 10, saveData: false }), configurable: true }); // 15. Battery API if (navigator.getBattery) { navigator.getBattery = () => Promise.resolve({ charging: true, chargingTime: 0, dischargingTime: Infinity, level: 1 }); } }); // Configuration viewport adaptative await this.page.setViewportSize({ width: 1366 + Math.floor(Math.random() * 100), height: 768 + Math.floor(Math.random() * 100) }); } /** * Crée un profil utilisateur temporaire réaliste */ async createRealUserProfile() { const tempDir = os.tmpdir(); const profileName = `chrome-undetected-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const userDataDir = path.join(tempDir, profileName); // Créer la structure de dossier Chrome réaliste const dirs = [ 'Default', 'Default/Extensions', 'Default/Local Storage', 'Default/Session Storage', 'Default/IndexedDB', 'ShaderCache', 'Default/GPUCache' ]; for (const dir of dirs) { const fullPath = path.join(userDataDir, dir); fs.mkdirSync(fullPath, { recursive: true }); } // Créer des fichiers de configuration réalistes const prefsContent = { "profile": { "default_content_setting_values": { "notifications": 2 }, "default_content_settings": { "popups": 0 }, "managed_user_id": "", "name": "Utilisateur", "avatar_icon": "chrome://theme/IDR_PROFILE_AVATAR_0" }, "browser": { "check_default_browser": false }, "distribution": { "make_chrome_default_for_user": false, "system_level": false } }; fs.writeFileSync(path.join(userDataDir, 'Default', 'Preferences'), JSON.stringify(prefsContent, null, 2)); console.log(`📁 Profil utilisateur créé: ${userDataDir}`); return userDataDir; } /** * Génère une identité réaliste avec rotation */ generateRealisticIdentity() { const identities = [ { platform: 'Windows', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', viewport: { width: 1920, height: 1080 }, locale: 'fr-FR', timezone: 'Europe/Paris', geolocation: { latitude: 48.8566, longitude: 2.3522 }, headers: { accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', acceptLanguage: 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7', secChUa: '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"', secChUaPlatform: '"Windows"' } }, { platform: 'MacOS', userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', viewport: { width: 1440, height: 900 }, locale: 'fr-FR', timezone: 'Europe/Paris', geolocation: { latitude: 48.8566, longitude: 2.3522 }, headers: { accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', acceptLanguage: 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7', secChUa: '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"', secChUaPlatform: '"macOS"' } }, { platform: 'Linux', userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', viewport: { width: 1366, height: 768 }, locale: 'fr-FR', timezone: 'Europe/Paris', geolocation: { latitude: 48.8566, longitude: 2.3522 }, headers: { accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', acceptLanguage: 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7', secChUa: '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"', secChUaPlatform: '"Linux"' } } ]; const selectedIdentity = identities[Math.floor(Math.random() * identities.length)]; console.log(`🎭 Identité générée: ${selectedIdentity.platform}`); return selectedIdentity; } /** * Injection de scripts anti-détection ultra-avancés (basés sur UC) */ async injectUndetectedScripts() { if (!this.page) return; console.log('💉 Injection scripts UNDETECTED...'); await this.page.addInitScript(() => { // === TECHNIQUE UNDETECTED-CHROMEDRIVER === // 1. Patch complet navigator.webdriver Object.defineProperty(navigator, 'webdriver', { get: () => undefined, configurable: true }); // 2. Supprimer TOUS les indicateurs automation const toDelete = [ '__playwright', '__pw_manual', '__pw_script', '__puppeteer', '_phantom', '__nightmare', '__selenium', '__webdriver_evaluate', '__driver_evaluate', '__webdriver_script_func', '__webdriver_script_fn', '__fxdriver_evaluate', '__driver_unwrapped', '__webdriver_unwrapped', '__selenium_unwrapped', '__fxdriver_unwrapped', 'callSelenium', '_Selenium_IDE_Recorder', 'calledSelenium', '$cdc_asdjflasutopfhvcZLmcfl_', '$chrome_asyncScriptInfo', '__$webdriverAsyncExecutor', 'webdriver', '_selenium', 'callPhantom' ]; toDelete.forEach(prop => { if (prop in window) { delete window[prop]; } }); // 3. Patch navigator properties (technique UC) Object.defineProperty(navigator, 'plugins', { get: () => [ { 0: { type: "application/x-google-chrome-pdf", suffixes: "pdf", description: "Portable Document Format", enabledPlugin: null }, description: "Portable Document Format", filename: "internal-pdf-viewer", length: 1, name: "Chrome PDF Plugin" }, { 0: { type: "application/pdf", suffixes: "pdf", description: "Portable Document Format", enabledPlugin: null }, description: "Portable Document Format", filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai", length: 1, name: "Chrome PDF Viewer" } ], configurable: true }); // 4. Patch window.chrome (UC technique) window.chrome = { app: { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } }, runtime: { onConnect: null, onMessage: null }, loadTimes: function () { return { commitLoadTime: performance.timeOrigin / 1000, connectionInfo: 'http/1.1', finishDocumentLoadTime: (performance.timeOrigin + performance.now()) / 1000, finishLoadTime: (performance.timeOrigin + performance.now()) / 1000, firstPaintAfterLoadTime: 0, firstPaintTime: (performance.timeOrigin + performance.now()) / 1000, navigationType: 'Other', npnNegotiatedProtocol: 'unknown', requestTime: performance.timeOrigin / 1000, startLoadTime: performance.timeOrigin / 1000, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: false, wasNpnNegotiated: false }; }, csi: function () { return { onloadT: performance.timeOrigin + performance.now(), pageT: performance.now(), startE: performance.timeOrigin, tran: 15 }; } }; // 5. Patch permissions API (UC) const originalQuery = navigator.permissions.query; navigator.permissions.query = function (parameters) { return originalQuery.call(this, parameters).then(result => { if (parameters.name === 'notifications') { Object.defineProperty(result, 'state', { value: 'default' }); } return result; }); }; // 6. Patch Language et Timezone cohérents Object.defineProperty(navigator, 'languages', { get: () => ['fr-FR', 'fr', 'en-US', 'en'], configurable: true }); // 7. Mock realistic hardware (UC technique) Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 + Math.floor(Math.random() * 4), configurable: true }); // 8. Patch Date/Timezone const originalDate = Date; Date.prototype.getTimezoneOffset = function () { return -60; // Paris timezone }; console.log('✅ Scripts UNDETECTED injectés'); }); } async navigateTo(url) { if (!this.page) { throw new Error('Browser not initialized'); } await this.page.goto(url, { waitUntil: 'networkidle' }); } async getPage() { if (!this.page) { throw new Error('Browser not initialized'); } return this.page; } async close() { if (this.browser) { await this.browser.close(); this.browser = undefined; this.page = undefined; } } async retry(fn) { let lastError; for (let i = 0; i < this.options.maxRetries; i++) { try { return await fn(); } catch (error) { lastError = error; if (i < this.options.maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } } throw lastError; } } exports.BrowserManager = BrowserManager;