# 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)