""" Controller für TikTok-spezifische Funktionalität. Mit TextSimilarity-Integration für robusteres UI-Element-Matching. """ import time import random from PyQt5.QtCore import QThread, pyqtSignal, QObject from typing import Dict, Any from controllers.platform_controllers.base_controller import BasePlatformController from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread from views.tabs.generator_tab import GeneratorTab from views.tabs.accounts_tab import AccountsTab from views.tabs.settings_tab import SettingsTab from views.widgets.forge_animation_widget import ForgeAnimationDialog from social_networks.tiktok.tiktok_automation import TikTokAutomation from utils.text_similarity import TextSimilarity from utils.logger import setup_logger logger = setup_logger("tiktok_controller") # Legacy WorkerThread als Backup beibehalten class LegacyTikTokWorkerThread(QThread): """Legacy Thread für die TikTok-Account-Erstellung (Backup).""" # Signale update_signal = pyqtSignal(str) log_signal = pyqtSignal(str) progress_signal = pyqtSignal(int) finished_signal = pyqtSignal(dict) error_signal = pyqtSignal(str) def __init__(self, params): super().__init__() self.params = params self.running = True # TextSimilarity für robustes Fehler-Matching self.text_similarity = TextSimilarity(default_threshold=0.7) # Fehler-Patterns für robustes Fehler-Matching self.error_patterns = [ "Fehler", "Error", "Fehlgeschlagen", "Failed", "Problem", "Issue", "Nicht möglich", "Not possible", "Bitte versuchen Sie es erneut", "Please try again", "Konnte nicht", "Could not", "Timeout" ] def run(self): """Führt die Account-Erstellung aus.""" try: self.log_signal.emit("TikTok-Account-Erstellung gestartet...") self.progress_signal.emit(10) # TikTok-Automation initialisieren automation = TikTokAutomation( headless=self.params.get("headless", False), use_proxy=self.params.get("use_proxy", False), proxy_type=self.params.get("proxy_type"), save_screenshots=True, debug=self.params.get("debug", False), email_domain=self.params.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com") ) self.update_signal.emit("TikTok-Automation initialisiert") self.progress_signal.emit(20) # Account registrieren self.log_signal.emit(f"Registriere Account für: {self.params['full_name']}") # Account registrieren - immer mit Email result = automation.register_account( full_name=self.params["full_name"], age=self.params["age"], registration_method="email", # Immer Email-Registrierung phone_number=None, # Keine Telefonnummer **self.params.get("additional_params", {}) ) self.progress_signal.emit(100) if result["success"]: self.log_signal.emit("Account erfolgreich erstellt!") self.finished_signal.emit(result) else: # Robuste Fehlerbehandlung mit TextSimilarity error_msg = result.get("error", "Unbekannter Fehler") # Versuche, Fehler nutzerfreundlicher zu interpretieren user_friendly_error = self._interpret_error(error_msg) self.log_signal.emit(f"Fehler bei der Account-Erstellung: {user_friendly_error}") self.error_signal.emit(user_friendly_error) except Exception as e: logger.error(f"Fehler im Worker-Thread: {e}") self.log_signal.emit(f"Schwerwiegender Fehler: {str(e)}") self.error_signal.emit(str(e)) self.progress_signal.emit(0) def _interpret_error(self, error_msg: str) -> str: """ Interpretiert Fehlermeldungen und gibt eine benutzerfreundlichere Version zurück. Verwendet TextSimilarity für robusteres Fehler-Matching. Args: error_msg: Die ursprüngliche Fehlermeldung Returns: str: Benutzerfreundliche Fehlermeldung """ # Bekannte Fehlermuster und deren Interpretationen error_interpretations = { "captcha": "TikTok hat einen Captcha-Test angefordert. Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy.", "verification": "Es gab ein Problem mit der Verifizierung des Accounts. Bitte prüfen Sie die E-Mail-Einstellungen.", "proxy": "Problem mit der Proxy-Verbindung. Bitte prüfen Sie Ihre Proxy-Einstellungen.", "timeout": "Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie Ihre Internetverbindung.", "username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig.", "password": "Das Passwort erfüllt nicht die Anforderungen von TikTok.", "email": "Die E-Mail-Adresse konnte nicht verwendet werden. Bitte nutzen Sie eine andere E-Mail-Domain.", "phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden.", "age": "Das eingegebene Alter erfüllt nicht die Anforderungen von TikTok.", "too_many_attempts": "Zu viele Registrierungsversuche. Bitte warten Sie und versuchen Sie es später erneut." } # Versuche, den Fehler zu kategorisieren for pattern, interpretation in error_interpretations.items(): for error_term in self.error_patterns: if (pattern in error_msg.lower() or self.text_similarity.is_similar(error_term, error_msg, threshold=0.7)): return interpretation # Fallback: Originale Fehlermeldung zurückgeben return error_msg def stop(self): """Stoppt den Thread.""" self.running = False self.terminate() # Neue Implementation mit BaseWorkerThread class TikTokWorkerThread(BaseAccountCreationWorkerThread): """Refaktorierte TikTok Worker Thread Implementation""" def __init__(self, params, session_controller=None, generator_tab=None): super().__init__(params, "TikTok", session_controller, generator_tab) def get_automation_class(self): from social_networks.tiktok.tiktok_automation import TikTokAutomation return TikTokAutomation def get_error_interpretations(self) -> Dict[str, str]: return { "captcha": "TikTok hat einen Captcha-Test angefordert. Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy.", "verification": "Es gab ein Problem mit der Verifizierung des Accounts. Bitte prüfen Sie die E-Mail-Einstellungen.", "proxy": "Problem mit der Proxy-Verbindung. Bitte prüfen Sie Ihre Proxy-Einstellungen.", "timeout": "Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie Ihre Internetverbindung.", "username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig.", "password": "Das Passwort erfüllt nicht die Anforderungen von TikTok.", "email": "Die E-Mail-Adresse konnte nicht verwendet werden. Bitte nutzen Sie eine andere E-Mail-Domain.", "phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden.", "phone number required": "Telefonnummer erforderlich", "invalid code": "Ungültiger Verifizierungscode", "age": "Das eingegebene Alter erfüllt nicht die Anforderungen von TikTok.", "too_many_attempts": "Zu viele Registrierungsversuche. Bitte warten Sie und versuchen Sie es später erneut.", "rate limit": "Zu viele Versuche - bitte später erneut versuchen", "already taken": "Der gewählte Benutzername ist bereits vergeben", "weak password": "Das Passwort ist zu schwach", "network error": "Netzwerkfehler - bitte Internetverbindung prüfen" } class TikTokController(BasePlatformController): """Controller für TikTok-spezifische Funktionalität.""" def __init__(self, db_manager, proxy_rotator, email_handler, language_manager=None): super().__init__("TikTok", db_manager, proxy_rotator, email_handler, language_manager) self.worker_thread = None # TextSimilarity für robustes UI-Element-Matching self.text_similarity = TextSimilarity(default_threshold=0.75) def create_generator_tab(self): """Erstellt den TikTok-Generator-Tab.""" generator_tab = GeneratorTab(self.platform_name, self.language_manager) # TikTok-spezifische Anpassungen # Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden # Signale verbinden generator_tab.start_requested.connect(self.start_account_creation) generator_tab.stop_requested.connect(self.stop_account_creation) return generator_tab def start_account_creation(self, params): """Startet die TikTok-Account-Erstellung.""" super().start_account_creation(params) # Validiere Eingaben is_valid, error_msg = self.validate_inputs(params) if not is_valid: self.get_generator_tab().show_error(error_msg) return # UI aktualisieren generator_tab = self.get_generator_tab() generator_tab.set_running(True) generator_tab.clear_log() generator_tab.set_progress(0) # Schmiedeanimation-Dialog erstellen und anzeigen parent_widget = generator_tab.window() # Hauptfenster als Parent self.forge_dialog = ForgeAnimationDialog(parent_widget, "TikTok") self.forge_dialog.cancel_clicked.connect(self.stop_account_creation) self.forge_dialog.closed.connect(self.stop_account_creation) # Fensterposition vom Hauptfenster holen if parent_widget: window_pos = parent_widget.pos() params["window_position"] = (window_pos.x(), window_pos.y()) # Fingerprint VOR Account-Erstellung generieren try: from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService from domain.entities.browser_fingerprint import BrowserFingerprint import uuid fingerprint_service = FingerprintGeneratorService() # Generiere einen neuen Fingerprint für diesen Account fingerprint = fingerprint_service.generate_fingerprint() # Das ist bereits ein BrowserFingerprint-Objekt, kein Dict! fingerprint.fingerprint_id = str(uuid.uuid4()) fingerprint.account_bound = True fingerprint.rotation_seed = str(uuid.uuid4()) # Konvertiere zu Dictionary für Übertragung params["fingerprint"] = fingerprint.to_dict() logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}") except Exception as e: logger.error(f"Fehler beim Generieren des Fingerprints: {e}") # Fortfahren ohne Fingerprint - wird später generiert # Worker-Thread starten mit optionalen Parametern session_controller = getattr(self, 'session_controller', None) generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None self.worker_thread = TikTokWorkerThread( params, session_controller=session_controller, generator_tab=generator_tab_ref ) # Updates an Forge-Dialog weiterleiten self.worker_thread.update_signal.connect(self.forge_dialog.set_status) self.worker_thread.log_signal.connect(self.forge_dialog.add_log) self.worker_thread.error_signal.connect(self._handle_error) self.worker_thread.finished_signal.connect(self._handle_finished) self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress) # Auch an Generator-Tab für Backup self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg)) self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value)) self.worker_thread.start() # Dialog anzeigen und Animation starten self.forge_dialog.start_animation() self.forge_dialog.show() def stop_account_creation(self): """Stoppt die TikTok-Account-Erstellung.""" if self.worker_thread and self.worker_thread.isRunning(): self.worker_thread.stop() generator_tab = self.get_generator_tab() generator_tab.add_log("Account-Erstellung wurde abgebrochen") generator_tab.set_running(False) generator_tab.set_progress(0) # Forge-Dialog schließen falls vorhanden if hasattr(self, 'forge_dialog') and self.forge_dialog: self.forge_dialog.close() self.forge_dialog = None def handle_account_created(self, result): """Verarbeitet erfolgreich erstellte Accounts mit Clean Architecture.""" generator_tab = self.get_generator_tab() generator_tab.set_running(False) # Account-Daten aus dem Ergebnis holen account_data = result.get("account_data", {}) # Account und Session über SessionController speichern (Clean Architecture) if hasattr(self, 'session_controller') and self.session_controller: try: session_data = result.get("session_data", {}) save_result = self.session_controller.create_and_save_account( platform=self.platform_name, account_data=account_data ) if save_result.get('success'): logger.info(f"Account und Session erfolgreich gespeichert") # Erfolgsmeldung anzeigen (nur einmal!) account_info = save_result.get('account_data', {}) from PyQt5.QtWidgets import QMessageBox QMessageBox.information( generator_tab, "Erfolg", f"Account erfolgreich erstellt!\n\n" f"Benutzername: {account_info.get('username', '')}\n" f"Passwort: {account_info.get('password', '')}\n" f"E-Mail/Telefon: {account_info.get('email') or account_info.get('phone', '')}" ) # Signal senden, um zur Hauptseite zurückzukehren if hasattr(self, 'return_to_main_requested') and callable(self.return_to_main_requested): self.return_to_main_requested() else: error_msg = save_result.get('message', 'Unbekannter Fehler') logger.error(f"Fehler beim Speichern: {error_msg}") from views.widgets.modern_message_box import show_error show_error( generator_tab, "Fehler beim Speichern", f"Beim Speichern des Accounts ist ein Fehler aufgetreten:\n\n{error_msg}" ) except Exception as e: logger.error(f"Fehler beim Speichern des Accounts: {e}") from views.widgets.modern_message_box import show_critical show_critical( generator_tab, "Unerwarteter Fehler", f"Ein unerwarteter Fehler ist beim Speichern des Accounts aufgetreten:\n\n{str(e)}" ) else: # Fallback: Alte Methode falls SessionController nicht verfügbar logger.warning("SessionController nicht verfügbar, verwende alte Methode") generator_tab.account_created.emit(self.platform_name, account_data) if hasattr(self, 'return_to_main_requested') and callable(self.return_to_main_requested): self.return_to_main_requested() # save_account_to_db wurde entfernt - Accounts werden jetzt über SessionController gespeichert def validate_inputs(self, inputs): """ Validiert die Eingaben für die Account-Erstellung. Verwendet TextSimilarity für robustere Validierung. """ # Basis-Validierungen von BasePlatformController verwenden valid, error_msg = super().validate_inputs(inputs) if not valid: return valid, error_msg # TikTok-spezifische Validierungen age = inputs.get("age", 0) if age < 13: return False, "Das Alter muss mindestens 13 sein (TikTok-Anforderung)." # E-Mail-Domain-Validierung (immer Email-Registrierung) email_domain = inputs.get("email_domain", "") # Blacklist von bekannten problematischen Domains blacklisted_domains = ["temp-mail.org", "guerrillamail.com", "maildrop.cc"] # Prüfe mit TextSimilarity auf Ähnlichkeit mit Blacklist for domain in blacklisted_domains: if self.text_similarity.is_similar(email_domain, domain, threshold=0.8): return False, f"Die E-Mail-Domain '{email_domain}' kann problematisch für die TikTok-Registrierung sein. Bitte verwenden Sie eine andere Domain." return True, "" def get_form_field_label(self, field_type: str) -> str: """ Gibt einen Label-Text für ein Formularfeld basierend auf dem Feldtyp zurück. Args: field_type: Typ des Formularfelds Returns: str: Label-Text für das Formularfeld """ # Mapping von Feldtypen zu Labels field_labels = { "full_name": "Vollständiger Name", "username": "Benutzername", "password": "Passwort", "email": "E-Mail-Adresse", "phone": "Telefonnummer", "age": "Alter", "birthday": "Geburtsdatum" } return field_labels.get(field_type, field_type.capitalize()) def _handle_error(self, error_msg: str): """Behandelt Fehler während der Account-Erstellung""" # Forge-Dialog schließen if hasattr(self, 'forge_dialog') and self.forge_dialog: self.forge_dialog.close() self.forge_dialog = None # Fehler anzeigen generator_tab = self.get_generator_tab() generator_tab.show_error(error_msg) generator_tab.set_running(False) def _handle_finished(self, result: dict): """Behandelt das Ende der Account-Erstellung""" # Forge-Dialog schließen if hasattr(self, 'forge_dialog') and self.forge_dialog: self.forge_dialog.close() self.forge_dialog = None # Normale Verarbeitung self.handle_account_created(result)