589 Zeilen
21 KiB
Python
589 Zeilen
21 KiB
Python
"""
|
|
Datenbankmanager für den Social Media Account Generator.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import sqlite3
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Dict, List, Any, Optional, Tuple, Union
|
|
|
|
from config.paths import PathConfig
|
|
|
|
logger = logging.getLogger("db_manager")
|
|
|
|
class DatabaseManager:
|
|
"""Klasse zur Verwaltung der Datenbank für Account-Informationen."""
|
|
|
|
def __init__(self, db_path: str = None):
|
|
"""
|
|
Initialisiert den DatabaseManager.
|
|
|
|
Args:
|
|
db_path: Pfad zur Datenbank-Datei (falls None, wird PathConfig.MAIN_DB verwendet)
|
|
"""
|
|
self.db_path = db_path if db_path is not None else PathConfig.MAIN_DB
|
|
|
|
# Stelle sicher, dass das Datenbankverzeichnis existiert
|
|
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
|
|
|
# Datenbank initialisieren
|
|
self.init_db()
|
|
|
|
def init_db(self) -> None:
|
|
"""Initialisiert die Datenbank und erstellt die benötigten Tabellen, wenn sie nicht existieren."""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
# Schema v2 laden und ausführen
|
|
try:
|
|
self._init_schema_v2(cursor)
|
|
conn.commit() # Commit nach Schema v2 Initialisierung
|
|
except Exception as e:
|
|
logger.warning(f"Konnte Schema v2 nicht initialisieren: {e}")
|
|
|
|
# Accounts-Tabelle erstellen
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS accounts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
platform TEXT NOT NULL,
|
|
username TEXT NOT NULL,
|
|
password TEXT NOT NULL,
|
|
email TEXT,
|
|
phone TEXT,
|
|
full_name TEXT,
|
|
created_at TEXT NOT NULL,
|
|
last_login TEXT,
|
|
notes TEXT,
|
|
cookies TEXT,
|
|
status TEXT,
|
|
fingerprint_id TEXT,
|
|
session_id TEXT,
|
|
last_session_update TEXT
|
|
)
|
|
''')
|
|
|
|
# Migration für bestehende Datenbanken
|
|
try:
|
|
cursor.execute("PRAGMA table_info(accounts)")
|
|
columns = [column[1] for column in cursor.fetchall()]
|
|
|
|
if "fingerprint_id" not in columns:
|
|
cursor.execute("ALTER TABLE accounts ADD COLUMN fingerprint_id TEXT")
|
|
logger.info("Added fingerprint_id column to accounts table")
|
|
|
|
if "session_id" not in columns:
|
|
cursor.execute("ALTER TABLE accounts ADD COLUMN session_id TEXT")
|
|
logger.info("Added session_id column to accounts table")
|
|
|
|
if "last_session_update" not in columns:
|
|
cursor.execute("ALTER TABLE accounts ADD COLUMN last_session_update TEXT")
|
|
logger.info("Added last_session_update column to accounts table")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Migration warning: {e}")
|
|
|
|
# Settings-Tabelle erstellen
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
)
|
|
''')
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
logger.info("Datenbank initialisiert")
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler bei der Datenbankinitialisierung: {e}")
|
|
|
|
def add_account(self, account_data: Dict[str, Any]) -> int:
|
|
"""
|
|
Fügt einen Account zur Datenbank hinzu.
|
|
|
|
Args:
|
|
account_data: Dictionary mit Account-Daten
|
|
|
|
Returns:
|
|
ID des hinzugefügten Accounts oder -1 im Fehlerfall
|
|
"""
|
|
try:
|
|
# Prüfe, ob erforderliche Felder vorhanden sind
|
|
required_fields = ["platform", "username", "password"]
|
|
for field in required_fields:
|
|
if field not in account_data:
|
|
logger.error(f"Fehlendes Pflichtfeld: {field}")
|
|
return -1
|
|
|
|
# Sicherstellen, dass created_at vorhanden ist
|
|
if "created_at" not in account_data:
|
|
account_data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
# SQL-Anweisung vorbereiten
|
|
fields = ", ".join(account_data.keys())
|
|
placeholders = ", ".join(["?" for _ in account_data])
|
|
|
|
query = f"INSERT INTO accounts ({fields}) VALUES ({placeholders})"
|
|
|
|
# Anweisung ausführen
|
|
cursor.execute(query, list(account_data.values()))
|
|
|
|
# ID des hinzugefügten Datensatzes abrufen
|
|
account_id = cursor.lastrowid
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
logger.info(f"Account hinzugefügt: {account_data['username']} (ID: {account_id})")
|
|
|
|
return account_id
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Hinzufügen des Accounts: {e}")
|
|
return -1
|
|
|
|
def get_account(self, account_id: int) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Gibt einen Account anhand seiner ID zurück.
|
|
|
|
Args:
|
|
account_id: ID des Accounts
|
|
|
|
Returns:
|
|
Dictionary mit Account-Daten oder None, wenn der Account nicht gefunden wurde
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row # Für dict-like Zugriff auf Zeilen
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM accounts WHERE id = ?", (account_id,))
|
|
row = cursor.fetchone()
|
|
|
|
conn.close()
|
|
|
|
if row:
|
|
# Konvertiere Row in Dictionary
|
|
account = dict(row)
|
|
logger.debug(f"Account gefunden: {account['username']} (ID: {account_id})")
|
|
return account
|
|
else:
|
|
logger.warning(f"Account nicht gefunden: ID {account_id}")
|
|
return None
|
|
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Abrufen des Accounts: {e}")
|
|
return None
|
|
|
|
def get_all_accounts(self) -> List[Dict[str, Any]]:
|
|
"""
|
|
Gibt alle Accounts zurück.
|
|
|
|
Returns:
|
|
Liste von Dictionaries mit Account-Daten
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM accounts ORDER BY id DESC")
|
|
rows = cursor.fetchall()
|
|
|
|
conn.close()
|
|
|
|
# Konvertiere Rows in Dictionaries
|
|
accounts = [dict(row) for row in rows]
|
|
|
|
logger.info(f"{len(accounts)} Accounts abgerufen")
|
|
|
|
return accounts
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Abrufen aller Accounts: {e}")
|
|
return []
|
|
|
|
def get_accounts_by_platform(self, platform: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Gibt alle Accounts einer bestimmten Plattform zurück.
|
|
|
|
Args:
|
|
platform: Plattformname (z.B. "instagram")
|
|
|
|
Returns:
|
|
Liste von Dictionaries mit Account-Daten
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM accounts WHERE platform = ? ORDER BY id DESC", (platform.lower(),))
|
|
rows = cursor.fetchall()
|
|
|
|
conn.close()
|
|
|
|
# Konvertiere Rows in Dictionaries
|
|
accounts = [dict(row) for row in rows]
|
|
|
|
logger.info(f"{len(accounts)} Accounts für Plattform '{platform}' abgerufen")
|
|
|
|
return accounts
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Abrufen der Accounts für Plattform '{platform}': {e}")
|
|
return []
|
|
|
|
def update_account(self, account_id: int, update_data: Dict[str, Any]) -> bool:
|
|
"""
|
|
Aktualisiert einen Account in der Datenbank.
|
|
|
|
Args:
|
|
account_id: ID des zu aktualisierenden Accounts
|
|
update_data: Dictionary mit zu aktualisierenden Feldern
|
|
|
|
Returns:
|
|
True bei Erfolg, False im Fehlerfall
|
|
"""
|
|
if not update_data:
|
|
logger.warning("Keine Aktualisierungsdaten bereitgestellt")
|
|
return False
|
|
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
# SQL-Anweisung vorbereiten
|
|
set_clause = ", ".join([f"{field} = ?" for field in update_data.keys()])
|
|
values = list(update_data.values())
|
|
values.append(account_id)
|
|
|
|
query = f"UPDATE accounts SET {set_clause} WHERE id = ?"
|
|
|
|
# Anweisung ausführen
|
|
cursor.execute(query, values)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
logger.info(f"Account aktualisiert: ID {account_id}")
|
|
|
|
return True
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Aktualisieren des Accounts: {e}")
|
|
return False
|
|
|
|
def delete_account(self, account_id: int) -> bool:
|
|
"""
|
|
Löscht einen Account aus der Datenbank.
|
|
|
|
Args:
|
|
account_id: ID des zu löschenden Accounts
|
|
|
|
Returns:
|
|
True bei Erfolg, False im Fehlerfall
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("DELETE FROM accounts WHERE id = ?", (account_id,))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
logger.info(f"Account gelöscht: ID {account_id}")
|
|
|
|
return True
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Löschen des Accounts: {e}")
|
|
return False
|
|
|
|
def search_accounts(self, query: str, platform: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
"""
|
|
Sucht nach Accounts in der Datenbank.
|
|
|
|
Args:
|
|
query: Suchbegriff
|
|
platform: Optional, Plattform für die Einschränkung der Suche
|
|
|
|
Returns:
|
|
Liste von Dictionaries mit gefundenen Account-Daten
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
cursor = conn.cursor()
|
|
|
|
# Suchbegriff für LIKE-Operator vorbereiten
|
|
search_term = f"%{query}%"
|
|
|
|
if platform:
|
|
query_sql = """
|
|
SELECT * FROM accounts
|
|
WHERE (username LIKE ? OR email LIKE ? OR phone LIKE ? OR full_name LIKE ?)
|
|
AND platform = ?
|
|
ORDER BY id DESC
|
|
"""
|
|
cursor.execute(query_sql, (search_term, search_term, search_term, search_term, platform.lower()))
|
|
else:
|
|
query_sql = """
|
|
SELECT * FROM accounts
|
|
WHERE username LIKE ? OR email LIKE ? OR phone LIKE ? OR full_name LIKE ?
|
|
ORDER BY id DESC
|
|
"""
|
|
cursor.execute(query_sql, (search_term, search_term, search_term, search_term))
|
|
|
|
rows = cursor.fetchall()
|
|
|
|
conn.close()
|
|
|
|
# Konvertiere Rows in Dictionaries
|
|
accounts = [dict(row) for row in rows]
|
|
|
|
logger.info(f"{len(accounts)} Accounts gefunden für Suchbegriff '{query}'")
|
|
|
|
return accounts
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler bei der Suche nach Accounts: {e}")
|
|
return []
|
|
|
|
def get_connection(self) -> sqlite3.Connection:
|
|
"""
|
|
Gibt eine neue Datenbankverbindung zurück.
|
|
|
|
Returns:
|
|
SQLite Connection Objekt
|
|
"""
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
return conn
|
|
|
|
def get_account_count(self, platform: Optional[str] = None) -> int:
|
|
"""
|
|
Gibt die Anzahl der Accounts zurück.
|
|
|
|
Args:
|
|
platform: Optional, Plattform für die Einschränkung der Zählung
|
|
|
|
Returns:
|
|
Anzahl der Accounts
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
if platform:
|
|
cursor.execute("SELECT COUNT(*) FROM accounts WHERE platform = ?", (platform.lower(),))
|
|
else:
|
|
cursor.execute("SELECT COUNT(*) FROM accounts")
|
|
|
|
count = cursor.fetchone()[0]
|
|
|
|
conn.close()
|
|
|
|
return count
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Zählen der Accounts: {e}")
|
|
return 0
|
|
|
|
def get_setting(self, key: str, default: Any = None) -> Any:
|
|
"""
|
|
Gibt einen Einstellungswert zurück.
|
|
|
|
Args:
|
|
key: Schlüssel der Einstellung
|
|
default: Standardwert, falls die Einstellung nicht gefunden wurde
|
|
|
|
Returns:
|
|
Wert der Einstellung oder der Standardwert
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT value FROM settings WHERE key = ?", (key,))
|
|
row = cursor.fetchone()
|
|
|
|
conn.close()
|
|
|
|
if row:
|
|
# Versuche, den Wert als JSON zu parsen
|
|
try:
|
|
return json.loads(row[0])
|
|
except json.JSONDecodeError:
|
|
# Wenn kein gültiges JSON, gib den Rohwert zurück
|
|
return row[0]
|
|
else:
|
|
return default
|
|
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Abrufen der Einstellung '{key}': {e}")
|
|
return default
|
|
|
|
def set_setting(self, key: str, value: Any) -> bool:
|
|
"""
|
|
Setzt einen Einstellungswert.
|
|
|
|
Args:
|
|
key: Schlüssel der Einstellung
|
|
value: Wert der Einstellung (wird als JSON gespeichert, wenn es kein String ist)
|
|
|
|
Returns:
|
|
True bei Erfolg, False im Fehlerfall
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
# Wert als JSON speichern, wenn es kein String ist
|
|
if not isinstance(value, str):
|
|
value = json.dumps(value)
|
|
|
|
# Prüfen, ob die Einstellung bereits existiert
|
|
cursor.execute("SELECT COUNT(*) FROM settings WHERE key = ?", (key,))
|
|
exists = cursor.fetchone()[0] > 0
|
|
|
|
if exists:
|
|
cursor.execute("UPDATE settings SET value = ? WHERE key = ?", (value, key))
|
|
else:
|
|
cursor.execute("INSERT INTO settings (key, value) VALUES (?, ?)", (key, value))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
logger.info(f"Einstellung gespeichert: {key}")
|
|
|
|
return True
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Speichern der Einstellung '{key}': {e}")
|
|
return False
|
|
|
|
def delete_setting(self, key: str) -> bool:
|
|
"""
|
|
Löscht eine Einstellung.
|
|
|
|
Args:
|
|
key: Schlüssel der zu löschenden Einstellung
|
|
|
|
Returns:
|
|
True bei Erfolg, False im Fehlerfall
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("DELETE FROM settings WHERE key = ?", (key,))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
logger.info(f"Einstellung gelöscht: {key}")
|
|
|
|
return True
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Löschen der Einstellung '{key}': {e}")
|
|
return False
|
|
|
|
def backup_database(self, backup_path: Optional[str] = None) -> bool:
|
|
"""
|
|
Erstellt ein Backup der Datenbank.
|
|
|
|
Args:
|
|
backup_path: Optional, Pfad für das Backup
|
|
|
|
Returns:
|
|
True bei Erfolg, False im Fehlerfall
|
|
"""
|
|
if not backup_path:
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
backup_path = f"database/backup/accounts_{timestamp}.db"
|
|
|
|
# Stelle sicher, dass das Backup-Verzeichnis existiert
|
|
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
|
|
|
|
try:
|
|
# SQLite-Backup-API verwenden
|
|
conn = sqlite3.connect(self.db_path)
|
|
backup_conn = sqlite3.connect(backup_path)
|
|
|
|
conn.backup(backup_conn)
|
|
|
|
conn.close()
|
|
backup_conn.close()
|
|
|
|
logger.info(f"Datenbank-Backup erstellt: {backup_path}")
|
|
|
|
return True
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Erstellen des Datenbank-Backups: {e}")
|
|
return False
|
|
|
|
def _init_schema_v2(self, cursor) -> None:
|
|
"""Initialisiert das Schema v2 mit Session-Tabellen."""
|
|
schema_path = PathConfig.SCHEMA_V2
|
|
|
|
try:
|
|
# Versuche schema_v2.sql zu laden
|
|
if PathConfig.file_exists(schema_path):
|
|
logger.info(f"Lade Schema v2 aus {schema_path}")
|
|
with open(schema_path, 'r', encoding='utf-8') as f:
|
|
schema_sql = f.read()
|
|
|
|
# Führe alle SQL-Statements aus
|
|
# SQLite unterstützt nur ein Statement pro execute(),
|
|
# daher müssen wir die Statements aufteilen
|
|
statements = [s.strip() for s in schema_sql.split(';') if s.strip()]
|
|
|
|
for statement in statements:
|
|
if statement: # Ignoriere leere Statements
|
|
cursor.execute(statement)
|
|
|
|
logger.info("Schema v2 erfolgreich aus SQL-Datei geladen")
|
|
else:
|
|
logger.warning(f"schema_v2.sql nicht gefunden unter {schema_path}")
|
|
# Fallback: Erstelle minimal notwendige Tabellen
|
|
self._create_minimal_v2_tables(cursor)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Laden von Schema v2: {e}")
|
|
# Fallback: Erstelle minimal notwendige Tabellen
|
|
self._create_minimal_v2_tables(cursor)
|
|
|
|
def _create_minimal_v2_tables(self, cursor) -> None:
|
|
"""Erstellt minimal notwendige v2 Tabellen als Fallback."""
|
|
try:
|
|
# Nur die wichtigsten Tabellen für One-Click-Login
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS browser_sessions (
|
|
id TEXT PRIMARY KEY,
|
|
fingerprint_id TEXT NOT NULL,
|
|
cookies TEXT NOT NULL,
|
|
local_storage TEXT,
|
|
session_storage TEXT,
|
|
account_id TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
last_used TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
health_score REAL DEFAULT 1.0
|
|
)
|
|
''')
|
|
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS browser_fingerprints (
|
|
id TEXT PRIMARY KEY,
|
|
canvas_noise_config TEXT NOT NULL,
|
|
webrtc_config TEXT NOT NULL,
|
|
fonts TEXT NOT NULL,
|
|
hardware_config TEXT NOT NULL,
|
|
navigator_props TEXT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
''')
|
|
|
|
logger.info("Minimale v2 Tabellen erstellt")
|
|
|
|
except sqlite3.Error as e:
|
|
logger.error(f"Fehler beim Erstellen der minimalen v2 Tabellen: {e}") |