Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

Datei anzeigen

Datei anzeigen

@ -0,0 +1,392 @@
"""
TikTok-Automatisierung - Hauptklasse für TikTok-Automatisierungsfunktionalität
"""
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
from utils.logger import setup_logger
# 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 = setup_logger("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,
window_position = None,
fingerprint = None,
auto_close_browser: bool = False):
"""
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)
window_position: Optional - Fensterposition als Tuple (x, y)
fingerprint: Optional - Vordefinierter Browser-Fingerprint
auto_close_browser: Ob Browser automatisch geschlossen werden soll (Standard: False)
"""
# 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,
window_position=window_position,
auto_close_browser=auto_close_browser
)
# 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)
# Nutze übergebenen Fingerprint wenn vorhanden
self.provided_fingerprint = fingerprint
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
if self.provided_fingerprint:
# Nutze den bereitgestellten Fingerprint
logger.info("Verwende bereitgestellten Fingerprint für Account-Erstellung")
# Konvertiere Dict zu BrowserFingerprint wenn nötig
if isinstance(self.provided_fingerprint, dict):
from domain.entities.browser_fingerprint import BrowserFingerprint
fingerprint_obj = BrowserFingerprint.from_dict(self.provided_fingerprint)
else:
fingerprint_obj = self.provided_fingerprint
# Wende Fingerprint über FingerprintProtection an
from browser.fingerprint_protection import FingerprintProtection
protection = FingerprintProtection(
context=self.browser.context,
fingerprint_config=fingerprint_obj
)
protection.apply_to_context(self.browser.context)
logger.info(f"Fingerprint {fingerprint_obj.fingerprint_id} angewendet")
else:
# Fallback: Zufällige Fingerprint-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}")
self._emit_customer_log(f"🎵 TikTok-Account wird erstellt für: {full_name}")
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()
def get_current_page(self):
"""
Gibt die aktuelle Playwright Page-Instanz zurück.
Returns:
Page: Die aktuelle Seite oder None
"""
if self.browser and hasattr(self.browser, 'page'):
return self.browser.page
return None
def get_session_data(self) -> Dict[str, Any]:
"""
Extrahiert Session-Daten (Cookies, LocalStorage, etc.) aus dem aktuellen Browser.
Returns:
Dict[str, Any]: Session-Daten
"""
if not self.is_browser_open():
return {}
try:
return {
"cookies": self.browser.page.context.cookies(),
"local_storage": self.browser.page.evaluate("() => Object.assign({}, window.localStorage)"),
"session_storage": self.browser.page.evaluate("() => Object.assign({}, window.sessionStorage)"),
"url": self.browser.page.url
}
except Exception as e:
logger.error(f"Fehler beim Extrahieren der Session-Daten: {e}")
return {}

Datei anzeigen

@ -0,0 +1,825 @@
"""
TikTok-Login - Klasse für die Anmeldefunktionalität bei TikTok
"""
import time
import re
from typing import Dict, List, Any, Optional, Tuple
from .tiktok_selectors import TikTokSelectors
from .tiktok_workflow import TikTokWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("tiktok_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
# Browser wird direkt von automation verwendet
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
"""
# Browser wird direkt von automation verwendet
# 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 über die Explore-Seite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Zur Explore-Seite navigieren
logger.info("Navigiere zur TikTok Explore-Seite")
self.automation.browser.navigate_to(TikTokSelectors.EXPLORE_URL)
# Warten, bis die Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Screenshot erstellen
self.automation._take_screenshot("tiktok_explore_page")
# Login-Button auf der Explore-Seite suchen und klicken
logger.info("Suche Anmelden-Button auf Explore-Seite")
login_button_selectors = [
"button#header-login-button",
"button.TUXButton--primary",
"button:has-text('Anmelden')",
TikTokSelectors.LOGIN_BUTTON_HEADER,
"button[class*='StyledLeftSidePrimaryButton']"
]
button_clicked = False
for selector in login_button_selectors:
logger.debug(f"Versuche Login-Button: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=2000):
# Kurz warten vor dem Klick
self.automation.human_behavior.random_delay(0.5, 1.0)
if self.automation.browser.click_element(selector):
button_clicked = True
logger.info(f"Anmelden-Button erfolgreich geklickt: {selector}")
break
if not button_clicked:
logger.error("Konnte keinen Anmelden-Button auf der Explore-Seite finden")
self.automation._take_screenshot("no_login_button_found")
return False
# Warten, bis der Login-Dialog erscheint
logger.info("Warte auf Login-Dialog")
self.automation.human_behavior.random_delay(2.0, 3.0)
# Prüfen, ob der Login-Dialog sichtbar ist
dialog_visible = False
dialog_selectors = [
"div[role='dialog']",
TikTokSelectors.LOGIN_DIALOG,
"div[class*='login-modal']",
"div[class*='DivLoginContainer']"
]
for dialog_selector in dialog_selectors:
if self.automation.browser.is_element_visible(dialog_selector, timeout=5000):
dialog_visible = True
logger.info(f"Login-Dialog erschienen: {dialog_selector}")
break
if not dialog_visible:
logger.error("Login-Dialog ist nach 5 Sekunden nicht erschienen")
self.automation._take_screenshot("no_login_dialog")
return False
# Screenshot vom geöffneten Dialog
self.automation._take_screenshot("login_dialog_opened")
logger.info("Erfolgreich zum Login-Dialog 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.
Akzeptiert IMMER Cookies für vollständiges Session-Management beim Login.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
# Cookie-Dialog-Erkennung
if self.automation.browser.is_element_visible(TikTokSelectors.COOKIE_DIALOG, timeout=2000):
logger.info("Cookie-Banner erkannt - akzeptiere alle Cookies für Session-Management")
# Akzeptieren-Button suchen und klicken (PRIMÄR für Login)
accept_success = self.automation.ui_helper.click_button_fuzzy(
TikTokSelectors.get_button_texts("accept_cookies"),
TikTokSelectors.COOKIE_ACCEPT_BUTTON
)
if accept_success:
logger.info("Cookie-Banner erfolgreich akzeptiert - Session-Cookies werden gespeichert")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
else:
logger.warning("Konnte Cookie-Banner nicht akzeptieren, versuche alternativen Akzeptieren-Button")
# Alternative Akzeptieren-Selektoren versuchen
alternative_accept_selectors = [
"//button[contains(text(), 'Alle akzeptieren')]",
"//button[contains(text(), 'Accept All')]",
"//button[contains(text(), 'Zulassen')]",
"//button[contains(text(), 'Allow All')]",
"//button[contains(@aria-label, 'Accept')]",
"[data-testid='accept-all-button']"
]
for selector in alternative_accept_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info("Cookie-Banner mit alternativem Selector akzeptiert")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte Cookie-Banner nicht akzeptieren - Session-Management könnte beeinträchtigt sein")
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:
# 1. E-Mail/Telefon-Login-Option auswählen
logger.info("Klicke auf 'Telefon-Nr./E-Mail/Anmeldename nutzen'")
# Warte kurz, damit Dialog vollständig geladen ist
self.automation.human_behavior.random_delay(1.0, 1.5)
# Selektoren für die Option - spezifischer für E-Mail/Telefon
phone_email_selectors = [
"div[data-e2e='channel-item']:has(p:has-text('Telefon-Nr./E-Mail/Anmeldename nutzen'))",
"div[role='link']:has-text('Telefon-Nr./E-Mail/Anmeldename nutzen')",
"//div[@data-e2e='channel-item'][.//p[contains(text(), 'Telefon-Nr./E-Mail/Anmeldename nutzen')]]",
"div.css-17hparj-DivBoxContainer:has-text('Telefon-Nr./E-Mail/Anmeldename nutzen')",
"//div[contains(@class, 'DivBoxContainer') and contains(., 'Telefon-Nr./E-Mail/Anmeldename nutzen')]"
]
email_phone_clicked = False
for selector in phone_email_selectors:
logger.debug(f"Versuche E-Mail/Telefon-Selektor: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=2000):
# Kurz warten vor dem Klick
self.automation.human_behavior.random_delay(0.3, 0.5)
if self.automation.browser.click_element(selector):
email_phone_clicked = True
logger.info(f"E-Mail/Telefon-Option erfolgreich geklickt: {selector}")
break
else:
logger.warning(f"Klick fehlgeschlagen für Selektor: {selector}")
if not email_phone_clicked:
logger.error("Konnte 'Telefon-Nr./E-Mail/Anmeldename nutzen' nicht klicken")
self.automation._take_screenshot("phone_email_option_not_found")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# 2. "Mit E-Mail-Adresse oder Benutzernamen anmelden" Link klicken
logger.info("Klicke auf 'Mit E-Mail-Adresse oder Benutzernamen anmelden'")
email_link_selectors = [
"a[href='/login/phone-or-email/email']",
"a.css-1mgli76-ALink-StyledLink",
"a:has-text('Mit E-Mail-Adresse oder Benutzernamen anmelden')",
"//a[contains(text(), 'Mit E-Mail-Adresse oder Benutzernamen anmelden')]"
]
email_login_clicked = False
for selector in email_link_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
if self.automation.browser.click_element(selector):
email_login_clicked = True
logger.info(f"E-Mail-Login-Link geklickt: {selector}")
break
if not email_login_clicked:
logger.error("Konnte E-Mail-Login-Link nicht klicken")
self.automation._take_screenshot("email_login_link_not_found")
return False
self.automation.human_behavior.random_delay(1.5, 2.5)
# 3. E-Mail/Benutzername eingeben (Character-by-Character)
logger.info(f"Gebe E-Mail/Benutzername ein: {account_data['username']}")
# Warte bis Formular geladen ist
self.automation.human_behavior.random_delay(0.5, 1.0)
username_selectors = [
"input[name='username']",
"input[placeholder='E-Mail-Adresse oder Benutzername']",
"input.css-11to27l-InputContainer[name='username']",
"input[type='text'][autocomplete='webauthn']"
]
username_success = False
for selector in username_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
username_success = self._fill_username_field_character_by_character(selector, account_data["username"])
if username_success:
logger.info(f"Benutzername erfolgreich eingegeben mit Selektor: {selector}")
break
if not username_success:
logger.error("Konnte Benutzername-Feld nicht ausfüllen")
self.automation._take_screenshot("username_field_not_found")
return False
self.automation.human_behavior.random_delay(0.5, 1.0)
# 4. Passwort eingeben (mit Character-by-Character für bessere Kompatibilität)
logger.info("Gebe Passwort ein")
password_selectors = [
"input[type='password']",
"input[placeholder='Passwort']",
"input.css-wv3bkt-InputContainer[type='password']",
"input[autocomplete='new-password']"
]
password_success = False
for selector in password_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
# Verwende character-by-character Eingabe
password_success = self._fill_password_field_character_by_character(selector, account_data["password"])
if password_success:
logger.info(f"Passwort erfolgreich eingegeben mit Selektor: {selector}")
break
if not password_success:
logger.error("Konnte Passwort-Feld nicht ausfüllen")
self.automation._take_screenshot("password_field_not_found")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot vorm Absenden
self.automation._take_screenshot("login_form_filled")
# 5. Prüfe ob Login-Button aktiviert ist
logger.info("Prüfe Login-Button Status")
login_button_selectors = [
"button[data-e2e='login-button']",
"button[type='submit'][data-e2e='login-button']",
"button.css-11sviba-Button-StyledButton",
"button:has-text('Anmelden')"
]
button_ready = False
active_selector = None
for selector in login_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
element = self.automation.browser.page.locator(selector).first
is_disabled = element.get_attribute("disabled")
if not is_disabled:
button_ready = True
active_selector = selector
logger.info(f"Login-Button ist aktiviert: {selector}")
break
else:
logger.warning(f"Login-Button ist disabled: {selector}")
if not button_ready:
logger.warning("Login-Button ist nicht bereit, warte zusätzlich")
self.automation.human_behavior.random_delay(2.0, 3.0)
# Nochmal prüfen
for selector in login_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
element = self.automation.browser.page.locator(selector).first
is_disabled = element.get_attribute("disabled")
if not is_disabled:
button_ready = True
active_selector = selector
break
# 6. Login-Button klicken
logger.info("Klicke auf Anmelden-Button")
if button_ready and active_selector:
if self.automation.browser.click_element(active_selector):
logger.info(f"Login-Button erfolgreich geklickt: {active_selector}")
else:
logger.error("Klick auf Login-Button fehlgeschlagen")
return False
else:
logger.error("Konnte keinen aktivierten Login-Button finden")
self.automation._take_screenshot("no_active_login_button")
return False
# Nach dem Absenden warten
self.automation.human_behavior.wait_for_page_load(multiplier=2.0)
# Ü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 _fill_username_field_character_by_character(self, selector: str, username: str) -> bool:
"""
Füllt das Benutzername-Feld Zeichen für Zeichen aus.
Args:
selector: CSS-Selektor für das Username-Feld
username: Der einzugebende Benutzername
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
element = self.automation.browser.page.locator(selector).first
if not element.is_visible():
return False
logger.info("Verwende Character-by-Character Eingabe für Benutzername-Feld")
# Fokussiere und lösche das Feld
element.click()
self.automation.human_behavior.random_delay(0.1, 0.2)
# Lösche existierenden Inhalt
element.select_text()
element.press("Delete")
self.automation.human_behavior.random_delay(0.1, 0.2)
# Tippe jeden Buchstaben einzeln
import random
for i, char in enumerate(username):
element.type(char, delay=random.randint(50, 150)) # Zufällige Tippgeschwindigkeit
# Nach jedem 4. Zeichen eine kleine Pause (simuliert echtes Tippen)
if (i + 1) % 4 == 0:
self.automation.human_behavior.random_delay(0.1, 0.3)
# Fokus verlassen
self.automation.human_behavior.random_delay(0.2, 0.4)
element.press("Tab")
logger.info(f"Benutzername character-by-character eingegeben: {len(username)} Zeichen")
return True
except Exception as e:
logger.error(f"Fehler bei Character-by-Character Benutzername-Eingabe: {e}")
return False
def _fill_password_field_character_by_character(self, selector: str, password: str) -> bool:
"""
Füllt das Passwort-Feld Zeichen für Zeichen aus, um React's State korrekt zu aktualisieren.
Args:
selector: CSS-Selektor für das Passwort-Feld
password: Das einzugebende Passwort
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
element = self.automation.browser.page.locator(selector).first
if not element.is_visible():
return False
logger.info("Verwende Character-by-Character Eingabe für Passwort-Feld")
# Fokussiere und lösche das Feld
element.click()
self.automation.human_behavior.random_delay(0.1, 0.2)
# Lösche existierenden Inhalt
element.select_text()
element.press("Delete")
self.automation.human_behavior.random_delay(0.1, 0.2)
# Tippe jeden Buchstaben einzeln
import random
for i, char in enumerate(password):
element.type(char, delay=random.randint(50, 150)) # Zufällige Tippgeschwindigkeit
# Nach jedem 3. Zeichen eine kleine Pause (simuliert echtes Tippen)
if (i + 1) % 3 == 0:
self.automation.human_behavior.random_delay(0.1, 0.3)
# Fokus verlassen, um Validierung zu triggern
self.automation.human_behavior.random_delay(0.2, 0.4)
element.press("Tab")
logger.info(f"Passwort character-by-character eingegeben: {len(password)} Zeichen")
return True
except Exception as e:
logger.error(f"Fehler bei Character-by-Character Passwort-Eingabe: {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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
confirm_clicked = True
break
if not confirm_clicked:
# Alternative: Mit Tastendruck bestätigen
self.automation.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.automation.browser.is_element_visible(selector, timeout=3000):
if self.automation.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.automation.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.automation.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.automation.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-Diff unterdrückt, da er zu groß ist Diff laden

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,225 @@
"""
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"
EXPLORE_URL = "https://www.tiktok.com/explore"
# Anmelden/Registrieren-Buttons Hauptseite
LOGIN_BUTTON = "button#header-login-button"
LOGIN_BUTTON_HEADER = "button#header-login-button"
LOGIN_BUTTON_CLASS = "button.TUXButton:has-text('Anmelden')"
LOGIN_BUTTON_LEFT = "button#header-login-button"
LOGIN_BUTTON_RIGHT = "button#top-right-action-bar-login-button"
LOGIN_BUTTON_TOP = "button#header-login-button"
LOGIN_BUTTON_TOP_RIGHT = "button#top-right-action-bar-login-button"
LOGIN_BUTTON_SIDEBAR = "button[data-e2e='login-button-sidebar']"
LOGIN_BUTTON_FALLBACK = "//button[contains(text(), 'Anmelden')]"
SIGNUP_LINK = "span[data-e2e='bottom-sign-up']"
SIGNUP_LINK_FALLBACK = "a[href*='/signup']"
# Login-Dialog Optionen
LOGIN_DIALOG = "div[role='dialog']"
LOGIN_EMAIL_PHONE_OPTION = "div[data-e2e='channel-item']"
LOGIN_EMAIL_USERNAME_LINK = "a[href='/login/phone-or-email/email']"
# Cookie-Dialog
COOKIE_DIALOG = "div[role='dialog'][data-testid='cookie-banner']"
COOKIE_ACCEPT_BUTTON = "button[data-testid='accept-all-cookies']"
# Registrierungsdialog - Methoden
REGISTER_DIALOG_TITLE = "h1:contains('Registrieren')"
REGISTER_LINK = "a:contains('Registrieren')"
REGISTER_LINK_FALLBACK = "//a[contains(text(), 'Registrieren')]"
REGISTRATION_DIALOG = "div[role='dialog']"
PHONE_EMAIL_BUTTON = "div[data-e2e='channel-item']"
PHONE_EMAIL_OPTION = "div[data-e2e='channel-item']"
PHONE_EMAIL_OPTION_FALLBACK = "//div[contains(text(), 'Telefonnummer oder E-Mail')]"
EMAIL_OPTION = "a[href*='/signup/phone-or-email/email']"
EMAIL_OPTION_FALLBACK = "//a[contains(text(), 'E-Mail')]"
PHONE_OPTION = "a[href*='/signup/phone-or-email/phone']"
PHONE_OPTION_FALLBACK = "//a[contains(text(), 'Telefon')]"
REGISTER_WITH_EMAIL = "a[href*='/signup/phone-or-email/email']"
REGISTER_WITH_PHONE = "a[href*='/signup/phone-or-email/phone']"
# Geburtsdatum-Selektoren
BIRTHDAY_MONTH_DROPDOWN = "select[name='month']"
BIRTHDAY_DAY_DROPDOWN = "select[name='day']"
BIRTHDAY_YEAR_DROPDOWN = "select[name='year']"
BIRTHDAY_MONTH_SELECT = "div.css-1leicpq-DivSelectLabel:contains('Monat')"
BIRTHDAY_DAY_SELECT = "div.css-1leicpq-DivSelectLabel:contains('Tag')"
BIRTHDAY_YEAR_SELECT = "div.css-1leicpq-DivSelectLabel:contains('Jahr')"
BIRTHDAY_DROPDOWN_OPTION = "div[role='option']"
BIRTHDAY_DROPDOWN_CONTAINER = "div.css-1leicpq-DivSelectLabel"
BIRTHDAY_ARROW = "svg.css-gz151e-StyledArrowTriangleDownLargeFill"
# Formularfelder - E-Mail-Registrierung
EMAIL_FIELD = "input[placeholder='E-Mail-Adresse']"
EMAIL_FIELD_ALT = "input[name='email']"
PASSWORD_FIELD = "input[placeholder='Passwort']"
PASSWORD_FIELD_ALT = "input[type='password']"
VERIFICATION_CODE_FIELD = "input[placeholder*='sechsstelligen Code']"
VERIFICATION_CODE_FIELD_ALT = "input[placeholder='Gib den sechsstelligen Code ein']"
USERNAME_FIELD = "input[placeholder='Benutzername']"
USERNAME_FIELD_ALT = "input[name='new-username']"
# 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']"
CONTINUE_BUTTON_ALT = "button.e1w6iovg0"
REGISTER_BUTTON = "button:contains('Registrieren')"
REGISTER_BUTTON_ALT = "button[type='submit']:contains('Registrieren')"
SKIP_BUTTON = "div:contains('Überspringen')"
SKIP_BUTTON_ALT = "div.css-4y1w75-DivTextContainer"
SKIP_USERNAME_BUTTON = "button:contains('Überspringen')"
# Checkbox
NEWSLETTER_CHECKBOX = "input[type='checkbox']"
# Login-Formularfelder
LOGIN_EMAIL_FIELD = "input[name='username'][placeholder='E-Mail-Adresse oder Benutzername']"
LOGIN_EMAIL_FIELD_ALT = "input.tiktok-11to27l-InputContainer[name='username']"
LOGIN_PASSWORD_FIELD = "input[type='password'][placeholder='Passwort']"
LOGIN_PASSWORD_FIELD_ALT = "input.tiktok-wv3bkt-InputContainer[type='password']"
LOGIN_SUBMIT_BUTTON = "button[type='submit'][data-e2e='login-button']"
LOGIN_SUBMIT_BUTTON_ALT = "button.tiktok-11sviba-Button-StyledButton[data-e2e='login-button']"
# 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')"
# Login-Fehler
ERROR_MESSAGE = "span.error-message"
LOGIN_ERROR_CONTAINER = "div[class*='error']"
# 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"
],
# E-Mail-Betreff-Muster für TikTok
"email_subject_patterns": [
"ist dein Bestätigungscode",
"is your confirmation code",
"TikTok verification code",
"TikTok Bestätigungscode"
]
}
@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"]
@classmethod
def get_month_option_selector(cls, month: int) -> str:
"""Returns selector for month option."""
return f"option[value='{month}']"
@classmethod
def get_day_option_selector(cls, day: int) -> str:
"""Returns selector for day option."""
return f"option[value='{day}']"
@classmethod
def get_year_option_selector(cls, year: int) -> str:
"""Returns selector for year option."""
return f"option[value='{year}']"

Datei anzeigen

@ -0,0 +1,520 @@
"""
TikTok-UI-Helper - Hilfsmethoden für die Interaktion mit der TikTok-UI
"""
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
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("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
# Browser wird direkt von automation verwendet
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.automation.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.automation.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.automation.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.automation.browser.is_element_visible(aria_selector, timeout=1000):
if self.automation.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.automation.browser.is_element_visible(placeholder_selector, timeout=1000):
if self.automation.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.automation.browser.is_element_visible(name_selector, timeout=1000):
if self.automation.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.automation.browser.is_element_visible(selector, timeout=1000):
button_element = self.automation.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.automation.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.automation.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.automation.browser.is_element_visible(aria_selector, timeout=1000):
if self.automation.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.automation.browser.is_element_visible(xpath_selector, timeout=1000):
if self.automation.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.automation.browser.is_element_visible(link_selector, timeout=1000):
if self.automation.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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.browser.is_element_visible(selector, timeout=2000):
logger.warning(f"Captcha erkannt (Selektor): {selector}")
return True
# Nach Texten suchen
page_content = self.automation.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.automation.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.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Registrierung erfolgreich (Indikator gefunden: {selector})")
return True
# URL überprüfen
current_url = self.automation.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,492 @@
"""
TikTok-Utils - Hilfsfunktionen für die TikTok-Automatisierung.
"""
import re
import time
import random
from typing import Dict, List, Any, Optional, Tuple, Union
from .tiktok_selectors import TikTokSelectors
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("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
# Browser wird direkt von automation verwendet
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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.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.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info("Dialog geschlossen")
return True
# Wenn kein Schließen-Button gefunden wurde, Escape-Taste drücken
self.automation.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.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.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.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.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.automation.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.automation.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.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Benutzer ist angemeldet (Indikator: {selector})")
return True
# URL überprüfen
current_url = self.automation.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.automation.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.automation.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,458 @@
# social_networks/tiktok/tiktok_verification.py
"""
TikTok-Verifizierung - Klasse für die Verifizierungsfunktionalität bei TikTok
"""
import time
import re
from typing import Dict, List, Any, Optional, Tuple
from .tiktok_selectors import TikTokSelectors
from .tiktok_workflow import TikTokWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("tiktok_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
# Browser wird direkt von automation verwendet
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
"""
# Browser wird direkt von automation verwendet
# 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.automation.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.automation.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,
TikTokSelectors.VERIFICATION_CODE_FIELD_ALT,
"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.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Codefeld gefunden mit Selektor: {selector}")
if self.automation.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.CONTINUE_BUTTON,
TikTokSelectors.CONTINUE_BUTTON_ALT,
"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.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Weiter-Button gefunden mit Selektor: {selector}")
if self.automation.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.automation.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.automation.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.automation.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.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.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.automation.browser.is_element_visible(selector, timeout=2000):
if self.automation.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
"""
from typing import Dict, List, Any, Optional, Tuple
import re
from utils.text_similarity import TextSimilarity
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("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"