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

@ -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 = [