Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,207 @@
"""
Controller für die Verwaltung von Accounts.
"""
import logging
import csv
from datetime import datetime
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QObject
from application.use_cases.export_accounts_use_case import ExportAccountsUseCase
logger = logging.getLogger("account_controller")
class AccountController(QObject):
"""Controller für die Verwaltung von Accounts."""
def __init__(self, db_manager):
super().__init__()
self.db_manager = db_manager
self.parent_view = None
self.export_use_case = ExportAccountsUseCase(db_manager)
# Import Fingerprint Generator
from application.use_cases.generate_account_fingerprint_use_case import GenerateAccountFingerprintUseCase
self.fingerprint_generator = GenerateAccountFingerprintUseCase(db_manager)
def set_parent_view(self, view):
"""Setzt die übergeordnete View für Dialoge."""
self.parent_view = view
def on_account_created(self, platform: str, account_data: dict):
"""Wird aufgerufen, wenn ein Account erstellt wurde."""
account = {
"platform": platform.lower(),
"username": account_data.get("username", ""),
"password": account_data.get("password", ""),
"email": account_data.get("email", ""),
"phone": account_data.get("phone", ""),
"full_name": account_data.get("full_name", ""),
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# Account zur Datenbank hinzufügen
account_id = self.db_manager.add_account(account)
logger.info(f"Account in Datenbank gespeichert: {account['username']} (ID: {account_id})")
# Fingerprint für neuen Account generieren
if account_id and account_id > 0:
logger.info(f"Generiere Fingerprint für neuen Account {account_id}")
fingerprint_id = self.fingerprint_generator.execute(account_id)
if fingerprint_id:
logger.info(f"Fingerprint {fingerprint_id} wurde Account {account_id} zugewiesen")
else:
logger.warning(f"Konnte keinen Fingerprint für Account {account_id} generieren")
# Erfolgsmeldung anzeigen
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Erfolg",
f"Account erfolgreich erstellt!\n\nBenutzername: {account['username']}\nPasswort: {account['password']}\nE-Mail/Telefon: {account['email'] or account['phone']}"
)
def load_accounts(self, platform=None):
"""Lädt Accounts aus der Datenbank."""
try:
if platform and hasattr(self.db_manager, "get_accounts_by_platform"):
accounts = self.db_manager.get_accounts_by_platform(platform.lower())
else:
accounts = self.db_manager.get_all_accounts()
if platform:
accounts = [acc for acc in accounts if acc.get("platform", "").lower() == platform.lower()]
return accounts
except Exception as e:
logger.error(f"Fehler beim Laden der Accounts: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler beim Laden der Accounts:\n{str(e)}"
)
return []
def export_accounts(self, platform=None, accounts_to_export=None):
"""Exportiert Accounts in eine CSV-Datei."""
parent = self.parent_view or None
# Dialog für Format-Auswahl
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QRadioButton, QPushButton, QCheckBox, QDialogButtonBox
dialog = QDialog(parent)
dialog.setWindowTitle("Export-Optionen")
dialog.setMinimumWidth(300)
layout = QVBoxLayout(dialog)
# Format-Auswahl
csv_radio = QRadioButton("CSV Format (Excel-kompatibel)")
csv_radio.setChecked(True)
json_radio = QRadioButton("JSON Format")
layout.addWidget(csv_radio)
layout.addWidget(json_radio)
# Passwort-Option
include_passwords = QCheckBox("Passwörter einschließen")
include_passwords.setChecked(True)
layout.addWidget(include_passwords)
# Buttons
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addWidget(buttons)
if dialog.exec_() != QDialog.Accepted:
return
# Format bestimmen
format = 'csv' if csv_radio.isChecked() else 'json'
file_extension = '*.csv' if format == 'csv' else '*.json'
file_filter = f"{format.upper()}-Dateien ({file_extension});;Alle Dateien (*)"
# Dateiname vorschlagen
suggested_filename = self.export_use_case.get_export_filename(platform, format)
file_path, _ = QFileDialog.getSaveFileName(
parent,
"Konten exportieren",
suggested_filename,
file_filter
)
if not file_path:
return
try:
# Export durchführen mit Use Case
if accounts_to_export:
# Wenn spezifische Accounts übergeben wurden
export_data = self.export_use_case.execute_with_accounts(
accounts=accounts_to_export,
format=format,
include_passwords=include_passwords.isChecked()
)
else:
# Standard-Export basierend auf Platform
export_data = self.export_use_case.execute(
platform=platform,
format=format,
include_passwords=include_passwords.isChecked()
)
if not export_data:
QMessageBox.warning(
parent,
"Keine Daten",
"Es wurden keine Accounts zum Exportieren gefunden."
)
return
# Datei schreiben
with open(file_path, "wb") as f:
f.write(export_data)
logger.info(f"Accounts erfolgreich nach {file_path} exportiert")
if parent:
QMessageBox.information(
parent,
"Export erfolgreich",
f"Konten wurden erfolgreich nach {file_path} exportiert."
)
except Exception as e:
logger.error(f"Fehler beim Exportieren der Accounts: {e}")
if parent:
QMessageBox.critical(
parent,
"Export fehlgeschlagen",
f"Fehler beim Exportieren der Konten:\n{str(e)}"
)
def delete_account(self, account_id):
"""Löscht einen Account aus der Datenbank."""
try:
success = self.db_manager.delete_account(account_id)
if not success:
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Konto mit ID {account_id} konnte nicht gelöscht werden."
)
return success
except Exception as e:
logger.error(f"Fehler beim Löschen des Accounts: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler beim Löschen des Kontos:\n{str(e)}"
)
return False

402
controllers/main_controller.py Normale Datei
Datei anzeigen

@ -0,0 +1,402 @@
"""
Hauptcontroller für die Social Media Account Generator Anwendung.
"""
import logging
import sys
from PyQt5.QtWidgets import QMessageBox, QApplication
from views.main_window import MainWindow
from views.dialogs.license_activation_dialog import LicenseActivationDialog
from controllers.platform_controllers.instagram_controller import InstagramController
from controllers.platform_controllers.tiktok_controller import TikTokController
from controllers.account_controller import AccountController
from controllers.settings_controller import SettingsController
from controllers.session_controller import SessionController
from database.db_manager import DatabaseManager
from utils.proxy_rotator import ProxyRotator
from utils.email_handler import EmailHandler
from utils.theme_manager import ThemeManager
from localization.language_manager import LanguageManager
from licensing.license_manager import LicenseManager
from updates.update_checker import UpdateChecker
logger = logging.getLogger("main")
class MainController:
"""Hauptcontroller, der die Anwendung koordiniert."""
def __init__(self, app):
# QApplication Referenz speichern
self.app = app
# Theme Manager initialisieren
self.theme_manager = ThemeManager(app)
# Language Manager initialisieren
self.language_manager = LanguageManager(app)
# Lizenz Manager als erstes initialisieren
self.license_manager = LicenseManager()
# Lizenz prüfen bevor andere Komponenten geladen werden
if not self._check_and_activate_license():
logger.error("Keine gültige Lizenz - Anwendung wird beendet")
sys.exit(1)
# Modelle initialisieren
self.db_manager = DatabaseManager()
self.proxy_rotator = ProxyRotator()
self.email_handler = EmailHandler()
self.update_checker = UpdateChecker(self.license_manager.api_client)
# Haupt-View erstellen
self.view = MainWindow(self.theme_manager, self.language_manager, self.db_manager)
# Untercontroller erstellen
self.account_controller = AccountController(self.db_manager)
self.account_controller.set_parent_view(self.view)
self.settings_controller = SettingsController(
self.proxy_rotator,
self.email_handler,
self.license_manager
)
self.session_controller = SessionController(self.db_manager)
# Plattform-Controller initialisieren
self.platform_controllers = {}
# Instagram Controller hinzufügen
instagram_controller = InstagramController(
self.db_manager,
self.proxy_rotator,
self.email_handler,
self.language_manager
)
# Signal für Rückkehr zur Hauptseite verbinden
instagram_controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset()
# SessionController referenz hinzufügen
instagram_controller.session_controller = self.session_controller
self.platform_controllers["instagram"] = instagram_controller
# TikTok Controller hinzufügen
tiktok_controller = TikTokController(
self.db_manager,
self.proxy_rotator,
self.email_handler,
self.language_manager
)
# Signal für Rückkehr zur Hauptseite verbinden
tiktok_controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset()
# SessionController referenz hinzufügen
tiktok_controller.session_controller = self.session_controller
self.platform_controllers["tiktok"] = tiktok_controller
# X (Twitter) Controller hinzufügen
from controllers.platform_controllers.x_controller import XController
x_controller = XController(
self.db_manager,
self.proxy_rotator,
self.email_handler,
self.language_manager
)
# Signal für Rückkehr zur Hauptseite verbinden
x_controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset()
# SessionController referenz hinzufügen
x_controller.session_controller = self.session_controller
self.platform_controllers["x"] = x_controller
# Gmail Controller hinzufügen
from controllers.platform_controllers.gmail_controller import GmailController
gmail_controller = GmailController(
self.db_manager,
self.proxy_rotator,
self.email_handler,
self.language_manager
)
# Signal für Rückkehr zur Hauptseite verbinden
gmail_controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset()
# SessionController referenz hinzufügen
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(...)
# Signals verbinden
self.connect_signals()
# Platform Selector Signal-Verbindungen
if hasattr(self.view.platform_selector, 'export_requested'):
self.view.platform_selector.export_requested.connect(
lambda accounts: self.account_controller.export_accounts(None, accounts)
)
if hasattr(self.view.platform_selector, 'login_requested'):
self.view.platform_selector.login_requested.connect(
self.session_controller.perform_one_click_login
)
# Session-Status-Update Signal entfernt (Session-Funktionalität deaktiviert)
# Login-Result Signals verbinden
self.session_controller.login_successful.connect(self._on_login_successful)
self.session_controller.login_failed.connect(self._on_login_failed)
# Session starten
self._start_license_session()
# Auf Updates prüfen
self.check_for_updates()
# Hauptfenster anzeigen
self.view.show()
def _on_login_successful(self, account_id: str, session_data: dict):
"""Behandelt erfolgreiches Login"""
# GEÄNDERT: Kein Popup mehr - User sieht Erfolg direkt im Browser
try:
account = self.db_manager.get_account(int(account_id))
username = account.get('username', 'Unknown') if account else 'Unknown'
platform = account.get('platform', 'Unknown') if account else 'Unknown'
logger.info(f"Login erfolgreich für Account {account_id} ({username}) - Browser bleibt offen")
# Popup entfernt - User hat direktes Feedback über Browser-Status
except Exception as e:
print(f"Error showing success message: {e}")
def _on_login_failed(self, account_id: str, error_message: str):
"""Behandelt fehlgeschlagenes Login"""
from PyQt5.QtWidgets import QMessageBox
# Account-Details für die Nachricht holen
try:
account = self.db_manager.get_account(int(account_id))
username = account.get('username', 'Unknown') if account else 'Unknown'
msg = QMessageBox(self.view)
msg.setIcon(QMessageBox.Warning)
msg.setWindowTitle("Login Fehlgeschlagen")
msg.setText(f"Ein-Klick-Login fehlgeschlagen!")
msg.setInformativeText(f"Account: {username}\nFehler: {error_message}")
msg.setStandardButtons(QMessageBox.Ok)
msg.exec_()
except Exception as e:
print(f"Error showing failure message: {e}")
def connect_signals(self):
"""Verbindet alle Signale mit den entsprechenden Slots."""
# Plattformauswahl-Signal verbinden
self.view.platform_selected.connect(self.on_platform_selected)
# Zurück-Button verbinden
self.view.back_to_selector_requested.connect(self.show_platform_selector)
# Theme-Toggle verbinden
self.view.theme_toggled.connect(self.on_theme_toggled)
def on_platform_selected(self, platform: str):
"""Wird aufgerufen, wenn eine Plattform ausgewählt wird."""
logger.info(f"Plattform ausgewählt: {platform}")
# Aktuelle Plattform setzen
self.current_platform = platform.lower()
# Prüfen, ob die Plattform unterstützt wird
if self.current_platform not in self.platform_controllers:
logger.error(f"Plattform '{platform}' wird nicht unterstützt")
QMessageBox.critical(
self.view,
"Nicht unterstützt",
f"Die Plattform '{platform}' ist noch nicht implementiert."
)
return
# Plattformspezifischen Controller abrufen
platform_controller = self.platform_controllers.get(self.current_platform)
# Plattform-View initialisieren
self.view.init_platform_ui(platform, platform_controller)
# Tab-Hooks verbinden
self.connect_tab_hooks(platform_controller)
# Plattformspezifische Ansicht anzeigen
self.view.show_platform_ui()
def on_theme_toggled(self):
"""Wird aufgerufen, wenn das Theme gewechselt wird."""
if self.theme_manager:
theme_name = self.theme_manager.get_current_theme()
logger.info(f"Theme gewechselt zu: {theme_name}")
# Hier kann zusätzliche Logik für Theme-Wechsel hinzugefügt werden
# z.B. UI-Elemente aktualisieren, die nicht automatisch aktualisiert werden
def connect_tab_hooks(self, platform_controller):
"""Verbindet die Tab-Hooks mit dem Plattform-Controller."""
# Generator-Tab-Hooks
# HINWEIS: account_created Signal ist nicht mehr verbunden, da Accounts
# jetzt über SessionController mit Clean Architecture gespeichert werden
if hasattr(platform_controller, "get_generator_tab"):
generator_tab = platform_controller.get_generator_tab()
# generator_tab.account_created.connect(self.account_controller.on_account_created) # Deaktiviert
# Einstellungen-Tab-Hooks
if hasattr(platform_controller, "get_settings_tab"):
settings_tab = platform_controller.get_settings_tab()
settings_tab.proxy_settings_saved.connect(self.settings_controller.save_proxy_settings)
settings_tab.proxy_tested.connect(self.settings_controller.test_proxy)
settings_tab.email_settings_saved.connect(self.settings_controller.save_email_settings)
settings_tab.email_tested.connect(self.settings_controller.test_email)
settings_tab.license_activated.connect(self.settings_controller.activate_license)
def show_platform_selector(self):
"""Zeigt den Plattform-Selektor an."""
logger.info("Zurück zur Plattformauswahl")
self.view.show_platform_selector()
if hasattr(self.view, "platform_selector"):
self.view.platform_selector.load_accounts()
def show_platform_selector_and_reset(self):
"""Zeigt den Plattform-Selektor an und setzt die Eingabefelder zurück."""
logger.info("Zurück zur Plattformauswahl mit Reset der Eingabefelder")
# Eingabefelder des aktuellen Platform-Controllers zurücksetzen
if hasattr(self, 'current_platform') and self.current_platform in self.platform_controllers:
controller = self.platform_controllers[self.current_platform]
if hasattr(controller, '_generator_tab') and controller._generator_tab:
# Tab auf None setzen, damit beim nächsten Öffnen ein neuer erstellt wird
controller._generator_tab = None
# Zur Plattformauswahl zurückkehren
self.show_platform_selector()
def _check_and_activate_license(self):
"""
Prüft die Lizenz und zeigt den Aktivierungsdialog wenn nötig.
Returns:
True wenn Lizenz gültig, False wenn Benutzer abbricht
"""
# Versuche Session fortzusetzen
if self.license_manager.resume_session():
logger.info("Bestehende Session fortgesetzt")
return True
# Prüfe ob Lizenz vorhanden ist
if self.license_manager.is_licensed():
# Starte neue Session
if self.license_manager.start_session():
logger.info("Neue Session gestartet")
return True
else:
logger.error("Session konnte nicht gestartet werden")
# Zeige Fehlermeldung statt Aktivierungsdialog
# Hole detaillierte Fehlermeldung
session_result = self.license_manager.session_manager.start_session(
self.license_manager.license_data["key"],
self.license_manager.license_data.get("activation_id")
)
error_msg = session_result.get("error", "Unbekannter Fehler")
QMessageBox.critical(
None,
"Session-Fehler",
f"Die Lizenz ist gültig, aber es konnte keine Session gestartet werden.\n\n"
f"Grund: {error_msg}\n\n"
"Mögliche Lösungen:\n"
"- Schließen Sie andere laufende Instanzen\n"
"- Warten Sie einen Moment und versuchen Sie es erneut\n"
"- Kontaktieren Sie den Support",
QMessageBox.Ok
)
return False
# Keine gültige Lizenz - zeige Aktivierungsdialog
logger.info("Keine gültige Lizenz gefunden - zeige Aktivierungsdialog")
dialog = LicenseActivationDialog(self.license_manager)
dialog.activation_successful.connect(self._on_license_activated)
result = dialog.exec_()
return result == dialog.Accepted
def _on_license_activated(self):
"""Wird aufgerufen wenn Lizenz erfolgreich aktiviert wurde."""
logger.info("Lizenz wurde erfolgreich aktiviert")
def _start_license_session(self):
"""Startet die Lizenz-Session für die laufende Anwendung."""
if not self.license_manager.session_manager.is_session_active():
if self.license_manager.is_licensed():
self.license_manager.start_session()
def check_license(self):
"""Überprüft den Lizenzstatus (für UI Updates)."""
is_licensed = self.license_manager.is_licensed()
license_info = self.license_manager.get_license_info()
status_text = self.license_manager.get_status_text()
# UI kann hier aktualisiert werden basierend auf Lizenzstatus
logger.info(f"Lizenzstatus: {status_text}")
return is_licensed
def check_for_updates(self):
"""Prüft auf Updates."""
try:
# Mit Lizenzschlüssel prüfen wenn vorhanden
license_key = self.license_manager.get_license_info().get("key")
update_info = self.update_checker.check_for_updates(license_key=license_key)
if update_info["has_update"]:
reply = QMessageBox.question(
self.view,
"Update verfügbar",
f"Eine neue Version ist verfügbar: {update_info['latest_version']}\n"
f"(Aktuelle Version: {update_info['current_version']})\n\n"
f"Release-Datum: {update_info['release_date']}\n"
f"Release-Notes:\n{update_info['release_notes']}\n\n"
"Möchten Sie das Update jetzt herunterladen?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
self.download_update(update_info)
except Exception as e:
logger.error(f"Fehler bei der Update-Prüfung: {e}")
def download_update(self, update_info):
"""Lädt ein Update herunter."""
try:
download_result = self.update_checker.download_update(
update_info["download_url"],
update_info["latest_version"]
)
if download_result["success"]:
QMessageBox.information(
self.view,
"Download erfolgreich",
f"Update wurde heruntergeladen: {download_result['file_path']}\n\n"
"Bitte schließen Sie die Anwendung und führen Sie das Update aus."
)
else:
QMessageBox.warning(
self.view,
"Download fehlgeschlagen",
f"Fehler beim Herunterladen des Updates:\n{download_result['error']}"
)
except Exception as e:
logger.error(f"Fehler beim Herunterladen des Updates: {e}")
QMessageBox.critical(
self.view,
"Fehler",
f"Fehler beim Herunterladen des Updates:\n{str(e)}"
)

Datei anzeigen

@ -0,0 +1,265 @@
"""
Basis-Controller für Plattform-spezifische Funktionalität.
"""
import logging
from PyQt5.QtCore import QObject
from typing import Dict, Any, Optional, Tuple
import random
from views.tabs.generator_tab import GeneratorTab
from views.tabs.accounts_tab import AccountsTab
# SettingsTab import entfernt - wird nicht mehr verwendet
class BasePlatformController(QObject):
"""Basis-Controller-Klasse für Plattformspezifische Logik."""
# Konstanten auf Klassen-Ebene
MOBILE_PROBABILITY = {
'instagram': 0.7,
'tiktok': 0.9,
'facebook': 0.5
}
MIN_AGE = 13
DEFAULT_EMAIL_DOMAIN = "z5m7q9dk3ah2v1plx6ju.com"
def __init__(self, platform_name, db_manager, proxy_rotator, email_handler, language_manager=None):
super().__init__()
self.platform_name = platform_name
self.logger = logging.getLogger(f"{platform_name.lower()}_controller")
# Modelle
self.db_manager = db_manager
self.proxy_rotator = proxy_rotator
self.email_handler = email_handler
self.language_manager = language_manager
# Tabs
self._generator_tab = None
self._accounts_tab = None
# Worker Thread
self.worker_thread = None
# Optional: Session Controller (Clean Architecture)
self.session_controller = None
# Optional: Forge Dialog
self.forge_dialog = None
# Plattformspezifische Initialisierungen
self.init_platform()
def set_tabs(self, generator_tab, accounts_tab):
"""
Setzt die Tab-Referenzen.
Args:
generator_tab: Generator-Tab
accounts_tab: Accounts-Tab
"""
self._generator_tab = generator_tab
self._accounts_tab = accounts_tab
def init_platform(self):
"""
Initialisiert plattformspezifische Komponenten.
Diese Methode sollte von Unterklassen überschrieben werden.
"""
pass
def get_generator_tab(self):
"""Gibt den Generator-Tab zurück oder erstellt ihn bei Bedarf."""
if not self._generator_tab:
self._generator_tab = self.create_generator_tab()
return self._generator_tab
def get_accounts_tab(self):
"""Gibt den Accounts-Tab zurück oder erstellt ihn bei Bedarf."""
if not self._accounts_tab:
self._accounts_tab = self.create_accounts_tab()
return self._accounts_tab
# get_settings_tab Methode entfernt - Settings-Tab wird nicht mehr verwendet
def create_generator_tab(self):
"""
Erstellt den Generator-Tab.
Diese Methode sollte von Unterklassen überschrieben werden.
"""
return GeneratorTab(self.platform_name, self.language_manager)
def create_accounts_tab(self):
"""
Erstellt den Accounts-Tab.
Diese Methode sollte von Unterklassen überschrieben werden.
"""
return AccountsTab(self.platform_name, self.db_manager, self.language_manager)
# create_settings_tab Methode entfernt - Settings-Tab wird nicht mehr verwendet
def start_account_creation(self, params):
"""
Startet die Account-Erstellung.
Diese Methode sollte von Unterklassen überschrieben werden.
Args:
params: Parameter für die Account-Erstellung
"""
self.logger.info(f"Account-Erstellung für {self.platform_name} gestartet")
# In Unterklassen implementieren
def validate_inputs(self, inputs):
"""
Validiert die Eingaben für die Account-Erstellung.
Args:
inputs: Eingaben für die Account-Erstellung
Returns:
(bool, str): (Ist gültig, Fehlermeldung falls nicht gültig)
"""
# Basis-Validierungen
if not inputs.get("full_name"):
return False, "Bitte geben Sie einen vollständigen Namen ein."
# Alter prüfen
age_text = inputs.get("age_text", "")
if not age_text:
return False, "Bitte geben Sie ein Alter ein."
# Alter muss eine Zahl sein
try:
age = int(age_text)
inputs["age"] = age # Füge das konvertierte Alter zu den Parametern hinzu
except ValueError:
return False, "Das Alter muss eine ganze Zahl sein."
# Alter-Bereich prüfen
if age < 13 or age > 99:
return False, "Das Alter muss zwischen 13 und 99 liegen."
# Telefonnummer prüfen, falls erforderlich
if inputs.get("registration_method") == "phone" and not inputs.get("phone_number"):
return False, "Telefonnummer erforderlich für Registrierung via Telefon."
return True, ""
def _determine_profile_type(self) -> str:
"""Bestimmt den Profil-Typ basierend auf Platform-Wahrscheinlichkeiten"""
mobile_prob = self.MOBILE_PROBABILITY.get(self.platform_name.lower(), 0.5)
return 'mobile' if random.random() < mobile_prob else 'desktop'
def _generate_fingerprint_for_platform(self) -> Optional[Any]:
"""Generiert Fingerprint mit Fehlerbehandlung"""
try:
if not hasattr(self, 'session_controller') or not self.session_controller:
return None
profile_type = self._determine_profile_type()
# Prüfe ob fingerprint_service existiert
if hasattr(self.session_controller, 'fingerprint_service'):
return self.session_controller.fingerprint_service.generate_fingerprint(
profile_type=profile_type,
platform=self.platform_name.lower()
)
except Exception as e:
self.logger.warning(f"Fingerprint-Generierung fehlgeschlagen: {e}")
return None
def _setup_ui_for_creation(self):
"""Bereitet die UI für die Account-Erstellung vor"""
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
def _connect_worker_signals(self):
"""Verbindet Worker-Signals mit UI-Elementen"""
if not self.worker_thread:
return
generator_tab = self.get_generator_tab()
# Forge-Dialog Signals
if self.forge_dialog:
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Generator-Tab Signals (Backup)
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
# Error und Finished Handling
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
def _show_forge_dialog(self):
"""Zeigt den Forge-Animation Dialog"""
try:
from views.widgets.forge_animation_widget import ForgeAnimationDialog
generator_tab = self.get_generator_tab()
parent_widget = generator_tab.window()
self.forge_dialog = ForgeAnimationDialog(parent_widget)
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
self.forge_dialog.start_animation()
self.forge_dialog.show()
except Exception as e:
self.logger.warning(f"Konnte Forge-Dialog nicht anzeigen: {e}")
def _handle_error(self, error_msg: str):
"""Gemeinsame Fehlerbehandlung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Fehler anzeigen
generator_tab = self.get_generator_tab()
generator_tab.show_error(error_msg)
generator_tab.set_running(False)
def _handle_finished(self, result: dict):
"""Gemeinsame Behandlung bei Abschluss"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Normale Verarbeitung
self.handle_account_created(result)
def stop_account_creation(self):
"""Stoppt die Account-Erstellung"""
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
generator_tab = self.get_generator_tab()
generator_tab.add_log(f"{self.platform_name}-Account-Erstellung wurde abgebrochen")
generator_tab.set_running(False)
generator_tab.set_progress(0)
# Forge-Dialog schließen falls vorhanden
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
def handle_account_created(self, result):
"""
Verarbeitet erfolgreich erstellte Accounts.
Sollte von Unterklassen überschrieben werden für spezifische Logik.
"""
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
# Standard-Implementierung - kann von Unterklassen erweitert werden
self.logger.info(f"Account erfolgreich erstellt für {self.platform_name}")

Datei anzeigen

@ -0,0 +1,217 @@
"""
Basis-Klasse für alle Platform Worker Threads zur Eliminierung von Code-Duplikation
"""
from abc import ABC, abstractmethod
from PyQt5.QtCore import QThread, pyqtSignal
from typing import Dict, Any, Optional
from utils.text_similarity import TextSimilarity
from domain.value_objects.browser_protection_style import BrowserProtectionStyle, ProtectionLevel
import traceback
class BaseAccountCreationWorkerThread(QThread):
"""Basis-Klasse für alle Platform Worker Threads"""
# Signals MÜSSEN identisch zu bestehenden sein
update_signal = pyqtSignal(str)
log_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int)
finished_signal = pyqtSignal(dict)
error_signal = pyqtSignal(str)
def __init__(self, params: Dict[str, Any], platform_name: str,
session_controller: Optional[Any] = None,
generator_tab: Optional[Any] = None):
super().__init__()
self.params = params
self.platform_name = platform_name
self.session_controller = session_controller
self.generator_tab = generator_tab
self.running = True
# TextSimilarity für robustes Fehler-Matching
self.text_similarity = TextSimilarity(default_threshold=0.8)
# Platform-spezifische Error-Patterns (überschreibbar)
self.error_interpretations = self.get_error_interpretations()
@abstractmethod
def get_automation_class(self):
"""Muss von Subklassen implementiert werden"""
pass
@abstractmethod
def get_error_interpretations(self) -> Dict[str, str]:
"""Platform-spezifische Fehlerinterpretationen"""
pass
def run(self):
"""Gemeinsame Logik für Account-Erstellung - IDENTISCH zum Original"""
try:
self.update_signal.emit("Status: Initialisierung...")
self.log_signal.emit(f"{self.platform_name}-Account-Erstellung gestartet...")
self.progress_signal.emit(10)
# Automation-Klasse dynamisch laden
AutomationClass = self.get_automation_class()
# WICHTIG: Exakt gleiche Parameter wie im Original
# Prüfe ob die Automation-Klasse die neuen Parameter unterstützt
automation_params = {
# Unterstütze beide Parameter-Namen für Abwärtskompatibilität
"headless": self.params.get("headless", not self.params.get("show_browser", False)),
"proxy_type": self.params.get("proxy_type", "NoProxy")
}
# Optionale Parameter nur hinzufügen wenn unterstützt
import inspect
init_signature = inspect.signature(AutomationClass.__init__)
param_names = list(init_signature.parameters.keys())
if "fingerprint" in param_names:
automation_params["fingerprint"] = self.params.get("fingerprint")
if "imap_handler" in param_names:
automation_params["imap_handler"] = self.params.get("imap_handler")
if "phone_service" in param_names:
automation_params["phone_service"] = self.params.get("phone_service")
if "use_proxy" in param_names:
automation_params["use_proxy"] = self.params.get("use_proxy", False)
if "save_screenshots" in param_names:
automation_params["save_screenshots"] = True
if "debug" in param_names:
automation_params["debug"] = self.params.get("debug", False)
if "email_domain" in param_names:
automation_params["email_domain"] = self.params.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com")
if "window_position" in param_names:
automation_params["window_position"] = self.params.get("window_position")
automation = AutomationClass(**automation_params)
# Setze Callback für kundenfreundliche Logs
automation.set_customer_log_callback(lambda msg: self.log_signal.emit(msg))
self.update_signal.emit(f"{self.platform_name}-Automation initialisiert")
self.progress_signal.emit(20)
# Browser-Schutz wird jetzt direkt in base_automation.py nach Browser-Start angewendet
# Account registrieren
self.log_signal.emit(f"Registriere Account für: {self.params['full_name']}")
# Account registrieren mit allen Original-Parametern
# Erstelle saubere Parameter für register_account
register_params = {
"full_name": self.params["full_name"],
"age": self.params["age"],
"registration_method": self.params.get("registration_method", "email"),
"email_domain": self.params.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com")
}
# Füge optionale Parameter hinzu wenn vorhanden
if "phone_number" in self.params:
register_params["phone_number"] = self.params["phone_number"]
# Additional params separat behandeln
if "additional_params" in self.params:
register_params.update(self.params["additional_params"])
result = automation.register_account(**register_params)
if result["success"]:
# Stelle sicher, dass die Datenstruktur kompatibel ist
if "account_data" not in result:
# Wenn account_data nicht existiert, erstelle es aus den Top-Level-Feldern
result["account_data"] = {
"username": result.get("username", ""),
"password": result.get("password", ""),
"email": result.get("email", ""),
"phone": result.get("phone", "")
}
result["fingerprint"] = self.params.get("fingerprint")
self.log_signal.emit("Account erfolgreich erstellt!")
self.finished_signal.emit(result)
self.progress_signal.emit(100)
# Session-Speicherung wenn verfügbar
self._save_session_if_available(result)
else:
error_msg = result.get("error", "Unbekannter Fehler")
interpreted_error = self._interpret_error(error_msg)
self.log_signal.emit(f"Fehler bei der Account-Erstellung: {interpreted_error}")
self.error_signal.emit(interpreted_error)
self.progress_signal.emit(0) # Reset progress on error
except Exception as e:
error_msg = str(e)
self.log_signal.emit(f"Schwerwiegender Fehler: {error_msg}")
self.log_signal.emit(traceback.format_exc())
interpreted_error = self._interpret_error(error_msg)
self.error_signal.emit(interpreted_error)
self.progress_signal.emit(0) # Reset progress on error
def _interpret_error(self, error_message: str) -> str:
"""Interpretiert Fehler mit Fuzzy-Matching"""
error_lower = error_message.lower()
for pattern, interpretation in self.error_interpretations.items():
if pattern in error_lower or self.text_similarity.is_similar(pattern, error_lower, threshold=0.8):
return interpretation
return f"Fehler bei der Registrierung: {error_message}"
def _save_session_if_available(self, result: Dict[str, Any]):
"""Speichert Session wenn Controller verfügbar"""
# Session über SessionController speichern wenn verfügbar
if hasattr(self, 'session_controller') and self.session_controller:
try:
# Verwende den SessionController direkt für Clean Architecture
if hasattr(self.session_controller, 'create_and_save_account'):
# Account-Daten aus dem korrekten Pfad extrahieren
if "account_data" in result:
account_data = result["account_data"]
else:
account_data = {
'username': result.get("username"),
'password': result.get("password"),
'email': result.get("email"),
'phone': result.get("phone")
}
save_result = self.session_controller.create_and_save_account(
platform=self.platform_name,
account_data=account_data
)
if save_result.get('success'):
self.log_signal.emit(f"Session erfolgreich gespeichert")
else:
self.log_signal.emit(f"Warnung: Session konnte nicht gespeichert werden")
except Exception as e:
self.log_signal.emit(f"Warnung: Session konnte nicht gespeichert werden: {e}")
# Alternativ: Signal an Generator Tab senden
elif hasattr(self, 'generator_tab') and self.generator_tab:
try:
if hasattr(self.generator_tab, 'account_created'):
# Account-Daten aus dem korrekten Pfad extrahieren
if "account_data" in result:
account_data = result["account_data"]
else:
account_data = {
'username': result.get("username"),
'password': result.get("password"),
'email': result.get("email"),
'phone': result.get("phone")
}
self.generator_tab.account_created.emit(self.platform_name, account_data)
except Exception as e:
self.log_signal.emit(f"Warnung: Konnte Account-Daten nicht an UI senden: {e}")
def stop(self):
"""Stoppt den Thread"""
self.running = False
self.terminate()

Datei anzeigen

@ -0,0 +1,244 @@
"""
Controller für Gmail/Google Account-spezifische Funktionalität
"""
import logging
from typing import Dict, Any
from controllers.platform_controllers.base_controller import BasePlatformController
from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread
from social_networks.gmail.gmail_automation import GmailAutomation
from utils.logger import setup_logger
logger = setup_logger("gmail_controller")
class GmailWorkerThread(BaseAccountCreationWorkerThread):
"""Worker-Thread für Gmail-Account-Erstellung"""
def __init__(self, params, session_controller=None, generator_tab=None):
logger.info(f"[GMAIL WORKER] __init__ aufgerufen")
logger.info(f"[GMAIL WORKER] params: {params}")
logger.info(f"[GMAIL WORKER] session_controller: {session_controller}")
logger.info(f"[GMAIL WORKER] generator_tab: {generator_tab}")
super().__init__(params, "Gmail", session_controller, generator_tab)
logger.info(f"[GMAIL WORKER] Initialisierung abgeschlossen")
def get_automation_class(self):
"""Gibt die Gmail-Automation-Klasse zurück"""
logger.info(f"[GMAIL WORKER] get_automation_class aufgerufen")
logger.info(f"[GMAIL WORKER] Gebe zurück: {GmailAutomation}")
return GmailAutomation
def get_error_interpretations(self) -> Dict[str, str]:
"""Gmail-spezifische Fehlerinterpretationen"""
return {
"captcha": "Google hat ein Captcha angefordert. Bitte versuchen Sie es später erneut.",
"phone": "Eine Telefonnummer ist zur Verifizierung erforderlich.",
"age": "Sie müssen mindestens 13 Jahre alt sein.",
"taken": "Diese E-Mail-Adresse ist bereits vergeben."
}
class GmailController(BasePlatformController):
"""Controller für Gmail-Funktionalität"""
def __init__(self, db_manager, proxy_rotator, email_handler, language_manager, theme_manager=None):
super().__init__("gmail", db_manager, proxy_rotator, email_handler, language_manager)
logger.info("Gmail Controller initialisiert")
def get_worker_thread_class(self):
"""Gibt die Worker-Thread-Klasse für Gmail zurück"""
return GmailWorkerThread
def get_platform_display_name(self) -> str:
"""Gibt den Anzeigenamen der Plattform zurück"""
return "Gmail"
def validate_account_data(self, account_data: Dict[str, Any]) -> Dict[str, Any]:
"""Validiert die Account-Daten für Gmail"""
errors = []
# Pflichtfelder prüfen
if not account_data.get("first_name"):
errors.append("Vorname ist erforderlich")
if not account_data.get("last_name"):
errors.append("Nachname ist erforderlich")
# Prüfe Geburtsdatum (muss mindestens 13 Jahre alt sein)
if account_data.get("birthday"):
from datetime import datetime
try:
birth_date = datetime.strptime(account_data["birthday"], "%Y-%m-%d")
age = (datetime.now() - birth_date).days / 365.25
if age < 13:
errors.append("Sie müssen mindestens 13 Jahre alt sein")
except:
errors.append("Ungültiges Geburtsdatum")
if errors:
return {
"valid": False,
"errors": errors
}
return {
"valid": True,
"errors": []
}
def create_generator_tab(self):
"""Erstellt den Generator-Tab und verbindet die Signale"""
from views.tabs.generator_tab import GeneratorTab
generator_tab = GeneratorTab(self.platform_name, self.language_manager)
# Signal verbinden
generator_tab.start_requested.connect(self.start_account_creation)
generator_tab.stop_requested.connect(self.stop_account_creation)
return generator_tab
def get_default_settings(self) -> Dict[str, Any]:
"""Gibt die Standard-Einstellungen für Gmail zurück"""
settings = super().get_default_settings()
settings.update({
"require_phone": False, # Optional, aber oft erforderlich
"require_email": False, # Gmail erstellt die Email
"min_age": 13,
"supported_languages": ["de", "en", "es", "fr", "it", "pt", "ru"],
"default_language": "de",
"captcha_warning": True # Gmail verwendet oft Captchas
})
return settings
def start_account_creation(self, params):
"""Startet die Gmail-Account-Erstellung."""
logger.info(f"[GMAIL] start_account_creation aufgerufen")
logger.info(f"[GMAIL] Parameter: {params}")
logger.info(f"[GMAIL] Controller-Typ: {type(self)}")
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Schmiedeanimation-Dialog erstellen und anzeigen
from views.widgets.forge_animation_widget import ForgeAnimationDialog
parent_widget = generator_tab.window()
self.forge_dialog = ForgeAnimationDialog(parent_widget, "Gmail")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Fingerprint generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
fingerprint_service = FingerprintGeneratorService()
fingerprint_data = fingerprint_service.generate_fingerprint()
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
params["fingerprint"] = fingerprint.to_dict()
logger.info(f"Fingerprint für Gmail Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Worker-Thread starten
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
logger.info(f"[GMAIL] Erstelle Worker Thread...")
logger.info(f"[GMAIL] session_controller: {session_controller}")
logger.info(f"[GMAIL] generator_tab_ref: {generator_tab_ref}")
self.worker_thread = GmailWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
logger.info(f"[GMAIL] Worker Thread erstellt: {self.worker_thread}")
# Signals verbinden
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(lambda result: self._handle_finished(result.get("success", False), result))
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
logger.info(f"[GMAIL] Starte Worker Thread...")
self.worker_thread.start()
logger.info(f"[GMAIL] Worker Thread gestartet!")
# Dialog anzeigen
logger.info(f"[GMAIL] Zeige Forge Dialog...")
self.forge_dialog.start_animation()
self.forge_dialog.show()
logger.info(f"[GMAIL] start_account_creation abgeschlossen")
def stop_account_creation(self):
"""Stoppt die laufende Account-Erstellung"""
logger.info("[GMAIL] Stoppe Account-Erstellung")
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
self.worker_thread.wait()
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
# UI zurücksetzen
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
def _handle_error(self, error_msg):
"""Behandelt Fehler während der Account-Erstellung"""
logger.error(f"[GMAIL] Fehler: {error_msg}")
# Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
generator_tab.show_error(f"Fehler: {error_msg}")
def _handle_finished(self, success, result_data):
"""Behandelt das Ende der Account-Erstellung"""
logger.info(f"[GMAIL] Account-Erstellung beendet. Erfolg: {success}")
# Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
if success:
generator_tab.show_success("Gmail Account erfolgreich erstellt!")
else:
error_msg = result_data.get('error', 'Unbekannter Fehler')
generator_tab.show_error(f"Fehler: {error_msg}")

Datei anzeigen

@ -0,0 +1,415 @@
"""
Controller für Instagram-spezifische Funktionalität.
Mit TextSimilarity-Integration für robusteres UI-Element-Matching.
"""
import logging
import time
import random
from PyQt5.QtCore import QThread, pyqtSignal, QObject
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.generator_tab import GeneratorTab
from views.tabs.accounts_tab import AccountsTab
from views.tabs.settings_tab import SettingsTab
from views.widgets.forge_animation_widget import ForgeAnimationDialog
from social_networks.instagram.instagram_automation import InstagramAutomation
from utils.text_similarity import TextSimilarity
from utils.logger import setup_logger
logger = setup_logger("instagram_controller")
# Legacy WorkerThread als Backup beibehalten
class LegacyInstagramWorkerThread(QThread):
"""Legacy Thread für die Instagram-Account-Erstellung (Backup)."""
# Signale
update_signal = pyqtSignal(str)
log_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int)
finished_signal = pyqtSignal(dict)
error_signal = pyqtSignal(str)
def __init__(self, params):
super().__init__()
self.params = params
self.running = True
# TextSimilarity für robustes Fehler-Matching
self.text_similarity = TextSimilarity(default_threshold=0.7)
# Fehler-Patterns für robustes Fehler-Matching
self.error_patterns = [
"Fehler", "Error", "Fehlgeschlagen", "Failed", "Problem", "Issue",
"Nicht möglich", "Not possible", "Bitte versuchen Sie es erneut",
"Please try again", "Konnte nicht", "Could not", "Timeout"
]
def run(self):
"""Führt die Account-Erstellung aus."""
try:
self.log_signal.emit("Instagram-Account-Erstellung gestartet...")
self.progress_signal.emit(10)
# Instagram-Automation initialisieren
automation = InstagramAutomation(
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")
)
self.update_signal.emit("Browser wird vorbereitet...")
self.progress_signal.emit(20)
# Account registrieren
self.log_signal.emit(f"Registriere Account für: {self.params['full_name']}")
# Account registrieren - immer mit Email
result = automation.register_account(
full_name=self.params["full_name"],
age=self.params["age"],
registration_method="email", # Immer Email-Registrierung
phone_number=None, # Keine Telefonnummer
**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:
# Robuste Fehlerbehandlung mit TextSimilarity
error_msg = result.get("error", "Unbekannter Fehler")
# Versuche, Fehler nutzerfreundlicher zu interpretieren
user_friendly_error = self._interpret_error(error_msg)
self.log_signal.emit(f"Fehler bei der Account-Erstellung: {user_friendly_error}")
self.error_signal.emit(user_friendly_error)
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))
self.progress_signal.emit(0)
def _interpret_error(self, error_msg: str) -> str:
"""
Interpretiert Fehlermeldungen und gibt eine benutzerfreundlichere Version zurück.
Verwendet TextSimilarity für robusteres Fehler-Matching.
Args:
error_msg: Die ursprüngliche Fehlermeldung
Returns:
str: Benutzerfreundliche Fehlermeldung
"""
# Bekannte Fehlermuster und deren Interpretationen
error_interpretations = {
"captcha": "Instagram hat einen Captcha-Test angefordert. Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy.",
"verification": "Es gab ein Problem mit der Verifizierung des Accounts. Bitte prüfen Sie die E-Mail-Einstellungen.",
"proxy": "Problem mit der Proxy-Verbindung. Bitte prüfen Sie Ihre Proxy-Einstellungen.",
"timeout": "Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie Ihre Internetverbindung.",
"username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig.",
"password": "Das Passwort erfüllt nicht die Anforderungen von Instagram.",
"email": "Die E-Mail-Adresse konnte nicht verwendet werden. Bitte nutzen Sie eine andere E-Mail-Domain.",
"phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden."
}
# Versuche, den Fehler zu kategorisieren
for pattern, interpretation in error_interpretations.items():
for error_term in self.error_patterns:
if (pattern in error_msg.lower() or
self.text_similarity.is_similar(error_term, error_msg, threshold=0.7)):
return interpretation
# Fallback: Originale Fehlermeldung zurückgeben
return error_msg
def stop(self):
"""Stoppt den Thread."""
self.running = False
self.terminate()
# Neue Implementation mit BaseWorkerThread
class InstagramWorkerThread(BaseAccountCreationWorkerThread):
"""Refaktorierte Instagram Worker Thread Implementation"""
def __init__(self, params, session_controller=None, generator_tab=None):
super().__init__(params, "Instagram", session_controller, generator_tab)
def get_automation_class(self):
from social_networks.instagram.instagram_automation import InstagramAutomation
return InstagramAutomation
def get_error_interpretations(self) -> Dict[str, str]:
return {
"already taken": "Dieser Benutzername ist bereits vergeben",
"weak password": "Das Passwort ist zu schwach",
"rate limit": "Zu viele Versuche - bitte später erneut versuchen",
"network error": "Netzwerkfehler - bitte Internetverbindung prüfen",
"captcha": "Captcha-Verifizierung erforderlich",
"verification": "Es gab ein Problem mit der Verifizierung des Accounts",
"proxy": "Problem mit der Proxy-Verbindung",
"timeout": "Zeitüberschreitung bei der Verbindung",
"username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig",
"password": "Das Passwort erfüllt nicht die Anforderungen von Instagram",
"email": "Die E-Mail-Adresse konnte nicht verwendet werden",
"phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden"
}
class InstagramController(BasePlatformController):
"""Controller für Instagram-spezifische Funktionalität."""
def __init__(self, db_manager, proxy_rotator, email_handler, language_manager=None):
super().__init__("Instagram", db_manager, proxy_rotator, email_handler, language_manager)
self.worker_thread = None
# TextSimilarity für robustes UI-Element-Matching
self.text_similarity = TextSimilarity(default_threshold=0.75)
def create_generator_tab(self):
"""Erstellt den Instagram-Generator-Tab."""
generator_tab = GeneratorTab(self.platform_name, self.language_manager)
# Instagram-spezifische Anpassungen
# Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden
# Signale verbinden
generator_tab.start_requested.connect(self.start_account_creation)
generator_tab.stop_requested.connect(self.stop_account_creation)
return generator_tab
def start_account_creation(self, params):
"""Startet die Instagram-Account-Erstellung."""
super().start_account_creation(params)
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Schmiedeanimation-Dialog erstellen und anzeigen
parent_widget = generator_tab.window() # Hauptfenster als Parent
self.forge_dialog = ForgeAnimationDialog(parent_widget, "Instagram")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Fingerprint VOR Account-Erstellung generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
fingerprint_service = FingerprintGeneratorService()
# Generiere einen neuen Fingerprint für diesen Account
fingerprint_data = fingerprint_service.generate_fingerprint()
# Erstelle BrowserFingerprint Entity mit allen notwendigen Daten
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
# Konvertiere zu Dictionary für Übertragung
params["fingerprint"] = fingerprint.to_dict()
logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Fortfahren ohne Fingerprint - wird später generiert
# Worker-Thread starten mit optionalen Parametern
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
self.worker_thread = InstagramWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Updates an Forge-Dialog weiterleiten
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab für Backup
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen und Animation starten
self.forge_dialog.start_animation()
self.forge_dialog.show()
def stop_account_creation(self):
"""Stoppt die Instagram-Account-Erstellung."""
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
generator_tab = self.get_generator_tab()
generator_tab.add_log("Account-Erstellung wurde abgebrochen")
generator_tab.set_running(False)
generator_tab.set_progress(0)
# Forge-Dialog schließen falls vorhanden
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
def handle_account_created(self, result):
"""Verarbeitet erfolgreich erstellte Accounts mit Clean Architecture."""
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
# Account-Daten aus dem Ergebnis holen
account_data = result.get("account_data", {})
# Account und Session über SessionController speichern (Clean Architecture)
if hasattr(self, 'session_controller') and self.session_controller:
try:
session_data = result.get("session_data", {})
save_result = self.session_controller.create_and_save_account(
platform=self.platform_name,
account_data=account_data
)
if save_result.get('success'):
logger.info(f"Account und Session erfolgreich gespeichert")
# Erfolgsmeldung anzeigen (nur einmal!)
account_info = save_result.get('account_data', {})
from PyQt5.QtWidgets import QMessageBox
QMessageBox.information(
generator_tab,
"Erfolg",
f"Account erfolgreich erstellt!\n\n"
f"Benutzername: {account_info.get('username', '')}\n"
f"Passwort: {account_info.get('password', '')}\n"
f"E-Mail/Telefon: {account_info.get('email') or account_info.get('phone', '')}"
)
# Signal senden, um zur Hauptseite zurückzukehren
if hasattr(self, 'return_to_main_requested') and callable(self.return_to_main_requested):
self.return_to_main_requested()
else:
error_msg = save_result.get('message', 'Unbekannter Fehler')
logger.error(f"Fehler beim Speichern: {error_msg}")
from views.widgets.modern_message_box import show_error
show_error(
generator_tab,
"Fehler beim Speichern",
f"Beim Speichern des Accounts ist ein Fehler aufgetreten:\n\n{error_msg}"
)
except Exception as e:
logger.error(f"Fehler beim Speichern des Accounts: {e}")
from views.widgets.modern_message_box import show_critical
show_critical(
generator_tab,
"Unerwarteter Fehler",
f"Ein unerwarteter Fehler ist beim Speichern des Accounts aufgetreten:\n\n{str(e)}"
)
else:
# Fallback: Alte Methode falls SessionController nicht verfügbar
logger.warning("SessionController nicht verfügbar, verwende alte Methode")
generator_tab.account_created.emit(self.platform_name, account_data)
if hasattr(self, 'return_to_main_requested') and callable(self.return_to_main_requested):
self.return_to_main_requested()
# save_account_to_db wurde entfernt - Accounts werden jetzt über SessionController gespeichert
def validate_inputs(self, inputs):
"""
Validiert die Eingaben für die Account-Erstellung.
Verwendet TextSimilarity für robustere Validierung.
"""
# Basis-Validierungen von BasePlatformController verwenden
valid, error_msg = super().validate_inputs(inputs)
if not valid:
return valid, error_msg
# Instagram-spezifische Validierungen
age = inputs.get("age", 0)
if age < 13: # Änderung von 14 auf 13
return False, "Das Alter muss mindestens 13 sein (Instagram-Anforderung)."
# E-Mail-Domain-Validierung (immer Email-Registrierung)
email_domain = inputs.get("email_domain", "")
# Blacklist von bekannten problematischen Domains
blacklisted_domains = ["temp-mail.org", "guerrillamail.com", "maildrop.cc"]
# Prüfe mit TextSimilarity auf Ähnlichkeit mit Blacklist
for domain in blacklisted_domains:
if self.text_similarity.is_similar(email_domain, domain, threshold=0.8):
return False, f"Die E-Mail-Domain '{email_domain}' kann problematisch für die Instagram-Registrierung sein. Bitte verwenden Sie eine andere Domain."
return True, ""
def _handle_error(self, error_msg: str):
"""Behandelt Fehler während der Account-Erstellung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Fehler anzeigen
generator_tab = self.get_generator_tab()
generator_tab.show_error(error_msg)
generator_tab.set_running(False)
def _handle_finished(self, result: dict):
"""Behandelt das Ende der Account-Erstellung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Normale Verarbeitung
self.handle_account_created(result)
def get_form_field_label(self, field_type: str) -> str:
"""
Gibt einen Label-Text für ein Formularfeld basierend auf dem Feldtyp zurück.
Args:
field_type: Typ des Formularfelds
Returns:
str: Label-Text für das Formularfeld
"""
# Mapping von Feldtypen zu Labels
field_labels = {
"full_name": "Vollständiger Name",
"username": "Benutzername",
"password": "Passwort",
"email": "E-Mail-Adresse",
"phone": "Telefonnummer",
"age": "Alter",
"birthday": "Geburtsdatum"
}
return field_labels.get(field_type, field_type.capitalize())

Datei anzeigen

@ -0,0 +1,317 @@
"""
Method rotation mixin for platform controllers.
Provides method rotation functionality without breaking existing inheritance hierarchy.
"""
import logging
from datetime import datetime
from typing import Dict, Any, Optional
from application.use_cases.method_rotation_use_case import MethodRotationUseCase, RotationContext
from infrastructure.repositories.method_strategy_repository import MethodStrategyRepository
from infrastructure.repositories.rotation_session_repository import RotationSessionRepository
from infrastructure.repositories.platform_method_state_repository import PlatformMethodStateRepository
from domain.entities.method_rotation import MethodStrategy, RotationSession, RiskLevel
class MethodRotationMixin:
"""
Mixin class that adds method rotation capabilities to platform controllers.
Can be mixed into existing controller classes without breaking inheritance.
"""
def _init_method_rotation_system(self):
"""Initialize the method rotation system components"""
try:
# Check if db_manager is available
if not hasattr(self, 'db_manager') or self.db_manager is None:
self.method_rotation_use_case = None
return
# Initialize repositories
self.method_strategy_repo = MethodStrategyRepository(self.db_manager)
self.rotation_session_repo = RotationSessionRepository(self.db_manager)
self.platform_state_repo = PlatformMethodStateRepository(self.db_manager)
# Initialize use case
self.method_rotation_use_case = MethodRotationUseCase(
strategy_repo=self.method_strategy_repo,
session_repo=self.rotation_session_repo,
state_repo=self.platform_state_repo
)
# Initialize rotation state
self.current_rotation_session = None
self.current_rotation_context = None
self.logger.info(f"Method rotation system initialized for {self.platform_name}")
except Exception as e:
self.logger.error(f"Failed to initialize method rotation system: {e}")
# Set to None so we can detect failures and fallback
self.method_rotation_use_case = None
def _apply_method_strategy(self, params: Dict[str, Any], strategy: MethodStrategy) -> Dict[str, Any]:
"""
Apply method strategy configuration to account creation parameters.
Args:
params: Original account creation parameters
strategy: Selected method strategy
Returns:
Updated parameters with method-specific configuration
"""
updated_params = params.copy()
# Apply method selection
updated_params['registration_method'] = strategy.method_name
# Apply method-specific configuration
config = strategy.configuration
if strategy.method_name.startswith('stealth_'):
# Instagram anti-bot strategy methods
updated_params['stealth_method'] = strategy.method_name
updated_params['enhanced_stealth'] = config.get('enhanced_stealth', False)
updated_params['user_agent_rotation'] = config.get('user_agent_rotation', False)
updated_params['fingerprint_complexity'] = config.get('fingerprint_complexity', 'basic')
updated_params['canvas_noise'] = config.get('canvas_noise', False)
updated_params['webrtc_protection'] = config.get('webrtc_protection', 'basic')
updated_params['viewport_randomization'] = config.get('viewport_randomization', False)
updated_params['navigator_spoof'] = config.get('navigator_spoof', False)
updated_params['timing_randomization'] = config.get('timing_randomization', False)
updated_params['screen_resolution_spoof'] = config.get('screen_resolution_spoof', False)
updated_params['memory_spoof'] = config.get('memory_spoof', False)
updated_params['hardware_spoof'] = config.get('hardware_spoof', False)
elif strategy.method_name == 'email':
# Email method configuration (legacy)
updated_params['email_domain'] = config.get('email_domain', self.DEFAULT_EMAIL_DOMAIN)
updated_params['require_phone_verification'] = config.get('require_phone_verification', False)
updated_params['auto_verify_email'] = config.get('auto_verify_email', True)
elif strategy.method_name == 'phone':
# Phone method configuration (legacy)
updated_params['registration_method'] = 'phone'
updated_params['require_email_backup'] = config.get('require_email_backup', True)
updated_params['phone_verification_timeout'] = config.get('phone_verification_timeout', 300)
elif strategy.method_name == 'social_login':
# Social login configuration (legacy)
updated_params['registration_method'] = 'social'
updated_params['social_providers'] = config.get('supported_providers', ['facebook'])
updated_params['fallback_to_email'] = config.get('fallback_to_email', True)
elif strategy.method_name in ['standard_registration', 'recovery_registration']:
# Gmail-specific methods
updated_params['registration_method'] = strategy.method_name
updated_params['recovery_email'] = config.get('recovery_email', False)
updated_params['recovery_phone'] = config.get('recovery_phone', False)
# Add strategy metadata
updated_params['_method_strategy'] = {
'strategy_id': strategy.strategy_id,
'method_name': strategy.method_name,
'priority': strategy.priority,
'risk_level': strategy.risk_level.value,
'effectiveness_score': strategy.effectiveness_score
}
return updated_params
def handle_method_failure(self, error_details: Dict[str, Any]) -> bool:
"""
Handle method failure and attempt rotation to next best method.
Args:
error_details: Details about the failure
Returns:
True if rotation succeeded and retry should be attempted, False otherwise
"""
if not self.method_rotation_use_case or not self.current_rotation_session:
return False
try:
# Record the failure
self.method_rotation_use_case.record_method_result(
session_id=self.current_rotation_session.session_id,
method_name=self.current_rotation_session.current_method,
success=False,
error_details=error_details
)
# Check if rotation should occur
if self.method_rotation_use_case.should_rotate_method(self.current_rotation_session.session_id):
# Attempt rotation
next_method = self.method_rotation_use_case.rotate_method(
session_id=self.current_rotation_session.session_id,
reason=f"Method failure: {error_details.get('error_type', 'unknown')}"
)
if next_method:
self.logger.info(f"Rotating from {self.current_rotation_session.current_method} to {next_method.method_name}")
# Update current session reference
self.current_rotation_session = self.rotation_session_repo.find_by_id(
self.current_rotation_session.session_id
)
return True
else:
self.logger.warning("No alternative methods available for rotation")
else:
self.logger.info("Rotation not triggered - continuing with current method")
except Exception as e:
self.logger.error(f"Method rotation failed: {e}")
return False
def handle_method_success(self, result: Dict[str, Any]) -> None:
"""
Handle successful method execution.
Args:
result: Result details from successful execution
"""
if not self.method_rotation_use_case or not self.current_rotation_session:
return
try:
execution_time = result.get('execution_time', 0.0)
# Record the success
self.method_rotation_use_case.record_method_result(
session_id=self.current_rotation_session.session_id,
method_name=self.current_rotation_session.current_method,
success=True,
execution_time=execution_time
)
self.logger.info(f"Method {self.current_rotation_session.current_method} succeeded in {execution_time:.2f}s")
except Exception as e:
self.logger.error(f"Failed to record method success: {e}")
def get_rotation_status(self) -> Optional[Dict[str, Any]]:
"""
Get current rotation session status.
Returns:
Dictionary with rotation status information or None if no active session
"""
if not self.method_rotation_use_case or not self.current_rotation_session:
return None
try:
return self.method_rotation_use_case.get_session_status(
self.current_rotation_session.session_id
)
except Exception as e:
self.logger.error(f"Failed to get rotation status: {e}")
return None
def get_platform_method_recommendations(self) -> Dict[str, Any]:
"""
Get method recommendations and insights for the current platform.
Returns:
Dictionary with recommendations and platform insights
"""
if not self.method_rotation_use_case:
return {}
try:
return self.method_rotation_use_case.get_platform_method_recommendations(
self.platform_name.lower()
)
except Exception as e:
self.logger.error(f"Failed to get method recommendations: {e}")
return {}
def enable_emergency_mode(self, reason: str = "manual_override") -> None:
"""
Enable emergency mode for the platform.
Args:
reason: Reason for enabling emergency mode
"""
if not self.method_rotation_use_case:
return
try:
self.method_rotation_use_case.enable_emergency_mode(
self.platform_name.lower(), reason
)
self.logger.warning(f"Emergency mode enabled for {self.platform_name}: {reason}")
except Exception as e:
self.logger.error(f"Failed to enable emergency mode: {e}")
def disable_emergency_mode(self) -> None:
"""Disable emergency mode for the platform."""
if not self.method_rotation_use_case:
return
try:
self.method_rotation_use_case.disable_emergency_mode(
self.platform_name.lower()
)
self.logger.info(f"Emergency mode disabled for {self.platform_name}")
except Exception as e:
self.logger.error(f"Failed to disable emergency mode: {e}")
def _create_rotation_context(self, params: Dict[str, Any]) -> RotationContext:
"""
Create rotation context from account creation parameters.
Args:
params: Account creation parameters
Returns:
RotationContext for method selection
"""
return RotationContext(
platform=self.platform_name.lower(),
account_id=params.get('account_id'),
fingerprint_id=params.get('fingerprint', {}).get('fingerprint_id'),
excluded_methods=params.get('_excluded_methods', []),
max_risk_level=RiskLevel(params.get('_max_risk_level', 'HIGH')),
emergency_mode=params.get('_emergency_mode', False),
session_metadata={
'user_inputs': {k: v for k, v in params.items() if not k.startswith('_')},
'creation_started_at': datetime.now().isoformat(),
'controller_type': self.__class__.__name__
}
)
def _should_use_rotation_system(self) -> bool:
"""
Check if rotation system should be used.
Returns:
True if rotation system is available and should be used
"""
return (
self.method_rotation_use_case is not None and
hasattr(self, 'db_manager') and
self.db_manager is not None
)
def cleanup_rotation_session(self) -> None:
"""Clean up current rotation session."""
if self.current_rotation_session:
try:
if self.current_rotation_session.is_active:
self.rotation_session_repo.archive_session(
self.current_rotation_session.session_id,
False
)
except Exception as e:
self.logger.error(f"Failed to cleanup rotation session: {e}")
finally:
self.current_rotation_session = None
self.current_rotation_context = None

Datei anzeigen

@ -0,0 +1,288 @@
"""
Worker thread mixin for method rotation integration.
Adds rotation support to base worker threads without breaking existing functionality.
"""
import logging
from typing import Dict, Any, Optional
from datetime import datetime
# Import rotation components (with fallback for missing imports)
try:
from controllers.platform_controllers.method_rotation_mixin import MethodRotationMixin
ROTATION_AVAILABLE = True
except ImportError:
ROTATION_AVAILABLE = False
class MethodRotationMixin:
pass
class MethodRotationWorkerMixin:
"""
Mixin for worker threads to add method rotation support.
Handles rotation-aware error handling and retry logic.
"""
def _init_rotation_support(self, controller_instance: Optional[Any] = None):
"""
Initialize rotation support for worker thread.
Args:
controller_instance: Reference to controller that has rotation capabilities
"""
self.controller_instance = controller_instance
self.rotation_session_id = self.params.get('_rotation_session_id')
self.strategy_id = self.params.get('_strategy_id')
self.rotation_retry_count = 0
self.max_rotation_retries = 3
def _handle_registration_failure(self, result: Dict[str, Any]) -> bool:
"""
Handle registration failure with rotation support.
Args:
result: Result from failed registration attempt
Returns:
True if rotation was attempted and should retry, False otherwise
"""
if not self._is_rotation_available():
return False
# Check if we've exceeded retry limit
if self.rotation_retry_count >= self.max_rotation_retries:
self.log_signal.emit("Maximum rotation retries reached, stopping")
return False
error_details = {
'error_type': self._classify_error(result.get('error', '')),
'message': result.get('error', 'Unknown error'),
'timestamp': datetime.now().isoformat(),
'attempt_number': self.rotation_retry_count + 1
}
# Attempt rotation through controller
try:
rotation_success = self.controller_instance.handle_method_failure(error_details)
if rotation_success:
self.rotation_retry_count += 1
# Get updated session to get new method
rotation_status = self.controller_instance.get_rotation_status()
if rotation_status:
new_method = rotation_status.get('current_method')
self.log_signal.emit(f"Rotating to method: {new_method} (attempt {self.rotation_retry_count})")
# Update params with new method
self._update_params_for_rotation(rotation_status)
return True
except Exception as e:
self.log_signal.emit(f"Rotation failed: {e}")
return False
def _handle_registration_success(self, result: Dict[str, Any]):
"""
Handle successful registration with rotation tracking.
Args:
result: Result from successful registration
"""
if not self._is_rotation_available():
return
try:
# Record success through controller
success_details = {
'execution_time': result.get('execution_time', 0.0),
'timestamp': datetime.now().isoformat(),
'method_used': self.params.get('registration_method', 'unknown'),
'retry_count': self.rotation_retry_count
}
self.controller_instance.handle_method_success(success_details)
if self.rotation_retry_count > 0:
self.log_signal.emit(f"Success after {self.rotation_retry_count} rotation(s)")
except Exception as e:
self.log_signal.emit(f"Failed to record rotation success: {e}")
def _update_params_for_rotation(self, rotation_status: Dict[str, Any]):
"""
Update worker parameters based on rotation status.
Args:
rotation_status: Current rotation session status
"""
new_method = rotation_status.get('current_method')
if new_method:
# Apply method-specific parameter updates
if new_method.startswith('stealth_'):
self.params['stealth_method'] = new_method
if new_method == 'stealth_basic':
self.params['enhanced_stealth'] = False
self.params['user_agent_rotation'] = False
self.log_signal.emit("Switched to basic stealth mode")
elif new_method == 'stealth_enhanced':
self.params['enhanced_stealth'] = True
self.params['user_agent_rotation'] = True
self.params['canvas_noise'] = True
self.log_signal.emit("Switched to enhanced stealth mode")
elif new_method == 'stealth_maximum':
self.params['enhanced_stealth'] = True
self.params['user_agent_rotation'] = True
self.params['canvas_noise'] = True
self.params['navigator_spoof'] = True
self.params['viewport_randomization'] = True
self.params['memory_spoof'] = True
self.params['hardware_spoof'] = True
self.log_signal.emit("Switched to maximum stealth mode")
elif new_method == 'phone':
self.params['require_phone_verification'] = True
self.log_signal.emit("Switched to phone registration method")
elif new_method == 'email':
self.params['require_phone_verification'] = False
self.log_signal.emit("Switched to email registration method")
elif new_method == 'social_login':
self.params['use_social_login'] = True
self.log_signal.emit("Switched to social login method")
def _classify_error(self, error_message: str) -> str:
"""
Classify error type for rotation decision making.
Args:
error_message: Error message from failed attempt
Returns:
Error classification string
"""
error_lower = error_message.lower()
# Browser-level and CSS parsing errors (high priority for rotation)
if any(term in error_lower for term in [
'css', 'javascript', 'parsing', '--font-family', '--gradient',
'stylesheet', 'rendering', 'dom', 'browser', 'navigation'
]):
return 'browser_level_error'
elif any(term in error_lower for term in ['rate limit', 'zu viele', 'too many']):
return 'rate_limit'
elif any(term in error_lower for term in ['suspended', 'gesperrt', 'blocked']):
return 'account_suspended'
elif any(term in error_lower for term in ['timeout', 'zeitüberschreitung']):
return 'timeout'
elif any(term in error_lower for term in ['captcha', 'verification']):
return 'verification_required'
elif any(term in error_lower for term in ['network', 'connection', 'verbindung']):
return 'network_error'
else:
return 'unknown'
def _is_rotation_available(self) -> bool:
"""
Check if rotation support is available.
Returns:
True if rotation is available and configured
"""
return (
ROTATION_AVAILABLE and
self.controller_instance is not None and
hasattr(self.controller_instance, 'handle_method_failure') and
hasattr(self.controller_instance, '_should_use_rotation_system') and
self.controller_instance._should_use_rotation_system()
)
def _enhanced_register_account(self, automation, register_params: Dict[str, Any]) -> Dict[str, Any]:
"""
Enhanced account registration with rotation support.
Args:
automation: Platform automation instance
register_params: Registration parameters
Returns:
Registration result with rotation tracking
"""
start_time = datetime.now()
try:
# Attempt registration
result = automation.register_account(**register_params)
# Calculate execution time
execution_time = (datetime.now() - start_time).total_seconds()
result['execution_time'] = execution_time
if result.get("success"):
# Handle success
self._handle_registration_success(result)
return result
else:
# Handle failure with potential rotation
if self._handle_registration_failure(result):
# Rotation was attempted, retry with new method
self.log_signal.emit("Retrying with rotated method...")
# Recursive call with updated params (limited by retry count)
updated_register_params = register_params.copy()
updated_register_params.update({
'registration_method': self.params.get('registration_method'),
'require_phone_verification': self.params.get('require_phone_verification', False)
})
return self._enhanced_register_account(automation, updated_register_params)
else:
# No rotation available or retry limit reached
return result
except Exception as e:
# Handle exceptions
error_result = {
'success': False,
'error': str(e),
'execution_time': (datetime.now() - start_time).total_seconds()
}
# Try rotation on exception as well
if self._handle_registration_failure(error_result):
self.log_signal.emit("Retrying after exception with rotated method...")
return self._enhanced_register_account(automation, register_params)
else:
return error_result
def _log_rotation_status(self):
"""Log current rotation status for debugging"""
if self._is_rotation_available():
try:
status = self.controller_instance.get_rotation_status()
if status:
self.log_signal.emit(f"Rotation Status - Method: {status.get('current_method')}, "
f"Attempts: {status.get('rotation_count', 0)}, "
f"Success Rate: {status.get('success_rate', 0.0):.2f}")
except Exception as e:
self.log_signal.emit(f"Could not get rotation status: {e}")
def cleanup_rotation(self):
"""Clean up rotation resources"""
if self._is_rotation_available():
try:
self.controller_instance.cleanup_rotation_session()
except Exception as e:
self.log_signal.emit(f"Rotation cleanup failed: {e}")
def stop(self):
"""Enhanced stop method with rotation cleanup"""
self.running = False
self.cleanup_rotation()
# Call original stop if it exists
if hasattr(super(), 'stop'):
super().stop()

Datei anzeigen

@ -0,0 +1,193 @@
"""
Controller für OK.ru (Odnoklassniki)-spezifische Funktionalität.
"""
import logging
from PyQt5.QtCore import QThread, pyqtSignal
from controllers.platform_controllers.base_controller import BasePlatformController
from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread
from views.tabs.generator_tab import GeneratorTab
from views.widgets.forge_animation_widget import ForgeAnimationDialog
from social_networks.ok_ru.ok_ru_automation import OkRuAutomation
from utils.logger import setup_logger
logger = setup_logger("ok_ru_controller")
class OkRuWorkerThread(BaseAccountCreationWorkerThread):
"""Worker Thread für OK.ru Account-Erstellung"""
def __init__(self, params, session_controller=None, generator_tab=None):
super().__init__(params, "OK.ru", session_controller, generator_tab)
def get_automation_class(self):
"""Gibt die OK.ru-Automation-Klasse zurück"""
return OkRuAutomation
def get_error_interpretations(self):
"""OK.ru-spezifische Fehlerinterpretationen"""
return {
"phone": "Diese Telefonnummer wird bereits verwendet.",
"captcha": "Bitte lösen Sie das Captcha.",
"age": "Sie müssen mindestens 14 Jahre alt sein.",
"blocked": "Zu viele Versuche. Bitte versuchen Sie es später erneut."
}
class OkRuController(BasePlatformController):
"""Controller für OK.ru (Odnoklassniki)-spezifische Funktionalität."""
def __init__(self, db_manager, proxy_rotator, email_handler, language_manager=None):
super().__init__("OK.ru", db_manager, proxy_rotator, email_handler, language_manager)
self.worker_thread = None
self.platform_icon = "ok_ru.png" # Spezifisches Icon für OK.ru
def create_generator_tab(self):
"""Erstellt den Generator-Tab für OK.ru."""
generator_tab = GeneratorTab(self.platform_name, self.language_manager)
# OK.ru verwendet nur Telefon-Registrierung
# Keine spezielle Konfiguration nötig, da GeneratorTab standardmäßig alle Felder hat
return generator_tab
def start_account_creation(self, params):
"""Startet die OK.ru-Account-Erstellung."""
logger.info(f"Starte OK.ru Account-Erstellung mit Parametern: {params}")
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Schmiedeanimation-Dialog erstellen und anzeigen
parent_widget = generator_tab.window()
self.forge_dialog = ForgeAnimationDialog(parent_widget, "OK.ru")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Fingerprint VOR Account-Erstellung generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
fingerprint_service = FingerprintGeneratorService()
# Generiere einen neuen Fingerprint für diesen Account
fingerprint_data = fingerprint_service.generate_fingerprint()
# Erstelle BrowserFingerprint Entity mit allen notwendigen Daten
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
# Konvertiere zu Dictionary für Übertragung
params["fingerprint"] = fingerprint.to_dict()
logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Fortfahren ohne Fingerprint - wird später generiert
# Worker-Thread starten
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
self.worker_thread = OkRuWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Updates an Forge-Dialog weiterleiten
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab für Backup
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen und Animation starten
self.forge_dialog.start_animation()
self.forge_dialog.show()
def stop_account_creation(self):
"""Stoppt die OK.ru-Account-Erstellung."""
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
generator_tab = self.get_generator_tab()
generator_tab.add_log("Account-Erstellung wurde abgebrochen")
generator_tab.set_running(False)
generator_tab.set_progress(0)
# Forge-Dialog schließen falls vorhanden
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
def validate_inputs(self, inputs):
"""
Validiert die Eingaben für die Account-Erstellung.
"""
# Basis-Validierungen von BasePlatformController verwenden
valid, error_msg = super().validate_inputs(inputs)
if not valid:
return valid, error_msg
# OK.ru-spezifische Validierungen
age = inputs.get("age", 0)
if age < 14:
return False, "Das Alter muss mindestens 14 sein (OK.ru-Anforderung)."
# Telefonnummer-Validierung für OK.ru - vorerst deaktiviert für Tests
# TODO: Telefonnummern-Feld in UI hinzufügen
# phone_number = inputs.get("phone_number", "")
# if not phone_number:
# return False, "Telefonnummer ist erforderlich für OK.ru-Registrierung."
#
# # Einfache Telefonnummern-Validierung
# if len(phone_number) < 10:
# return False, "Telefonnummer muss mindestens 10 Ziffern haben."
return True, ""
def _handle_error(self, error_msg: str):
"""Behandelt Fehler während der Account-Erstellung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Fehler anzeigen
generator_tab = self.get_generator_tab()
generator_tab.show_error(error_msg)
generator_tab.set_running(False)
def _handle_finished(self, result: dict):
"""Behandelt das Ende der Account-Erstellung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Normale Verarbeitung
self.handle_account_created(result)

Datei anzeigen

@ -0,0 +1,443 @@
"""
Comprehensive error handling and fallback mechanisms for method rotation system.
Provides robust error recovery and graceful degradation strategies.
"""
import logging
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional, Callable
from enum import Enum
from domain.entities.method_rotation import MethodStrategy, RotationSession, RiskLevel
from application.use_cases.method_rotation_use_case import MethodRotationUseCase
class ErrorSeverity(Enum):
"""Error severity levels for rotation decisions"""
LOW = "low" # Minor issues, continue with current method
MEDIUM = "medium" # Moderate issues, consider rotation
HIGH = "high" # Serious issues, rotate immediately
CRITICAL = "critical" # Critical failure, enable emergency mode
class RotationErrorHandler:
"""
Handles errors and provides fallback mechanisms for the rotation system.
Implements intelligent error classification and recovery strategies.
"""
def __init__(self, method_rotation_use_case: MethodRotationUseCase):
self.method_rotation_use_case = method_rotation_use_case
self.logger = logging.getLogger(self.__class__.__name__)
# Error classification patterns
self.error_patterns = self._init_error_patterns()
# Fallback strategies
self.fallback_strategies = self._init_fallback_strategies()
# Emergency mode settings
self.emergency_thresholds = {
'failure_rate_threshold': 0.8,
'consecutive_failures_threshold': 5,
'time_window_minutes': 30
}
def handle_rotation_error(self, platform: str, session_id: str,
error_details: Dict[str, Any]) -> Dict[str, Any]:
"""
Handle rotation system errors with intelligent recovery.
Args:
platform: Platform name
session_id: Current rotation session ID
error_details: Error information
Returns:
Recovery action result
"""
try:
# Classify error severity
severity = self._classify_error_severity(error_details)
# Log error with classification
self.logger.warning(f"Rotation error on {platform}: {error_details.get('message', 'Unknown')} (Severity: {severity.value})")
# Choose recovery strategy based on severity
if severity == ErrorSeverity.CRITICAL:
return self._handle_critical_error(platform, session_id, error_details)
elif severity == ErrorSeverity.HIGH:
return self._handle_high_severity_error(platform, session_id, error_details)
elif severity == ErrorSeverity.MEDIUM:
return self._handle_medium_severity_error(platform, session_id, error_details)
else:
return self._handle_low_severity_error(platform, session_id, error_details)
except Exception as e:
self.logger.error(f"Error in rotation error handler: {e}")
return self._fallback_to_original_behavior(platform, error_details)
def handle_system_failure(self, platform: str, failure_details: Dict[str, Any]) -> Dict[str, Any]:
"""
Handle complete rotation system failures with graceful degradation.
Args:
platform: Platform name
failure_details: System failure information
Returns:
Fallback strategy result
"""
self.logger.error(f"Rotation system failure on {platform}: {failure_details}")
try:
# Attempt to gracefully shut down rotation for this platform
self._disable_rotation_for_platform(platform, "system_failure")
# Enable emergency mode with safest methods only
self.method_rotation_use_case.enable_emergency_mode(
platform, f"System failure: {failure_details.get('message', 'Unknown')}"
)
return {
'success': True,
'action': 'emergency_mode_enabled',
'fallback_method': 'email',
'message': 'System failure handled, emergency mode activated'
}
except Exception as e:
self.logger.error(f"Failed to handle system failure: {e}")
return self._fallback_to_original_behavior(platform, failure_details)
def check_and_handle_emergency_conditions(self, platform: str) -> bool:
"""
Check if emergency conditions are met and handle accordingly.
Args:
platform: Platform to check
Returns:
True if emergency mode was triggered
"""
try:
# Get platform statistics
stats = self.method_rotation_use_case.strategy_repo.get_platform_statistics(platform)
# Check failure rate threshold
recent_failures = stats.get('recent_failures_24h', 0)
recent_successes = stats.get('recent_successes_24h', 0)
total_recent = recent_failures + recent_successes
if total_recent > 0:
failure_rate = recent_failures / total_recent
if failure_rate >= self.emergency_thresholds['failure_rate_threshold']:
self.logger.warning(f"High failure rate detected on {platform}: {failure_rate:.2f}")
self.method_rotation_use_case.enable_emergency_mode(
platform, f"High failure rate: {failure_rate:.2f}"
)
return True
# Check consecutive failures
session_stats = self.method_rotation_use_case.session_repo.get_session_statistics(platform, days=1)
failed_sessions = session_stats.get('failed_sessions', 0)
if failed_sessions >= self.emergency_thresholds['consecutive_failures_threshold']:
self.logger.warning(f"High consecutive failures on {platform}: {failed_sessions}")
self.method_rotation_use_case.enable_emergency_mode(
platform, f"Consecutive failures: {failed_sessions}"
)
return True
return False
except Exception as e:
self.logger.error(f"Failed to check emergency conditions: {e}")
return False
def recover_from_method_exhaustion(self, platform: str, session_id: str) -> Dict[str, Any]:
"""
Handle the case when all available methods have been exhausted.
Args:
platform: Platform name
session_id: Current session ID
Returns:
Recovery strategy result
"""
self.logger.warning(f"Method exhaustion on {platform}, implementing recovery strategy")
try:
# Enable emergency mode
self.method_rotation_use_case.enable_emergency_mode(
platform, "method_exhaustion"
)
# Reset method cooldowns for emergency use
self._reset_method_cooldowns(platform)
# Use safest method available
emergency_methods = self.method_rotation_use_case.strategy_repo.get_emergency_methods(platform)
if emergency_methods:
safest_method = emergency_methods[0]
return {
'success': True,
'action': 'emergency_recovery',
'method': safest_method.method_name,
'message': f'Recovered using emergency method: {safest_method.method_name}'
}
else:
# No emergency methods available, fall back to original behavior
return self._fallback_to_original_behavior(platform, {
'error': 'method_exhaustion',
'message': 'No emergency methods available'
})
except Exception as e:
self.logger.error(f"Failed to recover from method exhaustion: {e}")
return self._fallback_to_original_behavior(platform, {'error': str(e)})
def _classify_error_severity(self, error_details: Dict[str, Any]) -> ErrorSeverity:
"""Classify error severity based on error patterns"""
error_message = error_details.get('message', '').lower()
error_type = error_details.get('error_type', '').lower()
# Critical errors
critical_patterns = [
'system failure', 'database error', 'connection refused',
'authentication failed', 'service unavailable'
]
if any(pattern in error_message or pattern in error_type for pattern in critical_patterns):
return ErrorSeverity.CRITICAL
# High severity errors
high_patterns = [
'account suspended', 'rate limit exceeded', 'quota exceeded',
'blocked', 'banned', 'captcha failed multiple times'
]
if any(pattern in error_message or pattern in error_type for pattern in high_patterns):
return ErrorSeverity.HIGH
# Medium severity errors
medium_patterns = [
'timeout', 'verification failed', 'invalid credentials',
'network error', 'temporary failure'
]
if any(pattern in error_message or pattern in error_type for pattern in medium_patterns):
return ErrorSeverity.MEDIUM
# Default to low severity
return ErrorSeverity.LOW
def _handle_critical_error(self, platform: str, session_id: str,
error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle critical errors with immediate emergency mode activation"""
self.logger.error(f"Critical error on {platform}: {error_details}")
# Enable emergency mode immediately
self.method_rotation_use_case.enable_emergency_mode(
platform, f"Critical error: {error_details.get('message', 'Unknown')}"
)
# Archive current session
self.method_rotation_use_case.session_repo.archive_session(session_id, False)
return {
'success': True,
'action': 'emergency_mode',
'severity': 'critical',
'message': 'Critical error handled, emergency mode enabled'
}
def _handle_high_severity_error(self, platform: str, session_id: str,
error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle high severity errors with method blocking and rotation"""
error_type = error_details.get('error_type', 'unknown')
# Block the current method temporarily
session = self.method_rotation_use_case.session_repo.find_by_id(session_id)
if session:
current_method = session.current_method
# Block method for extended period
self.method_rotation_use_case.state_repo.block_method(
platform, current_method, f"High severity error: {error_type}"
)
# Attempt rotation to different method
next_method = self.method_rotation_use_case.rotate_method(
session_id, f"high_severity_error_{error_type}"
)
if next_method:
return {
'success': True,
'action': 'method_rotation',
'blocked_method': current_method,
'new_method': next_method.method_name,
'message': f'Rotated from {current_method} to {next_method.method_name}'
}
# Check if emergency mode should be triggered
if self.check_and_handle_emergency_conditions(platform):
return {
'success': True,
'action': 'emergency_mode_triggered',
'message': 'Emergency conditions detected, emergency mode enabled'
}
return {
'success': False,
'action': 'rotation_failed',
'message': 'Could not rotate to alternative method'
}
def _handle_medium_severity_error(self, platform: str, session_id: str,
error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle medium severity errors with conditional rotation"""
# Attempt rotation if failure count is high
session = self.method_rotation_use_case.session_repo.find_by_id(session_id)
if session and session.failure_count >= 2:
next_method = self.method_rotation_use_case.rotate_method(
session_id, f"medium_severity_error_{error_details.get('error_type', 'unknown')}"
)
if next_method:
return {
'success': True,
'action': 'conditional_rotation',
'new_method': next_method.method_name,
'message': f'Rotated to {next_method.method_name} after {session.failure_count} failures'
}
return {
'success': True,
'action': 'continue_current_method',
'message': 'Continuing with current method, failure count below threshold'
}
def _handle_low_severity_error(self, platform: str, session_id: str,
error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle low severity errors with minimal intervention"""
return {
'success': True,
'action': 'continue_current_method',
'message': 'Low severity error, continuing with current method'
}
def _fallback_to_original_behavior(self, platform: str,
error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Fallback to original behavior when rotation system fails completely"""
self.logger.warning(f"Falling back to original behavior for {platform}")
return {
'success': True,
'action': 'fallback_to_original',
'method': 'email', # Default method
'message': 'Rotation system disabled, using original behavior',
'fallback_reason': error_details.get('message', 'Unknown error')
}
def _disable_rotation_for_platform(self, platform: str, reason: str) -> None:
"""Temporarily disable rotation for a specific platform"""
try:
# Block all methods except the safest one
strategies = self.method_rotation_use_case.strategy_repo.find_active_by_platform(platform)
for strategy in strategies[1:]: # Keep the first (safest) method active
self.method_rotation_use_case.strategy_repo.disable_method(
platform, strategy.method_name, f"Platform disabled: {reason}"
)
self.logger.info(f"Rotation disabled for {platform}: {reason}")
except Exception as e:
self.logger.error(f"Failed to disable rotation for {platform}: {e}")
def _reset_method_cooldowns(self, platform: str) -> None:
"""Reset all method cooldowns for emergency recovery"""
try:
strategies = self.method_rotation_use_case.strategy_repo.find_by_platform(platform)
for strategy in strategies:
strategy.last_failure = None
strategy.cooldown_period = 0
self.method_rotation_use_case.strategy_repo.save(strategy)
self.logger.info(f"Method cooldowns reset for {platform}")
except Exception as e:
self.logger.error(f"Failed to reset cooldowns for {platform}: {e}")
def _init_error_patterns(self) -> Dict[str, List[str]]:
"""Initialize error classification patterns"""
return {
'rate_limit': [
'rate limit', 'too many requests', 'quota exceeded',
'zu viele anfragen', 'rate limiting', 'throttled'
],
'account_suspended': [
'suspended', 'banned', 'blocked', 'gesperrt',
'account disabled', 'violation', 'restricted'
],
'network_error': [
'network error', 'connection failed', 'timeout',
'netzwerkfehler', 'verbindung fehlgeschlagen', 'dns error'
],
'verification_failed': [
'verification failed', 'captcha', 'human verification',
'verifizierung fehlgeschlagen', 'bot detected'
],
'system_error': [
'internal server error', 'service unavailable', 'maintenance',
'server fehler', 'wartung', 'system down'
]
}
def _init_fallback_strategies(self) -> Dict[str, Callable]:
"""Initialize fallback strategy functions"""
return {
'rate_limit': self._handle_rate_limit_fallback,
'account_suspended': self._handle_suspension_fallback,
'network_error': self._handle_network_fallback,
'verification_failed': self._handle_verification_fallback,
'system_error': self._handle_system_error_fallback
}
def _handle_rate_limit_fallback(self, platform: str, error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle rate limiting with extended cooldowns"""
# Extend cooldown periods for all methods
strategies = self.method_rotation_use_case.strategy_repo.find_by_platform(platform)
for strategy in strategies:
strategy.cooldown_period = max(strategy.cooldown_period * 2, 1800) # At least 30 minutes
self.method_rotation_use_case.strategy_repo.save(strategy)
return {'action': 'extended_cooldown', 'cooldown_minutes': 30}
def _handle_suspension_fallback(self, platform: str, error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle account suspension with method blocking"""
# Enable emergency mode with only safest methods
self.method_rotation_use_case.enable_emergency_mode(platform, "account_suspension")
return {'action': 'emergency_mode', 'reason': 'account_suspension'}
def _handle_network_fallback(self, platform: str, error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle network errors with retry strategy"""
return {'action': 'retry_with_delay', 'delay_seconds': 60}
def _handle_verification_fallback(self, platform: str, error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle verification failures with method rotation"""
return {'action': 'rotate_method', 'reason': 'verification_failed'}
def _handle_system_error_fallback(self, platform: str, error_details: Dict[str, Any]) -> Dict[str, Any]:
"""Handle system errors with graceful degradation"""
self._disable_rotation_for_platform(platform, "system_error")
return {'action': 'disable_rotation', 'reason': 'system_error'}

Datei anzeigen

@ -0,0 +1,46 @@
"""
Safe imports for platform controllers.
Provides fallback when PyQt5 is not available during testing.
"""
try:
from PyQt5.QtCore import QObject, QThread, pyqtSignal
PYQT5_AVAILABLE = True
except ImportError:
# Fallback for testing without PyQt5
class QObject:
def __init__(self):
pass
class QThread(QObject):
def __init__(self):
super().__init__()
self.running = True
def start(self):
pass
def stop(self):
self.running = False
def isRunning(self):
return self.running
def quit(self):
self.running = False
def wait(self):
pass
def pyqtSignal(*args, **kwargs):
"""Mock pyqtSignal for testing"""
class MockSignal:
def connect(self, func):
pass
def emit(self, *args):
pass
return MockSignal()
PYQT5_AVAILABLE = False
__all__ = ['QObject', 'QThread', 'pyqtSignal', 'PYQT5_AVAILABLE']

Datei anzeigen

@ -0,0 +1,419 @@
"""
Controller für TikTok-spezifische Funktionalität.
Mit TextSimilarity-Integration für robusteres UI-Element-Matching.
"""
import time
import random
from PyQt5.QtCore import QThread, pyqtSignal, QObject
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.generator_tab import GeneratorTab
from views.tabs.accounts_tab import AccountsTab
from views.tabs.settings_tab import SettingsTab
from views.widgets.forge_animation_widget import ForgeAnimationDialog
from social_networks.tiktok.tiktok_automation import TikTokAutomation
from utils.text_similarity import TextSimilarity
from utils.logger import setup_logger
logger = setup_logger("tiktok_controller")
# Legacy WorkerThread als Backup beibehalten
class LegacyTikTokWorkerThread(QThread):
"""Legacy Thread für die TikTok-Account-Erstellung (Backup)."""
# Signale
update_signal = pyqtSignal(str)
log_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int)
finished_signal = pyqtSignal(dict)
error_signal = pyqtSignal(str)
def __init__(self, params):
super().__init__()
self.params = params
self.running = True
# TextSimilarity für robustes Fehler-Matching
self.text_similarity = TextSimilarity(default_threshold=0.7)
# Fehler-Patterns für robustes Fehler-Matching
self.error_patterns = [
"Fehler", "Error", "Fehlgeschlagen", "Failed", "Problem", "Issue",
"Nicht möglich", "Not possible", "Bitte versuchen Sie es erneut",
"Please try again", "Konnte nicht", "Could not", "Timeout"
]
def run(self):
"""Führt die Account-Erstellung aus."""
try:
self.log_signal.emit("TikTok-Account-Erstellung gestartet...")
self.progress_signal.emit(10)
# TikTok-Automation initialisieren
automation = TikTokAutomation(
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")
)
self.update_signal.emit("TikTok-Automation initialisiert")
self.progress_signal.emit(20)
# Account registrieren
self.log_signal.emit(f"Registriere Account für: {self.params['full_name']}")
# Account registrieren - immer mit Email
result = automation.register_account(
full_name=self.params["full_name"],
age=self.params["age"],
registration_method="email", # Immer Email-Registrierung
phone_number=None, # Keine Telefonnummer
**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:
# Robuste Fehlerbehandlung mit TextSimilarity
error_msg = result.get("error", "Unbekannter Fehler")
# Versuche, Fehler nutzerfreundlicher zu interpretieren
user_friendly_error = self._interpret_error(error_msg)
self.log_signal.emit(f"Fehler bei der Account-Erstellung: {user_friendly_error}")
self.error_signal.emit(user_friendly_error)
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))
self.progress_signal.emit(0)
def _interpret_error(self, error_msg: str) -> str:
"""
Interpretiert Fehlermeldungen und gibt eine benutzerfreundlichere Version zurück.
Verwendet TextSimilarity für robusteres Fehler-Matching.
Args:
error_msg: Die ursprüngliche Fehlermeldung
Returns:
str: Benutzerfreundliche Fehlermeldung
"""
# Bekannte Fehlermuster und deren Interpretationen
error_interpretations = {
"captcha": "TikTok hat einen Captcha-Test angefordert. Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy.",
"verification": "Es gab ein Problem mit der Verifizierung des Accounts. Bitte prüfen Sie die E-Mail-Einstellungen.",
"proxy": "Problem mit der Proxy-Verbindung. Bitte prüfen Sie Ihre Proxy-Einstellungen.",
"timeout": "Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie Ihre Internetverbindung.",
"username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig.",
"password": "Das Passwort erfüllt nicht die Anforderungen von TikTok.",
"email": "Die E-Mail-Adresse konnte nicht verwendet werden. Bitte nutzen Sie eine andere E-Mail-Domain.",
"phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden.",
"age": "Das eingegebene Alter erfüllt nicht die Anforderungen von TikTok.",
"too_many_attempts": "Zu viele Registrierungsversuche. Bitte warten Sie und versuchen Sie es später erneut."
}
# Versuche, den Fehler zu kategorisieren
for pattern, interpretation in error_interpretations.items():
for error_term in self.error_patterns:
if (pattern in error_msg.lower() or
self.text_similarity.is_similar(error_term, error_msg, threshold=0.7)):
return interpretation
# Fallback: Originale Fehlermeldung zurückgeben
return error_msg
def stop(self):
"""Stoppt den Thread."""
self.running = False
self.terminate()
# Neue Implementation mit BaseWorkerThread
class TikTokWorkerThread(BaseAccountCreationWorkerThread):
"""Refaktorierte TikTok Worker Thread Implementation"""
def __init__(self, params, session_controller=None, generator_tab=None):
super().__init__(params, "TikTok", session_controller, generator_tab)
def get_automation_class(self):
from social_networks.tiktok.tiktok_automation import TikTokAutomation
return TikTokAutomation
def get_error_interpretations(self) -> Dict[str, str]:
return {
"captcha": "TikTok hat einen Captcha-Test angefordert. Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy.",
"verification": "Es gab ein Problem mit der Verifizierung des Accounts. Bitte prüfen Sie die E-Mail-Einstellungen.",
"proxy": "Problem mit der Proxy-Verbindung. Bitte prüfen Sie Ihre Proxy-Einstellungen.",
"timeout": "Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie Ihre Internetverbindung.",
"username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig.",
"password": "Das Passwort erfüllt nicht die Anforderungen von TikTok.",
"email": "Die E-Mail-Adresse konnte nicht verwendet werden. Bitte nutzen Sie eine andere E-Mail-Domain.",
"phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden.",
"phone number required": "Telefonnummer erforderlich",
"invalid code": "Ungültiger Verifizierungscode",
"age": "Das eingegebene Alter erfüllt nicht die Anforderungen von TikTok.",
"too_many_attempts": "Zu viele Registrierungsversuche. Bitte warten Sie und versuchen Sie es später erneut.",
"rate limit": "Zu viele Versuche - bitte später erneut versuchen",
"already taken": "Der gewählte Benutzername ist bereits vergeben",
"weak password": "Das Passwort ist zu schwach",
"network error": "Netzwerkfehler - bitte Internetverbindung prüfen"
}
class TikTokController(BasePlatformController):
"""Controller für TikTok-spezifische Funktionalität."""
def __init__(self, db_manager, proxy_rotator, email_handler, language_manager=None):
super().__init__("TikTok", db_manager, proxy_rotator, email_handler, language_manager)
self.worker_thread = None
# TextSimilarity für robustes UI-Element-Matching
self.text_similarity = TextSimilarity(default_threshold=0.75)
def create_generator_tab(self):
"""Erstellt den TikTok-Generator-Tab."""
generator_tab = GeneratorTab(self.platform_name, self.language_manager)
# TikTok-spezifische Anpassungen
# Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden
# Signale verbinden
generator_tab.start_requested.connect(self.start_account_creation)
generator_tab.stop_requested.connect(self.stop_account_creation)
return generator_tab
def start_account_creation(self, params):
"""Startet die TikTok-Account-Erstellung."""
super().start_account_creation(params)
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Schmiedeanimation-Dialog erstellen und anzeigen
parent_widget = generator_tab.window() # Hauptfenster als Parent
self.forge_dialog = ForgeAnimationDialog(parent_widget, "TikTok")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Fingerprint VOR Account-Erstellung generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
fingerprint_service = FingerprintGeneratorService()
# Generiere einen neuen Fingerprint für diesen Account
fingerprint = fingerprint_service.generate_fingerprint()
# Das ist bereits ein BrowserFingerprint-Objekt, kein Dict!
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
# Konvertiere zu Dictionary für Übertragung
params["fingerprint"] = fingerprint.to_dict()
logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Fortfahren ohne Fingerprint - wird später generiert
# Worker-Thread starten mit optionalen Parametern
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
self.worker_thread = TikTokWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Updates an Forge-Dialog weiterleiten
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab für Backup
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen und Animation starten
self.forge_dialog.start_animation()
self.forge_dialog.show()
def stop_account_creation(self):
"""Stoppt die TikTok-Account-Erstellung."""
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
generator_tab = self.get_generator_tab()
generator_tab.add_log("Account-Erstellung wurde abgebrochen")
generator_tab.set_running(False)
generator_tab.set_progress(0)
# Forge-Dialog schließen falls vorhanden
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
def handle_account_created(self, result):
"""Verarbeitet erfolgreich erstellte Accounts mit Clean Architecture."""
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
# Account-Daten aus dem Ergebnis holen
account_data = result.get("account_data", {})
# Account und Session über SessionController speichern (Clean Architecture)
if hasattr(self, 'session_controller') and self.session_controller:
try:
session_data = result.get("session_data", {})
save_result = self.session_controller.create_and_save_account(
platform=self.platform_name,
account_data=account_data
)
if save_result.get('success'):
logger.info(f"Account und Session erfolgreich gespeichert")
# Erfolgsmeldung anzeigen (nur einmal!)
account_info = save_result.get('account_data', {})
from PyQt5.QtWidgets import QMessageBox
QMessageBox.information(
generator_tab,
"Erfolg",
f"Account erfolgreich erstellt!\n\n"
f"Benutzername: {account_info.get('username', '')}\n"
f"Passwort: {account_info.get('password', '')}\n"
f"E-Mail/Telefon: {account_info.get('email') or account_info.get('phone', '')}"
)
# Signal senden, um zur Hauptseite zurückzukehren
if hasattr(self, 'return_to_main_requested') and callable(self.return_to_main_requested):
self.return_to_main_requested()
else:
error_msg = save_result.get('message', 'Unbekannter Fehler')
logger.error(f"Fehler beim Speichern: {error_msg}")
from views.widgets.modern_message_box import show_error
show_error(
generator_tab,
"Fehler beim Speichern",
f"Beim Speichern des Accounts ist ein Fehler aufgetreten:\n\n{error_msg}"
)
except Exception as e:
logger.error(f"Fehler beim Speichern des Accounts: {e}")
from views.widgets.modern_message_box import show_critical
show_critical(
generator_tab,
"Unerwarteter Fehler",
f"Ein unerwarteter Fehler ist beim Speichern des Accounts aufgetreten:\n\n{str(e)}"
)
else:
# Fallback: Alte Methode falls SessionController nicht verfügbar
logger.warning("SessionController nicht verfügbar, verwende alte Methode")
generator_tab.account_created.emit(self.platform_name, account_data)
if hasattr(self, 'return_to_main_requested') and callable(self.return_to_main_requested):
self.return_to_main_requested()
# save_account_to_db wurde entfernt - Accounts werden jetzt über SessionController gespeichert
def validate_inputs(self, inputs):
"""
Validiert die Eingaben für die Account-Erstellung.
Verwendet TextSimilarity für robustere Validierung.
"""
# Basis-Validierungen von BasePlatformController verwenden
valid, error_msg = super().validate_inputs(inputs)
if not valid:
return valid, error_msg
# TikTok-spezifische Validierungen
age = inputs.get("age", 0)
if age < 13:
return False, "Das Alter muss mindestens 13 sein (TikTok-Anforderung)."
# E-Mail-Domain-Validierung (immer Email-Registrierung)
email_domain = inputs.get("email_domain", "")
# Blacklist von bekannten problematischen Domains
blacklisted_domains = ["temp-mail.org", "guerrillamail.com", "maildrop.cc"]
# Prüfe mit TextSimilarity auf Ähnlichkeit mit Blacklist
for domain in blacklisted_domains:
if self.text_similarity.is_similar(email_domain, domain, threshold=0.8):
return False, f"Die E-Mail-Domain '{email_domain}' kann problematisch für die TikTok-Registrierung sein. Bitte verwenden Sie eine andere Domain."
return True, ""
def get_form_field_label(self, field_type: str) -> str:
"""
Gibt einen Label-Text für ein Formularfeld basierend auf dem Feldtyp zurück.
Args:
field_type: Typ des Formularfelds
Returns:
str: Label-Text für das Formularfeld
"""
# Mapping von Feldtypen zu Labels
field_labels = {
"full_name": "Vollständiger Name",
"username": "Benutzername",
"password": "Passwort",
"email": "E-Mail-Adresse",
"phone": "Telefonnummer",
"age": "Alter",
"birthday": "Geburtsdatum"
}
return field_labels.get(field_type, field_type.capitalize())
def _handle_error(self, error_msg: str):
"""Behandelt Fehler während der Account-Erstellung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Fehler anzeigen
generator_tab = self.get_generator_tab()
generator_tab.show_error(error_msg)
generator_tab.set_running(False)
def _handle_finished(self, result: dict):
"""Behandelt das Ende der Account-Erstellung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Normale Verarbeitung
self.handle_account_created(result)

Datei anzeigen

@ -0,0 +1,159 @@
"""
Controller für VK-spezifische Funktionalität
"""
import logging
from typing import Dict, Any
from controllers.platform_controllers.base_controller import BasePlatformController
from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread
from social_networks.vk.vk_automation import VKAutomation
from utils.logger import setup_logger
logger = setup_logger("vk_controller")
class VKWorkerThread(BaseAccountCreationWorkerThread):
"""Worker-Thread für VK-Account-Erstellung"""
def __init__(self, params, session_controller=None, generator_tab=None):
super().__init__(params, "VK", session_controller, generator_tab)
def get_automation_class(self):
"""Gibt die VK-Automation-Klasse zurück"""
return VKAutomation
def get_error_interpretations(self) -> Dict[str, str]:
"""VK-spezifische Fehlerinterpretationen"""
return {
"phone": "Diese Telefonnummer wird bereits verwendet oder ist ungültig.",
"code": "Der Verifizierungscode ist ungültig.",
"blocked": "Zu viele Versuche. Bitte versuchen Sie es später erneut.",
"captcha": "Bitte lösen Sie das Captcha."
}
class VKController(BasePlatformController):
"""Controller für VK-Funktionalität"""
def __init__(self, db_manager, proxy_rotator, email_handler, language_manager, theme_manager=None):
super().__init__("vk", db_manager, proxy_rotator, email_handler, language_manager)
logger.info("VK Controller initialisiert")
def get_worker_thread_class(self):
"""Gibt die Worker-Thread-Klasse für VK zurück"""
return VKWorkerThread
def get_platform_display_name(self) -> str:
"""Gibt den Anzeigenamen der Plattform zurück"""
return "VK"
def validate_account_data(self, account_data: Dict[str, Any]) -> Dict[str, Any]:
"""Validiert die Account-Daten für VK"""
errors = []
# Pflichtfelder prüfen
if not account_data.get("first_name"):
errors.append("Vorname ist erforderlich")
if not account_data.get("last_name"):
errors.append("Nachname ist erforderlich")
if not account_data.get("phone"):
errors.append("Telefonnummer ist für VK erforderlich")
if errors:
return {
"valid": False,
"errors": errors
}
return {
"valid": True,
"errors": []
}
def get_default_settings(self) -> Dict[str, Any]:
"""Gibt die Standard-Einstellungen für VK zurück"""
settings = super().get_default_settings()
settings.update({
"require_phone": True,
"require_email": False,
"default_country_code": "+7", # Russland
"supported_languages": ["ru", "en", "de"],
"default_language": "ru"
})
return settings
def start_account_creation(self, params):
"""Startet die VK-Account-Erstellung."""
logger.info(f"Starte VK Account-Erstellung mit Parametern: {params}")
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Schmiedeanimation-Dialog erstellen und anzeigen
from views.widgets.forge_animation_widget import ForgeAnimationDialog
parent_widget = generator_tab.window()
self.forge_dialog = ForgeAnimationDialog(parent_widget, "VK")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Fingerprint generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
fingerprint_service = FingerprintGeneratorService()
fingerprint_data = fingerprint_service.generate_fingerprint()
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
params["fingerprint"] = fingerprint.to_dict()
logger.info(f"Fingerprint für VK Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Worker-Thread starten
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
self.worker_thread = VKWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Signals verbinden
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen
self.forge_dialog.start_animation()
self.forge_dialog.show()

Datei anzeigen

@ -0,0 +1,417 @@
"""
Controller für X (Twitter)-spezifische Funktionalität.
Mit TextSimilarity-Integration für robusteres UI-Element-Matching.
"""
import logging
import time
import random
from PyQt5.QtCore import QThread, pyqtSignal, QObject
from typing import Dict, Any, Tuple
from controllers.platform_controllers.base_controller import BasePlatformController
from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread
from views.tabs.generator_tab import GeneratorTab
from views.tabs.accounts_tab import AccountsTab
from views.tabs.settings_tab import SettingsTab
from views.widgets.forge_animation_widget import ForgeAnimationDialog
from social_networks.x.x_automation import XAutomation
from utils.text_similarity import TextSimilarity
from utils.logger import setup_logger
logger = setup_logger("x_controller")
# Legacy WorkerThread als Backup beibehalten
class LegacyXWorkerThread(QThread):
"""Legacy Thread für die X-Account-Erstellung (Backup)."""
# Signale
update_signal = pyqtSignal(str)
log_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int)
finished_signal = pyqtSignal(dict)
error_signal = pyqtSignal(str)
def __init__(self, params):
super().__init__()
self.params = params
self.running = True
# TextSimilarity für robustes Fehler-Matching
self.text_similarity = TextSimilarity(default_threshold=0.7)
# Fehler-Patterns für robustes Fehler-Matching
self.error_patterns = [
"Fehler", "Error", "Fehlgeschlagen", "Failed", "Problem", "Issue",
"Nicht möglich", "Not possible", "Bitte versuchen Sie es erneut",
"Please try again", "Konnte nicht", "Could not", "Timeout"
]
def run(self):
"""Führt die Account-Erstellung aus."""
try:
self.log_signal.emit("X-Account-Erstellung gestartet...")
self.progress_signal.emit(10)
# X-Automation initialisieren
automation = XAutomation(
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")
)
self.update_signal.emit("X-Automation initialisiert")
self.progress_signal.emit(20)
# Account registrieren
self.log_signal.emit(f"Registriere Account für: {self.params['full_name']}")
# Account registrieren - immer mit Email
result = automation.register_account(
full_name=self.params["full_name"],
age=self.params["age"],
registration_method="email", # Immer Email-Registrierung
phone_number=None, # Keine Telefonnummer
**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:
# Robuste Fehlerbehandlung mit TextSimilarity
error_msg = result.get("error", "Unbekannter Fehler")
# Versuche, Fehler nutzerfreundlicher zu interpretieren
user_friendly_error = self._interpret_error(error_msg)
self.log_signal.emit(f"Fehler bei der Account-Erstellung: {user_friendly_error}")
self.error_signal.emit(user_friendly_error)
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))
self.progress_signal.emit(0)
def _interpret_error(self, error_msg: str) -> str:
"""
Interpretiert Fehlermeldungen und gibt eine benutzerfreundlichere Version zurück.
Verwendet TextSimilarity für robusteres Fehler-Matching.
Args:
error_msg: Die ursprüngliche Fehlermeldung
Returns:
str: Benutzerfreundliche Fehlermeldung
"""
# Bekannte Fehlermuster und deren Interpretationen
error_interpretations = {
"captcha": "X hat einen Captcha-Test angefordert. Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy.",
"verification": "Es gab ein Problem mit der Verifizierung des Accounts. Bitte prüfen Sie die E-Mail-Einstellungen.",
"proxy": "Problem mit der Proxy-Verbindung. Bitte prüfen Sie Ihre Proxy-Einstellungen.",
"timeout": "Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie Ihre Internetverbindung.",
"username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig.",
"password": "Das Passwort erfüllt nicht die Anforderungen von X.",
"email": "Die E-Mail-Adresse konnte nicht verwendet werden. Bitte nutzen Sie eine andere E-Mail-Domain.",
"phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden.",
"rate limit": "Zu viele Anfragen. Bitte warten Sie einige Minuten und versuchen Sie es erneut.",
"suspended": "Account wurde gesperrt. Möglicherweise wurden Sicherheitsrichtlinien verletzt."
}
# Versuche, den Fehler zu kategorisieren
for pattern, interpretation in error_interpretations.items():
for error_term in self.error_patterns:
if (pattern in error_msg.lower() or
self.text_similarity.is_similar(error_term, error_msg, threshold=0.7)):
return interpretation
# Fallback: Originale Fehlermeldung zurückgeben
return error_msg
def stop(self):
"""Stoppt den Thread."""
self.running = False
self.terminate()
# Neue Implementation mit BaseWorkerThread
class XWorkerThread(BaseAccountCreationWorkerThread):
"""Refaktorierte X Worker Thread Implementation"""
def __init__(self, params, session_controller=None, generator_tab=None):
super().__init__(params, "X", session_controller, generator_tab)
def get_automation_class(self):
from social_networks.x.x_automation import XAutomation
return XAutomation
def get_error_interpretations(self) -> Dict[str, str]:
return {
"already taken": "Dieser Benutzername ist bereits vergeben",
"weak password": "Das Passwort ist zu schwach",
"rate limit": "Zu viele Versuche - bitte später erneut versuchen",
"network error": "Netzwerkfehler - bitte Internetverbindung prüfen",
"captcha": "Captcha-Verifizierung erforderlich",
"verification": "Es gab ein Problem mit der Verifizierung des Accounts",
"proxy": "Problem mit der Proxy-Verbindung",
"timeout": "Zeitüberschreitung bei der Verbindung",
"username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig",
"password": "Das Passwort erfüllt nicht die Anforderungen von X",
"email": "Die E-Mail-Adresse konnte nicht verwendet werden",
"suspended": "Account wurde gesperrt"
}
class XController(BasePlatformController):
"""Controller für X (Twitter)-spezifische Funktionalität."""
def __init__(self, db_manager, proxy_rotator, email_handler, language_manager=None):
super().__init__("X", db_manager, proxy_rotator, email_handler, language_manager)
self.worker_thread = None
# TextSimilarity für robustes UI-Element-Matching
self.text_similarity = TextSimilarity(default_threshold=0.75)
def create_generator_tab(self):
"""Erstellt den X-Generator-Tab."""
generator_tab = GeneratorTab(self.platform_name, self.language_manager)
# X-spezifische Anpassungen
# Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden
# Signale verbinden
generator_tab.start_requested.connect(self.start_account_creation)
generator_tab.stop_requested.connect(self.stop_account_creation)
return generator_tab
def start_account_creation(self, params):
"""Startet die X-Account-Erstellung."""
super().start_account_creation(params)
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Schmiedeanimation-Dialog erstellen und anzeigen
parent_widget = generator_tab.window() # Hauptfenster als Parent
self.forge_dialog = ForgeAnimationDialog(parent_widget, "X")
self.forge_dialog.cancel_clicked.connect(self.stop_account_creation)
self.forge_dialog.closed.connect(self.stop_account_creation)
# Fensterposition vom Hauptfenster holen
if parent_widget:
window_pos = parent_widget.pos()
params["window_position"] = (window_pos.x(), window_pos.y())
# Fingerprint VOR Account-Erstellung generieren
try:
from infrastructure.services.fingerprint.fingerprint_generator_service import FingerprintGeneratorService
from domain.entities.browser_fingerprint import BrowserFingerprint
import uuid
fingerprint_service = FingerprintGeneratorService()
# Generiere einen neuen Fingerprint für diesen Account
fingerprint_data = fingerprint_service.generate_fingerprint()
# Erstelle BrowserFingerprint Entity mit allen notwendigen Daten
fingerprint = BrowserFingerprint.from_dict(fingerprint_data)
fingerprint.fingerprint_id = str(uuid.uuid4())
fingerprint.account_bound = True
fingerprint.rotation_seed = str(uuid.uuid4())
# Konvertiere zu Dictionary für Übertragung
params["fingerprint"] = fingerprint.to_dict()
logger.info(f"Fingerprint für neue Account-Erstellung generiert: {fingerprint.fingerprint_id}")
except Exception as e:
logger.error(f"Fehler beim Generieren des Fingerprints: {e}")
# Fortfahren ohne Fingerprint - wird später generiert
# Worker-Thread starten mit optionalen Parametern
session_controller = getattr(self, 'session_controller', None)
generator_tab_ref = generator_tab if hasattr(generator_tab, 'store_created_account') else None
self.worker_thread = XWorkerThread(
params,
session_controller=session_controller,
generator_tab=generator_tab_ref
)
# Updates an Forge-Dialog weiterleiten
self.worker_thread.update_signal.connect(self.forge_dialog.set_status)
self.worker_thread.log_signal.connect(self.forge_dialog.add_log)
self.worker_thread.error_signal.connect(self._handle_error)
self.worker_thread.finished_signal.connect(self._handle_finished)
self.worker_thread.progress_signal.connect(self.forge_dialog.set_progress)
# Auch an Generator-Tab für Backup
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
# Dialog anzeigen und Animation starten
self.forge_dialog.start_animation()
self.forge_dialog.show()
def stop_account_creation(self):
"""Stoppt die X-Account-Erstellung."""
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
generator_tab = self.get_generator_tab()
generator_tab.add_log("Account-Erstellung wurde abgebrochen")
generator_tab.set_running(False)
generator_tab.set_progress(0)
# Forge-Dialog schließen falls vorhanden
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
def handle_account_created(self, result):
"""Verarbeitet erfolgreich erstellte Accounts mit Clean Architecture."""
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
# Account-Daten aus dem Ergebnis holen
account_data = result.get("account_data", {})
# Account und Session über SessionController speichern (Clean Architecture)
if hasattr(self, 'session_controller') and self.session_controller:
try:
session_data = result.get("session_data", {})
save_result = self.session_controller.create_and_save_account(
platform=self.platform_name,
account_data=account_data
)
if save_result.get('success'):
logger.info(f"Account und Session erfolgreich gespeichert")
# Erfolgsmeldung anzeigen (nur einmal!)
account_info = save_result.get('account_data', {})
from PyQt5.QtWidgets import QMessageBox
QMessageBox.information(
generator_tab,
"Erfolg",
f"Account erfolgreich erstellt!\n\n"
f"Benutzername: {account_info.get('username', '')}\n"
f"Passwort: {account_info.get('password', '')}\n"
f"E-Mail/Telefon: {account_info.get('email') or account_info.get('phone', '')}"
)
# Signal senden, um zur Hauptseite zurückzukehren
if hasattr(self, 'return_to_main_requested') and callable(self.return_to_main_requested):
self.return_to_main_requested()
else:
error_msg = save_result.get('message', 'Unbekannter Fehler')
logger.error(f"Fehler beim Speichern: {error_msg}")
from views.widgets.modern_message_box import show_error
show_error(
generator_tab,
"Fehler beim Speichern",
f"Beim Speichern des Accounts ist ein Fehler aufgetreten:\n\n{error_msg}"
)
except Exception as e:
logger.error(f"Fehler beim Speichern des Accounts: {e}")
from views.widgets.modern_message_box import show_critical
show_critical(
generator_tab,
"Unerwarteter Fehler",
f"Ein unerwarteter Fehler ist beim Speichern des Accounts aufgetreten:\n\n{str(e)}"
)
else:
# Fallback: Alte Methode falls SessionController nicht verfügbar
logger.warning("SessionController nicht verfügbar, verwende alte Methode")
generator_tab.account_created.emit(self.platform_name, account_data)
if hasattr(self, 'return_to_main_requested') and callable(self.return_to_main_requested):
self.return_to_main_requested()
# save_account_to_db wurde entfernt - Accounts werden jetzt über SessionController gespeichert
def validate_inputs(self, inputs):
"""
Validiert die Eingaben für die Account-Erstellung.
Verwendet TextSimilarity für robustere Validierung.
"""
# Basis-Validierungen von BasePlatformController verwenden
valid, error_msg = super().validate_inputs(inputs)
if not valid:
return valid, error_msg
# X-spezifische Validierungen
age = inputs.get("age", 0)
if age < 13:
return False, "Das Alter muss mindestens 13 sein (X-Anforderung)."
# E-Mail-Domain-Validierung (immer Email-Registrierung)
email_domain = inputs.get("email_domain", "")
# Blacklist von bekannten problematischen Domains
blacklisted_domains = ["temp-mail.org", "guerrillamail.com", "maildrop.cc"]
# Prüfe mit TextSimilarity auf Ähnlichkeit mit Blacklist
for domain in blacklisted_domains:
if self.text_similarity.is_similar(email_domain, domain, threshold=0.8):
return False, f"Die E-Mail-Domain '{email_domain}' kann problematisch für die X-Registrierung sein. Bitte verwenden Sie eine andere Domain."
return True, ""
def _handle_error(self, error_msg: str):
"""Behandelt Fehler während der Account-Erstellung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Fehler anzeigen
generator_tab = self.get_generator_tab()
generator_tab.show_error(error_msg)
generator_tab.set_running(False)
def _handle_finished(self, result: dict):
"""Behandelt das Ende der Account-Erstellung"""
# Forge-Dialog schließen
if hasattr(self, 'forge_dialog') and self.forge_dialog:
self.forge_dialog.close()
self.forge_dialog = None
# Normale Verarbeitung
self.handle_account_created(result)
def get_form_field_label(self, field_type: str) -> str:
"""
Gibt einen Label-Text für ein Formularfeld basierend auf dem Feldtyp zurück.
Args:
field_type: Typ des Formularfelds
Returns:
str: Label-Text für das Formularfeld
"""
# Mapping von Feldtypen zu Labels
field_labels = {
"full_name": "Vollständiger Name",
"username": "Benutzername",
"password": "Passwort",
"email": "E-Mail-Adresse",
"phone": "Telefonnummer",
"age": "Alter",
"birthday": "Geburtsdatum"
}
return field_labels.get(field_type, field_type.capitalize())

Datei anzeigen

@ -0,0 +1,321 @@
"""
Session Controller - Verwaltet Browser-Sessions und Ein-Klick-Login
"""
import logging
from typing import Dict, Any, Optional, List
from PyQt5.QtCore import QObject, pyqtSignal, QTimer
from PyQt5.QtWidgets import QMessageBox
from application.use_cases.one_click_login_use_case import OneClickLoginUseCase
from infrastructure.repositories.fingerprint_repository import FingerprintRepository
from infrastructure.repositories.account_repository import AccountRepository
logger = logging.getLogger("session_controller")
class SessionController(QObject):
"""Controller für Ein-Klick-Login (ohne Session-Speicherung)"""
# Signale
login_started = pyqtSignal(str) # account_id
login_successful = pyqtSignal(str, dict) # account_id, login_data
login_failed = pyqtSignal(str, str) # account_id, error_message
def __init__(self, db_manager):
super().__init__()
self.db_manager = db_manager
# Repositories initialisieren
self.fingerprint_repository = FingerprintRepository(db_manager.db_path)
self.account_repository = AccountRepository(db_manager.db_path)
# Import Fingerprint Generator Use Case
from application.use_cases.generate_account_fingerprint_use_case import GenerateAccountFingerprintUseCase
self.fingerprint_generator = GenerateAccountFingerprintUseCase(db_manager)
# Use Cases initialisieren
self.one_click_login_use_case = OneClickLoginUseCase(
self.fingerprint_repository,
self.account_repository
)
def perform_one_click_login(self, account_data: Dict[str, Any]):
"""
Führt Ein-Klick-Login für einen Account durch.
Args:
account_data: Dict mit Account-Daten inkl. id, platform, username, etc.
"""
account_id = str(account_data.get("id", ""))
platform = account_data.get("platform", "")
username = account_data.get("username", "")
logger.info(f"Ein-Klick-Login für Account {username} (ID: {account_id}) auf {platform}")
self.login_started.emit(account_id)
try:
# Stelle sicher, dass Account einen Fingerprint hat
fingerprint_id = account_data.get("fingerprint_id")
if not fingerprint_id:
logger.info(f"Generiere Fingerprint für Account {account_id}")
fingerprint_id = self.fingerprint_generator.execute(int(account_id))
if not fingerprint_id:
raise Exception("Konnte keinen Fingerprint generieren")
# Session-basierter Login deaktiviert - führe immer normalen Login durch
logger.info(f"Starte normalen Login für Account {account_id} (Session-Login deaktiviert)")
# Zeige Login-Dialog an bevor der Login startet
from views.widgets.forge_animation_widget import ForgeAnimationDialog
from PyQt5.QtWidgets import QApplication
# Hole das Hauptfenster als Parent
main_window = None
for widget in QApplication.topLevelWidgets():
if widget.objectName() == "AccountForgerMainWindow":
main_window = widget
break
self.login_dialog = ForgeAnimationDialog(main_window, platform, is_login=True)
self.login_dialog.cancel_clicked.connect(lambda: self._cancel_login(account_id))
self.login_dialog.closed.connect(lambda: self._cancel_login(account_id))
# Dialog anzeigen
self.login_dialog.start_animation()
self.login_dialog.show()
# Account-Daten direkt aus DB laden
account = self.account_repository.get_by_id(int(account_id))
if account:
account_login_data = {
'username': account.get('username'),
'password': account.get('password'),
'platform': account.get('platform'),
'fingerprint_id': account.get('fingerprint_id')
}
# Fensterposition vom Hauptfenster holen
if main_window:
window_pos = main_window.pos()
account_login_data['window_position'] = (window_pos.x(), window_pos.y())
self._perform_normal_login(account_id, account_login_data)
else:
error_msg = f"Account mit ID {account_id} nicht gefunden"
logger.error(error_msg)
self.login_failed.emit(account_id, error_msg)
except Exception as e:
logger.error(f"Fehler beim Ein-Klick-Login: {e}")
self.login_failed.emit(account_id, str(e))
def _cancel_login(self, account_id: str):
"""Bricht den Login-Prozess ab"""
logger.info(f"Login für Account {account_id} wurde abgebrochen")
if hasattr(self, 'login_dialog') and self.login_dialog:
self.login_dialog.close()
self.login_dialog = None
# TODO: Login-Worker stoppen falls vorhanden
def create_and_save_account(self, platform: str, account_data: Dict[str, Any]):
"""
Erstellt und speichert einen neuen Account (ohne Session-Speicherung).
Args:
platform: Plattform-Name
account_data: Account-Informationen (username, password, etc.)
Returns:
Dict mit success, account_id und message
"""
try:
# Account in DB speichern
from datetime import datetime
account_record = {
"platform": platform.lower(),
"username": account_data.get("username", ""),
"password": account_data.get("password", ""),
"email": account_data.get("email", ""),
"phone": account_data.get("phone", ""),
"full_name": account_data.get("full_name", ""),
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
account_id = self.db_manager.add_account(account_record)
logger.info(f"Account in Datenbank gespeichert: {account_record['username']} (ID: {account_id})")
# Fingerprint für Account generieren
if account_id and account_id > 0:
logger.info(f"Generiere Fingerprint für neuen Account {account_id}")
fingerprint_id = self.fingerprint_generator.execute(account_id)
if fingerprint_id:
logger.info(f"Fingerprint {fingerprint_id} wurde Account {account_id} zugewiesen")
return {
'success': True,
'account_id': account_id,
'account_data': account_record,
'message': 'Account erfolgreich erstellt'
}
except Exception as e:
logger.error(f"Fehler beim Erstellen des Accounts: {e}")
return {
'success': False,
'error': str(e),
'message': f'Fehler beim Erstellen des Accounts: {str(e)}'
}
def _perform_normal_login(self, account_id: str, account_data: Dict[str, Any], automation=None):
"""
Führt einen normalen Login durch wenn keine Session vorhanden ist.
Args:
account_id: Account ID
account_data: Account-Daten inkl. Username, Password, Platform, Fingerprint
automation: Optionale bereits erstellte Automation-Instanz
"""
from PyQt5.QtCore import QThread, pyqtSignal
from social_networks.instagram.instagram_automation import InstagramAutomation
from social_networks.tiktok.tiktok_automation import TikTokAutomation
class LoginWorkerThread(QThread):
"""Worker Thread für den Login-Prozess"""
login_completed = pyqtSignal(str, dict) # account_id, result
login_failed = pyqtSignal(str, str) # account_id, error
status_update = pyqtSignal(str) # status message
log_update = pyqtSignal(str) # log message
def __init__(self, account_id, account_data, session_controller, automation=None):
super().__init__()
self.account_id = account_id
self.account_data = account_data
self.session_controller = session_controller
self.automation = automation # Verwende bereitgestellte Automation oder erstelle neue
def run(self):
try:
# Verwende bereitgestellte Automation oder erstelle neue
if not self.automation:
# Fingerprint laden wenn vorhanden
fingerprint_dict = None
if self.account_data.get('fingerprint_id'):
fingerprint_obj = self.session_controller.fingerprint_repository.find_by_id(
self.account_data['fingerprint_id']
)
if fingerprint_obj:
fingerprint_dict = fingerprint_obj.to_dict()
# Automation basierend auf Platform auswählen
platform = self.account_data.get('platform', '').lower()
if platform == 'instagram':
self.automation = InstagramAutomation(
headless=False,
fingerprint=fingerprint_dict,
window_position=self.account_data.get('window_position')
)
# Callbacks setzen
self.automation.status_update_callback = lambda msg: self.status_update.emit(msg)
self.automation.log_update_callback = lambda msg: self.log_update.emit(msg)
elif platform == 'tiktok':
self.automation = TikTokAutomation(
headless=False,
fingerprint=fingerprint_dict,
window_position=self.account_data.get('window_position')
)
# Callbacks setzen
self.automation.status_update_callback = lambda msg: self.status_update.emit(msg)
self.automation.log_update_callback = lambda msg: self.log_update.emit(msg)
elif platform == 'x':
from social_networks.x.x_automation import XAutomation
self.automation = XAutomation(
headless=False,
fingerprint=fingerprint_dict,
window_position=self.account_data.get('window_position')
)
# Callbacks setzen
self.automation.status_update_callback = lambda msg: self.status_update.emit(msg)
self.automation.log_update_callback = lambda msg: self.log_update.emit(msg)
else:
self.login_failed.emit(self.account_id, f"Plattform {platform} nicht unterstützt")
return
platform = self.account_data.get('platform', '').lower()
# Status-Updates senden
self.status_update.emit(f"Starte Login für {platform.title()}")
self.log_update.emit(f"Öffne {platform.title()}-Webseite...")
# Login durchführen
result = self.automation.login_account(
username_or_email=self.account_data.get('username'),
password=self.account_data.get('password'),
account_id=self.account_id
)
if result['success']:
# Session-Speicherung komplett entfernt - nur Login-Erfolg melden
logger.info(f"Login erfolgreich für Account {self.account_id} - Session-Speicherung deaktiviert")
self.login_completed.emit(self.account_id, result)
else:
self.login_failed.emit(self.account_id, result.get('error', 'Login fehlgeschlagen'))
except Exception as e:
logger.error(f"Fehler beim normalen Login: {e}")
self.login_failed.emit(self.account_id, str(e))
def cleanup(self):
"""Browser NICHT schließen - User soll Kontrolle behalten"""
logger.info(f"Browser für Account {self.account_id} bleibt offen (User-Kontrolle)")
# GEÄNDERT: Browser wird NICHT automatisch geschlossen
# try:
# if self.automation and hasattr(self.automation, 'browser'):
# if hasattr(self.automation.browser, 'close'):
# self.automation.browser.close()
# logger.info(f"Browser für Account {self.account_id} geschlossen")
# except Exception as e:
# logger.error(f"Fehler beim Schließen des Browsers: {e}")
# Worker Thread erstellen und starten
self.login_worker = LoginWorkerThread(account_id, account_data, self, automation)
# Dialog-Verbindungen falls vorhanden
if hasattr(self, 'login_dialog') and self.login_dialog:
self.login_worker.status_update.connect(self.login_dialog.set_status)
self.login_worker.log_update.connect(self.login_dialog.add_log)
# Browser NICHT automatisch schließen - User behält Kontrolle
def on_login_completed(aid, result):
self.login_successful.emit(aid, result)
# Dialog schließen bei Erfolg
if hasattr(self, 'login_dialog') and self.login_dialog:
self.login_dialog.close()
self.login_dialog = None
# GEÄNDERT: Browser wird NICHT automatisch geschlossen
logger.info(f"Login erfolgreich für Account {aid} - Browser bleibt offen")
# if hasattr(self.login_worker, 'cleanup'):
# QTimer.singleShot(1000, self.login_worker.cleanup) # 1 Sekunde warten dann cleanup
def on_login_failed(aid, error):
self.login_failed.emit(aid, error)
# Dialog schließen bei Fehler
if hasattr(self, 'login_dialog') and self.login_dialog:
self.login_dialog.close()
self.login_dialog = None
# Browser auch bei Fehler schließen
if hasattr(self.login_worker, 'cleanup'):
QTimer.singleShot(1000, self.login_worker.cleanup)
self.login_worker.login_completed.connect(on_login_completed)
self.login_worker.login_failed.connect(on_login_failed)
self.login_worker.start()
def _show_manual_login_required(self, account_id: str, platform: str, reason: str):
"""Zeigt Dialog für erforderlichen manuellen Login"""
QMessageBox.information(
None,
"Manueller Login erforderlich",
f"Ein-Klick-Login für {platform} ist nicht möglich.\n\n"
f"Grund: {reason}\n\n"
"Bitte melden Sie sich manuell an, um eine neue Session zu erstellen."
)

Datei anzeigen

@ -0,0 +1,294 @@
"""
Controller für die Verwaltung von Einstellungen.
"""
import logging
import random
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import QObject
logger = logging.getLogger("settings_controller")
class SettingsController(QObject):
"""Controller für die Verwaltung von Einstellungen."""
def __init__(self, proxy_rotator, email_handler, license_manager):
super().__init__()
self.proxy_rotator = proxy_rotator
self.email_handler = email_handler
self.license_manager = license_manager
self.parent_view = None
def set_parent_view(self, view):
"""Setzt die übergeordnete View für Dialoge."""
self.parent_view = view
def load_proxy_settings(self):
"""Lädt die Proxy-Einstellungen."""
try:
proxy_config = self.proxy_rotator.get_config() or {}
settings = {
"ipv4_proxies": proxy_config.get("ipv4", []),
"ipv6_proxies": proxy_config.get("ipv6", []),
"mobile_proxies": proxy_config.get("mobile", []),
"mobile_api": proxy_config.get("mobile_api", {})
}
return settings
except Exception as e:
logger.error(f"Fehler beim Laden der Proxy-Einstellungen: {e}")
return {}
def save_proxy_settings(self, settings):
"""Speichert die Proxy-Einstellungen."""
try:
# IPv4 Proxies
ipv4_proxies = settings.get("ipv4_proxies", [])
if isinstance(ipv4_proxies, str):
ipv4_proxies = [line.strip() for line in ipv4_proxies.splitlines() if line.strip()]
# IPv6 Proxies
ipv6_proxies = settings.get("ipv6_proxies", [])
if isinstance(ipv6_proxies, str):
ipv6_proxies = [line.strip() for line in ipv6_proxies.splitlines() if line.strip()]
# Mobile Proxies
mobile_proxies = settings.get("mobile_proxies", [])
if isinstance(mobile_proxies, str):
mobile_proxies = [line.strip() for line in mobile_proxies.splitlines() if line.strip()]
# API Keys
mobile_api = settings.get("mobile_api", {})
# Konfiguration aktualisieren
self.proxy_rotator.update_config({
"ipv4": ipv4_proxies,
"ipv6": ipv6_proxies,
"mobile": mobile_proxies,
"mobile_api": mobile_api
})
logger.info("Proxy-Einstellungen gespeichert")
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Erfolg",
"Proxy-Einstellungen wurden gespeichert."
)
return True
except Exception as e:
logger.error(f"Fehler beim Speichern der Proxy-Einstellungen: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Proxy-Einstellungen konnten nicht gespeichert werden:\n{str(e)}"
)
return False
def test_proxy(self, proxy_type):
"""Testet einen zufälligen Proxy des ausgewählten Typs."""
try:
# Überprüfe, ob Proxies konfiguriert sind
proxies = self.proxy_rotator.get_proxies_by_type(proxy_type)
if not proxies:
if self.parent_view:
QMessageBox.warning(
self.parent_view,
"Keine Proxies",
f"Keine {proxy_type.upper()}-Proxies konfiguriert.\nBitte fügen Sie Proxies in den Einstellungen hinzu."
)
return False
# Zufälligen Proxy auswählen
proxy = random.choice(proxies)
# Proxy testen
result = self.proxy_rotator.test_proxy(proxy_type)
if result["success"]:
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Proxy-Test erfolgreich",
f"IP: {result['ip']}\nLand: {result['country'] or 'Unbekannt'}\nAntwortzeit: {result['response_time']:.2f}s"
)
return True
else:
if self.parent_view:
QMessageBox.warning(
self.parent_view,
"Proxy-Test fehlgeschlagen",
f"Fehler: {result['error']}"
)
return False
except Exception as e:
logger.error(f"Fehler beim Testen des Proxy: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler beim Testen des Proxy:\n{str(e)}"
)
return False
def load_email_settings(self):
"""Lädt die E-Mail-Einstellungen."""
try:
email_config = self.email_handler.get_config() or {}
settings = {
"imap_server": email_config.get("imap_server", ""),
"imap_port": email_config.get("imap_port", 993),
"imap_user": email_config.get("imap_user", ""),
"imap_pass": email_config.get("imap_pass", "")
}
return settings
except Exception as e:
logger.error(f"Fehler beim Laden der E-Mail-Einstellungen: {e}")
return {}
def save_email_settings(self, settings):
"""Speichert die E-Mail-Einstellungen."""
try:
# Einstellungen aktualisieren
self.email_handler.update_config(settings)
logger.info("E-Mail-Einstellungen gespeichert")
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Erfolg",
"E-Mail-Einstellungen wurden gespeichert."
)
return True
except Exception as e:
logger.error(f"Fehler beim Speichern der E-Mail-Einstellungen: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"E-Mail-Einstellungen konnten nicht gespeichert werden:\n{str(e)}"
)
return False
def test_email(self, settings=None):
"""Testet die E-Mail-Verbindung."""
try:
if settings:
# Temporär Einstellungen aktualisieren
self.email_handler.update_credentials(
settings.get("imap_user", ""),
settings.get("imap_pass", "")
)
self.email_handler.update_server(
settings.get("imap_server", ""),
settings.get("imap_port", 993)
)
# Verbindung testen
result = self.email_handler.test_connection()
if result["success"]:
if self.parent_view:
QMessageBox.information(
self.parent_view,
"E-Mail-Test erfolgreich",
f"Verbindung zu {result['server']}:{result['port']} hergestellt.\nGefundene Postfächer: {result['mailbox_count']}"
)
return True
else:
if self.parent_view:
QMessageBox.warning(
self.parent_view,
"E-Mail-Test fehlgeschlagen",
f"Fehler: {result['error']}"
)
return False
except Exception as e:
logger.error(f"Fehler beim Testen der E-Mail-Verbindung: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler beim Testen der E-Mail-Verbindung:\n{str(e)}"
)
return False
def load_license_info(self):
"""Lädt die Lizenzinformationen."""
try:
license_info = self.license_manager.get_license_info()
return license_info
except Exception as e:
logger.error(f"Fehler beim Laden der Lizenzinformationen: {e}")
return {}
def activate_license(self, license_key):
"""Aktiviert eine Lizenz."""
try:
success, message = self.license_manager.activate_license(license_key)
if success:
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Lizenz aktiviert",
message
)
else:
if self.parent_view:
QMessageBox.warning(
self.parent_view,
"Lizenzaktivierung fehlgeschlagen",
message
)
return success, message
except Exception as e:
logger.error(f"Fehler bei der Lizenzaktivierung: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler bei der Lizenzaktivierung:\n{str(e)}"
)
return False, str(e)
def check_license(self):
"""Überprüft, ob eine gültige Lizenz vorhanden ist."""
try:
is_licensed = self.license_manager.is_licensed()
if not is_licensed and self.parent_view:
license_info = self.license_manager.get_license_info()
status = license_info.get("status_text", "Inaktiv")
QMessageBox.warning(
self.parent_view,
"Keine gültige Lizenz",
f"Status: {status}\n\nBitte aktivieren Sie eine Lizenz, um die Software zu nutzen."
)
return is_licensed
except Exception as e:
logger.error(f"Fehler bei der Lizenzprüfung: {e}")
return False