Problem bei X gelöst
Dieser Commit ist enthalten in:
@ -0,0 +1,341 @@
|
||||
# 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)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren