Files
AccountForger-neuerUpload/controllers/platform_controllers/base_worker_thread.py
2025-11-27 21:17:32 +01:00

307 Zeilen
14 KiB
Python

# -*- 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