Dieser Commit ist enthalten in:
Claude Project Manager
2026-01-18 18:15:34 +01:00
Ursprung 4e82d5ef8f
Commit a25a26a01a
47 geänderte Dateien mit 4756 neuen und 2956 gelöschten Zeilen

Datei anzeigen

@ -327,15 +327,140 @@ class FacebookUIHelper:
def check_element_exists(self, selector: str, timeout: int = 1000) -> bool:
"""
Prüft ob ein Element existiert.
Args:
selector: CSS-Selektor
timeout: Maximale Wartezeit in ms
Returns:
bool: True wenn Element existiert
"""
if not self._ensure_browser():
return False
return self.automation.browser.is_element_visible(selector, timeout=timeout)
return self.automation.browser.is_element_visible(selector, timeout=timeout)
# ==========================================================================
# ANTI-DETECTION: Tab-Navigation Methoden
# ==========================================================================
def navigate_to_next_field(self, use_tab: bool = None) -> bool:
"""
Navigiert zum nächsten Feld, entweder per Tab oder Maus-Klick.
Diese Methode simuliert menschliches Verhalten durch zufällige
Verwendung von Tab-Navigation (wie viele echte Benutzer es tun).
Args:
use_tab: Explizit Tab verwenden. Wenn None, 50% Chance für Tab.
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
# Entscheide zufällig ob Tab verwendet wird (50% Chance)
if use_tab is None:
use_tab = random.random() < 0.5
try:
if use_tab:
logger.debug("Verwende Tab-Navigation zum nächsten Feld")
self.automation.browser.page.keyboard.press("Tab")
# Variable Wartezeit nach Tab
time.sleep(random.uniform(0.15, 0.35))
# Gelegentlich Tab + Shift-Tab (versehentlich zu weit gegangen)
if random.random() < 0.08:
logger.debug("Simuliere Tab-Korrektur (zu weit)")
self.automation.browser.page.keyboard.press("Tab")
time.sleep(random.uniform(0.2, 0.4))
self.automation.browser.page.keyboard.press("Shift+Tab")
time.sleep(random.uniform(0.15, 0.3))
return True
else:
logger.debug("Tab-Navigation nicht verwendet (Maus wird später genutzt)")
return False
except Exception as e:
logger.error(f"Fehler bei Tab-Navigation: {e}")
return False
def fill_field_with_tab_navigation(self, selector: str, value: str,
use_tab_navigation: bool = None,
error_rate: float = 0.15) -> bool:
"""
Füllt ein Feld aus mit optionaler Tab-Navigation.
Diese Methode kombiniert Tab-Navigation mit menschenähnlichem Tippen
für ein realistischeres Verhalten.
Args:
selector: CSS-Selektor des Feldes
value: Einzugebender Wert
use_tab_navigation: Ob Tab verwendet werden soll (None = 50% Chance)
error_rate: Wahrscheinlichkeit für Tippfehler (10-20%)
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
try:
# Entscheide Navigationsmethode
if use_tab_navigation is None:
use_tab_navigation = random.random() < 0.5
if use_tab_navigation:
# Tab-Navigation verwenden
logger.debug(f"Fülle Feld {selector} mit Tab-Navigation")
self.automation.browser.page.keyboard.press("Tab")
time.sleep(random.uniform(0.15, 0.35))
else:
# Maus-Klick auf Feld
logger.debug(f"Fülle Feld {selector} mit Maus-Klick")
if not self.automation.browser.click_element(selector):
logger.warning(f"Konnte Feld nicht anklicken: {selector}")
return False
time.sleep(random.uniform(0.2, 0.5))
# Anti-Detection Delay vor Eingabe
if hasattr(self.automation, 'human_behavior'):
self.automation.human_behavior.anti_detection_delay("field_focus")
# Text mit Tippfehlern eingeben
for i, char in enumerate(value):
# Tippfehler simulieren
if random.random() < error_rate:
# Falsches Zeichen
wrong_char = random.choice('abcdefghijklmnopqrstuvwxyz0123456789')
self.automation.browser.page.keyboard.type(wrong_char)
time.sleep(random.uniform(0.1, 0.3))
# Korrektur mit Backspace
self.automation.browser.page.keyboard.press("Backspace")
time.sleep(random.uniform(0.08, 0.2))
# Korrektes Zeichen
self.automation.browser.page.keyboard.type(char)
# Variable Verzögerung
delay_ms = random.randint(50, 150)
if char in ' .,!?':
delay_ms *= random.uniform(1.3, 2.0)
time.sleep(delay_ms / 1000)
# Gelegentlich längere Pause (Denken)
if random.random() < 0.05:
time.sleep(random.uniform(0.3, 0.8))
logger.info(f"Feld {selector} erfolgreich ausgefüllt")
return True
except Exception as e:
logger.error(f"Fehler beim Ausfüllen mit Tab-Navigation: {e}")
return False

Datei anzeigen

@ -258,87 +258,199 @@ class InstagramRegistration:
def _navigate_to_signup_page(self) -> bool:
"""
Navigiert zur Instagram-Registrierungsseite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.info(f"Navigiere zur Registrierungsseite: {InstagramSelectors.SIGNUP_URL}")
# Zur Registrierungsseite navigieren
self.automation.browser.navigate_to(InstagramSelectors.SIGNUP_URL)
# Warten, bis die Seite geladen ist
logger.debug("Warte auf Seitenladung...")
self.automation.human_behavior.wait_for_page_load()
# Aktuelle URL loggen (für Debugging bei Weiterleitungen)
try:
current_url = self.automation.browser.page.url
logger.info(f"Aktuelle URL nach Navigation: {current_url}")
# Prüfen ob wir umgeleitet wurden
if "login" in current_url.lower() and "signup" not in current_url.lower():
logger.warning("Wurde zur Login-Seite umgeleitet - versuche erneut zur Registrierung zu navigieren")
self.automation.browser.navigate_to(InstagramSelectors.SIGNUP_URL)
self.automation.human_behavior.wait_for_page_load()
current_url = self.automation.browser.page.url
logger.info(f"URL nach erneutem Navigieren: {current_url}")
except Exception as url_err:
logger.debug(f"Konnte aktuelle URL nicht abrufen: {url_err}")
# SOFORT Cookie-Banner behandeln BEVOR weitere Aktionen (TIMING-FIX)
logger.info("Behandle Cookie-Banner SOFORT nach Navigation für korrekte Session-Cookies")
cookie_handled = self._handle_cookie_banner()
if not cookie_handled:
logger.warning("Cookie-Banner konnte nicht behandelt werden - Session könnte beeinträchtigt sein")
# Kurz warten damit Cookies gesetzt werden können
logger.debug("Warte nach Cookie-Behandlung...")
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot erstellen
self.automation._take_screenshot("signup_page")
# Prüfen, ob Registrierungsformular sichtbar ist
logger.debug(f"Suche nach Registrierungsformular mit Selektor: {InstagramSelectors.EMAIL_PHONE_FIELD}")
if not self.automation.browser.is_element_visible(InstagramSelectors.EMAIL_PHONE_FIELD, timeout=5000):
logger.warning("Registrierungsformular nicht sichtbar")
logger.warning(f"Hauptselektor {InstagramSelectors.EMAIL_PHONE_FIELD} nicht gefunden - versuche Alternativen")
# Alternative Selektoren versuchen
alt_selectors = [
InstagramSelectors.ALT_EMAIL_FIELD,
"input[type='email']",
"input[type='text'][aria-label*='mail']",
"input[type='text'][aria-label*='Mail']",
"input[type='text'][placeholder*='mail']",
"input[name='email']",
"//input[contains(@aria-label, 'E-Mail') or contains(@aria-label, 'Email')]"
]
for alt_selector in alt_selectors:
logger.debug(f"Versuche alternativen Selektor: {alt_selector}")
if self.automation.browser.is_element_visible(alt_selector, timeout=1000):
logger.info(f"Formular mit alternativem Selektor gefunden: {alt_selector}")
return True
# Debug: Logge sichtbare Elemente auf der Seite
try:
page_title = self.automation.browser.page.title()
logger.debug(f"Seitentitel: {page_title}")
# Prüfe auf bekannte Blockade-Elemente
if self.automation.browser.is_element_visible("div[role='dialog']", timeout=1000):
logger.warning("Ein Dialog ist noch sichtbar - könnte Cookie-Dialog oder anderer Modal sein")
# Screenshot vom aktuellen Zustand
self.automation._take_screenshot("signup_page_blocked")
# Prüfe auf Fehlermeldungen
if self.automation.browser.is_element_visible("p[class*='error'], div[class*='error']", timeout=1000):
logger.warning("Fehlermeldung auf der Seite erkannt")
# Prüfe ob wir auf einer anderen Seite sind
current_url = self.automation.browser.page.url
if "challenge" in current_url.lower():
logger.error("CHECKPOINT/CHALLENGE erkannt - Instagram verlangt Verifizierung!")
elif "suspended" in current_url.lower():
logger.error("Account wurde suspendiert oder IP blockiert!")
except Exception as debug_err:
logger.debug(f"Debug-Informationen konnten nicht abgerufen werden: {debug_err}")
logger.warning("Registrierungsformular nicht sichtbar - alle Selektoren fehlgeschlagen")
return False
logger.info("Erfolgreich zur Registrierungsseite navigiert und Cookies akzeptiert")
return True
except Exception as e:
logger.error(f"Fehler beim Navigieren zur Registrierungsseite: {e}")
logger.error(f"Fehler beim Navigieren zur Registrierungsseite: {e}", exc_info=True)
return False
def _handle_cookie_banner(self) -> bool:
"""
Behandelt den Cookie-Banner, falls angezeigt.
Akzeptiert IMMER Cookies für vollständiges Session-Management bei der Registrierung.
ANTI-DETECTION: Wartet 3-8 Sekunden bevor der Cookie-Dialog geklickt wird,
um menschliches Leseverhalten zu simulieren.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
# Cookie-Dialog-Erkennung
if self.automation.browser.is_element_visible(InstagramSelectors.COOKIE_DIALOG, timeout=2000):
logger.info("Cookie-Banner erkannt - akzeptiere alle Cookies für Session-Management")
logger.debug(f"Prüfe auf Cookie-Dialog mit Selektor: {InstagramSelectors.COOKIE_DIALOG}")
if self.automation.browser.is_element_visible(InstagramSelectors.COOKIE_DIALOG, timeout=3000):
logger.info("Cookie-Banner erkannt - simuliere Lesen bevor geklickt wird")
# ANTI-DETECTION: Lese-Pause bevor Cookie-Dialog geklickt wird (3-8 Sekunden)
# Echte Menschen lesen den Cookie-Text bevor sie klicken
logger.debug("Starte Anti-Detection Lese-Pause für Cookie-Banner...")
self.automation.human_behavior.anti_detection_delay("cookie_reading")
logger.debug("Anti-Detection Lese-Pause abgeschlossen")
# Gelegentlich etwas scrollen um "mehr zu lesen" (30% Chance)
if random.random() < 0.3:
try:
logger.debug("Simuliere Scrollen im Cookie-Dialog...")
self.automation.browser.page.evaluate("window.scrollBy(0, 50)")
time.sleep(random.uniform(0.8, 1.5))
self.automation.browser.page.evaluate("window.scrollBy(0, -50)")
time.sleep(random.uniform(0.3, 0.6))
except Exception as scroll_err:
logger.debug(f"Cookie-Dialog Scroll übersprungen: {scroll_err}")
logger.info("Klicke Cookie-Banner - akzeptiere alle Cookies für Session-Management")
# Akzeptieren-Button suchen und klicken (PRIMÄR für Registrierung)
accept_success = self.automation.ui_helper.click_button_fuzzy(
InstagramSelectors.get_button_texts("accept_cookies"),
InstagramSelectors.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)
# WICHTIG: Nach Cookie-Klick warten bis Seite stabil ist
logger.debug("Warte auf Seitenstabilität nach Cookie-Akzeptierung...")
time.sleep(2.0) # Feste Wartezeit für Seiten-Reload
# Warte auf Network-Idle
try:
self.automation.browser.page.wait_for_load_state("networkidle", timeout=10000)
logger.debug("Seite ist nach Cookie-Akzeptierung stabil (networkidle)")
except Exception as wait_err:
logger.warning(f"Timeout beim Warten auf networkidle: {wait_err}")
self.automation.human_behavior.random_delay(1.0, 2.0)
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(), 'Alle Cookies erlauben')]",
"//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:
logger.debug(f"Versuche alternativen Cookie-Selektor: {selector}")
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)
logger.info(f"Cookie-Banner mit alternativem Selector akzeptiert: {selector}")
# Nach Cookie-Klick warten
time.sleep(2.0)
try:
self.automation.browser.page.wait_for_load_state("networkidle", timeout=10000)
except:
pass
self.automation.human_behavior.random_delay(1.0, 2.0)
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")
logger.debug("Kein Cookie-Banner erkannt - fahre fort")
return True
def _select_registration_method(self, method: str) -> bool:

Datei anzeigen

@ -5,6 +5,7 @@ Instagram-UI-Helper - Hilfsmethoden für die Interaktion mit der Instagram-UI
"""
import logging
import random
import re
import time
from typing import Dict, List, Any, Optional, Tuple, Union, Callable
@ -786,38 +787,165 @@ class InstagramUIHelper:
def wait_for_page_load(self, timeout: int = 30000, check_interval: int = 500) -> bool:
"""
Wartet, bis die Seite vollständig geladen ist.
Args:
timeout: Zeitlimit in Millisekunden
check_interval: Intervall zwischen den Prüfungen in Millisekunden
Returns:
bool: True wenn die Seite geladen wurde, False bei Zeitüberschreitung
"""
if not self._ensure_browser():
return False
try:
# Warten auf Netzwerk-Idle
self.automation.browser.page.wait_for_load_state("networkidle", timeout=timeout)
# Zusätzlich auf das Verschwinden der Ladeindikatoren warten
start_time = time.time()
end_time = start_time + (timeout / 1000)
while time.time() < end_time:
if not self.is_page_loading():
# Noch eine kurze Pause für Animationen
time.sleep(0.5)
logger.info("Seite vollständig geladen")
return True
# Kurze Pause vor der nächsten Prüfung
time.sleep(check_interval / 1000)
logger.warning("Zeitüberschreitung beim Warten auf das Laden der Seite")
return False
except Exception as e:
logger.error(f"Fehler beim Warten auf das Laden der Seite: {e}")
return False
# ==========================================================================
# ANTI-DETECTION: Tab-Navigation Methoden
# ==========================================================================
def navigate_to_next_field(self, use_tab: bool = None) -> bool:
"""
Navigiert zum nächsten Feld, entweder per Tab oder Maus-Klick.
Diese Methode simuliert menschliches Verhalten durch zufällige
Verwendung von Tab-Navigation (wie viele echte Benutzer es tun).
Args:
use_tab: Explizit Tab verwenden. Wenn None, 50% Chance für Tab.
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
# Entscheide zufällig ob Tab verwendet wird (50% Chance)
if use_tab is None:
use_tab = random.random() < 0.5
try:
if use_tab:
logger.debug("Verwende Tab-Navigation zum nächsten Feld")
self.automation.browser.page.keyboard.press("Tab")
# Variable Wartezeit nach Tab
time.sleep(random.uniform(0.15, 0.35))
# Gelegentlich Tab + Shift-Tab (versehentlich zu weit gegangen)
if random.random() < 0.08:
logger.debug("Simuliere Tab-Korrektur (zu weit)")
self.automation.browser.page.keyboard.press("Tab")
time.sleep(random.uniform(0.2, 0.4))
self.automation.browser.page.keyboard.press("Shift+Tab")
time.sleep(random.uniform(0.15, 0.3))
return True
else:
logger.debug("Tab-Navigation nicht verwendet (Maus wird später genutzt)")
return False
except Exception as e:
logger.error(f"Fehler bei Tab-Navigation: {e}")
return False
def fill_field_with_tab_navigation(self, selector: str, value: str,
use_tab_navigation: bool = None,
error_rate: float = 0.15) -> bool:
"""
Füllt ein Feld aus mit optionaler Tab-Navigation.
Diese Methode kombiniert Tab-Navigation mit menschenähnlichem Tippen
für ein realistischeres Verhalten.
Args:
selector: CSS-Selektor des Feldes (nur für Maus-Klick verwendet)
value: Einzugebender Wert
use_tab_navigation: Ob Tab verwendet werden soll (None = 50% Chance)
error_rate: Wahrscheinlichkeit für Tippfehler (10-20%)
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
try:
import random
# Entscheide Navigationsmethode
if use_tab_navigation is None:
use_tab_navigation = random.random() < 0.5
if use_tab_navigation:
# Tab-Navigation verwenden
logger.debug(f"Fülle Feld mit Tab-Navigation")
self.automation.browser.page.keyboard.press("Tab")
time.sleep(random.uniform(0.15, 0.35))
else:
# Maus-Klick auf Feld
logger.debug(f"Fülle Feld {selector} mit Maus-Klick")
if not self.automation.browser.click_element(selector):
logger.warning(f"Konnte Feld nicht anklicken: {selector}")
return False
time.sleep(random.uniform(0.2, 0.5))
# Anti-Detection Delay vor Eingabe
if hasattr(self.automation, 'human_behavior') and hasattr(self.automation.human_behavior, 'anti_detection_delay'):
self.automation.human_behavior.anti_detection_delay("field_focus")
# Text mit Tippfehlern eingeben
for i, char in enumerate(value):
# Tippfehler simulieren
if random.random() < error_rate:
# Falsches Zeichen
wrong_char = random.choice('abcdefghijklmnopqrstuvwxyz0123456789')
self.automation.browser.page.keyboard.type(wrong_char)
time.sleep(random.uniform(0.1, 0.3))
# Korrektur mit Backspace
self.automation.browser.page.keyboard.press("Backspace")
time.sleep(random.uniform(0.08, 0.2))
# Korrektes Zeichen
self.automation.browser.page.keyboard.type(char)
# Variable Verzögerung
delay_ms = random.randint(50, 150)
if char in ' .,!?':
delay_ms *= random.uniform(1.3, 2.0)
time.sleep(delay_ms / 1000)
# Gelegentlich längere Pause (Denken)
if random.random() < 0.05:
time.sleep(random.uniform(0.3, 0.8))
logger.info(f"Feld erfolgreich ausgefüllt")
return True
except Exception as e:
logger.error(f"Fehler beim Ausfüllen mit Tab-Navigation: {e}")
return False

Datei anzeigen

@ -451,14 +451,97 @@ class TikTokRegistration:
logger.debug("Kein Cookie-Banner erkannt")
return True
def _close_video_overlay(self) -> bool:
"""
Schließt Video-Overlays, die Klicks blockieren können.
TikTok zeigt manchmal Promo-Videos, die den Login-Button überlagern.
Returns:
bool: True wenn Overlay geschlossen wurde oder nicht existiert
"""
try:
# Prüfe ob ein Video-Modal sichtbar ist
video_modal_selectors = [
"div[data-focus-lock-disabled]",
"div[class*='VideoPlayer']",
"div[class*='video-card']",
"div[class*='DivVideoContainer']"
]
for selector in video_modal_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
logger.info(f"Video-Overlay erkannt: {selector}")
# Versuche verschiedene Methoden zum Schließen
close_selectors = [
"button[aria-label='Close']",
"button[aria-label='Schließen']",
"div[data-focus-lock-disabled] button:has(svg)",
"[data-e2e='browse-close']",
"button.TUXButton:has-text('×')",
"button:has-text('×')"
]
for close_selector in close_selectors:
try:
if self.automation.browser.is_element_visible(close_selector, timeout=500):
self.automation.browser.click_element(close_selector)
logger.info(f"Video-Overlay geschlossen mit: {close_selector}")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
except:
continue
# Fallback: ESC-Taste drücken
try:
self.automation.browser.page.keyboard.press("Escape")
logger.info("Video-Overlay mit ESC-Taste geschlossen")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
except:
pass
# Fallback: JavaScript zum Entfernen des Video-Elements
try:
js_code = """
// Video-Elemente pausieren und verstecken
document.querySelectorAll('video').forEach(v => {
v.pause();
v.style.display = 'none';
});
// Modal-Container mit focus-lock entfernen
const modal = document.querySelector('div[data-focus-lock-disabled]');
if (modal && modal.querySelector('video')) {
modal.style.display = 'none';
return true;
}
return false;
"""
result = self.automation.browser.page.evaluate(js_code)
if result:
logger.info("Video-Overlay mit JavaScript versteckt")
self.automation.human_behavior.random_delay(0.3, 0.5)
return True
except Exception as e:
logger.debug(f"JavaScript-Entfernung fehlgeschlagen: {e}")
return True # Kein Overlay gefunden = OK
except Exception as e:
logger.warning(f"Fehler beim Schließen des Video-Overlays: {e}")
return True # Trotzdem fortfahren
def _click_login_button(self) -> bool:
"""
Klickt auf den Anmelden-Button auf der Startseite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# WICHTIG: Erst Video-Overlays schließen, die Klicks blockieren können
self._close_video_overlay()
# Liste aller Login-Button-Selektoren, die wir versuchen wollen
login_selectors = [
self.selectors.LOGIN_BUTTON, # button#header-login-button
@ -469,16 +552,34 @@ class TikTokRegistration:
"button[aria-label*='Anmelden']", # Aria-Label
"button:has(.TUXButton-label:text('Anmelden'))" # Verschachtelte Struktur
]
# Versuche jeden Selektor
# Versuche jeden Selektor mit force=True für blockierte Elemente
for i, selector in enumerate(login_selectors):
logger.debug(f"Versuche Login-Selektor {i+1}: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=3000):
result = self.automation.browser.click_element(selector)
if result:
logger.info(f"Anmelden-Button erfolgreich geklickt mit Selektor {i+1}")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Erst normaler Klick
try:
result = self.automation.browser.click_element(selector)
if result:
logger.info(f"Anmelden-Button erfolgreich geklickt mit Selektor {i+1}")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
except Exception as click_error:
# Bei Blockierung: Force-Click mit JavaScript
logger.debug(f"Normaler Klick blockiert, versuche JavaScript-Klick: {click_error}")
try:
escaped_selector = selector.replace("'", "\\'")
js_click = f"""
const el = document.querySelector('{escaped_selector}');
if (el) {{ el.click(); return true; }}
return false;
"""
if self.automation.browser.page.evaluate(js_click):
logger.info(f"Anmelden-Button mit JavaScript geklickt (Selektor {i+1})")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
except:
continue
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
@ -501,35 +602,50 @@ class TikTokRegistration:
def _click_register_link(self) -> bool:
"""
Klickt auf den Registrieren-Link im Login-Dialog.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis der Login-Dialog angezeigt wird
self.automation.human_behavior.random_delay(2.0, 3.0)
# Video-Overlay schließen falls vorhanden (blockiert oft Klicks)
self._close_video_overlay()
# Screenshot für Debugging
self.automation._take_screenshot("after_login_button_click")
# Verschiedene Registrieren-Selektoren versuchen
# Verschiedene Registrieren-Selektoren versuchen (prioritätsbezogen sortiert)
register_selectors = [
"a:text('Registrieren')", # Direkter Text-Match
"button:text('Registrieren')", # Button-Text
"div:text('Registrieren')", # Div-Text
"span:text('Registrieren')", # Span-Text
# Primäre Selektoren (data-e2e Attribute sind am stabilsten)
"span[data-e2e='bottom-sign-up']", # Offizieller TikTok-Selektor
"[data-e2e='bottom-sign-up']", # Allgemeiner
"[data-e2e*='sign-up']", # Partial match
"[data-e2e*='signup']", # Data-Attribute
"[data-e2e*='register']", # Data-Attribute
# Dialog-bezogene Selektoren
"div[role='dialog'] a:has-text('Registrieren')", # Link im Dialog
"div[role='dialog'] span:has-text('Registrieren')", # Span im Dialog
"div[role='dialog'] div:has-text('Registrieren')", # Div im Dialog
# Text-basierte Selektoren
"a:text('Registrieren')", # Direkter Text-Match
"button:text('Registrieren')", # Button-Text
"span:text('Registrieren')", # Span-Text
"div:text('Registrieren')", # Div-Text
# Href-basierte Selektoren
"a[href*='signup']", # Signup-Link
"//a[contains(text(), 'Registrieren')]", # XPath
"//button[contains(text(), 'Registrieren')]", # XPath Button
"//span[contains(text(), 'Registrieren')]", # XPath Span
"//div[contains(text(), 'Konto erstellen')]", # Alternative Text
"//a[contains(text(), 'Sign up')]", # Englisch
".signup-link", # CSS-Klasse
".register-link" # CSS-Klasse
"a[href*='/signup']", # Mit Slash
# XPath als Fallback
"//a[contains(text(), 'Registrieren')]", # XPath
"//span[contains(text(), 'Registrieren')]", # XPath Span
"//div[contains(text(), 'Konto erstellen')]", # Alternative Text
"//a[contains(text(), 'Sign up')]", # Englisch
# CSS-Klassen als letzter Fallback
".signup-link", # CSS-Klasse
".register-link" # CSS-Klasse
]
# Versuche jeden Selektor
for i, selector in enumerate(register_selectors):
logger.debug(f"Versuche Registrieren-Selektor {i+1}: {selector}")
@ -543,8 +659,37 @@ class TikTokRegistration:
except Exception as e:
logger.debug(f"Selektor {i+1} fehlgeschlagen: {e}")
continue
# Fallback: Fuzzy-Text-Suche
# JavaScript-Fallback: Element per JS klicken (umgeht Overlays)
logger.debug("Versuche JavaScript-Klick für Registrieren-Link")
try:
js_selectors = [
"span[data-e2e='bottom-sign-up']",
"[data-e2e*='sign-up']",
"a[href*='signup']"
]
for js_sel in js_selectors:
try:
clicked = self.automation.browser.page.evaluate(f'''
() => {{
const el = document.querySelector("{js_sel}");
if (el) {{
el.click();
return true;
}}
return false;
}}
''')
if clicked:
logger.info(f"Registrieren-Link per JavaScript geklickt: {js_sel}")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
except Exception:
continue
except Exception as e:
logger.debug(f"JavaScript-Klick fehlgeschlagen: {e}")
# Fallback: Fuzzy-Text-Suche mit Playwright Locator
try:
page_content = self.automation.browser.page.content()
if "Registrieren" in page_content or "Sign up" in page_content:
@ -559,18 +704,26 @@ class TikTokRegistration:
try:
element = self.automation.browser.page.locator(text_sel).first
if element.is_visible():
element.click()
logger.info(f"Auf Text geklickt: {text_sel}")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche normalen Klick
try:
element.click(timeout=3000)
logger.info(f"Auf Text geklickt: {text_sel}")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
except Exception:
# Falls blockiert, force-click
element.click(force=True)
logger.info(f"Auf Text force-geklickt: {text_sel}")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
except Exception:
continue
except Exception as e:
logger.debug(f"Fallback-Text-Suche fehlgeschlagen: {e}")
logger.error("Konnte keinen Registrieren-Link finden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf den Registrieren-Link: {e}")
# Debug-Screenshot bei Fehler
@ -931,7 +1084,8 @@ class TikTokRegistration:
year_selected = False
# Berechne den Index für das Jahr (normalerweise absteigend sortiert)
# Annahme: Jahre von aktuellem Jahr bis 1900, also Index = aktuelles_jahr - gewähltes_jahr
current_year = 2025 # oder datetime.now().year
from datetime import datetime
current_year = datetime.now().year
year_index = current_year - birthday['year']
year_option_selectors = [