""" TikTok-Login - Klasse für die Anmeldefunktionalität bei TikTok """ import logging import time import re from typing import Dict, List, Any, Optional, Tuple from .tiktok_selectors import TikTokSelectors from .tiktok_workflow import TikTokWorkflow # Konfiguriere Logger logger = logging.getLogger("tiktok_login") class TikTokLogin: """ Klasse für die Anmeldung bei TikTok-Konten. Enthält alle Methoden für den Login-Prozess. """ def __init__(self, automation): """ Initialisiert die TikTok-Login-Funktionalität. Args: automation: Referenz auf die Hauptautomatisierungsklasse """ self.automation = automation self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt self.selectors = TikTokSelectors() self.workflow = TikTokWorkflow.get_login_workflow() logger.debug("TikTok-Login initialisiert") def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]: """ Führt den Login-Prozess für ein TikTok-Konto durch. Args: username_or_email: Benutzername oder E-Mail-Adresse password: Passwort **kwargs: Weitere optionale Parameter Returns: Dict[str, Any]: Ergebnis des Logins mit Status """ # Hole Browser-Referenz von der Hauptklasse self.browser = self.automation.browser # Validiere die Eingaben if not self._validate_login_inputs(username_or_email, password): return { "success": False, "error": "Ungültige Login-Eingaben", "stage": "input_validation" } # Account-Daten für die Anmeldung account_data = { "username": username_or_email, "password": password, "handle_2fa": kwargs.get("handle_2fa", False), "two_factor_code": kwargs.get("two_factor_code"), "skip_save_login": kwargs.get("skip_save_login", True) } logger.info(f"Starte TikTok-Login für {username_or_email}") try: # 1. Zur Login-Seite navigieren if not self._navigate_to_login_page(): return { "success": False, "error": "Konnte nicht zur Login-Seite navigieren", "stage": "navigation" } # 2. Cookie-Banner behandeln self._handle_cookie_banner() # 3. Login-Formular ausfüllen if not self._fill_login_form(account_data): return { "success": False, "error": "Fehler beim Ausfüllen des Login-Formulars", "stage": "login_form" } # 4. Auf 2FA prüfen und behandeln, falls nötig needs_2fa, two_fa_error = self._check_needs_two_factor_auth() if needs_2fa: if not account_data["handle_2fa"]: return { "success": False, "error": "Zwei-Faktor-Authentifizierung erforderlich, aber nicht aktiviert", "stage": "two_factor_required" } # 2FA behandeln if not self._handle_two_factor_auth(account_data["two_factor_code"]): return { "success": False, "error": "Fehler bei der Zwei-Faktor-Authentifizierung", "stage": "two_factor_auth" } # 5. Benachrichtigungserlaubnis-Dialog behandeln self._handle_notifications_prompt() # 6. Erfolgreichen Login überprüfen if not self._check_login_success(): error_message = self._get_login_error() return { "success": False, "error": f"Login fehlgeschlagen: {error_message or 'Unbekannter Fehler'}", "stage": "login_check" } # Login erfolgreich logger.info(f"TikTok-Login für {username_or_email} erfolgreich") return { "success": True, "stage": "completed", "username": username_or_email } except Exception as e: error_msg = f"Unerwarteter Fehler beim TikTok-Login: {str(e)}" logger.error(error_msg, exc_info=True) return { "success": False, "error": error_msg, "stage": "exception" } def _validate_login_inputs(self, username_or_email: str, password: str) -> bool: """ Validiert die Eingaben für den Login. Args: username_or_email: Benutzername oder E-Mail-Adresse password: Passwort Returns: bool: True wenn alle Eingaben gültig sind, False sonst """ if not username_or_email or len(username_or_email) < 3: logger.error("Ungültiger Benutzername oder E-Mail") return False if not password or len(password) < 6: logger.error("Ungültiges Passwort") return False return True def _navigate_to_login_page(self) -> bool: """ Navigiert zur TikTok-Login-Seite. Returns: bool: True bei Erfolg, False bei Fehler """ try: # Zur Login-Seite navigieren self.browser.navigate_to(TikTokSelectors.LOGIN_URL) # Warten, bis die Seite geladen ist self.automation.human_behavior.wait_for_page_load() # Screenshot erstellen self.automation._take_screenshot("login_page") # Prüfen, ob Login-Dialog sichtbar ist if not self.browser.is_element_visible(TikTokSelectors.LOGIN_EMAIL_FIELD, timeout=5000): logger.warning("Login-Dialog nicht sichtbar, versuche Login-Button zu klicken") # Versuche, den Login-Button zu klicken, um das Login-Modal zu öffnen login_buttons = [ TikTokSelectors.LOGIN_BUTTON_TOP, TikTokSelectors.LOGIN_BUTTON_SIDEBAR ] button_clicked = False for button in login_buttons: if self.browser.is_element_visible(button, timeout=2000): self.browser.click_element(button) button_clicked = True break if not button_clicked: logger.warning("Keine Login-Buttons gefunden") return False # Warten, bis der Login-Dialog erscheint self.automation.human_behavior.wait_between_actions("decision", 1.5) # Erneut prüfen, ob der Login-Dialog sichtbar ist if not self.browser.is_element_visible(TikTokSelectors.LOGIN_DIALOG, timeout=5000): logger.warning("Login-Dialog nach dem Klicken auf den Login-Button nicht sichtbar") return False logger.info("Erfolgreich zur Login-Seite navigiert") return True except Exception as e: logger.error(f"Fehler beim Navigieren zur Login-Seite: {e}") return False def _handle_cookie_banner(self) -> bool: """ Behandelt den Cookie-Banner, falls angezeigt. Returns: bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler """ # Cookie-Dialog-Erkennung if self.browser.is_element_visible(TikTokSelectors.COOKIE_DIALOG, timeout=2000): logger.info("Cookie-Banner erkannt") # Ablehnen-Button suchen und klicken reject_success = self.automation.ui_helper.click_button_fuzzy( TikTokSelectors.get_button_texts("reject_cookies"), TikTokSelectors.COOKIE_REJECT_BUTTON ) if reject_success: logger.info("Cookie-Banner erfolgreich abgelehnt") self.automation.human_behavior.random_delay(0.5, 1.5) return True else: logger.warning("Konnte Cookie-Banner nicht ablehnen, versuche zu akzeptieren") # Akzeptieren-Button als Fallback accept_success = self.browser.click_element(TikTokSelectors.COOKIE_ACCEPT_BUTTON) if accept_success: logger.info("Cookie-Banner erfolgreich akzeptiert") self.automation.human_behavior.random_delay(0.5, 1.5) return True else: logger.error("Konnte Cookie-Banner weder ablehnen noch akzeptieren") return False else: logger.debug("Kein Cookie-Banner erkannt") return True def _fill_login_form(self, account_data: Dict[str, Any]) -> bool: """ Füllt das Login-Formular aus und sendet es ab. Args: account_data: Account-Daten für den Login Returns: bool: True bei Erfolg, False bei Fehler """ try: # E-Mail/Telefon-Login-Option auswählen email_phone_option = self.automation.ui_helper.click_button_fuzzy( ["Telefon-Nr./E-Mail/Anmeldename nutzen", "Use phone / email / username"], TikTokSelectors.LOGIN_EMAIL_PHONE_OPTION ) if not email_phone_option: logger.warning("Konnte die E-Mail/Telefon-Option nicht auswählen, versuche direkt das Formular auszufüllen") self.automation.human_behavior.random_delay(0.5, 1.5) # E-Mail/Benutzername eingeben username_success = self.automation.ui_helper.fill_field_fuzzy( TikTokSelectors.get_field_labels("email_username"), account_data["username"], TikTokSelectors.LOGIN_EMAIL_FIELD ) if not username_success: logger.error("Konnte Benutzername-Feld nicht ausfüllen") return False self.automation.human_behavior.random_delay(0.5, 1.5) # Passwort eingeben password_success = self.automation.ui_helper.fill_field_fuzzy( TikTokSelectors.get_field_labels("password"), account_data["password"], TikTokSelectors.LOGIN_PASSWORD_FIELD ) if not password_success: logger.error("Konnte Passwort-Feld nicht ausfüllen") return False self.automation.human_behavior.random_delay(1.0, 2.0) # Screenshot vorm Absenden self.automation._take_screenshot("login_form_filled") # Formular absenden submit_success = self.automation.ui_helper.click_button_fuzzy( ["Anmelden", "Log in", "Login"], TikTokSelectors.LOGIN_SUBMIT_BUTTON ) if not submit_success: logger.error("Konnte Login-Formular nicht absenden") return False # Nach dem Absenden warten self.automation.human_behavior.wait_for_page_load(multiplier=1.5) # Überprüfen, ob es eine Fehlermeldung gab error_message = self._get_login_error() if error_message: logger.error(f"Login-Fehler erkannt: {error_message}") return False logger.info("Login-Formular erfolgreich ausgefüllt und abgesendet") return True except Exception as e: logger.error(f"Fehler beim Ausfüllen des Login-Formulars: {e}") return False def _get_login_error(self) -> Optional[str]: """ Überprüft, ob eine Login-Fehlermeldung angezeigt wird. Returns: Optional[str]: Fehlermeldung oder None, wenn keine gefunden wurde """ try: # Auf Fehlermeldungen prüfen error_selectors = [ TikTokSelectors.ERROR_MESSAGE, "p[class*='error']", "div[role='alert']", "div[class*='error']", "div[class*='Error']" ] for selector in error_selectors: error_element = self.browser.wait_for_selector(selector, timeout=2000) if error_element: error_text = error_element.text_content() if error_text and len(error_text.strip()) > 0: return error_text.strip() # Wenn keine spezifische Fehlermeldung gefunden wurde, nach bekannten Fehlermustern suchen error_texts = [ "Falsches Passwort", "Benutzername nicht gefunden", "incorrect password", "username you entered doesn't belong", "please wait a few minutes", "try again later", "Bitte warte einige Minuten", "versuche es später noch einmal" ] page_content = self.browser.page.content() for error_text in error_texts: if error_text.lower() in page_content.lower(): return f"Erkannter Fehler: {error_text}" return None except Exception as e: logger.error(f"Fehler beim Prüfen auf Login-Fehler: {e}") return None def _check_needs_two_factor_auth(self) -> Tuple[bool, Optional[str]]: """ Überprüft, ob eine Zwei-Faktor-Authentifizierung erforderlich ist. Returns: Tuple[bool, Optional[str]]: (2FA erforderlich, Fehlermeldung falls vorhanden) """ try: # Nach 2FA-Indikatoren suchen two_fa_selectors = [ "input[name='verificationCode']", "input[placeholder*='code']", "input[placeholder*='Code']", "div[class*='verification-code']", "div[class*='two-factor']" ] for selector in two_fa_selectors: if self.browser.is_element_visible(selector, timeout=2000): logger.info("Zwei-Faktor-Authentifizierung erforderlich") return True, None # Texte, die auf 2FA hinweisen two_fa_indicators = [ "Verifizierungscode", "Verification code", "Sicherheitscode", "Security code", "zwei-faktor", "two-factor", "2FA" ] # Seiteninhalt durchsuchen page_content = self.browser.page.content().lower() for indicator in two_fa_indicators: if indicator.lower() in page_content: logger.info(f"Zwei-Faktor-Authentifizierung erkannt durch Text: {indicator}") return True, None return False, None except Exception as e: logger.error(f"Fehler beim Prüfen auf 2FA: {e}") return False, f"Fehler bei der 2FA-Erkennung: {str(e)}" def _handle_two_factor_auth(self, two_factor_code: Optional[str] = None) -> bool: """ Behandelt die Zwei-Faktor-Authentifizierung. Args: two_factor_code: Optional vorhandener 2FA-Code Returns: bool: True bei Erfolg, False bei Fehler """ try: # Screenshot erstellen self.automation._take_screenshot("two_factor_auth") # 2FA-Eingabefeld finden two_fa_selectors = [ "input[name='verificationCode']", "input[placeholder*='code']", "input[placeholder*='Code']" ] two_fa_field = None for selector in two_fa_selectors: element = self.browser.wait_for_selector(selector, timeout=2000) if element: two_fa_field = selector break if not two_fa_field: logger.error("Konnte 2FA-Eingabefeld nicht finden") return False # Wenn kein Code bereitgestellt wurde, Benutzer auffordern if not two_factor_code: logger.warning("Kein 2FA-Code bereitgestellt, kann nicht fortfahren") return False # 2FA-Code eingeben code_success = self.browser.fill_form_field(two_fa_field, two_factor_code) if not code_success: logger.error("Konnte 2FA-Code nicht eingeben") return False self.automation.human_behavior.random_delay(1.0, 2.0) # Bestätigen-Button finden und klicken confirm_button_selectors = [ "button[type='submit']", "//button[contains(text(), 'Bestätigen')]", "//button[contains(text(), 'Confirm')]", "//button[contains(text(), 'Verify')]" ] confirm_clicked = False for selector in confirm_button_selectors: if self.browser.is_element_visible(selector, timeout=1000): if self.browser.click_element(selector): confirm_clicked = True break if not confirm_clicked: # Alternative: Mit Tastendruck bestätigen self.browser.page.keyboard.press("Enter") logger.info("Enter-Taste gedrückt, um 2FA zu bestätigen") # Warten nach der Bestätigung self.automation.human_behavior.wait_for_page_load(multiplier=1.5) # Überprüfen, ob 2FA erfolgreich war still_on_2fa = self._check_needs_two_factor_auth()[0] if still_on_2fa: # Prüfen, ob Fehlermeldung angezeigt wird error_message = self._get_login_error() if error_message: logger.error(f"2FA-Fehler: {error_message}") else: logger.error("2FA fehlgeschlagen, immer noch auf 2FA-Seite") return False logger.info("Zwei-Faktor-Authentifizierung erfolgreich") return True except Exception as e: logger.error(f"Fehler bei der Zwei-Faktor-Authentifizierung: {e}") return False def _handle_notifications_prompt(self) -> bool: """ Behandelt den Benachrichtigungen-Dialog. Returns: bool: True bei Erfolg, False bei Fehler """ try: # Nach "Nicht jetzt"-Button suchen not_now_selectors = [ "//button[contains(text(), 'Nicht jetzt')]", "//button[contains(text(), 'Not now')]", "//button[contains(text(), 'Skip')]", "//button[contains(text(), 'Später')]", "//button[contains(text(), 'Later')]" ] for selector in not_now_selectors: if self.browser.is_element_visible(selector, timeout=3000): if self.browser.click_element(selector): logger.info("Benachrichtigungen-Dialog übersprungen") self.automation.human_behavior.random_delay(0.5, 1.0) return True # Wenn kein Button gefunden wurde, ist der Dialog wahrscheinlich nicht vorhanden logger.debug("Kein Benachrichtigungen-Dialog erkannt") return True except Exception as e: logger.warning(f"Fehler beim Behandeln des Benachrichtigungen-Dialogs: {e}") # Dies ist nicht kritisch, daher geben wir trotzdem True zurück return True def _check_login_success(self) -> bool: """ Überprüft, ob der Login erfolgreich war. Returns: bool: True wenn erfolgreich, False sonst """ try: # Warten nach dem Login self.automation.human_behavior.wait_for_page_load(multiplier=1.5) # Screenshot erstellen self.automation._take_screenshot("login_final") # Erfolg anhand verschiedener Indikatoren prüfen success_indicators = TikTokSelectors.SUCCESS_INDICATORS for indicator in success_indicators: if self.browser.is_element_visible(indicator, timeout=2000): logger.info(f"Login-Erfolgsindikator gefunden: {indicator}") return True # Alternativ prüfen, ob wir auf der TikTok-Startseite sind current_url = self.browser.page.url if "tiktok.com" in current_url and "/login" not in current_url: logger.info(f"Login-Erfolg basierend auf URL: {current_url}") return True # Prüfen, ob immer noch auf der Login-Seite if "/login" in current_url or self.browser.is_element_visible(TikTokSelectors.LOGIN_EMAIL_FIELD, timeout=1000): logger.warning("Immer noch auf der Login-Seite, Login fehlgeschlagen") return False logger.warning("Keine Login-Erfolgsindikatoren gefunden") return False except Exception as e: logger.error(f"Fehler beim Überprüfen des Login-Erfolgs: {e}") return False