From ee1f8add5ec7617c82848e88d0ee4e43b96bf2f4 Mon Sep 17 00:00:00 2001 From: Claude Project Manager Date: Sat, 4 Oct 2025 00:25:17 +0200 Subject: [PATCH] Popup Fenster nach Vorne --- .claude/settings.local.json | 5 +- CLAUDE_PROJECT_README.md | 12 +- .../platform_controllers/base_controller.py | 11 +- .../facebook_controller.py | 43 ++-- .../platform_controllers/gmail_controller.py | 4 +- .../instagram_controller.py | 7 +- .../platform_controllers/ok_ru_controller.py | 6 +- .../platform_controllers/tiktok_controller.py | 7 +- .../platform_controllers/x_controller.py | 7 +- database/accounts.db | Bin 372736 -> 376832 bytes tests/test_generator_tab_factory.py | 150 ++++++++++++++ views/tabs/facebook_generator_tab.py | 6 +- views/tabs/generator_tab.py | 6 +- views/tabs/generator_tab_factory.py | 185 ++++++++++++++++++ views/widgets/forge_animation_widget.py | 16 +- 15 files changed, 410 insertions(+), 55 deletions(-) create mode 100644 tests/test_generator_tab_factory.py create mode 100644 views/tabs/generator_tab_factory.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 975d131..f6ec167 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -15,7 +15,10 @@ "Bash(claude mcp)", "Bash(claude mcp:*)", "Bash(sqlite3:*)", - "Bash(python -m pip install:*)" + "Bash(python -m pip install:*)", + "Bash(python -m pytest teststest_generator_tab_factory.py -v)", + "Bash(python tests:*)", + "Bash(python tests/test_generator_tab_factory.py)" ], "deny": [], "defaultMode": "acceptEdits", diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index 7b1bec6..a0edc0f 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -5,9 +5,9 @@ ## Project Overview - **Path**: `A:\GiTea\AccountForger` -- **Files**: 990 files -- **Size**: 182.0 MB -- **Last Modified**: 2025-09-14 18:54 +- **Files**: 1024 files +- **Size**: 184.1 MB +- **Last Modified**: 2025-10-03 01:25 ## Technology Stack @@ -296,6 +296,7 @@ styles/ │ ├── modal_styles.py │ └── __init__.py tests/ +│ ├── test_generator_tab_factory.py │ ├── test_method_rotation.py │ ├── bright_data/ │ │ └── logs/ @@ -383,6 +384,7 @@ views/ │ ├── accounts_tab.py │ ├── facebook_generator_tab.py │ ├── generator_tab.py + │ ├── generator_tab_factory.py │ └── settings_tab.py └── widgets/ ├── account_card.py @@ -423,3 +425,7 @@ This project is managed with Claude Project Manager. To work with this project: - README updated on 2025-09-13 20:45:18 - README updated on 2025-09-14 11:33:43 - README updated on 2025-09-14 18:54:48 +- README updated on 2025-09-17 19:46:22 +- README updated on 2025-09-21 10:31:22 +- README updated on 2025-09-24 19:25:49 +- README updated on 2025-10-03 20:29:19 diff --git a/controllers/platform_controllers/base_controller.py b/controllers/platform_controllers/base_controller.py index cfaa655..982c0b2 100644 --- a/controllers/platform_controllers/base_controller.py +++ b/controllers/platform_controllers/base_controller.py @@ -7,7 +7,7 @@ from PyQt5.QtCore import QObject from typing import Dict, Any, Optional, Tuple import random -from views.tabs.generator_tab import GeneratorTab +from views.tabs.generator_tab_factory import GeneratorTabFactory from views.tabs.accounts_tab import AccountsTab # SettingsTab import entfernt - wird nicht mehr verwendet @@ -86,10 +86,13 @@ class BasePlatformController(QObject): def create_generator_tab(self): """ - Erstellt den Generator-Tab. - Diese Methode sollte von Unterklassen überschrieben werden. + Erstellt den Generator-Tab mit der Factory. + Unterklassen können diese Methode überschreiben für spezielle Anforderungen. + + Returns: + QWidget: Plattform-spezifischer oder generischer Generator-Tab """ - return GeneratorTab(self.platform_name, self.language_manager) + return GeneratorTabFactory.create_tab(self.platform_name, self.language_manager) def create_accounts_tab(self): """ diff --git a/controllers/platform_controllers/facebook_controller.py b/controllers/platform_controllers/facebook_controller.py index fcfe357..5657bda 100644 --- a/controllers/platform_controllers/facebook_controller.py +++ b/controllers/platform_controllers/facebook_controller.py @@ -11,7 +11,7 @@ from typing import Dict, Any from controllers.platform_controllers.base_controller import BasePlatformController from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread -from views.tabs.generator_tab import GeneratorTab # Verwende generischen GeneratorTab +from views.tabs.generator_tab_factory import GeneratorTabFactory # Factory Pattern für Tab-Erstellung from views.widgets.forge_animation_widget import ForgeAnimationDialog # Forge Dialog für Animation from social_networks.facebook.facebook_automation import FacebookAutomation @@ -127,38 +127,39 @@ class FacebookController(BasePlatformController): def get_generator_tab(self): """ Erstellt und konfiguriert den Generator-Tab für Facebook. - + Verwendet die Factory für saubere Tab-Erstellung. + Returns: - GeneratorTab: Konfigurierter Tab für Facebook mit Geschlechtsauswahl + QWidget: Facebook-spezifischer Tab mit Geschlechtsauswahl """ - # Erstelle generischen Generator-Tab - generator_tab = GeneratorTab( + # Verwende Factory Pattern für Tab-Erstellung + # Die Factory entscheidet, welche Tab-Implementierung verwendet wird + generator_tab = GeneratorTabFactory.create_tab( "Facebook", self.language_manager ) - - # Facebook-spezifische Konfiguration - self._configure_facebook_fields(generator_tab) - - # Verbinde Signale - generator_tab.start_requested.connect(self.handle_generation_request) - generator_tab.stop_requested.connect(self.stop_generation) - + + # Verbinde Signale unabhängig von der Tab-Implementierung + # Alle Tabs müssen diese Signale unterstützen (Interface-Kontrakt) + if hasattr(generator_tab, 'start_requested'): + generator_tab.start_requested.connect(self.handle_generation_request) + if hasattr(generator_tab, 'stop_requested'): + generator_tab.stop_requested.connect(self.stop_generation) + return generator_tab def _configure_facebook_fields(self, generator_tab): """ - Konfiguriert Facebook-spezifische Felder im Generator-Tab. - + Legacy-Methode für Abwärtskompatibilität. + Die Konfiguration erfolgt jetzt in FacebookGeneratorTab. + Args: generator_tab: Der zu konfigurierende Tab """ - # Facebook-spezifische Konfiguration - # Der GeneratorTab kann für Facebook-spezifische Felder erweitert werden - # Geschlecht wird aus den Parametern extrahiert - # Vor- und Nachnamen werden im Worker-Thread aus full_name extrahiert - - logger.debug("Facebook-spezifische Felder konfiguriert (inkl. Geschlechtsauswahl)") + # Diese Methode ist nicht mehr nötig, da FacebookGeneratorTab + # bereits alle Facebook-spezifischen Felder enthält + # Behalten für Abwärtskompatibilität + pass def handle_generation_request(self, params: Dict[str, Any]): """ diff --git a/controllers/platform_controllers/gmail_controller.py b/controllers/platform_controllers/gmail_controller.py index 763cbfd..56fa3d2 100644 --- a/controllers/platform_controllers/gmail_controller.py +++ b/controllers/platform_controllers/gmail_controller.py @@ -89,8 +89,8 @@ class GmailController(BasePlatformController): def create_generator_tab(self): """Erstellt den Generator-Tab und verbindet die Signale""" - from views.tabs.generator_tab import GeneratorTab - generator_tab = GeneratorTab(self.platform_name, self.language_manager) + # Verwende die Basisimplementierung, die die GeneratorTabFactory nutzt + generator_tab = super().create_generator_tab() # Signal verbinden generator_tab.start_requested.connect(self.start_account_creation) diff --git a/controllers/platform_controllers/instagram_controller.py b/controllers/platform_controllers/instagram_controller.py index 082bc33..23ceef5 100644 --- a/controllers/platform_controllers/instagram_controller.py +++ b/controllers/platform_controllers/instagram_controller.py @@ -11,7 +11,7 @@ from typing import Dict, Any from controllers.platform_controllers.base_controller import BasePlatformController from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread -from views.tabs.generator_tab import GeneratorTab +from views.tabs.generator_tab_factory import GeneratorTabFactory from views.tabs.accounts_tab import AccountsTab from views.tabs.settings_tab import SettingsTab from views.widgets.forge_animation_widget import ForgeAnimationDialog @@ -178,7 +178,8 @@ class InstagramController(BasePlatformController): def create_generator_tab(self): """Erstellt den Instagram-Generator-Tab.""" - generator_tab = GeneratorTab(self.platform_name, self.language_manager) + # Verwende die Basisimplementierung, die die GeneratorTabFactory nutzt + generator_tab = super().create_generator_tab() # Instagram-spezifische Anpassungen # Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden @@ -412,4 +413,4 @@ class InstagramController(BasePlatformController): "birthday": "Geburtsdatum" } - return field_labels.get(field_type, field_type.capitalize()) \ No newline at end of file + return field_labels.get(field_type, field_type.capitalize()) diff --git a/controllers/platform_controllers/ok_ru_controller.py b/controllers/platform_controllers/ok_ru_controller.py index d16f31b..c4362cb 100644 --- a/controllers/platform_controllers/ok_ru_controller.py +++ b/controllers/platform_controllers/ok_ru_controller.py @@ -7,7 +7,6 @@ from PyQt5.QtCore import QThread, pyqtSignal from controllers.platform_controllers.base_controller import BasePlatformController from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread -from views.tabs.generator_tab import GeneratorTab from views.widgets.forge_animation_widget import ForgeAnimationDialog from social_networks.ok_ru.ok_ru_automation import OkRuAutomation @@ -44,7 +43,8 @@ class OkRuController(BasePlatformController): def create_generator_tab(self): """Erstellt den Generator-Tab für OK.ru.""" - generator_tab = GeneratorTab(self.platform_name, self.language_manager) + # Verwende die Basisimplementierung, die die GeneratorTabFactory nutzt + generator_tab = super().create_generator_tab() # OK.ru verwendet nur Telefon-Registrierung # Keine spezielle Konfiguration nötig, da GeneratorTab standardmäßig alle Felder hat @@ -190,4 +190,4 @@ class OkRuController(BasePlatformController): self.forge_dialog = None # Normale Verarbeitung - self.handle_account_created(result) \ No newline at end of file + self.handle_account_created(result) diff --git a/controllers/platform_controllers/tiktok_controller.py b/controllers/platform_controllers/tiktok_controller.py index bceb15f..74ff97d 100644 --- a/controllers/platform_controllers/tiktok_controller.py +++ b/controllers/platform_controllers/tiktok_controller.py @@ -10,7 +10,7 @@ from typing import Dict, Any from controllers.platform_controllers.base_controller import BasePlatformController from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread -from views.tabs.generator_tab import GeneratorTab +from views.tabs.generator_tab_factory import GeneratorTabFactory from views.tabs.accounts_tab import AccountsTab from views.tabs.settings_tab import SettingsTab from views.widgets.forge_animation_widget import ForgeAnimationDialog @@ -183,7 +183,8 @@ class TikTokController(BasePlatformController): def create_generator_tab(self): """Erstellt den TikTok-Generator-Tab.""" - generator_tab = GeneratorTab(self.platform_name, self.language_manager) + # Verwende die Basisimplementierung, die die GeneratorTabFactory nutzt + generator_tab = super().create_generator_tab() # TikTok-spezifische Anpassungen # Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden @@ -416,4 +417,4 @@ class TikTokController(BasePlatformController): self.forge_dialog = None # Normale Verarbeitung - self.handle_account_created(result) \ No newline at end of file + self.handle_account_created(result) diff --git a/controllers/platform_controllers/x_controller.py b/controllers/platform_controllers/x_controller.py index 0a84829..41e0c84 100644 --- a/controllers/platform_controllers/x_controller.py +++ b/controllers/platform_controllers/x_controller.py @@ -11,7 +11,7 @@ from typing import Dict, Any, Tuple from controllers.platform_controllers.base_controller import BasePlatformController from controllers.platform_controllers.base_worker_thread import BaseAccountCreationWorkerThread -from views.tabs.generator_tab import GeneratorTab +from views.tabs.generator_tab_factory import GeneratorTabFactory from views.tabs.accounts_tab import AccountsTab from views.tabs.settings_tab import SettingsTab from views.widgets.forge_animation_widget import ForgeAnimationDialog @@ -180,7 +180,8 @@ class XController(BasePlatformController): def create_generator_tab(self): """Erstellt den X-Generator-Tab.""" - generator_tab = GeneratorTab(self.platform_name, self.language_manager) + # Verwende die Basisimplementierung, die die GeneratorTabFactory nutzt + generator_tab = super().create_generator_tab() # X-spezifische Anpassungen # Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden @@ -414,4 +415,4 @@ class XController(BasePlatformController): "birthday": "Geburtsdatum" } - return field_labels.get(field_type, field_type.capitalize()) \ No newline at end of file + return field_labels.get(field_type, field_type.capitalize()) diff --git a/database/accounts.db b/database/accounts.db index ee292061f3b260b321469589ba560f34baecbfbd..2f20744c3148559d44116eb4d605472bee22af6f 100644 GIT binary patch delta 1423 zcma)*-A~(A7{=|BV-Y7IAEFDSEs%soE9(OGv7KYnsg$D9YCxew4it(qCvh0%qbPv_ zLTd;Js#Ptbs?_%aZD{Sbj#Zkr?jo9Yvrdz$X%|&@!@e%Kn6%5a{Rfx>n~=DexUglt z=XuY0&+mCWzvZ8wuUtLCtuPF8>2|Q0UHR&r^+q=HNWiD;VNcvDGnekTCQc(y{A-`U zd(eQsIFK(6>t_RdxYpHHP}S~W6q zR5D^>R8A>k)X+pTN@Fxd#bky^X)%7xrn5;(lV(~rqp~cdqB2P-QB~8_s41k47?p@B zDk=T?t(UCzpyI8Uw&`kH+}#6ZUc7zV&hkw()Gd^wNuiHTZ#VS0n7x*h?CF zNpliEU~O?%7nGf{G_HPEm$lED)91Ar` zm0qUqze$G2bCbqU))>2Nyqh%=nW3tK4O@ek13kx-AFB1?$2_^!oBb>j;jd0tt9dR>^^FB9Y(HrI3 zZXVNX^+KqSU#M7UTn^MV7xdTIg0F?~E%VU;V`un-90e^;(4lIXsFI>mNe~oOQRO#+ z;hbrv!%aa=P$)bO*&>jCh`NwQpzt}agy1GBgXALeL+Bz7z}9D0t$Pz4fg2;(0Z$Ke zPWWhvdleGD<2I=M1vM1ABG}17*B2-P@BNDOyTEbK-N~t5K@l`b(KJyNL|G*gT=*MR z`9vBcBqpe$rbtv!i2^OZp&H;DZPn2I2@((O*<6C!ph&XSYJ7;7VXTj9{omQ_aIcn& z!Rjnm2JTsG_iLK0q)AdEF@oe zVfzX4!pU{60^0J}1Nq|?!`^ya0XH5Y2OM3qs4Ht+9q5aw%F{MAIh8fS)~098aq}t| ze;}bazl0}Pn7fK!hwTpT0$j5$2<_8cttUoHHjo&lGNu2z;3Y(mC52K!6s4G^itua* z*Mz%`>~QXffx&&@F8yu|8YP;jfN>2M;m0b@f54f8@bionp)Z%P8}hSU6?|8Z?XMWr RNK->IGg_L&w9Kw8{{Xz%j|u<) delta 242 zcmZo@5N~)OHbI)Toq>TNdZL0oWBbO0`TC40n+#YSI28G#fx-;YlX(i9HVYaE@HfZR zZ;z{ITvosw!qhvR{R3k)59>je(>&G89Gr)^uWz69fpI0{WCeq;$q&+4+9!Tu1Y)M` z6F)H*%QA=X#ZG4zU|9(?Xf5ydSUHyEf)FtSxR@Qwaz^G5p492=4q&mZ+|&JiSh(8r zeOR{V`>;+G7UAG}!ocsuw}g+MH;HE_k1}^F*At+DXSlc< None: + """ + Registriert eine Tab-Klasse für eine bestimmte Plattform. + + Args: + platform: Name der Plattform (wird zu lowercase konvertiert) + tab_class: Die Tab-Klasse für diese Plattform + """ + platform_lower = platform.lower() + cls._tab_registry[platform_lower] = tab_class + logger.info(f"Tab-Klasse {tab_class.__name__} für Plattform '{platform}' registriert") + + @classmethod + def create_tab(cls, platform: str, language_manager: Optional[object] = None) -> QWidget: + """ + Erstellt einen Generator-Tab für die angegebene Plattform. + + Diese Methode verwendet Lazy Loading für plattform-spezifische Tabs, + um zirkuläre Imports zu vermeiden und die Performance zu optimieren. + + Args: + platform: Name der Plattform + language_manager: Optionaler Language Manager für Internationalisierung + + Returns: + QWidget: Der erstellte Generator-Tab + + Raises: + ValueError: Wenn die Plattform nicht unterstützt wird + """ + platform_lower = platform.lower() + + # Lazy Loading und Registrierung für bekannte Plattformen + if platform_lower not in cls._tab_registry: + cls._lazy_load_platform_tab(platform_lower) + + # Tab-Klasse aus Registry holen + tab_class = cls._tab_registry.get(platform_lower) + + if tab_class: + logger.info(f"Erstelle {tab_class.__name__} für Plattform '{platform}'") + try: + return tab_class(platform, language_manager) + except Exception as e: + logger.error(f"Fehler beim Erstellen des Tabs für '{platform}': {e}") + logger.info(f"Fallback auf generischen GeneratorTab für '{platform}'") + return GeneratorTab(platform, language_manager) + else: + # Fallback auf generischen Tab + logger.info(f"Verwende generischen GeneratorTab für Plattform '{platform}'") + return GeneratorTab(platform, language_manager) + + @classmethod + def _lazy_load_platform_tab(cls, platform: str) -> None: + """ + Lädt plattform-spezifische Tab-Klassen lazy (bei Bedarf). + + Dies vermeidet zirkuläre Imports und verbessert die Startzeit, + indem nur benötigte Module geladen werden. + + Args: + platform: Name der Plattform (lowercase) + """ + try: + if platform == "facebook": + from views.tabs.facebook_generator_tab import FacebookGeneratorTab + cls.register_tab("facebook", FacebookGeneratorTab) + + elif platform == "instagram": + # Instagram verwendet möglicherweise einen spezifischen Tab + # Vorerst den generischen Tab verwenden + cls.register_tab("instagram", GeneratorTab) + + elif platform == "tiktok": + # TikTok könnte einen eigenen Tab haben + # Prüfe ob TikTokGeneratorTab existiert + try: + from views.tabs.tiktok_generator_tab import TikTokGeneratorTab + cls.register_tab("tiktok", TikTokGeneratorTab) + except ImportError: + # Fallback auf generischen Tab + cls.register_tab("tiktok", GeneratorTab) + + elif platform == "x": + # X (Twitter) Tab + try: + from views.tabs.x_generator_tab import XGeneratorTab + cls.register_tab("x", XGeneratorTab) + except ImportError: + cls.register_tab("x", GeneratorTab) + + else: + # Unbekannte Plattform - verwende generischen Tab + cls.register_tab(platform, GeneratorTab) + + except ImportError as e: + logger.warning(f"Konnte spezifischen Tab für '{platform}' nicht laden: {e}") + # Registriere generischen Tab als Fallback + cls.register_tab(platform, GeneratorTab) + + @classmethod + def get_supported_platforms(cls) -> list: + """ + Gibt eine Liste der unterstützten Plattformen zurück. + + Returns: + list: Liste der Plattform-Namen + """ + # Bekannte Plattformen, die lazy geladen werden können + known_platforms = ["facebook", "instagram", "tiktok", "x"] + + # Bereits registrierte Plattformen hinzufügen + registered = list(cls._tab_registry.keys()) + + # Kombiniere und entferne Duplikate + all_platforms = list(set(known_platforms + registered)) + all_platforms.sort() + + return all_platforms + + @classmethod + def is_platform_supported(cls, platform: str) -> bool: + """ + Prüft, ob eine Plattform unterstützt wird. + + Args: + platform: Name der Plattform + + Returns: + bool: True wenn unterstützt, sonst False + """ + return platform.lower() in cls.get_supported_platforms() + + @classmethod + def clear_registry(cls) -> None: + """ + Löscht die Tab-Registry. + Hauptsächlich für Tests und Cleanup. + """ + cls._tab_registry.clear() + logger.debug("Tab-Registry gelöscht") + + +# Convenience-Funktion für direkten Import +def create_generator_tab(platform: str, language_manager: Optional[object] = None) -> QWidget: + """ + Convenience-Funktion zum Erstellen eines Generator-Tabs. + + Args: + platform: Name der Plattform + language_manager: Optionaler Language Manager + + Returns: + QWidget: Der erstellte Generator-Tab + """ + return GeneratorTabFactory.create_tab(platform, language_manager) \ No newline at end of file diff --git a/views/widgets/forge_animation_widget.py b/views/widgets/forge_animation_widget.py index c96943f..c161d4a 100644 --- a/views/widgets/forge_animation_widget.py +++ b/views/widgets/forge_animation_widget.py @@ -23,13 +23,13 @@ class ForgeAnimationDialog(QDialog): # Timer für das regelmäßige Nach-vorne-Holen self.raise_timer = QTimer() self.raise_timer.timeout.connect(self._raise_to_front) - self.raise_timer.setInterval(500) # Alle 500ms + self.raise_timer.setInterval(1000) # Alle 1000ms (weniger aggressiv) def init_ui(self): """Initialisiert die UI mit verbessertem Design""" - # Dialog-Fenster ohne Stay-on-Top, um Browser nicht zu blockieren - # Entfernt Qt.WindowStaysOnTopHint, da dies den Browser-Fokus stören kann - self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + # Dialog-Fenster mit Stay-on-Top für Sichtbarkeit + # WindowStaysOnTopHint sorgt dafür, dass die Warnung immer sichtbar bleibt + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setModal(False) # Nicht modal - blockiert nicht das Hauptfenster self.setFixedSize(650, 600) # Ursprüngliche Größe beibehalten @@ -187,8 +187,8 @@ class ForgeAnimationDialog(QDialog): def start_animation(self): """Zeigt den Dialog an""" self.status_label.setText("Initialisiere...") - # Timer deaktiviert - verhindert Fokus-Probleme mit dem Browser - # self.raise_timer.start() # Deaktiviert: Stört Browser-Interaktion + # Timer aktiviert als Fallback für Window-Manager die TopHint ignorieren + self.raise_timer.start() # Fallback: Hält Dialog zuverlässig im Vordergrund def stop_animation(self): """Stoppt die Animation und den Timer""" @@ -227,9 +227,9 @@ class ForgeAnimationDialog(QDialog): super().keyPressEvent(event) def _raise_to_front(self): - """Holt den Dialog in den Vordergrund""" + """Holt den Dialog in den Vordergrund ohne Fokus zu stehlen""" self.raise_() - # Nicht activateWindow() aufrufen - das holt das Hauptfenster mit + # Nicht activateWindow() aufrufen - das würde den Fokus vom Browser stehlen def show(self): """Überschreibt show() um den Dialog richtig zu positionieren"""