diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index 0c39541..7b1bec6 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -5,9 +5,9 @@ ## Project Overview - **Path**: `A:\GiTea\AccountForger` -- **Files**: 886 files -- **Size**: 125.8 MB -- **Last Modified**: 2025-09-10 21:18 +- **Files**: 990 files +- **Size**: 182.0 MB +- **Last Modified**: 2025-09-14 18:54 ## Technology Stack @@ -32,6 +32,7 @@ application/ │ ├── __init__.py │ ├── services/ │ │ ├── error_handler.py +│ │ ├── platform_service.py │ │ └── __init__.py │ └── use_cases/ │ ├── adaptive_rate_limit_use_case.py @@ -101,6 +102,7 @@ domain/ │ │ ├── browser_fingerprint.py │ │ ├── error_event.py │ │ ├── method_rotation.py +│ │ ├── platform.py │ │ ├── rate_limit_policy.py │ │ └── __init__.py │ ├── enums @@ -122,6 +124,7 @@ domain/ │ ├── error_summary.py │ ├── login_credentials.py │ ├── operation_result.py +│ ├── platform_name.py │ ├── report.py │ └── __init__.py infrastructure/ @@ -189,14 +192,14 @@ logs/ │ └── screenshots/ │ ├── after_account_create_click_1755281004.png │ ├── after_account_create_click_1757531758.png +│ ├── after_account_create_click_1757807105.png +│ ├── after_account_create_click_1757807217.png +│ ├── after_account_create_click_1757810496.png │ ├── after_code_retrieval_1757531778.png -│ ├── after_login_button_click_1755168832.png -│ ├── after_login_button_click_1755280227.png -│ ├── after_login_button_click_1755280551.png -│ ├── after_login_button_click_1755280826.png -│ ├── after_login_button_click_1755282576.png -│ ├── after_login_button_click_1755282842.png -│ └── after_login_button_click_1755341790.png +│ ├── after_code_retrieval_1757807127.png +│ ├── after_code_retrieval_1757807239.png +│ ├── after_code_retrieval_1757810517.png +│ └── after_cookie_consent_1757852739.png resources/ │ ├── icons/ │ │ ├── check-white.svg @@ -213,6 +216,8 @@ resources/ │ ├── dark.qss │ └── light.qss screenshots +scripts/ +│ └── fix_x_accounts.py social_networks/ │ ├── base_automation.py │ ├── __init__.py @@ -225,6 +230,7 @@ social_networks/ │ │ ├── facebook_utils.py │ │ ├── facebook_verification.py │ │ ├── facebook_workflow.py +│ │ ├── IMPROVEMENT_SUGGESTIONS.md │ │ └── __init__.py │ ├── gmail/ │ │ ├── gmail_automation.py @@ -415,3 +421,5 @@ This project is managed with Claude Project Manager. To work with this project: - README generated on 2025-09-06 21:49:36 - README updated on 2025-09-13 20:45:18 +- README updated on 2025-09-14 11:33:43 +- README updated on 2025-09-14 18:54:48 diff --git a/controllers/platform_controllers/facebook_controller.py b/controllers/platform_controllers/facebook_controller.py index 4142346..fcfe357 100644 --- a/controllers/platform_controllers/facebook_controller.py +++ b/controllers/platform_controllers/facebook_controller.py @@ -1,5 +1,5 @@ """ -Controller für Facebook-spezifische Funktionalität. +Controller für Facebook-spezifische Funktionalität. Mit Fingerprint-Protection und Anti-Bot Features. """ @@ -11,7 +11,8 @@ 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 views.tabs.generator_tab import GeneratorTab # Verwende generischen GeneratorTab +from views.widgets.forge_animation_widget import ForgeAnimationDialog # Forge Dialog für Animation from social_networks.facebook.facebook_automation import FacebookAutomation from utils.birthday_generator import BirthdayGenerator @@ -108,7 +109,15 @@ class FacebookWorkerThread(BaseAccountCreationWorkerThread): self.running = False class FacebookController(BasePlatformController): - """Controller für die Facebook-Plattform.""" + """Controller für die Facebook-Plattform.""" + + # Qt Signale für Kommunikation mit UI + status_update = pyqtSignal(str) # Status-Updates + log_message = pyqtSignal(str) # Log-Nachrichten + progress_update = pyqtSignal(int) # Fortschritt (0-100) + generation_started = pyqtSignal() # Generierung gestartet + generation_completed = pyqtSignal(dict) # Generierung abgeschlossen + generation_failed = pyqtSignal(str) # Generierung fehlgeschlagen 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) @@ -117,13 +126,13 @@ class FacebookController(BasePlatformController): def get_generator_tab(self): """ - Erstellt und konfiguriert den Generator-Tab für Facebook. + Erstellt und konfiguriert den Generator-Tab für Facebook. Returns: - FacebookGeneratorTab: Konfigurierter Tab für Facebook mit Geschlechtsauswahl + GeneratorTab: Konfigurierter Tab für Facebook mit Geschlechtsauswahl """ - # Erstelle Facebook-spezifischen Generator-Tab mit Geschlechtsfeld - generator_tab = FacebookGeneratorTab( + # Erstelle generischen Generator-Tab + generator_tab = GeneratorTab( "Facebook", self.language_manager ) @@ -145,32 +154,52 @@ class FacebookController(BasePlatformController): generator_tab: Der zu konfigurierende Tab """ # Facebook-spezifische Konfiguration - # Der FacebookGeneratorTab hat bereits das Geschlechtsfeld integriert + # Der GeneratorTab kann für Facebook-spezifische Felder erweitert werden + # Geschlecht wird aus den Parametern extrahiert # 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. + Behandelt eine Anfrage zur Account-Generierung mit Forge Dialog. Args: - params: Parameter für die Generierung + params: Parameter für die Generierung """ logger.info(f"Facebook-Account-Generierung angefordert: {params}") - # Prüfe ob bereits ein Thread läuft + # 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 + # Facebook-spezifische Parameter hinzufügen params["platform"] = "facebook" + # Hole Generator Tab für Parent Widget + generator_tab = self.get_generator_tab() + + # Schmiedeanimation-Dialog erstellen und anzeigen (wie TikTok) + parent_widget = generator_tab.window() # Hauptfenster als Parent + self.forge_dialog = ForgeAnimationDialog(parent_widget, "Facebook") + self.forge_dialog.cancel_clicked.connect(self.stop_generation) + self.forge_dialog.closed.connect(self.stop_generation) + + # Fensterposition vom Hauptfenster holen + if parent_widget: + window_pos = parent_widget.pos() + params["window_position"] = (window_pos.x(), window_pos.y()) + # Starte Worker-Thread self.worker_thread = FacebookWorkerThread(params) - # Verbinde Signale + # Verbinde Signale mit Forge Dialog + self.worker_thread.update_signal.connect(self.forge_dialog.set_status) + self.worker_thread.log_signal.connect(self.forge_dialog.add_log) + self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress) + + # Zusätzlich an Controller-Signale für andere Komponenten self.worker_thread.update_signal.connect( lambda msg: self.status_update.emit(msg) ) @@ -190,6 +219,10 @@ class FacebookController(BasePlatformController): # Starte Thread self.worker_thread.start() self.generation_started.emit() + + # Dialog anzeigen und Animation starten + self.forge_dialog.start_animation() + self.forge_dialog.show() def _handle_generation_success(self, result: Dict[str, Any]): """ @@ -200,6 +233,11 @@ class FacebookController(BasePlatformController): """ logger.info("Facebook-Account erfolgreich erstellt") + # Forge-Dialog schließen + if hasattr(self, 'forge_dialog') and self.forge_dialog: + self.forge_dialog.close() + self.forge_dialog = None + # Speichere Account in Datenbank if self.db_manager and result.get("account_data"): account_data = result["account_data"] @@ -218,6 +256,16 @@ class FacebookController(BasePlatformController): error_msg: Fehlermeldung """ logger.error(f"Fehler bei Facebook-Account-Erstellung: {error_msg}") + + # Forge-Dialog schließen mit Fehlerbehandlung + if hasattr(self, 'forge_dialog') and self.forge_dialog: + try: + self.forge_dialog.close() + except RuntimeError as e: + logger.warning(f"Forge Dialog bereits geschlossen: {e}") + finally: + self.forge_dialog = None + self.generation_failed.emit(error_msg) def stop_generation(self): @@ -226,8 +274,13 @@ class FacebookController(BasePlatformController): logger.info("Stoppe Facebook-Account-Generierung") self.worker_thread.stop() self.worker_thread.wait() + + # Forge-Dialog schließen falls vorhanden + if hasattr(self, 'forge_dialog') and self.forge_dialog: + self.forge_dialog.close() + self.forge_dialog = None def cleanup(self): - """Räumt Ressourcen auf.""" + """Räumt Ressourcen auf.""" self.stop_generation() - logger.info("Facebook Controller aufgeräumt") \ No newline at end of file + logger.info("Facebook Controller aufgeräumt") \ No newline at end of file diff --git a/database/accounts.db b/database/accounts.db index 85afaaf..ee29206 100644 Binary files a/database/accounts.db and b/database/accounts.db differ diff --git a/database/accounts.db-journal b/database/accounts.db-journal deleted file mode 100644 index db6a7b5..0000000 Binary files a/database/accounts.db-journal and /dev/null differ diff --git a/social_networks/base_automation.py b/social_networks/base_automation.py index 7780677..392ea9a 100644 --- a/social_networks/base_automation.py +++ b/social_networks/base_automation.py @@ -13,7 +13,6 @@ from browser.playwright_manager import PlaywrightManager from utils.proxy_rotator import ProxyRotator from utils.email_handler import EmailHandler from utils.text_similarity import TextSimilarity, fuzzy_find_element, click_fuzzy_button -from domain.value_objects.browser_protection_style import BrowserProtectionStyle, ProtectionLevel # Konfiguriere Logger logger = logging.getLogger("base_automation") @@ -167,12 +166,6 @@ class BaseAutomation(ABC): self.browser.start() logger.info("Browser erfolgreich initialisiert") - # Browser-Schutz anwenden wenn nicht headless - if not self.headless: - self._emit_customer_log("🛡️ Sicherheitseinstellungen werden konfiguriert...") - # TEMPORÄR DEAKTIVIERT zum Testen - # self._apply_browser_protection() - logger.info("Browser-Schutz wurde temporär deaktiviert") self._emit_customer_log("✅ Verbindung erfolgreich hergestellt") return True @@ -532,51 +525,6 @@ class BaseAutomation(ABC): return code - def _apply_browser_protection(self): - """Wendet Browser-Schutz an, um versehentliche Interaktionen zu verhindern.""" - try: - # Lade Schutz-Einstellungen aus stealth_config.json - import json - from pathlib import Path - - protection_config = None - try: - config_file = Path(__file__).parent.parent / "config" / "stealth_config.json" - if config_file.exists(): - with open(config_file, 'r', encoding='utf-8') as f: - stealth_config = json.load(f) - protection_config = stealth_config.get("browser_protection", {}) - except Exception as e: - logger.warning(f"Konnte Browser-Schutz-Konfiguration nicht laden: {e}") - - # Nutze Konfiguration oder Standardwerte - if protection_config and protection_config.get("enabled", True): - level_mapping = { - "none": ProtectionLevel.NONE, - "light": ProtectionLevel.LIGHT, - "medium": ProtectionLevel.MEDIUM, - "strong": ProtectionLevel.STRONG - } - - protection_style = BrowserProtectionStyle( - level=level_mapping.get(protection_config.get("level", "medium"), ProtectionLevel.MEDIUM), - show_border=protection_config.get("show_border", True), - show_badge=protection_config.get("show_badge", True), - blur_effect=protection_config.get("blur_effect", False), - opacity=protection_config.get("opacity", 0.1), - badge_text=protection_config.get("badge_text", "🔒 Account wird erstellt - Bitte nicht eingreifen"), - badge_position=protection_config.get("badge_position", "top-right"), - border_color=protection_config.get("border_color", "rgba(255, 0, 0, 0.5)") - ) - - # Wende Schutz an - if hasattr(self.browser, 'apply_protection'): - self.browser.apply_protection(protection_style) - logger.info("Browser-Schutz aktiviert") - - except Exception as e: - # Browser-Schutz ist optional, Fehler nicht kritisch - logger.warning(f"Browser-Schutz konnte nicht aktiviert werden: {str(e)}") def _is_text_similar(self, text1: str, text2: str, threshold: float = None) -> bool: """ diff --git a/social_networks/facebook/IMPROVEMENT_SUGGESTIONS.md b/social_networks/facebook/IMPROVEMENT_SUGGESTIONS.md new file mode 100644 index 0000000..9661bbf --- /dev/null +++ b/social_networks/facebook/IMPROVEMENT_SUGGESTIONS.md @@ -0,0 +1,103 @@ +# Facebook-Modul Verbesserungsvorschläge + +## 1. Facebook Login noch nicht implementiert +**Problem:** `facebook_login.py` enthält nur einen Platzhalter +```python +def login_account(self, email_or_phone: str, password: str, **kwargs) -> Dict[str, Any]: + logger.warning("Facebook-Login noch nicht vollständig implementiert") + return {"success": False, "error": "Login-Funktion noch nicht implementiert"} +``` + +**Lösung:** Vollständige Login-Implementation analog zu Registration + +## 2. Fehlende Browser-Verfügbarkeitsprüfung +**Problem:** In `facebook_ui_helper.py` wird `_ensure_browser()` verwendet, aber nicht konsistent in allen Methoden + +**Empfehlung:** Decorator-Pattern für Browser-Checks implementieren: +```python +def requires_browser(func): + def wrapper(self, *args, **kwargs): + if not self._ensure_browser(): + return False + return func(self, *args, **kwargs) + return wrapper +``` + +## 3. Hardcodierte Timeouts +**Problem:** Viele hardcodierte Timeout-Werte (1000ms, 2000ms, etc.) + +**Empfehlung:** Zentrale Timeout-Konfiguration: +```python +class FacebookTimeouts: + SHORT = 1000 + MEDIUM = 3000 + LONG = 5000 + VERIFICATION = 120000 +``` + +## 4. Unvollständige SMS-Verifikation +**Problem:** `handle_sms_verification()` in `facebook_verification.py` ist nicht implementiert + +**Empfehlung:** SMS-Service Integration planen oder als "nicht unterstützt" markieren + +## 5. Fehlende Unit-Tests +**Problem:** Keine Test-Dateien für das Facebook-Modul gefunden + +**Empfehlung:** Test-Suite erstellen mit pytest: +- test_facebook_selectors.py +- test_facebook_registration.py +- test_facebook_utils.py + +## 6. Redundanter Code in Selektoren +**Problem:** Viele alternative Selektoren könnten in Listen organisiert werden + +**Empfehlung:** +```python +class FacebookSelectors: + FIRSTNAME_SELECTORS = [ + "input[name='firstname']", + "input[aria-label='Vorname']", + "input[placeholder*='Vorname']" + ] +``` + +## 7. Fehlende Retry-Logik +**Problem:** Workflow definiert `retry` Werte, aber keine Implementation dafür + +**Empfehlung:** Retry-Decorator implementieren: +```python +@retry(max_attempts=3, delay=1.0) +def _open_registration_form(self): + # ... +``` + +## 8. Unvollständige Captcha-Behandlung +**Problem:** `handle_captcha()` erkennt nur Captchas, löst sie aber nicht + +**Empfehlung:** +- 2Captcha/Anti-Captcha Service Integration +- Oder manuellen Modus mit Benachrichtigung implementieren + +## 9. Fehlende Konfigurationsdatei +**Problem:** Keine zentrale Konfiguration für Facebook-spezifische Settings + +**Empfehlung:** `facebook_config.py` erstellen: +```python +class FacebookConfig: + BASE_URL = "https://www.facebook.com" + SUPPORTED_LANGUAGES = ["de", "en"] + MIN_PASSWORD_LENGTH = 6 + VERIFICATION_CODE_LENGTH = 5 +``` + +## 10. Unvollständige Internationalisierung +**Problem:** Texte sind teilweise hardcodiert auf Deutsch + +**Empfehlung:** i18n-Support verbessern mit Language-Dictionary + +## Priorität der Verbesserungen: +1. **Hoch:** Facebook Login implementieren +2. **Hoch:** Retry-Logik implementieren +3. **Mittel:** Timeout-Konfiguration zentralisieren +4. **Mittel:** Unit-Tests hinzufügen +5. **Niedrig:** Code-Redundanz reduzieren \ No newline at end of file diff --git a/social_networks/facebook/facebook_automation.py b/social_networks/facebook/facebook_automation.py index f72cf0c..330f55a 100644 --- a/social_networks/facebook/facebook_automation.py +++ b/social_networks/facebook/facebook_automation.py @@ -13,7 +13,6 @@ 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 @@ -85,6 +84,9 @@ class FacebookAutomation(BaseAutomation): window_position=window_position ) + # Zusätzliche Initialisierungen für Facebook + self.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" @@ -118,33 +120,37 @@ class FacebookAutomation(BaseAutomation): def _initialize_browser(self) -> bool: """ - Initialisiert den Browser mit Anti-Bot Features. - Identisch zu Instagram für konsistente Fingerprint-Protection. + Initialisiert den Browser mit Facebook-spezifischen Einstellungen. + Folgt dem Instagram-Pattern für saubere Implementierung. Returns: bool: True bei Erfolg, False bei Fehler """ try: + self._emit_customer_log("🔄 Sichere Verbindung wird aufgebaut...") + # Proxy-Konfiguration, falls aktiviert proxy_config = None if self.use_proxy: + self._emit_customer_log("🌐 Optimale Verbindung wird ausgewählt...") 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 + # Browser initialisieren (wie Instagram - direkt, ohne super()) self.browser = PlaywrightManager( headless=self.headless, proxy=proxy_config, browser_type="chromium", screenshots_dir=self.screenshots_dir, - slowmo=self.slowmo + slowmo=self.slowmo, + window_position=self.window_position ) # Browser starten self.browser.start() - # Erweiterten Fingerprint-Schutz aktivieren (wie bei Instagram) + # Erweiterten Fingerprint-Schutz aktivieren, wenn gewünscht if self.enhanced_stealth: # Erstelle Extensions-Objekt extensions = PlaywrightExtensions(self.browser) @@ -152,39 +158,55 @@ class FacebookAutomation(BaseAutomation): # Methoden anhängen extensions.hook_into_playwright_manager() - # Fingerprint-Schutz aktivieren + # Fingerprint-Schutz aktivieren mit angepasster Konfiguration if self.provided_fingerprint: + # Nutze den bereitgestellten Fingerprint logger.info("Verwende bereitgestellten Fingerprint für Account-Erstellung") + # Konvertiere Dict zu BrowserFingerprint wenn nötig if isinstance(self.provided_fingerprint, dict): - from infrastructure.models.browser_fingerprint import BrowserFingerprint + from domain.entities.browser_fingerprint import BrowserFingerprint fingerprint_obj = BrowserFingerprint.from_dict(self.provided_fingerprint) else: fingerprint_obj = self.provided_fingerprint - + + # Wende Fingerprint über FingerprintProtection an + from browser.fingerprint_protection import FingerprintProtection + protection = FingerprintProtection( + context=self.browser.context, + fingerprint_config=fingerprint_obj + ) + protection.apply_to_context(self.browser.context) + logger.info(f"Fingerprint {fingerprint_obj.fingerprint_id} angewendet") 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})") + # Fallback: Zufällige Fingerprint-Konfiguration + fingerprint_config = { + "noise_level": self.fingerprint_noise, + "canvas_noise": True, + "audio_noise": True, + "webgl_noise": True, + "hardware_concurrency": random.choice([4, 6, 8]), + "device_memory": random.choice([4, 8]), + "language": self.language, + "timezone_id": "Europe/Berlin" + } + + success = self.browser.enable_enhanced_fingerprint_protection(fingerprint_config) + if success: + logger.info("Erweiterter Fingerprint-Schutz erfolgreich aktiviert") + else: + logger.warning("Erweiterter Fingerprint-Schutz konnte nicht aktiviert werden") # Facebook-spezifische Browser-Einstellungen self._apply_facebook_specific_settings() + self._emit_customer_log("✅ Verbindung erfolgreich hergestellt") logger.info("Browser erfolgreich initialisiert") return True except Exception as e: logger.error(f"Fehler bei der Browser-Initialisierung: {e}") + self.status["error"] = f"Browser-Initialisierungsfehler: {str(e)}" return False def _apply_facebook_specific_settings(self): @@ -257,9 +279,10 @@ class FacebookAutomation(BaseAutomation): # Passwort generieren wenn nicht vorhanden if not password: + # Facebook-spezifische Passwort-Policy password = self.password_generator.generate_password( - length=random.randint(12, 16), - include_special=True + platform="facebook", # Plattform-spezifische Regeln + length=random.randint(12, 16) # Überschreibt default-Länge ) logger.info("Passwort generiert") @@ -388,6 +411,82 @@ class FacebookAutomation(BaseAutomation): # Browser offen lassen für User-Kontrolle logger.info("Login abgeschlossen - Browser bleibt offen") + def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]: + """ + Verifiziert einen Facebook-Account mit einem Bestätigungscode. + Implementiert die abstrakte Methode aus BaseAutomation. + + Args: + verification_code: Der Bestätigungscode (5-stellig für Facebook) + **kwargs: Weitere optionale Parameter + + Returns: + Dict[str, Any]: Ergebnis der Verifizierung mit Status + """ + logger.info(f"Starte Facebook-Account-Verifizierung mit Code: {verification_code}") + + try: + # Browser initialisieren falls noch nicht geschehen + if not self.browser or not hasattr(self.browser, 'page'): + if not self._initialize_browser(): + return {"success": False, "error": "Browser konnte nicht initialisiert werden"} + + # Delegiere an Verification-Klasse + # Die eigentliche Verifizierung wird bereits in registration.py gehandhabt + # Diese Methode ist für standalone Verifizierung + + # Prüfe ob wir auf der Verifizierungsseite sind + current_url = self.browser.page.url if self.browser else "" + if "confirmemail" not in current_url: + logger.warning("Nicht auf der Verifizierungsseite - navigiere dorthin") + # Könnte hier zur Verifizierungsseite navigieren wenn nötig + + # Nutze die _enter_verification_code Methode aus registration + from .facebook_registration import FacebookRegistration + temp_registration = FacebookRegistration(self) + success = temp_registration._enter_verification_code(verification_code) + + if success: + logger.info("Verifizierung erfolgreich abgeschlossen") + result = { + "success": True, + "stage": "verified", + "message": "Account erfolgreich verifiziert" + } + else: + logger.error("Verifizierung fehlgeschlagen") + result = { + "success": False, + "error": "Verifizierungscode konnte nicht bestätigt werden", + "stage": "verification_failed" + } + + # Screenshot + self._take_screenshot(f"verification_result_{int(time.time())}") + + # Status aktualisieren + self.status.update(result) + + return result + + except Exception as e: + error_msg = f"Unerwarteter Fehler bei der Verifizierung: {str(e)}" + logger.error(error_msg, exc_info=True) + + # Fehler-Screenshot + self._take_screenshot(f"verification_error_{int(time.time())}") + + self.status.update({ + "success": False, + "error": error_msg, + "stage": "error" + }) + + return self.status + finally: + # Browser offen lassen für weitere Aktionen + logger.info("Verifizierung abgeschlossen - Browser bleibt offen") + def _initialize_browser_with_fingerprint(self, account_id: str) -> bool: """ Initialisiert den Browser mit einem Account-spezifischen Fingerprint. diff --git a/social_networks/facebook/facebook_registration.py b/social_networks/facebook/facebook_registration.py index 40d7325..980b164 100644 --- a/social_networks/facebook/facebook_registration.py +++ b/social_networks/facebook/facebook_registration.py @@ -168,7 +168,11 @@ class FacebookRegistration: logger.info(f"Navigiere zu {self.automation.base_url}") # Navigiere zur Facebook-Seite - self.automation.browser.navigate_to(self.automation.base_url) + navigation_success = self.automation.browser.navigate_to(self.automation.base_url) + + if not navigation_success: + logger.error(f"Navigation zu {self.automation.base_url} fehlgeschlagen") + return False # Warte auf Seitenladung self.automation.human_behavior.wait_for_page_load(multiplier=1.5) @@ -178,11 +182,20 @@ class FacebookRegistration: # Prüfe ob wir auf Facebook sind current_url = self.automation.browser.page.url + logger.info(f"Aktuelle URL nach Navigation: {current_url}") + + # Debug: Prüfe sichtbare Elemente + logger.debug(f"Sichtbare Buttons: {self.automation.browser.page.locator('button').count()}") + logger.debug(f"Sichtbare Links: {self.automation.browser.page.locator('a').count()}") + if "facebook.com" in current_url: logger.info("Erfolgreich zu Facebook navigiert") return True else: logger.error(f"Nicht auf Facebook gelandet: {current_url}") + # Zusätzliche Debug-Info + page_title = self.automation.browser.page.title() + logger.debug(f"Seiten-Titel: {page_title}") return False except Exception as e: @@ -287,15 +300,31 @@ class FacebookRegistration: button_clicked = True if button_clicked: - # Warte auf Formular-Ladung - self.automation.human_behavior.wait_for_page_load() - self.automation._take_screenshot("registration_form") + # Warte auf Formular-Ladung mit längerer Wartezeit + self.automation.human_behavior.wait_for_page_load(multiplier=2.0) + + # Extra Wartezeit für Facebook's React-Rendering + self.automation.human_behavior.random_delay(2.0, 3.0) + + # Prüfe ob Browser noch aktiv ist + if not self.automation.browser or not self.automation.browser.page: + logger.error("Browser wurde unerwartet geschlossen") + return False + + try: + self.automation._take_screenshot("registration_form") + except Exception as e: + logger.warning(f"Screenshot fehlgeschlagen: {e}") # 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 + try: + 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 + except Exception as e: + logger.error(f"Fehler beim Abrufen der URL: {e}") + return False logger.error("Konnte Registrierungsformular nicht öffnen") return False @@ -307,6 +336,7 @@ class FacebookRegistration: def _fill_registration_form(self, account_data: Dict[str, Any]) -> bool: """ Füllt das Registrierungsformular aus. + Verbesserte Version mit Fallback-Selektoren und robusterem Handling. Args: account_data: Account-Daten @@ -317,90 +347,234 @@ class FacebookRegistration: 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") + # Prüfe ob Browser noch aktiv ist + if not self.automation.browser or not self.automation.browser.page: + logger.error("Browser ist nicht mehr aktiv vor Formular-Ausfüllung") return False - # Nachname - if not self.automation.ui_helper.type_text_human_like( + # Debug: Prüfe welche Input-Felder sichtbar sind + try: + visible_inputs = self.automation.browser.page.locator("input[type='text']").count() + logger.debug(f"Anzahl sichtbarer Text-Input-Felder: {visible_inputs}") + + # Liste alle sichtbaren Inputs mit Namen auf + all_inputs = self.automation.browser.page.locator("input").all() + for idx, inp in enumerate(all_inputs[:10]): # Nur erste 10 zur Sicherheit + try: + name = inp.get_attribute("name") + placeholder = inp.get_attribute("placeholder") + aria_label = inp.get_attribute("aria-label") + logger.debug(f"Input {idx}: name='{name}', placeholder='{placeholder}', aria-label='{aria_label}'") + except: + pass + except Exception as e: + logger.warning(f"Debug-Ausgabe fehlgeschlagen: {e}") + + # Vorname mit Fallback + firstname_selectors = [ + self.selectors.REG_FIRSTNAME_FIELD, + self.selectors.REG_FIRSTNAME_FIELD_ALT, + "input[placeholder*='Vorname']", + "input[name='firstname']", # Direkter Name-Selektor + "input[aria-label*='Vorname' i]" # Case-insensitive aria-label + ] + firstname_filled = False + for selector in firstname_selectors: + logger.debug(f"Versuche Vorname-Selektor: {selector}") + try: + if self.automation.browser.is_element_visible(selector, timeout=1000): + logger.debug(f"Selektor {selector} ist sichtbar") + if self.automation.ui_helper.type_text_human_like(selector, account_data["first_name"]): + logger.info(f"Vorname eingegeben mit: {selector}") + firstname_filled = True + break + else: + logger.debug(f"Selektor {selector} nicht sichtbar") + except Exception as e: + logger.debug(f"Fehler bei Selektor {selector}: {e}") + + if not firstname_filled: + logger.error(f"Fehler beim Eingeben des Vornamens. Getestete Selektoren: {firstname_selectors}") + # Zusätzliche Debug-Info + try: + current_url = self.automation.browser.page.url + logger.error(f"Aktuelle URL beim Fehler: {current_url}") + except: + pass + return False + + # Nachname mit Fallback + lastname_selectors = [ self.selectors.REG_LASTNAME_FIELD, - account_data["last_name"] - ): + self.selectors.REG_LASTNAME_FIELD_ALT, + "input[placeholder*='Nachname']" + ] + lastname_filled = False + for selector in lastname_selectors: + if self.automation.browser.is_element_visible(selector, timeout=1000): + if self.automation.ui_helper.type_text_human_like(selector, account_data["last_name"]): + logger.info(f"Nachname eingegeben mit: {selector}") + lastname_filled = True + break + + if not lastname_filled: logger.error("Fehler beim Eingeben des Nachnamens") return False - # Geburtsdatum + # Geburtsdatum - Verbesserte Dropdown-Behandlung birth_date = account_data["birth_date"] - # Tag auswählen - if not self.automation.browser.select_option( + # Tag auswählen mit Fallback + day_selectors = [ self.selectors.REG_BIRTHDAY_DAY, - str(birth_date["day"]) - ): + self.selectors.REG_BIRTHDAY_DAY_ALT, + "select[aria-label='Tag']", + "select[title='Tag']" + ] + day_selected = False + for selector in day_selectors: + try: + if self.automation.browser.select_option(selector, str(birth_date["day"])): + logger.info(f"Tag {birth_date['day']} ausgewählt mit: {selector}") + day_selected = True + break + except: + continue + + if not day_selected: logger.error("Fehler beim Auswählen des Geburtstags") return False - # Monat auswählen - if not self.automation.browser.select_option( + self.automation.human_behavior.random_delay(0.3, 0.5) + + # Monat auswählen mit Fallback + month_selectors = [ self.selectors.REG_BIRTHDAY_MONTH, - str(birth_date["month"]) - ): + self.selectors.REG_BIRTHDAY_MONTH_ALT, + "select[aria-label='Monat']", + "select[title='Monat']" + ] + month_selected = False + for selector in month_selectors: + try: + if self.automation.browser.select_option(selector, str(birth_date["month"])): + logger.info(f"Monat {birth_date['month']} ausgewählt mit: {selector}") + month_selected = True + break + except: + continue + + if not month_selected: logger.error("Fehler beim Auswählen des Geburtsmonats") return False - # Jahr auswählen - if not self.automation.browser.select_option( + self.automation.human_behavior.random_delay(0.3, 0.5) + + # Jahr auswählen mit Fallback + year_selectors = [ self.selectors.REG_BIRTHDAY_YEAR, - str(birth_date["year"]) - ): + self.selectors.REG_BIRTHDAY_YEAR_ALT, + "select[aria-label='Jahr']", + "select[title='Jahr']" + ] + year_selected = False + for selector in year_selectors: + try: + if self.automation.browser.select_option(selector, str(birth_date["year"])): + logger.info(f"Jahr {birth_date['year']} ausgewählt mit: {selector}") + year_selected = True + break + except: + continue + + if not year_selected: logger.error("Fehler beim Auswählen des Geburtsjahrs") return False self.automation.human_behavior.random_delay(0.5, 1.0) - # Geschlecht auswählen + # Geschlecht auswählen - Verbesserte Radio-Button Behandlung gender_selector = self.selectors.get_gender_selector(account_data["gender"]) - if not self.automation.browser.click_element(gender_selector): + gender_selected = False + + # Versuche erst direkten Click auf das Input-Element + if self.automation.browser.click_element(gender_selector): + logger.info(f"Geschlecht direkt ausgewählt: {account_data['gender']}") + gender_selected = True + else: + # Fallback: Klicke auf das Label + label_selectors = [ + f"label:has-text('{'Weiblich' if account_data['gender'] == 'female' else 'Männlich' if account_data['gender'] == 'male' else 'Divers'}')", + f"label._58mt:has(input[value='{'1' if account_data['gender'] == 'female' else '2' if account_data['gender'] == 'male' else '-1'}'])" + ] + for selector in label_selectors: + try: + if self.automation.browser.click_element(selector): + logger.info(f"Geschlecht über Label ausgewählt: {account_data['gender']}") + gender_selected = True + break + except: + continue + + if not gender_selected: 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") + # E-Mail eingeben mit Fallback + email_field = account_data.get("email") + if not email_field: + logger.error("Keine E-Mail-Adresse angegeben") return False - if not self.automation.ui_helper.type_text_human_like( + email_selectors = [ self.selectors.REG_EMAIL_OR_PHONE, - contact_field - ): - logger.error("Fehler beim Eingeben der Kontaktdaten") + self.selectors.REG_EMAIL_OR_PHONE_ALT, + "input[placeholder*='E-Mail']", + "input[placeholder*='Handynummer oder E-Mail']" + ] + email_filled = False + for selector in email_selectors: + if self.automation.browser.is_element_visible(selector, timeout=1000): + if self.automation.ui_helper.type_text_human_like(selector, email_field): + logger.info(f"E-Mail eingegeben mit: {selector}") + email_filled = True + break + + if not email_filled: + logger.error("Fehler beim Eingeben der E-Mail") 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") + # E-Mail-Bestätigung falls erforderlich + 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, + email_field + ): + logger.warning("Fehler beim Bestätigen der E-Mail") - # Passwort - if not self.automation.ui_helper.type_text_human_like( + # Passwort mit Fallback + password_selectors = [ self.selectors.REG_PASSWORD, - account_data["password"] - ): + self.selectors.REG_PASSWORD_ALT, + self.selectors.REG_PASSWORD_ALT2, + "input[type='password']", + "input[autocomplete='new-password']" + ] + password_filled = False + for selector in password_selectors: + if self.automation.browser.is_element_visible(selector, timeout=1000): + if self.automation.ui_helper.type_text_human_like(selector, account_data["password"]): + logger.info(f"Passwort eingegeben mit: {selector}") + password_filled = True + break + + if not password_filled: logger.error("Fehler beim Eingeben des Passworts") return False @@ -522,30 +696,68 @@ class FacebookRegistration: def _enter_verification_code(self, code: str) -> bool: """ Gibt den Verifikationscode ein. + Verbesserte Version für 5-stelligen Code mit Fallback-Selektoren. Args: - code: Verifikationscode + code: Verifikationscode (5-stellig, nur Zahlen) Returns: bool: True bei Erfolg """ try: + # Extrahiere nur Zahlen aus dem Code (falls FB- prefix vorhanden) + import re + code_digits = re.sub(r'\D', '', code) + if len(code_digits) == 5: + code = code_digits 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") + # Versuche verschiedene Selektoren für das Code-Eingabefeld + code_selectors = [ + self.selectors.VERIFICATION_CODE_INPUT, # input#code_in_cliff + self.selectors.VERIFICATION_CODE_INPUT_ALT, # input[name='code'] + self.selectors.VERIFICATION_CODE_INPUT_ALT2, # input[maxlength='5'] + "input[size='5']", # 5-stelliges Feld + "input[type='text'][maxlength='5']", # Text-Input mit 5 Zeichen + "input[placeholder*='Code']", # Placeholder mit 'Code' + ] + + code_entered = False + for selector in code_selectors: + try: + if self.automation.browser.is_element_visible(selector, timeout=1000): + # Stelle sicher, dass das Feld leer ist + self.automation.browser.page.locator(selector).first.clear() + self.automation.human_behavior.random_delay(0.2, 0.4) + + # Gebe Code ein + if self.automation.ui_helper.type_text_human_like(selector, code): + logger.info(f"Verifikationscode eingegeben mit: {selector}") + code_entered = True + break + except: + continue + + if not code_entered: + logger.error("Fehler beim Eingeben des Verifikationscodes - kein Eingabefeld gefunden") return False + self.automation.human_behavior.random_delay(0.5, 1.0) + # Weiter-Button klicken continue_clicked = False - # Versuche verschiedene Selektoren - for text in self.selectors.get_button_texts("continue"): - selector = f"button:has-text('{text}')" + # Versuche verschiedene Button-Selektoren + continue_selectors = [ + self.selectors.VERIFICATION_CONTINUE_BUTTON, # button:has-text('Weiter') + self.selectors.VERIFICATION_CONTINUE_BUTTON_EN, # button:has-text('Continue') + "button[type='submit']", # Submit-Button + "button:has-text('Bestätigen')", # Bestätigen + "button:has-text('Confirm')", # Englisch + "button:has-text('OK')", # OK + ] + + for selector in continue_selectors: try: if self.automation.browser.click_element(selector, timeout=1000): logger.info(f"Verifikation fortgesetzt mit: {selector}") @@ -554,18 +766,35 @@ class FacebookRegistration: except: continue - # Oder Enter drücken + # Fallback: Enter drücken if not continue_clicked: self.automation.browser.page.keyboard.press("Enter") logger.info("Verifikation mit Enter fortgesetzt") + continue_clicked = True - # Warte auf Navigation - self.automation.human_behavior.wait_for_page_load() + if not continue_clicked: + logger.error("Konnte Verifikation nicht fortsetzen") + return False + + # Warte auf Navigation/Verarbeitung + self.automation.human_behavior.wait_for_page_load(multiplier=1.5) # 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") + ok_selectors = [ + self.selectors.VERIFICATION_OK_BUTTON, + self.selectors.VERIFICATION_OK_BUTTON_ALT, + "a:has-text('OK')", + "button:has-text('OK')" + ] + + for selector in ok_selectors: + try: + if self.automation.browser.is_element_visible(selector, timeout=2000): + self.automation.browser.click_element(selector) + logger.info(f"OK-Button nach Verifikation geklickt: {selector}") + break + except: + continue return True diff --git a/social_networks/facebook/facebook_selectors.py b/social_networks/facebook/facebook_selectors.py index e3a9be3..a7fed1e 100644 --- a/social_networks/facebook/facebook_selectors.py +++ b/social_networks/facebook/facebook_selectors.py @@ -29,27 +29,34 @@ class FacebookSelectors: LOGIN_BUTTON_ALT = "button[data-testid='royal_login_button']" # ===== REGISTRATION FORM ===== - # Name fields + # Name fields - Updated based on actual HTML REG_FIRSTNAME_FIELD = "input[name='firstname']" + REG_FIRSTNAME_FIELD_ALT = "input[aria-label='Vorname']" REG_LASTNAME_FIELD = "input[name='lastname']" + REG_LASTNAME_FIELD_ALT = "input[aria-label='Nachname']" - # Birthday selects + # Birthday selects - Updated with correct selectors REG_BIRTHDAY_DAY = "select[name='birthday_day']" + REG_BIRTHDAY_DAY_ALT = "select#day" REG_BIRTHDAY_MONTH = "select[name='birthday_month']" + REG_BIRTHDAY_MONTH_ALT = "select#month" REG_BIRTHDAY_YEAR = "select[name='birthday_year']" + REG_BIRTHDAY_YEAR_ALT = "select#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']" + # Gender radio buttons - Updated with correct values + REG_GENDER_FEMALE = "input[name='sex'][value='1']" # Weiblich + REG_GENDER_MALE = "input[name='sex'][value='2']" # Männlich + REG_GENDER_CUSTOM = "input[name='sex'][value='-1']" # Divers - # Contact info + # Contact info - Updated based on actual HTML REG_EMAIL_OR_PHONE = "input[name='reg_email__']" + REG_EMAIL_OR_PHONE_ALT = "input[aria-label='Handynummer oder E-Mail-Adresse']" REG_EMAIL_CONFIRM = "input[name='reg_email_confirmation__']" # Erscheint wenn Email eingegeben - # Password + # Password - Updated with correct selectors REG_PASSWORD = "input[name='reg_passwd__']" REG_PASSWORD_ALT = "input#password_step_input" + REG_PASSWORD_ALT2 = "input[aria-label='Neues Passwort']" # Submit button REG_SUBMIT_BUTTON = "button[name='websubmit']" @@ -57,8 +64,10 @@ class FacebookSelectors: REG_SUBMIT_BUTTON_EN = "button:has-text('Sign Up')" # ===== EMAIL VERIFICATION ===== + # Updated for 5-digit code based on actual HTML VERIFICATION_CODE_INPUT = "input#code_in_cliff" VERIFICATION_CODE_INPUT_ALT = "input[name='code']" + VERIFICATION_CODE_INPUT_ALT2 = "input[maxlength='5']" # 5-stelliger Code VERIFICATION_CONTINUE_BUTTON = "button:has-text('Weiter')" VERIFICATION_CONTINUE_BUTTON_EN = "button:has-text('Continue')" diff --git a/views/widgets/forge_animation_widget.py b/views/widgets/forge_animation_widget.py index 5fae8fa..c96943f 100644 --- a/views/widgets/forge_animation_widget.py +++ b/views/widgets/forge_animation_widget.py @@ -27,8 +27,9 @@ class ForgeAnimationDialog(QDialog): def init_ui(self): """Initialisiert die UI mit verbessertem Design""" - # Nur Dialog im Vordergrund, nicht das ganze Hauptfenster - self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) + # Dialog-Fenster ohne Stay-on-Top, um Browser nicht zu blockieren + # Entfernt Qt.WindowStaysOnTopHint, da dies den Browser-Fokus stören kann + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) self.setModal(False) # Nicht modal - blockiert nicht das Hauptfenster self.setFixedSize(650, 600) # Ursprüngliche Größe beibehalten @@ -186,7 +187,8 @@ class ForgeAnimationDialog(QDialog): def start_animation(self): """Zeigt den Dialog an""" self.status_label.setText("Initialisiere...") - self.raise_timer.start() # Starte Timer für Always-on-Top + # Timer deaktiviert - verhindert Fokus-Probleme mit dem Browser + # self.raise_timer.start() # Deaktiviert: Stört Browser-Interaktion def stop_animation(self): """Stoppt die Animation und den Timer"""