Files
Claude Project Manager a25a26a01a Update changes
2026-01-18 18:15:34 +01:00

466 Zeilen
17 KiB
Python

# social_networks/facebook/facebook_ui_helper.py
"""
Facebook UI Helper - Hilfsklasse für UI-Interaktionen bei Facebook
"""
import logging
import time
import random
from typing import List, Union, Optional
from .facebook_selectors import FacebookSelectors
from utils.text_similarity import TextSimilarity
from utils.logger import setup_logger
logger = setup_logger("facebook_ui_helper")
class FacebookUIHelper:
"""
Hilfsklasse für UI-Interaktionen bei Facebook.
Bietet Methoden für menschenähnliche Interaktionen.
"""
def __init__(self, automation):
"""
Initialisiert den UI Helper.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.selectors = FacebookSelectors()
self.text_similarity = TextSimilarity(default_threshold=0.7)
logger.debug("Facebook UI Helper initialisiert")
def _ensure_browser(self) -> bool:
"""
Stellt sicher, dass der Browser verfügbar ist.
Returns:
bool: True wenn Browser verfügbar
"""
if not self.automation.browser or not hasattr(self.automation.browser, 'page'):
logger.error("Browser nicht verfügbar")
return False
return True
def type_text_human_like(self, selector: str, text: str, delay_range: tuple = (50, 150)) -> bool:
"""
Tippt Text menschenähnlich mit zufälligen Verzögerungen.
Args:
selector: CSS-Selektor des Eingabefelds
text: Einzugebender Text
delay_range: Bereich für Verzögerung zwischen Tastenanschlägen in ms
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
try:
# Element fokussieren
if not self.automation.browser.click_element(selector):
logger.error(f"Konnte Element nicht fokussieren: {selector}")
return False
# Kurze Pause nach Fokus
self.automation.human_behavior.random_delay(0.2, 0.5)
# Feld leeren (dreifach-klick und löschen)
self.automation.browser.page.click(selector, click_count=3)
self.automation.browser.page.keyboard.press("Delete")
# Text Zeichen für Zeichen eingeben
for char in text:
self.automation.browser.page.keyboard.type(char)
# Zufällige Verzögerung zwischen Zeichen
delay_ms = random.randint(*delay_range)
time.sleep(delay_ms / 1000)
# Gelegentlich längere Pause (Denken)
if random.random() < 0.1:
self.automation.human_behavior.random_delay(0.3, 0.8)
# Sehr selten Tippfehler simulieren und korrigieren
if random.random() < 0.02 and len(text) > 5:
# Falschen Buchstaben tippen
wrong_char = random.choice('abcdefghijklmnopqrstuvwxyz')
self.automation.browser.page.keyboard.type(wrong_char)
time.sleep(random.randint(100, 300) / 1000)
# Korrigieren
self.automation.browser.page.keyboard.press("Backspace")
time.sleep(random.randint(50, 150) / 1000)
logger.debug(f"Text erfolgreich eingegeben in {selector}")
return True
except Exception as e:
logger.error(f"Fehler beim menschenähnlichen Tippen: {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 Textähnlichkeit (0-1)
timeout: Zeitlimit für die Suche in Millisekunden
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
try:
# Normalisiere button_texts zu einer Liste
if isinstance(button_texts, str):
button_texts = [button_texts]
logger.info(f"Suche nach Button mit Texten: {button_texts}")
# Versuche jeden Text
for target_text in button_texts:
# Methode 1: Exakter Text-Match mit has-text
selector = f"button:has-text('{target_text}')"
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info(f"Button geklickt (exakter Match): {target_text}")
return True
# Methode 2: Link als Button
selector = f"a:has-text('{target_text}')"
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info(f"Link-Button geklickt: {target_text}")
return True
# Methode 3: Div mit role="button"
selector = f"div[role='button']:has-text('{target_text}')"
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info(f"Div-Button geklickt: {target_text}")
return True
# Methode 4: Fuzzy-Matching mit allen Buttons
try:
all_buttons = self.automation.browser.page.query_selector_all("button, a[role='button'], div[role='button']")
for button in all_buttons:
button_text = button.inner_text().strip()
# Prüfe Ähnlichkeit mit jedem Zieltext
for target_text in button_texts:
similarity = self.text_similarity.calculate_similarity(button_text.lower(), target_text.lower())
if similarity >= threshold:
logger.info(f"Fuzzy-Match gefunden: '{button_text}' (Ähnlichkeit: {similarity:.2f})")
button.click()
return True
except Exception as e:
logger.debug(f"Fuzzy-Matching fehlgeschlagen: {e}")
# Fallback-Selektor verwenden
if fallback_selector:
if self.automation.browser.click_element(fallback_selector, timeout=timeout):
logger.info(f"Button geklickt mit Fallback-Selektor: {fallback_selector}")
return True
logger.warning(f"Konnte keinen Button mit Texten finden: {button_texts}")
return False
except Exception as e:
logger.error(f"Fehler beim Fuzzy-Button-Click: {e}")
return False
def scroll_to_element(self, selector: str) -> bool:
"""
Scrollt zu einem Element.
Args:
selector: CSS-Selektor des Elements
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
try:
self.automation.browser.page.eval_on_selector(
selector,
"element => element.scrollIntoView({behavior: 'smooth', block: 'center'})"
)
# Warte auf Scroll-Animation
self.automation.human_behavior.random_delay(0.5, 1.0)
logger.debug(f"Zu Element gescrollt: {selector}")
return True
except Exception as e:
logger.error(f"Fehler beim Scrollen zu Element: {e}")
return False
def wait_for_element(self, selector: str, timeout: int = 10000, state: str = "visible") -> bool:
"""
Wartet auf ein Element.
Args:
selector: CSS-Selektor
timeout: Maximale Wartezeit in ms
state: Erwarteter Zustand ("visible", "hidden", "attached", "detached")
Returns:
bool: True wenn Element im erwarteten Zustand
"""
if not self._ensure_browser():
return False
try:
self.automation.browser.page.wait_for_selector(selector, timeout=timeout, state=state)
logger.debug(f"Element gefunden: {selector} (Zustand: {state})")
return True
except Exception as e:
logger.debug(f"Element nicht gefunden: {selector} - {e}")
return False
def handle_dialog(self, action: str = "accept") -> bool:
"""
Behandelt JavaScript-Dialoge (alert, confirm, prompt).
Args:
action: "accept" oder "dismiss"
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
try:
# Dialog-Handler setzen
def handle_dialog(dialog):
logger.info(f"Dialog erkannt: {dialog.message}")
if action == "accept":
dialog.accept()
else:
dialog.dismiss()
self.automation.browser.page.on("dialog", handle_dialog)
# Kurz warten
time.sleep(0.5)
# Handler wieder entfernen
self.automation.browser.page.remove_listener("dialog", handle_dialog)
return True
except Exception as e:
logger.error(f"Fehler bei Dialog-Behandlung: {e}")
return False
def get_element_text(self, selector: str) -> Optional[str]:
"""
Holt den Text eines Elements.
Args:
selector: CSS-Selektor
Returns:
Optional[str]: Text des Elements oder None
"""
if not self._ensure_browser():
return None
try:
element = self.automation.browser.page.query_selector(selector)
if element:
return element.inner_text().strip()
return None
except Exception as e:
logger.error(f"Fehler beim Abrufen des Element-Texts: {e}")
return None
def fill_select_option(self, selector: str, value: str) -> bool:
"""
Wählt eine Option in einem Select-Element aus.
Args:
selector: CSS-Selektor des Select-Elements
value: Wert der zu wählenden Option
Returns:
bool: True bei Erfolg
"""
if not self._ensure_browser():
return False
try:
# Menschenähnliche Interaktion: Erst klicken, dann wählen
self.automation.browser.click_element(selector)
self.automation.human_behavior.random_delay(0.2, 0.5)
# Option auswählen
self.automation.browser.page.select_option(selector, value=value)
logger.debug(f"Option '{value}' ausgewählt in {selector}")
return True
except Exception as e:
logger.error(f"Fehler beim Auswählen der Option: {e}")
return False
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)
# ==========================================================================
# 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