321 Zeilen
16 KiB
Python
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."
|
|
) |