X-Problem gelöst
Dieser Commit ist enthalten in:
@ -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",
|
||||
|
||||
139
application/services/platform_service.py
Normale Datei
139
application/services/platform_service.py
Normale Datei
@ -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")
|
||||
55
config/platforms.yaml
Normale Datei
55
config/platforms.yaml
Normale Datei
@ -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"
|
||||
@ -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)}"
|
||||
)
|
||||
)
|
||||
|
||||
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}")
|
||||
@ -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
|
||||
|
||||
Binäre Datei nicht angezeigt.
@ -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")
|
||||
|
||||
|
||||
74
domain/entities/platform.py
Normale Datei
74
domain/entities/platform.py
Normale Datei
@ -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})"
|
||||
160
domain/value_objects/platform_name.py
Normale Datei
160
domain/value_objects/platform_name.py
Normale Datei
@ -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()))
|
||||
@ -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
|
||||
|
||||
85
scripts/fix_x_accounts.py
Normale Datei
85
scripts/fix_x_accounts.py
Normale Datei
@ -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()
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren