Files
AccountForger-neuerUpload/social_networks/tiktok/tiktok_ui_helper.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

520 Zeilen
22 KiB
Python

"""
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