Update changes
Dieser Commit ist enthalten in:
@ -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
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
@ -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 = [
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren