Initial commit
Dieser Commit ist enthalten in:
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."
|
||||
)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren