diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6f12e14..975d131 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -14,7 +14,8 @@ "Bash(claude config list:*)", "Bash(claude mcp)", "Bash(claude mcp:*)", - "Bash(sqlite3:*)" + "Bash(sqlite3:*)", + "Bash(python -m pip install:*)" ], "deny": [], "defaultMode": "acceptEdits", diff --git a/application/services/platform_service.py b/application/services/platform_service.py new file mode 100644 index 0000000..1786647 --- /dev/null +++ b/application/services/platform_service.py @@ -0,0 +1,139 @@ +""" +Platform Service - Business logic for platform management +""" +import os +import yaml +import logging +from typing import List, Dict, Any, Optional +from pathlib import Path + +from domain.entities.platform import Platform, PlatformStatus + +logger = logging.getLogger("platform_service") + + +class PlatformService: + """ + Service for managing platform configuration and availability. + Clean Architecture: Application Service + """ + + def __init__(self, db_manager=None): + self.db_manager = db_manager + self._platforms_cache = None + self._config_path = Path(__file__).parent.parent.parent / "config" / "platforms.yaml" + + def _load_platforms(self) -> Dict[str, Platform]: + """Load platforms from configuration file""" + if self._platforms_cache is not None: + return self._platforms_cache + + try: + with open(self._config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + + platforms = {} + for platform_id, platform_data in config.get('platforms', {}).items(): + platform = Platform( + id=platform_data.get('id', platform_id), + display_name=platform_data.get('display_name', platform_id.title()), + enabled=platform_data.get('enabled', False), + icon=platform_data.get('icon', f"{platform_id}.svg"), + color=platform_data.get('color', '#000000') + ) + platforms[platform.id] = platform + + self._platforms_cache = platforms + return platforms + + except FileNotFoundError: + logger.warning(f"Platform config not found at {self._config_path}, using defaults") + return self._get_default_platforms() + except Exception as e: + logger.error(f"Error loading platform config: {e}") + return self._get_default_platforms() + + def _get_default_platforms(self) -> Dict[str, Platform]: + """Get default platform configuration""" + defaults = { + 'instagram': Platform('instagram', 'Instagram', True, 'instagram.svg', '#E4405F'), + 'facebook': Platform('facebook', 'Facebook', True, 'facebook.svg', '#1877F2'), + 'tiktok': Platform('tiktok', 'TikTok', True, 'tiktok.svg', '#000000'), + 'x': Platform('x', 'X', True, 'twitter.svg', '#000000'), + 'gmail': Platform('gmail', 'Gmail', False, 'gmail.svg', '#EA4335'), + 'vk': Platform('vk', 'VK', False, 'vk.svg', '#0077FF'), + 'ok': Platform('ok', 'OK.ru', False, 'ok.svg', '#FF6600') + } + return defaults + + def get_all_platforms(self) -> List[Platform]: + """Get all platforms (enabled and disabled)""" + platforms = self._load_platforms() + return list(platforms.values()) + + def get_active_platforms(self) -> List[Platform]: + """Get only enabled platforms for UI display""" + platforms = self._load_platforms() + return [p for p in platforms.values() if p.is_enabled] + + def get_platform_by_id(self, platform_id: str) -> Optional[Platform]: + """Get a specific platform by ID""" + platforms = self._load_platforms() + return platforms.get(platform_id.lower()) + + def is_platform_enabled(self, platform_id: str) -> bool: + """Check if a platform is enabled""" + platform = self.get_platform_by_id(platform_id) + return platform.is_enabled if platform else False + + def get_filter_platforms(self) -> List[Platform]: + """ + Get platforms for sidebar filters. + Shows: + - All enabled platforms + - Disabled platforms that have existing accounts + """ + platforms = self._load_platforms() + filter_platforms = [] + + for platform in platforms.values(): + if platform.is_enabled: + # Always show enabled platforms + filter_platforms.append(platform) + elif self.db_manager: + # Show disabled platforms only if they have accounts + try: + count = self.db_manager.get_account_count(platform.id) + if count > 0: + filter_platforms.append(platform) + except Exception as e: + logger.error(f"Error checking accounts for {platform.id}: {e}") + + return filter_platforms + + def get_platforms_with_accounts(self) -> Dict[str, int]: + """ + Get platform IDs with their account counts. + Returns dict: {platform_id: count} + """ + if not self.db_manager: + return {} + + platforms = self._load_platforms() + counts = {} + + for platform_id in platforms.keys(): + try: + count = self.db_manager.get_account_count(platform_id) + if count > 0: + counts[platform_id] = count + except Exception as e: + logger.error(f"Error getting count for {platform_id}: {e}") + counts[platform_id] = 0 + + return counts + + def reload_configuration(self): + """Force reload of platform configuration""" + self._platforms_cache = None + logger.info("Platform configuration reloaded") \ No newline at end of file diff --git a/config/platforms.yaml b/config/platforms.yaml new file mode 100644 index 0000000..4dfa07c --- /dev/null +++ b/config/platforms.yaml @@ -0,0 +1,55 @@ +# Platform Configuration +# Controls which platforms are active in the UI +# Set enabled: true to activate a platform + +platforms: + # Currently active platforms (shown in UI) + instagram: + id: instagram + display_name: Instagram + enabled: true + icon: instagram.svg + color: "#E4405F" + + facebook: + id: facebook + display_name: Facebook + enabled: true + icon: facebook.svg + color: "#1877F2" + + tiktok: + id: tiktok + display_name: TikTok + enabled: true + icon: tiktok.svg + color: "#000000" + + x: + id: x + display_name: "X" + enabled: true + icon: twitter.svg + color: "#000000" + + # Disabled platforms (hidden in UI, but accounts remain accessible) + gmail: + id: gmail + display_name: Gmail + enabled: false + icon: gmail.svg + color: "#EA4335" + + vk: + id: vk + display_name: VK + enabled: false + icon: vk.svg + color: "#0077FF" + + ok: + id: ok + display_name: OK.ru + enabled: false + icon: ok.svg + color: "#FF6600" \ No newline at end of file diff --git a/controllers/main_controller.py b/controllers/main_controller.py index 005753c..7f749b9 100644 --- a/controllers/main_controller.py +++ b/controllers/main_controller.py @@ -21,6 +21,7 @@ from utils.theme_manager import ThemeManager from localization.language_manager import LanguageManager from licensing.license_manager import LicenseManager from updates.update_checker import UpdateChecker +from application.services.platform_service import PlatformService logger = logging.getLogger("main") @@ -50,6 +51,7 @@ class MainController: self.proxy_rotator = ProxyRotator() self.email_handler = EmailHandler() self.update_checker = UpdateChecker(self.license_manager.api_client) + self.platform_service = PlatformService(self.db_manager) # Haupt-View erstellen self.view = MainWindow(self.theme_manager, self.language_manager, self.db_manager) @@ -64,72 +66,9 @@ class MainController: ) self.session_controller = SessionController(self.db_manager) - # Plattform-Controller initialisieren + # Plattform-Controller initialisieren - nur für aktive Plattformen self.platform_controllers = {} - - # Instagram Controller hinzufügen - instagram_controller = InstagramController( - self.db_manager, - self.proxy_rotator, - self.email_handler, - self.language_manager - ) - # Signal für Rückkehr zur Hauptseite verbinden - instagram_controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset() - # SessionController referenz hinzufügen - instagram_controller.session_controller = self.session_controller - self.platform_controllers["instagram"] = instagram_controller - - # TikTok Controller hinzufügen - tiktok_controller = TikTokController( - self.db_manager, - self.proxy_rotator, - self.email_handler, - self.language_manager - ) - # Signal für Rückkehr zur Hauptseite verbinden - tiktok_controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset() - # SessionController referenz hinzufügen - tiktok_controller.session_controller = self.session_controller - self.platform_controllers["tiktok"] = tiktok_controller - - # X (Twitter) Controller hinzufügen - from controllers.platform_controllers.x_controller import XController - x_controller = XController( - self.db_manager, - self.proxy_rotator, - self.email_handler, - self.language_manager - ) - # Signal für Rückkehr zur Hauptseite verbinden - x_controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset() - # SessionController referenz hinzufügen - x_controller.session_controller = self.session_controller - self.platform_controllers["x"] = x_controller - - # Gmail Controller hinzufügen - from controllers.platform_controllers.gmail_controller import GmailController - gmail_controller = GmailController( - self.db_manager, - self.proxy_rotator, - self.email_handler, - self.language_manager - ) - # Signal für Rückkehr zur Hauptseite verbinden - gmail_controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset() - # SessionController referenz hinzufügen - gmail_controller.session_controller = self.session_controller - self.platform_controllers["gmail"] = gmail_controller - - # Facebook Controller hinzufügen - from controllers.platform_controllers.facebook_controller import FacebookController - facebook_controller = FacebookController( - self.db_manager, - self.proxy_rotator, - self.email_handler, - self.language_manager - ) - self.platform_controllers["facebook"] = facebook_controller + self._initialize_active_platform_controllers() # Signals verbinden self.connect_signals() @@ -406,4 +345,65 @@ class MainController: self.view, "Fehler", f"Fehler beim Herunterladen des Updates:\n{str(e)}" - ) \ No newline at end of file + ) + + def _initialize_active_platform_controllers(self): + """Initialisiert nur Controller für aktive Plattformen.""" + active_platforms = self.platform_service.get_active_platforms() + + for platform in active_platforms: + platform_id = platform.id.lower() + + try: + if platform_id == "instagram": + controller = InstagramController( + self.db_manager, + self.proxy_rotator, + self.email_handler, + self.language_manager + ) + controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset() + controller.session_controller = self.session_controller + self.platform_controllers["instagram"] = controller + + elif platform_id == "tiktok": + controller = TikTokController( + self.db_manager, + self.proxy_rotator, + self.email_handler, + self.language_manager + ) + controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset() + controller.session_controller = self.session_controller + self.platform_controllers["tiktok"] = controller + + elif platform_id == "x": + from controllers.platform_controllers.x_controller import XController + controller = XController( + self.db_manager, + self.proxy_rotator, + self.email_handler, + self.language_manager + ) + controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset() + controller.session_controller = self.session_controller + self.platform_controllers["x"] = controller + + elif platform_id == "facebook": + from controllers.platform_controllers.facebook_controller import FacebookController + controller = FacebookController( + self.db_manager, + self.proxy_rotator, + self.email_handler, + self.language_manager + ) + controller.return_to_main_requested = lambda: self.show_platform_selector_and_reset() + controller.session_controller = self.session_controller + self.platform_controllers["facebook"] = controller + + logger.info(f"Controller für Plattform '{platform_id}' initialisiert") + + except ImportError as e: + logger.warning(f"Controller für Plattform '{platform_id}' konnte nicht geladen werden: {e}") + except Exception as e: + logger.error(f"Fehler beim Initialisieren des Controllers für '{platform_id}': {e}") \ No newline at end of file diff --git a/controllers/session_controller.py b/controllers/session_controller.py index 5e452ee..3557ce3 100644 --- a/controllers/session_controller.py +++ b/controllers/session_controller.py @@ -129,10 +129,14 @@ class SessionController(QObject): Dict mit success, account_id und message """ try: + # Platform-Name standardisieren + from domain.value_objects.platform_name import PlatformName + platform_obj = PlatformName(platform) + # Account in DB speichern from datetime import datetime account_record = { - "platform": platform.lower(), + "platform": platform_obj.canonical, # Verwendet kanonischen Namen (lowercase) "username": account_data.get("username", ""), "password": account_data.get("password", ""), "email": account_data.get("email", ""), @@ -142,6 +146,18 @@ class SessionController(QObject): } account_id = self.db_manager.add_account(account_record) + + # Prüfe ob DB-Operation erfolgreich war + if account_id == -1 or account_id is None: + error_msg = f"Fehler beim Speichern des Accounts {account_record['username']} in der Datenbank" + logger.error(error_msg) + return { + 'success': False, + 'account_id': None, + 'error': error_msg, + 'message': error_msg + } + logger.info(f"Account in Datenbank gespeichert: {account_record['username']} (ID: {account_id})") # Fingerprint für Account generieren diff --git a/database/accounts.db b/database/accounts.db index f687e16..4a6d6d1 100644 Binary files a/database/accounts.db and b/database/accounts.db differ diff --git a/database/db_manager.py b/database/db_manager.py index b856cea..6583d78 100644 --- a/database/db_manager.py +++ b/database/db_manager.py @@ -117,6 +117,11 @@ class DatabaseManager: if field not in account_data: logger.error(f"Fehlendes Pflichtfeld: {field}") return -1 + # Prüfe auch ob Felder nicht leer sind + if not account_data[field] or account_data[field] == "": + logger.error(f"Pflichtfeld ist leer: {field} = '{account_data[field]}'") + logger.error(f"Account-Daten: {account_data}") + return -1 # Sicherstellen, dass created_at vorhanden ist if "created_at" not in account_data: @@ -218,11 +223,21 @@ class DatabaseManager: Liste von Dictionaries mit Account-Daten """ try: + # Platform-Name standardisieren + from domain.value_objects.platform_name import PlatformName + try: + platform_obj = PlatformName(platform) + canonical_platform = platform_obj.canonical + except ValueError: + # Fallback für unbekannte Platforms + canonical_platform = platform.lower() + 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(),)) + # Case-insensitive Suche mit LOWER() für Backward-Compatibility + cursor.execute("SELECT * FROM accounts WHERE LOWER(platform) = LOWER(?) ORDER BY id DESC", (canonical_platform,)) rows = cursor.fetchall() conn.close() @@ -377,7 +392,17 @@ class DatabaseManager: cursor = conn.cursor() if platform: - cursor.execute("SELECT COUNT(*) FROM accounts WHERE platform = ?", (platform.lower(),)) + # Platform-Name standardisieren + from domain.value_objects.platform_name import PlatformName + try: + platform_obj = PlatformName(platform) + canonical_platform = platform_obj.canonical + except ValueError: + # Fallback für unbekannte Platforms + canonical_platform = platform.lower() + + # Case-insensitive Suche + cursor.execute("SELECT COUNT(*) FROM accounts WHERE LOWER(platform) = LOWER(?)", (canonical_platform,)) else: cursor.execute("SELECT COUNT(*) FROM accounts") diff --git a/domain/entities/platform.py b/domain/entities/platform.py new file mode 100644 index 0000000..f6316fb --- /dev/null +++ b/domain/entities/platform.py @@ -0,0 +1,74 @@ +""" +Platform Entity - Represents a social media platform +""" +from dataclasses import dataclass +from typing import Optional, Dict, Any +from enum import Enum + + +class PlatformStatus(Enum): + """Platform availability status""" + ENABLED = "enabled" + DISABLED = "disabled" + UPCOMING = "upcoming" + + +@dataclass +class Platform: + """ + Platform entity representing a social media platform. + Clean Architecture: Domain Entity + """ + id: str + display_name: str + status: PlatformStatus + icon: str + color: str + + def __init__( + self, + id: str, + display_name: str, + enabled: bool = True, + icon: str = None, + color: str = "#000000" + ): + self.id = id + self.display_name = display_name + self.status = PlatformStatus.ENABLED if enabled else PlatformStatus.DISABLED + self.icon = icon or f"{id}.svg" + self.color = color + + @property + def is_enabled(self) -> bool: + """Check if platform is enabled""" + return self.status == PlatformStatus.ENABLED + + @property + def is_disabled(self) -> bool: + """Check if platform is disabled""" + return self.status == PlatformStatus.DISABLED + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + 'id': self.id, + 'display_name': self.display_name, + 'status': self.status.value, + 'icon': self.icon, + 'color': self.color + } + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> 'Platform': + """Create from dictionary""" + return cls( + id=data.get('id'), + display_name=data.get('display_name'), + enabled=data.get('enabled', True), + icon=data.get('icon'), + color=data.get('color', '#000000') + ) + + def __repr__(self) -> str: + return f"Platform(id={self.id}, name={self.display_name}, status={self.status.value})" \ No newline at end of file diff --git a/domain/value_objects/platform_name.py b/domain/value_objects/platform_name.py new file mode 100644 index 0000000..6b145a2 --- /dev/null +++ b/domain/value_objects/platform_name.py @@ -0,0 +1,160 @@ +""" +Platform Name Value Object - Standardisiert Platform-Namen +""" + +from typing import Optional + + +class PlatformName: + """ + Value Object für standardisierte Platform-Namen. + Stellt sicher, dass Platform-Namen konsistent behandelt werden. + """ + + # Mapping von verschiedenen Schreibweisen zu kanonischen Namen + PLATFORM_MAPPINGS = { + # Kanonische Namen (für DB und interne Verwendung) + 'instagram': 'instagram', + 'facebook': 'facebook', + 'tiktok': 'tiktok', + 'x': 'x', + 'gmail': 'gmail', + 'vk': 'vk', + 'ok': 'ok', + + # Alternative Schreibweisen + 'X': 'x', + 'twitter': 'x', + 'Twitter': 'x', + 'X (Twitter)': 'x', + 'ok.ru': 'ok', + 'OK.ru': 'ok', + 'VK': 'vk', + 'Gmail': 'gmail', + 'Instagram': 'instagram', + 'Facebook': 'facebook', + 'TikTok': 'tiktok' + } + + # Display-Namen für UI + DISPLAY_NAMES = { + 'instagram': 'Instagram', + 'facebook': 'Facebook', + 'tiktok': 'TikTok', + 'x': 'X', + 'gmail': 'Gmail', + 'vk': 'VK', + 'ok': 'OK.ru' + } + + def __init__(self, name: str): + """ + Initialisiert ein PlatformName Value Object. + + Args: + name: Platform-Name in beliebiger Schreibweise + """ + self._canonical_name = self._normalize(name) + if not self._canonical_name: + raise ValueError(f"Unknown platform: {name}") + + def _normalize(self, name: str) -> Optional[str]: + """ + Normalisiert einen Platform-Namen zu seiner kanonischen Form. + + Args: + name: Platform-Name in beliebiger Schreibweise + + Returns: + Kanonischer Platform-Name oder None + """ + if not name: + return None + + # Trimmen und in Mapping suchen + normalized = name.strip() + return self.PLATFORM_MAPPINGS.get(normalized, self.PLATFORM_MAPPINGS.get(normalized.lower())) + + @property + def canonical(self) -> str: + """ + Gibt den kanonischen Namen zurück (für DB und interne Verwendung). + Immer lowercase. + """ + return self._canonical_name + + @property + def display(self) -> str: + """ + Gibt den Display-Namen zurück (für UI). + Mit korrekter Groß-/Kleinschreibung. + """ + return self.DISPLAY_NAMES.get(self._canonical_name, self._canonical_name.title()) + + @property + def db_value(self) -> str: + """ + Gibt den Wert für Datenbank-Speicherung zurück. + Alias für canonical. + """ + return self.canonical + + def matches(self, other: str) -> bool: + """ + Prüft, ob ein anderer Platform-Name diesem entspricht. + + Args: + other: Zu vergleichender Platform-Name + + Returns: + True wenn die Platforms übereinstimmen + """ + other_normalized = self._normalize(other) + return other_normalized == self._canonical_name if other_normalized else False + + def __str__(self) -> str: + """String-Repräsentation (Display-Name).""" + return self.display + + def __repr__(self) -> str: + """Debug-Repräsentation.""" + return f"PlatformName(canonical='{self.canonical}', display='{self.display}')" + + def __eq__(self, other) -> bool: + """Gleichheitsvergleich.""" + if isinstance(other, PlatformName): + return self._canonical_name == other._canonical_name + elif isinstance(other, str): + return self.matches(other) + return False + + def __hash__(self) -> int: + """Hash-Wert für Set/Dict-Verwendung.""" + return hash(self._canonical_name) + + @classmethod + def is_valid(cls, name: str) -> bool: + """ + Prüft, ob ein Platform-Name gültig ist. + + Args: + name: Zu prüfender Platform-Name + + Returns: + True wenn der Name gültig ist + """ + try: + cls(name) + return True + except ValueError: + return False + + @classmethod + def get_all_canonical(cls) -> list: + """ + Gibt alle kanonischen Platform-Namen zurück. + + Returns: + Liste aller kanonischen Platform-Namen + """ + return list(set(cls.PLATFORM_MAPPINGS.values())) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a728c2b..086a27a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,7 @@ IMAPClient>=2.1.0 python-dateutil>=2.8.1 Pillow>=8.0.0 cryptography>=3.4.0 +PyYAML>=6.0 # Web automation and anti-detection random-user-agent>=1.0.1 diff --git a/scripts/fix_x_accounts.py b/scripts/fix_x_accounts.py new file mode 100644 index 0000000..c3a92f4 --- /dev/null +++ b/scripts/fix_x_accounts.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +""" +Script to fix existing X accounts in the database. +Updates platform names from various formats to canonical 'x'. +""" + +import sqlite3 +import os +import sys +from pathlib import Path + +# Add parent directory to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from domain.value_objects.platform_name import PlatformName + + +def fix_x_accounts(db_path: str): + """ + Fix X accounts in the database by standardizing platform names. + + Args: + db_path: Path to the database file + """ + if not os.path.exists(db_path): + print(f"❌ Database not found: {db_path}") + return + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Find all potential X accounts with different naming + possible_x_names = ['X', 'x', 'twitter', 'Twitter', 'X (Twitter)'] + + total_fixed = 0 + + for name in possible_x_names: + cursor.execute("SELECT COUNT(*) FROM accounts WHERE platform = ?", (name,)) + count = cursor.fetchone()[0] + + if count > 0: + print(f"Found {count} accounts with platform='{name}'") + + # Update to canonical name + cursor.execute("UPDATE accounts SET platform = ? WHERE platform = ?", ('x', name)) + total_fixed += count + print(f"✅ Updated {count} accounts from '{name}' to 'x'") + + # Check for accounts that might have been missed + cursor.execute("SELECT DISTINCT platform FROM accounts") + all_platforms = cursor.fetchall() + + print("\n📊 Current platforms in database:") + for platform in all_platforms: + cursor.execute("SELECT COUNT(*) FROM accounts WHERE platform = ?", (platform[0],)) + count = cursor.fetchone()[0] + print(f" - {platform[0]}: {count} accounts") + + # Commit changes + if total_fixed > 0: + conn.commit() + print(f"\n✅ Successfully fixed {total_fixed} accounts") + else: + print("\n✨ No accounts needed fixing") + + conn.close() + + +def main(): + """Main function.""" + # Database path + db_path = Path(__file__).parent.parent / "database" / "accounts.db" + + print("🔧 X Platform Name Fix Script") + print("=" * 40) + print(f"Database: {db_path}") + print() + + fix_x_accounts(str(db_path)) + + print("\n🎉 Done!") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/views/components/accounts_overview_view.py b/views/components/accounts_overview_view.py index 4cd8429..424a016 100644 --- a/views/components/accounts_overview_view.py +++ b/views/components/accounts_overview_view.py @@ -11,6 +11,7 @@ from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QFont from views.widgets.account_card import AccountCard +from application.services.platform_service import PlatformService logger = logging.getLogger("accounts_overview") @@ -19,9 +20,11 @@ class SidebarFilter(QWidget): """Sidebar mit Plattform-Filter nach Styleguide""" filter_changed = pyqtSignal(str) - def __init__(self, language_manager=None): + def __init__(self, language_manager=None, db_manager=None): super().__init__() self.language_manager = language_manager + self.db_manager = db_manager + self.platform_service = PlatformService(db_manager) self.filter_buttons = [] self.account_counts = {} self.init_ui() @@ -41,18 +44,15 @@ class SidebarFilter(QWidget): # Title removed - no longer needed - # Filter Buttons - self.filters = [ - ("Alle", "all"), - ("Instagram", "instagram"), - ("TikTok", "tiktok"), - ("Facebook", "facebook"), - ("X (Twitter)", "x"), - ("VK", "vk"), - ("OK.ru", "ok"), - ("Gmail", "gmail") - ] + # Build filter list dynamically + self.filters = [("Alle", "all")] + # Get platforms that should be shown in filters + filter_platforms = self.platform_service.get_filter_platforms() + for platform in filter_platforms: + self.filters.append((platform.display_name, platform.id)) + + # Create filter buttons for name, key in self.filters: btn = self._create_filter_button(name, key) self.filter_buttons.append((btn, key)) @@ -136,7 +136,7 @@ class AccountsOverviewView(QWidget): main_layout.setSpacing(0) # Sidebar - self.sidebar = SidebarFilter(self.language_manager) + self.sidebar = SidebarFilter(self.language_manager, self.db_manager) self.sidebar.filter_changed.connect(self._on_filter_changed) main_layout.addWidget(self.sidebar) @@ -212,9 +212,13 @@ class AccountsOverviewView(QWidget): if self.current_filter == "all": filtered_accounts = self.accounts else: + # Use PlatformName for consistent filtering + from domain.value_objects.platform_name import PlatformName + filter_platform = PlatformName(self.current_filter) filtered_accounts = [ acc for acc in self.accounts - if acc.get("platform", "").lower() == self.current_filter + if PlatformName.is_valid(acc.get("platform", "")) and + PlatformName(acc.get("platform", "")) == filter_platform ] # Gruppiere nach Plattform wenn "Alle" ausgewählt @@ -317,13 +321,19 @@ class AccountsOverviewView(QWidget): """Behandelt Filter-Änderungen""" self.current_filter = filter_key - # Update title + # Update title using PlatformName for consistency if filter_key == "all": self.title.setText("Alle Accounts") else: - platform_name = filter_key.capitalize() - if filter_key == "x": - platform_name = "X (Twitter)" + try: + from domain.value_objects.platform_name import PlatformName + platform_obj = PlatformName(filter_key) + platform_name = platform_obj.display + except: + # Fallback + platform_name = filter_key.capitalize() + if filter_key == "x": + platform_name = "X" self.title.setText(f"{platform_name} Accounts") self._update_display() diff --git a/views/components/platform_grid_view.py b/views/components/platform_grid_view.py index e91b186..dabed52 100644 --- a/views/components/platform_grid_view.py +++ b/views/components/platform_grid_view.py @@ -8,6 +8,7 @@ from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QFont from views.widgets.platform_button import PlatformButton +from application.services.platform_service import PlatformService class PlatformGridView(QWidget): @@ -19,9 +20,11 @@ class PlatformGridView(QWidget): # Signal wird ausgelöst, wenn eine Plattform ausgewählt wird platform_selected = pyqtSignal(str) - def __init__(self, language_manager=None): + def __init__(self, language_manager=None, db_manager=None): super().__init__() self.language_manager = language_manager + self.db_manager = db_manager + self.platform_service = PlatformService(db_manager) self.init_ui() if self.language_manager: @@ -53,49 +56,38 @@ class PlatformGridView(QWidget): grid_layout = QGridLayout(platforms_container) grid_layout.setSpacing(24) # Styleguide Grid-Gap - # Definiere verfügbare Plattformen - platforms = [ - {"name": "Instagram", "enabled": True}, - {"name": "Facebook", "enabled": True}, - {"name": "TikTok", "enabled": True}, - {"name": "X", "enabled": True}, - {"name": "VK", "enabled": True}, - {"name": "OK.ru", "enabled": True}, - {"name": "Gmail", "enabled": True} - ] + # Lade nur aktive Plattformen aus dem Service + active_platforms = self.platform_service.get_active_platforms() # Icon-Pfade current_dir = os.path.dirname(os.path.abspath(__file__)) parent_dir = os.path.dirname(os.path.dirname(current_dir)) icons_dir = os.path.join(parent_dir, "resources", "icons") - # Platziere Buttons in einem 2x4 Grid - for i, platform in enumerate(platforms): - row = i // 4 - col = i % 4 + # Platziere Buttons in einem 2x2 Grid (nur 4 Plattformen) + for i, platform in enumerate(active_platforms): + row = i // 2 # 2 Spalten + col = i % 2 # Icon-Pfad erstellen - platform_icon_name = platform['name'].lower() - if platform['name'] == "X": - platform_icon_name = "twitter" - elif platform['name'] == "OK.ru": - platform_icon_name = "ok" - icon_path = os.path.join(icons_dir, f"{platform_icon_name}.svg") - + icon_path = os.path.join(icons_dir, platform.icon) if not os.path.exists(icon_path): - icon_path = None + # Fallback für X/Twitter + if platform.id == "x": + icon_path = os.path.join(icons_dir, "twitter.svg") + if not os.path.exists(icon_path): + icon_path = None # Platform Button erstellen button = PlatformButton( - platform["name"], + platform.display_name, icon_path, - platform["enabled"] + True # Alle aktiven Plattformen sind enabled ) # Signal verbinden - platform_signal_name = "x" if platform["name"] == "X" else platform["name"] button.clicked.connect( - lambda checked=False, p=platform_signal_name: self.platform_selected.emit(p.lower()) + lambda checked=False, p=platform.id: self.platform_selected.emit(p) ) grid_layout.addWidget(button, row, col, Qt.AlignCenter) diff --git a/views/platform_selector.py b/views/platform_selector.py index 8298ec6..06366fd 100644 --- a/views/platform_selector.py +++ b/views/platform_selector.py @@ -51,7 +51,7 @@ class PlatformSelector(QWidget): self.content_stack.setObjectName("content_stack") # For QSS targeting, no inline styles! # Platform Grid View (Tab 0) - self.platform_grid = PlatformGridView(self.language_manager) + self.platform_grid = PlatformGridView(self.language_manager, self.db_manager) self.platform_grid.platform_selected.connect(self.platform_selected.emit) self.content_stack.addWidget(self.platform_grid)