# social_networks/tiktok/tiktok_registration_new.py """ TikTok-Registrierung - Optimierte Klasse für die Kontoerstellung bei TikTok NEUE IMPLEMENTIERUNG mit korrekter Workflow-Reihenfolge für maximale Stabilität. OPTIMIERTER WORKFLOW: 1. E-Mail eingeben 2. Passwort eingeben 3. Code senden Button klicken 4. Code empfangen und eingeben 5. Weiter Button wird automatisch aktiviert """ import time import random import re from typing import Dict, List, Any, Optional, Tuple from .tiktok_selectors import TikTokSelectors from .tiktok_workflow import TikTokWorkflow from utils.logger import setup_logger # Konfiguriere Logger logger = setup_logger("tiktok_registration") class TikTokRegistration: """ Optimierte Klasse für die Registrierung von TikTok-Konten. Implementiert einen robusten, zukunftssicheren Workflow. """ def __init__(self, automation): """ Initialisiert die TikTok-Registrierung. Args: automation: Referenz auf die Hauptautomatisierungsklasse """ self.automation = automation self.selectors = TikTokSelectors() self.workflow = TikTokWorkflow.get_registration_workflow() logger.debug("TikTok-Registrierung initialisiert") def register_account(self, full_name: str, age: int, registration_method: str = "email", phone_number: str = None, **kwargs) -> Dict[str, Any]: """ Führt den vollständigen Registrierungsprozess für einen TikTok-Account durch. Args: full_name: Vollständiger Name für den Account age: Alter des Benutzers registration_method: "email" oder "phone" phone_number: Telefonnummer (nur bei registration_method="phone") **kwargs: Weitere optionale Parameter Returns: Dict[str, Any]: Ergebnis der Registrierung mit Status und Account-Daten """ # Validiere die Eingaben if not self._validate_registration_inputs(full_name, age, registration_method, phone_number): return { "success": False, "error": "Ungültige Eingabeparameter", "stage": "input_validation" } # Account-Daten generieren account_data = self._generate_account_data(full_name, age, registration_method, phone_number, **kwargs) # Starte den Registrierungsprozess logger.info(f"Starte optimierten TikTok-Registrierungsprozess für {account_data['username']} via {registration_method}") try: # 1. Zur Startseite navigieren self.automation._emit_customer_log("🌐 Mit TikTok verbinden...") if not self._navigate_to_homepage(): return self._create_error_result("Konnte nicht zur TikTok-Startseite navigieren", "navigation", account_data) # 2. Cookie-Banner behandeln self.automation._emit_customer_log("⚙️ Einstellungen werden vorbereitet...") self._handle_cookie_banner() # 3. Anmelden-Button klicken self.automation._emit_customer_log("📋 Registrierungsformular wird geöffnet...") if not self._click_login_button(): return self._create_error_result("Konnte nicht auf Anmelden-Button klicken", "login_button", account_data) # 4. Registrieren-Link klicken if not self._click_register_link(): return self._create_error_result("Konnte nicht auf Registrieren-Link klicken", "register_link", account_data) # 5. Telefon/E-Mail-Option auswählen if not self._click_phone_email_option(): return self._create_error_result("Konnte nicht auf Telefon/E-Mail-Option klicken", "phone_email_option", account_data) # 6. E-Mail oder Telefon als Registrierungsmethode wählen if not self._select_registration_method(registration_method): return self._create_error_result(f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen", "registration_method", account_data) # 7. Geburtsdatum eingeben self.automation._emit_customer_log("🎂 Geburtsdatum wird festgelegt...") if not self._enter_birthday(account_data["birthday"]): return self._create_error_result("Fehler beim Eingeben des Geburtsdatums", "birthday", account_data) # 8. OPTIMIERTER REGISTRIERUNGSWORKFLOW self.automation._emit_customer_log("📝 Persönliche Daten werden übertragen...") if not self._execute_optimized_registration_workflow(account_data, registration_method): return self._create_error_result("Fehler im Registrierungsworkflow", "registration_workflow", account_data) # 9. Benutzernamen erstellen self.automation._emit_customer_log("👤 Benutzername wird erstellt...") if not self._create_username(account_data): return self._create_error_result("Fehler beim Erstellen des Benutzernamens", "username", account_data) # 10. Erfolgreiche Registrierung überprüfen self.automation._emit_customer_log("🔍 Account wird finalisiert...") if not self._check_registration_success(): return self._create_error_result("Registrierung fehlgeschlagen oder konnte nicht verifiziert werden", "final_check", account_data) # Registrierung erfolgreich abgeschlossen logger.info(f"TikTok-Account {account_data['username']} erfolgreich erstellt") self.automation._emit_customer_log("✅ Account erfolgreich erstellt!") return { "success": True, "stage": "completed", "account_data": account_data } except Exception as e: error_msg = f"Unerwarteter Fehler bei der TikTok-Registrierung: {str(e)}" logger.error(error_msg, exc_info=True) return { "success": False, "error": error_msg, "stage": "exception", "account_data": account_data } def _execute_optimized_registration_workflow(self, account_data: Dict[str, Any], registration_method: str) -> bool: """ Führt den optimierten Registrierungsworkflow aus. KORRIGIERTE REIHENFOLGE für E-Mail-Registrierung: 1. E-Mail eingeben 2. Code senden Button klicken 3. Code empfangen und eingeben 4. Passwort eingeben 5. Dummy-Input-Trick anwenden 6. Weiter Button klicken Args: account_data: Account-Daten registration_method: "email" oder "phone" Returns: bool: True bei Erfolg, False bei Fehler """ try: if registration_method == "email": return self._execute_email_workflow(account_data) elif registration_method == "phone": return self._execute_phone_workflow(account_data) else: logger.error(f"Unbekannte Registrierungsmethode: {registration_method}") return False except Exception as e: logger.error(f"Fehler im optimierten Registrierungsworkflow: {e}") return False def _execute_email_workflow(self, account_data: Dict[str, Any]) -> bool: """ Führt den optimierten E-Mail-Registrierungsworkflow aus. KORRIGIERTER WORKFLOW: E-Mail → Code senden → Code eingeben → Passwort → Dummy-Trick → Weiter Args: account_data: Account-Daten Returns: bool: True bei Erfolg, False bei Fehler """ try: logger.info("=== STARTE OPTIMIERTEN E-MAIL-WORKFLOW ===") # SCHRITT 1: E-Mail-Feld ausfüllen logger.info("SCHRITT 1/6: E-Mail-Adresse eingeben") if not self._fill_email_field(account_data["email"]): logger.error("Fehler beim Ausfüllen des E-Mail-Feldes") return False # SCHRITT 2: Code senden Button klicken (VOR Passwort!) logger.info("SCHRITT 2/6: Code senden Button klicken") if not self._click_send_code_button(): logger.error("Fehler beim Klicken des Code-senden-Buttons") return False # SCHRITT 3: Verifizierungscode empfangen und eingeben logger.info("SCHRITT 3/6: Auf Code warten und eingeben") if not self._handle_email_verification(account_data["email"]): logger.error("Fehler bei der E-Mail-Verifizierung") return False # SCHRITT 4: Passwort-Feld ausfüllen (NACH Code-Eingabe!) logger.info("SCHRITT 4/6: Passwort eingeben (nach Code-Verifizierung)") if not self._fill_password_field(account_data["password"]): logger.error("Fehler beim Ausfüllen des Passwort-Feldes") return False # SCHRITT 5: Dummy-Input-Trick anwenden logger.info("SCHRITT 5/6: Dummy-Input-Trick anwenden") if not self._apply_dummy_input_trick(): logger.error("Fehler beim Dummy-Input-Trick") return False # SCHRITT 6: Weiter Button klicken logger.info("SCHRITT 6/6: Weiter Button klicken") if not self._click_continue_button(): logger.error("Fehler beim Klicken des Weiter-Buttons") return False logger.info("=== E-MAIL-WORKFLOW ERFOLGREICH ABGESCHLOSSEN ===") # Kurze Pause für UI-Updates - das Weiter-Button sollte jetzt aktiviert sein self.automation.human_behavior.random_delay(1.0, 2.0) return True except Exception as e: logger.error(f"Fehler im E-Mail-Workflow: {e}") return False def _fill_email_field(self, email: str) -> bool: """ Füllt das E-Mail-Feld mit robusten Selektoren aus. Args: email: E-Mail-Adresse Returns: bool: True bei Erfolg, False bei Fehler """ try: # Robuste E-Mail-Feld-Selektoren (in Prioritätsreihenfolge) email_selectors = [ "input[placeholder*='E-Mail']", "input[placeholder*='Email']", "input[type='email']", "input[name='email']", "input[aria-label*='Email']", "input[aria-label*='E-Mail']", self.selectors.EMAIL_FIELD, self.selectors.EMAIL_FIELD_ALT ] for i, selector in enumerate(email_selectors): try: if self.automation.browser.is_element_visible(selector, timeout=2000): success = self.automation.browser.fill_form_field(selector, email, human_typing=True) if success: logger.info(f"E-Mail-Feld erfolgreich ausgefüllt mit Selektor {i+1}: {email}") self.automation.human_behavior.random_delay(0.5, 1.0) return True except Exception as e: logger.debug(f"E-Mail-Selektor {i+1} fehlgeschlagen: {e}") continue # Fallback: Fuzzy-Matching success = self.automation.ui_helper.fill_field_fuzzy( ["E-Mail-Adresse", "Email", "E-Mail"], email, email_selectors[0] ) if success: logger.info(f"E-Mail-Feld über Fuzzy-Matching ausgefüllt: {email}") return True logger.error("Konnte E-Mail-Feld mit keinem Selektor ausfüllen") return False except Exception as e: logger.error(f"Fehler beim Ausfüllen des E-Mail-Feldes: {e}") return False def _fill_password_field(self, password: str) -> bool: """ Füllt das Passwort-Feld mit robusten Selektoren aus. Args: password: Passwort Returns: bool: True bei Erfolg, False bei Fehler """ try: # Robuste Passwort-Feld-Selektoren (in Prioritätsreihenfolge) password_selectors = [ "input[type='password'][placeholder*='Passwort']", "input[type='password'][placeholder*='Password']", "input[type='password']", "input[name='password']", "input[aria-label*='Password']", "input[aria-label*='Passwort']", self.selectors.PASSWORD_FIELD, self.selectors.PASSWORD_FIELD_ALT ] for i, selector in enumerate(password_selectors): try: if self.automation.browser.is_element_visible(selector, timeout=2000): success = self.automation.browser.fill_form_field(selector, password, human_typing=True) if success: logger.info(f"Passwort-Feld erfolgreich ausgefüllt mit Selektor {i+1}") self.automation.human_behavior.random_delay(0.5, 1.0) return True except Exception as e: logger.debug(f"Passwort-Selektor {i+1} fehlgeschlagen: {e}") continue # Fallback: Fuzzy-Matching success = self.automation.ui_helper.fill_field_fuzzy( ["Passwort", "Password"], password, password_selectors[0] ) if success: logger.info("Passwort-Feld über Fuzzy-Matching ausgefüllt") return True logger.error("Konnte Passwort-Feld mit keinem Selektor ausfüllen") return False except Exception as e: logger.error(f"Fehler beim Ausfüllen des Passwort-Feldes: {e}") return False def _click_send_code_button(self) -> bool: """ Klickt den 'Code senden'-Button mit robusten Selektoren. Returns: bool: True bei Erfolg, False bei Fehler """ try: # Kurze Pause vor dem Klicken self.automation.human_behavior.random_delay(0.5, 1.0) # Robuste Send-Code-Button-Selektoren send_code_selectors = [ "button[data-e2e='send-code-button']", "button:has-text('Code senden')", "button:has-text('Send code')", "button[type='submit']", "button.css-10nhlj9-Button-StyledButton", self.selectors.SEND_CODE_BUTTON ] for i, selector in enumerate(send_code_selectors): try: if self.automation.browser.is_element_visible(selector, timeout=2000): # Prüfe, ob Button enabled ist element = self.automation.browser.wait_for_selector(selector, timeout=1000) if element: is_disabled = element.get_attribute("disabled") if is_disabled: logger.debug(f"Send-Code-Button {i+1} ist disabled, versuche nächsten") continue success = self.automation.browser.click_element(selector) if success: logger.info(f"'Code senden'-Button erfolgreich geklickt mit Selektor {i+1}") self.automation.human_behavior.random_delay(1.0, 2.0) return True except Exception as e: logger.debug(f"Send-Code-Selektor {i+1} fehlgeschlagen: {e}") continue # Fallback: Fuzzy-Button-Matching success = self.automation.ui_helper.click_button_fuzzy( ["Code senden", "Send code", "Senden"], send_code_selectors[0] ) if success: logger.info("'Code senden'-Button über Fuzzy-Matching geklickt") return True logger.error("Konnte 'Code senden'-Button mit keinem Selektor klicken") return False except Exception as e: logger.error(f"Fehler beim Klicken des 'Code senden'-Buttons: {e}") return False def _handle_email_verification(self, email: str) -> bool: """ Behandelt die E-Mail-Verifizierung mit verbessertem Timing. Args: email: E-Mail-Adresse Returns: bool: True bei Erfolg, False bei Fehler """ try: logger.info("Warte auf E-Mail-Verifizierungscode...") # Warte auf den Code mit exponential backoff verification_code = self._get_email_verification_code_with_retry(email) if not verification_code: logger.error("Konnte keinen Verifizierungscode empfangen") return False logger.info(f"Verifizierungscode empfangen: {verification_code}") # Code-Feld ausfüllen if not self._fill_verification_code_field(verification_code): logger.error("Konnte Verifizierungscode-Feld nicht ausfüllen") return False logger.info("Verifizierungscode erfolgreich eingegeben") # Kurze Pause nach Code-Eingabe self.automation.human_behavior.random_delay(1.0, 2.0) return True except Exception as e: logger.error(f"Fehler bei der E-Mail-Verifizierung: {e}") return False def _get_email_verification_code_with_retry(self, email: str, max_attempts: int = 30) -> Optional[str]: """ Ruft den E-Mail-Verifizierungscode mit Retry-Logik ab. Args: email: E-Mail-Adresse max_attempts: Maximale Anzahl Versuche Returns: Optional[str]: Verifizierungscode oder None """ try: for attempt in range(max_attempts): # Exponential backoff: 2s, 3s, 4.5s, 6.75s, ... (max 30s) delay = min(2 * (1.5 ** attempt), 30) logger.debug(f"E-Mail-Abruf Versuch {attempt + 1}/{max_attempts} (Wartezeit: {delay:.1f}s)") # Versuche Code abzurufen code = self.automation.email_handler.get_verification_code( target_email=email, platform="tiktok", max_attempts=1, # Nur ein Versuch pro Iteration delay_seconds=1 ) if code: logger.info(f"E-Mail-Code nach {attempt + 1} Versuchen empfangen") return code # Warte vor nächstem Versuch time.sleep(delay) logger.warning(f"Kein E-Mail-Code nach {max_attempts} Versuchen empfangen") return None except Exception as e: logger.error(f"Fehler beim E-Mail-Code-Abruf: {e}") return None def _fill_verification_code_field(self, code: str) -> bool: """ Füllt das Verifizierungscode-Feld aus. Args: code: Verifizierungscode Returns: bool: True bei Erfolg, False bei Fehler """ try: # Robuste Verifizierungscode-Feld-Selektoren code_selectors = [ "input[placeholder*='sechsstelligen Code']", "input[placeholder*='verification code']", "input[placeholder*='Code']", "input[name='verificationCode']", "input[type='text'][maxlength='6']", self.selectors.VERIFICATION_CODE_FIELD, self.selectors.VERIFICATION_CODE_FIELD_ALT ] for i, selector in enumerate(code_selectors): try: if self.automation.browser.is_element_visible(selector, timeout=3000): # Normale Code-Eingabe (Dummy-Trick wird separat angewendet) success = self.automation.browser.fill_form_field(selector, code, human_typing=True) if success: logger.info(f"Verifizierungscode-Feld erfolgreich ausgefüllt mit Selektor {i+1}") return True except Exception as e: logger.debug(f"Code-Selektor {i+1} fehlgeschlagen: {e}") continue # Fallback: Fuzzy-Matching success = self.automation.ui_helper.fill_field_fuzzy( ["Gib den sechsstelligen Code ein", "Enter verification code", "Verification code"], code, code_selectors[0] ) if success: logger.info("Verifizierungscode-Feld über Fuzzy-Matching ausgefüllt") return True logger.error("Konnte Verifizierungscode-Feld mit keinem Selektor ausfüllen") return False except Exception as e: logger.error(f"Fehler beim Ausfüllen des Verifizierungscode-Feldes: {e}") return False def _execute_phone_workflow(self, account_data: Dict[str, Any]) -> bool: """ Führt den Telefon-Registrierungsworkflow aus. Args: account_data: Account-Daten Returns: bool: True bei Erfolg, False bei Fehler """ try: logger.info("=== STARTE TELEFON-WORKFLOW ===") # Telefonnummer aufbereiten phone_number = account_data["phone"] if phone_number.startswith("+"): parts = phone_number.split(" ", 1) if len(parts) > 1: phone_number = parts[1] # Telefonnummer eingeben phone_success = self.automation.ui_helper.fill_field_fuzzy( ["Telefonnummer", "Phone number", "Phone"], phone_number, self.selectors.PHONE_FIELD ) if not phone_success: logger.error("Konnte Telefonnummer-Feld nicht ausfüllen") return False logger.info(f"Telefonnummer-Feld ausgefüllt: {phone_number}") self.automation.human_behavior.random_delay(0.5, 1.5) # Code senden if not self._click_send_code_button(): return False # SMS-Code behandeln (Platzhalter) logger.warning("SMS-Verifizierung ist noch nicht vollständig implementiert") return True except Exception as e: logger.error(f"Fehler im Telefon-Workflow: {e}") return False # Hilfsmethoden für die Basis-Funktionalität def _validate_registration_inputs(self, full_name: str, age: int, registration_method: str, phone_number: str) -> bool: """Validiert die Eingaben für die Registrierung.""" if not full_name or len(full_name) < 3: logger.error("Ungültiger vollständiger Name") return False if age < 13: logger.error("Benutzer muss mindestens 13 Jahre alt sein") return False if registration_method not in ["email", "phone"]: logger.error(f"Ungültige Registrierungsmethode: {registration_method}") return False if registration_method == "phone" and not phone_number: logger.error("Telefonnummer erforderlich für Registrierung via Telefon") return False return True def _generate_account_data(self, full_name: str, age: int, registration_method: str, phone_number: str, **kwargs) -> Dict[str, Any]: """Generiert Account-Daten für die Registrierung.""" # Benutzername generieren username = kwargs.get("username") if not username: username = self.automation.username_generator.generate_username("tiktok", full_name) # Passwort generieren password = kwargs.get("password") if not password: password = self.automation.password_generator.generate_password("tiktok") # E-Mail generieren (falls nötig) email = None if registration_method == "email": email_prefix = username.lower().replace(".", "").replace("_", "") email = f"{email_prefix}@{self.automation.email_domain}" # Geburtsdatum generieren birthday = self.automation.birthday_generator.generate_birthday_components("tiktok", age) # Account-Daten zusammenstellen account_data = { "username": username, "password": password, "full_name": full_name, "email": email, "phone": phone_number, "birthday": birthday, "age": age, "registration_method": registration_method } logger.debug(f"Account-Daten generiert: {account_data['username']}") return account_data def _create_error_result(self, error_msg: str, stage: str, account_data: Dict[str, Any]) -> Dict[str, Any]: """Erstellt ein standardisiertes Fehler-Result.""" return { "success": False, "error": error_msg, "stage": stage, "account_data": account_data } # Platzhalter für weitere Methoden (Navigation, etc.) def _navigate_to_homepage(self) -> bool: """Navigiert zur TikTok-Startseite.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _handle_cookie_banner(self) -> bool: """Behandelt den Cookie-Banner.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _click_login_button(self) -> bool: """Klickt auf den Anmelden-Button.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _click_register_link(self) -> bool: """Klickt auf den Registrieren-Link.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _click_phone_email_option(self) -> bool: """Klickt auf die Telefon/E-Mail-Option.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _select_registration_method(self, method: str) -> bool: """Wählt die Registrierungsmethode aus.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _enter_birthday(self, birthday: Dict[str, Any]) -> bool: """Gibt das Geburtsdatum ein.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _create_username(self, account_data: Dict[str, Any]) -> bool: """Erstellt einen Benutzernamen.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _check_registration_success(self) -> bool: """Überprüft, ob die Registrierung erfolgreich war.""" # Diese Methode würde aus der ursprünglichen Implementierung übernommen return True def _apply_dummy_input_trick(self) -> bool: """ Wendet den Dummy-Input-Trick auf das Code-Feld an. Returns: bool: True bei Erfolg, False bei Fehler """ try: logger.debug("Wende Dummy-Input-Trick an") # Code-Feld-Selektoren code_selectors = [ "input[placeholder*='sechsstelligen Code']", "input[placeholder*='verification code']", "input[placeholder*='Code']", "input[name='verificationCode']", "input[type='text'][maxlength='6']", self.selectors.VERIFICATION_CODE_FIELD, self.selectors.VERIFICATION_CODE_FIELD_ALT ] for i, selector in enumerate(code_selectors): try: if self.automation.browser.is_element_visible(selector, timeout=2000): # Dummy-Input-Trick anwenden success = self.automation.browser.fill_form_field_with_dummy_trick( selector, "123456", timeout=3000 ) if success: logger.info(f"Dummy-Input-Trick erfolgreich angewendet mit Selektor {i+1}") self.automation.human_behavior.random_delay(0.5, 1.0) return True except Exception as e: logger.debug(f"Dummy-Input-Trick Selektor {i+1} fehlgeschlagen: {e}") continue logger.warning("Dummy-Input-Trick konnte nicht angewendet werden") return True # Nicht kritisch - fortfahren except Exception as e: logger.error(f"Kritischer Fehler beim Dummy-Input-Trick: {e}") return True # Nicht kritisch - fortfahren def _click_continue_button(self) -> bool: """ Klickt den Weiter/Continue-Button mit robusten Selektoren. Returns: bool: True bei Erfolg, False bei Fehler """ try: logger.debug("Klicke Weiter-Button") # Robuste Continue-Button-Selektoren continue_selectors = [ "button[data-e2e='continue-button']", "button:has-text('Weiter')", "button:has-text('Continue')", "button:has-text('Fortfahren')", "button[type='submit']", "button.css-10nhlj9-Button-StyledButton:not([disabled])" ] for i, selector in enumerate(continue_selectors): try: if self.automation.browser.is_element_visible(selector, timeout=3000): # Prüfe, ob Button enabled ist element = self.automation.browser.wait_for_selector(selector, timeout=1000) if element: is_disabled = element.get_attribute("disabled") aria_disabled = element.get_attribute("aria-disabled") if is_disabled or aria_disabled == "true": logger.debug(f"Continue-Button {i+1} ist disabled, versuche nächsten") continue # Button klicken success = self.automation.browser.click_element(selector) if success: logger.info(f"Weiter-Button erfolgreich geklickt mit Selektor {i+1}") self.automation.human_behavior.random_delay(0.5, 1.0) return True except Exception as e: logger.debug(f"Continue-Selektor {i+1} fehlgeschlagen: {e}") continue # Fallback: Fuzzy-Button-Matching try: success = self.automation.ui_helper.click_button_fuzzy( ["Weiter", "Continue", "Fortfahren", "Next"], continue_selectors[0] ) if success: logger.info("Weiter-Button über Fuzzy-Matching geklickt") self.automation.human_behavior.random_delay(0.5, 1.0) return True except Exception as e: logger.debug(f"Continue Fuzzy-Matching fehlgeschlagen: {e}") logger.error("Weiter-Button konnte nicht geklickt werden") return False except Exception as e: logger.error(f"Kritischer Fehler beim Weiter-Button: {e}") return False