Dieser Commit ist enthalten in:
Claude Project Manager
2025-07-03 21:11:05 +02:00
Commit 08ed938105
239 geänderte Dateien mit 21554 neuen und 0 gelöschten Zeilen

Datei anzeigen

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -0,0 +1,328 @@
"""
TikTok-Automatisierung - Hauptklasse für TikTok-Automatisierungsfunktionalität
"""
import logging
import time
import random
from typing import Dict, List, Any, Optional, Tuple
from browser.playwright_manager import PlaywrightManager
from browser.playwright_extensions import PlaywrightExtensions
from social_networks.base_automation import BaseAutomation
from utils.password_generator import PasswordGenerator
from utils.username_generator import UsernameGenerator
from utils.birthday_generator import BirthdayGenerator
from utils.human_behavior import HumanBehavior
# Importiere Helferklassen
from .tiktok_registration import TikTokRegistration
from .tiktok_login import TikTokLogin
from .tiktok_verification import TikTokVerification
from .tiktok_ui_helper import TikTokUIHelper
from .tiktok_utils import TikTokUtils
# Konfiguriere Logger
logger = logging.getLogger("tiktok_automation")
class TikTokAutomation(BaseAutomation):
"""
Hauptklasse für die TikTok-Automatisierung.
Implementiert die Registrierung und Anmeldung bei TikTok.
"""
def __init__(self,
headless: bool = False,
use_proxy: bool = False,
proxy_type: str = None,
save_screenshots: bool = True,
screenshots_dir: str = None,
slowmo: int = 0,
debug: bool = False,
email_domain: str = "z5m7q9dk3ah2v1plx6ju.com",
enhanced_stealth: bool = True,
fingerprint_noise: float = 0.5):
"""
Initialisiert die TikTok-Automatisierung.
Args:
headless: Ob der Browser im Headless-Modus ausgeführt werden soll
use_proxy: Ob ein Proxy verwendet werden soll
proxy_type: Proxy-Typ ("ipv4", "ipv6", "mobile") oder None für zufälligen Typ
save_screenshots: Ob Screenshots gespeichert werden sollen
screenshots_dir: Verzeichnis für Screenshots
slowmo: Verzögerung zwischen Aktionen in Millisekunden (nützlich für Debugging)
debug: Ob Debug-Informationen angezeigt werden sollen
email_domain: Domain für generierte E-Mail-Adressen
enhanced_stealth: Ob erweiterter Stealth-Modus aktiviert werden soll
fingerprint_noise: Menge an Rauschen für Fingerprint-Verschleierung (0.0-1.0)
"""
# Initialisiere die Basisklasse
super().__init__(
headless=headless,
use_proxy=use_proxy,
proxy_type=proxy_type,
save_screenshots=save_screenshots,
screenshots_dir=screenshots_dir,
slowmo=slowmo,
debug=debug,
email_domain=email_domain
)
# Stealth-Modus-Einstellungen
self.enhanced_stealth = enhanced_stealth
self.fingerprint_noise = max(0.0, min(1.0, fingerprint_noise))
# Initialisiere Helferklassen
self.registration = TikTokRegistration(self)
self.login = TikTokLogin(self)
self.verification = TikTokVerification(self)
self.ui_helper = TikTokUIHelper(self)
self.utils = TikTokUtils(self)
# Zusätzliche Hilfsklassen
self.password_generator = PasswordGenerator()
self.username_generator = UsernameGenerator()
self.birthday_generator = BirthdayGenerator()
self.human_behavior = HumanBehavior(speed_factor=0.8, randomness=0.6)
logger.info("TikTok-Automatisierung initialisiert")
def _initialize_browser(self) -> bool:
"""
Initialisiert den Browser mit den entsprechenden Einstellungen.
Diese Methode überschreibt die Methode der Basisklasse, um den erweiterten
Fingerprint-Schutz zu aktivieren.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Proxy-Konfiguration, falls aktiviert
proxy_config = None
if self.use_proxy:
proxy_config = self.proxy_rotator.get_proxy(self.proxy_type)
if not proxy_config:
logger.warning(f"Kein Proxy vom Typ '{self.proxy_type}' verfügbar, verwende direkten Zugriff")
# Browser initialisieren
self.browser = PlaywrightManager(
headless=self.headless,
proxy=proxy_config,
browser_type="chromium",
screenshots_dir=self.screenshots_dir,
slowmo=self.slowmo
)
# Browser starten
self.browser.start()
# Erweiterten Fingerprint-Schutz aktivieren, wenn gewünscht
if self.enhanced_stealth:
# Erstelle Extensions-Objekt
extensions = PlaywrightExtensions(self.browser)
# Methoden anhängen
extensions.hook_into_playwright_manager()
# Fingerprint-Schutz aktivieren mit angepasster Konfiguration
fingerprint_config = {
"noise_level": self.fingerprint_noise,
"canvas_noise": True,
"audio_noise": True,
"webgl_noise": True,
"hardware_concurrency": random.choice([4, 6, 8]),
"device_memory": random.choice([4, 8]),
"timezone_id": "Europe/Berlin"
}
success = self.browser.enable_enhanced_fingerprint_protection(fingerprint_config)
if success:
logger.info("Erweiterter Fingerprint-Schutz erfolgreich aktiviert")
else:
logger.warning("Erweiterter Fingerprint-Schutz konnte nicht aktiviert werden")
logger.info("Browser erfolgreich initialisiert")
return True
except Exception as e:
logger.error(f"Fehler bei der Browser-Initialisierung: {e}")
self.status["error"] = f"Browser-Initialisierungsfehler: {str(e)}"
return False
def register_account(self, full_name: str, age: int, registration_method: str = "email",
phone_number: str = None, **kwargs) -> Dict[str, Any]:
"""
Registriert einen neuen TikTok-Account.
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
"""
logger.info(f"Starte TikTok-Account-Registrierung für '{full_name}' via {registration_method}")
try:
# Initialisiere Browser, falls noch nicht geschehen
if not self.browser or not hasattr(self.browser, 'page'):
if not self._initialize_browser():
return {"success": False, "error": "Browser konnte nicht initialisiert werden"}
# Rotiere Fingerprint vor der Hauptaktivität, um Erkennung weiter zu erschweren
if self.enhanced_stealth and hasattr(self.browser, 'rotate_fingerprint'):
self.browser.rotate_fingerprint()
logger.info("Browser-Fingerprint vor der Registrierung rotiert")
# Delegiere die Hauptregistrierungslogik an die Registration-Klasse
result = self.registration.register_account(full_name, age, registration_method, phone_number, **kwargs)
# Nehme Abschlussfoto auf
self._take_screenshot(f"registration_finished_{int(time.time())}")
# Aktualisiere Status
self.status.update(result)
return result
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der Account-Registrierung: {str(e)}"
logger.error(error_msg, exc_info=True)
# Fehler-Screenshot
self._take_screenshot(f"registration_error_{int(time.time())}")
# Aktualisiere Status
self.status.update({
"success": False,
"error": error_msg,
"stage": "error"
})
return self.status
finally:
# Browser schließen
self._close_browser()
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
"""
Meldet sich bei einem bestehenden TikTok-Account an.
Args:
username_or_email: Benutzername oder E-Mail-Adresse
password: Passwort
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Anmeldung mit Status
"""
logger.info(f"Starte TikTok-Login für '{username_or_email}'")
try:
# Initialisiere Browser, falls noch nicht geschehen
if not self.browser or not hasattr(self.browser, 'page'):
if not self._initialize_browser():
return {"success": False, "error": "Browser konnte nicht initialisiert werden"}
# Rotiere Fingerprint vor dem Login
if self.enhanced_stealth and hasattr(self.browser, 'rotate_fingerprint'):
self.browser.rotate_fingerprint()
logger.info("Browser-Fingerprint vor dem Login rotiert")
# Delegiere die Hauptlogin-Logik an die Login-Klasse
result = self.login.login_account(username_or_email, password, **kwargs)
# Nehme Abschlussfoto auf
self._take_screenshot(f"login_finished_{int(time.time())}")
# Aktualisiere Status
self.status.update(result)
return result
except Exception as e:
error_msg = f"Unerwarteter Fehler beim Login: {str(e)}"
logger.error(error_msg, exc_info=True)
# Fehler-Screenshot
self._take_screenshot(f"login_error_{int(time.time())}")
# Aktualisiere Status
self.status.update({
"success": False,
"error": error_msg,
"stage": "error"
})
return self.status
finally:
# Browser schließen
self._close_browser()
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
"""
Verifiziert einen TikTok-Account mit einem Bestätigungscode.
Args:
verification_code: Der Bestätigungscode
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Verifizierung mit Status
"""
logger.info(f"Starte TikTok-Account-Verifizierung mit Code: {verification_code}")
try:
# Initialisiere Browser, falls noch nicht geschehen
if not self.browser or not hasattr(self.browser, 'page'):
if not self._initialize_browser():
return {"success": False, "error": "Browser konnte nicht initialisiert werden"}
# Delegiere die Hauptverifizierungslogik an die Verification-Klasse
result = self.verification.verify_account(verification_code, **kwargs)
# Nehme Abschlussfoto auf
self._take_screenshot(f"verification_finished_{int(time.time())}")
# Aktualisiere Status
self.status.update(result)
return result
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der Account-Verifizierung: {str(e)}"
logger.error(error_msg, exc_info=True)
# Fehler-Screenshot
self._take_screenshot(f"verification_error_{int(time.time())}")
# Aktualisiere Status
self.status.update({
"success": False,
"error": error_msg,
"stage": "error"
})
return self.status
finally:
# Browser schließen
self._close_browser()
def get_fingerprint_status(self) -> Dict[str, Any]:
"""
Gibt den aktuellen Status des Fingerprint-Schutzes zurück.
Returns:
Dict[str, Any]: Status des Fingerprint-Schutzes
"""
if not self.enhanced_stealth or not hasattr(self.browser, 'get_fingerprint_status'):
return {
"active": False,
"message": "Erweiterter Fingerprint-Schutz ist nicht aktiviert"
}
return self.browser.get_fingerprint_status()

Datei anzeigen

@ -0,0 +1,582 @@
"""
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

Datei anzeigen

@ -0,0 +1,987 @@
# social_networks/tiktok/tiktok_registration.py
"""
TikTok-Registrierung - Klasse für die Kontoerstellung bei TikTok
"""
import logging
import time
import random
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_registration")
class TikTokRegistration:
"""
Klasse für die Registrierung von TikTok-Konten.
Enthält alle Methoden zur Kontoerstellung.
"""
def __init__(self, automation):
"""
Initialisiert die TikTok-Registrierung.
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_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
"""
# Hole Browser-Referenz von der Hauptklasse
self.browser = self.automation.browser
# 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 TikTok-Registrierung für {account_data['username']} via {registration_method}")
try:
# 1. Zur Startseite navigieren
if not self._navigate_to_homepage():
return {
"success": False,
"error": "Konnte nicht zur TikTok-Startseite navigieren",
"stage": "navigation",
"account_data": account_data
}
# 2. Cookie-Banner behandeln
self._handle_cookie_banner()
# 3. Anmelden-Button klicken
if not self._click_login_button():
return {
"success": False,
"error": "Konnte nicht auf Anmelden-Button klicken",
"stage": "login_button",
"account_data": account_data
}
# 4. Registrieren-Link klicken
if not self._click_register_link():
return {
"success": False,
"error": "Konnte nicht auf Registrieren-Link klicken",
"stage": "register_link",
"account_data": account_data
}
# 5. Telefon/E-Mail-Option auswählen
if not self._click_phone_email_option():
return {
"success": False,
"error": "Konnte nicht auf Telefon/E-Mail-Option klicken",
"stage": "phone_email_option",
"account_data": account_data
}
# 6. E-Mail oder Telefon als Registrierungsmethode wählen
if not self._select_registration_method(registration_method):
return {
"success": False,
"error": f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen",
"stage": "registration_method",
"account_data": account_data
}
# 7. Geburtsdatum eingeben
if not self._enter_birthday(account_data["birthday"]):
return {
"success": False,
"error": "Fehler beim Eingeben des Geburtsdatums",
"stage": "birthday",
"account_data": account_data
}
# 8. Registrierungsformular ausfüllen
if not self._fill_registration_form(account_data, registration_method):
return {
"success": False,
"error": "Fehler beim Ausfüllen des Registrierungsformulars",
"stage": "registration_form",
"account_data": account_data
}
# 9. Bestätigungscode abrufen und eingeben
if not self._handle_verification(account_data, registration_method):
return {
"success": False,
"error": "Fehler bei der Verifizierung",
"stage": "verification",
"account_data": account_data
}
# 10. Benutzernamen erstellen
if not self._create_username(account_data):
return {
"success": False,
"error": "Fehler beim Erstellen des Benutzernamens",
"stage": "username",
"account_data": account_data
}
# 11. Erfolgreiche Registrierung überprüfen
if not self._check_registration_success():
return {
"success": False,
"error": "Registrierung fehlgeschlagen oder konnte nicht verifiziert werden",
"stage": "final_check",
"account_data": account_data
}
# Registrierung erfolgreich abgeschlossen
logger.info(f"TikTok-Account {account_data['username']} 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 _validate_registration_inputs(self, full_name: str, age: int,
registration_method: str, phone_number: str) -> bool:
"""
Validiert die Eingaben für die Registrierung.
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")
Returns:
bool: True wenn alle Eingaben gültig sind, False sonst
"""
# Vollständiger Name prüfen
if not full_name or len(full_name) < 3:
logger.error("Ungültiger vollständiger Name")
return False
# Alter prüfen
if age < 13:
logger.error("Benutzer muss mindestens 13 Jahre alt sein")
return False
# Registrierungsmethode prüfen
if registration_method not in ["email", "phone"]:
logger.error(f"Ungültige Registrierungsmethode: {registration_method}")
return False
# Telefonnummer prüfen, falls erforderlich
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.
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]: Generierte Account-Daten
"""
# 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 _navigate_to_homepage(self) -> bool:
"""
Navigiert zur TikTok-Startseite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Zur Startseite navigieren
self.browser.navigate_to(TikTokSelectors.BASE_URL)
# Warten, bis die Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Screenshot erstellen
self.automation._take_screenshot("tiktok_homepage")
# Prüfen, ob die Seite korrekt geladen wurde
if not self.browser.is_element_visible(TikTokSelectors.LOGIN_BUTTON, timeout=5000):
logger.warning("TikTok-Startseite nicht korrekt geladen")
return False
logger.info("Erfolgreich zur TikTok-Startseite navigiert")
return True
except Exception as e:
logger.error(f"Fehler beim Navigieren zur TikTok-Startseite: {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 _click_login_button(self) -> bool:
"""
Klickt auf den Anmelden-Button auf der Startseite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Versuche zuerst den Hauptbutton
if self.browser.is_element_visible(TikTokSelectors.LOGIN_BUTTON, timeout=2000):
result = self.browser.click_element(TikTokSelectors.LOGIN_BUTTON)
if result:
logger.info("Anmelden-Button erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche alternativ den Button in der oberen rechten Ecke
if self.browser.is_element_visible(TikTokSelectors.LOGIN_BUTTON_TOP_RIGHT, timeout=2000):
result = self.browser.click_element(TikTokSelectors.LOGIN_BUTTON_TOP_RIGHT)
if result:
logger.info("Anmelden-Button (oben rechts) erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Anmelden", "Log in", "Login"],
TikTokSelectors.LOGIN_BUTTON_FALLBACK
)
if result:
logger.info("Anmelden-Button über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte keinen Anmelden-Button finden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf den Anmelden-Button: {e}")
return False
def _click_register_link(self) -> bool:
"""
Klickt auf den Registrieren-Link im Login-Dialog.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis der Login-Dialog angezeigt wird
self.automation.human_behavior.random_delay(1.0, 2.0)
# Prüfen, ob wir bereits im Registrierungsdialog sind
if self.browser.is_element_visible(TikTokSelectors.REGISTER_DIALOG_TITLE, timeout=2000):
logger.info("Bereits im Registrierungsdialog")
return True
# Versuche, den Registrieren-Link zu finden und zu klicken
if self.browser.is_element_visible(TikTokSelectors.REGISTER_LINK, timeout=2000):
result = self.browser.click_element(TikTokSelectors.REGISTER_LINK)
if result:
logger.info("Registrieren-Link erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Registrieren", "Sign up", "Konto erstellen", "Register"],
TikTokSelectors.REGISTER_LINK_FALLBACK
)
if result:
logger.info("Registrieren-Link über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Prüfe, ob der Text "Du hast noch kein Konto? Registrieren" vorhanden ist
register_link_text = "Du hast noch kein Konto? Registrieren"
elements = self.browser.page.query_selector_all("*")
for element in elements:
if register_link_text in element.inner_text():
# Finde das "Registrieren"-Wort und klicke darauf
matches = re.search(r"(.*?)(Registrieren)$", element.inner_text())
if matches:
# Versuche, nur auf das Wort "Registrieren" zu klicken
element.click()
logger.info("Auf 'Registrieren' Text geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte keinen Registrieren-Link finden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf den Registrieren-Link: {e}")
return False
def _click_phone_email_option(self) -> bool:
"""
Klickt auf die Telefon/E-Mail-Option im Registrierungsdialog.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis der Registrierungsdialog angezeigt wird
self.automation.human_behavior.random_delay(1.0, 2.0)
# Prüfen, ob wir bereits die Optionen für Telefon/E-Mail sehen
if self.browser.is_element_visible(TikTokSelectors.EMAIL_FIELD, timeout=2000) or \
self.browser.is_element_visible(TikTokSelectors.PHONE_FIELD, timeout=2000):
logger.info("Bereits auf der Telefon/E-Mail-Registrierungsseite")
return True
# Versuche, die Telefon/E-Mail-Option zu finden und zu klicken
if self.browser.is_element_visible(TikTokSelectors.PHONE_EMAIL_OPTION, timeout=2000):
result = self.browser.click_element(TikTokSelectors.PHONE_EMAIL_OPTION)
if result:
logger.info("Telefon/E-Mail-Option erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Telefonnummer oder E-Mail-Adresse nutzen", "Use phone or email", "Phone or email"],
TikTokSelectors.PHONE_EMAIL_OPTION_FALLBACK
)
if result:
logger.info("Telefon/E-Mail-Option über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte keine Telefon/E-Mail-Option finden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf die Telefon/E-Mail-Option: {e}")
return False
def _select_registration_method(self, registration_method: str) -> bool:
"""
Wählt die Registrierungsmethode (E-Mail oder Telefon).
Args:
registration_method: "email" oder "phone"
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis die Registrierungsmethoden-Seite geladen ist
self.automation.human_behavior.random_delay(1.0, 2.0)
if registration_method == "email":
# Wenn bereits das E-Mail-Feld sichtbar ist, sind wir schon auf der richtigen Seite
if self.browser.is_element_visible(TikTokSelectors.EMAIL_FIELD, timeout=1000):
logger.info("Bereits auf der E-Mail-Registrierungsseite")
return True
# Suche nach dem "Mit E-Mail-Adresse registrieren" Link
if self.browser.is_element_visible(TikTokSelectors.EMAIL_OPTION, timeout=2000):
result = self.browser.click_element(TikTokSelectors.EMAIL_OPTION)
if result:
logger.info("E-Mail-Registrierungsmethode erfolgreich ausgewählt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Mit E-Mail-Adresse registrieren", "Register with email", "E-Mail-Adresse"],
TikTokSelectors.EMAIL_OPTION_FALLBACK
)
if result:
logger.info("E-Mail-Option über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
elif registration_method == "phone":
# Wenn bereits das Telefon-Feld sichtbar ist, sind wir schon auf der richtigen Seite
if self.browser.is_element_visible(TikTokSelectors.PHONE_FIELD, timeout=1000):
logger.info("Bereits auf der Telefon-Registrierungsseite")
return True
# Suche nach dem "Mit Telefonnummer registrieren" Link
if self.browser.is_element_visible(TikTokSelectors.PHONE_OPTION, timeout=2000):
result = self.browser.click_element(TikTokSelectors.PHONE_OPTION)
if result:
logger.info("Telefon-Registrierungsmethode erfolgreich ausgewählt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Mit Telefonnummer registrieren", "Register with phone", "Telefonnummer"],
TikTokSelectors.PHONE_OPTION_FALLBACK
)
if result:
logger.info("Telefon-Option über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error(f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen")
return False
except Exception as e:
logger.error(f"Fehler beim Auswählen der Registrierungsmethode: {e}")
return False
def _enter_birthday(self, birthday: Dict[str, int]) -> bool:
"""
Gibt das Geburtsdatum ein.
Args:
birthday: Dictionary mit 'year', 'month', 'day' Schlüsseln
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis die Geburtstagsauswahl angezeigt wird
self.automation.human_behavior.random_delay(1.0, 2.0)
# Monat auswählen
month_dropdown = self.browser.wait_for_selector(TikTokSelectors.BIRTHDAY_MONTH_DROPDOWN, timeout=5000)
if not month_dropdown:
logger.error("Monat-Dropdown nicht gefunden")
return False
month_dropdown.click()
self.automation.human_behavior.random_delay(0.3, 0.8)
# Monat-Option auswählen
month_option = self.browser.wait_for_selector(
TikTokSelectors.get_month_option_selector(birthday["month"]),
timeout=3000
)
if month_option:
month_option.click()
logger.info(f"Monat {birthday['month']} ausgewählt")
else:
# Fallback: Monat über Select-Funktion auswählen
month_success = self.browser.select_option(
TikTokSelectors.BIRTHDAY_MONTH_DROPDOWN,
str(birthday["month"])
)
if not month_success:
logger.error(f"Konnte Monat {birthday['month']} nicht auswählen")
return False
self.automation.human_behavior.random_delay(0.3, 0.8)
# Tag auswählen
day_dropdown = self.browser.wait_for_selector(TikTokSelectors.BIRTHDAY_DAY_DROPDOWN, timeout=3000)
if not day_dropdown:
logger.error("Tag-Dropdown nicht gefunden")
return False
day_dropdown.click()
self.automation.human_behavior.random_delay(0.3, 0.8)
# Tag-Option auswählen
day_option = self.browser.wait_for_selector(
TikTokSelectors.get_day_option_selector(birthday["day"]),
timeout=3000
)
if day_option:
day_option.click()
logger.info(f"Tag {birthday['day']} ausgewählt")
else:
# Fallback: Tag über Select-Funktion auswählen
day_success = self.browser.select_option(
TikTokSelectors.BIRTHDAY_DAY_DROPDOWN,
str(birthday["day"])
)
if not day_success:
logger.error(f"Konnte Tag {birthday['day']} nicht auswählen")
return False
self.automation.human_behavior.random_delay(0.3, 0.8)
# Jahr auswählen
year_dropdown = self.browser.wait_for_selector(TikTokSelectors.BIRTHDAY_YEAR_DROPDOWN, timeout=3000)
if not year_dropdown:
logger.error("Jahr-Dropdown nicht gefunden")
return False
year_dropdown.click()
self.automation.human_behavior.random_delay(0.3, 0.8)
# Jahr-Option auswählen
year_option = self.browser.wait_for_selector(
TikTokSelectors.get_year_option_selector(birthday["year"]),
timeout=3000
)
if year_option:
year_option.click()
logger.info(f"Jahr {birthday['year']} ausgewählt")
else:
# Fallback: Jahr über Select-Funktion auswählen
year_success = self.browser.select_option(
TikTokSelectors.BIRTHDAY_YEAR_DROPDOWN,
str(birthday["year"])
)
if not year_success:
logger.error(f"Konnte Jahr {birthday['year']} nicht auswählen")
return False
logger.info(f"Geburtsdatum {birthday['month']}/{birthday['day']}/{birthday['year']} erfolgreich eingegeben")
return True
except Exception as e:
logger.error(f"Fehler beim Eingeben des Geburtsdatums: {e}")
return False
def _fill_registration_form(self, account_data: Dict[str, Any], registration_method: str) -> bool:
"""
Füllt das Registrierungsformular aus.
Args:
account_data: Account-Daten für die Registrierung
registration_method: "email" oder "phone"
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Je nach Registrierungsmethode das entsprechende Feld ausfüllen
if registration_method == "email":
# E-Mail-Feld ausfüllen
email_success = self.automation.ui_helper.fill_field_fuzzy(
["E-Mail-Adresse", "Email", "E-Mail"],
account_data["email"],
TikTokSelectors.EMAIL_FIELD
)
if not email_success:
logger.error("Konnte E-Mail-Feld nicht ausfüllen")
return False
logger.info(f"E-Mail-Feld ausgefüllt: {account_data['email']}")
elif registration_method == "phone":
# Telefonnummer-Feld ausfüllen (ohne Ländervorwahl)
phone_number = account_data["phone"]
if phone_number.startswith("+"):
# Entferne Ländervorwahl, wenn vorhanden
parts = phone_number.split(" ", 1)
if len(parts) > 1:
phone_number = parts[1]
phone_success = self.automation.ui_helper.fill_field_fuzzy(
["Telefonnummer", "Phone number", "Phone"],
phone_number,
TikTokSelectors.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)
# Bei E-Mail-Registrierung auch das Passwort-Feld ausfüllen
if registration_method == "email":
password_success = self.automation.ui_helper.fill_field_fuzzy(
["Passwort", "Password"],
account_data["password"],
TikTokSelectors.PASSWORD_FIELD
)
if not password_success:
logger.error("Konnte Passwort-Feld nicht ausfüllen")
return False
logger.info("Passwort-Feld ausgefüllt")
self.automation.human_behavior.random_delay(0.5, 1.5)
# Code senden Button klicken
send_code_success = self.automation.ui_helper.click_button_fuzzy(
["Code senden", "Send code", "Send verification code"],
TikTokSelectors.SEND_CODE_BUTTON
)
if not send_code_success:
logger.error("Konnte 'Code senden'-Button nicht klicken")
return False
logger.info("'Code senden'-Button erfolgreich geklickt")
return True
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {e}")
return False
def _handle_verification(self, account_data: Dict[str, Any], registration_method: str) -> bool:
"""
Behandelt den Verifizierungsprozess (E-Mail/SMS).
Args:
account_data: Account-Daten mit E-Mail/Telefon
registration_method: "email" oder "phone"
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis der Bestätigungscode gesendet wurde
self.automation.human_behavior.wait_for_page_load()
self.automation.human_behavior.random_delay(2.0, 4.0)
# Verifizierungscode je nach Methode abrufen
if registration_method == "email":
# Verifizierungscode von E-Mail abrufen
verification_code = self._get_email_confirmation_code(account_data["email"])
else:
# Verifizierungscode von SMS abrufen
verification_code = self._get_sms_confirmation_code(account_data["phone"])
if not verification_code:
logger.error("Konnte keinen Verifizierungscode abrufen")
return False
logger.info(f"Verifizierungscode erhalten: {verification_code}")
# Verifizierungscode-Feld ausfüllen
code_success = self.automation.ui_helper.fill_field_fuzzy(
["Gib den sechsstelligen Code ein", "Enter verification code", "Verification code"],
verification_code,
TikTokSelectors.VERIFICATION_CODE_FIELD
)
if not code_success:
logger.error("Konnte Verifizierungscode-Feld nicht ausfüllen")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# Weiter-Button klicken
continue_success = self.automation.ui_helper.click_button_fuzzy(
["Weiter", "Continue", "Next", "Submit"],
TikTokSelectors.CONTINUE_BUTTON
)
if not continue_success:
logger.error("Konnte 'Weiter'-Button nicht klicken")
return False
logger.info("Verifizierungscode eingegeben und 'Weiter' geklickt")
# Warten nach der Verifizierung
self.automation.human_behavior.wait_for_page_load()
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
except Exception as e:
logger.error(f"Fehler bei der Verifizierung: {e}")
return False
def _get_email_confirmation_code(self, email: str) -> Optional[str]:
"""
Ruft den Bestätigungscode von einer E-Mail ab.
Args:
email: E-Mail-Adresse, an die der Code gesendet wurde
Returns:
Optional[str]: Der Bestätigungscode oder None, wenn nicht gefunden
"""
try:
# Warte auf die E-Mail
verification_code = self.automation.email_handler.get_verification_code(
email_domain=self.automation.email_domain,
platform="tiktok",
timeout=120 # Warte bis zu 2 Minuten auf den Code
)
if verification_code:
return verification_code
# Wenn kein Code gefunden wurde, prüfen, ob der Code vielleicht direkt angezeigt wird
verification_code = self._extract_code_from_page()
if verification_code:
logger.info(f"Verifizierungscode direkt von der Seite extrahiert: {verification_code}")
return verification_code
logger.warning(f"Konnte keinen Verifizierungscode für {email} finden")
return None
except Exception as e:
logger.error(f"Fehler beim Abrufen des E-Mail-Bestätigungscodes: {e}")
return None
def _get_sms_confirmation_code(self, phone: str) -> Optional[str]:
"""
Ruft den Bestätigungscode aus einer SMS ab.
Hier müsste ein SMS-Empfangs-Service eingebunden werden.
Args:
phone: Telefonnummer, an die der Code gesendet wurde
Returns:
Optional[str]: Der Bestätigungscode oder None, wenn nicht gefunden
"""
# Diese Implementierung ist ein Platzhalter
# In einer echten Implementierung würde hier ein SMS-Empfangs-Service verwendet
logger.warning("SMS-Verifizierung ist noch nicht implementiert")
# Versuche, den Code trotzdem zu extrahieren, falls er auf der Seite angezeigt wird
return self._extract_code_from_page()
def _extract_code_from_page(self) -> Optional[str]:
"""
Versucht, einen Bestätigungscode direkt von der Seite zu extrahieren.
Returns:
Optional[str]: Der extrahierte Code oder None, wenn nicht gefunden
"""
try:
# Gesamten Seiteninhalt abrufen
page_content = self.browser.page.content()
# Mögliche Regex-Muster für Bestätigungscodes
patterns = [
r"Dein Code ist (\d{6})",
r"Your code is (\d{6})",
r"Bestätigungscode: (\d{6})",
r"Confirmation code: (\d{6})",
r"(\d{6}) ist dein TikTok-Code",
r"(\d{6}) is your TikTok code"
]
for pattern in patterns:
match = re.search(pattern, page_content)
if match:
return match.group(1)
return None
except Exception as e:
logger.error(f"Fehler beim Extrahieren des Codes von der Seite: {e}")
return None
def _create_username(self, account_data: Dict[str, Any]) -> bool:
"""
Erstellt einen Benutzernamen.
Args:
account_data: Account-Daten
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis die Benutzernamen-Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Prüfen, ob wir auf der Benutzernamen-Seite sind
if not self.browser.is_element_visible(TikTokSelectors.USERNAME_FIELD, timeout=5000):
logger.warning("Benutzernamen-Feld nicht gefunden, möglicherweise ist dieser Schritt übersprungen worden")
# Versuche, den "Überspringen"-Button zu klicken, falls vorhanden
skip_visible = self.browser.is_element_visible(TikTokSelectors.SKIP_USERNAME_BUTTON, timeout=2000)
if skip_visible:
self.browser.click_element(TikTokSelectors.SKIP_USERNAME_BUTTON)
logger.info("Benutzernamen-Schritt übersprungen")
return True
# Möglicherweise wurde der Benutzername automatisch erstellt
logger.info("Benutzernamen-Schritt möglicherweise automatisch abgeschlossen")
return True
# Benutzernamen eingeben
username_success = self.automation.ui_helper.fill_field_fuzzy(
["Benutzername", "Username"],
account_data["username"],
TikTokSelectors.USERNAME_FIELD
)
if not username_success:
logger.error("Konnte Benutzernamen-Feld nicht ausfüllen")
return False
logger.info(f"Benutzernamen-Feld ausgefüllt: {account_data['username']}")
self.automation.human_behavior.random_delay(1.0, 2.0)
# Registrieren-Button klicken
register_success = self.automation.ui_helper.click_button_fuzzy(
["Registrieren", "Register", "Sign up", "Submit"],
TikTokSelectors.REGISTER_BUTTON
)
if not register_success:
logger.error("Konnte 'Registrieren'-Button nicht klicken")
return False
logger.info("'Registrieren'-Button erfolgreich geklickt")
# Warten nach der Registrierung
self.automation.human_behavior.wait_for_page_load()
return True
except Exception as e:
logger.error(f"Fehler beim Erstellen des Benutzernamens: {e}")
return False
def _check_registration_success(self) -> bool:
"""
Überprüft, ob die Registrierung erfolgreich war.
Returns:
bool: True wenn erfolgreich, False sonst
"""
try:
# Warten nach der Registrierung
self.automation.human_behavior.wait_for_page_load(multiplier=2.0)
# Screenshot erstellen
self.automation._take_screenshot("registration_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=3000):
logger.info(f"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 "/signup" not in current_url and "/login" not in current_url:
logger.info(f"Erfolg basierend auf URL: {current_url}")
return True
logger.warning("Keine Erfolgsindikatoren gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Überprüfen des Registrierungserfolgs: {e}")
return False

Datei anzeigen

@ -0,0 +1,150 @@
"""
TikTok-Selektoren - CSS-Selektoren und XPath-Ausdrücke für die TikTok-Automatisierung
Mit Text-Matching-Funktionen für robuste Element-Erkennung
"""
from typing import List, Dict, Optional, Any
class TikTokSelectors:
"""
Zentrale Sammlung aller Selektoren für die TikTok-Automatisierung.
Bei Änderungen der TikTok-Webseite müssen nur hier Anpassungen vorgenommen werden.
Enthält auch Fuzzy-Text-Matching-Daten für robustere Element-Erkennung.
"""
# URL-Konstanten
BASE_URL = "https://www.tiktok.com"
SIGNUP_URL = "https://www.tiktok.com/signup"
LOGIN_URL = "https://www.tiktok.com/login"
# Anmelden/Registrieren-Buttons Hauptseite
LOGIN_BUTTON_LEFT = "button#header-login-button"
LOGIN_BUTTON_RIGHT = "button#top-right-action-bar-login-button"
SIGNUP_LINK = "a[href*='/signup']"
# Registrierungsdialog - Methoden
REGISTRATION_DIALOG = "div[role='dialog']"
PHONE_EMAIL_BUTTON = "div[data-e2e='channel-item']"
REGISTER_WITH_EMAIL = "a[href*='/signup/phone-or-email/email']"
REGISTER_WITH_PHONE = "a[href*='/signup/phone-or-email/phone']"
# Geburtsdatum-Selektoren
BIRTHDAY_MONTH_SELECT = "div.css-1fi2hzv-DivSelectLabel:contains('Monat')"
BIRTHDAY_DAY_SELECT = "div.css-1fi2hzv-DivSelectLabel:contains('Tag')"
BIRTHDAY_YEAR_SELECT = "div.css-1fi2hzv-DivSelectLabel:contains('Jahr')"
BIRTHDAY_DROPDOWN_OPTION = "div[role='option']"
# Formularfelder - E-Mail-Registrierung
EMAIL_FIELD = "input[placeholder='E-Mail-Adresse']"
PASSWORD_FIELD = "input[placeholder='Passwort']"
VERIFICATION_CODE_FIELD = "input[placeholder*='sechsstelligen Code']"
USERNAME_FIELD = "input[placeholder='Benutzername']"
# Formularfelder - Telefon-Registrierung
COUNTRY_CODE_SELECT = "div[role='combobox']"
PHONE_FIELD = "input[placeholder='Telefonnummer']"
# Buttons
SEND_CODE_BUTTON = "button[data-e2e='send-code-button']"
RESEND_CODE_BUTTON = "button:contains('Code erneut senden')"
CONTINUE_BUTTON = "button[type='submit']"
REGISTER_BUTTON = "button:contains('Registrieren')"
SKIP_BUTTON = "button:contains('Überspringen')"
# Checkbox
NEWSLETTER_CHECKBOX = "input[type='checkbox']"
# Erfolgs-Indikatoren für Registrierung
SUCCESS_INDICATORS = [
"a[href='/foryou']",
"a[href='/explore']",
"button[data-e2e='profile-icon']",
"svg[data-e2e='profile-icon']"
]
# Links für Nutzungsbedingungen und Datenschutz
TERMS_LINK = "a:contains('Nutzungsbedingungen')"
PRIVACY_LINK = "a:contains('Datenschutzerklärung')"
# Text-Matching-Parameter für Fuzzy-Matching
TEXT_MATCH = {
# Formularfelder
"form_fields": {
"email": ["E-Mail-Adresse", "E-Mail", "Email", "Mail"],
"phone": ["Telefonnummer", "Telefon", "Phone", "Mobile"],
"password": ["Passwort", "Password"],
"verification_code": ["Bestätigungscode", "Code", "Verifizierungscode", "Sicherheitscode"],
"username": ["Benutzername", "Username", "Name"]
},
# Buttons
"buttons": {
"send_code": ["Code senden", "Senden", "Send code", "Verification code", "Send"],
"continue": ["Weiter", "Continue", "Next", "Fortfahren"],
"register": ["Registrieren", "Register", "Sign up", "Konto erstellen"],
"skip": ["Überspringen", "Skip", "Later", "Später", "Nicht jetzt"],
},
# Fehler-Indikatoren
"error_indicators": [
"Fehler", "Error", "Leider", "Ungültig", "Invalid", "Nicht verfügbar",
"Fehlgeschlagen", "Problem", "Failed", "Nicht möglich", "Bereits verwendet",
"Too many attempts", "Zu viele Versuche", "Rate limit", "Bitte warte"
],
# Bestätigungscode-Texte in E-Mails
"email_verification_patterns": [
"ist dein Bestätigungscode",
"ist dein TikTok-Code",
"is your TikTok code",
"is your verification code",
"Dein Bestätigungscode lautet",
"Your verification code is"
]
}
@classmethod
def get_field_labels(cls, field_type: str) -> List[str]:
"""
Gibt die möglichen Bezeichnungen für ein Formularfeld zurück.
Args:
field_type: Typ des Formularfelds (z.B. "email", "phone")
Returns:
List[str]: Liste mit möglichen Bezeichnungen
"""
return cls.TEXT_MATCH["form_fields"].get(field_type, [])
@classmethod
def get_button_texts(cls, button_type: str) -> List[str]:
"""
Gibt die möglichen Texte für einen Button zurück.
Args:
button_type: Typ des Buttons (z.B. "send_code", "continue")
Returns:
List[str]: Liste mit möglichen Button-Texten
"""
return cls.TEXT_MATCH["buttons"].get(button_type, [])
@classmethod
def get_error_indicators(cls) -> List[str]:
"""
Gibt die möglichen Texte für Fehlerindikatoren zurück.
Returns:
List[str]: Liste mit möglichen Fehlerindikator-Texten
"""
return cls.TEXT_MATCH["error_indicators"]
@classmethod
def get_email_verification_patterns(cls) -> List[str]:
"""
Gibt die möglichen Texte für Bestätigungscodes in E-Mails zurück.
Returns:
List[str]: Liste mit möglichen E-Mail-Bestätigungscode-Texten
"""
return cls.TEXT_MATCH["email_verification_patterns"]

Datei anzeigen

@ -0,0 +1,523 @@
"""
TikTok-UI-Helper - Hilfsmethoden für die Interaktion mit der TikTok-UI
"""
import logging
import time
import re
from typing import Dict, List, Any, Optional, Tuple, Union, Callable
from .tiktok_selectors import TikTokSelectors
from utils.text_similarity import TextSimilarity, fuzzy_find_element, click_fuzzy_button
# Konfiguriere Logger
logger = logging.getLogger("tiktok_ui_helper")
class TikTokUIHelper:
"""
Hilfsmethoden für die Interaktion mit der TikTok-Benutzeroberfläche.
Bietet robuste Funktionen zum Finden und Interagieren mit UI-Elementen.
"""
def __init__(self, automation):
"""
Initialisiert den TikTok-UI-Helper.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
self.selectors = TikTokSelectors()
# Initialisiere TextSimilarity für Fuzzy-Matching
self.text_similarity = TextSimilarity(default_threshold=0.7)
logger.debug("TikTok-UI-Helper initialisiert")
def _ensure_browser(self) -> bool:
"""
Stellt sicher, dass die Browser-Referenz verfügbar ist.
Returns:
bool: True wenn Browser verfügbar, False sonst
"""
if self.browser is None:
self.browser = self.automation.browser
if self.browser is None:
logger.error("Browser-Referenz nicht verfügbar")
return False
return True
def fill_field_fuzzy(self, field_labels: Union[str, List[str]],
value: str, fallback_selector: str = None,
threshold: float = 0.7, timeout: int = 5000) -> bool:
"""
Füllt ein Formularfeld mit Fuzzy-Text-Matching aus.
Args:
field_labels: Bezeichner oder Liste von Bezeichnern des Feldes
value: Einzugebender Wert
fallback_selector: CSS-Selektor für Fallback
threshold: Schwellenwert für die Textähnlichkeit (0-1)
timeout: Zeitlimit für die Suche in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Normalisiere field_labels zu einer Liste
if isinstance(field_labels, str):
field_labels = [field_labels]
# Versuche, das Feld mit Fuzzy-Matching zu finden
element = fuzzy_find_element(
self.browser.page,
field_labels,
selector_type="input",
threshold=threshold,
wait_time=timeout
)
if element:
# Versuche, das Feld zu fokussieren und den Wert einzugeben
element.focus()
time.sleep(0.1)
element.fill("") # Leere das Feld zuerst
time.sleep(0.2)
# Text menschenähnlich eingeben
for char in value:
element.type(char, delay=self.automation.human_behavior.delays["typing_per_char"] * 1000)
time.sleep(0.01)
logger.info(f"Feld mit Fuzzy-Matching gefüllt: {value}")
return True
# Fuzzy-Matching fehlgeschlagen, versuche über Attribute
if fallback_selector:
field_success = self.browser.fill_form_field(fallback_selector, value)
if field_success:
logger.info(f"Feld mit Fallback-Selektor gefüllt: {fallback_selector}")
return True
# Versuche noch alternative Selektoren basierend auf field_labels
for label in field_labels:
# Versuche aria-label Attribut
aria_selector = f"input[aria-label='{label}'], textarea[aria-label='{label}']"
if self.browser.is_element_visible(aria_selector, timeout=1000):
if self.browser.fill_form_field(aria_selector, value):
logger.info(f"Feld über aria-label gefüllt: {label}")
return True
# Versuche placeholder Attribut
placeholder_selector = f"input[placeholder*='{label}'], textarea[placeholder*='{label}']"
if self.browser.is_element_visible(placeholder_selector, timeout=1000):
if self.browser.fill_form_field(placeholder_selector, value):
logger.info(f"Feld über placeholder gefüllt: {label}")
return True
# Versuche name Attribut
name_selector = f"input[name='{label.lower().replace(' ', '')}']"
if self.browser.is_element_visible(name_selector, timeout=1000):
if self.browser.fill_form_field(name_selector, value):
logger.info(f"Feld über name-Attribut gefüllt: {label}")
return True
logger.warning(f"Konnte kein Feld für '{field_labels}' finden oder ausfüllen")
return False
except Exception as e:
logger.error(f"Fehler beim Fuzzy-Ausfüllen des Feldes: {e}")
return False
def click_button_fuzzy(self, button_texts: Union[str, List[str]],
fallback_selector: str = None, threshold: float = 0.7,
timeout: int = 5000) -> bool:
"""
Klickt einen Button mit Fuzzy-Text-Matching.
Args:
button_texts: Text oder Liste von Texten des Buttons
fallback_selector: CSS-Selektor für Fallback
threshold: Schwellenwert für die Textähnlichkeit (0-1)
timeout: Zeitlimit für die Suche in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Normalisiere button_texts zu einer Liste
if isinstance(button_texts, str):
button_texts = [button_texts]
# Logging der Suche
logger.info(f"Suche nach Button mit Texten: {button_texts}")
if not button_texts or button_texts == [[]]:
logger.warning("Leere Button-Text-Liste angegeben!")
return False
# TikTok-spezifische Selektoren zuerst prüfen
# Diese Selektoren sind häufig in TikTok's UI zu finden
tiktok_button_selectors = [
"button[type='submit']",
"button[data-e2e='send-code-button']",
"button.e1w6iovg0",
"button.css-10nhlj9-Button-StyledButton"
]
for selector in tiktok_button_selectors:
if self.browser.is_element_visible(selector, timeout=1000):
button_element = self.browser.wait_for_selector(selector, timeout=1000)
if button_element:
button_text = button_element.inner_text().strip()
# Überprüfe, ob der Button-Text mit einem der gesuchten Texte übereinstimmt
for text in button_texts:
if self.text_similarity.is_similar(text, button_text, threshold=threshold):
logger.info(f"Button mit passendem Text gefunden: '{button_text}'")
button_element.click()
return True
# Die allgemeine fuzzy_click_button-Funktion verwenden
result = click_fuzzy_button(
self.browser.page,
button_texts,
threshold=threshold,
timeout=timeout
)
if result:
logger.info(f"Button mit Fuzzy-Matching geklickt")
return True
# Wenn Fuzzy-Matching fehlschlägt, versuche mit fallback_selector
if fallback_selector:
logger.info(f"Versuche Fallback-Selektor: {fallback_selector}")
if self.browser.click_element(fallback_selector):
logger.info(f"Button mit Fallback-Selektor geklickt: {fallback_selector}")
return True
# Versuche alternative Methoden
# 1. Versuche über aria-label
for text in button_texts:
if not text:
continue
aria_selector = f"button[aria-label*='{text}'], [role='button'][aria-label*='{text}']"
if self.browser.is_element_visible(aria_selector, timeout=1000):
if self.browser.click_element(aria_selector):
logger.info(f"Button über aria-label geklickt: {text}")
return True
# 2. Versuche über role='button' mit Text
for text in button_texts:
if not text:
continue
xpath_selector = f"//div[@role='button' and contains(., '{text}')]"
if self.browser.is_element_visible(xpath_selector, timeout=1000):
if self.browser.click_element(xpath_selector):
logger.info(f"Button über role+text geklickt: {text}")
return True
# 3. Versuche über Link-Text
for text in button_texts:
if not text:
continue
link_selector = f"//a[contains(text(), '{text}')]"
if self.browser.is_element_visible(link_selector, timeout=1000):
if self.browser.click_element(link_selector):
logger.info(f"Link mit passendem Text geklickt: {text}")
return True
# 4. Als letzten Versuch, klicke auf einen beliebigen Button
logger.warning("Kein spezifischer Button gefunden, versuche beliebigen Button zu klicken")
buttons = self.browser.page.query_selector_all("button")
if buttons and len(buttons) > 0:
for button in buttons:
visible = button.is_visible()
if visible:
logger.info("Klicke auf beliebigen sichtbaren Button")
button.click()
return True
logger.warning(f"Konnte keinen Button für '{button_texts}' finden oder klicken")
return False
except Exception as e:
logger.error(f"Fehler beim Fuzzy-Klicken des Buttons: {e}")
return False
def select_dropdown_option(self, dropdown_selector: str, option_value: str,
option_type: str = "text", timeout: int = 5000) -> bool:
"""
Wählt eine Option aus einer Dropdown-Liste aus.
Args:
dropdown_selector: Selektor für das Dropdown-Element
option_value: Wert oder Text der auszuwählenden Option
option_type: "text" für Text-Matching, "value" für Wert-Matching
timeout: Zeitlimit in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Auf Dropdown-Element klicken, um die Optionen anzuzeigen
dropdown_element = self.browser.wait_for_selector(dropdown_selector, timeout=timeout)
if not dropdown_element:
logger.warning(f"Dropdown-Element nicht gefunden: {dropdown_selector}")
return False
# Dropdown öffnen
dropdown_element.click()
time.sleep(0.5) # Kurz warten, damit die Optionen angezeigt werden
# Optionen suchen
option_selector = "div[role='option']"
options = self.browser.page.query_selector_all(option_selector)
if not options or len(options) == 0:
logger.warning(f"Keine Optionen gefunden für Dropdown: {dropdown_selector}")
return False
# Option nach Text oder Wert suchen
selected = False
for option in options:
option_text = option.inner_text().strip()
if option_type == "text":
if option_text == option_value or self.text_similarity.is_similar(option_text, option_value, threshold=0.9):
option.click()
selected = True
break
elif option_type == "value":
option_val = option.get_attribute("value") or ""
if option_val == option_value:
option.click()
selected = True
break
if not selected:
logger.warning(f"Keine passende Option für '{option_value}' gefunden")
return False
logger.info(f"Option '{option_value}' im Dropdown ausgewählt")
return True
except Exception as e:
logger.error(f"Fehler bei der Auswahl der Dropdown-Option: {e}")
return False
def check_for_error(self, error_selectors: List[str] = None,
error_texts: List[str] = None) -> Optional[str]:
"""
Überprüft, ob Fehlermeldungen angezeigt werden.
Args:
error_selectors: Liste mit CSS-Selektoren für Fehlermeldungen
error_texts: Liste mit typischen Fehlertexten
Returns:
Optional[str]: Die Fehlermeldung oder None, wenn keine Fehler gefunden wurden
"""
if not self._ensure_browser():
return None
try:
# Standardselektoren verwenden, wenn keine angegeben sind
if error_selectors is None:
error_selectors = [
"div[role='alert']",
"p[class*='error']",
"span[class*='error']",
".error-message"
]
# Standardfehlertexte verwenden, wenn keine angegeben sind
if error_texts is None:
error_texts = TikTokSelectors.get_error_indicators()
# 1. Nach Fehlerselektoren suchen
for selector in error_selectors:
element = self.browser.wait_for_selector(selector, timeout=2000)
if element:
error_text = element.text_content()
if error_text and len(error_text.strip()) > 0:
logger.info(f"Fehlermeldung gefunden (Selektor): {error_text.strip()}")
return error_text.strip()
# 2. Alle Texte auf der Seite durchsuchen
page_content = self.browser.page.content()
for error_text in error_texts:
if error_text.lower() in page_content.lower():
# Versuche, den genauen Fehlertext zu extrahieren
matches = re.findall(r'<[^>]*>([^<]*' + re.escape(error_text.lower()) + '[^<]*)<', page_content.lower())
if matches:
full_error = matches[0].strip()
logger.info(f"Fehlermeldung gefunden (Text): {full_error}")
return full_error
else:
logger.info(f"Fehlermeldung gefunden (Allgemein): {error_text}")
return error_text
# 3. Nach weiteren Fehlerelementen suchen
elements = self.browser.page.query_selector_all("p, div, span")
for element in elements:
element_text = element.inner_text()
if not element_text:
continue
element_text = element_text.strip()
# Prüfe Textähnlichkeit mit Fehlertexten
for error_text in error_texts:
if self.text_similarity.is_similar(error_text, element_text, threshold=0.7) or \
self.text_similarity.contains_similar_text(element_text, error_texts, threshold=0.7):
logger.info(f"Fehlermeldung gefunden (Ähnlichkeit): {element_text}")
return element_text
return None
except Exception as e:
logger.error(f"Fehler beim Prüfen auf Fehlermeldungen: {e}")
return None
def check_for_captcha(self) -> bool:
"""
Überprüft, ob ein Captcha angezeigt wird.
Returns:
bool: True wenn Captcha erkannt, False sonst
"""
if not self._ensure_browser():
return False
try:
# Selektoren für Captcha-Erkennung
captcha_selectors = [
"div[data-testid='captcha']",
"iframe[src*='captcha']",
"iframe[title*='captcha']",
"iframe[title*='reCAPTCHA']"
]
# Captcha-Texte für textbasierte Erkennung
captcha_texts = [
"captcha", "recaptcha", "sicherheitsüberprüfung", "security check",
"i'm not a robot", "ich bin kein roboter", "verify you're human",
"bestätige, dass du ein mensch bist"
]
# Nach Selektoren suchen
for selector in captcha_selectors:
if self.browser.is_element_visible(selector, timeout=2000):
logger.warning(f"Captcha erkannt (Selektor): {selector}")
return True
# Nach Texten suchen
page_content = self.browser.page.content().lower()
for text in captcha_texts:
if text in page_content:
logger.warning(f"Captcha erkannt (Text): {text}")
return True
return False
except Exception as e:
logger.error(f"Fehler bei der Captcha-Erkennung: {e}")
return False
def wait_for_element(self, selectors: Union[str, List[str]],
timeout: int = 10000, check_interval: int = 500) -> Optional[Any]:
"""
Wartet auf das Erscheinen eines Elements.
Args:
selectors: CSS-Selektor oder Liste von Selektoren
timeout: Zeitlimit in Millisekunden
check_interval: Intervall zwischen den Prüfungen in Millisekunden
Returns:
Optional[Any]: Das gefundene Element oder None, wenn die Zeit abgelaufen ist
"""
if not self._ensure_browser():
return None
try:
# Normalisiere selectors zu einer Liste
if isinstance(selectors, str):
selectors = [selectors]
start_time = time.time()
end_time = start_time + (timeout / 1000)
while time.time() < end_time:
for selector in selectors:
element = self.browser.wait_for_selector(selector, timeout=check_interval)
if element:
logger.info(f"Element mit Selektor '{selector}' gefunden")
return element
# Kurze Pause vor der nächsten Prüfung
time.sleep(check_interval / 1000)
logger.warning(f"Zeitüberschreitung beim Warten auf Element mit Selektoren '{selectors}'")
return None
except Exception as e:
logger.error(f"Fehler beim Warten auf Element: {e}")
return None
def is_registration_successful(self) -> bool:
"""
Überprüft, ob die Registrierung erfolgreich war.
Returns:
bool: True wenn erfolgreich, False sonst
"""
try:
# Erfolgsindikatoren überprüfen
success_indicators = TikTokSelectors.SUCCESS_INDICATORS
for selector in success_indicators:
if self.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Registrierung erfolgreich (Indikator gefunden: {selector})")
return True
# URL überprüfen
current_url = self.browser.page.url
if "/foryou" in current_url or "tiktok.com/explore" in current_url:
logger.info("Registrierung erfolgreich (Erfolgreiche Navigation erkannt)")
return True
# Überprüfen, ob Fehler angezeigt werden
error_message = self.check_for_error()
if error_message:
logger.warning(f"Registrierung nicht erfolgreich: {error_message}")
return False
logger.warning("Konnte Registrierungserfolg nicht bestätigen")
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung des Registrierungserfolgs: {e}")
return False

Datei anzeigen

@ -0,0 +1,495 @@
"""
TikTok-Utils - Hilfsfunktionen für die TikTok-Automatisierung.
"""
import logging
import re
import time
import random
from typing import Dict, List, Any, Optional, Tuple, Union
from .tiktok_selectors import TikTokSelectors
# Konfiguriere Logger
logger = logging.getLogger("tiktok_utils")
class TikTokUtils:
"""
Hilfsfunktionen für die TikTok-Automatisierung.
Enthält allgemeine Hilfsmethoden und kleinere Funktionen.
"""
def __init__(self, automation):
"""
Initialisiert die TikTok-Utils.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
self.selectors = TikTokSelectors()
logger.debug("TikTok-Utils initialisiert")
def _ensure_browser(self) -> bool:
"""
Stellt sicher, dass die Browser-Referenz verfügbar ist.
Returns:
bool: True wenn Browser verfügbar, False sonst
"""
if self.browser is None:
self.browser = self.automation.browser
if self.browser is None:
logger.error("Browser-Referenz nicht verfügbar")
return False
return True
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
"""
if not self._ensure_browser():
return False
try:
# Cookie-Dialoge in TikTok prüfen
cookie_selectors = [
"button[data-e2e='cookie-banner-reject']",
"button:contains('Ablehnen')",
"button:contains('Nur erforderliche')",
"button:contains('Reject')",
"button[data-e2e='cookie-banner-accept']"
]
for selector in cookie_selectors:
if self.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Cookie-Banner erkannt: {selector}")
# Versuche, den Ablehnen-Button zu klicken
if "reject" in selector.lower() or "ablehnen" in selector.lower() or "erforderliche" in selector.lower():
if self.browser.click_element(selector):
logger.info("Cookie-Banner erfolgreich abgelehnt")
time.sleep(random.uniform(0.5, 1.5))
return True
# Fallback: Akzeptieren-Button klicken, wenn Ablehnen nicht funktioniert
else:
if self.browser.click_element(selector):
logger.info("Cookie-Banner erfolgreich akzeptiert")
time.sleep(random.uniform(0.5, 1.5))
return True
# Wenn kein Cookie-Banner gefunden wurde
logger.debug("Kein Cookie-Banner erkannt")
return True
except Exception as e:
logger.error(f"Fehler beim Behandeln des Cookie-Banners: {e}")
return False
def extract_username_from_url(self, url: str) -> Optional[str]:
"""
Extrahiert den Benutzernamen aus einer TikTok-URL.
Args:
url: Die TikTok-URL
Returns:
Optional[str]: Der extrahierte Benutzername oder None
"""
try:
# Muster für Profil-URLs
patterns = [
r'tiktok\.com/@([a-zA-Z0-9._]+)/?(?:$|\?|#)',
r'tiktok\.com/user/([a-zA-Z0-9._]+)/?',
r'tiktok\.com/video/[^/]+/by/([a-zA-Z0-9._]+)/?'
]
for pattern in patterns:
match = re.search(pattern, url)
if match:
username = match.group(1)
# Einige Ausnahmen filtern
if username not in ["explore", "accounts", "video", "foryou", "trending"]:
return username
return None
except Exception as e:
logger.error(f"Fehler beim Extrahieren des Benutzernamens aus der URL: {e}")
return None
def get_current_username(self) -> Optional[str]:
"""
Versucht, den Benutzernamen des aktuell angemeldeten Kontos zu ermitteln.
Returns:
Optional[str]: Der Benutzername oder None, wenn nicht gefunden
"""
if not self._ensure_browser():
return None
try:
# Verschiedene Methoden zur Erkennung des Benutzernamens
# 1. Benutzername aus URL des Profils
profile_link_selectors = [
"a[href*='/@']",
"a[href*='/user/']"
]
for selector in profile_link_selectors:
element = self.browser.wait_for_selector(selector, timeout=2000)
if element:
href = element.get_attribute("href")
if href:
username = self.extract_username_from_url(href)
if username:
logger.info(f"Benutzername aus Profil-Link ermittelt: {username}")
return username
# 2. Profilicon prüfen auf data-e2e-Attribut
profile_icon_selectors = [
"button[data-e2e='profile-icon']",
"svg[data-e2e='profile-icon']"
]
for selector in profile_icon_selectors:
element = self.browser.wait_for_selector(selector, timeout=2000)
if element:
# Prüfen, ob ein Elternelement möglicherweise ein data-e2e-Attribut mit dem Benutzernamen hat
parent = element.evaluate("node => node.parentElement")
if parent:
data_e2e = parent.get_attribute("data-e2e")
if data_e2e and "profile" in data_e2e:
username_match = re.search(r'profile-([a-zA-Z0-9._]+)', data_e2e)
if username_match:
username = username_match.group(1)
logger.info(f"Benutzername aus data-e2e-Attribut ermittelt: {username}")
return username
# 3. TikTok-spezifisches Element mit Benutzername suchen
username_element = self.browser.wait_for_selector("h1[data-e2e='user-title']", timeout=2000)
if username_element:
username = username_element.inner_text().strip()
if username:
logger.info(f"Benutzername aus user-title-Element ermittelt: {username}")
return username
logger.warning("Konnte Benutzernamen nicht ermitteln")
return None
except Exception as e:
logger.error(f"Fehler bei der Ermittlung des Benutzernamens: {e}")
return None
def wait_for_navigation(self, expected_url_pattern: str = None,
timeout: int = 30000, check_interval: int = 500) -> bool:
"""
Wartet, bis die Seite zu einer URL mit einem bestimmten Muster navigiert.
Args:
expected_url_pattern: Erwartetes Muster der URL (Regex)
timeout: Zeitlimit in Millisekunden
check_interval: Intervall zwischen den Prüfungen in Millisekunden
Returns:
bool: True wenn die Navigation erfolgreich war, False sonst
"""
if not self._ensure_browser():
return False
try:
start_time = time.time()
end_time = start_time + (timeout / 1000)
while time.time() < end_time:
current_url = self.browser.page.url
if expected_url_pattern and re.search(expected_url_pattern, current_url):
logger.info(f"Navigation zu URL mit Muster '{expected_url_pattern}' erfolgreich")
return True
# Kurze Pause vor der nächsten Prüfung
time.sleep(check_interval / 1000)
logger.warning(f"Zeitüberschreitung bei Navigation zu URL mit Muster '{expected_url_pattern}'")
return False
except Exception as e:
logger.error(f"Fehler beim Warten auf Navigation: {e}")
return False
def handle_dialog_or_popup(self, expected_text: Union[str, List[str]] = None,
action: str = "close", timeout: int = 5000) -> bool:
"""
Behandelt einen Dialog oder Popup.
Args:
expected_text: Erwarteter Text im Dialog oder Liste von Texten
action: Aktion ("close", "confirm", "cancel")
timeout: Zeitlimit in Millisekunden
Returns:
bool: True wenn der Dialog erfolgreich behandelt wurde, False sonst
"""
if not self._ensure_browser():
return False
try:
# Dialog-Element suchen
dialog_selector = "div[role='dialog']"
dialog_element = self.browser.wait_for_selector(dialog_selector, timeout=timeout)
if not dialog_element:
logger.debug("Kein Dialog gefunden")
return False
logger.info("Dialog gefunden")
# Text im Dialog prüfen, falls angegeben
if expected_text:
if isinstance(expected_text, str):
expected_text = [expected_text]
dialog_text = dialog_element.inner_text()
text_found = False
for text in expected_text:
if text in dialog_text:
logger.info(f"Erwarteter Text im Dialog gefunden: '{text}'")
text_found = True
break
if not text_found:
logger.warning(f"Erwarteter Text nicht im Dialog gefunden: {expected_text}")
return False
# Aktion ausführen
if action == "close":
# Schließen-Button suchen und klicken
close_button_selectors = [
"button[data-e2e='modal-close']",
"svg[data-e2e='modal-close']",
"button.css-1afoydx-StyledCloseButton",
"div[role='dialog'] button:first-child"
]
for selector in close_button_selectors:
if self.browser.is_element_visible(selector, timeout=1000):
if self.browser.click_element(selector):
logger.info("Dialog geschlossen")
return True
# Wenn kein Schließen-Button gefunden wurde, Escape-Taste drücken
self.browser.page.keyboard.press("Escape")
logger.info("Dialog mit Escape-Taste geschlossen")
elif action == "confirm":
# Bestätigen-Button suchen und klicken
confirm_button_selectors = [
"button[type='submit']",
"button:contains('OK')",
"button:contains('Ja')",
"button:contains('Yes')",
"button:contains('Bestätigen')",
"button:contains('Confirm')"
]
for selector in confirm_button_selectors:
if self.browser.is_element_visible(selector, timeout=1000):
if self.browser.click_element(selector):
logger.info("Dialog bestätigt")
return True
elif action == "cancel":
# Abbrechen-Button suchen und klicken
cancel_button_selectors = [
"button:contains('Abbrechen')",
"button:contains('Cancel')",
"button:contains('Nein')",
"button:contains('No')"
]
for selector in cancel_button_selectors:
if self.browser.is_element_visible(selector, timeout=1000):
if self.browser.click_element(selector):
logger.info("Dialog abgebrochen")
return True
logger.warning(f"Konnte keine {action}-Aktion für den Dialog ausführen")
return False
except Exception as e:
logger.error(f"Fehler bei der Dialog-Behandlung: {e}")
return False
def handle_rate_limiting(self, rotate_proxy: bool = True) -> bool:
"""
Behandelt eine Rate-Limiting-Situation.
Args:
rotate_proxy: Ob der Proxy rotiert werden soll
Returns:
bool: True wenn erfolgreich behandelt, False sonst
"""
if not self._ensure_browser():
return False
try:
logger.warning("Rate-Limiting erkannt, warte und versuche es erneut")
# Screenshot erstellen
self.automation._take_screenshot("rate_limit_detected")
# Proxy rotieren, falls gewünscht
if rotate_proxy and self.automation.use_proxy:
success = self.automation._rotate_proxy()
if not success:
logger.warning("Konnte Proxy nicht rotieren")
# Längere Wartezeit
wait_time = random.uniform(120, 300) # 2-5 Minuten
logger.info(f"Warte {wait_time:.1f} Sekunden vor dem nächsten Versuch")
time.sleep(wait_time)
# Seite neuladen
self.browser.page.reload()
self.automation.human_behavior.wait_for_page_load()
# Prüfen, ob Rate-Limiting noch aktiv ist
rate_limit_texts = [
"bitte warte einige minuten",
"please wait a few minutes",
"try again later",
"versuche es später erneut",
"zu viele anfragen",
"too many requests"
]
page_content = self.browser.page.content().lower()
still_rate_limited = False
for text in rate_limit_texts:
if text in page_content:
still_rate_limited = True
break
if still_rate_limited:
logger.warning("Immer noch Rate-Limited nach dem Warten")
return False
else:
logger.info("Rate-Limiting scheint aufgehoben zu sein")
return True
except Exception as e:
logger.error(f"Fehler bei der Behandlung des Rate-Limitings: {e}")
return False
def is_logged_in(self) -> bool:
"""
Überprüft, ob der Benutzer bei TikTok angemeldet ist.
Returns:
bool: True wenn angemeldet, False sonst
"""
if not self._ensure_browser():
return False
try:
# Erfolgsindikatoren überprüfen
success_indicators = TikTokSelectors.SUCCESS_INDICATORS
for selector in success_indicators:
if self.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Benutzer ist angemeldet (Indikator: {selector})")
return True
# URL überprüfen
current_url = self.browser.page.url
if "/foryou" in current_url or "tiktok.com/explore" in current_url:
logger.info("Benutzer ist angemeldet (URL-Check)")
return True
# Anmelden-Button prüfen - wenn sichtbar, dann nicht angemeldet
login_button_selectors = [
TikTokSelectors.LOGIN_BUTTON_LEFT,
TikTokSelectors.LOGIN_BUTTON_RIGHT
]
for selector in login_button_selectors:
if self.browser.is_element_visible(selector, timeout=2000):
logger.info("Benutzer ist nicht angemeldet (Anmelde-Button sichtbar)")
return False
# Profilicon checken - wenn sichtbar, dann angemeldet
profile_selectors = [
"button[data-e2e='profile-icon']",
"svg[data-e2e='profile-icon']"
]
for selector in profile_selectors:
if self.browser.is_element_visible(selector, timeout=2000):
logger.info("Benutzer ist angemeldet (Profilicon sichtbar)")
return True
logger.warning("Konnte Login-Status nicht eindeutig bestimmen")
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung des Login-Status: {e}")
return False
def extract_verification_code_from_email(self, email_body: str) -> Optional[str]:
"""
Extrahiert den Verifizierungscode aus einer E-Mail.
Args:
email_body: Der E-Mail-Text
Returns:
Optional[str]: Der Verifizierungscode oder None, wenn nicht gefunden
"""
try:
# Muster für TikTok-Verifizierungscodes
patterns = [
r'(\d{6}) ist dein Bestätigungscode',
r'(\d{6}) ist dein TikTok-Code',
r'(\d{6}) is your TikTok code',
r'(\d{6}) is your verification code',
r'Dein Bestätigungscode lautet (\d{6})',
r'Your verification code is (\d{6})',
r'Verification code: (\d{6})',
r'Bestätigungscode: (\d{6})',
r'TikTok code: (\d{6})',
r'TikTok-Code: (\d{6})'
]
for pattern in patterns:
match = re.search(pattern, email_body)
if match:
code = match.group(1)
logger.info(f"Verifizierungscode aus E-Mail extrahiert: {code}")
return code
# Allgemeine Suche nach 6-stelligen Zahlen, wenn keine spezifischen Muster passen
general_match = re.search(r'[^\d](\d{6})[^\d]', email_body)
if general_match:
code = general_match.group(1)
logger.info(f"6-stelliger Code aus E-Mail extrahiert: {code}")
return code
logger.warning("Kein Verifizierungscode in der E-Mail gefunden")
return None
except Exception as e:
logger.error(f"Fehler beim Extrahieren des Verifizierungscodes aus der E-Mail: {e}")
return None

Datei anzeigen

@ -0,0 +1,457 @@
# social_networks/tiktok/tiktok_verification.py
"""
TikTok-Verifizierung - Klasse für die Verifizierungsfunktionalitä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_verification")
class TikTokVerification:
"""
Klasse für die Verifizierung von TikTok-Konten.
Enthält alle Methoden für den Verifizierungsprozess.
"""
def __init__(self, automation):
"""
Initialisiert die TikTok-Verifizierung.
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_verification_workflow()
logger.debug("TikTok-Verifizierung initialisiert")
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
"""
Führt den Verifizierungsprozess für ein TikTok-Konto durch.
Args:
verification_code: Der Bestätigungscode
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Verifizierung mit Status
"""
# Hole Browser-Referenz von der Hauptklasse
self.browser = self.automation.browser
# Validiere den Verifizierungscode
if not self._validate_verification_code(verification_code):
return {
"success": False,
"error": "Ungültiger Verifizierungscode",
"stage": "code_validation"
}
try:
# 1. Überprüfen, ob wir auf der Verifizierungsseite sind
if not self._is_on_verification_page():
# Versuche, zur Verifizierungsseite zu navigieren, falls möglich
# Direktnavigation ist jedoch normalerweise nicht möglich
return {
"success": False,
"error": "Nicht auf der Verifizierungsseite",
"stage": "page_check"
}
# 2. Verifizierungscode eingeben und absenden
if not self.enter_and_submit_verification_code(verification_code):
return {
"success": False,
"error": "Fehler beim Eingeben oder Absenden des Verifizierungscodes",
"stage": "code_entry"
}
# 3. Überprüfen, ob die Verifizierung erfolgreich war
success, error_message = self._check_verification_success()
if not success:
return {
"success": False,
"error": f"Verifizierung fehlgeschlagen: {error_message or 'Unbekannter Fehler'}",
"stage": "verification_check"
}
# 4. Zusätzliche Dialoge behandeln
self._handle_post_verification_dialogs()
# Verifizierung erfolgreich
logger.info("TikTok-Verifizierung erfolgreich abgeschlossen")
return {
"success": True,
"stage": "completed"
}
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der TikTok-Verifizierung: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"stage": "exception"
}
def _validate_verification_code(self, verification_code: str) -> bool:
"""
Validiert den Verifizierungscode.
Args:
verification_code: Der zu validierende Code
Returns:
bool: True wenn der Code gültig ist, False sonst
"""
# Leerer Code
if not verification_code:
logger.error("Verifizierungscode ist leer")
return False
# Code-Format prüfen (normalerweise 6-stellige Zahl)
if not re.match(r"^\d{6}$", verification_code):
logger.warning(f"Verifizierungscode hat unerwartetes Format: {verification_code}")
# Wir geben trotzdem True zurück, da einige Codes andere Formate haben könnten
return True
return True
def _is_on_verification_page(self) -> bool:
"""
Überprüft, ob wir auf der Verifizierungsseite sind.
Returns:
bool: True wenn auf der Verifizierungsseite, False sonst
"""
try:
# Screenshot erstellen
self.automation._take_screenshot("verification_page_check")
# Nach Verifizierungsfeld suchen
verification_selectors = [
TikTokSelectors.VERIFICATION_CODE_FIELD,
"input[placeholder*='Code']",
"input[placeholder*='code']",
"input[placeholder*='sechsstelligen']",
"input[data-e2e='verification-code-input']"
]
for selector in verification_selectors:
if self.browser.is_element_visible(selector, timeout=3000):
logger.info("Auf Verifizierungsseite")
return True
# Textbasierte Erkennung
verification_texts = [
"Bestätigungscode",
"Verification code",
"sechsstelligen Code",
"6-digit code",
"Code senden",
"Send code"
]
page_content = self.browser.page.content().lower()
for text in verification_texts:
if text.lower() in page_content:
logger.info(f"Auf Verifizierungsseite (erkannt durch Text: {text})")
return True
logger.warning("Nicht auf der Verifizierungsseite")
return False
except Exception as e:
logger.error(f"Fehler beim Überprüfen der Verifizierungsseite: {e}")
return False
def enter_and_submit_verification_code(self, verification_code: str) -> bool:
"""
Gibt den Verifizierungscode ein und sendet ihn ab.
Args:
verification_code: Der einzugebende Verifizierungscode
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.info(f"Versuche Verifizierungscode einzugeben: {verification_code}")
# Mögliche Selektoren für das Verifizierungscode-Feld
code_field_selectors = [
TikTokSelectors.VERIFICATION_CODE_FIELD,
"input[placeholder*='Code']",
"input[placeholder*='sechsstelligen Code']",
"input[data-e2e='verification-code-input']"
]
# Versuche, das Feld zu finden und auszufüllen
code_field_found = False
for selector in code_field_selectors:
logger.debug(f"Versuche Selektor: {selector}")
if self.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Codefeld gefunden mit Selektor: {selector}")
if self.browser.fill_form_field(selector, verification_code):
code_field_found = True
logger.info(f"Verifizierungscode eingegeben: {verification_code}")
break
# Versuche es mit der Fuzzy-Matching-Methode, wenn direkte Selektoren fehlschlagen
if not code_field_found:
logger.info("Versuche Fuzzy-Matching für Codefeld")
code_field_found = self.automation.ui_helper.fill_field_fuzzy(
["Bestätigungscode", "Code eingeben", "Verification code", "6-digit code"],
verification_code
)
if not code_field_found:
logger.error("Konnte Verifizierungscode-Feld nicht finden oder ausfüllen")
# Erstelle einen Screenshot zum Debuggen
self.automation._take_screenshot("code_field_not_found")
return False
# Menschliche Verzögerung vor dem Absenden
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot erstellen
self.automation._take_screenshot("verification_code_entered")
# "Weiter"-Button finden und klicken
weiter_button_selectors = [
TikTokSelectors.WEITER_BUTTON,
"button[type='submit']",
"button.e1w6iovg0",
"button[data-e2e='next-button']",
"//button[contains(text(), 'Weiter')]"
]
weiter_button_found = False
logger.info("Suche nach Weiter-Button")
for selector in weiter_button_selectors:
logger.debug(f"Versuche Weiter-Button-Selektor: {selector}")
if self.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Weiter-Button gefunden mit Selektor: {selector}")
if self.browser.click_element(selector):
weiter_button_found = True
logger.info("Verifizierungscode-Formular abgesendet")
break
# Versuche es mit der Fuzzy-Matching-Methode, wenn direkte Selektoren fehlschlagen
if not weiter_button_found:
logger.info("Versuche Fuzzy-Matching für Weiter-Button")
weiter_buttons = ["Weiter", "Next", "Continue", "Fertig", "Submit", "Verify", "Senden"]
weiter_button_found = self.automation.ui_helper.click_button_fuzzy(
weiter_buttons
)
if not weiter_button_found:
# Erstelle einen Screenshot zum Debuggen
self.automation._take_screenshot("weiter_button_not_found")
# Versuche es mit Enter-Taste als letzten Ausweg
logger.info("Konnte Weiter-Button nicht finden, versuche Enter-Taste")
self.browser.page.keyboard.press("Enter")
logger.info("Enter-Taste zur Bestätigung des Verifizierungscodes gedrückt")
weiter_button_found = True
# Warten nach dem Absenden
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
return weiter_button_found
except Exception as e:
logger.error(f"Fehler beim Eingeben und Absenden des Verifizierungscodes: {e}")
return False
def _check_verification_success(self) -> Tuple[bool, Optional[str]]:
"""
Überprüft, ob die Verifizierung erfolgreich war.
Returns:
Tuple[bool, Optional[str]]: (Erfolg, Fehlermeldung falls vorhanden)
"""
try:
# Warten nach der Verifizierung
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
# Screenshot erstellen
self.automation._take_screenshot("verification_result")
# Immer noch auf der Verifizierungsseite?
still_on_verification = self._is_on_verification_page()
if still_on_verification:
# Fehlermeldung suchen
error_message = self.automation.ui_helper.check_for_error()
if error_message:
logger.error(f"Verifizierungsfehler: {error_message}")
return False, error_message
else:
logger.error("Verifizierung fehlgeschlagen, immer noch auf der Verifizierungsseite")
return False, "Immer noch auf der Verifizierungsseite"
# Prüfe, ob wir zur Benutzernamen-Erstellung weitergeleitet wurden
username_selectors = [
"input[placeholder='Benutzername']",
"input[name='new-username']",
"//input[@placeholder='Benutzername']"
]
for selector in username_selectors:
if self.browser.is_element_visible(selector, timeout=2000):
logger.info("Verifizierung erfolgreich, zur Benutzernamenauswahl weitergeleitet")
return True, None
# Prüfe auf TikTok-Startseite
current_url = self.browser.page.url
if "tiktok.com" in current_url and "/login" not in current_url and "/signup" not in current_url:
logger.info("Verifizierung erfolgreich, jetzt auf der Startseite")
return True, None
# Wenn keine eindeutigen Indikatoren gefunden wurden, aber auch keine Fehler
logger.warning("Keine eindeutigen Erfolgsindikatoren für die Verifizierung gefunden")
return True, None # Wir gehen davon aus, dass es erfolgreich war
except Exception as e:
logger.error(f"Fehler beim Überprüfen des Verifizierungserfolgs: {e}")
return False, f"Fehler bei der Erfolgsprüfung: {str(e)}"
def _handle_post_verification_dialogs(self) -> None:
"""
Behandelt Dialoge, die nach erfolgreicher Verifizierung erscheinen können.
"""
try:
# Liste der möglichen Dialoge und wie man sie überspringt
dialogs_to_handle = [
{
"name": "username_setup",
"skip_texts": ["Überspringen", "Skip"],
"skip_selectors": ["//button[contains(text(), 'Überspringen')]", "//button[contains(text(), 'Skip')]"]
},
{
"name": "interests",
"skip_texts": ["Überspringen", "Skip"],
"skip_selectors": ["//button[contains(text(), 'Überspringen')]", "//button[contains(text(), 'Skip')]"]
},
{
"name": "follow_accounts",
"skip_texts": ["Überspringen", "Skip"],
"skip_selectors": ["//button[contains(text(), 'Überspringen')]", "//button[contains(text(), 'Skip')]"]
},
{
"name": "notifications",
"skip_texts": ["Später", "Nein", "Nicht jetzt", "Later", "No", "Not now"],
"skip_selectors": ["//button[contains(text(), 'Später')]", "//button[contains(text(), 'Not now')]"]
}
]
# Versuche, jeden möglichen Dialog zu behandeln
for dialog in dialogs_to_handle:
self._try_skip_dialog(dialog)
logger.info("Nachverifizierungs-Dialoge behandelt")
except Exception as e:
logger.warning(f"Fehler beim Behandeln der Nachverifizierungs-Dialoge: {e}")
# Nicht kritisch, daher keine Fehlerbehandlung
def _try_skip_dialog(self, dialog: Dict[str, Any]) -> bool:
"""
Versucht, einen bestimmten Dialog zu überspringen.
Args:
dialog: Informationen zum Dialog
Returns:
bool: True wenn Dialog gefunden und übersprungen, False sonst
"""
try:
# Zuerst mit Fuzzy-Matching versuchen
skip_clicked = self.automation.ui_helper.click_button_fuzzy(
dialog["skip_texts"],
threshold=0.7,
timeout=3000
)
if skip_clicked:
logger.info(f"Dialog '{dialog['name']}' mit Fuzzy-Matching übersprungen")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
# Wenn Fuzzy-Matching fehlschlägt, direkte Selektoren versuchen
for selector in dialog["skip_selectors"]:
if self.browser.is_element_visible(selector, timeout=1000):
if self.browser.click_element(selector):
logger.info(f"Dialog '{dialog['name']}' mit direktem Selektor übersprungen")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
return False
except Exception as e:
logger.warning(f"Fehler beim Versuch, Dialog '{dialog['name']}' zu überspringen: {e}")
return False
def resend_verification_code(self) -> bool:
"""
Versucht, den Verifizierungscode erneut senden zu lassen.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Resend-Button suchen und klicken
resend_selectors = [
"button[data-e2e='send-code-button']",
"//button[contains(text(), 'Code senden')]",
"//button[contains(text(), 'Code erneut senden')]",
"//button[contains(text(), 'Erneut senden')]",
"a[data-e2e='resend-code-link']"
]
for selector in resend_selectors:
if self.browser.is_element_visible(selector, timeout=2000):
if self.browser.click_element(selector):
logger.info("Code erneut angefordert")
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
# Fuzzy-Matching versuchen
resend_texts = ["Code senden", "Code erneut senden", "Erneut senden", "Resend code", "Send again"]
resend_clicked = self.automation.ui_helper.click_button_fuzzy(
resend_texts,
threshold=0.7,
timeout=3000
)
if resend_clicked:
logger.info("Code erneut angefordert (über Fuzzy-Matching)")
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
logger.warning("Konnte keinen 'Code erneut senden'-Button finden")
return False
except Exception as e:
logger.error(f"Fehler beim erneuten Anfordern des Codes: {e}")
return False

Datei anzeigen

@ -0,0 +1,427 @@
"""
TikTok-Workflow - Definiert die Schritte für die TikTok-Anmeldung und -Registrierung
"""
import logging
from typing import Dict, List, Any, Optional, Tuple
import re
from utils.text_similarity import TextSimilarity
# Konfiguriere Logger
logger = logging.getLogger("tiktok_workflow")
class TikTokWorkflow:
"""
Definiert die Workflow-Schritte für verschiedene TikTok-Aktionen
wie Registrierung, Anmeldung und Verifizierung.
"""
# Text-Ähnlichkeits-Threshold für Fuzzy-Matching
SIMILARITY_THRESHOLD = 0.7
# Initialisiere TextSimilarity für Matching
text_similarity = TextSimilarity(default_threshold=SIMILARITY_THRESHOLD)
# Mögliche alternative Texte für verschiedene UI-Elemente
TEXT_ALTERNATIVES = {
"email": ["E-Mail", "Email", "E-mail", "Mail", "email"],
"phone": ["Telefon", "Telefonnummer", "Phone", "Mobile", "mobile"],
"password": ["Passwort", "Password", "pass"],
"code": ["Code", "Bestätigungscode", "Verification code", "Sicherheitscode"],
"username": ["Benutzername", "Username", "user name"],
"submit": ["Registrieren", "Sign up", "Anmelden", "Login", "Log in", "Submit"],
"next": ["Weiter", "Next", "Continue", "Fortfahren"],
"skip": ["Überspringen", "Skip", "Later", "Später", "Not now", "Nicht jetzt"]
}
@staticmethod
def get_registration_workflow(registration_method: str = "email") -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die TikTok-Registrierung zurück.
Args:
registration_method: "email" oder "phone"
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
# Basisschritte für beide Methoden
common_steps = [
{
"name": "navigate_to_signup",
"description": "Zur TikTok-Startseite navigieren",
"url": "https://www.tiktok.com",
"wait_for": ["button#header-login-button", "button#top-right-action-bar-login-button"],
"fuzzy_match": None
},
{
"name": "click_login_button",
"description": "Anmelden-Button klicken",
"action": "click",
"target": "button#top-right-action-bar-login-button",
"wait_for": ["a[href*='/signup']", "div[role='dialog']"],
"fuzzy_match": ["Anmelden", "Sign in", "Log in"]
},
{
"name": "click_register_link",
"description": "Registrieren-Link klicken",
"action": "click",
"target": "a[href*='/signup']",
"wait_for": ["div[data-e2e='channel-item']"],
"fuzzy_match": ["Registrieren", "Sign up", "Register"]
},
{
"name": "click_phone_email_button",
"description": "Telefon/E-Mail-Option auswählen",
"action": "click",
"target": "div[data-e2e='channel-item']",
"wait_for": ["a[href*='/signup/phone-or-email/email']", "a[href*='/signup/phone-or-email/phone']"],
"fuzzy_match": ["Telefonnummer oder E-Mail-Adresse", "Phone or Email"]
}
]
# Spezifische Schritte je nach Registrierungsmethode
method_steps = []
if registration_method == "email":
method_steps.append({
"name": "click_email_registration",
"description": "Mit E-Mail registrieren auswählen",
"action": "click",
"target": "a[href*='/signup/phone-or-email/email']",
"wait_for": ["input[placeholder='E-Mail-Adresse']"],
"fuzzy_match": ["Mit E-Mail-Adresse registrieren", "Email", "E-Mail"]
})
else: # phone
method_steps.append({
"name": "click_phone_registration",
"description": "Mit Telefonnummer registrieren auswählen",
"action": "click",
"target": "a[href*='/signup/phone-or-email/phone']",
"wait_for": ["input[placeholder='Telefonnummer']"],
"fuzzy_match": ["Mit Telefonnummer registrieren", "Phone", "Telefon"]
})
# Geburtsdatum-Schritte
birthday_steps = [
{
"name": "select_birth_month",
"description": "Geburtsmonat auswählen",
"action": "click",
"target": "div.css-1fi2hzv-DivSelectLabel:contains('Monat')",
"wait_for": ["div[role='option']"],
"fuzzy_match": ["Monat", "Month"]
},
{
"name": "select_month_option",
"description": "Monats-Option auswählen",
"action": "select_option",
"target": "div[role='option']",
"value": "{MONTH_NAME}",
"wait_for": [],
"fuzzy_match": None
},
{
"name": "select_birth_day",
"description": "Geburtstag auswählen",
"action": "click",
"target": "div.css-1fi2hzv-DivSelectLabel:contains('Tag')",
"wait_for": ["div[role='option']"],
"fuzzy_match": ["Tag", "Day"]
},
{
"name": "select_day_option",
"description": "Tags-Option auswählen",
"action": "select_option",
"target": "div[role='option']",
"value": "{DAY}",
"wait_for": [],
"fuzzy_match": None
},
{
"name": "select_birth_year",
"description": "Geburtsjahr auswählen",
"action": "click",
"target": "div.css-1fi2hzv-DivSelectLabel:contains('Jahr')",
"wait_for": ["div[role='option']"],
"fuzzy_match": ["Jahr", "Year"]
},
{
"name": "select_year_option",
"description": "Jahres-Option auswählen",
"action": "select_option",
"target": "div[role='option']",
"value": "{YEAR}",
"wait_for": [],
"fuzzy_match": None
}
]
# Formularschritte für E-Mail
email_form_steps = [
{
"name": "fill_email",
"description": "E-Mail-Adresse eingeben",
"action": "fill",
"target": "input[placeholder='E-Mail-Adresse']",
"value": "{EMAIL}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["email"]
},
{
"name": "fill_password",
"description": "Passwort eingeben",
"action": "fill",
"target": "input[placeholder='Passwort']",
"value": "{PASSWORD}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["password"]
},
{
"name": "click_send_code",
"description": "Code senden klicken",
"action": "click",
"target": "button[data-e2e='send-code-button']",
"wait_for": ["input[placeholder*='sechsstelligen Code']"],
"fuzzy_match": ["Code senden", "Send code", "Senden"]
},
{
"name": "fill_verification_code",
"description": "Bestätigungscode eingeben",
"action": "fill",
"target": "input[placeholder*='sechsstelligen Code']",
"value": "{VERIFICATION_CODE}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["code"]
},
{
"name": "click_continue",
"description": "Weiter klicken",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["input[placeholder='Benutzername']"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["next"]
}
]
# Formularschritte für Telefon
phone_form_steps = [
{
"name": "select_country_code",
"description": "Ländervorwahl auswählen",
"action": "click",
"target": "div[role='combobox']",
"wait_for": ["div[role='option']"],
"fuzzy_match": None
},
{
"name": "select_country_option",
"description": "Land auswählen",
"action": "select_option",
"target": "div[role='option']",
"value": "{COUNTRY_NAME}",
"wait_for": [],
"fuzzy_match": None
},
{
"name": "fill_phone",
"description": "Telefonnummer eingeben",
"action": "fill",
"target": "input[placeholder='Telefonnummer']",
"value": "{PHONE}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["phone"]
},
{
"name": "click_send_code",
"description": "Code senden klicken",
"action": "click",
"target": "button[data-e2e='send-code-button']",
"wait_for": ["input[placeholder*='sechsstelligen Code']"],
"fuzzy_match": ["Code senden", "Send code", "Senden"]
},
{
"name": "fill_verification_code",
"description": "Bestätigungscode eingeben",
"action": "fill",
"target": "input[placeholder*='sechsstelligen Code']",
"value": "{VERIFICATION_CODE}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["code"]
},
{
"name": "click_continue",
"description": "Weiter klicken",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["input[placeholder='Benutzername']"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["next"]
}
]
# Benutzername-Schritte
username_steps = [
{
"name": "fill_username",
"description": "Benutzernamen eingeben",
"action": "fill",
"target": "input[placeholder='Benutzername']",
"value": "{USERNAME}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["username"]
},
{
"name": "click_register",
"description": "Registrieren klicken",
"action": "click",
"target": "button:contains('Registrieren')",
"wait_for": ["a[href='/foryou']", "button:contains('Überspringen')"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["submit"]
},
{
"name": "handle_skip_option",
"description": "Optional: Überspringen klicken",
"action": "click",
"target": "button:contains('Überspringen')",
"optional": True,
"wait_for": ["a[href='/foryou']"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["skip"]
}
]
# Vollständigen Workflow zusammenstellen
if registration_method == "email":
workflow = common_steps + method_steps + birthday_steps + email_form_steps + username_steps
else: # phone
workflow = common_steps + method_steps + birthday_steps + phone_form_steps + username_steps
return workflow
@staticmethod
def get_login_workflow() -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die TikTok-Anmeldung zurück.
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
login_steps = [
{
"name": "navigate_to_login",
"description": "Zur TikTok-Startseite navigieren",
"url": "https://www.tiktok.com",
"wait_for": ["button#header-login-button", "button#top-right-action-bar-login-button"],
"fuzzy_match": None
},
{
"name": "click_login_button",
"description": "Anmelden-Button klicken",
"action": "click",
"target": "button#top-right-action-bar-login-button",
"wait_for": ["div[role='dialog']"],
"fuzzy_match": ["Anmelden", "Sign in", "Log in"]
},
{
"name": "click_phone_email_button",
"description": "Telefon/E-Mail-Option auswählen",
"action": "click",
"target": "div[data-e2e='channel-item']",
"wait_for": ["input[type='text']"],
"fuzzy_match": ["Telefon-Nr./E-Mail/Anmeldename", "Phone or Email"]
},
{
"name": "fill_login_field",
"description": "Benutzername/E-Mail/Telefon eingeben",
"action": "fill",
"target": "input[type='text']",
"value": "{USERNAME_OR_EMAIL}",
"fuzzy_match": ["Email", "Benutzername", "Telefon"]
},
{
"name": "fill_password",
"description": "Passwort eingeben",
"action": "fill",
"target": "input[type='password']",
"value": "{PASSWORD}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["password"]
},
{
"name": "click_login",
"description": "Anmelden klicken",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["a[href='/foryou']"],
"fuzzy_match": ["Anmelden", "Log in", "Login"]
}
]
return login_steps
@staticmethod
def get_verification_workflow() -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die TikTok-Verifizierung zurück.
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
verification_steps = [
{
"name": "fill_verification_code",
"description": "Bestätigungscode eingeben",
"action": "fill",
"target": "input[placeholder*='sechsstelligen Code']",
"value": "{VERIFICATION_CODE}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["code"]
},
{
"name": "click_continue",
"description": "Weiter klicken",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["input[placeholder='Benutzername']", "a[href='/foryou']"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["next"]
}
]
return verification_steps
@staticmethod
def identify_current_step(page_title: str, page_url: str, visible_elements: List[str]) -> str:
"""
Identifiziert den aktuellen Schritt basierend auf dem Seitentitel, der URL und sichtbaren Elementen.
Args:
page_title: Titel der Seite
page_url: URL der Seite
visible_elements: Liste sichtbarer Elemente (Selektoren)
Returns:
str: Name des identifizierten Schritts
"""
# Auf der Startseite
if "tiktok.com" in page_url and not "/signup" in page_url and not "/login" in page_url:
return "navigate_to_signup"
# Anmelde-/Registrierungsauswahl
if "signup" in page_url or "login" in page_url:
if any("channel-item" in element for element in visible_elements):
return "click_phone_email_button"
# Geburtsdatum
if "Monat" in page_title or "Month" in page_title or any("Geburtsdatum" in element for element in visible_elements):
return "select_birth_month"
# E-Mail-/Telefon-Eingabe
if any("E-Mail-Adresse" in element for element in visible_elements):
return "fill_email"
if any("Telefonnummer" in element for element in visible_elements):
return "fill_phone"
# Bestätigungscode
if any("sechsstelligen Code" in element for element in visible_elements):
return "fill_verification_code"
# Benutzernamen-Erstellung
if any("Benutzername" in element for element in visible_elements):
return "fill_username"
# Erfolgreiche Anmeldung
if "foryou" in page_url or any("Für dich" in element for element in visible_elements):
return "logged_in"
return "unknown"