Facebook - Workflow geht aber Popup nicht da mit Browser nicht anbtouchen
Dieser Commit ist enthalten in:
@ -5,9 +5,9 @@
|
|||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
- **Path**: `A:\GiTea\AccountForger`
|
- **Path**: `A:\GiTea\AccountForger`
|
||||||
- **Files**: 886 files
|
- **Files**: 990 files
|
||||||
- **Size**: 125.8 MB
|
- **Size**: 182.0 MB
|
||||||
- **Last Modified**: 2025-09-10 21:18
|
- **Last Modified**: 2025-09-14 18:54
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ application/
|
|||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ ├── services/
|
│ ├── services/
|
||||||
│ │ ├── error_handler.py
|
│ │ ├── error_handler.py
|
||||||
|
│ │ ├── platform_service.py
|
||||||
│ │ └── __init__.py
|
│ │ └── __init__.py
|
||||||
│ └── use_cases/
|
│ └── use_cases/
|
||||||
│ ├── adaptive_rate_limit_use_case.py
|
│ ├── adaptive_rate_limit_use_case.py
|
||||||
@ -101,6 +102,7 @@ domain/
|
|||||||
│ │ ├── browser_fingerprint.py
|
│ │ ├── browser_fingerprint.py
|
||||||
│ │ ├── error_event.py
|
│ │ ├── error_event.py
|
||||||
│ │ ├── method_rotation.py
|
│ │ ├── method_rotation.py
|
||||||
|
│ │ ├── platform.py
|
||||||
│ │ ├── rate_limit_policy.py
|
│ │ ├── rate_limit_policy.py
|
||||||
│ │ └── __init__.py
|
│ │ └── __init__.py
|
||||||
│ ├── enums
|
│ ├── enums
|
||||||
@ -122,6 +124,7 @@ domain/
|
|||||||
│ ├── error_summary.py
|
│ ├── error_summary.py
|
||||||
│ ├── login_credentials.py
|
│ ├── login_credentials.py
|
||||||
│ ├── operation_result.py
|
│ ├── operation_result.py
|
||||||
|
│ ├── platform_name.py
|
||||||
│ ├── report.py
|
│ ├── report.py
|
||||||
│ └── __init__.py
|
│ └── __init__.py
|
||||||
infrastructure/
|
infrastructure/
|
||||||
@ -189,14 +192,14 @@ logs/
|
|||||||
│ └── screenshots/
|
│ └── screenshots/
|
||||||
│ ├── after_account_create_click_1755281004.png
|
│ ├── after_account_create_click_1755281004.png
|
||||||
│ ├── after_account_create_click_1757531758.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_code_retrieval_1757531778.png
|
||||||
│ ├── after_login_button_click_1755168832.png
|
│ ├── after_code_retrieval_1757807127.png
|
||||||
│ ├── after_login_button_click_1755280227.png
|
│ ├── after_code_retrieval_1757807239.png
|
||||||
│ ├── after_login_button_click_1755280551.png
|
│ ├── after_code_retrieval_1757810517.png
|
||||||
│ ├── after_login_button_click_1755280826.png
|
│ └── after_cookie_consent_1757852739.png
|
||||||
│ ├── after_login_button_click_1755282576.png
|
|
||||||
│ ├── after_login_button_click_1755282842.png
|
|
||||||
│ └── after_login_button_click_1755341790.png
|
|
||||||
resources/
|
resources/
|
||||||
│ ├── icons/
|
│ ├── icons/
|
||||||
│ │ ├── check-white.svg
|
│ │ ├── check-white.svg
|
||||||
@ -213,6 +216,8 @@ resources/
|
|||||||
│ ├── dark.qss
|
│ ├── dark.qss
|
||||||
│ └── light.qss
|
│ └── light.qss
|
||||||
screenshots
|
screenshots
|
||||||
|
scripts/
|
||||||
|
│ └── fix_x_accounts.py
|
||||||
social_networks/
|
social_networks/
|
||||||
│ ├── base_automation.py
|
│ ├── base_automation.py
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
@ -225,6 +230,7 @@ social_networks/
|
|||||||
│ │ ├── facebook_utils.py
|
│ │ ├── facebook_utils.py
|
||||||
│ │ ├── facebook_verification.py
|
│ │ ├── facebook_verification.py
|
||||||
│ │ ├── facebook_workflow.py
|
│ │ ├── facebook_workflow.py
|
||||||
|
│ │ ├── IMPROVEMENT_SUGGESTIONS.md
|
||||||
│ │ └── __init__.py
|
│ │ └── __init__.py
|
||||||
│ ├── gmail/
|
│ ├── gmail/
|
||||||
│ │ ├── gmail_automation.py
|
│ │ ├── 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 generated on 2025-09-06 21:49:36
|
||||||
- README updated on 2025-09-13 20:45:18
|
- 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
|
||||||
|
|||||||
@ -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.
|
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_controller import BasePlatformController
|
||||||
from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread
|
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 social_networks.facebook.facebook_automation import FacebookAutomation
|
||||||
from utils.birthday_generator import BirthdayGenerator
|
from utils.birthday_generator import BirthdayGenerator
|
||||||
@ -108,7 +109,15 @@ class FacebookWorkerThread(BaseAccountCreationWorkerThread):
|
|||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
class FacebookController(BasePlatformController):
|
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):
|
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)
|
super().__init__("Facebook", db_manager, proxy_rotator, email_handler, language_manager)
|
||||||
@ -117,13 +126,13 @@ class FacebookController(BasePlatformController):
|
|||||||
|
|
||||||
def get_generator_tab(self):
|
def get_generator_tab(self):
|
||||||
"""
|
"""
|
||||||
Erstellt und konfiguriert den Generator-Tab für Facebook.
|
Erstellt und konfiguriert den Generator-Tab für Facebook.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
FacebookGeneratorTab: Konfigurierter Tab für Facebook mit Geschlechtsauswahl
|
GeneratorTab: Konfigurierter Tab für Facebook mit Geschlechtsauswahl
|
||||||
"""
|
"""
|
||||||
# Erstelle Facebook-spezifischen Generator-Tab mit Geschlechtsfeld
|
# Erstelle generischen Generator-Tab
|
||||||
generator_tab = FacebookGeneratorTab(
|
generator_tab = GeneratorTab(
|
||||||
"Facebook",
|
"Facebook",
|
||||||
self.language_manager
|
self.language_manager
|
||||||
)
|
)
|
||||||
@ -145,32 +154,52 @@ class FacebookController(BasePlatformController):
|
|||||||
generator_tab: Der zu konfigurierende Tab
|
generator_tab: Der zu konfigurierende Tab
|
||||||
"""
|
"""
|
||||||
# Facebook-spezifische Konfiguration
|
# 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
|
# Vor- und Nachnamen werden im Worker-Thread aus full_name extrahiert
|
||||||
|
|
||||||
logger.debug("Facebook-spezifische Felder konfiguriert (inkl. Geschlechtsauswahl)")
|
logger.debug("Facebook-spezifische Felder konfiguriert (inkl. Geschlechtsauswahl)")
|
||||||
|
|
||||||
def handle_generation_request(self, params: Dict[str, Any]):
|
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:
|
Args:
|
||||||
params: Parameter für die Generierung
|
params: Parameter für die Generierung
|
||||||
"""
|
"""
|
||||||
logger.info(f"Facebook-Account-Generierung angefordert: {params}")
|
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():
|
if self.worker_thread and self.worker_thread.isRunning():
|
||||||
logger.warning("Ein Account wird bereits erstellt")
|
logger.warning("Ein Account wird bereits erstellt")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Facebook-spezifische Parameter hinzufügen
|
# Facebook-spezifische Parameter hinzufügen
|
||||||
params["platform"] = "facebook"
|
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
|
# Starte Worker-Thread
|
||||||
self.worker_thread = FacebookWorkerThread(params)
|
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(
|
self.worker_thread.update_signal.connect(
|
||||||
lambda msg: self.status_update.emit(msg)
|
lambda msg: self.status_update.emit(msg)
|
||||||
)
|
)
|
||||||
@ -190,6 +219,10 @@ class FacebookController(BasePlatformController):
|
|||||||
# Starte Thread
|
# Starte Thread
|
||||||
self.worker_thread.start()
|
self.worker_thread.start()
|
||||||
self.generation_started.emit()
|
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]):
|
def _handle_generation_success(self, result: Dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
@ -200,6 +233,11 @@ class FacebookController(BasePlatformController):
|
|||||||
"""
|
"""
|
||||||
logger.info("Facebook-Account erfolgreich erstellt")
|
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
|
# Speichere Account in Datenbank
|
||||||
if self.db_manager and result.get("account_data"):
|
if self.db_manager and result.get("account_data"):
|
||||||
account_data = result["account_data"]
|
account_data = result["account_data"]
|
||||||
@ -218,6 +256,16 @@ class FacebookController(BasePlatformController):
|
|||||||
error_msg: Fehlermeldung
|
error_msg: Fehlermeldung
|
||||||
"""
|
"""
|
||||||
logger.error(f"Fehler bei Facebook-Account-Erstellung: {error_msg}")
|
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)
|
self.generation_failed.emit(error_msg)
|
||||||
|
|
||||||
def stop_generation(self):
|
def stop_generation(self):
|
||||||
@ -226,8 +274,13 @@ class FacebookController(BasePlatformController):
|
|||||||
logger.info("Stoppe Facebook-Account-Generierung")
|
logger.info("Stoppe Facebook-Account-Generierung")
|
||||||
self.worker_thread.stop()
|
self.worker_thread.stop()
|
||||||
self.worker_thread.wait()
|
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):
|
def cleanup(self):
|
||||||
"""Räumt Ressourcen auf."""
|
"""Räumt Ressourcen auf."""
|
||||||
self.stop_generation()
|
self.stop_generation()
|
||||||
logger.info("Facebook Controller aufgeräumt")
|
logger.info("Facebook Controller aufgeräumt")
|
||||||
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
@ -13,7 +13,6 @@ from browser.playwright_manager import PlaywrightManager
|
|||||||
from utils.proxy_rotator import ProxyRotator
|
from utils.proxy_rotator import ProxyRotator
|
||||||
from utils.email_handler import EmailHandler
|
from utils.email_handler import EmailHandler
|
||||||
from utils.text_similarity import TextSimilarity, fuzzy_find_element, click_fuzzy_button
|
from utils.text_similarity import TextSimilarity, fuzzy_find_element, click_fuzzy_button
|
||||||
from domain.value_objects.browser_protection_style import BrowserProtectionStyle, ProtectionLevel
|
|
||||||
|
|
||||||
# Konfiguriere Logger
|
# Konfiguriere Logger
|
||||||
logger = logging.getLogger("base_automation")
|
logger = logging.getLogger("base_automation")
|
||||||
@ -167,12 +166,6 @@ class BaseAutomation(ABC):
|
|||||||
self.browser.start()
|
self.browser.start()
|
||||||
logger.info("Browser erfolgreich initialisiert")
|
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")
|
self._emit_customer_log("✅ Verbindung erfolgreich hergestellt")
|
||||||
return True
|
return True
|
||||||
@ -532,51 +525,6 @@ class BaseAutomation(ABC):
|
|||||||
|
|
||||||
return code
|
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:
|
def _is_text_similar(self, text1: str, text2: str, threshold: float = None) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
103
social_networks/facebook/IMPROVEMENT_SUGGESTIONS.md
Normale Datei
103
social_networks/facebook/IMPROVEMENT_SUGGESTIONS.md
Normale Datei
@ -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
|
||||||
@ -13,7 +13,6 @@ from typing import Dict, List, Any, Optional, Tuple
|
|||||||
|
|
||||||
from browser.playwright_manager import PlaywrightManager
|
from browser.playwright_manager import PlaywrightManager
|
||||||
from browser.playwright_extensions import PlaywrightExtensions
|
from browser.playwright_extensions import PlaywrightExtensions
|
||||||
from browser.fingerprint_protection import FingerprintProtection
|
|
||||||
from social_networks.base_automation import BaseAutomation
|
from social_networks.base_automation import BaseAutomation
|
||||||
from infrastructure.services.advanced_fingerprint_service import AdvancedFingerprintService
|
from infrastructure.services.advanced_fingerprint_service import AdvancedFingerprintService
|
||||||
from infrastructure.repositories.fingerprint_repository import FingerprintRepository
|
from infrastructure.repositories.fingerprint_repository import FingerprintRepository
|
||||||
@ -85,6 +84,9 @@ class FacebookAutomation(BaseAutomation):
|
|||||||
window_position=window_position
|
window_position=window_position
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Zusätzliche Initialisierungen für Facebook
|
||||||
|
self.window_position = window_position
|
||||||
|
|
||||||
# Facebook-spezifische Einstellungen
|
# Facebook-spezifische Einstellungen
|
||||||
self.language = language
|
self.language = language
|
||||||
self.base_url = f"https://www.facebook.com/?locale={language}_DE" if language == "de" else "https://www.facebook.com"
|
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:
|
def _initialize_browser(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Initialisiert den Browser mit Anti-Bot Features.
|
Initialisiert den Browser mit Facebook-spezifischen Einstellungen.
|
||||||
Identisch zu Instagram für konsistente Fingerprint-Protection.
|
Folgt dem Instagram-Pattern für saubere Implementierung.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True bei Erfolg, False bei Fehler
|
bool: True bei Erfolg, False bei Fehler
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
self._emit_customer_log("🔄 Sichere Verbindung wird aufgebaut...")
|
||||||
|
|
||||||
# Proxy-Konfiguration, falls aktiviert
|
# Proxy-Konfiguration, falls aktiviert
|
||||||
proxy_config = None
|
proxy_config = None
|
||||||
if self.use_proxy:
|
if self.use_proxy:
|
||||||
|
self._emit_customer_log("🌐 Optimale Verbindung wird ausgewählt...")
|
||||||
proxy_config = self.proxy_rotator.get_proxy(self.proxy_type)
|
proxy_config = self.proxy_rotator.get_proxy(self.proxy_type)
|
||||||
if not proxy_config:
|
if not proxy_config:
|
||||||
logger.warning(f"Kein Proxy vom Typ '{self.proxy_type}' verfügbar, verwende direkten Zugriff")
|
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(
|
self.browser = PlaywrightManager(
|
||||||
headless=self.headless,
|
headless=self.headless,
|
||||||
proxy=proxy_config,
|
proxy=proxy_config,
|
||||||
browser_type="chromium",
|
browser_type="chromium",
|
||||||
screenshots_dir=self.screenshots_dir,
|
screenshots_dir=self.screenshots_dir,
|
||||||
slowmo=self.slowmo
|
slowmo=self.slowmo,
|
||||||
|
window_position=self.window_position
|
||||||
)
|
)
|
||||||
|
|
||||||
# Browser starten
|
# Browser starten
|
||||||
self.browser.start()
|
self.browser.start()
|
||||||
|
|
||||||
# Erweiterten Fingerprint-Schutz aktivieren (wie bei Instagram)
|
# Erweiterten Fingerprint-Schutz aktivieren, wenn gewünscht
|
||||||
if self.enhanced_stealth:
|
if self.enhanced_stealth:
|
||||||
# Erstelle Extensions-Objekt
|
# Erstelle Extensions-Objekt
|
||||||
extensions = PlaywrightExtensions(self.browser)
|
extensions = PlaywrightExtensions(self.browser)
|
||||||
@ -152,39 +158,55 @@ class FacebookAutomation(BaseAutomation):
|
|||||||
# Methoden anhängen
|
# Methoden anhängen
|
||||||
extensions.hook_into_playwright_manager()
|
extensions.hook_into_playwright_manager()
|
||||||
|
|
||||||
# Fingerprint-Schutz aktivieren
|
# Fingerprint-Schutz aktivieren mit angepasster Konfiguration
|
||||||
if self.provided_fingerprint:
|
if self.provided_fingerprint:
|
||||||
|
# Nutze den bereitgestellten Fingerprint
|
||||||
logger.info("Verwende bereitgestellten Fingerprint für Account-Erstellung")
|
logger.info("Verwende bereitgestellten Fingerprint für Account-Erstellung")
|
||||||
|
# Konvertiere Dict zu BrowserFingerprint wenn nötig
|
||||||
if isinstance(self.provided_fingerprint, dict):
|
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)
|
fingerprint_obj = BrowserFingerprint.from_dict(self.provided_fingerprint)
|
||||||
else:
|
else:
|
||||||
fingerprint_obj = self.provided_fingerprint
|
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
|
self.account_fingerprint = fingerprint_obj
|
||||||
fingerprint_config = fingerprint_obj.to_config()
|
|
||||||
else:
|
else:
|
||||||
# Generiere neuen Fingerprint
|
# Fallback: Zufällige Fingerprint-Konfiguration
|
||||||
self.account_fingerprint = self.fingerprint_service.generate_fingerprint("facebook")
|
fingerprint_config = {
|
||||||
fingerprint_config = self.account_fingerprint.to_config()
|
"noise_level": self.fingerprint_noise,
|
||||||
|
"canvas_noise": True,
|
||||||
# Fingerprint-Protection mit Noise-Level
|
"audio_noise": True,
|
||||||
protection = FingerprintProtection(
|
"webgl_noise": True,
|
||||||
noise_level=self.fingerprint_noise,
|
"hardware_concurrency": random.choice([4, 6, 8]),
|
||||||
fingerprint_config=fingerprint_config
|
"device_memory": random.choice([4, 8]),
|
||||||
)
|
"language": self.language,
|
||||||
protection.apply(self.browser.page)
|
"timezone_id": "Europe/Berlin"
|
||||||
|
}
|
||||||
logger.info(f"Fingerprint-Schutz aktiviert (Noise-Level: {self.fingerprint_noise})")
|
|
||||||
|
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
|
# Facebook-spezifische Browser-Einstellungen
|
||||||
self._apply_facebook_specific_settings()
|
self._apply_facebook_specific_settings()
|
||||||
|
|
||||||
|
self._emit_customer_log("✅ Verbindung erfolgreich hergestellt")
|
||||||
logger.info("Browser erfolgreich initialisiert")
|
logger.info("Browser erfolgreich initialisiert")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler bei der Browser-Initialisierung: {e}")
|
logger.error(f"Fehler bei der Browser-Initialisierung: {e}")
|
||||||
|
self.status["error"] = f"Browser-Initialisierungsfehler: {str(e)}"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _apply_facebook_specific_settings(self):
|
def _apply_facebook_specific_settings(self):
|
||||||
@ -257,9 +279,10 @@ class FacebookAutomation(BaseAutomation):
|
|||||||
|
|
||||||
# Passwort generieren wenn nicht vorhanden
|
# Passwort generieren wenn nicht vorhanden
|
||||||
if not password:
|
if not password:
|
||||||
|
# Facebook-spezifische Passwort-Policy
|
||||||
password = self.password_generator.generate_password(
|
password = self.password_generator.generate_password(
|
||||||
length=random.randint(12, 16),
|
platform="facebook", # Plattform-spezifische Regeln
|
||||||
include_special=True
|
length=random.randint(12, 16) # Überschreibt default-Länge
|
||||||
)
|
)
|
||||||
logger.info("Passwort generiert")
|
logger.info("Passwort generiert")
|
||||||
|
|
||||||
@ -388,6 +411,82 @@ class FacebookAutomation(BaseAutomation):
|
|||||||
# Browser offen lassen für User-Kontrolle
|
# Browser offen lassen für User-Kontrolle
|
||||||
logger.info("Login abgeschlossen - Browser bleibt offen")
|
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:
|
def _initialize_browser_with_fingerprint(self, account_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Initialisiert den Browser mit einem Account-spezifischen Fingerprint.
|
Initialisiert den Browser mit einem Account-spezifischen Fingerprint.
|
||||||
|
|||||||
@ -168,7 +168,11 @@ class FacebookRegistration:
|
|||||||
logger.info(f"Navigiere zu {self.automation.base_url}")
|
logger.info(f"Navigiere zu {self.automation.base_url}")
|
||||||
|
|
||||||
# Navigiere zur Facebook-Seite
|
# 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
|
# Warte auf Seitenladung
|
||||||
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
|
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
|
||||||
@ -178,11 +182,20 @@ class FacebookRegistration:
|
|||||||
|
|
||||||
# Prüfe ob wir auf Facebook sind
|
# Prüfe ob wir auf Facebook sind
|
||||||
current_url = self.automation.browser.page.url
|
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:
|
if "facebook.com" in current_url:
|
||||||
logger.info("Erfolgreich zu Facebook navigiert")
|
logger.info("Erfolgreich zu Facebook navigiert")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.error(f"Nicht auf Facebook gelandet: {current_url}")
|
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
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -287,15 +300,31 @@ class FacebookRegistration:
|
|||||||
button_clicked = True
|
button_clicked = True
|
||||||
|
|
||||||
if button_clicked:
|
if button_clicked:
|
||||||
# Warte auf Formular-Ladung
|
# Warte auf Formular-Ladung mit längerer Wartezeit
|
||||||
self.automation.human_behavior.wait_for_page_load()
|
self.automation.human_behavior.wait_for_page_load(multiplier=2.0)
|
||||||
self.automation._take_screenshot("registration_form")
|
|
||||||
|
# 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
|
# Prüfe ob wir auf der Registrierungsseite sind
|
||||||
current_url = self.automation.browser.page.url
|
try:
|
||||||
if "/r.php" in current_url or "registration" in current_url:
|
current_url = self.automation.browser.page.url
|
||||||
logger.info("Registrierungsformular erfolgreich geöffnet")
|
if "/r.php" in current_url or "registration" in current_url:
|
||||||
return True
|
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")
|
logger.error("Konnte Registrierungsformular nicht öffnen")
|
||||||
return False
|
return False
|
||||||
@ -307,6 +336,7 @@ class FacebookRegistration:
|
|||||||
def _fill_registration_form(self, account_data: Dict[str, Any]) -> bool:
|
def _fill_registration_form(self, account_data: Dict[str, Any]) -> bool:
|
||||||
"""
|
"""
|
||||||
Füllt das Registrierungsformular aus.
|
Füllt das Registrierungsformular aus.
|
||||||
|
Verbesserte Version mit Fallback-Selektoren und robusterem Handling.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
account_data: Account-Daten
|
account_data: Account-Daten
|
||||||
@ -317,90 +347,234 @@ class FacebookRegistration:
|
|||||||
try:
|
try:
|
||||||
logger.info("Fülle Registrierungsformular aus")
|
logger.info("Fülle Registrierungsformular aus")
|
||||||
|
|
||||||
# Vorname
|
# Prüfe ob Browser noch aktiv ist
|
||||||
if not self.automation.ui_helper.type_text_human_like(
|
if not self.automation.browser or not self.automation.browser.page:
|
||||||
self.selectors.REG_FIRSTNAME_FIELD,
|
logger.error("Browser ist nicht mehr aktiv vor Formular-Ausfüllung")
|
||||||
account_data["first_name"]
|
|
||||||
):
|
|
||||||
logger.error("Fehler beim Eingeben des Vornamens")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Nachname
|
# Debug: Prüfe welche Input-Felder sichtbar sind
|
||||||
if not self.automation.ui_helper.type_text_human_like(
|
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,
|
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")
|
logger.error("Fehler beim Eingeben des Nachnamens")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Geburtsdatum
|
# Geburtsdatum - Verbesserte Dropdown-Behandlung
|
||||||
birth_date = account_data["birth_date"]
|
birth_date = account_data["birth_date"]
|
||||||
|
|
||||||
# Tag auswählen
|
# Tag auswählen mit Fallback
|
||||||
if not self.automation.browser.select_option(
|
day_selectors = [
|
||||||
self.selectors.REG_BIRTHDAY_DAY,
|
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")
|
logger.error("Fehler beim Auswählen des Geburtstags")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Monat auswählen
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
||||||
if not self.automation.browser.select_option(
|
|
||||||
|
# Monat auswählen mit Fallback
|
||||||
|
month_selectors = [
|
||||||
self.selectors.REG_BIRTHDAY_MONTH,
|
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")
|
logger.error("Fehler beim Auswählen des Geburtsmonats")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Jahr auswählen
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
||||||
if not self.automation.browser.select_option(
|
|
||||||
|
# Jahr auswählen mit Fallback
|
||||||
|
year_selectors = [
|
||||||
self.selectors.REG_BIRTHDAY_YEAR,
|
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")
|
logger.error("Fehler beim Auswählen des Geburtsjahrs")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.automation.human_behavior.random_delay(0.5, 1.0)
|
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"])
|
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']}")
|
logger.error(f"Fehler beim Auswählen des Geschlechts: {account_data['gender']}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.automation.human_behavior.random_delay(0.5, 1.0)
|
self.automation.human_behavior.random_delay(0.5, 1.0)
|
||||||
|
|
||||||
# E-Mail oder Telefonnummer
|
# E-Mail eingeben mit Fallback
|
||||||
contact_field = account_data.get("email") or account_data.get("phone_number")
|
email_field = account_data.get("email")
|
||||||
if not contact_field:
|
if not email_field:
|
||||||
logger.error("Keine E-Mail oder Telefonnummer angegeben")
|
logger.error("Keine E-Mail-Adresse angegeben")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.automation.ui_helper.type_text_human_like(
|
email_selectors = [
|
||||||
self.selectors.REG_EMAIL_OR_PHONE,
|
self.selectors.REG_EMAIL_OR_PHONE,
|
||||||
contact_field
|
self.selectors.REG_EMAIL_OR_PHONE_ALT,
|
||||||
):
|
"input[placeholder*='E-Mail']",
|
||||||
logger.error("Fehler beim Eingeben der Kontaktdaten")
|
"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
|
return False
|
||||||
|
|
||||||
# Warte auf mögliches E-Mail-Bestätigungsfeld
|
# Warte auf mögliches E-Mail-Bestätigungsfeld
|
||||||
self.automation.human_behavior.random_delay(1.0, 2.0)
|
self.automation.human_behavior.random_delay(1.0, 2.0)
|
||||||
|
|
||||||
# Wenn E-Mail eingegeben wurde, könnte ein Bestätigungsfeld erscheinen
|
# E-Mail-Bestätigung falls erforderlich
|
||||||
if account_data.get("email"):
|
if self.automation.browser.is_element_visible(self.selectors.REG_EMAIL_CONFIRM, timeout=2000):
|
||||||
if self.automation.browser.is_element_visible(self.selectors.REG_EMAIL_CONFIRM, timeout=2000):
|
logger.info("E-Mail-Bestätigungsfeld erkannt")
|
||||||
logger.info("E-Mail-Bestätigungsfeld erkannt")
|
if not self.automation.ui_helper.type_text_human_like(
|
||||||
if not self.automation.ui_helper.type_text_human_like(
|
self.selectors.REG_EMAIL_CONFIRM,
|
||||||
self.selectors.REG_EMAIL_CONFIRM,
|
email_field
|
||||||
account_data["email"]
|
):
|
||||||
):
|
logger.warning("Fehler beim Bestätigen der E-Mail")
|
||||||
logger.warning("Fehler beim Bestätigen der E-Mail")
|
|
||||||
|
|
||||||
# Passwort
|
# Passwort mit Fallback
|
||||||
if not self.automation.ui_helper.type_text_human_like(
|
password_selectors = [
|
||||||
self.selectors.REG_PASSWORD,
|
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")
|
logger.error("Fehler beim Eingeben des Passworts")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -522,30 +696,68 @@ class FacebookRegistration:
|
|||||||
def _enter_verification_code(self, code: str) -> bool:
|
def _enter_verification_code(self, code: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Gibt den Verifikationscode ein.
|
Gibt den Verifikationscode ein.
|
||||||
|
Verbesserte Version für 5-stelligen Code mit Fallback-Selektoren.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code: Verifikationscode
|
code: Verifikationscode (5-stellig, nur Zahlen)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True bei Erfolg
|
bool: True bei Erfolg
|
||||||
"""
|
"""
|
||||||
try:
|
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}")
|
logger.info(f"Gebe Verifikationscode ein: {code}")
|
||||||
|
|
||||||
# Code eingeben
|
# Versuche verschiedene Selektoren für das Code-Eingabefeld
|
||||||
if not self.automation.ui_helper.type_text_human_like(
|
code_selectors = [
|
||||||
self.selectors.VERIFICATION_CODE_INPUT,
|
self.selectors.VERIFICATION_CODE_INPUT, # input#code_in_cliff
|
||||||
code
|
self.selectors.VERIFICATION_CODE_INPUT_ALT, # input[name='code']
|
||||||
):
|
self.selectors.VERIFICATION_CODE_INPUT_ALT2, # input[maxlength='5']
|
||||||
logger.error("Fehler beim Eingeben des Verifikationscodes")
|
"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
|
return False
|
||||||
|
|
||||||
|
self.automation.human_behavior.random_delay(0.5, 1.0)
|
||||||
|
|
||||||
# Weiter-Button klicken
|
# Weiter-Button klicken
|
||||||
continue_clicked = False
|
continue_clicked = False
|
||||||
|
|
||||||
# Versuche verschiedene Selektoren
|
# Versuche verschiedene Button-Selektoren
|
||||||
for text in self.selectors.get_button_texts("continue"):
|
continue_selectors = [
|
||||||
selector = f"button:has-text('{text}')"
|
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:
|
try:
|
||||||
if self.automation.browser.click_element(selector, timeout=1000):
|
if self.automation.browser.click_element(selector, timeout=1000):
|
||||||
logger.info(f"Verifikation fortgesetzt mit: {selector}")
|
logger.info(f"Verifikation fortgesetzt mit: {selector}")
|
||||||
@ -554,18 +766,35 @@ class FacebookRegistration:
|
|||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Oder Enter drücken
|
# Fallback: Enter drücken
|
||||||
if not continue_clicked:
|
if not continue_clicked:
|
||||||
self.automation.browser.page.keyboard.press("Enter")
|
self.automation.browser.page.keyboard.press("Enter")
|
||||||
logger.info("Verifikation mit Enter fortgesetzt")
|
logger.info("Verifikation mit Enter fortgesetzt")
|
||||||
|
continue_clicked = True
|
||||||
|
|
||||||
# Warte auf Navigation
|
if not continue_clicked:
|
||||||
self.automation.human_behavior.wait_for_page_load()
|
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)
|
# Prüfe auf OK-Button (Popup nach erfolgreicher Verifikation)
|
||||||
if self.automation.browser.is_element_visible(self.selectors.VERIFICATION_OK_BUTTON, timeout=3000):
|
ok_selectors = [
|
||||||
self.automation.browser.click_element(self.selectors.VERIFICATION_OK_BUTTON)
|
self.selectors.VERIFICATION_OK_BUTTON,
|
||||||
logger.info("OK-Button nach Verifikation geklickt")
|
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
|
return True
|
||||||
|
|
||||||
|
|||||||
@ -29,27 +29,34 @@ class FacebookSelectors:
|
|||||||
LOGIN_BUTTON_ALT = "button[data-testid='royal_login_button']"
|
LOGIN_BUTTON_ALT = "button[data-testid='royal_login_button']"
|
||||||
|
|
||||||
# ===== REGISTRATION FORM =====
|
# ===== REGISTRATION FORM =====
|
||||||
# Name fields
|
# Name fields - Updated based on actual HTML
|
||||||
REG_FIRSTNAME_FIELD = "input[name='firstname']"
|
REG_FIRSTNAME_FIELD = "input[name='firstname']"
|
||||||
|
REG_FIRSTNAME_FIELD_ALT = "input[aria-label='Vorname']"
|
||||||
REG_LASTNAME_FIELD = "input[name='lastname']"
|
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 = "select[name='birthday_day']"
|
||||||
|
REG_BIRTHDAY_DAY_ALT = "select#day"
|
||||||
REG_BIRTHDAY_MONTH = "select[name='birthday_month']"
|
REG_BIRTHDAY_MONTH = "select[name='birthday_month']"
|
||||||
|
REG_BIRTHDAY_MONTH_ALT = "select#month"
|
||||||
REG_BIRTHDAY_YEAR = "select[name='birthday_year']"
|
REG_BIRTHDAY_YEAR = "select[name='birthday_year']"
|
||||||
|
REG_BIRTHDAY_YEAR_ALT = "select#year"
|
||||||
|
|
||||||
# Gender radio buttons
|
# Gender radio buttons - Updated with correct values
|
||||||
REG_GENDER_FEMALE = "input[name='sex'][value='1']"
|
REG_GENDER_FEMALE = "input[name='sex'][value='1']" # Weiblich
|
||||||
REG_GENDER_MALE = "input[name='sex'][value='2']"
|
REG_GENDER_MALE = "input[name='sex'][value='2']" # Männlich
|
||||||
REG_GENDER_CUSTOM = "input[name='sex'][value='-1']"
|
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 = "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
|
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 = "input[name='reg_passwd__']"
|
||||||
REG_PASSWORD_ALT = "input#password_step_input"
|
REG_PASSWORD_ALT = "input#password_step_input"
|
||||||
|
REG_PASSWORD_ALT2 = "input[aria-label='Neues Passwort']"
|
||||||
|
|
||||||
# Submit button
|
# Submit button
|
||||||
REG_SUBMIT_BUTTON = "button[name='websubmit']"
|
REG_SUBMIT_BUTTON = "button[name='websubmit']"
|
||||||
@ -57,8 +64,10 @@ class FacebookSelectors:
|
|||||||
REG_SUBMIT_BUTTON_EN = "button:has-text('Sign Up')"
|
REG_SUBMIT_BUTTON_EN = "button:has-text('Sign Up')"
|
||||||
|
|
||||||
# ===== EMAIL VERIFICATION =====
|
# ===== EMAIL VERIFICATION =====
|
||||||
|
# Updated for 5-digit code based on actual HTML
|
||||||
VERIFICATION_CODE_INPUT = "input#code_in_cliff"
|
VERIFICATION_CODE_INPUT = "input#code_in_cliff"
|
||||||
VERIFICATION_CODE_INPUT_ALT = "input[name='code']"
|
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 = "button:has-text('Weiter')"
|
||||||
VERIFICATION_CONTINUE_BUTTON_EN = "button:has-text('Continue')"
|
VERIFICATION_CONTINUE_BUTTON_EN = "button:has-text('Continue')"
|
||||||
|
|
||||||
|
|||||||
@ -27,8 +27,9 @@ class ForgeAnimationDialog(QDialog):
|
|||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
"""Initialisiert die UI mit verbessertem Design"""
|
"""Initialisiert die UI mit verbessertem Design"""
|
||||||
# Nur Dialog im Vordergrund, nicht das ganze Hauptfenster
|
# Dialog-Fenster ohne Stay-on-Top, um Browser nicht zu blockieren
|
||||||
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
|
# 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.setModal(False) # Nicht modal - blockiert nicht das Hauptfenster
|
||||||
self.setFixedSize(650, 600) # Ursprüngliche Größe beibehalten
|
self.setFixedSize(650, 600) # Ursprüngliche Größe beibehalten
|
||||||
|
|
||||||
@ -186,7 +187,8 @@ class ForgeAnimationDialog(QDialog):
|
|||||||
def start_animation(self):
|
def start_animation(self):
|
||||||
"""Zeigt den Dialog an"""
|
"""Zeigt den Dialog an"""
|
||||||
self.status_label.setText("Initialisiere...")
|
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):
|
def stop_animation(self):
|
||||||
"""Stoppt die Animation und den Timer"""
|
"""Stoppt die Animation und den Timer"""
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren