""" 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." )