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