417 Zeilen
18 KiB
Python
417 Zeilen
18 KiB
Python
"""
|
|
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()) |