diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 3262f6d..540d574 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,12 @@ "WebFetch(domain:support.google.com)", "Bash(python3 -m pip list:*)", "Bash(python3:*)", - "Bash(grep:*)" + "Bash(grep:*)", + "Bash(cat:*)", + "Bash(claude config)", + "Bash(claude config list:*)", + "Bash(claude mcp)", + "Bash(claude mcp:*)" ], "deny": [], "additionalDirectories": [ diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index 622b6a4..326cd86 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -5,9 +5,9 @@ ## Project Overview - **Path**: `A:\GiTea\AccountForger` -- **Files**: 987 files -- **Size**: 364.7 MB -- **Last Modified**: 2025-08-10 00:03 +- **Files**: 1011 files +- **Size**: 369.9 MB +- **Last Modified**: 2025-08-10 20:51 ## Technology Stack @@ -70,17 +70,16 @@ controllers/ │ └── platform_controllers/ │ ├── base_controller.py │ ├── base_worker_thread.py +│ ├── facebook_controller.py │ ├── gmail_controller.py │ ├── instagram_controller.py │ ├── method_rotation_mixin.py │ ├── method_rotation_worker_mixin.py │ ├── ok_ru_controller.py │ ├── rotation_error_handler.py -│ ├── safe_imports.py -│ └── tiktok_controller.py +│ └── safe_imports.py database/ │ ├── accounts.db -│ ├── accounts.db-journal │ ├── account_repository.py │ ├── db_manager.py │ ├── schema_v2.sql @@ -204,7 +203,7 @@ resources/ │ │ ├── fr.svg │ │ ├── gmail.svg │ │ ├── instagram.svg -│ │ └── intelsight-logo.svg +│ │ └── intelsight-dark.svg │ └── themes/ │ ├── dark.qss │ └── light.qss @@ -297,6 +296,10 @@ styles/ │ └── __init__.py tests/ │ └── test_method_rotation.py +themes/ +│ ├── qss_generator.py +│ ├── theme_config.py +│ └── __init__.py updates/ │ ├── downloader.py │ ├── update_checker.py @@ -318,6 +321,9 @@ views/ ├── about_dialog.py ├── main_window.py ├── platform_selector.py + ├── base/ + │ ├── theme_aware_widget.py + │ └── __init__.py ├── components/ │ ├── accounts_overview_view.py │ ├── platform_grid_view.py @@ -329,6 +335,7 @@ views/ │ └── __init__.py ├── tabs/ │ ├── accounts_tab.py + │ ├── facebook_generator_tab.py │ ├── generator_tab.py │ ├── generator_tab_modern.py │ └── settings_tab.py @@ -336,13 +343,13 @@ views/ ├── account_card.py ├── account_creation_modal.py ├── account_creation_modal_v2.py + ├── dark_mode_toggle.py ├── forge_animation_widget.py ├── forge_animation_widget_v2.py ├── icon_factory.py ├── language_dropdown.py ├── login_process_modal.py - ├── modern_message_box.py - └── platform_button.py + └── modern_message_box.py ``` ## Key Files @@ -378,3 +385,4 @@ This project is managed with Claude Project Manager. To work with this project: - README updated on 2025-08-09 01:31:28 - README updated on 2025-08-10 00:03:51 - README updated on 2025-08-10 12:55:25 +- README updated on 2025-08-11 19:49:09 diff --git a/controllers/main_controller.py b/controllers/main_controller.py index 0cd491f..005753c 100644 --- a/controllers/main_controller.py +++ b/controllers/main_controller.py @@ -121,8 +121,15 @@ class MainController: gmail_controller.session_controller = self.session_controller self.platform_controllers["gmail"] = gmail_controller - # Hier können in Zukunft weitere Controller hinzugefügt werden: - # self.platform_controllers["facebook"] = FacebookController(...) + # Facebook Controller hinzufügen + from controllers.platform_controllers.facebook_controller import FacebookController + facebook_controller = FacebookController( + self.db_manager, + self.proxy_rotator, + self.email_handler, + self.language_manager + ) + self.platform_controllers["facebook"] = facebook_controller # Signals verbinden self.connect_signals() diff --git a/controllers/platform_controllers/facebook_controller.py b/controllers/platform_controllers/facebook_controller.py new file mode 100644 index 0000000..4142346 --- /dev/null +++ b/controllers/platform_controllers/facebook_controller.py @@ -0,0 +1,233 @@ +""" +Controller für Facebook-spezifische Funktionalität. +Mit Fingerprint-Protection und Anti-Bot Features. +""" + +import logging +import time +import random +from PyQt5.QtCore import QThread, pyqtSignal +from typing import Dict, Any + +from controllers.platform_controllers.base_controller import BasePlatformController +from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread +from views.tabs.facebook_generator_tab import FacebookGeneratorTab + +from social_networks.facebook.facebook_automation import FacebookAutomation +from utils.birthday_generator import BirthdayGenerator +from utils.logger import setup_logger + +logger = setup_logger("facebook_controller") + +class FacebookWorkerThread(BaseAccountCreationWorkerThread): + """Worker-Thread für die Facebook-Account-Erstellung.""" + + def __init__(self, params): + super().__init__(params, "Facebook") + self.birthday_generator = BirthdayGenerator() + + def get_automation_class(self): + """Gibt die Facebook Automation Klasse zurück""" + return FacebookAutomation + + def get_error_interpretations(self) -> Dict[str, str]: + """Facebook-spezifische Fehlerinterpretationen""" + return { + "email already in use": "Diese E-Mail-Adresse wird bereits verwendet", + "phone number required": "Telefonnummer erforderlich", + "invalid birth date": "Ungültiges Geburtsdatum", + "account suspended": "Account wurde gesperrt", + "verification required": "Verifizierung erforderlich" + } + + def run(self): + """Führt die Account-Erstellung aus.""" + try: + self.log_signal.emit("Facebook-Account-Erstellung gestartet...") + self.progress_signal.emit(10) + + # Facebook-Automation initialisieren (mit Anti-Bot Features wie Instagram) + automation = FacebookAutomation( + headless=self.params.get("headless", False), + use_proxy=self.params.get("use_proxy", False), + proxy_type=self.params.get("proxy_type"), + save_screenshots=True, + debug=self.params.get("debug", False), + email_domain=self.params.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com"), + enhanced_stealth=True, # Anti-Bot Features aktivieren + fingerprint_noise=0.5, # Fingerprint-Verschleierung + language="de" # Deutsche Version + ) + + self.update_signal.emit("Browser wird vorbereitet...") + self.progress_signal.emit(20) + + # Namen aufteilen (falls zusammen übergeben) + full_name = self.params.get("full_name", "") + name_parts = full_name.split(" ", 1) + first_name = name_parts[0] if name_parts else "Test" + last_name = name_parts[1] if len(name_parts) > 1 else "User" + + # Geburtsdatum aus Alter generieren + age = self.params.get("age", random.randint(18, 65)) + birth_date_components = self.birthday_generator.generate_birthday_components("facebook", age) + + # Geschlecht aus den Parametern oder default + gender = self.params.get("gender", random.choice(["male", "female"])) + logger.info(f"Geschlecht: {gender}") + + self.log_signal.emit(f"Registriere Account für: {first_name} {last_name} (Alter: {age})") + + # Account registrieren + result = automation.register_account( + first_name=first_name, + last_name=last_name, + birth_date=birth_date_components, + gender=gender, + email=self.params.get("email"), # Wird generiert wenn nicht vorhanden + phone_number=self.params.get("phone_number"), + password=self.params.get("password"), # Wird generiert wenn nicht vorhanden + **self.params.get("additional_params", {}) + ) + + self.progress_signal.emit(100) + + if result["success"]: + self.log_signal.emit("Account erfolgreich erstellt!") + self.finished_signal.emit(result) + else: + error_msg = result.get("error", "Unbekannter Fehler") + self.log_signal.emit(f"Fehler bei der Account-Erstellung: {error_msg}") + self.error_signal.emit(error_msg) + + except Exception as e: + logger.error(f"Fehler im Worker-Thread: {e}") + self.log_signal.emit(f"Schwerwiegender Fehler: {str(e)}") + self.error_signal.emit(str(e)) + finally: + self.running = False + +class FacebookController(BasePlatformController): + """Controller für die Facebook-Plattform.""" + + def __init__(self, db_manager=None, proxy_rotator=None, email_handler=None, language_manager=None): + super().__init__("Facebook", db_manager, proxy_rotator, email_handler, language_manager) + self.worker_thread = None + logger.info("Facebook Controller initialisiert") + + def get_generator_tab(self): + """ + Erstellt und konfiguriert den Generator-Tab für Facebook. + + Returns: + FacebookGeneratorTab: Konfigurierter Tab für Facebook mit Geschlechtsauswahl + """ + # Erstelle Facebook-spezifischen Generator-Tab mit Geschlechtsfeld + generator_tab = FacebookGeneratorTab( + "Facebook", + self.language_manager + ) + + # Facebook-spezifische Konfiguration + self._configure_facebook_fields(generator_tab) + + # Verbinde Signale + generator_tab.start_requested.connect(self.handle_generation_request) + generator_tab.stop_requested.connect(self.stop_generation) + + return generator_tab + + def _configure_facebook_fields(self, generator_tab): + """ + Konfiguriert Facebook-spezifische Felder im Generator-Tab. + + Args: + generator_tab: Der zu konfigurierende Tab + """ + # Facebook-spezifische Konfiguration + # Der FacebookGeneratorTab hat bereits das Geschlechtsfeld integriert + # Vor- und Nachnamen werden im Worker-Thread aus full_name extrahiert + + logger.debug("Facebook-spezifische Felder konfiguriert (inkl. Geschlechtsauswahl)") + + def handle_generation_request(self, params: Dict[str, Any]): + """ + Behandelt eine Anfrage zur Account-Generierung. + + Args: + params: Parameter für die Generierung + """ + logger.info(f"Facebook-Account-Generierung angefordert: {params}") + + # Prüfe ob bereits ein Thread läuft + if self.worker_thread and self.worker_thread.isRunning(): + logger.warning("Ein Account wird bereits erstellt") + return + + # Facebook-spezifische Parameter hinzufügen + params["platform"] = "facebook" + + # Starte Worker-Thread + self.worker_thread = FacebookWorkerThread(params) + + # Verbinde Signale + self.worker_thread.update_signal.connect( + lambda msg: self.status_update.emit(msg) + ) + self.worker_thread.log_signal.connect( + lambda msg: self.log_message.emit(msg) + ) + self.worker_thread.progress_signal.connect( + lambda val: self.progress_update.emit(val) + ) + self.worker_thread.finished_signal.connect( + self._handle_generation_success + ) + self.worker_thread.error_signal.connect( + self._handle_generation_error + ) + + # Starte Thread + self.worker_thread.start() + self.generation_started.emit() + + def _handle_generation_success(self, result: Dict[str, Any]): + """ + Behandelt erfolgreiche Account-Erstellung. + + Args: + result: Ergebnis der Erstellung + """ + logger.info("Facebook-Account erfolgreich erstellt") + + # Speichere Account in Datenbank + if self.db_manager and result.get("account_data"): + account_data = result["account_data"] + account_data["platform"] = "facebook" + + # Speichere in DB + # TODO: DB-Integration + + self.generation_completed.emit(result) + + def _handle_generation_error(self, error_msg: str): + """ + Behandelt Fehler bei der Account-Erstellung. + + Args: + error_msg: Fehlermeldung + """ + logger.error(f"Fehler bei Facebook-Account-Erstellung: {error_msg}") + self.generation_failed.emit(error_msg) + + def stop_generation(self): + """Stoppt die laufende Account-Generierung.""" + if self.worker_thread and self.worker_thread.isRunning(): + logger.info("Stoppe Facebook-Account-Generierung") + self.worker_thread.stop() + self.worker_thread.wait() + + def cleanup(self): + """Räumt Ressourcen auf.""" + self.stop_generation() + logger.info("Facebook Controller aufgeräumt") \ No newline at end of file diff --git a/database/accounts.db b/database/accounts.db index 963148f..39574b4 100644 Binary files a/database/accounts.db and b/database/accounts.db differ diff --git a/social_networks/facebook/__init__.py b/social_networks/facebook/__init__.py index e69de29..c91bdb9 100644 --- a/social_networks/facebook/__init__.py +++ b/social_networks/facebook/__init__.py @@ -0,0 +1,26 @@ +""" +Facebook-Automatisierungsmodul fuer AccountForger. +Implementiert Account-Registrierung und -Login mit Anti-Bot Features. +""" + +from .facebook_automation import FacebookAutomation +from .facebook_registration import FacebookRegistration +from .facebook_login import FacebookLogin +from .facebook_verification import FacebookVerification +from .facebook_ui_helper import FacebookUIHelper +from .facebook_utils import FacebookUtils +from .facebook_selectors import FacebookSelectors +from .facebook_workflow import FacebookWorkflow + +__all__ = [ + 'FacebookAutomation', + 'FacebookRegistration', + 'FacebookLogin', + 'FacebookVerification', + 'FacebookUIHelper', + 'FacebookUtils', + 'FacebookSelectors', + 'FacebookWorkflow' +] + +__version__ = '1.0.0' \ No newline at end of file diff --git a/social_networks/facebook/facebook_automation.py b/social_networks/facebook/facebook_automation.py index e69de29..f72cf0c 100644 --- a/social_networks/facebook/facebook_automation.py +++ b/social_networks/facebook/facebook_automation.py @@ -0,0 +1,451 @@ +# social_networks/facebook/facebook_automation.py + +""" +Facebook-Automatisierung - Hauptklasse für Facebook-Automatisierungsfunktionalität +Mit Anti-Bot Features wie bei Instagram. +""" + +import logging +import time +import random +from datetime import datetime +from typing import Dict, List, Any, Optional, Tuple + +from browser.playwright_manager import PlaywrightManager +from browser.playwright_extensions import PlaywrightExtensions +from browser.fingerprint_protection import FingerprintProtection +from social_networks.base_automation import BaseAutomation +from infrastructure.services.advanced_fingerprint_service import AdvancedFingerprintService +from infrastructure.repositories.fingerprint_repository import FingerprintRepository +from utils.password_generator import PasswordGenerator +from utils.username_generator import UsernameGenerator +from utils.birthday_generator import BirthdayGenerator +from utils.human_behavior import HumanBehavior +from utils.email_handler import EmailHandler + +# Importiere Helferklassen +from .facebook_registration import FacebookRegistration +from .facebook_login import FacebookLogin +from .facebook_verification import FacebookVerification +from .facebook_ui_helper import FacebookUIHelper +from .facebook_utils import FacebookUtils +from utils.logger import setup_logger + +# Konfiguriere Logger +logger = setup_logger("facebook_automation") + +class FacebookAutomation(BaseAutomation): + """ + Hauptklasse für die Facebook-Automatisierung. + Implementiert die Registrierung und Anmeldung bei Facebook mit Anti-Bot Features. + """ + + def __init__(self, + headless: bool = False, + use_proxy: bool = False, + proxy_type: str = None, + save_screenshots: bool = True, + screenshots_dir: str = None, + slowmo: int = 0, + debug: bool = False, + email_domain: str = "z5m7q9dk3ah2v1plx6ju.com", + enhanced_stealth: bool = True, + fingerprint_noise: float = 0.5, + window_position = None, + fingerprint = None, + language: str = "de"): + """ + Initialisiert die Facebook-Automatisierung. + + Args: + headless: Ob der Browser im Headless-Modus ausgeführt werden soll + use_proxy: Ob ein Proxy verwendet werden soll + proxy_type: Proxy-Typ ("ipv4", "ipv6", "mobile") oder None für zufälligen Typ + save_screenshots: Ob Screenshots gespeichert werden sollen + screenshots_dir: Verzeichnis für Screenshots + slowmo: Verzögerung zwischen Aktionen in Millisekunden + debug: Ob Debug-Informationen angezeigt werden sollen + email_domain: Domain für generierte E-Mail-Adressen + enhanced_stealth: Ob erweiterter Stealth-Modus aktiviert werden soll + fingerprint_noise: Menge an Rauschen für Fingerprint-Verschleierung (0.0-1.0) + window_position: Position des Browser-Fensters + fingerprint: Vordefinierter Fingerprint + language: Sprache für Facebook ("de" oder "en") + """ + # Initialisiere die Basisklasse + super().__init__( + headless=headless, + use_proxy=use_proxy, + proxy_type=proxy_type, + save_screenshots=save_screenshots, + screenshots_dir=screenshots_dir, + slowmo=slowmo, + debug=debug, + email_domain=email_domain, + window_position=window_position + ) + + # Facebook-spezifische Einstellungen + self.language = language + self.base_url = f"https://www.facebook.com/?locale={language}_DE" if language == "de" else "https://www.facebook.com" + + # Stealth-Modus-Einstellungen (wie bei Instagram) + self.enhanced_stealth = enhanced_stealth + self.fingerprint_noise = max(0.0, min(1.0, fingerprint_noise)) + + # Initialisiere Helferklassen + self.registration = FacebookRegistration(self) + self.login = FacebookLogin(self) + self.verification = FacebookVerification(self) + self.ui_helper = FacebookUIHelper(self) + self.utils = FacebookUtils(self) + + # Zusätzliche Hilfsklassen + self.password_generator = PasswordGenerator() + self.username_generator = UsernameGenerator() + self.birthday_generator = BirthdayGenerator() + self.human_behavior = HumanBehavior(speed_factor=0.8, randomness=0.6) + self.email_handler = EmailHandler() # Fuer E-Mail-Verifikation wie bei Instagram + + # Fingerprint Service für Account-gebundene Fingerprints (wie bei Instagram) + self.fingerprint_service = AdvancedFingerprintService(FingerprintRepository()) + self.account_fingerprint = None + + # Nutze übergebenen Fingerprint wenn vorhanden + self.provided_fingerprint = fingerprint + + logger.info(f"Facebook-Automatisierung initialisiert (Sprache: {language})") + + def _initialize_browser(self) -> bool: + """ + Initialisiert den Browser mit Anti-Bot Features. + Identisch zu Instagram für konsistente Fingerprint-Protection. + + Returns: + bool: True bei Erfolg, False bei Fehler + """ + try: + # Proxy-Konfiguration, falls aktiviert + proxy_config = None + if self.use_proxy: + proxy_config = self.proxy_rotator.get_proxy(self.proxy_type) + if not proxy_config: + logger.warning(f"Kein Proxy vom Typ '{self.proxy_type}' verfügbar, verwende direkten Zugriff") + + # Browser initialisieren + self.browser = PlaywrightManager( + headless=self.headless, + proxy=proxy_config, + browser_type="chromium", + screenshots_dir=self.screenshots_dir, + slowmo=self.slowmo + ) + + # Browser starten + self.browser.start() + + # Erweiterten Fingerprint-Schutz aktivieren (wie bei Instagram) + if self.enhanced_stealth: + # Erstelle Extensions-Objekt + extensions = PlaywrightExtensions(self.browser) + + # Methoden anhängen + extensions.hook_into_playwright_manager() + + # Fingerprint-Schutz aktivieren + if self.provided_fingerprint: + logger.info("Verwende bereitgestellten Fingerprint für Account-Erstellung") + if isinstance(self.provided_fingerprint, dict): + from infrastructure.models.browser_fingerprint import BrowserFingerprint + fingerprint_obj = BrowserFingerprint.from_dict(self.provided_fingerprint) + else: + fingerprint_obj = self.provided_fingerprint + + self.account_fingerprint = fingerprint_obj + fingerprint_config = fingerprint_obj.to_config() + else: + # Generiere neuen Fingerprint + self.account_fingerprint = self.fingerprint_service.generate_fingerprint("facebook") + fingerprint_config = self.account_fingerprint.to_config() + + # Fingerprint-Protection mit Noise-Level + protection = FingerprintProtection( + noise_level=self.fingerprint_noise, + fingerprint_config=fingerprint_config + ) + protection.apply(self.browser.page) + + logger.info(f"Fingerprint-Schutz aktiviert (Noise-Level: {self.fingerprint_noise})") + + # Facebook-spezifische Browser-Einstellungen + self._apply_facebook_specific_settings() + + logger.info("Browser erfolgreich initialisiert") + return True + + except Exception as e: + logger.error(f"Fehler bei der Browser-Initialisierung: {e}") + return False + + def _apply_facebook_specific_settings(self): + """ + Wendet Facebook-spezifische Browser-Einstellungen an. + """ + try: + # Setze Facebook-spezifische Headers + extra_headers = { + 'Accept-Language': 'de-DE,de;q=0.9,en;q=0.8' if self.language == 'de' else 'en-US,en;q=0.9', + 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + } + + self.browser.page.set_extra_http_headers(extra_headers) + + # Viewport für Desktop + self.browser.page.set_viewport_size({ + 'width': random.randint(1366, 1920), + 'height': random.randint(768, 1080) + }) + + logger.debug("Facebook-spezifische Browser-Einstellungen angewendet") + + except Exception as e: + logger.warning(f"Fehler beim Anwenden Facebook-spezifischer Einstellungen: {e}") + + def register_account(self, + first_name: str, + last_name: str, + birth_date: Dict[str, int], + gender: str, + email: str = None, + phone_number: str = None, + password: str = None, + **kwargs) -> Dict[str, Any]: + """ + Registriert einen neuen Facebook-Account. + + Args: + first_name: Vorname + last_name: Nachname + birth_date: Geburtsdatum als Dict mit 'day', 'month', 'year' + gender: Geschlecht ('male', 'female', 'custom') + email: E-Mail-Adresse (optional, wird generiert wenn nicht angegeben) + phone_number: Telefonnummer (optional, alternativ zu E-Mail) + password: Passwort (optional, wird generiert wenn nicht angegeben) + **kwargs: Weitere optionale Parameter + + Returns: + Dict[str, Any]: Ergebnis der Registrierung + """ + logger.info(f"Starte Facebook-Registrierung für {first_name} {last_name}") + + try: + # Browser initialisieren + if not self._initialize_browser(): + return {"success": False, "error": "Browser konnte nicht initialisiert werden"} + + # Fingerprint rotieren für neue Registrierung + if self.enhanced_stealth and hasattr(self.browser, 'rotate_fingerprint'): + self.browser.rotate_fingerprint() + logger.info("Browser-Fingerprint für Registrierung rotiert") + + # E-Mail generieren wenn nicht vorhanden + if not email and not phone_number: + email = self._generate_email(first_name, last_name) + logger.info(f"Generierte E-Mail: {email}") + + # Passwort generieren wenn nicht vorhanden + if not password: + password = self.password_generator.generate_password( + length=random.randint(12, 16), + include_special=True + ) + logger.info("Passwort generiert") + + # Account-Daten vorbereiten + account_data = { + "first_name": first_name, + "last_name": last_name, + "birth_date": birth_date, + "gender": gender, + "email": email, + "phone_number": phone_number, + "password": password, + "language": self.language, + **kwargs + } + + # Delegiere an Registration-Klasse + result = self.registration.register_account(account_data) + + # Screenshot am Ende + self._take_screenshot(f"registration_finished_{int(time.time())}") + + # Fingerprint-Statistiken aktualisieren bei Erfolg + if result.get("success") and self.account_fingerprint: + try: + account_id = result.get("account_data", {}).get("account_id", f"fb_{int(time.time())}") + self.fingerprint_service.update_fingerprint_stats( + self.account_fingerprint.fingerprint_id, + account_id, + success=True + ) + logger.info("Fingerprint-Statistiken für erfolgreiche Registrierung aktualisiert") + except Exception as e: + logger.warning(f"Fehler beim Aktualisieren der Fingerprint-Statistiken: {e}") + + # Status aktualisieren + self.status.update(result) + + return result + + except Exception as e: + error_msg = f"Unerwarteter Fehler bei der Registrierung: {str(e)}" + logger.error(error_msg, exc_info=True) + + # Fehler-Screenshot + self._take_screenshot(f"registration_error_{int(time.time())}") + + self.status.update({ + "success": False, + "error": error_msg, + "stage": "error" + }) + + return self.status + finally: + # Browser offen lassen für Debugging + logger.info("Registrierung abgeschlossen - Browser bleibt offen") + + def login_account(self, email_or_phone: str, password: str, account_id: Optional[str] = None, **kwargs) -> Dict[str, Any]: + """ + Meldet sich bei einem bestehenden Facebook-Account an. + + Args: + email_or_phone: E-Mail-Adresse oder Telefonnummer + password: Passwort + account_id: Optional account ID für gespeicherten Fingerprint + **kwargs: Weitere optionale Parameter + + Returns: + Dict[str, Any]: Ergebnis der Anmeldung + """ + logger.info(f"Starte Facebook-Login für '{email_or_phone}'") + + try: + # Browser initialisieren mit Account-Fingerprint wenn vorhanden + if not self.browser or not hasattr(self.browser, 'page') or not self.browser.page: + if account_id: + # Verwende Account-spezifischen Fingerprint + if not self._initialize_browser_with_fingerprint(account_id): + return {"success": False, "error": "Browser konnte nicht mit Account-Fingerprint initialisiert werden"} + else: + # Verwende zufälligen Fingerprint + if not self._initialize_browser(): + return {"success": False, "error": "Browser konnte nicht initialisiert werden"} + else: + logger.info("Verwende bereits geöffneten Browser für Login") + + # Delegiere an Login-Klasse + result = self.login.login_account(email_or_phone, password, **kwargs) + + # Screenshot am Ende + self._take_screenshot(f"login_finished_{int(time.time())}") + + # Fingerprint-Statistiken aktualisieren + if account_id and self.account_fingerprint and result.get("success"): + try: + self.fingerprint_service.update_fingerprint_stats( + self.account_fingerprint.fingerprint_id, + account_id, + success=True + ) + logger.info("Fingerprint-Statistiken für erfolgreichen Login aktualisiert") + except Exception as e: + logger.warning(f"Fehler beim Aktualisieren der Fingerprint-Statistiken: {e}") + + # Status aktualisieren + self.status.update(result) + + return result + + except Exception as e: + error_msg = f"Unerwarteter Fehler beim Login: {str(e)}" + logger.error(error_msg, exc_info=True) + + # Fehler-Screenshot + self._take_screenshot(f"login_error_{int(time.time())}") + + self.status.update({ + "success": False, + "error": error_msg, + "stage": "error" + }) + + return self.status + finally: + # Browser offen lassen für User-Kontrolle + logger.info("Login abgeschlossen - Browser bleibt offen") + + def _initialize_browser_with_fingerprint(self, account_id: str) -> bool: + """ + Initialisiert den Browser mit einem Account-spezifischen Fingerprint. + + Args: + account_id: Account-ID für den Fingerprint + + Returns: + bool: True bei Erfolg, False bei Fehler + """ + try: + # Lade gespeicherten Fingerprint für Account + saved_fingerprint = self.fingerprint_service.get_fingerprint_for_account(account_id, "facebook") + + if saved_fingerprint: + logger.info(f"Verwende gespeicherten Fingerprint für Account {account_id}") + self.provided_fingerprint = saved_fingerprint + else: + logger.info(f"Kein gespeicherter Fingerprint für Account {account_id}, generiere neuen") + self.provided_fingerprint = self.fingerprint_service.generate_fingerprint("facebook") + + # Browser mit Fingerprint initialisieren + return self._initialize_browser() + + except Exception as e: + logger.error(f"Fehler bei Browser-Initialisierung mit Fingerprint: {e}") + return False + + def _generate_email(self, first_name: str, last_name: str) -> str: + """ + Generiert eine E-Mail-Adresse basierend auf dem Namen. + + Args: + first_name: Vorname + last_name: Nachname + + Returns: + str: Generierte E-Mail-Adresse + """ + # Entferne Umlaute und Sonderzeichen + first_clean = first_name.lower().replace('ä', 'ae').replace('ö', 'oe').replace('ü', 'ue').replace('ß', 'ss') + last_clean = last_name.lower().replace('ä', 'ae').replace('ö', 'oe').replace('ü', 'ue').replace('ß', 'ss') + + # Verschiedene E-Mail-Formate + formats = [ + f"{first_clean}.{last_clean}", + f"{first_clean}{last_clean}", + f"{first_clean[0]}{last_clean}", + f"{first_clean}_{last_clean}", + f"{last_clean}.{first_clean}", + ] + + # Wähle zufälliges Format + base_email = random.choice(formats) + + # Füge optional Zahlen hinzu + if random.random() > 0.5: + base_email += str(random.randint(1, 999)) + + # Füge Domain hinzu + return f"{base_email}@{self.email_domain}" \ No newline at end of file diff --git a/social_networks/facebook/facebook_login.py b/social_networks/facebook/facebook_login.py index e69de29..71fa1bc 100644 --- a/social_networks/facebook/facebook_login.py +++ b/social_networks/facebook/facebook_login.py @@ -0,0 +1,58 @@ +# social_networks/facebook/facebook_login.py + +""" +Facebook-Login - Klasse für die Anmeldefunktionalität bei Facebook +Placeholder für zukünftige Implementierung. +""" + +import logging +import time +from typing import Dict, Any + +from .facebook_selectors import FacebookSelectors +from .facebook_workflow import FacebookWorkflow +from utils.logger import setup_logger + +logger = setup_logger("facebook_login") + +class FacebookLogin: + """ + Klasse für die Anmeldung bei Facebook-Konten. + TODO: Vollständige Implementierung wenn Login-Details verfügbar. + """ + + def __init__(self, automation): + """ + Initialisiert die Facebook-Login-Funktionalität. + + Args: + automation: Referenz auf die Hauptautomatisierungsklasse + """ + self.automation = automation + self.selectors = FacebookSelectors() + self.workflow = FacebookWorkflow.get_login_workflow() + + logger.debug("Facebook-Login initialisiert") + + def login_account(self, email_or_phone: str, password: str, **kwargs) -> Dict[str, Any]: + """ + Führt den Login-Prozess für ein Facebook-Konto durch. + + Args: + email_or_phone: E-Mail-Adresse oder Telefonnummer + password: Passwort + **kwargs: Weitere optionale Parameter + + Returns: + Dict[str, Any]: Ergebnis des Logins + """ + logger.info(f"Starte Facebook-Login für {email_or_phone}") + + # TODO: Implementierung sobald Login-Details verfügbar + logger.warning("Facebook-Login noch nicht vollständig implementiert") + + return { + "success": False, + "error": "Login-Funktion noch nicht implementiert", + "stage": "not_implemented" + } \ No newline at end of file diff --git a/social_networks/facebook/facebook_registration.py b/social_networks/facebook/facebook_registration.py index e69de29..40d7325 100644 --- a/social_networks/facebook/facebook_registration.py +++ b/social_networks/facebook/facebook_registration.py @@ -0,0 +1,630 @@ +# social_networks/facebook/facebook_registration.py + +""" +Facebook-Registrierung - Klasse für die Registrierungsfunktionalität bei Facebook +""" + +import logging +import time +import re +from typing import Dict, List, Any, Optional, Tuple + +from .facebook_selectors import FacebookSelectors +from .facebook_workflow import FacebookWorkflow +from utils.logger import setup_logger + +# Konfiguriere Logger +logger = setup_logger("facebook_registration") + +class FacebookRegistration: + """ + Klasse für die Registrierung neuer Facebook-Konten. + Enthält alle Methoden für den Registrierungsprozess. + """ + + def __init__(self, automation): + """ + Initialisiert die Facebook-Registrierungs-Funktionalität. + + Args: + automation: Referenz auf die Hauptautomatisierungsklasse + """ + self.automation = automation + self.selectors = FacebookSelectors() + self.workflow = FacebookWorkflow.get_registration_workflow() + + logger.debug("Facebook-Registrierung initialisiert") + + def register_account(self, account_data: Dict[str, Any]) -> Dict[str, Any]: + """ + Führt den Registrierungsprozess für einen neuen Facebook-Account durch. + + Args: + account_data: Dictionary mit Account-Daten + + Returns: + Dict[str, Any]: Ergebnis der Registrierung + """ + logger.info(f"Starte Facebook-Registrierung für {account_data['first_name']} {account_data['last_name']}") + + try: + # 1. Zur Facebook-Hauptseite navigieren + self.automation._send_status_update("Öffne Facebook-Webseite") + self.automation._send_log_update("Navigiere zu Facebook...") + + if not self._navigate_to_facebook(): + return { + "success": False, + "error": "Konnte nicht zu Facebook navigieren", + "stage": "navigation" + } + + # 2. Cookie-Consent behandeln + self.automation._send_status_update("Behandle Cookie-Einstellungen") + self.automation._send_log_update("Lehne optionale Cookies ab...") + + if not self._handle_cookie_consent(): + logger.warning("Cookie-Consent konnte nicht behandelt werden, fahre trotzdem fort") + + # 3. Registrierungsformular öffnen + self.automation._send_status_update("Öffne Registrierungsformular") + self.automation._send_log_update("Klicke auf 'Neues Konto erstellen'...") + + if not self._open_registration_form(): + return { + "success": False, + "error": "Konnte Registrierungsformular nicht öffnen", + "stage": "open_form" + } + + # 4. Registrierungsformular ausfüllen + self.automation._send_status_update("Fülle Registrierungsformular aus") + self.automation._send_log_update("Gebe persönliche Daten ein...") + + if not self._fill_registration_form(account_data): + return { + "success": False, + "error": "Fehler beim Ausfüllen des Registrierungsformulars", + "stage": "fill_form" + } + + # 5. Formular absenden + self.automation._send_status_update("Sende Registrierung ab") + self.automation._send_log_update("Klicke auf 'Registrieren'...") + + if not self._submit_registration(): + return { + "success": False, + "error": "Fehler beim Absenden der Registrierung", + "stage": "submit" + } + + # 6. E-Mail-Verifikation behandeln + needs_verification = self._check_needs_verification() + + if needs_verification: + self.automation._send_status_update("E-Mail-Verifikation erforderlich") + self.automation._send_log_update("Warte auf Verifikationscode...") + + # Warte auf Verifikationscode + verification_code = self._wait_for_verification_code(account_data.get("email")) + + if verification_code: + if not self._enter_verification_code(verification_code): + return { + "success": False, + "error": "Fehler bei der E-Mail-Verifikation", + "stage": "verification" + } + else: + return { + "success": False, + "error": "Verifikationscode nicht erhalten", + "stage": "verification_timeout" + } + + # 7. Erfolgreiche Registrierung überprüfen + if not self._check_registration_success(): + return { + "success": False, + "error": "Registrierung fehlgeschlagen", + "stage": "final_check" + } + + # Registrierung erfolgreich + logger.info(f"Facebook-Account erfolgreich erstellt") + self.automation._send_status_update("Registrierung erfolgreich!") + self.automation._send_log_update("Account wurde erfolgreich erstellt") + + # Account-Daten für Rückgabe vorbereiten + account_data["platform"] = "facebook" + account_data["created_at"] = time.time() + + return { + "success": True, + "stage": "completed", + "account_data": account_data + } + + except Exception as e: + error_msg = f"Unerwarteter Fehler bei der Facebook-Registrierung: {str(e)}" + logger.error(error_msg, exc_info=True) + + return { + "success": False, + "error": error_msg, + "stage": "exception", + "account_data": account_data + } + + def _navigate_to_facebook(self) -> bool: + """ + Navigiert zur Facebook-Hauptseite. + + Returns: + bool: True bei Erfolg + """ + try: + logger.info(f"Navigiere zu {self.automation.base_url}") + + # Navigiere zur Facebook-Seite + self.automation.browser.navigate_to(self.automation.base_url) + + # Warte auf Seitenladung + self.automation.human_behavior.wait_for_page_load(multiplier=1.5) + + # Screenshot + self.automation._take_screenshot("facebook_homepage") + + # Prüfe ob wir auf Facebook sind + current_url = self.automation.browser.page.url + if "facebook.com" in current_url: + logger.info("Erfolgreich zu Facebook navigiert") + return True + else: + logger.error(f"Nicht auf Facebook gelandet: {current_url}") + return False + + except Exception as e: + logger.error(f"Fehler bei Navigation zu Facebook: {e}") + return False + + def _handle_cookie_consent(self) -> bool: + """ + Behandelt den Cookie-Consent-Dialog. + + Returns: + bool: True wenn behandelt + """ + try: + logger.info("Prüfe auf Cookie-Consent-Dialog") + + # Warte kurz auf möglichen Cookie-Dialog + self.automation.human_behavior.random_delay(1.0, 2.0) + + # Versuche "Optionale Cookies ablehnen" zu klicken + decline_clicked = False + + # Methode 1: Direkter Selektor + if self.automation.browser.is_element_visible(self.selectors.COOKIE_DECLINE_BUTTON, timeout=2000): + if self.automation.browser.click_element(self.selectors.COOKIE_DECLINE_BUTTON): + logger.info("Cookie-Consent abgelehnt (Methode 1)") + decline_clicked = True + + # Methode 2: Alternative Selektoren + if not decline_clicked: + for selector in [self.selectors.COOKIE_DECLINE_BUTTON_ALT, + "button:has-text('Optionale Cookies ablehnen')", + "//button[contains(., 'Optionale Cookies ablehnen')]"]: + try: + if self.automation.browser.click_element(selector, timeout=1000): + logger.info(f"Cookie-Consent abgelehnt mit Selektor: {selector}") + decline_clicked = True + break + except: + continue + + # Methode 3: Fuzzy Button Click + if not decline_clicked: + if self.automation.ui_helper.click_button_fuzzy( + self.selectors.get_button_texts("decline_cookies"), + fallback_selector="button" + ): + logger.info("Cookie-Consent abgelehnt (Fuzzy Match)") + decline_clicked = True + + if decline_clicked: + # Warte auf Dialog-Schließung + self.automation.human_behavior.random_delay(1.0, 2.0) + self.automation._take_screenshot("after_cookie_consent") + return True + else: + logger.debug("Kein Cookie-Consent-Dialog gefunden oder bereits behandelt") + return False + + except Exception as e: + logger.error(f"Fehler bei Cookie-Consent-Behandlung: {e}") + return False + + def _open_registration_form(self) -> bool: + """ + Öffnet das Registrierungsformular. + + Returns: + bool: True bei Erfolg + """ + try: + logger.info("Öffne Registrierungsformular") + + # Versuche "Neues Konto erstellen" zu klicken + button_clicked = False + + # Methode 1: data-testid Selektor + if self.automation.browser.is_element_visible(self.selectors.CREATE_ACCOUNT_BUTTON, timeout=3000): + if self.automation.browser.click_element(self.selectors.CREATE_ACCOUNT_BUTTON): + logger.info("Registrierungsformular geöffnet (data-testid)") + button_clicked = True + + # Methode 2: Alternative Selektoren + if not button_clicked: + for selector in [self.selectors.CREATE_ACCOUNT_BUTTON_ALT, + "a:has-text('Neues Konto erstellen')", + "a[href*='/r.php']"]: + try: + if self.automation.browser.click_element(selector, timeout=2000): + logger.info(f"Registrierungsformular geöffnet mit: {selector}") + button_clicked = True + break + except: + continue + + # Methode 3: Fuzzy Button Click + if not button_clicked: + if self.automation.ui_helper.click_button_fuzzy( + self.selectors.get_button_texts("create_account") + ): + logger.info("Registrierungsformular geöffnet (Fuzzy Match)") + button_clicked = True + + if button_clicked: + # Warte auf Formular-Ladung + self.automation.human_behavior.wait_for_page_load() + self.automation._take_screenshot("registration_form") + + # Prüfe ob wir auf der Registrierungsseite sind + current_url = self.automation.browser.page.url + if "/r.php" in current_url or "registration" in current_url: + logger.info("Registrierungsformular erfolgreich geöffnet") + return True + + logger.error("Konnte Registrierungsformular nicht öffnen") + return False + + except Exception as e: + logger.error(f"Fehler beim Öffnen des Registrierungsformulars: {e}") + return False + + def _fill_registration_form(self, account_data: Dict[str, Any]) -> bool: + """ + Füllt das Registrierungsformular aus. + + Args: + account_data: Account-Daten + + Returns: + bool: True bei Erfolg + """ + try: + logger.info("Fülle Registrierungsformular aus") + + # Vorname + if not self.automation.ui_helper.type_text_human_like( + self.selectors.REG_FIRSTNAME_FIELD, + account_data["first_name"] + ): + logger.error("Fehler beim Eingeben des Vornamens") + return False + + # Nachname + if not self.automation.ui_helper.type_text_human_like( + self.selectors.REG_LASTNAME_FIELD, + account_data["last_name"] + ): + logger.error("Fehler beim Eingeben des Nachnamens") + return False + + # Geburtsdatum + birth_date = account_data["birth_date"] + + # Tag auswählen + if not self.automation.browser.select_option( + self.selectors.REG_BIRTHDAY_DAY, + str(birth_date["day"]) + ): + logger.error("Fehler beim Auswählen des Geburtstags") + return False + + # Monat auswählen + if not self.automation.browser.select_option( + self.selectors.REG_BIRTHDAY_MONTH, + str(birth_date["month"]) + ): + logger.error("Fehler beim Auswählen des Geburtsmonats") + return False + + # Jahr auswählen + if not self.automation.browser.select_option( + self.selectors.REG_BIRTHDAY_YEAR, + str(birth_date["year"]) + ): + logger.error("Fehler beim Auswählen des Geburtsjahrs") + return False + + self.automation.human_behavior.random_delay(0.5, 1.0) + + # Geschlecht auswählen + gender_selector = self.selectors.get_gender_selector(account_data["gender"]) + if not self.automation.browser.click_element(gender_selector): + logger.error(f"Fehler beim Auswählen des Geschlechts: {account_data['gender']}") + return False + + self.automation.human_behavior.random_delay(0.5, 1.0) + + # E-Mail oder Telefonnummer + contact_field = account_data.get("email") or account_data.get("phone_number") + if not contact_field: + logger.error("Keine E-Mail oder Telefonnummer angegeben") + return False + + if not self.automation.ui_helper.type_text_human_like( + self.selectors.REG_EMAIL_OR_PHONE, + contact_field + ): + logger.error("Fehler beim Eingeben der Kontaktdaten") + return False + + # Warte auf mögliches E-Mail-Bestätigungsfeld + self.automation.human_behavior.random_delay(1.0, 2.0) + + # Wenn E-Mail eingegeben wurde, könnte ein Bestätigungsfeld erscheinen + if account_data.get("email"): + if self.automation.browser.is_element_visible(self.selectors.REG_EMAIL_CONFIRM, timeout=2000): + logger.info("E-Mail-Bestätigungsfeld erkannt") + if not self.automation.ui_helper.type_text_human_like( + self.selectors.REG_EMAIL_CONFIRM, + account_data["email"] + ): + logger.warning("Fehler beim Bestätigen der E-Mail") + + # Passwort + if not self.automation.ui_helper.type_text_human_like( + self.selectors.REG_PASSWORD, + account_data["password"] + ): + logger.error("Fehler beim Eingeben des Passworts") + return False + + # Screenshot des ausgefüllten Formulars + self.automation._take_screenshot("filled_registration_form") + + logger.info("Registrierungsformular erfolgreich ausgefüllt") + return True + + except Exception as e: + logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {e}") + return False + + def _submit_registration(self) -> bool: + """ + Sendet das Registrierungsformular ab. + + Returns: + bool: True bei Erfolg + """ + try: + logger.info("Sende Registrierungsformular ab") + + # Versuche Submit-Button zu klicken + submit_clicked = False + + # Methode 1: Name-Attribut + if self.automation.browser.is_element_visible(self.selectors.REG_SUBMIT_BUTTON, timeout=2000): + if self.automation.browser.click_element(self.selectors.REG_SUBMIT_BUTTON): + logger.info("Registrierung abgesendet (name-Attribut)") + submit_clicked = True + + # Methode 2: Text-basiert + if not submit_clicked: + for text in self.selectors.get_button_texts("register"): + selector = f"button:has-text('{text}')" + try: + if self.automation.browser.click_element(selector, timeout=1000): + logger.info(f"Registrierung abgesendet mit: {selector}") + submit_clicked = True + break + except: + continue + + # Methode 3: Fuzzy Match + if not submit_clicked: + if self.automation.ui_helper.click_button_fuzzy( + self.selectors.get_button_texts("register") + ): + logger.info("Registrierung abgesendet (Fuzzy Match)") + submit_clicked = True + + if submit_clicked: + # Warte auf Navigation + self.automation.human_behavior.wait_for_page_load(multiplier=2.0) + self.automation._take_screenshot("after_submit") + return True + else: + logger.error("Konnte Registrierung nicht absenden") + return False + + except Exception as e: + logger.error(f"Fehler beim Absenden der Registrierung: {e}") + return False + + def _check_needs_verification(self) -> bool: + """ + Prüft ob eine E-Mail-Verifikation erforderlich ist. + + Returns: + bool: True wenn Verifikation erforderlich + """ + try: + # Warte kurz + self.automation.human_behavior.random_delay(2.0, 3.0) + + # Prüfe URL + current_url = self.automation.browser.page.url + if "confirmemail" in current_url or "confirm" in current_url: + logger.info("E-Mail-Verifikation erforderlich (URL-Check)") + return True + + # Prüfe auf Verifikations-Input + if self.automation.browser.is_element_visible(self.selectors.VERIFICATION_CODE_INPUT, timeout=2000): + logger.info("E-Mail-Verifikation erforderlich (Input-Field)") + return True + + # Prüfe auf Verifikations-Text + page_content = self.automation.browser.page.content().lower() + verification_keywords = ["bestätigungscode", "verification code", "confirm email", "code eingeben"] + + for keyword in verification_keywords: + if keyword in page_content: + logger.info(f"E-Mail-Verifikation erforderlich (Keyword: {keyword})") + return True + + logger.info("Keine E-Mail-Verifikation erforderlich") + return False + + except Exception as e: + logger.error(f"Fehler bei Verifikations-Check: {e}") + return False + + def _wait_for_verification_code(self, email: str) -> Optional[str]: + """ + Wartet auf den Verifikationscode aus der E-Mail. + + Args: + email: E-Mail-Adresse + + Returns: + Optional[str]: Verifikationscode oder None + """ + logger.info(f"Warte auf Verifikationscode für {email}") + + # Delegiere an Verification-Klasse + return self.automation.verification.wait_for_email_code(email, timeout=120) + + def _enter_verification_code(self, code: str) -> bool: + """ + Gibt den Verifikationscode ein. + + Args: + code: Verifikationscode + + Returns: + bool: True bei Erfolg + """ + try: + logger.info(f"Gebe Verifikationscode ein: {code}") + + # Code eingeben + if not self.automation.ui_helper.type_text_human_like( + self.selectors.VERIFICATION_CODE_INPUT, + code + ): + logger.error("Fehler beim Eingeben des Verifikationscodes") + return False + + # Weiter-Button klicken + continue_clicked = False + + # Versuche verschiedene Selektoren + for text in self.selectors.get_button_texts("continue"): + selector = f"button:has-text('{text}')" + try: + if self.automation.browser.click_element(selector, timeout=1000): + logger.info(f"Verifikation fortgesetzt mit: {selector}") + continue_clicked = True + break + except: + continue + + # Oder Enter drücken + if not continue_clicked: + self.automation.browser.page.keyboard.press("Enter") + logger.info("Verifikation mit Enter fortgesetzt") + + # Warte auf Navigation + self.automation.human_behavior.wait_for_page_load() + + # Prüfe auf OK-Button (Popup nach erfolgreicher Verifikation) + if self.automation.browser.is_element_visible(self.selectors.VERIFICATION_OK_BUTTON, timeout=3000): + self.automation.browser.click_element(self.selectors.VERIFICATION_OK_BUTTON) + logger.info("OK-Button nach Verifikation geklickt") + + return True + + except Exception as e: + logger.error(f"Fehler beim Eingeben des Verifikationscodes: {e}") + return False + + def _check_registration_success(self) -> bool: + """ + Überprüft ob die Registrierung erfolgreich war. + + Returns: + bool: True bei Erfolg + """ + try: + # Warte auf finale Navigation + self.automation.human_behavior.wait_for_page_load(multiplier=2.0) + + # Screenshot + self.automation._take_screenshot("registration_final") + + # Prüfe URL + current_url = self.automation.browser.page.url + logger.info(f"Finale URL: {current_url}") + + # Erfolgs-URLs + success_patterns = [ + "facebook.com/?", + "facebook.com/home", + "facebook.com/feed", + "welcome", + "onboarding" + ] + + for pattern in success_patterns: + if pattern in current_url: + logger.info(f"Registrierung erfolgreich (URL-Pattern: {pattern})") + return True + + # Prüfe auf Erfolgs-Indikatoren + for indicator in self.selectors.SUCCESS_INDICATORS: + if self.automation.browser.is_element_visible(indicator, timeout=2000): + logger.info(f"Registrierung erfolgreich (Indikator: {indicator})") + return True + + # Prüfe auf Fehler + if self.automation.browser.is_element_visible(self.selectors.ERROR_MESSAGE, timeout=1000): + error_text = self.automation.browser.get_text(self.selectors.ERROR_MESSAGE) + logger.error(f"Registrierungsfehler: {error_text}") + return False + + # Bei Unsicherheit als erfolgreich werten wenn keine Login-Form mehr da ist + if not self.automation.browser.is_element_visible(self.selectors.REG_FIRSTNAME_FIELD, timeout=1000): + logger.info("Registrierung wahrscheinlich erfolgreich (kein Formular mehr sichtbar)") + return True + + logger.warning("Registrierungsstatus unklar") + return False + + except Exception as e: + logger.error(f"Fehler bei Erfolgs-Check: {e}") + return False \ No newline at end of file diff --git a/social_networks/facebook/facebook_selectors.py b/social_networks/facebook/facebook_selectors.py index e69de29..e3a9be3 100644 --- a/social_networks/facebook/facebook_selectors.py +++ b/social_networks/facebook/facebook_selectors.py @@ -0,0 +1,173 @@ +""" +Facebook-Selektoren für die Automatisierung. +Basierend auf dem aktuellen Facebook UI (2024/2025). +""" + +from typing import List, Dict, Any + + +class FacebookSelectors: + """ + Zentrale Sammlung aller Facebook-Selektoren für die Automatisierung. + """ + + # ===== COOKIE CONSENT ===== + COOKIE_DIALOG = "div[role='dialog']" + COOKIE_DECLINE_BUTTON = "div.html-div span:has-text('Optionale Cookies ablehnen')" + COOKIE_DECLINE_BUTTON_ALT = "//span[contains(text(), 'Optionale Cookies ablehnen')]" + COOKIE_ACCEPT_BUTTON = "button:has-text('Alle Cookies erlauben')" + + # ===== LANDING PAGE ===== + CREATE_ACCOUNT_BUTTON = "a[data-testid='open-registration-form-button']" + CREATE_ACCOUNT_BUTTON_ALT = "a:has-text('Neues Konto erstellen')" + CREATE_ACCOUNT_BUTTON_ID = "#u_0_0_gL" # ID kann sich ändern + + # ===== LOGIN FORM ===== + LOGIN_EMAIL_FIELD = "input[name='email']" + LOGIN_PASSWORD_FIELD = "input[name='pass']" + LOGIN_BUTTON = "button[name='login']" + LOGIN_BUTTON_ALT = "button[data-testid='royal_login_button']" + + # ===== REGISTRATION FORM ===== + # Name fields + REG_FIRSTNAME_FIELD = "input[name='firstname']" + REG_LASTNAME_FIELD = "input[name='lastname']" + + # Birthday selects + REG_BIRTHDAY_DAY = "select[name='birthday_day']" + REG_BIRTHDAY_MONTH = "select[name='birthday_month']" + REG_BIRTHDAY_YEAR = "select[name='birthday_year']" + + # Gender radio buttons + REG_GENDER_FEMALE = "input[name='sex'][value='1']" + REG_GENDER_MALE = "input[name='sex'][value='2']" + REG_GENDER_CUSTOM = "input[name='sex'][value='-1']" + + # Contact info + REG_EMAIL_OR_PHONE = "input[name='reg_email__']" + REG_EMAIL_CONFIRM = "input[name='reg_email_confirmation__']" # Erscheint wenn Email eingegeben + + # Password + REG_PASSWORD = "input[name='reg_passwd__']" + REG_PASSWORD_ALT = "input#password_step_input" + + # Submit button + REG_SUBMIT_BUTTON = "button[name='websubmit']" + REG_SUBMIT_BUTTON_ALT = "button:has-text('Registrieren')" + REG_SUBMIT_BUTTON_EN = "button:has-text('Sign Up')" + + # ===== EMAIL VERIFICATION ===== + VERIFICATION_CODE_INPUT = "input#code_in_cliff" + VERIFICATION_CODE_INPUT_ALT = "input[name='code']" + VERIFICATION_CONTINUE_BUTTON = "button:has-text('Weiter')" + VERIFICATION_CONTINUE_BUTTON_EN = "button:has-text('Continue')" + + # Verification success popup + VERIFICATION_OK_BUTTON = "a.layerCancel:has-text('OK')" + VERIFICATION_OK_BUTTON_ALT = "a[role='button']:has-text('OK')" + + # ===== ERROR MESSAGES ===== + ERROR_MESSAGE = "div[role='alert']" + ERROR_MESSAGE_ALT = "div.uiContextualLayer" + FIELD_ERROR = "div._5v-0._53im" + + # ===== SUCCESS INDICATORS ===== + SUCCESS_INDICATORS = [ + "div[role='navigation']", # Navigation bar + "div[role='main']", # Main feed + "div[data-pagelet='Feed']", # Feed container + "a[href='/me/']", # Profile link + "div[aria-label='Facebook']", # Facebook logo + ] + + # ===== DIALOGS & POPUPS ===== + DIALOG_CLOSE_BUTTON = "div[aria-label='Schließen']" + DIALOG_CLOSE_BUTTON_EN = "div[aria-label='Close']" + SKIP_BUTTON = "a:has-text('Überspringen')" + SKIP_BUTTON_EN = "a:has-text('Skip')" + NOT_NOW_BUTTON = "a:has-text('Jetzt nicht')" + NOT_NOW_BUTTON_EN = "a:has-text('Not Now')" + + # ===== URLS ===== + BASE_URL = "https://www.facebook.com" + BASE_URL_DE = "https://www.facebook.com/?locale=de_DE" + REGISTRATION_URL = "https://www.facebook.com/r.php" + LOGIN_URL = "https://www.facebook.com/login" + CONFIRM_EMAIL_URL = "https://www.facebook.com/confirmemail.php" + + # ===== BUTTON TEXTS (für Fuzzy Matching) ===== + BUTTON_TEXTS = { + "create_account": ["Neues Konto erstellen", "Create new account", "Sign up"], + "register": ["Registrieren", "Sign Up", "Register"], + "login": ["Anmelden", "Log In", "Login"], + "continue": ["Weiter", "Continue", "Next"], + "skip": ["Überspringen", "Skip", "Later"], + "decline_cookies": ["Optionale Cookies ablehnen", "Decline optional cookies", "Reject"], + "accept_cookies": ["Alle Cookies erlauben", "Allow all cookies", "Accept all"], + "ok": ["OK", "Okay"], + "not_now": ["Jetzt nicht", "Not Now", "Later"], + } + + # ===== MONTH NAMES (für Birthday) ===== + MONTH_NAMES_DE = { + 1: "Jan.", 2: "Feb.", 3: "März", 4: "Apr.", + 5: "Mai", 6: "Juni", 7: "Juli", 8: "Aug.", + 9: "Sept.", 10: "Okt.", 11: "Nov.", 12: "Dez." + } + + MONTH_NAMES_EN = { + 1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", + 5: "May", 6: "Jun", 7: "Jul", 8: "Aug", + 9: "Sep", 10: "Oct", 11: "Nov", 12: "Dec" + } + + @classmethod + def get_button_texts(cls, button_type: str) -> List[str]: + """ + Gibt eine Liste von möglichen Button-Texten zurück. + + Args: + button_type: Typ des Buttons + + Returns: + Liste von möglichen Texten + """ + return cls.BUTTON_TEXTS.get(button_type, []) + + @classmethod + def get_month_name(cls, month: int, language: str = "de") -> str: + """ + Gibt den Monatsnamen für die Auswahl zurück. + + Args: + month: Monatszahl (1-12) + language: Sprache ("de" oder "en") + + Returns: + Monatsname + """ + if language == "de": + return cls.MONTH_NAMES_DE.get(month, str(month)) + else: + return cls.MONTH_NAMES_EN.get(month, str(month)) + + @classmethod + def get_gender_selector(cls, gender: str) -> str: + """ + Gibt den passenden Selektor für das Geschlecht zurück. + + Args: + gender: "male", "female" oder "custom" + + Returns: + CSS-Selektor für das Geschlecht + """ + gender_map = { + "female": cls.REG_GENDER_FEMALE, + "male": cls.REG_GENDER_MALE, + "custom": cls.REG_GENDER_CUSTOM, + "weiblich": cls.REG_GENDER_FEMALE, + "männlich": cls.REG_GENDER_MALE, + "divers": cls.REG_GENDER_CUSTOM, + } + return gender_map.get(gender.lower(), cls.REG_GENDER_CUSTOM) \ No newline at end of file diff --git a/social_networks/facebook/facebook_ui_helper.py b/social_networks/facebook/facebook_ui_helper.py index e69de29..506341c 100644 --- a/social_networks/facebook/facebook_ui_helper.py +++ b/social_networks/facebook/facebook_ui_helper.py @@ -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) \ No newline at end of file diff --git a/social_networks/facebook/facebook_utils.py b/social_networks/facebook/facebook_utils.py index e69de29..f17dddb 100644 --- a/social_networks/facebook/facebook_utils.py +++ b/social_networks/facebook/facebook_utils.py @@ -0,0 +1,293 @@ +# social_networks/facebook/facebook_utils.py + +""" +Facebook Utils - Hilfsfunktionen für Facebook-Automatisierung +""" + +import logging +import random +import re +from typing import Optional, List, Dict, Any +from utils.logger import setup_logger + +logger = setup_logger("facebook_utils") + +class FacebookUtils: + """ + Hilfsklasse mit Utility-Funktionen für Facebook. + """ + + def __init__(self, automation): + """ + Initialisiert die Utils-Klasse. + + Args: + automation: Referenz auf die Hauptautomatisierungsklasse + """ + self.automation = automation + logger.debug("Facebook Utils initialisiert") + + @staticmethod + def validate_email(email: str) -> bool: + """ + Validiert eine E-Mail-Adresse. + + Args: + email: E-Mail-Adresse + + Returns: + bool: True wenn gültig + """ + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + return bool(re.match(pattern, email)) + + @staticmethod + def validate_phone(phone: str) -> bool: + """ + Validiert eine Telefonnummer. + + Args: + phone: Telefonnummer + + Returns: + bool: True wenn gültig + """ + # Entferne alle Nicht-Ziffern + digits_only = re.sub(r'\D', '', phone) + + # Prüfe Länge (international 7-15 Ziffern) + return 7 <= len(digits_only) <= 15 + + @staticmethod + def generate_username_from_name(first_name: str, last_name: str) -> str: + """ + Generiert einen Benutzernamen aus Vor- und Nachname. + + Args: + first_name: Vorname + last_name: Nachname + + Returns: + str: Generierter Benutzername + """ + # Bereinige Namen + first_clean = re.sub(r'[^a-zA-Z]', '', first_name.lower()) + last_clean = re.sub(r'[^a-zA-Z]', '', last_name.lower()) + + # Verschiedene Formate + formats = [ + f"{first_clean}.{last_clean}", + f"{first_clean}{last_clean}", + f"{first_clean}_{last_clean}", + f"{first_clean[0]}{last_clean}", + f"{last_clean}.{first_clean}", + ] + + username = random.choice(formats) + + # Füge optional Zahlen hinzu + if random.random() > 0.5: + username += str(random.randint(1, 999)) + + return username + + @staticmethod + def format_phone_number(phone: str, country_code: str = "+49") -> str: + """ + Formatiert eine Telefonnummer. + + Args: + phone: Telefonnummer + country_code: Ländervorwahl + + Returns: + str: Formatierte Telefonnummer + """ + # Entferne alle Nicht-Ziffern + digits = re.sub(r'\D', '', phone) + + # Füge Ländervorwahl hinzu wenn nicht vorhanden + if not phone.startswith('+'): + return f"{country_code}{digits}" + + return f"+{digits}" + + @staticmethod + def is_valid_password(password: str) -> bool: + """ + Prüft ob ein Passwort den Facebook-Anforderungen entspricht. + + Facebook-Anforderungen: + - Mindestens 6 Zeichen + - Kombination aus Buchstaben, Zahlen und/oder Sonderzeichen empfohlen + + Args: + password: Zu prüfendes Passwort + + Returns: + bool: True wenn gültig + """ + if len(password) < 6: + return False + + # Prüfe auf verschiedene Zeichentypen (empfohlen, nicht erforderlich) + has_letter = bool(re.search(r'[a-zA-Z]', password)) + has_digit = bool(re.search(r'\d', password)) + has_special = bool(re.search(r'[!@#$%^&*(),.?":{}|<>]', password)) + + # Mindestens 2 verschiedene Zeichentypen empfohlen + char_types = sum([has_letter, has_digit, has_special]) + + if char_types < 2: + logger.warning("Passwort sollte mindestens 2 verschiedene Zeichentypen enthalten") + + return True + + def detect_language(self) -> str: + """ + Erkennt die aktuelle Sprache der Facebook-Seite. + + Returns: + str: Sprachcode ("de", "en", etc.) + """ + try: + # Prüfe HTML lang-Attribut + lang = self.automation.browser.page.get_attribute("html", "lang") + if lang: + return lang[:2] # Erste 2 Zeichen (de, en, etc.) + + # Prüfe Meta-Tags + meta_lang = self.automation.browser.page.get_attribute( + "meta[property='og:locale']", "content" + ) + if meta_lang: + return meta_lang[:2] + + # Fallback: Prüfe Text-Inhalte + page_content = self.automation.browser.page.content().lower() + + german_keywords = ["anmelden", "registrieren", "passwort", "konto"] + english_keywords = ["login", "register", "password", "account"] + + german_count = sum(1 for kw in german_keywords if kw in page_content) + english_count = sum(1 for kw in english_keywords if kw in page_content) + + if german_count > english_count: + return "de" + else: + return "en" + + except Exception as e: + logger.error(f"Fehler bei Spracherkennung: {e}") + return "en" # Fallback zu Englisch + + def extract_error_message(self) -> Optional[str]: + """ + Extrahiert Fehlermeldungen von der Seite. + + Returns: + Optional[str]: Fehlermeldung oder None + """ + try: + # Verschiedene Fehler-Selektoren + error_selectors = [ + "div[role='alert']", + "div.uiContextualLayer", + "div._5v-0._53im", + "div[data-testid='error']", + "span.error", + "div.error-message" + ] + + for selector in error_selectors: + try: + error_elem = self.automation.browser.page.query_selector(selector) + if error_elem and error_elem.is_visible(): + error_text = error_elem.inner_text().strip() + if error_text: + logger.info(f"Fehlermeldung gefunden: {error_text}") + return error_text + except: + continue + + # Prüfe auf bekannte Fehler-Keywords im Seiteninhalt + page_content = self.automation.browser.page.content() + error_patterns = [ + r"(Fehler|Error):\s*([^<\n]+)", + r"(Problem|Issue):\s*([^<\n]+)", + r"class=\"error[^\"]*\"[^>]*>([^<]+)", + ] + + for pattern in error_patterns: + match = re.search(pattern, page_content, re.IGNORECASE) + if match: + error_text = match.group(1) if len(match.groups()) == 1 else match.group(2) + logger.info(f"Fehler-Pattern gefunden: {error_text}") + return error_text.strip() + + return None + + except Exception as e: + logger.error(f"Fehler beim Extrahieren der Fehlermeldung: {e}") + return None + + @staticmethod + def parse_facebook_url(url: str) -> Dict[str, Any]: + """ + Parst eine Facebook-URL und extrahiert Informationen. + + Args: + url: Facebook-URL + + Returns: + Dict mit URL-Komponenten + """ + parsed = { + "is_facebook": "facebook.com" in url, + "is_login": "/login" in url or "/accounts/login" in url, + "is_registration": "/r.php" in url or "/reg" in url, + "is_checkpoint": "/checkpoint" in url, + "is_confirmation": "/confirmemail" in url, + "is_home": url.endswith("facebook.com/") or "/home" in url, + "has_error": "error=" in url or "err=" in url + } + + # Extrahiere Query-Parameter + if "?" in url: + query_string = url.split("?")[1] + params = {} + for param in query_string.split("&"): + if "=" in param: + key, value = param.split("=", 1) + params[key] = value + parsed["params"] = params + + return parsed + + def take_debug_screenshot(self, name: str = "debug") -> str: + """ + Nimmt einen Debug-Screenshot auf. + + Args: + name: Name für den Screenshot + + Returns: + str: Pfad zum Screenshot + """ + return self.automation._take_screenshot(f"facebook_{name}") + + @staticmethod + def get_random_user_agent() -> str: + """ + Gibt einen zufälligen User-Agent zurück. + + Returns: + str: User-Agent String + """ + user_agents = [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + return random.choice(user_agents) \ No newline at end of file diff --git a/social_networks/facebook/facebook_verification.py b/social_networks/facebook/facebook_verification.py index e69de29..f27f324 100644 --- a/social_networks/facebook/facebook_verification.py +++ b/social_networks/facebook/facebook_verification.py @@ -0,0 +1,291 @@ +# social_networks/facebook/facebook_verification.py + +""" +Facebook-Verifikation - Klasse für die Verifikationsfunktionalität bei Facebook +Nutzt den gleichen EmailHandler wie Instagram. +""" + +import logging +import time +import re +from typing import Optional, Dict, Any, List + +from .facebook_selectors import FacebookSelectors +from utils.logger import setup_logger + +logger = setup_logger("facebook_verification") + +class FacebookVerification: + """ + Klasse für die Verifikation bei Facebook (E-Mail, SMS, etc.). + Nutzt den gleichen E-Mail-Service wie Instagram. + """ + + def __init__(self, automation): + """ + Initialisiert die Facebook-Verifikations-Funktionalität. + + Args: + automation: Referenz auf die Hauptautomatisierungsklasse + """ + self.automation = automation + self.selectors = FacebookSelectors() + + logger.debug("Facebook-Verifikation initialisiert") + + def wait_for_email_code(self, email: str, timeout: int = 120) -> Optional[str]: + """ + Wartet auf den Verifikationscode aus der E-Mail. + Nutzt den gleichen E-Mail-Service wie Instagram über EmailHandler. + + Facebook-spezifisch: + - Betreff: "Dein Bestätigungscode lautet FB-12345" + - Code ist 5-stellig + + Args: + email: E-Mail-Adresse + timeout: Maximale Wartezeit in Sekunden + + Returns: + Optional[str]: 5-stelliger Verifikationscode oder None + """ + logger.info(f"Warte auf Verifikationscode für {email}") + + try: + # Nutze den EmailHandler wie bei Instagram + verification_code = self.automation.email_handler.get_verification_code( + target_email=email, + platform="facebook", + max_attempts=timeout // 2, # 2 Sekunden pro Versuch + delay_seconds=2 + ) + + if verification_code: + logger.info(f"Verifikationscode erhalten: {verification_code}") + return verification_code + + logger.warning(f"Kein Verifikationscode nach {timeout} Sekunden erhalten") + return None + + except Exception as e: + logger.error(f"Fehler beim Warten auf E-Mail-Code: {e}") + return None + + def extract_code_from_email(self, email_content: str) -> Optional[str]: + """ + Extrahiert den Verifikationscode aus dem E-Mail-Inhalt. + Diese Methode wird vom EmailHandler aufgerufen. + + Args: + email_content: Inhalt der E-Mail + + Returns: + Optional[str]: 5-stelliger Code oder None + """ + try: + # Facebook-spezifische Patterns für Verifikationscode + # Betreff: "Dein Bestätigungscode lautet FB-12345" + patterns = [ + r'FB-(\d{5})', # Format: FB-12345 + r'Bestätigungscode lautet (\d{5})', # Deutscher Text + r'Bestätigungscode: (\d{5})', # Alternative + r'confirmation code is (\d{5})', # Englisch + r'verification code: (\d{5})', # Alternative Englisch + r'Code: (\d{5})', # Kurz + r'\b(\d{5})\b', # Jede 5-stellige Zahl als Fallback + ] + + for pattern in patterns: + match = re.search(pattern, email_content, re.IGNORECASE) + if match: + code = match.group(1) if '(' in pattern else match.group(0) + logger.info(f"Verifikationscode gefunden: {code}") + return code + + logger.warning("Kein Verifikationscode im E-Mail-Inhalt gefunden") + return None + + except Exception as e: + logger.error(f"Fehler beim Extrahieren des Codes: {e}") + return None + + def handle_sms_verification(self, phone_number: str, timeout: int = 120) -> Optional[str]: + """ + Behandelt SMS-Verifikation (für zukünftige Implementierung). + + Args: + phone_number: Telefonnummer + timeout: Maximale Wartezeit + + Returns: + Optional[str]: SMS-Code oder None + """ + logger.info(f"SMS-Verifikation für {phone_number} angefordert") + + # TODO: SMS-Service-Integration implementieren + # Könnte später den gleichen Service wie Instagram nutzen + + logger.warning("SMS-Verifikation noch nicht implementiert") + return None + + def handle_captcha(self) -> bool: + """ + Behandelt Captcha-Herausforderungen. + + Returns: + bool: True bei Erfolg + """ + try: + logger.info("Prüfe auf Captcha") + + # Captcha-Erkennung + captcha_selectors = [ + "div[id*='captcha']", + "div[class*='captcha']", + "iframe[src*='recaptcha']", + "div[class*='recaptcha']" + ] + + captcha_found = False + for selector in captcha_selectors: + if self.automation.browser.is_element_visible(selector, timeout=1000): + captcha_found = True + logger.warning(f"Captcha erkannt: {selector}") + break + + if captcha_found: + # Screenshot für manuelles Lösen + self.automation._take_screenshot("captcha_challenge") + + # TODO: Captcha-Lösung implementieren + # Könnte die gleiche Strategie wie Instagram nutzen + + logger.error("Captcha-Lösung noch nicht implementiert") + return False + + logger.debug("Kein Captcha gefunden") + return True + + except Exception as e: + logger.error(f"Fehler bei Captcha-Behandlung: {e}") + return False + + def handle_identity_verification(self) -> bool: + """ + Behandelt Identitäts-Verifikation (Ausweis-Upload, etc.). + + Returns: + bool: True bei Erfolg + """ + try: + logger.info("Prüfe auf Identitäts-Verifikation") + + # Prüfe auf Identitäts-Verifikations-Anforderung + id_verification_keywords = [ + "identität bestätigen", + "verify identity", + "ausweis hochladen", + "upload id", + "foto hochladen", + "upload photo" + ] + + page_content = self.automation.browser.page.content().lower() + + for keyword in id_verification_keywords: + if keyword in page_content: + logger.warning(f"Identitäts-Verifikation erforderlich: {keyword}") + + # Screenshot + self.automation._take_screenshot("identity_verification_required") + + # Kann nicht automatisiert werden + return False + + logger.debug("Keine Identitäts-Verifikation erforderlich") + return True + + except Exception as e: + logger.error(f"Fehler bei Identitäts-Verifikations-Check: {e}") + return False + + def handle_suspicious_activity(self) -> bool: + """ + Behandelt "Verdächtige Aktivität" Warnungen. + + Returns: + bool: True wenn behandelt + """ + try: + logger.info("Prüfe auf verdächtige Aktivitäts-Warnungen") + + # Prüfe auf Warnungen + suspicious_keywords = [ + "verdächtige aktivität", + "suspicious activity", + "ungewöhnliche aktivität", + "unusual activity", + "sicherheitsprüfung", + "security check" + ] + + page_content = self.automation.browser.page.content().lower() + + for keyword in suspicious_keywords: + if keyword in page_content: + logger.warning(f"Verdächtige Aktivitäts-Warnung: {keyword}") + + # Screenshot + self.automation._take_screenshot("suspicious_activity") + + # Versuche fortzufahren + continue_buttons = [ + "button:has-text('Weiter')", + "button:has-text('Continue')", + "button:has-text('Fortfahren')" + ] + + for button in continue_buttons: + if self.automation.browser.click_element(button, timeout=2000): + logger.info("Verdächtige Aktivitäts-Dialog behandelt") + return True + + return False + + logger.debug("Keine verdächtige Aktivitäts-Warnung gefunden") + return True + + except Exception as e: + logger.error(f"Fehler bei Behandlung verdächtiger Aktivität: {e}") + return False + + def bypass_checkpoint(self) -> bool: + """ + Versucht Facebook Checkpoints zu umgehen. + + Returns: + bool: True bei Erfolg + """ + try: + logger.info("Prüfe auf Facebook Checkpoint") + + # Prüfe URL + current_url = self.automation.browser.page.url + if "checkpoint" in current_url: + logger.warning("Facebook Checkpoint erkannt") + + # Screenshot + self.automation._take_screenshot("checkpoint") + + # Checkpoint-Typen behandeln + # Diese könnten ähnlich wie bei Instagram behandelt werden + + logger.error("Checkpoint-Umgehung noch nicht vollständig implementiert") + return False + + logger.debug("Kein Checkpoint erkannt") + return True + + except Exception as e: + logger.error(f"Fehler bei Checkpoint-Umgehung: {e}") + return False \ No newline at end of file diff --git a/social_networks/facebook/facebook_workflow.py b/social_networks/facebook/facebook_workflow.py index e69de29..401985f 100644 --- a/social_networks/facebook/facebook_workflow.py +++ b/social_networks/facebook/facebook_workflow.py @@ -0,0 +1,239 @@ +# social_networks/facebook/facebook_workflow.py + +""" +Facebook Workflow - Definiert die Arbeitsabläufe für Facebook-Automatisierung +""" + +import logging +from typing import Dict, List, Any +from utils.logger import setup_logger + +logger = setup_logger("facebook_workflow") + +class FacebookWorkflow: + """ + Klasse zur Definition von Facebook-Workflows. + Enthält die Schritte für verschiedene Prozesse. + """ + + @staticmethod + def get_registration_workflow() -> Dict[str, Any]: + """ + Gibt den Workflow für die Registrierung zurück. + + Returns: + Dict mit Workflow-Schritten + """ + return { + "name": "Facebook Registration", + "steps": [ + { + "id": "navigate", + "name": "Navigate to Facebook", + "description": "Öffne Facebook-Webseite", + "required": True, + "retry": 3 + }, + { + "id": "cookie_consent", + "name": "Handle Cookie Consent", + "description": "Lehne optionale Cookies ab", + "required": False, + "retry": 2 + }, + { + "id": "open_registration", + "name": "Open Registration Form", + "description": "Klicke auf 'Neues Konto erstellen'", + "required": True, + "retry": 3 + }, + { + "id": "fill_form", + "name": "Fill Registration Form", + "description": "Fülle Registrierungsformular aus", + "required": True, + "retry": 1, + "fields": [ + "first_name", + "last_name", + "birth_date", + "gender", + "email_or_phone", + "password" + ] + }, + { + "id": "submit", + "name": "Submit Registration", + "description": "Klicke auf 'Registrieren'", + "required": True, + "retry": 2 + }, + { + "id": "email_verification", + "name": "Email Verification", + "description": "Verifiziere E-Mail-Adresse", + "required": False, + "retry": 3, + "timeout": 120 + }, + { + "id": "complete", + "name": "Complete Registration", + "description": "Schließe Registrierung ab", + "required": True, + "retry": 1 + } + ], + "error_handlers": { + "captcha": "handle_captcha", + "checkpoint": "handle_checkpoint", + "suspicious_activity": "handle_suspicious_activity" + } + } + + @staticmethod + def get_login_workflow() -> Dict[str, Any]: + """ + Gibt den Workflow für den Login zurück. + + Returns: + Dict mit Workflow-Schritten + """ + return { + "name": "Facebook Login", + "steps": [ + { + "id": "navigate", + "name": "Navigate to Facebook", + "description": "Öffne Facebook-Webseite", + "required": True, + "retry": 3 + }, + { + "id": "cookie_consent", + "name": "Handle Cookie Consent", + "description": "Behandle Cookie-Banner", + "required": False, + "retry": 2 + }, + { + "id": "fill_credentials", + "name": "Fill Login Credentials", + "description": "Gebe E-Mail und Passwort ein", + "required": True, + "retry": 1 + }, + { + "id": "submit_login", + "name": "Submit Login", + "description": "Klicke auf 'Anmelden'", + "required": True, + "retry": 2 + }, + { + "id": "handle_2fa", + "name": "Handle 2FA", + "description": "Behandle Zwei-Faktor-Authentifizierung", + "required": False, + "retry": 3 + }, + { + "id": "handle_dialogs", + "name": "Handle Post-Login Dialogs", + "description": "Behandle Dialoge nach Login", + "required": False, + "retry": 1 + }, + { + "id": "verify_login", + "name": "Verify Login Success", + "description": "Überprüfe erfolgreichen Login", + "required": True, + "retry": 1 + } + ], + "error_handlers": { + "wrong_password": "handle_wrong_password", + "account_locked": "handle_account_locked", + "checkpoint": "handle_checkpoint" + } + } + + @staticmethod + def get_profile_setup_workflow() -> Dict[str, Any]: + """ + Gibt den Workflow für die Profil-Einrichtung zurück. + + Returns: + Dict mit Workflow-Schritten + """ + return { + "name": "Facebook Profile Setup", + "steps": [ + { + "id": "skip_suggestions", + "name": "Skip Friend Suggestions", + "description": "Überspringe Freundschaftsvorschläge", + "required": False, + "retry": 1 + }, + { + "id": "skip_photo", + "name": "Skip Profile Photo", + "description": "Überspringe Profilbild-Upload", + "required": False, + "retry": 1 + }, + { + "id": "skip_interests", + "name": "Skip Interests", + "description": "Überspringe Interessen-Auswahl", + "required": False, + "retry": 1 + }, + { + "id": "privacy_settings", + "name": "Configure Privacy", + "description": "Konfiguriere Privatsphäre-Einstellungen", + "required": False, + "retry": 1 + } + ] + } + + @staticmethod + def validate_workflow_step(step: Dict[str, Any], result: bool) -> bool: + """ + Validiert einen Workflow-Schritt. + + Args: + step: Workflow-Schritt + result: Ergebnis des Schritts + + Returns: + bool: True wenn Schritt erfolgreich oder optional + """ + if result: + logger.info(f"Schritt '{step['name']}' erfolgreich") + return True + elif not step.get("required", True): + logger.warning(f"Optionaler Schritt '{step['name']}' fehlgeschlagen, fahre fort") + return True + else: + logger.error(f"Erforderlicher Schritt '{step['name']}' fehlgeschlagen") + return False + + @staticmethod + def get_step_timeout(step: Dict[str, Any]) -> int: + """ + Gibt das Timeout für einen Schritt zurück. + + Args: + step: Workflow-Schritt + + Returns: + int: Timeout in Sekunden + """ + return step.get("timeout", 30) \ No newline at end of file diff --git a/social_networks/x/x_registration.py b/social_networks/x/x_registration.py index d8e75b7..91ec591 100644 --- a/social_networks/x/x_registration.py +++ b/social_networks/x/x_registration.py @@ -464,6 +464,41 @@ class XRegistration: try: page = self.automation.browser.page + # Zuerst auf "E-Mail verwenden" klicken, um vom Telefon- zum E-Mail-Formular zu wechseln + email_use_selectors = [ + 'span:has-text("E-Mail verwenden")', + 'text="E-Mail verwenden"', + 'span.css-1jxf684:has-text("E-Mail verwenden")', + '[class*="r-bcqeeo"]:has-text("E-Mail verwenden")', + 'div:has-text("E-Mail verwenden")', + 'a:has-text("E-Mail verwenden")', + 'button:has-text("E-Mail verwenden")', + '[role="button"]:has-text("E-Mail verwenden")', + # Englische Versionen als Fallback + 'span:has-text("Use email")', + 'text="Use email"', + 'span:has-text("Use email instead")', + 'text="Use email instead"' + ] + + email_link_clicked = False + for selector in email_use_selectors: + try: + element = page.wait_for_selector(selector, timeout=2000, state="visible") + if element: + logger.info(f"'E-Mail verwenden' Link gefunden mit Selektor: {selector}") + element.click() + self.automation.human_behavior.random_delay(0.5, 1) + email_link_clicked = True + logger.info("Zu E-Mail-Registrierung gewechselt") + break + except Exception as e: + logger.debug(f"Selektor {selector} nicht gefunden: {e}") + continue + + if not email_link_clicked: + logger.warning("'E-Mail verwenden' Link nicht gefunden, versuche trotzdem fortzufahren") + # Name eingeben name_selectors = [ 'input[name="name"]', diff --git a/views/tabs/facebook_generator_tab.py b/views/tabs/facebook_generator_tab.py new file mode 100644 index 0000000..63da0ed --- /dev/null +++ b/views/tabs/facebook_generator_tab.py @@ -0,0 +1,340 @@ +""" +Facebook-spezifischer Generator-Tab mit Geschlechtsauswahl. +""" + +import logging +import random +from PyQt5.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, + QLabel, QLineEdit, QSpinBox, QPushButton, + QGroupBox, QRadioButton, QButtonGroup, QProgressBar +) +from PyQt5.QtCore import pyqtSignal, Qt + +logger = logging.getLogger("facebook_generator_tab") + +class FacebookGeneratorTab(QWidget): + """ + Facebook-spezifischer Generator-Tab mit Geschlechtsauswahl. + """ + + # Signale + start_requested = pyqtSignal(dict) + stop_requested = pyqtSignal() + account_created = pyqtSignal(str, dict) # (platform, account_data) + + def __init__(self, platform_name, language_manager=None): + super().__init__() + self.platform_name = platform_name + self.language_manager = language_manager + self.init_ui() + + if self.language_manager: + self.language_manager.language_changed.connect(self.update_texts) + self.update_texts() + + def init_ui(self): + """Initialisiert die UI mit Facebook-spezifischen Feldern.""" + layout = QVBoxLayout(self) + layout.setContentsMargins(20, 20, 20, 20) + layout.setSpacing(20) + + # Formular-Gruppe + self.form_group = QGroupBox("Account-Informationen") + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(15) + grid_layout.setVerticalSpacing(10) + self.form_group.setLayout(grid_layout) + + # Zeile 0: Vorname und Nachname + self.first_name_label = QLabel("Vorname:") + self.first_name_input = QLineEdit() + self.first_name_input.setPlaceholderText("Max") + + self.last_name_label = QLabel("Nachname:") + self.last_name_input = QLineEdit() + self.last_name_input.setPlaceholderText("Mustermann") + + grid_layout.addWidget(self.first_name_label, 0, 0) + grid_layout.addWidget(self.first_name_input, 0, 1) + grid_layout.addWidget(self.last_name_label, 0, 2) + grid_layout.addWidget(self.last_name_input, 0, 3) + + # Zeile 1: Alter + self.age_label = QLabel("Alter:") + self.age_input = QLineEdit() + self.age_input.setMaximumWidth(100) + + grid_layout.addWidget(self.age_label, 1, 0) + grid_layout.addWidget(self.age_input, 1, 1) + + # Zeile 2: Geschlecht + self.gender_label = QLabel("Geschlecht:") + + # Geschlechts-Widget + gender_widget = QWidget() + gender_layout = QHBoxLayout(gender_widget) + gender_layout.setContentsMargins(0, 0, 0, 0) + + # Radio Buttons + self.gender_male = QRadioButton("Männlich") + self.gender_female = QRadioButton("Weiblich") + self.gender_custom = QRadioButton("Divers") + + # Standard: Weiblich + self.gender_female.setChecked(True) + + # Button Group für exklusive Auswahl + self.gender_group = QButtonGroup() + self.gender_group.addButton(self.gender_male, 1) + self.gender_group.addButton(self.gender_female, 2) + self.gender_group.addButton(self.gender_custom, 3) + + # Füge Radio Buttons zum Layout + gender_layout.addWidget(self.gender_male) + gender_layout.addWidget(self.gender_female) + gender_layout.addWidget(self.gender_custom) + gender_layout.addStretch() + + grid_layout.addWidget(self.gender_label, 2, 0) + grid_layout.addWidget(gender_widget, 2, 1, 1, 3) + + layout.addWidget(self.form_group) + + # Fortschrittsanzeige + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + self.progress_bar.setMaximumHeight(20) + self.progress_bar.setMinimumWidth(300) + self.progress_bar.hide() # Versteckt bis zur Nutzung + layout.addWidget(self.progress_bar) + + # Buttons + button_layout = QHBoxLayout() + button_layout.addStretch() + + self.start_button = QPushButton("Account erstellen") + self.start_button.setMinimumWidth(150) + self.start_button.clicked.connect(self.on_start_clicked) + + self.stop_button = QPushButton("Abbrechen") + self.stop_button.setMinimumWidth(150) + self.stop_button.clicked.connect(self.on_stop_clicked) + self.stop_button.setEnabled(False) + + button_layout.addWidget(self.start_button) + button_layout.addWidget(self.stop_button) + + layout.addLayout(button_layout) + + # Spacer am Ende + layout.addStretch() + + def get_params(self): + """Sammelt alle Parameter für die Account-Erstellung.""" + # Namen sammeln + first_name = self.first_name_input.text().strip() + last_name = self.last_name_input.text().strip() + full_name = f"{first_name} {last_name}" + + # Alter verarbeiten + age_text = self.age_input.text().strip() + age = self.parse_age(age_text) + + # Geschlecht bestimmen + gender = self.get_selected_gender() + + params = { + "first_name": first_name, + "last_name": last_name, + "full_name": full_name, + "age": age, + "age_text": age_text, + "gender": gender, + "registration_method": "email", + "headless": False, + "debug": True, + "email_domain": "z5m7q9dk3ah2v1plx6ju.com" + } + + logger.debug(f"Facebook Formulardaten: {params}") + return params + + def parse_age(self, age_text): + """Parst den Alters-Text und gibt ein numerisches Alter zurück.""" + if not age_text: + return random.randint(18, 65) + + # Versuche Range zu parsen (z.B. "18-25") + if '-' in age_text: + try: + min_age, max_age = age_text.split('-') + min_age = int(min_age.strip()) + max_age = int(max_age.strip()) + return random.randint(min_age, max_age) + except: + pass + + # Versuche als einzelne Zahl + try: + return int(age_text) + except: + return random.randint(18, 65) + + def get_selected_gender(self): + """Gibt das ausgewählte Geschlecht zurück.""" + if self.gender_group: + checked_button = self.gender_group.checkedButton() + if checked_button: + gender_id = self.gender_group.id(checked_button) + if gender_id == 1: + return 'male' + elif gender_id == 2: + return 'female' + else: + return 'custom' + + # Fallback: zufällige Auswahl + return random.choice(['male', 'female']) + + def validate_inputs(self, params): + """Validiert die Eingaben.""" + # Name prüfen + if not params.get('first_name') or not params.get('last_name'): + return False, "Vor- und Nachname sind erforderlich" + + # Alter prüfen + age = params.get('age', 0) + if age < 13: + return False, "Mindestalter für Facebook ist 13 Jahre" + if age > 100: + return False, "Bitte geben Sie ein realistisches Alter ein" + + return True, None + + def on_start_clicked(self): + """Behandelt den Start-Button-Klick.""" + # Parameter sammeln + params = self.get_params() + + # Validierung + valid, error_msg = self.validate_inputs(params) + if not valid: + self.show_error(error_msg) + return + + # Status aktualisieren + self.set_status("Starte Account-Erstellung...") + + # Signal senden + self.start_requested.emit(params) + + def on_stop_clicked(self): + """Behandelt den Stop-Button-Klick.""" + self.stop_requested.emit() + + def set_running(self, running: bool): + """Setzt den Status auf 'Wird ausgeführt' oder 'Bereit'.""" + self.start_button.setEnabled(not running) + self.stop_button.setEnabled(running) + + if running: + self.set_status("Läuft...") + else: + self.progress_bar.setValue(0) + self.progress_bar.hide() + + def set_progress(self, value: int): + """Setzt den Fortschritt der Fortschrittsanzeige.""" + if value > 0: + self.progress_bar.show() + self.progress_bar.setValue(value) + if value >= 100: + self.progress_bar.hide() + + def show_error(self, message: str): + """Zeigt eine Fehlermeldung an.""" + title = "Fehler" + if self.language_manager: + title = self.language_manager.get_text( + "generator_tab.error_title", "Fehler" + ) + + from views.widgets.modern_message_box import show_error + show_error(self, title, message) + self.set_status("Fehler aufgetreten") + + def show_success(self, message: str): + """Zeigt eine Erfolgsmeldung an.""" + title = "Erfolg" + if self.language_manager: + title = self.language_manager.get_text( + "generator_tab.success_title", "Erfolg" + ) + + from views.widgets.modern_message_box import show_success + show_success(self, title, message) + self.set_status("Erfolgreich abgeschlossen") + + # Kompatibilitätsmethoden für bestehenden Code + def clear_log(self): + """Kompatibilitätsmethode - tut nichts, da kein Log vorhanden.""" + pass + + def add_log(self, message: str): + """Kompatibilitätsmethode - gibt an die Konsole aus.""" + logger.info(message) + + def store_created_account(self, result_data: dict): + """Speichert die erstellten Account-Daten.""" + if "account_data" in result_data: + self.account_created.emit(self.platform_name, result_data["account_data"]) + + def set_status(self, message): + """Setzt den Status-Text.""" + # Status-Label wurde entfernt - Log to console for debugging + logger.info(f"Status: {message}") + + def update_texts(self): + """Aktualisiert die Texte nach Sprachwechsel.""" + if not self.language_manager: + return + + # Labels + self.first_name_label.setText( + self.language_manager.get_text("generator.first_name", "Vorname") + ":" + ) + self.last_name_label.setText( + self.language_manager.get_text("generator.last_name", "Nachname") + ":" + ) + self.age_label.setText( + self.language_manager.get_text("generator.age", "Alter") + ":" + ) + self.gender_label.setText( + self.language_manager.get_text("generator.gender", "Geschlecht") + ":" + ) + + # Radio Buttons + self.gender_male.setText( + self.language_manager.get_text("generator.gender_male", "Männlich") + ) + self.gender_female.setText( + self.language_manager.get_text("generator.gender_female", "Weiblich") + ) + self.gender_custom.setText( + self.language_manager.get_text("generator.gender_custom", "Divers") + ) + + # Buttons + self.start_button.setText( + self.language_manager.get_text("buttons.create", "Account erstellen") + ) + self.stop_button.setText( + self.language_manager.get_text("buttons.cancel", "Abbrechen") + ) + + # GroupBox + self.form_group.setTitle( + self.language_manager.get_text("generator_tab.form_title", "Account-Informationen") + ) \ No newline at end of file