Initial commit
Dieser Commit ist enthalten in:
207
controllers/account_controller.py
Normale Datei
207
controllers/account_controller.py
Normale Datei
@ -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
402
controllers/main_controller.py
Normale Datei
@ -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)}"
|
||||
)
|
||||
0
controllers/platform_controllers/__init__.py
Normale Datei
0
controllers/platform_controllers/__init__.py
Normale Datei
265
controllers/platform_controllers/base_controller.py
Normale Datei
265
controllers/platform_controllers/base_controller.py
Normale Datei
@ -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}")
|
||||
217
controllers/platform_controllers/base_worker_thread.py
Normale Datei
217
controllers/platform_controllers/base_worker_thread.py
Normale Datei
@ -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()
|
||||
244
controllers/platform_controllers/gmail_controller.py
Normale Datei
244
controllers/platform_controllers/gmail_controller.py
Normale Datei
@ -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}")
|
||||
415
controllers/platform_controllers/instagram_controller.py
Normale Datei
415
controllers/platform_controllers/instagram_controller.py
Normale Datei
@ -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())
|
||||
317
controllers/platform_controllers/method_rotation_mixin.py
Normale Datei
317
controllers/platform_controllers/method_rotation_mixin.py
Normale Datei
@ -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
|
||||
288
controllers/platform_controllers/method_rotation_worker_mixin.py
Normale Datei
288
controllers/platform_controllers/method_rotation_worker_mixin.py
Normale Datei
@ -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()
|
||||
193
controllers/platform_controllers/ok_ru_controller.py
Normale Datei
193
controllers/platform_controllers/ok_ru_controller.py
Normale Datei
@ -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)
|
||||
443
controllers/platform_controllers/rotation_error_handler.py
Normale Datei
443
controllers/platform_controllers/rotation_error_handler.py
Normale Datei
@ -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'}
|
||||
46
controllers/platform_controllers/safe_imports.py
Normale Datei
46
controllers/platform_controllers/safe_imports.py
Normale Datei
@ -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']
|
||||
419
controllers/platform_controllers/tiktok_controller.py
Normale Datei
419
controllers/platform_controllers/tiktok_controller.py
Normale Datei
@ -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)
|
||||
159
controllers/platform_controllers/vk_controller.py
Normale Datei
159
controllers/platform_controllers/vk_controller.py
Normale Datei
@ -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()
|
||||
417
controllers/platform_controllers/x_controller.py
Normale Datei
417
controllers/platform_controllers/x_controller.py
Normale Datei
@ -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())
|
||||
321
controllers/session_controller.py
Normale Datei
321
controllers/session_controller.py
Normale Datei
@ -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."
|
||||
)
|
||||
294
controllers/settings_controller.py
Normale Datei
294
controllers/settings_controller.py
Normale Datei
@ -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
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren