Files
AccountForger-neuerUpload/controllers/session_controller.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

321 Zeilen
16 KiB
Python

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