# -*- coding: utf-8 -*- """ Basis-Klasse für alle Platform Worker Threads zur Eliminierung von Code-Duplikation """ from abc import ABC, abstractmethod from PyQt5.QtCore import QThread, pyqtSignal from typing import Dict, Any, Optional from utils.text_similarity import TextSimilarity from domain.value_objects.browser_protection_style import BrowserProtectionStyle, ProtectionLevel import traceback class BaseAccountCreationWorkerThread(QThread): """Basis-Klasse für alle Platform Worker Threads""" # Signals MÜSSEN identisch zu bestehenden sein update_signal = pyqtSignal(str) log_signal = pyqtSignal(str) progress_signal = pyqtSignal(int) finished_signal = pyqtSignal(dict) error_signal = pyqtSignal(str) def __init__(self, params: Dict[str, Any], platform_name: str, session_controller: Optional[Any] = None, generator_tab: Optional[Any] = None): super().__init__() self.params = params self.platform_name = platform_name self.session_controller = session_controller self.generator_tab = generator_tab self.running = True # TextSimilarity für robustes Fehler-Matching self.text_similarity = TextSimilarity(default_threshold=0.8) # Platform-spezifische Error-Patterns (überschreibbar) self.error_interpretations = self.get_error_interpretations() @abstractmethod def get_automation_class(self): """Muss von Subklassen implementiert werden""" pass @abstractmethod def get_error_interpretations(self) -> Dict[str, str]: """Platform-spezifische Fehlerinterpretationen""" pass def run(self): """Gemeinsame Logik für Account-Erstellung - IDENTISCH zum Original""" # Feature 5: Tracke Erfolg für Process Guard success = False try: self.update_signal.emit("Status: Initialisierung...") self.log_signal.emit(f"{self.platform_name}-Account-Erstellung gestartet...") self.progress_signal.emit(10) # Automation-Klasse dynamisch laden AutomationClass = self.get_automation_class() # WICHTIG: Exakt gleiche Parameter wie im Original # Prüfe ob die Automation-Klasse die neuen Parameter unterstützt automation_params = { # Unterstütze beide Parameter-Namen für Abwärtskompatibilität "headless": self.params.get("headless", not self.params.get("show_browser", False)), "proxy_type": self.params.get("proxy_type", "NoProxy") } # Optionale Parameter nur hinzufügen wenn unterstützt import inspect init_signature = inspect.signature(AutomationClass.__init__) param_names = list(init_signature.parameters.keys()) if "fingerprint" in param_names: fingerprint_data = self.params.get("fingerprint") # Handle BrowserFingerprint object vs dict if fingerprint_data and hasattr(fingerprint_data, 'to_dict'): automation_params["fingerprint"] = fingerprint_data.to_dict() else: automation_params["fingerprint"] = fingerprint_data if "imap_handler" in param_names: automation_params["imap_handler"] = self.params.get("imap_handler") if "phone_service" in param_names: automation_params["phone_service"] = self.params.get("phone_service") if "use_proxy" in param_names: automation_params["use_proxy"] = self.params.get("use_proxy", False) if "save_screenshots" in param_names: automation_params["save_screenshots"] = True if "debug" in param_names: automation_params["debug"] = self.params.get("debug", False) if "email_domain" in param_names: automation_params["email_domain"] = self.params.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com") if "window_position" in param_names: automation_params["window_position"] = self.params.get("window_position") automation = AutomationClass(**automation_params) # Setze Callback für kundenfreundliche Logs automation.set_customer_log_callback(lambda msg: self.log_signal.emit(msg)) # Setze zusätzliche Callbacks für Facebook-Kompatibilität # Facebook verwendet _send_status_update und _send_log_update if hasattr(automation, 'status_update_callback'): automation.status_update_callback = lambda msg: self.update_signal.emit(msg) if hasattr(automation, 'log_update_callback'): automation.log_update_callback = lambda msg: self.log_signal.emit(msg) self.update_signal.emit(f"{self.platform_name}-Automation initialisiert") self.progress_signal.emit(20) # Browser-Schutz wird jetzt direkt in base_automation.py nach Browser-Start angewendet # Account registrieren self.log_signal.emit(f"Registriere Account: {self.params['full_name']}") # Account registrieren mit allen Original-Parametern # Erstelle saubere Parameter für register_account register_params = { "full_name": self.params["full_name"], "age": self.params["age"], "registration_method": self.params.get("registration_method", "email"), "email_domain": self.params.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com") } # Füge optionale Parameter hinzu wenn vorhanden if "phone_number" in self.params: register_params["phone_number"] = self.params["phone_number"] # Gender für Facebook (optional) if "gender" in self.params: register_params["gender"] = self.params["gender"] # Additional params separat behandeln if "additional_params" in self.params: register_params.update(self.params["additional_params"]) result = automation.register_account(**register_params) if result["success"]: # Stelle sicher, dass die Datenstruktur kompatibel ist if "account_data" not in result: # Wenn account_data nicht existiert, erstelle es aus den Top-Level-Feldern result["account_data"] = { "username": result.get("username", ""), "password": result.get("password", ""), "email": result.get("email", ""), "phone": result.get("phone", "") } # KRITISCHE VALIDIERUNG: Prüfe PFLICHTFELDER vor Weitergabe account_data = result.get("account_data", {}) validation_errors = [] # Prüfe Username if not account_data.get("username") or account_data["username"] == "": validation_errors.append("Username fehlt oder ist leer") # Prüfe Password if not account_data.get("password") or account_data["password"] == "": validation_errors.append("Passwort fehlt oder ist leer") # Prüfe Email oder Phone (mindestens eins muss vorhanden sein) if not account_data.get("email") and not account_data.get("phone"): validation_errors.append("Weder E-Mail noch Telefon vorhanden") # Wenn Validierungsfehler existieren, markiere als Fehler if validation_errors: error_msg = f"Account-Daten unvollständig: {', '.join(validation_errors)}" self.log_signal.emit(f"VALIDIERUNGSFEHLER: {error_msg}") self.log_signal.emit(f"Account-Daten: {account_data}") self.error_signal.emit(error_msg) self.progress_signal.emit(0) return # Breche ab - sende KEIN finished_signal fingerprint_data = self.params.get("fingerprint") # Handle BrowserFingerprint object vs dict if fingerprint_data and hasattr(fingerprint_data, 'to_dict'): result["fingerprint"] = fingerprint_data.to_dict() else: result["fingerprint"] = fingerprint_data self.log_signal.emit("Account erfolgreich erstellt!") self.progress_signal.emit(100) # Session-Speicherung vor Benachrichtigung durchführen save_result = self._save_session_if_available(result) if save_result is not None: result["save_result"] = save_result # Feature 5: Markiere als erfolgreich für Process Guard success = True self.finished_signal.emit(result) else: error_msg = result.get("error", "Unbekannter Fehler") interpreted_error = self._interpret_error(error_msg) self.log_signal.emit(f"Fehler bei der Account-Erstellung: {interpreted_error}") self.error_signal.emit(interpreted_error) self.progress_signal.emit(0) # Reset progress on error except Exception as e: error_msg = str(e) self.log_signal.emit(f"Schwerwiegender Fehler: {error_msg}") self.log_signal.emit(traceback.format_exc()) interpreted_error = self._interpret_error(error_msg) self.error_signal.emit(interpreted_error) self.progress_signal.emit(0) # Reset progress on error finally: # Feature 5: Process Guard freigeben from utils.process_guard import get_guard guard = get_guard() guard.end(success) def _interpret_error(self, error_message: str) -> str: """Interpretiert Fehler mit Fuzzy-Matching""" error_lower = error_message.lower() for pattern, interpretation in self.error_interpretations.items(): if pattern in error_lower or self.text_similarity.is_similar(pattern, error_lower, threshold=0.8): return interpretation return f"Fehler bei der Registrierung: {error_message}" def _save_session_if_available(self, result: Dict[str, Any]) -> Optional[Dict[str, Any]]: """Speichert Session wenn Controller verfügbar und gibt das Ergebnis zurück""" save_result: Optional[Dict[str, Any]] = None # Session über SessionController speichern wenn verfügbar if hasattr(self, 'session_controller') and self.session_controller: try: if hasattr(self.session_controller, 'create_and_save_account'): # Account-Daten aus dem korrekten Pfad extrahieren if "account_data" in result: account_data = result["account_data"] else: account_data = { 'username': result.get("username"), 'password': result.get("password"), 'email': result.get("email"), 'phone': result.get("phone") } save_result = self.session_controller.create_and_save_account( platform=self.platform_name, account_data=account_data ) if save_result.get('success'): self.log_signal.emit("Session erfolgreich gespeichert") else: self.log_signal.emit("Warnung: Session konnte nicht gespeichert werden") except Exception as e: error_msg = f"Warnung: Session konnte nicht gespeichert werden: {e}" self.log_signal.emit(error_msg) save_result = { 'success': False, 'error': str(e), 'message': error_msg } # Alternativ: Signal an Generator Tab senden elif hasattr(self, 'generator_tab') and self.generator_tab: try: if hasattr(self.generator_tab, 'account_created'): if "account_data" in result: account_data = result["account_data"] else: account_data = { 'username': result.get("username"), 'password': result.get("password"), 'email': result.get("email"), 'phone': result.get("phone") } self.generator_tab.account_created.emit(self.platform_name, account_data) except Exception as e: self.log_signal.emit(f"Warnung: Konnte Account-Daten nicht an UI senden: {e}") return save_result def stop(self): """ Stoppt den Thread sauber mit Guard-Freigabe. WICHTIG: Guard wird SOFORT freigegeben, da terminate() den finally-Block überspringt. """ import logging logger = logging.getLogger(__name__) self.running = False # Guard SOFORT freigeben bevor terminate() # Grund: terminate() überspringt den finally-Block in run() from utils.process_guard import get_guard guard = get_guard() if guard.is_locked(): guard.end(success=False) logger.info("Guard freigegeben bei Worker-Stop (vor terminate)") # Jetzt Thread beenden self.terminate() self.wait(2000) # Warte max 2 Sekunden auf sauberes Ende