""" Gmail Automatisierung - Hauptklasse """ import logging import time import random from typing import Dict, Optional, Any from playwright.sync_api import Page from social_networks.base_automation import BaseAutomation from social_networks.gmail import gmail_selectors as selectors from social_networks.gmail.gmail_ui_helper import GmailUIHelper from social_networks.gmail.gmail_registration import GmailRegistration from social_networks.gmail.gmail_login import GmailLogin from social_networks.gmail.gmail_verification import GmailVerification from social_networks.gmail.gmail_utils import GmailUtils logger = logging.getLogger("gmail_automation") class GmailAutomation(BaseAutomation): """ Gmail/Google Account-spezifische Automatisierung """ def __init__(self, **kwargs): """ Initialisiert die Gmail-Automatisierung """ super().__init__(**kwargs) self.platform_name = "gmail" self.ui_helper: Optional[GmailUIHelper] = None self.registration: Optional[GmailRegistration] = None self.login_helper: Optional[GmailLogin] = None self.verification: Optional[GmailVerification] = None self.utils: Optional[GmailUtils] = None # Optionaler SMS-Dienst (z.B. 5sim, sms-activate), vom Worker injizierbar self.phone_service = kwargs.get("phone_service") def _initialize_helpers(self, page: Page): """ Initialisiert die Hilfsklassen """ self.ui_helper = GmailUIHelper(page, self.screenshots_dir, self.save_screenshots) self.registration = GmailRegistration(page, self.ui_helper, self.screenshots_dir, self.save_screenshots, phone_service=self.phone_service) self.login_helper = GmailLogin(page, self.ui_helper, self.screenshots_dir, self.save_screenshots) self.verification = GmailVerification( page, self.ui_helper, self.email_handler, self.screenshots_dir, self.save_screenshots, phone_service=self.phone_service ) self.utils = GmailUtils() def register_account(self, full_name: str, age: int, registration_method: str = "email", phone_number: str = None, **kwargs) -> Dict[str, Any]: """ Erstellt einen neuen Gmail/Google Account. Delegiert nach Navigation an den GmailRegistration-Flow und gibt ein konsistentes Ergebnis zurück. """ try: logger.info("[GMAIL AUTOMATION] register_account gestartet") # Accountdaten zusammenstellen account_data = { "full_name": full_name, "first_name": kwargs.get("first_name", (full_name.split()[0] if full_name else "")), "last_name": kwargs.get("last_name", (full_name.split()[-1] if full_name and len(full_name.split()) > 1 else "")), "age": age, "birthday": kwargs.get("birthday", self._generate_birthday(age)), "gender": kwargs.get("gender", random.choice(["male", "female"])), "username": kwargs.get("username", ""), "password": kwargs.get("password", ""), "phone": phone_number, # Recovery-E-Mail reduziert häufig das Phone-Requirement "recovery_email": kwargs.get("recovery_email", kwargs.get("backup_email", "")) } # Falls keine Recovery-Email übergeben wurde, generiere eine Fallback-Adresse if not account_data.get("recovery_email"): try: imap_user = self.email_handler.get_config().get("imap_user", "") domain = imap_user.split("@")[-1] if "@" in imap_user else self.email_domain suffix = str(int(time.time()))[-6:] local = "account.recovery" # generischer Local-Part account_data["recovery_email"] = f"{local}+{suffix}@{domain}" logger.info(f"Recovery-Email automatisch gesetzt: {account_data['recovery_email']}") except Exception: pass # Browser/Seite sicherstellen - Fresh Browser Profile für bessere Bypass-Chance if not self._initialize_fresh_browser(): return { "success": False, "error": "Browser konnte nicht initialisiert werden", "message": "Browser-Initialisierung fehlgeschlagen" } page = self.browser.page self._initialize_helpers(page) # Versuche mehrere Locale-Varianten der direkten Signup-URL (reduziert UI-Abhängigkeit) # Standardmäßig DE verwenden – versuche DE-Varianten (DE, AT, CH) in deutscher Sprache locales = kwargs.get("locales_try", ["de", "de-AT", "de-CH"]) signup_base = "https://accounts.google.com/signup/v2/createaccount?service=mail&flowName=GlifWebSignIn&flowEntry=SignUp&hl={hl}" last_error: Optional[str] = None for hl in locales: try: # Pro Locale mehrere Versuche mit variierenden Profilen (Geschlecht) for attempt in range(3): # Re-Init Browser/Seite für völlig frischen Kontext (Fresh Browser Profile) try: self._close_browser() except Exception: pass if not self._initialize_fresh_browser(): return {"success": False, "error": "Browser init failed"} page = self.browser.page self._initialize_helpers(page) # Varianz: Geschlecht wechseln für verschiedene Versuche account_data["gender"] = random.choice(["male", "female"]) if attempt % 2 == 0 else account_data.get("gender", "male") url = signup_base.format(hl=hl) logger.info(f"[Versuch {attempt+1}/3] Öffne Signup-URL (hl={hl}): {url}") page.goto(url, wait_until="networkidle") time.sleep(2) # Sicherstellen, dass die Signup-Felder sichtbar sind try: if not page.locator(selectors.FIRST_NAME_INPUT).is_visible(timeout=7000): logger.info("Namensfeld nicht sichtbar – versuche Reload") page.goto(url, wait_until="networkidle") time.sleep(2) except Exception: page.goto(url, wait_until="networkidle") time.sleep(2) # Registrierungs-Flow ausführen flow_result = self.registration.start_registration_flow(account_data) if flow_result.get("success"): username = flow_result.get("username") or account_data.get("username") email = flow_result.get("email") or (f"{username}@gmail.com" if username else "") return { "success": True, "username": username, "email": email, "password": account_data.get("password", ""), "account_data": { "username": username, "email": email, "password": account_data.get("password", ""), "phone": account_data.get("phone", ""), "full_name": account_data.get("full_name", "") }, "message": f"Gmail-Registrierung erfolgreich (hl={hl}, attempt={attempt+1})" } # Prüfe Fehlertext err_text = (flow_result.get("error") or flow_result.get("message") or "").lower() last_error = err_text or last_error if "phone required" in err_text or "telefon" in err_text: logger.info(f"(hl={hl}, attempt={attempt+1}) verlangte Telefon – wiederhole mit geänderten Parametern") continue # versuche nächsten Attempt in gleicher Locale # Sonst: gib den Fehler direkt zurück return flow_result # Nach drei Versuchen in dieser Locale – zur nächsten Locale wechseln logger.info(f"Wechsle Locale nach 3 Versuchen ohne Erfolg: {hl}") except Exception as e: last_error = str(e) logger.warning(f"Fehler bei Locale {hl}: {e}") # Wenn alle Locales scheitern, gib letzten Fehler zurück return { "success": False, "error": last_error or "Unbekannter Fehler", "message": "Registrierung ohne Telefonnummer nicht möglich in dieser Umgebung" } except Exception as e: logger.error(f"Fehler bei der Gmail-Registrierung: {e}") return {"success": False, "error": str(e), "message": f"Registrierung fehlgeschlagen: {str(e)}"} finally: # Browser aktuell schließen, um Ressourcen freizugeben self._close_browser() def login(self, username: str, password: str) -> Dict[str, Any]: """ Meldet sich bei einem bestehenden Gmail/Google Account an """ try: logger.info(f"Starte Gmail Login für {username}") # Initialisiere Browser, falls noch nicht geschehen if not self.browser or not hasattr(self.browser, 'page'): if not self._initialize_browser(): return { "success": False, "error": "Browser konnte nicht initialisiert werden", "message": "Browser-Initialisierung fehlgeschlagen" } # Page-Objekt holen page = self.browser.page self._initialize_helpers(page) # Login durchführen return self.login_helper.login(username, password) except Exception as e: logger.error(f"Fehler beim Gmail Login: {str(e)}") return { "success": False, "error": str(e), "message": f"Login fehlgeschlagen: {str(e)}" } finally: self._close_browser() def get_account_info(self) -> Dict[str, Any]: """ Ruft Informationen über den aktuellen Account ab """ # TODO: Implementierung return { "success": False, "message": "Noch nicht implementiert" } def logout(self) -> bool: """ Meldet sich vom aktuellen Account ab """ # TODO: Implementierung return False def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]: """ Meldet sich bei einem bestehenden Gmail Account an. Implementiert die abstrakte Methode aus BaseAutomation. """ return self.login(username_or_email, password) def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]: """ Verifiziert einen Gmail Account mit einem Bestätigungscode. Implementiert die abstrakte Methode aus BaseAutomation. """ try: if self.verification: return self.verification.verify_with_code(verification_code) else: return { "success": False, "error": "Verification helper nicht initialisiert", "message": "Verifizierung kann nicht durchgeführt werden" } except Exception as e: return { "success": False, "error": str(e), "message": f"Verifizierung fehlgeschlagen: {str(e)}" } def _generate_birthday(self, age: int) -> str: """ Generiert ein Geburtsdatum basierend auf dem Alter """ from datetime import datetime, timedelta today = datetime.now() birth_year = today.year - age # Zufälliger Tag im Jahr random_days = random.randint(0, 364) birthday = datetime(birth_year, 1, 1) + timedelta(days=random_days) return birthday.strftime("%Y-%m-%d") def _initialize_fresh_browser(self) -> bool: """ Initialisiert einen völlig frischen Browser-Kontext für optimale Phone-Bypass-Chancen. Verwendet erweiterte 2025-Techniken für bessere Erfolgschancen. """ try: logger.info("[FRESH BROWSER] Initialisiere enhanced Browser-Kontext mit 2025 Optimierungen") # Sicherheitsschließung des alten Browsers if self.browser: try: self.browser.close() except Exception: pass finally: self.browser = None # Erstelle temporäres User-Data-Directory für isolierten Kontext import tempfile import shutil temp_profile_dir = tempfile.mkdtemp(prefix="gmail_fresh_profile_") # Proxy-Konfiguration übernehmen falls vorhanden proxy_config = None if self.use_proxy: proxy_config = self.proxy_rotator.get_proxy(self.proxy_type) if not proxy_config: logger.warning("Kein Proxy verfügbar, verwende direkten Zugriff") # Enhanced Browser-Konfiguration mit 2025 Optimierungen from browser.playwright_manager import PlaywrightManager # Randomisiere Browser-Eigenschaften für besseren Fingerprint import random viewport_configs = [ {"width": 1920, "height": 1080}, {"width": 1366, "height": 768}, {"width": 1440, "height": 900}, {"width": 1536, "height": 864}, {"width": 1280, "height": 720} ] selected_viewport = random.choice(viewport_configs) self.browser = PlaywrightManager( headless=False, # Niemals headless für Gmail (erhöht Erkennungsrisiko) proxy=proxy_config, browser_type="chromium", screenshots_dir=self.screenshots_dir, slowmo=random.randint(50, 150), # Randomisierte Geschwindigkeit window_position=self.window_position ) # ÜBERSCHREIBE Launch-Optionen für Fresh Profile # Backup der ursprünglichen Start-Methode original_start = self.browser.start def fresh_start(): # Enhanced Chrome-Args mit 2025 Anti-Detection Features fresh_args = [ '--no-first-run', '--no-default-browser-check', '--disable-default-apps', '--disable-blink-features=AutomationControlled', # Kritisch für 2025 '--disable-dev-shm-usage', '--disable-web-security', '--disable-features=IsolateOrigins,site-per-process', '--disable-site-isolation-trials', '--disable-translate', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-component-update', '--disable-client-side-phishing-detection', '--disable-sync', '--disable-features=TranslateUI,BlinkGenPropertyTrees', '--disable-ipc-flooding-protection', '--enable-features=NetworkService,NetworkServiceInProcess', '--force-color-profile=srgb', '--metrics-recording-only', '--incognito' # Incognito-Modus für frischen Start ] # Temporär Launch-Optionen erweitern if hasattr(self.browser, 'playwright') and self.browser.playwright: self.browser.playwright = None # Force re-init # Browser mit Fresh Args starten from playwright.sync_api import sync_playwright self.browser.playwright = sync_playwright().start() browser_instance = self.browser.playwright.chromium # Kombiniere bestehende und fresh args existing_args = getattr(self.browser, 'browser_args', []) combined_args = list(set(existing_args + fresh_args)) # Duplikate entfernen # Verwende launch_persistent_context für user-data-dir launch_options = { "headless": self.browser.headless, "args": combined_args, "slow_mo": self.browser.slowmo } # Fensterposition if self.browser.window_position and not self.browser.headless: x, y = self.browser.window_position combined_args.append(f'--window-position={x},{y}') # Enhanced Context mit 2025 Anti-Detection Eigenschaften import random user_agents = [ "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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.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", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ] context_options = { "viewport": selected_viewport, "user_agent": random.choice(user_agents), "device_scale_factor": random.choice([1.0, 1.25, 1.5, 2.0]), "is_mobile": False, "has_touch": False, "locale": random.choice(["de-DE", "en-US", "de-AT", "de-CH"]), "timezone_id": random.choice([ "Europe/Berlin", "Europe/Vienna", "Europe/Zurich", "America/New_York", "America/Chicago", "Europe/London" ]), "accept_downloads": True, "ignore_https_errors": True, "bypass_csp": True, # Bypass Content Security Policy "java_script_enabled": True, "extra_http_headers": { "Accept-Language": "de-DE,de;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate, br", "DNT": "1" } } # Randomized User-Agent user_agents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0" ] context_options["user_agent"] = random.choice(user_agents) # Proxy falls vorhanden if proxy_config: context_options["proxy"] = proxy_config # Kombiniere Launch-Optionen mit Context-Optionen launch_options.update(context_options) # KORREKTUR: Verwende launch_persistent_context (erstellt Browser UND Context) self.browser.context = browser_instance.launch_persistent_context( user_data_dir=temp_profile_dir, **launch_options ) # Bei launch_persistent_context ist der Browser im Context enthalten self.browser.browser = None # Nicht benötigt bei persistent context # Fresh Stealth-Scripts anwenden (variiert) self._apply_fresh_stealth_scripts() # Bei launch_persistent_context ist bereits eine Seite vorhanden pages = self.browser.context.pages if pages: self.browser.page = pages[0] # Verwende erste verfügbare Seite else: self.browser.page = self.browser.context.new_page() return self.browser.page # Ersetze die start-Methode self.browser.start = fresh_start # Browser starten page = self.browser.start() logger.info("[FRESH BROWSER] Fresh Browser erfolgreich initialisiert") # Cleanup-Handler für temp directory (speichere Referenz für späteren Cleanup) self._temp_profile_cleanup = lambda: shutil.rmtree(temp_profile_dir, ignore_errors=True) import atexit atexit.register(self._temp_profile_cleanup) return True except Exception as e: logger.error(f"[FRESH BROWSER] Fehler bei Fresh Browser Initialisierung: {e}") return False def _apply_fresh_stealth_scripts(self): """ Wendet variierte Stealth-Scripts an, die für jeden Fresh Browser unterschiedlich sind """ import random # Base Stealth Scripts base_scripts = [ # WebDriver detection removal """ () => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined, }); delete navigator.__proto__.webdriver; } """, # Randomized navigator properties f""" () => {{ Object.defineProperty(navigator, 'platform', {{ get: () => '{random.choice(["Win32", "MacIntel", "Linux x86_64"])}' }}); Object.defineProperty(navigator, 'languages', {{ get: () => {random.choice([ "['de-DE', 'de', 'en-US', 'en']", "['en-US', 'en', 'de-DE', 'de']", "['de-AT', 'de', 'en-US', 'en']" ])} }}); Object.defineProperty(navigator, 'hardwareConcurrency', {{ get: () => {random.choice([4, 8, 12, 16])} }}); Object.defineProperty(navigator, 'deviceMemory', {{ get: () => {random.choice([4, 8, 16, 32])} }}); }} """, # Chrome runtime """ () => { if (!window.chrome) { window.chrome = {}; } if (!window.chrome.runtime) { window.chrome.runtime = { onConnect: undefined, onMessage: undefined, sendMessage: function() {}, }; } } """, # Permissions randomization f""" () => {{ const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) => {{ const responses = {{ 'notifications': '{random.choice(['granted', 'denied', 'prompt'])}', 'geolocation': '{random.choice(['denied', 'prompt'])}', 'camera': '{random.choice(['denied', 'prompt'])}', 'microphone': '{random.choice(['denied', 'prompt'])}' }}; const state = responses[parameters.name] || 'prompt'; return Promise.resolve({{ state }}); }}; }} """ ] # Randomized Canvas fingerprinting noise_level = random.uniform(0.1, 2.0) canvas_script = f""" () => {{ const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function(type) {{ const context = this.getContext('2d'); const originalImageData = context.getImageData; context.getImageData = function(x, y, width, height) {{ const imageData = originalImageData.apply(this, arguments); const data = imageData.data; // Add subtle random noise for (let i = 0; i < data.length; i += 4) {{ if (Math.random() < 0.1) {{ // 10% chance per pixel const noise = (Math.random() - 0.5) * {noise_level}; data[i] = Math.min(255, Math.max(0, data[i] + noise)); data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + noise)); data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + noise)); }} }} return imageData; }}; return originalToDataURL.apply(this, arguments); }}; }} """ base_scripts.append(canvas_script) # Apply all scripts to context for script in base_scripts: try: self.browser.context.add_init_script(script) except Exception as e: logger.warning(f"[FRESH BROWSER] Script application failed: {e}") continue logger.info(f"[FRESH BROWSER] Applied {len(base_scripts)} fresh stealth scripts") def _close_browser(self) -> None: """ Überschreibt die Standard-Browser-Schließung für Fresh Browser Cleanup """ try: # Cleanup temp directory falls vorhanden if hasattr(self, '_temp_profile_cleanup') and self._temp_profile_cleanup: try: self._temp_profile_cleanup() logger.info("[FRESH BROWSER] Temp profile directory cleaned up") except Exception as e: logger.warning(f"[FRESH BROWSER] Cleanup-Warnung: {e}") # Standard Browser-Schließung super()._close_browser() except Exception as e: logger.error(f"[FRESH BROWSER] Fehler beim Browser-Cleanup: {e}") # Fallback: Force Browser schließen if self.browser: try: if hasattr(self.browser, 'context') and self.browser.context: self.browser.context.close() if hasattr(self.browser, 'browser') and self.browser.browser: self.browser.browser.close() if hasattr(self.browser, 'playwright') and self.browser.playwright: self.browser.playwright.stop() except Exception: pass finally: self.browser = None