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

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