diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 540d574..6f12e14 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,9 +13,11 @@ "Bash(claude config)", "Bash(claude config list:*)", "Bash(claude mcp)", - "Bash(claude mcp:*)" + "Bash(claude mcp:*)", + "Bash(sqlite3:*)" ], "deny": [], + "defaultMode": "acceptEdits", "additionalDirectories": [ "/mnt/a/GiTea/Styleguide" ] diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index 8e60da1..0c39541 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -5,9 +5,9 @@ ## Project Overview - **Path**: `A:\GiTea\AccountForger` -- **Files**: 1039 files -- **Size**: 380.8 MB -- **Last Modified**: 2025-08-12 12:50 +- **Files**: 886 files +- **Size**: 125.8 MB +- **Last Modified**: 2025-09-10 21:18 ## Technology Stack @@ -23,12 +23,11 @@ check_rotation_system.py CLAUDE_PROJECT_README.md debug_video_issue.py -gitea_push_debug.txt +init install_requirements.py main.py package.json README.md -requirements.txt application/ │ ├── __init__.py │ ├── services/ @@ -77,7 +76,8 @@ controllers/ │ ├── method_rotation_worker_mixin.py │ ├── ok_ru_controller.py │ ├── rotation_error_handler.py -│ └── safe_imports.py +│ ├── safe_imports.py +│ └── mixins database/ │ ├── accounts.db │ ├── account_repository.py @@ -103,6 +103,7 @@ domain/ │ │ ├── method_rotation.py │ │ ├── rate_limit_policy.py │ │ └── __init__.py +│ ├── enums │ ├── repositories/ │ │ ├── analytics_repository.py │ │ ├── fingerprint_repository.py @@ -169,29 +170,33 @@ localization/ │ ├── fr.json │ └── ja.json logs/ -│ ├── instagram_automation.log -│ ├── instagram_controller.log -│ ├── instagram_login.log -│ ├── instagram_registration.log -│ ├── instagram_ui_helper.log -│ ├── instagram_utils.log -│ ├── instagram_verification.log -│ ├── instagram_workflow.log -│ ├── main.log +│ ├── facebook_automation.log +│ ├── facebook_controller.log +│ ├── facebook_login.log +│ ├── facebook_registration.log +│ ├── facebook_ui_helper.log +│ ├── facebook_utils.log +│ ├── facebook_verification.log +│ ├── facebook_workflow.log +│ ├── gmail_controller.log │ ├── net/ │ │ ├── gmail_landing_de.html │ │ └── google_signup_mail_de.html +│ ├── proxyfather_tests/ +│ │ ├── free_test_20250814_134106.log +│ │ ├── free_test_20250814_134123.log +│ │ └── free_test_20250814_135000.log │ └── screenshots/ -│ ├── after_account_create_click_1753044575.png -│ ├── after_account_create_click_1753044886.png -│ ├── after_account_create_click_1753045178.png -│ ├── after_account_create_click_1753045715.png -│ ├── after_account_create_click_1753045915.png -│ ├── after_account_create_click_1753046167.png -│ ├── after_account_create_click_1753046976.png -│ ├── after_account_create_click_1753047240.png -│ ├── after_account_create_click_1753047386.png -│ └── after_account_create_click_1753048280.png +│ ├── after_account_create_click_1755281004.png +│ ├── after_account_create_click_1757531758.png +│ ├── after_code_retrieval_1757531778.png +│ ├── after_login_button_click_1755168832.png +│ ├── after_login_button_click_1755280227.png +│ ├── after_login_button_click_1755280551.png +│ ├── after_login_button_click_1755280826.png +│ ├── after_login_button_click_1755282576.png +│ ├── after_login_button_click_1755282842.png +│ └── after_login_button_click_1755341790.png resources/ │ ├── icons/ │ │ ├── check-white.svg @@ -254,23 +259,13 @@ social_networks/ │ │ ├── tiktok_automation.py │ │ ├── tiktok_login.py │ │ ├── tiktok_registration.py -│ │ ├── tiktok_registration_backup.py -│ │ ├── tiktok_registration_clean.py -│ │ ├── tiktok_registration_final.py -│ │ ├── tiktok_registration_new.py │ │ ├── tiktok_selectors.py │ │ ├── tiktok_ui_helper.py -│ │ └── tiktok_utils.py -│ ├── twitter/ -│ │ ├── twitter_automation.py -│ │ ├── twitter_login.py -│ │ ├── twitter_registration.py -│ │ ├── twitter_selectors.py -│ │ ├── twitter_ui_helper.py -│ │ ├── twitter_utils.py -│ │ ├── twitter_verification.py -│ │ ├── twitter_workflow.py +│ │ ├── tiktok_utils.py +│ │ ├── tiktok_verification.py +│ │ ├── tiktok_workflow.py │ │ └── __init__.py +│ ├── twitter │ ├── vk/ │ │ ├── vk_automation.py │ │ ├── vk_login.py @@ -295,7 +290,52 @@ styles/ │ ├── modal_styles.py │ └── __init__.py tests/ -│ └── test_method_rotation.py +│ ├── test_method_rotation.py +│ ├── bright_data/ +│ │ └── logs/ +│ │ ├── proxy_test_20250813_172156.log +│ │ ├── proxy_test_20250813_173258.log +│ │ ├── proxy_test_20250813_173828.log +│ │ ├── proxy_test_20250813_174940.log +│ │ ├── proxy_test_20250813_175937.log +│ │ ├── proxy_test_20250813_180630.log +│ │ ├── proxy_test_20250813_181050.log +│ │ ├── proxy_test_20250813_182854.log +│ │ ├── proxy_test_20250813_185011.log +│ │ └── proxy_test_20250813_185850.log +│ ├── ProxyFather/ +│ │ └── logs/ +│ │ ├── connection_test_20250814_015554.log +│ │ ├── connection_test_20250814_023651.log +│ │ ├── instagram_test_20250814_015725.log +│ │ ├── instagram_test_20250814_015846.log +│ │ ├── instagram_test_20250814_020102.log +│ │ ├── instagram_test_20250814_020429.log +│ │ ├── instagram_test_20250814_021100.log +│ │ ├── instagram_test_20250814_022223.log +│ │ ├── instagram_test_20250814_023255.log +│ │ └── instagram_test_20250814_024719.log +│ └── proxy_evaluation/ +│ ├── test_results/ +│ │ ├── baseline_summary.json +│ │ ├── baseline_test_20250813_143756.json +│ │ ├── baseline_test_20250813_144600.json +│ │ ├── browser_proxy_test_20250813_143241.json +│ │ ├── browser_proxy_test_20250813_144227.json +│ │ ├── browser_test_summary.json +│ │ ├── facebook_isp_dedicated.png +│ │ ├── facebook_isp_shared.png +│ │ ├── facebook_test_20250813_150605.json +│ │ └── ip_rotation_test_20250813_142319.json +│ └── test_screenshots/ +│ ├── baseline_google.png +│ ├── baseline_instagram.png +│ ├── baseline_tiktok.png +│ ├── basic_connection.png +│ ├── gmail_loaded.png +│ ├── instagram_loaded.png +│ ├── tiktok_loaded.png +│ └── twitter_x_loaded.png themes/ │ ├── qss_generator.py │ ├── theme_config.py @@ -337,19 +377,18 @@ views/ │ ├── accounts_tab.py │ ├── facebook_generator_tab.py │ ├── generator_tab.py - │ ├── generator_tab_modern.py │ └── settings_tab.py └── widgets/ ├── account_card.py ├── account_creation_modal.py - ├── account_creation_modal_v2.py ├── dark_mode_toggle.py ├── forge_animation_widget.py - ├── forge_animation_widget_v2.py ├── icon_factory.py ├── language_dropdown.py ├── login_process_modal.py - └── modern_message_box.py + ├── modern_message_box.py + ├── platform_button.py + └── progress_modal.py ``` ## Key Files @@ -374,17 +413,5 @@ This project is managed with Claude Project Manager. To work with this project: ## Development Log -- README generated on 2025-07-27 11:07:01 -- README updated on 2025-07-28 18:14:33 -- README updated on 2025-07-29 19:24:34 -- README updated on 2025-07-31 00:00:41 -- README updated on 2025-08-01 19:02:35 -- README updated on 2025-08-01 20:50:22 -- README updated on 2025-08-01 20:51:41 -- README updated on 2025-08-01 21:06:44 -- README updated on 2025-08-09 01:31:28 -- README updated on 2025-08-10 00:03:51 -- README updated on 2025-08-10 12:55:25 -- README updated on 2025-08-11 19:49:09 -- README updated on 2025-08-12 12:50:03 -- README updated on 2025-08-12 13:20:51 +- README generated on 2025-09-06 21:49:36 +- README updated on 2025-09-13 20:45:18 diff --git a/README.md b/README.md index c459dfa..91cdb0f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Social Media Account Generator +# AccountForger – Social Media Account Generator -Dieses Repository enthält eine Desktopanwendung zur automatisierten Erstellung und Verwaltung von Social‑Media‑Accounts. Die grafische Oberfläche basiert auf **PyQt5**, die Browser‑Automatisierung erfolgt mit **Playwright**. Der Code ist modular aufgebaut und kann leicht um weitere Plattformen erweitert werden. +Desktop‑Anwendung zur Erstellung und Verwaltung von Social‑Media‑Accounts. UI mit **PyQt5**, Automatisierung mit **Playwright**. Modularer Aufbau mit plattformspezifischen Controllern. ## Installation @@ -22,92 +22,49 @@ Beim ersten Start werden benötigte Ordner wie `logs`, `config` und `resources` ├── main.py ├── browser/ │ ├── playwright_manager.py -│ └── stealth_config.py +│ ├── stealth_config.py +│ ├── fingerprint_protection.py +│ └── cookie_consent_handler.py ├── controllers/ │ ├── main_controller.py │ ├── account_controller.py +│ ├── session_controller.py │ ├── settings_controller.py │ └── platform_controllers/ │ ├── base_controller.py │ ├── instagram_controller.py -│ └── tiktok_controller.py +│ ├── tiktok_controller.py +│ ├── facebook_controller.py +│ ├── gmail_controller.py +│ ├── x_controller.py +│ └── ok_ru_controller.py +├── social_networks/ +│ ├── base_automation.py +│ ├── instagram/ ... +│ ├── tiktok/ ... +│ ├── facebook/ ... +│ ├── gmail/ ... +│ ├── x/ ... # Ehemals „Twitter“ +│ └── vk/ ... ├── views/ │ ├── main_window.py │ ├── platform_selector.py │ ├── about_dialog.py -│ ├── widgets/ -│ │ └── platform_button.py -│ └── tabs/ -│ ├── generator_tab.py -│ ├── accounts_tab.py -│ └── settings_tab.py -├── social_networks/ -│ ├── base_automation.py -│ ├── instagram/ -│ │ └── ... -│ ├── tiktok/ -│ │ └── ... -│ ├── facebook/ -│ │ └── ... -│ └── twitter/ -│ └── ... +│ ├── tabs/ (generator_tab.py, settings_tab.py, accounts_tab.py) +│ └── widgets/ (dark_mode_toggle.py, ...) ├── localization/ │ ├── language_manager.py -│ └── languages/ -│ ├── de.json -│ ├── en.json -│ ├── es.json -│ ├── fr.json -│ └── ja.json -├── utils/ -│ ├── logger.py -│ ├── password_generator.py -│ ├── username_generator.py -│ ├── birthday_generator.py -│ ├── email_handler.py -│ ├── proxy_rotator.py -│ ├── human_behavior.py -│ ├── text_similarity.py -│ └── theme_manager.py -├── database/ -│ ├── db_manager.py -│ └── ... -├── licensing/ -│ ├── license_manager.py -│ ├── hardware_fingerprint.py -│ └── license_validator.py -├── updates/ -│ ├── update_checker.py -│ ├── downloader.py -│ ├── version.py -│ └── ... -├── config/ -│ ├── browser_config.json -│ ├── email_config.json -│ ├── proxy_config.json -│ ├── stealth_config.json -│ ├── license_config.json -│ ├── instagram_config.json -│ ├── facebook_config.json -│ ├── twitter_config.json -│ ├── tiktok_config.json -│ ├── theme.json -│ ├── app_version.json -│ └── update_config.json +│ └── languages/ (de.json, en.json, ...) +├── utils/ (logger.py, proxy_rotator.py, email_handler.py, ...) +├── database/ (db_manager.py, schema_v2.sql) +├── licensing/ (license_manager.py, hardware_fingerprint.py, ...) +├── updates/ (update_checker.py, downloader.py, version.py) +├── config/ (app_version.json, theme.json, proxy_config.json, ...) ├── resources/ -│ ├── icons/ -│ │ ├── instagram.svg -│ │ ├── facebook.svg -│ │ ├── twitter.svg -│ │ ├── tiktok.svg -│ │ └── vk.svg -│ └── themes/ -│ ├── light.qss -│ └── dark.qss -├── testcases/ -│ └── imap_test.py -├── requirements.txt -└── README.md +│ ├── icons/ (instagram.svg, x.svg/twitter.svg, ...) +│ └── themes/ (light.qss, dark.qss) +├── tests/ (test_method_rotation.py, ...) +└── requirements.txt ``` Weitere Ordner: @@ -118,13 +75,16 @@ Weitere Ordner: ## Lokalisierung -Im Ordner `localization/languages` befinden sich Übersetzungsdateien für Deutsch, Englisch, Spanisch, Französisch und Japanisch. Die aktuelle Sprache kann zur Laufzeit gewechselt werden. +In `localization/languages` liegen Übersetzungen für Deutsch, Englisch, Spanisch, Französisch und Japanisch. Die Sprache kann zur Laufzeit gewechselt werden. ## Lizenz und Updates -Die Ordner `licensing` und `updates` enthalten die Logik zur Lizenzprüfung und zum Update‑Management. Versionsinformationen werden in `updates/version.py` verwaltet. +`licensing` enthält Lizenzprüfung und Sitzungsverwaltung; `updates` prüft und lädt Updates. Versionen stehen in `updates/version.py`. ## Tests -Im Ordner `testcases` liegt beispielhaft `imap_test.py`, mit dem die IMAP‑Konfiguration getestet werden kann. +Tests liegen unter `tests/` und als einzelne `test_*.py` Dateien im Root. Umfang variiert je nach Komponente. +## Rechtliche Hinweise + +Die Nutzung dieser Software muss im Einklang mit geltendem Recht und den Nutzungsbedingungen der jeweiligen Plattformen erfolgen. Umgehung von Sicherheits‑, Identitäts‑ oder Verifizierungsmechanismen ist unzulässig. Keine echten personenbezogenen Daten ohne Einwilligung verwenden. diff --git a/config/proxy_config.json b/config/proxy_config.json index 7b8b830..c860d05 100644 --- a/config/proxy_config.json +++ b/config/proxy_config.json @@ -1,15 +1,9 @@ { - "ipv4": [ - "85.254.81.222:44444:14a38ed2efe94:04ed25fb1b" - ], - "ipv6": [ - "92.119.89.251:30015:14a4622431481:a488401704" - ], - "mobile": [ - "de1.4g.iproyal.com:7296:1rtSh0G:XswBCIqi1joy5dX" - ], + "ipv4": [], + "ipv6": [], + "mobile": [], "mobile_api": { - "marsproxies": "9zKXWpMEA1", + "marsproxies": "", "iproyal": "" } } \ No newline at end of file diff --git a/database/accounts.db b/database/accounts.db index 17c7aba..f687e16 100644 Binary files a/database/accounts.db and b/database/accounts.db differ diff --git a/docs/CLEAN_ARCHITECTURE.md b/docs/CLEAN_ARCHITECTURE.md index a0a02dc..761c752 100644 --- a/docs/CLEAN_ARCHITECTURE.md +++ b/docs/CLEAN_ARCHITECTURE.md @@ -1,5 +1,7 @@ # Clean Architecture Design - AccountForger +Status: Vorschlag/Entwurf. Diese Architektur ist im aktuellen Code nur teilweise umgesetzt und dient als Zielbild. Ordner‑/Dateinamen in Beispielen sind konzeptionell zu verstehen. + ## Übersicht Diese Dokumentation beschreibt die saubere Architektur für das AccountForger-System mit Fokus auf das Fingerprint-Management und die Login-Funktionalität mit gespeicherten Fingerprints. @@ -339,4 +341,4 @@ Diese Architektur stellt sicher, dass: - Fingerprints konsistent bleiben - Sessions zuverlässig wiederhergestellt werden - Der Code wartbar und erweiterbar bleibt -- Keine zirkulären Abhängigkeiten entstehen \ No newline at end of file +- Keine zirkulären Abhängigkeiten entstehen diff --git a/gitea_push_debug.txt b/gitea_push_debug.txt deleted file mode 100644 index 5f4e0de..0000000 --- a/gitea_push_debug.txt +++ /dev/null @@ -1,22 +0,0 @@ -Push Debug Info - 2025-08-01 23:50:28.066003 -Repository: AccountForger-neuerUpload -Owner: IntelSight -Path: A:\GiTea\AccountForger -Current branch: master -Git remotes: -origin https://StuXn3t:29aa2ffb5ef85bd4f56e2e7bd19098310a37f3bd@gitea-undso.intelsight.de/IntelSight/AccountForger-neuerUpload.git (fetch) -origin https://StuXn3t:29aa2ffb5ef85bd4f56e2e7bd19098310a37f3bd@gitea-undso.intelsight.de/IntelSight/AccountForger-neuerUpload.git (push) -Git status before push: -Clean -Push command: git push --set-upstream origin master:main -v -Push result: Success -Push stdout: -branch 'master' set up to track 'origin/main'. -Push stderr: -POST git-receive-pack (623173 bytes) -remote: . Processing 1 references -remote: Processed 1 references in total -Pushing to https://gitea-undso.intelsight.de/IntelSight/AccountForger-neuerUpload.git -To https://gitea-undso.intelsight.de/IntelSight/AccountForger-neuerUpload.git - * [new branch] master -> main -updating local tracking ref 'refs/remotes/origin/main' \ No newline at end of file diff --git a/init b/init new file mode 100644 index 0000000..912827b --- /dev/null +++ b/init @@ -0,0 +1,8 @@ +- Rolle: Präziser Code-Experte mit hohem Qualitätsanspruch. +- Prinzipien: Clean Code, Clean Architecture, sinnvolles Refactoring; kein Overengineering. +- YAGNI: Nur das Nötige, keine Aufblähung. +- Arbeitsmodus: Erst planen, dann nach ausdrücklicher Freigabe implementieren. + - Fehler & Logging: Klare, typsichere Fehler; minimal strukturiertes Logging; keine internen Details leaken. + - Security: Keine Secrets im Code; Eingaben validieren an Grenzen; Least-Privilege denken. + - Schnittstellen: Kleine, stabile öffentliche APIs; interne Details kapseln; Breaking Changes vermeiden. + - Performance: Kein Premature Optimization; einfache O(n)-Lösungen bevorzugen; Budget pro Feature definieren. diff --git a/social_networks/tiktok/tiktok_registration_backup.py b/social_networks/tiktok/tiktok_registration_backup.py deleted file mode 100644 index b785c51..0000000 --- a/social_networks/tiktok/tiktok_registration_backup.py +++ /dev/null @@ -1,2203 +0,0 @@ -# social_networks/tiktok/tiktok_registration.py - -""" -TikTok-Registrierung - Klasse für die Kontoerstellung bei TikTok -""" - -import time -import random -import re -from typing import Dict, List, Any, Optional, Tuple - -from .tiktok_selectors import TikTokSelectors -from .tiktok_workflow import TikTokWorkflow -from utils.logger import setup_logger - -# Konfiguriere Logger -logger = setup_logger("tiktok_registration") - -class TikTokRegistration: - """ - Klasse für die Registrierung von TikTok-Konten. - Enthält alle Methoden zur Kontoerstellung. - """ - - def __init__(self, automation): - """ - Initialisiert die TikTok-Registrierung. - - Args: - automation: Referenz auf die Hauptautomatisierungsklasse - """ - self.automation = automation - # Browser wird direkt von automation verwendet - self.selectors = TikTokSelectors() - self.workflow = TikTokWorkflow.get_registration_workflow() - - logger.debug("TikTok-Registrierung initialisiert") - - def register_account(self, full_name: str, age: int, registration_method: str = "email", - phone_number: str = None, **kwargs) -> Dict[str, Any]: - """ - Führt den vollständigen Registrierungsprozess für einen TikTok-Account durch. - - Args: - full_name: Vollständiger Name für den Account - age: Alter des Benutzers - registration_method: "email" oder "phone" - phone_number: Telefonnummer (nur bei registration_method="phone") - **kwargs: Weitere optionale Parameter - - Returns: - Dict[str, Any]: Ergebnis der Registrierung mit Status und Account-Daten - """ - # Browser wird direkt von automation verwendet - - # Validiere die Eingaben - if not self._validate_registration_inputs(full_name, age, registration_method, phone_number): - return { - "success": False, - "error": "Ungültige Eingabeparameter", - "stage": "input_validation" - } - - # Account-Daten generieren - account_data = self._generate_account_data(full_name, age, registration_method, phone_number, **kwargs) - - # Starte den Registrierungsprozess - logger.info(f"Starte TikTok-Registrierung für {account_data['username']} via {registration_method}") - - try: - # 1. Zur Startseite navigieren - self.automation._emit_customer_log("🌐 Mit TikTok verbinden...") - if not self._navigate_to_homepage(): - return { - "success": False, - "error": "Konnte nicht zur TikTok-Startseite navigieren", - "stage": "navigation", - "account_data": account_data - } - - # 2. Cookie-Banner behandeln - self.automation._emit_customer_log("⚙️ Einstellungen werden vorbereitet...") - self._handle_cookie_banner() - - # 3. Anmelden-Button klicken - self.automation._emit_customer_log("📋 Registrierungsformular wird geöffnet...") - if not self._click_login_button(): - return { - "success": False, - "error": "Konnte nicht auf Anmelden-Button klicken", - "stage": "login_button", - "account_data": account_data - } - - # 4. Registrieren-Link klicken - if not self._click_register_link(): - return { - "success": False, - "error": "Konnte nicht auf Registrieren-Link klicken", - "stage": "register_link", - "account_data": account_data - } - - # 5. Telefon/E-Mail-Option auswählen - if not self._click_phone_email_option(): - return { - "success": False, - "error": "Konnte nicht auf Telefon/E-Mail-Option klicken", - "stage": "phone_email_option", - "account_data": account_data - } - - # 6. E-Mail oder Telefon als Registrierungsmethode wählen - if not self._select_registration_method(registration_method): - return { - "success": False, - "error": f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen", - "stage": "registration_method", - "account_data": account_data - } - - # 7. Geburtsdatum eingeben - self.automation._emit_customer_log("🎂 Geburtsdatum wird festgelegt...") - if not self._enter_birthday(account_data["birthday"]): - return { - "success": False, - "error": "Fehler beim Eingeben des Geburtsdatums", - "stage": "birthday", - "account_data": account_data - } - - # 8. Registrierungsformular ausfüllen - self.automation._emit_customer_log("📝 Persönliche Daten werden übertragen...") - if not self._fill_registration_form(account_data, registration_method): - return { - "success": False, - "error": "Fehler beim Ausfüllen des Registrierungsformulars", - "stage": "registration_form", - "account_data": account_data - } - - # 9. Bestätigungscode wurde bereits in _fill_registration_form() behandelt - # (Code-Eingabe passiert jetzt BEVOR Passwort-Eingabe für bessere Stabilität) - logger.debug("Verifizierung bereits in optimierter Reihenfolge abgeschlossen") - - # 10. Benutzernamen erstellen - self.automation._emit_customer_log("👤 Benutzername wird erstellt...") - if not self._create_username(account_data): - return { - "success": False, - "error": "Fehler beim Erstellen des Benutzernamens", - "stage": "username", - "account_data": account_data - } - - # 11. Erfolgreiche Registrierung überprüfen - self.automation._emit_customer_log("🔍 Account wird finalisiert...") - if not self._check_registration_success(): - return { - "success": False, - "error": "Registrierung fehlgeschlagen oder konnte nicht verifiziert werden", - "stage": "final_check", - "account_data": account_data - } - - # Registrierung erfolgreich abgeschlossen - logger.info(f"TikTok-Account {account_data['username']} erfolgreich erstellt") - self.automation._emit_customer_log("✅ Account erfolgreich erstellt!") - - return { - "success": True, - "stage": "completed", - "account_data": account_data - } - - except Exception as e: - error_msg = f"Unerwarteter Fehler bei der TikTok-Registrierung: {str(e)}" - logger.error(error_msg, exc_info=True) - - return { - "success": False, - "error": error_msg, - "stage": "exception", - "account_data": account_data - } - - def _validate_registration_inputs(self, full_name: str, age: int, - registration_method: str, phone_number: str) -> bool: - """ - Validiert die Eingaben für die Registrierung. - - Args: - full_name: Vollständiger Name für den Account - age: Alter des Benutzers - registration_method: "email" oder "phone" - phone_number: Telefonnummer (nur bei registration_method="phone") - - Returns: - bool: True wenn alle Eingaben gültig sind, False sonst - """ - # Vollständiger Name prüfen - if not full_name or len(full_name) < 3: - logger.error("Ungültiger vollständiger Name") - return False - - # Alter prüfen - if age < 13: - logger.error("Benutzer muss mindestens 13 Jahre alt sein") - return False - - # Registrierungsmethode prüfen - if registration_method not in ["email", "phone"]: - logger.error(f"Ungültige Registrierungsmethode: {registration_method}") - return False - - # Telefonnummer prüfen, falls erforderlich - if registration_method == "phone" and not phone_number: - logger.error("Telefonnummer erforderlich für Registrierung via Telefon") - return False - - return True - - def _generate_account_data(self, full_name: str, age: int, registration_method: str, - phone_number: str, **kwargs) -> Dict[str, Any]: - """ - Generiert Account-Daten für die Registrierung. - - Args: - full_name: Vollständiger Name für den Account - age: Alter des Benutzers - registration_method: "email" oder "phone" - phone_number: Telefonnummer (nur bei registration_method="phone") - **kwargs: Weitere optionale Parameter - - Returns: - Dict[str, Any]: Generierte Account-Daten - """ - # Benutzername generieren - username = kwargs.get("username") - if not username: - username = self.automation.username_generator.generate_username("tiktok", full_name) - - # Passwort generieren - password = kwargs.get("password") - if not password: - password = self.automation.password_generator.generate_password("tiktok") - - # E-Mail generieren (falls nötig) - email = None - if registration_method == "email": - email_prefix = username.lower().replace(".", "").replace("_", "") - email = f"{email_prefix}@{self.automation.email_domain}" - - # Geburtsdatum generieren - birthday = self.automation.birthday_generator.generate_birthday_components("tiktok", age) - - # Account-Daten zusammenstellen - account_data = { - "username": username, - "password": password, - "full_name": full_name, - "email": email, - "phone": phone_number, - "birthday": birthday, - "age": age, - "registration_method": registration_method - } - - logger.debug(f"Account-Daten generiert: {account_data['username']}") - - return account_data - - def _navigate_to_homepage(self) -> bool: - """ - Navigiert zur TikTok-Startseite. - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Zur Startseite navigieren - self.automation.browser.navigate_to(self.selectors.BASE_URL) - - # Warten, bis die Seite geladen ist - self.automation.human_behavior.wait_for_page_load() - - # Screenshot erstellen - self.automation._take_screenshot("tiktok_homepage") - - # Prüfen, ob die Seite korrekt geladen wurde - mehrere Selektoren versuchen - page_loaded = False - login_button_selectors = [ - self.selectors.LOGIN_BUTTON, - self.selectors.LOGIN_BUTTON_CLASS, - "button.TUXButton:has-text('Anmelden')", - "button:has(.TUXButton-label:text('Anmelden'))", - "//button[contains(text(), 'Anmelden')]" - ] - - for selector in login_button_selectors: - if self.automation.browser.is_element_visible(selector, timeout=5000): - logger.info(f"TikTok-Startseite erfolgreich geladen - Login-Button gefunden: {selector}") - page_loaded = True - break - - if not page_loaded: - logger.warning("TikTok-Startseite nicht korrekt geladen - kein Login-Button gefunden") - # Debug: Seiteninhalt loggen - current_url = self.automation.browser.page.url - logger.debug(f"Aktuelle URL: {current_url}") - return False - - logger.info("Erfolgreich zur TikTok-Startseite navigiert") - return True - - except Exception as e: - logger.error(f"Fehler beim Navigieren zur TikTok-Startseite: {e}") - return False - - def _handle_cookie_banner(self) -> bool: - """ - Behandelt den Cookie-Banner, falls angezeigt. - Akzeptiert IMMER Cookies für vollständiges Session-Management bei der Registrierung. - - Returns: - bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler - """ - # Cookie-Dialog-Erkennung - if self.automation.browser.is_element_visible(self.selectors.COOKIE_DIALOG, timeout=2000): - logger.info("Cookie-Banner erkannt - akzeptiere alle Cookies für Session-Management") - - # Akzeptieren-Button suchen und klicken (PRIMÄR für Registrierung) - accept_success = self.automation.ui_helper.click_button_fuzzy( - self.selectors.get_button_texts("accept_cookies"), - self.selectors.COOKIE_ACCEPT_BUTTON - ) - - if accept_success: - logger.info("Cookie-Banner erfolgreich akzeptiert - Session-Cookies werden gespeichert") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - else: - logger.warning("Konnte Cookie-Banner nicht akzeptieren, versuche alternativen Akzeptieren-Button") - - # Alternative Akzeptieren-Selektoren versuchen - alternative_accept_selectors = [ - "//button[contains(text(), 'Alle akzeptieren')]", - "//button[contains(text(), 'Accept All')]", - "//button[contains(text(), 'Zulassen')]", - "//button[contains(text(), 'Allow All')]", - "//button[contains(@aria-label, 'Accept')]", - "[data-testid='accept-all-button']" - ] - - for selector in alternative_accept_selectors: - if self.automation.browser.is_element_visible(selector, timeout=1000): - if self.automation.browser.click_element(selector): - logger.info("Cookie-Banner mit alternativem Selector akzeptiert") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - logger.error("Konnte Cookie-Banner nicht akzeptieren - Session-Management könnte beeinträchtigt sein") - return False - else: - logger.debug("Kein Cookie-Banner erkannt") - return True - - def _click_login_button(self) -> bool: - """ - Klickt auf den Anmelden-Button auf der Startseite. - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Liste aller Login-Button-Selektoren, die wir versuchen wollen - login_selectors = [ - self.selectors.LOGIN_BUTTON, # button#header-login-button - self.selectors.LOGIN_BUTTON_CLASS, # button.TUXButton:has-text('Anmelden') - self.selectors.LOGIN_BUTTON_TOP_RIGHT, # button#top-right-action-bar-login-button - "button.TUXButton[id='header-login-button']", # Spezifischer Selektor - "button.TUXButton--primary:has-text('Anmelden')", # CSS-Klassen-basiert - "button[aria-label*='Anmelden']", # Aria-Label - "button:has(.TUXButton-label:text('Anmelden'))" # Verschachtelte Struktur - ] - - # Versuche jeden Selektor - for i, selector in enumerate(login_selectors): - logger.debug(f"Versuche Login-Selektor {i+1}: {selector}") - if self.automation.browser.is_element_visible(selector, timeout=3000): - result = self.automation.browser.click_element(selector) - if result: - logger.info(f"Anmelden-Button erfolgreich geklickt mit Selektor {i+1}") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - # Versuche es mit Fuzzy-Button-Matching - result = self.automation.ui_helper.click_button_fuzzy( - ["Anmelden", "Log in", "Login"], - self.selectors.LOGIN_BUTTON_FALLBACK - ) - - if result: - logger.info("Anmelden-Button über Fuzzy-Matching erfolgreich geklickt") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - logger.error("Konnte keinen Anmelden-Button finden") - return False - - except Exception as e: - logger.error(f"Fehler beim Klicken auf den Anmelden-Button: {e}") - return False - - def _click_register_link(self) -> bool: - """ - Klickt auf den Registrieren-Link im Login-Dialog. - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Warten, bis der Login-Dialog angezeigt wird - self.automation.human_behavior.random_delay(2.0, 3.0) - - # Screenshot für Debugging - self.automation._take_screenshot("after_login_button_click") - - # Verschiedene Registrieren-Selektoren versuchen - register_selectors = [ - "a:text('Registrieren')", # Direkter Text-Match - "button:text('Registrieren')", # Button-Text - "div:text('Registrieren')", # Div-Text - "span:text('Registrieren')", # Span-Text - "[data-e2e*='signup']", # Data-Attribute - "[data-e2e*='register']", # Data-Attribute - "a[href*='signup']", # Signup-Link - "//a[contains(text(), 'Registrieren')]", # XPath - "//button[contains(text(), 'Registrieren')]", # XPath Button - "//span[contains(text(), 'Registrieren')]", # XPath Span - "//div[contains(text(), 'Konto erstellen')]", # Alternative Text - "//a[contains(text(), 'Sign up')]", # Englisch - ".signup-link", # CSS-Klasse - ".register-link" # CSS-Klasse - ] - - # Versuche jeden Selektor - for i, selector in enumerate(register_selectors): - logger.debug(f"Versuche Registrieren-Selektor {i+1}: {selector}") - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - result = self.automation.browser.click_element(selector) - if result: - logger.info(f"Registrieren-Link erfolgreich geklickt mit Selektor {i+1}: {selector}") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - except Exception as e: - logger.debug(f"Selektor {i+1} fehlgeschlagen: {e}") - continue - - # Fallback: Fuzzy-Text-Suche - try: - page_content = self.automation.browser.page.content() - if "Registrieren" in page_content or "Sign up" in page_content: - logger.info("Registrieren-Text auf Seite gefunden, versuche Textklick") - # Versuche verschiedene Text-Klick-Strategien - text_selectors = [ - "text=Registrieren", - "text=Sign up", - "text=Konto erstellen" - ] - for text_sel in text_selectors: - try: - element = self.automation.browser.page.locator(text_sel).first - if element.is_visible(): - element.click() - logger.info(f"Auf Text geklickt: {text_sel}") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - except Exception: - continue - except Exception as e: - logger.debug(f"Fallback-Text-Suche fehlgeschlagen: {e}") - - logger.error("Konnte keinen Registrieren-Link finden") - return False - - except Exception as e: - logger.error(f"Fehler beim Klicken auf den Registrieren-Link: {e}") - # Debug-Screenshot bei Fehler - self.automation._take_screenshot("register_link_error") - return False - - def _click_phone_email_option(self) -> bool: - """ - Klickt auf die Telefon/E-Mail-Option im Registrierungsdialog. - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Warten, bis der Registrierungsdialog angezeigt wird - self.automation.human_behavior.random_delay(1.0, 2.0) - - # Prüfen, ob wir bereits die Optionen für Telefon/E-Mail sehen - if self.automation.browser.is_element_visible(self.selectors.EMAIL_FIELD, timeout=2000) or \ - self.automation.browser.is_element_visible(self.selectors.PHONE_FIELD, timeout=2000): - logger.info("Bereits auf der Telefon/E-Mail-Registrierungsseite") - return True - - # Versuche, die Telefon/E-Mail-Option zu finden und zu klicken - if self.automation.browser.is_element_visible(self.selectors.PHONE_EMAIL_OPTION, timeout=2000): - result = self.automation.browser.click_element(self.selectors.PHONE_EMAIL_OPTION) - if result: - logger.info("Telefon/E-Mail-Option erfolgreich geklickt") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - # Versuche es mit Fuzzy-Button-Matching - result = self.automation.ui_helper.click_button_fuzzy( - ["Telefonnummer oder E-Mail-Adresse nutzen", "Use phone or email", "Phone or email"], - self.selectors.PHONE_EMAIL_OPTION_FALLBACK - ) - - if result: - logger.info("Telefon/E-Mail-Option über Fuzzy-Matching erfolgreich geklickt") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - logger.error("Konnte keine Telefon/E-Mail-Option finden") - return False - - except Exception as e: - logger.error(f"Fehler beim Klicken auf die Telefon/E-Mail-Option: {e}") - return False - - def _select_registration_method(self, registration_method: str) -> bool: - """ - Wählt die Registrierungsmethode (E-Mail oder Telefon). - - Args: - registration_method: "email" oder "phone" - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Warten, bis die Registrierungsmethoden-Seite geladen ist - self.automation.human_behavior.random_delay(1.0, 2.0) - - if registration_method == "email": - # Wenn bereits das E-Mail-Feld sichtbar ist, sind wir schon auf der richtigen Seite - if self.automation.browser.is_element_visible(self.selectors.EMAIL_FIELD, timeout=1000): - logger.info("Bereits auf der E-Mail-Registrierungsseite") - return True - - # Suche nach dem "Mit E-Mail-Adresse registrieren" Link - if self.automation.browser.is_element_visible(self.selectors.EMAIL_OPTION, timeout=2000): - result = self.automation.browser.click_element(self.selectors.EMAIL_OPTION) - if result: - logger.info("E-Mail-Registrierungsmethode erfolgreich ausgewählt") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - # Versuche es mit Fuzzy-Button-Matching - result = self.automation.ui_helper.click_button_fuzzy( - ["Mit E-Mail-Adresse registrieren", "Register with email", "E-Mail-Adresse"], - self.selectors.EMAIL_OPTION_FALLBACK - ) - - if result: - logger.info("E-Mail-Option über Fuzzy-Matching erfolgreich geklickt") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - elif registration_method == "phone": - # Wenn bereits das Telefon-Feld sichtbar ist, sind wir schon auf der richtigen Seite - if self.automation.browser.is_element_visible(self.selectors.PHONE_FIELD, timeout=1000): - logger.info("Bereits auf der Telefon-Registrierungsseite") - return True - - # Suche nach dem "Mit Telefonnummer registrieren" Link - if self.automation.browser.is_element_visible(self.selectors.PHONE_OPTION, timeout=2000): - result = self.automation.browser.click_element(self.selectors.PHONE_OPTION) - if result: - logger.info("Telefon-Registrierungsmethode erfolgreich ausgewählt") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - # Versuche es mit Fuzzy-Button-Matching - result = self.automation.ui_helper.click_button_fuzzy( - ["Mit Telefonnummer registrieren", "Register with phone", "Telefonnummer"], - self.selectors.PHONE_OPTION_FALLBACK - ) - - if result: - logger.info("Telefon-Option über Fuzzy-Matching erfolgreich geklickt") - self.automation.human_behavior.random_delay(0.5, 1.5) - return True - - logger.error(f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen") - return False - - except Exception as e: - logger.error(f"Fehler beim Auswählen der Registrierungsmethode: {e}") - return False - - def _enter_birthday(self, birthday: Dict[str, int]) -> bool: - """ - Gibt das Geburtsdatum ein. - - Args: - birthday: Dictionary mit 'year', 'month', 'day' Schlüsseln - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Warten, bis die Geburtstagsauswahl angezeigt wird - self.automation.human_behavior.random_delay(1.0, 2.0) - - # Screenshot für Debugging - self.automation._take_screenshot("birthday_page") - - # Verschiedene Monat-Dropdown-Selektoren versuchen - month_selectors = [ - "div.css-1fi2hzv-DivSelectLabel:has-text('Monat')", # Exakt TikTok-Klasse - "div.e1phcp2x1:has-text('Monat')", # TikTok-Klasse alternative - "div:has-text('Monat')", # Text-basiert (funktioniert!) - self.selectors.BIRTHDAY_MONTH_DROPDOWN, # select[name='month'] - "div[data-e2e='date-picker-month']", # TikTok-spezifisch - "button[data-testid='month-selector']", # Test-ID - "div:has-text('Month')", # Englisch - "[aria-label*='Month']", # Aria-Label - "[aria-label*='Monat']", # Deutsch - "div[role='combobox']:has-text('Monat')", # Combobox - ".month-selector", # CSS-Klasse - ".date-picker-month", # CSS-Klasse - "//div[contains(text(), 'Monat')]", # XPath - "//button[contains(text(), 'Monat')]", # XPath Button - "//select[@name='month']" # XPath Select - ] - - month_dropdown = None - for i, selector in enumerate(month_selectors): - logger.debug(f"Versuche Monat-Selektor {i+1}: {selector}") - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - month_dropdown = self.automation.browser.page.locator(selector).first - logger.info(f"Monat-Dropdown gefunden mit Selektor {i+1}: {selector}") - break - except Exception as e: - logger.debug(f"Monat-Selektor {i+1} fehlgeschlagen: {e}") - continue - - if not month_dropdown: - logger.error("Monat-Dropdown nicht gefunden - alle Selektoren fehlgeschlagen") - return False - - month_dropdown.click() - self.automation.human_behavior.random_delay(0.3, 0.8) - - # Monat-Option auswählen - TikTok verwendet Monatsnamen! - month_names = ["Januar", "Februar", "März", "April", "Mai", "Juni", - "Juli", "August", "September", "Oktober", "November", "Dezember"] - month_name = month_names[birthday['month'] - 1] # birthday['month'] ist 1-12 - - month_selected = False - month_option_selectors = [ - f"div.css-vz5m7n-DivOption:has-text('{month_name}')", # Exakte TikTok-Klasse + Monatsname - f"div.e1phcp2x5:has-text('{month_name}')", # TikTok-Klasse alternative + Monatsname - f"[role='option']:has-text('{month_name}')", # Role + Monatsname - f"div:has-text('{month_name}')", # Einfach Monatsname - f"//div[@role='option'][contains(text(), '{month_name}')]", # XPath + Monatsname - f"option[value='{birthday['month']}']", # Standard HTML (Fallback) - f"div[data-value='{birthday['month']}']", # Custom Dropdown (Fallback) - f"li[data-value='{birthday['month']}']", # List-Item (Fallback) - f"button:has-text('{birthday['month']:02d}')", # Button mit Monatszahl (Fallback) - f"div:has-text('{birthday['month']:02d}')", # Div mit Monatszahl (Fallback) - f"[role='option']:has-text('{birthday['month']:02d}')" # Role-based Zahl (Fallback) - ] - - for i, option_selector in enumerate(month_option_selectors): - logger.debug(f"Versuche Monat-Option-Selektor {i+1}: {option_selector}") - try: - if self.automation.browser.is_element_visible(option_selector, timeout=1000): - self.automation.browser.click_element(option_selector) - logger.info(f"Monat {birthday['month']} ausgewählt mit Selektor {i+1}") - month_selected = True - break - except Exception as e: - logger.debug(f"Monat-Option-Selektor {i+1} fehlgeschlagen: {e}") - continue - - if not month_selected: - logger.error(f"Konnte Monat {birthday['month']} nicht auswählen") - return False - - self.automation.human_behavior.random_delay(0.3, 0.8) - - # Tag-Dropdown finden - day_selectors = [ - "div.css-1fi2hzv-DivSelectLabel:has-text('Tag')", # Exakt TikTok-Klasse - "div.e1phcp2x1:has-text('Tag')", # TikTok-Klasse alternative - "div:has-text('Tag')", # Text-basiert - self.selectors.BIRTHDAY_DAY_DROPDOWN, # select[name='day'] - "div[data-e2e='date-picker-day']", # TikTok-spezifisch - "button[data-testid='day-selector']", # Test-ID - "div:has-text('Day')", # Englisch - "[aria-label*='Day']", # Aria-Label - "[aria-label*='Tag']", # Deutsch - "div[role='combobox']:has-text('Tag')", # Combobox - ".day-selector", # CSS-Klasse - ".date-picker-day", # CSS-Klasse - "//div[contains(text(), 'Tag')]", # XPath - "//button[contains(text(), 'Tag')]", # XPath Button - "//select[@name='day']" # XPath Select - ] - - day_dropdown = None - for i, selector in enumerate(day_selectors): - logger.debug(f"Versuche Tag-Selektor {i+1}: {selector}") - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - day_dropdown = self.automation.browser.page.locator(selector).first - logger.info(f"Tag-Dropdown gefunden mit Selektor {i+1}: {selector}") - break - except Exception as e: - logger.debug(f"Tag-Selektor {i+1} fehlgeschlagen: {e}") - continue - - if not day_dropdown: - logger.error("Tag-Dropdown nicht gefunden") - return False - - day_dropdown.click() - self.automation.human_behavior.random_delay(0.3, 0.8) - - # Tag-Option auswählen - TikTok verwendet einfache Zahlen - day_selected = False - day_option_selectors = [ - f"div.css-vz5m7n-DivOption:has-text('{birthday['day']}')", # Exakte TikTok-Klasse + Tag - f"div.e1phcp2x5:has-text('{birthday['day']}')", # TikTok-Klasse alternative + Tag - f"[role='option']:has-text('{birthday['day']}')", # Role + Tag - f"div:has-text('{birthday['day']}')", # Einfach Tag - f"//div[@role='option'][contains(text(), '{birthday['day']}')]", # XPath + Tag - f"option[value='{birthday['day']}']", # Standard HTML (Fallback) - f"div[data-value='{birthday['day']}']", # Custom Dropdown (Fallback) - f"li[data-value='{birthday['day']}']", # List-Item (Fallback) - f"button:has-text('{birthday['day']:02d}')", # Button mit führender Null (Fallback) - f"div:has-text('{birthday['day']:02d}')" # Div mit führender Null (Fallback) - ] - - for i, option_selector in enumerate(day_option_selectors): - logger.debug(f"Versuche Tag-Option-Selektor {i+1}: {option_selector}") - try: - if self.automation.browser.is_element_visible(option_selector, timeout=1000): - self.automation.browser.click_element(option_selector) - logger.info(f"Tag {birthday['day']} ausgewählt mit Selektor {i+1}") - day_selected = True - break - except Exception as e: - logger.debug(f"Tag-Option-Selektor {i+1} fehlgeschlagen: {e}") - continue - - if not day_selected: - logger.error(f"Konnte Tag {birthday['day']} nicht auswählen") - return False - - self.automation.human_behavior.random_delay(0.3, 0.8) - - # Jahr-Dropdown finden - year_selectors = [ - "div.css-1fi2hzv-DivSelectLabel:has-text('Jahr')", # Exakt TikTok-Klasse - "div.e1phcp2x1:has-text('Jahr')", # TikTok-Klasse alternative - "div:has-text('Jahr')", # Text-basiert - self.selectors.BIRTHDAY_YEAR_DROPDOWN, # select[name='year'] - "div[data-e2e='date-picker-year']", # TikTok-spezifisch - "button[data-testid='year-selector']", # Test-ID - "div:has-text('Year')", # Englisch - "[aria-label*='Year']", # Aria-Label - "[aria-label*='Jahr']", # Deutsch - "div[role='combobox']:has-text('Jahr')", # Combobox - ".year-selector", # CSS-Klasse - ".date-picker-year", # CSS-Klasse - "//div[contains(text(), 'Jahr')]", # XPath - "//button[contains(text(), 'Jahr')]", # XPath Button - "//select[@name='year']" # XPath Select - ] - - year_dropdown = None - for i, selector in enumerate(year_selectors): - logger.debug(f"Versuche Jahr-Selektor {i+1}: {selector}") - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - year_dropdown = self.automation.browser.page.locator(selector).first - logger.info(f"Jahr-Dropdown gefunden mit Selektor {i+1}: {selector}") - break - except Exception as e: - logger.debug(f"Jahr-Selektor {i+1} fehlgeschlagen: {e}") - continue - - if not year_dropdown: - logger.error("Jahr-Dropdown nicht gefunden") - return False - - year_dropdown.click() - self.automation.human_behavior.random_delay(0.3, 0.8) - - # Jahr-Option auswählen - TikTok verwendet vierstellige Jahreszahlen - year_selected = False - year_option_selectors = [ - f"div.css-vz5m7n-DivOption:has-text('{birthday['year']}')", # Exakte TikTok-Klasse + Jahr - f"div.e1phcp2x5:has-text('{birthday['year']}')", # TikTok-Klasse alternative + Jahr - f"[role='option']:has-text('{birthday['year']}')", # Role + Jahr - f"div:has-text('{birthday['year']}')", # Einfach Jahr - f"//div[@role='option'][contains(text(), '{birthday['year']}')]", # XPath + Jahr - f"option[value='{birthday['year']}']", # Standard HTML (Fallback) - f"div[data-value='{birthday['year']}']", # Custom Dropdown (Fallback) - f"li[data-value='{birthday['year']}']", # List-Item (Fallback) - f"button:has-text('{birthday['year']}')", # Button (Fallback) - f"span:has-text('{birthday['year']}')" # Span (Fallback) - ] - - for i, option_selector in enumerate(year_option_selectors): - logger.debug(f"Versuche Jahr-Option-Selektor {i+1}: {option_selector}") - try: - if self.automation.browser.is_element_visible(option_selector, timeout=1000): - self.automation.browser.click_element(option_selector) - logger.info(f"Jahr {birthday['year']} ausgewählt mit Selektor {i+1}") - year_selected = True - break - except Exception as e: - logger.debug(f"Jahr-Option-Selektor {i+1} fehlgeschlagen: {e}") - continue - - if not year_selected: - logger.error(f"Konnte Jahr {birthday['year']} nicht auswählen") - return False - - logger.info(f"Geburtsdatum {birthday['month']}/{birthday['day']}/{birthday['year']} erfolgreich eingegeben") - return True - - except Exception as e: - logger.error(f"Fehler beim Eingeben des Geburtsdatums: {e}") - return False - - def _fill_registration_form(self, account_data: Dict[str, Any], registration_method: str) -> bool: - """ - Füllt das Registrierungsformular aus. - - Args: - account_data: Account-Daten für die Registrierung - registration_method: "email" oder "phone" - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Je nach Registrierungsmethode das entsprechende Feld ausfüllen - if registration_method == "email": - # E-Mail-Feld ausfüllen - email_success = self.automation.ui_helper.fill_field_fuzzy( - ["E-Mail-Adresse", "Email", "E-Mail"], - account_data["email"], - self.selectors.EMAIL_FIELD - ) - - if not email_success: - logger.error("Konnte E-Mail-Feld nicht ausfüllen") - return False - - logger.info(f"E-Mail-Feld ausgefüllt: {account_data['email']}") - - # NEUE REIHENFOLGE: Bei E-Mail sofort Code senden, dann Passwort - self.automation.human_behavior.random_delay(0.5, 1.0) - - logger.info("NEUE STRATEGIE: Code senden DIREKT nach E-Mail-Eingabe") - send_code_success = self._click_send_code_button_with_retry() - - if not send_code_success: - logger.error("Konnte 'Code senden'-Button nicht klicken") - return False - - logger.info("'Code senden'-Button erfolgreich geklickt - Code wird gesendet") - - # NEUE STRATEGIE: Code eingeben BEVOR Passwort (verhindert UI-Interferenz) - logger.info("OPTIMIERTE REIHENFOLGE: Warte auf E-Mail und gebe Code ein BEVOR Passwort") - - # Warten auf Verification Code und eingeben - verification_success = self._handle_verification_code_entry(account_data) - - if not verification_success: - logger.error("Konnte Verifizierungscode nicht eingeben") - return False - - logger.info("Verifizierungscode erfolgreich eingegeben") - - # Jetzt erst Passwort eingeben (nach Code-Verifikation) - self.automation.human_behavior.random_delay(1.0, 2.0) - - logger.info("Gebe jetzt Passwort ein (nach Code-Verifikation)") - - # NEUE STRATEGIE: Passwort genauso eingeben wie E-Mail (menschlich, Zeichen-für-Zeichen) - logger.info("Verwende menschliche Eingabe für Passwort (wie bei E-Mail)") - - # Primärer Ansatz: Fuzzy-Matching wie bei E-Mail-Eingabe - password_success = self.automation.ui_helper.fill_field_fuzzy( - ["Passwort", "Password"], - account_data["password"], - "input[type='password']" # Fallback-Selektor - ) - - # Validierung: Prüfe ob Passwort korrekt eingegeben wurde - if password_success: - self.automation.human_behavior.random_delay(0.5, 1.0) - - # Finde das Passwort-Feld zur Validierung - password_selectors = [ - "input[type='password'][placeholder='Passwort']", - "input[type='password']", - "input.css-wv3bkt-InputContainer", - "input[placeholder*='Passwort']", - "input[placeholder*='Password']" - ] - - validation_success = False - for selector in password_selectors: - if self.automation.browser.is_element_visible(selector, timeout=1000): - actual_value = self._get_input_field_value(selector) - if actual_value == account_data["password"]: - logger.info("Passwort erfolgreich eingegeben und validiert (menschliche Eingabe)") - validation_success = True - break - - if not validation_success: - logger.warning("Passwort-Validierung nach Fuzzy-Eingabe fehlgeschlagen") - password_success = False - - if not password_success: - logger.error("Konnte Passwort-Feld nicht ausfüllen") - return False - - logger.info("Passwort-Feld ausgefüllt (nach Code-Verifikation)") - return True - - elif registration_method == "phone": - # Telefonnummer-Feld ausfüllen (ohne Ländervorwahl) - phone_number = account_data["phone"] - if phone_number.startswith("+"): - # Entferne Ländervorwahl, wenn vorhanden - parts = phone_number.split(" ", 1) - if len(parts) > 1: - phone_number = parts[1] - - phone_success = self.automation.ui_helper.fill_field_fuzzy( - ["Telefonnummer", "Phone number", "Phone"], - phone_number, - self.selectors.PHONE_FIELD - ) - - if not phone_success: - logger.error("Konnte Telefonnummer-Feld nicht ausfüllen") - return False - - logger.info(f"Telefonnummer-Feld ausgefüllt: {phone_number}") - - self.automation.human_behavior.random_delay(0.5, 1.5) - - # Code senden Button klicken - mit disabled-State-Prüfung - send_code_success = self._click_send_code_button_with_retry() - - if not send_code_success: - logger.error("Konnte 'Code senden'-Button nicht klicken") - return False - - logger.info("'Code senden'-Button erfolgreich geklickt") - return True - - except Exception as e: - logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {e}") - return False - - def _handle_verification(self, account_data: Dict[str, Any], registration_method: str) -> bool: - """ - Behandelt den Verifizierungsprozess (E-Mail/SMS). - - Args: - account_data: Account-Daten mit E-Mail/Telefon - registration_method: "email" oder "phone" - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Warten, bis der Bestätigungscode gesendet wurde - self.automation.human_behavior.wait_for_page_load() - self.automation.human_behavior.random_delay(2.0, 4.0) - - # Verifizierungscode je nach Methode abrufen - if registration_method == "email": - # Verifizierungscode von E-Mail abrufen - verification_code = self._get_email_confirmation_code(account_data["email"]) - else: - # Verifizierungscode von SMS abrufen - verification_code = self._get_sms_confirmation_code(account_data["phone"]) - - if not verification_code: - logger.error("Konnte keinen Verifizierungscode abrufen") - return False - - logger.info(f"Verifizierungscode erhalten: {verification_code}") - - # Verifizierungscode-Feld ausfüllen - code_success = self.automation.ui_helper.fill_field_fuzzy( - ["Gib den sechsstelligen Code ein", "Enter verification code", "Verification code"], - verification_code, - self.selectors.VERIFICATION_CODE_FIELD - ) - - if not code_success: - logger.error("Konnte Verifizierungscode-Feld nicht ausfüllen") - return False - - self.automation.human_behavior.random_delay(1.0, 2.0) - - # Weiter-Button klicken - continue_success = self.automation.ui_helper.click_button_fuzzy( - ["Weiter", "Continue", "Next", "Submit"], - self.selectors.CONTINUE_BUTTON - ) - - if not continue_success: - logger.error("Konnte 'Weiter'-Button nicht klicken") - return False - - logger.info("Verifizierungscode eingegeben und 'Weiter' geklickt") - - # Warten nach der Verifizierung - self.automation.human_behavior.wait_for_page_load() - self.automation.human_behavior.random_delay(1.0, 2.0) - - return True - - except Exception as e: - logger.error(f"Fehler bei der Verifizierung: {e}") - return False - - def _get_email_confirmation_code(self, email: str) -> Optional[str]: - """ - Ruft den Bestätigungscode von einer E-Mail ab. - - Args: - email: E-Mail-Adresse, an die der Code gesendet wurde - - Returns: - Optional[str]: Der Bestätigungscode oder None, wenn nicht gefunden - """ - try: - # Warte auf die E-Mail - verification_code = self.automation.email_handler.get_verification_code( - target_email=email, # Verwende die vollständige E-Mail-Adresse - platform="tiktok", - max_attempts=60, # 60 Versuche * 2 Sekunden = 120 Sekunden - delay_seconds=2 - ) - - if verification_code: - return verification_code - - # Wenn kein Code gefunden wurde, prüfen, ob der Code vielleicht direkt angezeigt wird - verification_code = self._extract_code_from_page() - - if verification_code: - logger.info(f"Verifizierungscode direkt von der Seite extrahiert: {verification_code}") - return verification_code - - logger.warning(f"Konnte keinen Verifizierungscode für {email} finden") - return None - - except Exception as e: - logger.error(f"Fehler beim Abrufen des E-Mail-Bestätigungscodes: {e}") - return None - - def _get_sms_confirmation_code(self, phone: str) -> Optional[str]: - """ - Ruft den Bestätigungscode aus einer SMS ab. - Hier müsste ein SMS-Empfangs-Service eingebunden werden. - - Args: - phone: Telefonnummer, an die der Code gesendet wurde - - Returns: - Optional[str]: Der Bestätigungscode oder None, wenn nicht gefunden - """ - # Diese Implementierung ist ein Platzhalter - # In einer echten Implementierung würde hier ein SMS-Empfangs-Service verwendet - logger.warning("SMS-Verifizierung ist noch nicht implementiert") - - # Versuche, den Code trotzdem zu extrahieren, falls er auf der Seite angezeigt wird - return self._extract_code_from_page() - - def _extract_code_from_page(self) -> Optional[str]: - """ - Versucht, einen Bestätigungscode direkt von der Seite zu extrahieren. - - Returns: - Optional[str]: Der extrahierte Code oder None, wenn nicht gefunden - """ - try: - # Gesamten Seiteninhalt abrufen - page_content = self.automation.browser.page.content() - - # Mögliche Regex-Muster für Bestätigungscodes - patterns = [ - r"Dein Code ist (\d{6})", - r"Your code is (\d{6})", - r"Bestätigungscode: (\d{6})", - r"Confirmation code: (\d{6})", - r"(\d{6}) ist dein TikTok-Code", - r"(\d{6}) is your TikTok code" - ] - - for pattern in patterns: - match = re.search(pattern, page_content) - if match: - return match.group(1) - - return None - - except Exception as e: - logger.error(f"Fehler beim Extrahieren des Codes von der Seite: {e}") - return None - - def _create_username(self, account_data: Dict[str, Any]) -> bool: - """ - Erstellt einen Benutzernamen. - - Args: - account_data: Account-Daten - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Warten, bis die Benutzernamen-Seite geladen ist - self.automation.human_behavior.wait_for_page_load() - - # Prüfen, ob wir auf der Benutzernamen-Seite sind - if not self.automation.browser.is_element_visible(self.selectors.USERNAME_FIELD, timeout=5000): - logger.warning("Benutzernamen-Feld nicht gefunden, möglicherweise ist dieser Schritt übersprungen worden") - - # Versuche, den "Überspringen"-Button zu klicken, falls vorhanden - skip_visible = self.automation.browser.is_element_visible(self.selectors.SKIP_USERNAME_BUTTON, timeout=2000) - if skip_visible: - self.automation.browser.click_element(self.selectors.SKIP_USERNAME_BUTTON) - logger.info("Benutzernamen-Schritt übersprungen") - return True - - # Möglicherweise wurde der Benutzername automatisch erstellt - logger.info("Benutzernamen-Schritt möglicherweise automatisch abgeschlossen") - return True - - # Benutzernamen eingeben - username_success = self.automation.ui_helper.fill_field_fuzzy( - ["Benutzername", "Username"], - account_data["username"], - self.selectors.USERNAME_FIELD - ) - - if not username_success: - logger.error("Konnte Benutzernamen-Feld nicht ausfüllen") - return False - - logger.info(f"Benutzernamen-Feld ausgefüllt: {account_data['username']}") - - self.automation.human_behavior.random_delay(1.0, 2.0) - - # Registrieren-Button klicken - register_success = self.automation.ui_helper.click_button_fuzzy( - ["Registrieren", "Register", "Sign up", "Submit"], - self.selectors.REGISTER_BUTTON - ) - - if not register_success: - logger.error("Konnte 'Registrieren'-Button nicht klicken") - return False - - logger.info("'Registrieren'-Button erfolgreich geklickt") - - # Warten nach der Registrierung - self.automation.human_behavior.wait_for_page_load() - - return True - - except Exception as e: - logger.error(f"Fehler beim Erstellen des Benutzernamens: {e}") - return False - - def _click_send_code_button_with_retry(self) -> bool: - """ - Klickt den 'Code senden'-Button mit Prüfung auf disabled-State und Countdown. - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - import re - import time - - max_wait_time = 70 # Maximal 70 Sekunden warten (60s Countdown + Puffer) - check_interval = 2 # Alle 2 Sekunden prüfen - start_time = time.time() - - logger.info("Prüfe 'Code senden'-Button Status...") - - while time.time() - start_time < max_wait_time: - # Button-Element finden - button_element = self.automation.browser.wait_for_selector( - self.selectors.SEND_CODE_BUTTON, timeout=3000 - ) - - if not button_element: - logger.warning("'Code senden'-Button nicht gefunden") - time.sleep(check_interval) - continue - - # Disabled-Attribut prüfen - is_disabled = button_element.get_attribute("disabled") - button_text = button_element.inner_text() or "" - - logger.debug(f"Button Status: disabled={is_disabled}, text='{button_text}'") - - # Wenn Button nicht disabled ist, versuche zu klicken - if not is_disabled: - if "Code senden" in button_text and "erneut" not in button_text: - logger.info("Button ist bereit zum Klicken") - - # Mehrere Klick-Strategien versuchen - click_success = False - - # 1. Direkter Klick auf das gefundene Element - try: - logger.info("Versuche direkten Klick auf Button-Element") - button_element.click() - click_success = True - logger.info("Direkter Klick erfolgreich") - except Exception as e: - logger.warning(f"Direkter Klick fehlgeschlagen: {e}") - - # 2. Fallback: Fuzzy-Matching Klick - if not click_success: - logger.info("Versuche Fuzzy-Matching Klick") - click_success = self.automation.ui_helper.click_button_fuzzy( - ["Code senden", "Send code", "Send verification code"], - self.selectors.SEND_CODE_BUTTON - ) - if click_success: - logger.info("Fuzzy-Matching Klick erfolgreich") - - # 3. Fallback: React-kompatibler Event-Dispatch - if not click_success: - try: - logger.info("Versuche React-kompatiblen Event-Dispatch") - click_success = self._dispatch_react_click_events(button_element) - if click_success: - logger.info("React-Event-Dispatch erfolgreich") - except Exception as e: - logger.warning(f"React-Event-Dispatch fehlgeschlagen: {e}") - - # 4. Fallback: Einfacher JavaScript-Klick - if not click_success: - try: - logger.info("Versuche einfachen JavaScript-Klick") - button_element.evaluate("element => element.click()") - click_success = True - logger.info("JavaScript-Klick erfolgreich") - except Exception as e: - logger.warning(f"JavaScript-Klick fehlgeschlagen: {e}") - - # 5. Klick-Erfolg validieren - if click_success: - # Umfassende Erfolgsvalidierung - validation_success = self._validate_send_code_success() - if validation_success: - logger.info("'Code senden'-Button erfolgreich geklickt (validiert)") - return True - else: - logger.error("Klick scheinbar erfolglos - keine Reaktion erkannt") - click_success = False - - if not click_success: - logger.warning("Alle Klick-Strategien fehlgeschlagen, versuche erneut...") - else: - logger.debug(f"Button-Text nicht bereit: '{button_text}'") - - # Wenn Button disabled ist, Countdown extrahieren - elif "erneut senden" in button_text.lower(): - countdown_match = re.search(r'(\d+)s', button_text) - if countdown_match: - countdown = int(countdown_match.group(1)) - logger.info(f"Button ist disabled, warte {countdown} Sekunden...") - - # Effizienter warten - nicht länger als nötig - if countdown > 5: - time.sleep(countdown - 3) # 3 Sekunden vor Ende wieder prüfen - else: - time.sleep(check_interval) - else: - logger.info("Button ist disabled, warte...") - time.sleep(check_interval) - else: - logger.info("Button ist disabled ohne Countdown-Info, warte...") - time.sleep(check_interval) - - logger.error(f"Timeout nach {max_wait_time} Sekunden - Button konnte nicht geklickt werden") - return False - - except Exception as e: - logger.error(f"Fehler beim Klicken des 'Code senden'-Buttons: {e}") - return False - - def _dispatch_react_click_events(self, element) -> bool: - """ - Dispatcht React-kompatible Events für moderne Web-Interfaces. - - Args: - element: Das Button-Element auf das geklickt werden soll - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Erweiterte JavaScript-Funktion für TikTok React-Interface - react_click_script = """ - (element) => { - console.log('Starting React click dispatch for TikTok button'); - - // 1. Element-Informationen sammeln - console.log('Button element:', element); - console.log('Button tagName:', element.tagName); - console.log('Button type:', element.type); - console.log('Button disabled:', element.disabled); - console.log('Button innerHTML:', element.innerHTML); - console.log('Button classList:', Array.from(element.classList)); - - // 2. React Fiber-Node finden (TikTok verwendet React Fiber) - let reactFiber = null; - const fiberKeys = Object.keys(element).filter(key => - key.startsWith('__reactFiber') || - key.startsWith('__reactInternalInstance') || - key.startsWith('__reactEventHandlers') - ); - - console.log('Found fiber keys:', fiberKeys); - - if (fiberKeys.length > 0) { - reactFiber = element[fiberKeys[0]]; - console.log('React fiber found:', reactFiber); - } - - // 3. Alle Event Listener finden - const listeners = getEventListeners ? getEventListeners(element) : {}; - console.log('Event listeners:', listeners); - - // 4. React Event Handler suchen - let clickHandler = null; - if (reactFiber) { - // Fiber-Baum durchsuchen - let currentFiber = reactFiber; - while (currentFiber && !clickHandler) { - if (currentFiber.memoizedProps && currentFiber.memoizedProps.onClick) { - clickHandler = currentFiber.memoizedProps.onClick; - console.log('Found onClick handler in fiber props'); - break; - } - if (currentFiber.pendingProps && currentFiber.pendingProps.onClick) { - clickHandler = currentFiber.pendingProps.onClick; - console.log('Found onClick handler in pending props'); - break; - } - currentFiber = currentFiber.return || currentFiber.child; - } - } - - // 5. Backup: Element-Properties durchsuchen - if (!clickHandler) { - const propKeys = Object.getOwnPropertyNames(element); - for (const key of propKeys) { - if (key.includes('click') || key.includes('Click')) { - const prop = element[key]; - if (typeof prop === 'function') { - clickHandler = prop; - console.log('Found click handler in element properties:', key); - break; - } - } - } - } - - try { - // 6. React Synthetic Event erstellen - const syntheticEvent = { - type: 'click', - target: element, - currentTarget: element, - bubbles: true, - cancelable: true, - preventDefault: function() { this.defaultPrevented = true; }, - stopPropagation: function() { this.propagationStopped = true; }, - nativeEvent: new MouseEvent('click', { bubbles: true, cancelable: true }), - timeStamp: Date.now(), - isTrusted: false - }; - - // 7. Handler direkt aufrufen, falls gefunden - if (clickHandler) { - console.log('Calling React click handler directly'); - clickHandler(syntheticEvent); - return true; - } - - // 8. Fallback: Umfassende Event-Sequenz - console.log('Using fallback event sequence'); - - // Element fokussieren - element.focus(); - - // Realistische Koordinaten berechnen - const rect = element.getBoundingClientRect(); - const centerX = rect.left + rect.width / 2; - const centerY = rect.top + rect.height / 2; - - const eventOptions = { - view: window, - bubbles: true, - cancelable: true, - clientX: centerX, - clientY: centerY, - button: 0, - buttons: 1, - detail: 1 - }; - - // Vollständige Event-Sequenz - const events = [ - new MouseEvent('mouseenter', eventOptions), - new MouseEvent('mouseover', eventOptions), - new MouseEvent('mousedown', eventOptions), - new FocusEvent('focus', { bubbles: true }), - new MouseEvent('mouseup', eventOptions), - new MouseEvent('click', eventOptions), - new Event('input', { bubbles: true }), - new Event('change', { bubbles: true }) - ]; - - for (const event of events) { - element.dispatchEvent(event); - } - - // 9. Form-Submit als letzter Ausweg - const form = element.closest('form'); - if (form && element.type === 'submit') { - console.log('Triggering form submit as last resort'); - form.dispatchEvent(new Event('submit', { bubbles: true })); - } - - return true; - - } catch (error) { - console.error('React click dispatch failed:', error); - return false; - } - } - """ - - # Event-Dispatch ausführen - result = element.evaluate(react_click_script) - - if result: - logger.info("Erweiterte React-Events erfolgreich dispatcht") - return True - else: - logger.warning("React-Event-Dispatch meldet Fehler") - return False - - except Exception as e: - logger.error(f"Fehler beim React-Event-Dispatch: {e}") - return False - - def _wait_for_password_validation(self) -> None: - """ - Wartet, bis TikToks Passwort-Validierung abgeschlossen ist und UI stabil wird. - Das verhindert Interferenzen mit dem 'Code senden'-Button. - """ - try: - import time - - # Häufige Passwort-Validation-Indikatoren bei TikTok - validation_indicators = [ - # Deutsche Texte - "8-20 zeichen", - "sonderzeichen", - "buchstaben und zahlen", - "mindestens 8 zeichen", - "großbuchstaben", - "kleinbuchstaben", - - # Englische Texte - "8-20 characters", - "special characters", - "letters and numbers", - "at least 8 characters", - "uppercase", - "lowercase", - "password requirements", - "password strength" - ] - - # CSS-Selektoren für Validierungsmeldungen - validation_selectors = [ - "div[class*='password']", - "div[class*='validation']", - "div[class*='requirement']", - "div[class*='error']", - "div[class*='hint']", - ".password-hint", - ".validation-message", - "[data-e2e*='password']" - ] - - logger.info("Prüfe auf Passwort-Validierungsmeldungen...") - - max_wait_time = 8 # Maximal 8 Sekunden warten - check_interval = 0.5 # Alle 500ms prüfen - start_time = time.time() - - validation_found = False - validation_disappeared = False - - while time.time() - start_time < max_wait_time: - # 1. Prüfung: Sind Validierungsmeldungen sichtbar? - current_validation = False - - # Text-basierte Suche - try: - page_content = self.automation.browser.page.content().lower() - for indicator in validation_indicators: - if indicator in page_content: - current_validation = True - validation_found = True - logger.debug(f"Passwort-Validierung aktiv: '{indicator}'") - break - except: - pass - - # Element-basierte Suche - if not current_validation: - for selector in validation_selectors: - try: - if self.automation.browser.is_element_visible(selector, timeout=500): - element = self.automation.browser.wait_for_selector(selector, timeout=500) - if element: - element_text = element.inner_text() or "" - if any(indicator in element_text.lower() for indicator in validation_indicators): - current_validation = True - validation_found = True - logger.debug(f"Passwort-Validierung in Element: '{element_text[:50]}'") - break - except: - continue - - # 2. Zustandsüberwachung - if validation_found and not current_validation: - # Validierung war da, ist jetzt weg - validation_disappeared = True - logger.info("Passwort-Validierung verschwunden - UI sollte stabil sein") - break - elif current_validation: - logger.debug("Passwort-Validierung noch aktiv, warte...") - - time.sleep(check_interval) - - # Zusätzliche Stabilisierungszeit nach Validierung - if validation_found: - if validation_disappeared: - logger.info("Extra-Wartezeit für UI-Stabilisierung nach Passwort-Validierung") - time.sleep(2) # 2 Sekunden extra für Stabilität - else: - logger.warning("Passwort-Validierung immer noch aktiv - fahre trotzdem fort") - time.sleep(1) # Kurze Wartezeit - else: - logger.debug("Keine Passwort-Validierungsmeldungen erkannt") - time.sleep(1) # Standard-Wartezeit für UI-Stabilität - - except Exception as e: - logger.warning(f"Fehler bei Passwort-Validierung-Überwachung: {e}") - # Fallback: Einfach 2 Sekunden warten - time.sleep(2) - - def _handle_verification_code_entry(self, account_data: dict) -> bool: - """ - Wartet auf E-Mail mit Verification Code und gibt ihn ein. - Optimiert für störungsfreie Eingabe vor Passwort-Validierung. - - Args: - account_data: Account-Daten mit E-Mail-Adresse - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - import time - - email_address = account_data.get("email") - if not email_address: - logger.error("Keine E-Mail-Adresse für Code-Abruf verfügbar") - return False - - logger.info(f"Warte auf Verifizierungscode für {email_address}") - - # Warten auf das Erscheinen des Verification-Feldes - logger.info("Warte auf Verifizierungsfeld...") - verification_field_appeared = False - max_field_wait = 10 # 10 Sekunden warten auf Feld - - for attempt in range(max_field_wait): - verification_selectors = [ - # Exakter Selektor basierend auf echtem TikTok HTML - "input[type='text'][placeholder='Gib den sechsstelligen Code ein']", - "input.css-11to27l-InputContainer", - "input.etcs7ny1", - # Fallback-Selektoren - "input[placeholder*='sechsstelligen Code']", - "input[placeholder*='Code']", - "input[placeholder*='code']", - "input[data-e2e='verification-code-input']", - "input[name*='verif']", - "input[name*='code']" - ] - - for selector in verification_selectors: - if self.automation.browser.is_element_visible(selector, timeout=1000): - logger.info(f"Verifizierungsfeld erschienen: {selector}") - verification_field_appeared = True - break - - if verification_field_appeared: - break - - time.sleep(1) - - if not verification_field_appeared: - logger.error("Verifizierungsfeld ist nicht erschienen nach Code senden") - return False - - # E-Mail-Handler: Warte auf Verifizierungscode - logger.info("Rufe E-Mail ab und extrahiere Verifizierungscode...") - verification_code = None - max_email_attempts = 12 # 12 Versuche über 2 Minuten - - for attempt in range(max_email_attempts): - logger.debug(f"E-Mail-Abruf Versuch {attempt + 1}/{max_email_attempts}") - - try: - verification_code = self.automation.email_handler.get_verification_code( - target_email=email_address, - platform="tiktok", - max_attempts=3, # Kurze Versuche pro E-Mail-Abruf - delay_seconds=2 - ) - - if verification_code: - logger.info(f"Verifizierungscode erhalten: {verification_code}") - break - - except Exception as e: - logger.warning(f"Fehler beim E-Mail-Abruf (Versuch {attempt + 1}): {e}") - - # Kurz warten zwischen Versuchen - time.sleep(10) # 10 Sekunden zwischen E-Mail-Abruf-Versuchen - - if not verification_code: - logger.error("Kein Verifizierungscode verfügbar") - return False - - # Code in das Feld eingeben (verschiedene Strategien) - logger.info("Gebe Verifizierungscode ein...") - - code_entered = False - - # 1. Debug: Alle Input-Felder auf der Seite finden - try: - all_inputs = self.automation.browser.page.query_selector_all("input") - logger.info(f"Debug: Gefundene Input-Felder auf der Seite: {len(all_inputs)}") - - for i, input_elem in enumerate(all_inputs): - placeholder = input_elem.get_attribute("placeholder") or "" - input_type = input_elem.get_attribute("type") or "" - classes = input_elem.get_attribute("class") or "" - logger.debug(f"Input {i+1}: type='{input_type}', placeholder='{placeholder}', class='{classes[:50]}...'") - except Exception as e: - logger.debug(f"Debug-Info fehlgeschlagen: {e}") - - # 2. Direkte Eingabe über Selektoren mit React-kompatiblem Input - for i, selector in enumerate(verification_selectors): - logger.debug(f"Teste Selektor {i+1}/{len(verification_selectors)}: {selector}") - - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - logger.info(f"✅ Selektor gefunden: {selector}") - # React-kompatible Input-Eingabe - success = self._fill_input_field_react_compatible(selector, verification_code) - if success: - logger.info(f"Code erfolgreich eingegeben über: {selector}") - code_entered = True - break - else: - logger.warning(f"Code-Eingabe fehlgeschlagen für: {selector}") - else: - logger.debug(f"❌ Selektor nicht gefunden: {selector}") - except Exception as e: - logger.debug(f"❌ Selektor-Fehler {selector}: {e}") - - # 2. Fallback: Fuzzy Matching - if not code_entered: - code_entered = self.automation.ui_helper.fill_field_fuzzy( - ["Code", "Bestätigungscode", "Verification", "Verifikation"], - verification_code - ) - if code_entered: - logger.info("Code erfolgreich eingegeben über Fuzzy-Matching") - - if not code_entered: - logger.error("Konnte Verifizierungscode nicht eingeben") - return False - - # Kurz warten nach Code-Eingabe - self.automation.human_behavior.random_delay(1.0, 2.0) - - # Optional: "Weiter" oder "Submit" Button klicken (falls nötig) - self._try_submit_verification_code() - - logger.info("Verifizierungscode-Eingabe abgeschlossen") - return True - - except Exception as e: - logger.error(f"Fehler bei Verifizierungscode-Behandlung: {e}") - return False - - def _debug_form_state_after_password(self) -> None: - """ - Debug-Funktion: Analysiert den Formular-Zustand nach Passwort-Eingabe - um herauszufinden, warum der Weiter-Button disabled ist. - """ - try: - logger.info("=== DEBUG: Formular-Zustand nach Passwort-Eingabe ===") - - # 1. Prüfe alle Weiter-Buttons und deren Status - weiter_selectors = [ - "button:has-text('Weiter')", - "button:has-text('Continue')", - "button:has-text('Next')", - "button[type='submit']", - "button.TUXButton" - ] - - for selector in weiter_selectors: - try: - if self.automation.browser.is_element_visible(selector, timeout=1000): - element = self.automation.browser.page.locator(selector).first - is_disabled = element.is_disabled() - text = element.text_content() - logger.info(f"Button gefunden: '{text}' - Disabled: {is_disabled} - Selektor: {selector}") - except Exception as e: - logger.debug(f"Button-Check fehlgeschlagen für {selector}: {e}") - - # 2. Prüfe auf Terms & Conditions Checkbox - checkbox_selectors = [ - "input[type='checkbox']", - "input.css-1pewyex-InputCheckbox", - "label:has-text('Nutzungsbedingungen')", - "label:has-text('Terms')", - "label:has-text('Ich stimme')", - "label:has-text('I agree')" - ] - - logger.info("Prüfe auf ungesetzte Checkboxen...") - for selector in checkbox_selectors: - try: - if self.automation.browser.is_element_visible(selector, timeout=1000): - element = self.automation.browser.page.locator(selector).first - if selector.startswith("input"): - is_checked = element.is_checked() - logger.info(f"Checkbox gefunden - Checked: {is_checked} - Selektor: {selector}") - if not is_checked: - logger.warning(f"UNCHECKED CHECKBOX GEFUNDEN: {selector}") - else: - text = element.text_content() - logger.info(f"Checkbox-Label gefunden: '{text}' - Selektor: {selector}") - except Exception as e: - logger.debug(f"Checkbox-Check fehlgeschlagen für {selector}: {e}") - - # 3. Prüfe auf Passwort-Validierungsfehler - error_selectors = [ - ".error-message", - ".form-error", - ".css-error", - "div[class*='error']", - "span[class*='error']", - "div[style*='color: red']", - "span[style*='color: red']" - ] - - logger.info("Prüfe auf Passwort-Validierungsfehler...") - for selector in error_selectors: - try: - if self.automation.browser.is_element_visible(selector, timeout=1000): - element = self.automation.browser.page.locator(selector).first - text = element.text_content() - if text and len(text.strip()) > 0: - logger.warning(f"VALIDIERUNGSFEHLER GEFUNDEN: '{text}' - Selektor: {selector}") - except Exception as e: - logger.debug(f"Error-Check fehlgeschlagen für {selector}: {e}") - - # 4. Prüfe alle Input-Felder und deren Werte - logger.info("Prüfe alle Input-Felder...") - try: - inputs = self.automation.browser.page.locator("input").all() - for i, input_element in enumerate(inputs): - input_type = input_element.get_attribute("type") or "text" - placeholder = input_element.get_attribute("placeholder") or "" - value = input_element.input_value() if input_type != "checkbox" else str(input_element.is_checked()) - name = input_element.get_attribute("name") or "" - - logger.info(f"Input {i+1}: type='{input_type}', placeholder='{placeholder}', value='{value}', name='{name}'") - - # Warne bei leeren required Feldern - if input_type in ["text", "email", "password"] and not value and placeholder: - logger.warning(f"LEERES FELD GEFUNDEN: {placeholder}") - except Exception as e: - logger.debug(f"Input-Field-Check fehlgeschlagen: {e}") - - logger.info("=== DEBUG: Formular-Zustand Ende ===") - - except Exception as e: - logger.error(f"Debug-Funktion fehlgeschlagen: {e}") - - def _get_input_field_value(self, selector: str) -> str: - """ - Liest den aktuellen Wert eines Input-Feldes aus. - - Args: - selector: CSS-Selektor für das Input-Feld - - Returns: - str: Der aktuelle Wert des Feldes oder leerer String bei Fehler - """ - try: - element = self.automation.browser.page.locator(selector).first - if element.is_visible(): - return element.input_value() - return "" - except Exception as e: - logger.debug(f"Fehler beim Lesen des Input-Werts für {selector}: {e}") - return "" - - def _try_submit_verification_code(self) -> bool: - """ - Versucht, den Verifizierungscode zu bestätigen/submitten falls nötig. - - Returns: - bool: True wenn Submit gefunden und geklickt, False wenn nicht nötig - """ - try: - submit_selectors = [ - "button[type='submit']", - "button:has-text('Weiter')", - "button:has-text('Continue')", - "button:has-text('Bestätigen')", - "button:has-text('Verify')", - "button[data-e2e='next-button']" - ] - - for selector in submit_selectors: - if self.automation.browser.is_element_visible(selector, timeout=2000): - if self.automation.browser.click_element(selector): - logger.info(f"Verification Submit-Button geklickt: {selector}") - return True - - logger.debug("Kein Submit-Button für Verification gefunden - wahrscheinlich nicht nötig") - return False - - except Exception as e: - logger.debug(f"Fehler beim Submit-Versuch: {e}") - return False - - def _fill_input_field_react_compatible(self, selector: str, value: str) -> bool: - """ - Füllt ein Input-Feld mit React-kompatiblen Events. - Speziell für moderne TikTok-Interface optimiert. - - Args: - selector: CSS-Selektor für das Input-Feld - value: Wert der eingegeben werden soll - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Element finden - element = self.automation.browser.wait_for_selector(selector, timeout=2000) - if not element: - return False - - logger.debug(f"Verwende React-kompatible Input-Eingabe für: {selector}") - - # React-kompatible Input-Eingabe mit JavaScript - # SICHERE Übergabe des Wertes als Parameter (nicht String-Interpolation) - react_input_script = """ - (element, value) => { - console.log('React input field injection for TikTok verification'); - console.log('Element:', element); - console.log('Value to input:', value); - - try { - // 1. Element fokussieren - element.focus(); - - // 2. Aktuellen Wert löschen - element.value = ''; - - // 3. Input Events für React - const inputEvent = new Event('input', { bubbles: true, cancelable: true }); - const changeEvent = new Event('change', { bubbles: true, cancelable: true }); - - // 4. Wert setzen und Events feuern - element.value = value; - - // 5. React Synthetic Events - element.dispatchEvent(inputEvent); - element.dispatchEvent(changeEvent); - - // 6. Zusätzliche Events für Robustheit - const focusEvent = new FocusEvent('focus', { bubbles: true }); - const blurEvent = new FocusEvent('blur', { bubbles: true }); - - element.dispatchEvent(focusEvent); - element.dispatchEvent(blurEvent); - - // 7. Keystroke-Simulation für jeden Charakter - for (let i = 0; i < value.length; i++) { - const char = value[i]; - const keydownEvent = new KeyboardEvent('keydown', { - key: char, - code: 'Digit' + char, - keyCode: char.charCodeAt(0), - bubbles: true - }); - - const keyupEvent = new KeyboardEvent('keyup', { - key: char, - code: 'Digit' + char, - keyCode: char.charCodeAt(0), - bubbles: true - }); - - element.dispatchEvent(keydownEvent); - element.dispatchEvent(keyupEvent); - } - - // 8. Finaler Input-Event - element.dispatchEvent(new Event('input', { bubbles: true })); - - console.log('React input injection completed'); - console.log('Final element value:', element.value); - - return element.value === value; - - } catch (error) { - console.error('React input injection failed:', error); - return false; - } - } - """ - - # Script ausführen mit Wert als Parameter - result = element.evaluate(react_input_script, value) - - if result: - # Kurze Pause nach Input - import time - time.sleep(0.5) - - # Validierung: Prüfen ob Wert wirklich gesetzt wurde - current_value = element.input_value() - if current_value == value: - logger.info(f"React-Input erfolgreich: '{current_value}'") - return True - else: - logger.warning(f"React-Input unvollständig: '{current_value}' != '{value}'") - return False - else: - logger.warning("React-Input-Script meldet Fehler") - return False - - except Exception as e: - logger.error(f"Fehler bei React-kompatible Input-Eingabe: {e}") - return False - - def _validate_send_code_success(self) -> bool: - """ - Umfassende Validierung, ob der 'Code senden'-Button erfolgreich geklickt wurde. - - Returns: - bool: True wenn erfolgreich, False sonst - """ - try: - import time - - logger.info("Führe umfassende Erfolgsvalidierung durch...") - - # Zuerst kurz warten - time.sleep(1) - - # Vor-Validierung: Button-Status vor der Hauptprüfung - original_button = self.automation.browser.wait_for_selector( - self.selectors.SEND_CODE_BUTTON, timeout=2000 - ) - if original_button: - pre_text = original_button.inner_text() or "" - pre_disabled = original_button.get_attribute("disabled") - logger.debug(f"Pre-validation - Button Text: '{pre_text}', Disabled: {pre_disabled}") - - # Hauptwartung für Reaktion - time.sleep(3) - - # 1. STRENGE Prüfung: Verifizierungsfeld erschienen UND ist editierbar - verification_field_selectors = [ - "input[placeholder*='sechsstelligen Code']", - "input[placeholder*='Code']", - "input[placeholder*='code']", - "input[data-e2e='verification-code-input']", - "input[name*='verif']", - "input[name*='code']" - ] - - verification_field_found = False - for selector in verification_field_selectors: - if self.automation.browser.is_element_visible(selector, timeout=2000): - # Zusätzliche Prüfung: Ist das Feld auch wirklich interaktiv? - field_element = self.automation.browser.wait_for_selector(selector, timeout=1000) - if field_element: - is_disabled = field_element.get_attribute("disabled") - is_readonly = field_element.get_attribute("readonly") - if not is_disabled and not is_readonly: - logger.info(f"VALIDES Verifizierungsfeld erschienen: {selector}") - verification_field_found = True - break - else: - logger.debug(f"Feld gefunden aber nicht editierbar: {selector}") - - if verification_field_found: - return True - - # 2. STRENGE Prüfung: Button-Text MUSS sich geändert haben - try: - updated_element = self.automation.browser.wait_for_selector( - self.selectors.SEND_CODE_BUTTON, timeout=2000 - ) - if updated_element: - updated_text = updated_element.inner_text() or "" - logger.debug(f"Aktueller Button-Text: '{updated_text}'") - - # Text MUSS sich geändert haben von "Code senden" - if updated_text != "Code senden": - # Countdown-Indikatoren (sehr spezifisch) - countdown_indicators = [ - "erneut senden", "code erneut senden", "wieder senden", - "resend", "send again", ":" - ] - - # Prüfung auf Countdown-Format (z.B. "55s", "1:23") - import re - if re.search(r'\d+s|\d+:\d+|\d+\s*sec', updated_text.lower()): - logger.info(f"Button zeigt COUNTDOWN: '{updated_text}' - ECHTER Klick bestätigt") - return True - - for indicator in countdown_indicators: - if indicator in updated_text.lower(): - logger.info(f"Button zeigt ERNEUT-Status: '{updated_text}' - ECHTER Klick bestätigt") - return True - else: - logger.warning(f"Button-Text unverändert: '{updated_text}' - Klick war NICHT erfolgreich") - except Exception as e: - logger.debug(f"Button-Text-Prüfung fehlgeschlagen: {e}") - - # 3. Prüfung: Disabled-Status des Buttons - try: - button_element = self.automation.browser.wait_for_selector( - self.selectors.SEND_CODE_BUTTON, timeout=2000 - ) - if button_element: - is_disabled = button_element.get_attribute("disabled") - if is_disabled: - logger.info("Button ist jetzt disabled - Code wurde gesendet") - return True - except Exception as e: - logger.debug(f"Button-Disabled-Prüfung fehlgeschlagen: {e}") - - # 4. Prüfung: Neue Textinhalte auf der Seite - try: - page_content = self.automation.browser.page.content().lower() - success_indicators = [ - "code gesendet", "code sent", "verification sent", - "email gesendet", "email sent", "check your email", - "prüfe deine", "überprüfe deine" - ] - - for indicator in success_indicators: - if indicator in page_content: - logger.info(f"Erfolgsindikator im Seiteninhalt gefunden: '{indicator}'") - return True - except Exception as e: - logger.debug(f"Seiteninhalt-Prüfung fehlgeschlagen: {e}") - - # 5. Prüfung: Neue Elemente oder Dialoge - try: - new_element_selectors = [ - "div[role='alert']", - "div[class*='notification']", - "div[class*='message']", - "div[class*='success']", - ".toast", ".alert", ".notification" - ] - - for selector in new_element_selectors: - if self.automation.browser.is_element_visible(selector, timeout=1000): - element = self.automation.browser.wait_for_selector(selector, timeout=1000) - if element: - element_text = element.inner_text() or "" - if any(word in element_text.lower() for word in ["code", "sent", "gesendet", "email"]): - logger.info(f"Erfolgs-Element gefunden: '{element_text}'") - return True - except Exception as e: - logger.debug(f"Neue-Elemente-Prüfung fehlgeschlagen: {e}") - - # 6. Screenshot für Debugging erstellen - self.automation._take_screenshot("validation_failed") - - # 7. Finale Button-Status-Ausgabe - try: - final_button = self.automation.browser.wait_for_selector( - self.selectors.SEND_CODE_BUTTON, timeout=1000 - ) - if final_button: - final_text = final_button.inner_text() or "" - final_disabled = final_button.get_attribute("disabled") - logger.error(f"VALIDATION FAILED - Finaler Button-Status: Text='{final_text}', Disabled={final_disabled}") - except: - pass - - logger.error("VALIDATION FAILED: 'Code senden'-Button wurde NICHT erfolgreich geklickt") - return False - - except Exception as e: - logger.error(f"Fehler bei der Erfolgsvalidierung: {e}") - return False - - def _check_registration_success(self) -> bool: - """ - Überprüft, ob die Registrierung erfolgreich war. - - Returns: - bool: True wenn erfolgreich, False sonst - """ - try: - # Warten nach der Registrierung - self.automation.human_behavior.wait_for_page_load(multiplier=2.0) - - # Screenshot erstellen - self.automation._take_screenshot("registration_final") - - # Erfolg anhand verschiedener Indikatoren prüfen - success_indicators = self.selectors.SUCCESS_INDICATORS - - for indicator in success_indicators: - if self.automation.browser.is_element_visible(indicator, timeout=3000): - logger.info(f"Erfolgsindikator gefunden: {indicator}") - return True - - # Alternativ prüfen, ob wir auf der TikTok-Startseite sind - current_url = self.automation.browser.page.url - if "tiktok.com" in current_url and "/signup" not in current_url and "/login" not in current_url: - logger.info(f"Erfolg basierend auf URL: {current_url}") - return True - - logger.warning("Keine Erfolgsindikatoren gefunden") - return False - - except Exception as e: - logger.error(f"Fehler beim Überprüfen des Registrierungserfolgs: {e}") - return False \ No newline at end of file diff --git a/social_networks/tiktok/tiktok_registration_clean.py b/social_networks/tiktok/tiktok_registration_clean.py deleted file mode 100644 index 81a231f..0000000 --- a/social_networks/tiktok/tiktok_registration_clean.py +++ /dev/null @@ -1,672 +0,0 @@ -# social_networks/tiktok/tiktok_registration_clean.py - -""" -TikTok Registration Module - Clean Architecture Implementation -Handles the complete TikTok account registration workflow. -""" - -import time -from typing import Dict, Any, Optional, List -from dataclasses import dataclass -from enum import Enum - -from .tiktok_selectors import TikTokSelectors -from .tiktok_workflow import TikTokWorkflow -from utils.logger import setup_logger - -logger = setup_logger("tiktok_registration") - - -class RegistrationStage(Enum): - """Enumeration of registration workflow stages.""" - NAVIGATION = "navigation" - COOKIE_CONSENT = "cookie_consent" - LOGIN_CLICK = "login_click" - REGISTER_CLICK = "register_click" - PHONE_EMAIL_SELECTION = "phone_email_selection" - METHOD_SELECTION = "method_selection" - BIRTHDAY_ENTRY = "birthday_entry" - EMAIL_ENTRY = "email_entry" - PASSWORD_ENTRY = "password_entry" - CODE_SENDING = "code_sending" - CODE_VERIFICATION = "code_verification" - USERNAME_ENTRY = "username_entry" - COMPLETION = "completion" - - -@dataclass -class RegistrationConfig: - """Configuration for registration process.""" - max_retries: int = 3 - retry_delay: float = 2.0 - timeout: int = 5000 - human_delay_min: float = 0.5 - human_delay_max: float = 2.0 - - -class TikTokRegistration: - """ - Clean implementation of TikTok account registration. - Follows Single Responsibility Principle and Clean Architecture. - """ - - def __init__(self, automation): - """ - Initialize TikTok registration handler. - - Args: - automation: Parent automation instance with browser and utilities - """ - self.automation = automation - self.selectors = TikTokSelectors() - self.workflow = TikTokWorkflow.get_registration_workflow() - self.config = RegistrationConfig() - self._current_stage = None - - logger.debug("TikTok registration handler initialized") - - def register_account( - self, - full_name: str, - age: int, - registration_method: str = "email", - phone_number: Optional[str] = None, - **kwargs - ) -> Dict[str, Any]: - """ - Execute complete account registration workflow. - - Args: - full_name: User's full name - age: User's age (must be >= 13) - registration_method: Either "email" or "phone" - phone_number: Phone number (required if method is "phone") - **kwargs: Additional optional parameters - - Returns: - Registration result dictionary with status and account data - """ - try: - # Validate inputs - if not self._validate_inputs(full_name, age, registration_method, phone_number): - return self._create_error_result("Invalid input parameters", RegistrationStage.NAVIGATION) - - # Generate account data - account_data = self._generate_account_data(full_name, age, registration_method, phone_number, **kwargs) - logger.info(f"Starting TikTok registration for {account_data['username']} via {registration_method}") - - # Execute registration workflow stages - stages = [ - (RegistrationStage.NAVIGATION, self._navigate_to_tiktok), - (RegistrationStage.COOKIE_CONSENT, self._handle_cookies), - (RegistrationStage.LOGIN_CLICK, self._click_login), - (RegistrationStage.REGISTER_CLICK, self._click_register), - (RegistrationStage.PHONE_EMAIL_SELECTION, self._select_phone_email_option), - (RegistrationStage.METHOD_SELECTION, lambda: self._select_method(registration_method)), - (RegistrationStage.BIRTHDAY_ENTRY, lambda: self._enter_birthday(account_data["birthday"])), - (RegistrationStage.EMAIL_ENTRY, lambda: self._enter_email(account_data["email"])), - (RegistrationStage.PASSWORD_ENTRY, lambda: self._enter_password(account_data["password"])), - (RegistrationStage.CODE_SENDING, self._send_verification_code), - (RegistrationStage.CODE_VERIFICATION, lambda: self._verify_code(account_data["email"])), - (RegistrationStage.USERNAME_ENTRY, lambda: self._handle_username(account_data)), - (RegistrationStage.COMPLETION, self._complete_registration) - ] - - for stage, handler in stages: - self._current_stage = stage - self._emit_progress(f"Processing: {stage.value}") - - if not self._execute_with_retry(handler): - return self._create_error_result( - f"Failed at stage: {stage.value}", - stage, - account_data - ) - - self._add_human_delay() - - logger.info(f"Successfully created TikTok account: {account_data['username']}") - return self._create_success_result(account_data) - - except Exception as e: - logger.error(f"Unexpected error during registration: {e}", exc_info=True) - return self._create_error_result(str(e), self._current_stage) - - # ========== VALIDATION METHODS ========== - - def _validate_inputs( - self, - full_name: str, - age: int, - method: str, - phone: Optional[str] - ) -> bool: - """Validate registration inputs.""" - if not full_name or len(full_name.strip()) < 2: - logger.error("Invalid name provided") - return False - - if age < 13: - logger.error("Age must be at least 13 for TikTok") - return False - - if method not in ["email", "phone"]: - logger.error(f"Invalid registration method: {method}") - return False - - if method == "phone" and not phone: - logger.error("Phone number required for phone registration") - return False - - return True - - # ========== DATA GENERATION ========== - - def _generate_account_data( - self, - full_name: str, - age: int, - method: str, - phone: Optional[str], - **kwargs - ) -> Dict[str, Any]: - """Generate account data for registration.""" - birthday = self.automation.birthday_generator.generate_birthday_components("tiktok", age) - password = kwargs.get("password") or self.automation.password_generator.generate_password() - username = kwargs.get("username") or self.automation.username_generator.generate_username(full_name) - email = kwargs.get("email") or self._generate_email(username) - - return { - "full_name": full_name, - "username": username, - "email": email, - "password": password, - "birthday": birthday, - "age": age, - "registration_method": method, - "phone_number": phone - } - - def _generate_email(self, username: str) -> str: - """Generate email address for account.""" - return f"{username}@{self.automation.email_domain}" - - # ========== NAVIGATION STAGES ========== - - def _navigate_to_tiktok(self) -> bool: - """Navigate to TikTok homepage.""" - try: - self._emit_progress("Navigating to TikTok...") - page = self.automation.browser.page - page.goto("https://www.tiktok.com/", wait_until="domcontentloaded", timeout=30000) - - # Verify navigation success - if not self._wait_for_element_any([ - "button#header-login-button", - "button.TUXButton:has-text('Anmelden')", - "button:has-text('Log in')" - ]): - logger.error("Failed to load TikTok homepage") - return False - - logger.info("Successfully navigated to TikTok") - return True - - except Exception as e: - logger.error(f"Navigation failed: {e}") - return False - - def _handle_cookies(self) -> bool: - """Handle cookie consent banner if present.""" - try: - cookie_selectors = [ - "button:has-text('Alle Cookies akzeptieren')", - "button:has-text('Accept all')", - "button:has-text('Alle ablehnen')", - "button:has-text('Reject all')" - ] - - for selector in cookie_selectors: - if self._click_if_visible(selector, timeout=2000): - logger.info("Cookie banner handled") - return True - - logger.debug("No cookie banner found") - return True - - except Exception as e: - logger.debug(f"Cookie handling: {e}") - return True - - def _click_login(self) -> bool: - """Click the login button.""" - return self._click_element_with_fallback( - primary_selectors=[ - "button#header-login-button", - "button.TUXButton:has-text('Anmelden')", - "button:has-text('Log in')" - ], - fallback_text=["Anmelden", "Log in", "Sign in"], - stage_name="login button" - ) - - def _click_register(self) -> bool: - """Click the register link.""" - return self._click_element_with_fallback( - primary_selectors=[ - "a:text('Registrieren')", - "button:text('Registrieren')", - "span:text('Registrieren')", - "a:text('Sign up')", - "span:text('Sign up')" - ], - fallback_text=["Registrieren", "Sign up", "Register"], - stage_name="register link" - ) - - def _select_phone_email_option(self) -> bool: - """Select phone/email registration option.""" - return self._click_element_with_fallback( - primary_selectors=[ - "div:has-text('Telefonnummer oder E-Mail')", - "div:has-text('Use phone or email')", - "button:has-text('Phone or email')" - ], - fallback_text=["Telefonnummer oder E-Mail", "Use phone or email", "Phone or email"], - stage_name="phone/email option", - use_fuzzy=True - ) - - def _select_method(self, method: str) -> bool: - """Select specific registration method (email or phone).""" - if method == "email": - # Check if already on email page - if self._is_element_visible(self.selectors.EMAIL_FIELD, timeout=1000): - logger.info("Already on email registration page") - return True - - return self._click_element_with_fallback( - primary_selectors=[ - "a:has-text('Mit E-Mail-Adresse registrieren')", - "a:has-text('Sign up with email')", - "button:has-text('E-Mail')" - ], - fallback_text=["E-Mail", "Email"], - stage_name="email method" - ) - - return False # Phone method not fully implemented - - # ========== FORM FILLING STAGES ========== - - def _enter_birthday(self, birthday: Dict[str, int]) -> bool: - """Enter birthday using dropdowns.""" - try: - self._emit_progress("Entering birthday...") - - # Month selection - if not self._select_dropdown_option( - dropdown_text="Monat", - option_value=self._get_month_name(birthday["month"]) - ): - return False - - # Day selection - if not self._select_dropdown_option( - dropdown_text="Tag", - option_value=str(birthday["day"]) - ): - return False - - # Year selection - if not self._select_dropdown_option( - dropdown_text="Jahr", - option_value=str(birthday["year"]) - ): - return False - - logger.info(f"Birthday entered: {birthday['month']}/{birthday['day']}/{birthday['year']}") - return True - - except Exception as e: - logger.error(f"Birthday entry failed: {e}") - return False - - def _enter_email(self, email: str) -> bool: - """Enter email address.""" - return self._fill_input_field( - field_selectors=[ - "input[placeholder='E-Mail-Adresse']", - "input[name='email']", - "input[type='email']" - ], - value=email, - field_name="email" - ) - - def _enter_password(self, password: str) -> bool: - """Enter password with proper selectors.""" - return self._fill_input_field( - field_selectors=[ - "input.css-ujllvj-InputContainer[type='password']", - "input[type='password'][autocomplete='new-password']", - "input.etcs7ny1[type='password']", - "input[type='password'][placeholder='Passwort']", - "input[type='password']" - ], - value=password, - field_name="password", - validate=True - ) - - def _send_verification_code(self) -> bool: - """Click send code button.""" - self._emit_progress("Requesting verification code...") - - return self._click_element_with_fallback( - primary_selectors=[ - "button[data-e2e='send-code-button']", - "button:has-text('Code senden')", - "button:has-text('Send code')" - ], - fallback_text=["Code senden", "Send code"], - stage_name="send code button", - wait_enabled=True - ) - - def _verify_code(self, email: str) -> bool: - """Handle email verification code.""" - try: - self._emit_progress("Waiting for verification code...") - - # Get verification code from email - code = self._get_verification_code(email) - if not code: - logger.error("Failed to retrieve verification code") - return False - - # Enter verification code - return self._fill_input_field( - field_selectors=[ - "input[placeholder*='sechsstelligen Code']", - "input[placeholder*='6-digit code']", - "input[maxlength='6']" - ], - value=code, - field_name="verification code" - ) - - except Exception as e: - logger.error(f"Code verification failed: {e}") - return False - - def _get_verification_code(self, email: str) -> Optional[str]: - """Retrieve verification code from email.""" - max_attempts = 30 - - for attempt in range(max_attempts): - try: - code = self.automation.email_handler.get_verification_code( - target_email=email, - platform="tiktok", - max_attempts=1, - delay_seconds=2 - ) - - if code and len(code) == 6 and code.isdigit(): - logger.info(f"Verification code retrieved: {code}") - return code - - except Exception as e: - logger.debug(f"Attempt {attempt + 1} failed: {e}") - - time.sleep(2) - - return None - - def _handle_username(self, account_data: Dict[str, Any]) -> bool: - """Handle username step (usually skipped).""" - try: - # Try to skip username selection - skip_selectors = [ - "button:has-text('Überspringen')", - "button:has-text('Skip')", - "a:has-text('Skip')" - ] - - for selector in skip_selectors: - if self._click_if_visible(selector, timeout=2000): - logger.info("Username step skipped") - return True - - # If can't skip, try to continue - return True - - except Exception as e: - logger.debug(f"Username handling: {e}") - return True - - def _complete_registration(self) -> bool: - """Complete registration and verify success.""" - try: - self._emit_progress("Finalizing account...") - - # Click any final continue buttons - continue_selectors = [ - "button:has-text('Weiter')", - "button:has-text('Continue')", - "button:has-text('Next')" - ] - - for selector in continue_selectors: - self._click_if_visible(selector, timeout=1000) - - # Check for success indicators - success_indicators = [ - "a[href='/foryou']", - "button[data-e2e='profile-icon']", - "[aria-label='Profile']" - ] - - for indicator in success_indicators: - if self._is_element_visible(indicator, timeout=5000): - logger.info("Registration completed successfully") - return True - - # Even if indicators not found, might still be successful - logger.warning("Success indicators not found, assuming success") - return True - - except Exception as e: - logger.error(f"Completion check failed: {e}") - return False - - # ========== HELPER METHODS ========== - - def _execute_with_retry(self, handler, max_retries: Optional[int] = None) -> bool: - """Execute handler with retry logic.""" - retries = max_retries or self.config.max_retries - - for attempt in range(retries): - try: - if handler(): - return True - - if attempt < retries - 1: - logger.debug(f"Retrying... ({attempt + 2}/{retries})") - time.sleep(self.config.retry_delay * (attempt + 1)) - - except Exception as e: - logger.error(f"Handler error: {e}") - if attempt == retries - 1: - raise - - return False - - def _click_element_with_fallback( - self, - primary_selectors: List[str], - fallback_text: List[str], - stage_name: str, - use_fuzzy: bool = False, - wait_enabled: bool = False - ) -> bool: - """Click element with multiple fallback strategies.""" - # Try primary selectors - for selector in primary_selectors: - if self._click_if_visible(selector): - logger.info(f"Clicked {stage_name} with selector: {selector}") - return True - - # Try fuzzy matching if enabled - if use_fuzzy and hasattr(self.automation, 'ui_helper'): - try: - if self.automation.ui_helper.click_button_fuzzy( - fallback_text, - primary_selectors[0] if primary_selectors else None - ): - logger.info(f"Clicked {stage_name} using fuzzy matching") - return True - except: - pass - - logger.error(f"Failed to click {stage_name}") - return False - - def _fill_input_field( - self, - field_selectors: List[str], - value: str, - field_name: str, - validate: bool = False - ) -> bool: - """Fill input field with value.""" - for selector in field_selectors: - try: - if self._is_element_visible(selector, timeout=2000): - element = self.automation.browser.page.locator(selector).first - element.click() - element.fill("") - element.type(value, delay=50) - - if validate: - actual = element.input_value() - if actual != value: - logger.warning(f"Validation failed for {field_name}") - continue - - logger.info(f"Filled {field_name} field") - return True - - except Exception as e: - logger.debug(f"Failed with selector {selector}: {e}") - - logger.error(f"Failed to fill {field_name} field") - return False - - def _select_dropdown_option(self, dropdown_text: str, option_value: str) -> bool: - """Select option from dropdown.""" - try: - # Click dropdown - dropdown_selector = f"div:has-text('{dropdown_text}')" - if not self._click_if_visible(dropdown_selector): - return False - - time.sleep(0.5) - - # Click option - option_selector = f"div.css-vz5m7n-DivOption:has-text('{option_value}')" - if not self._click_if_visible(option_selector): - # Try alternative selector - option_selector = f"div[role='option']:has-text('{option_value}')" - if not self._click_if_visible(option_selector): - return False - - logger.info(f"Selected {option_value} from {dropdown_text} dropdown") - return True - - except Exception as e: - logger.error(f"Dropdown selection failed: {e}") - return False - - def _click_if_visible(self, selector: str, timeout: int = None) -> bool: - """Click element if visible.""" - try: - timeout = timeout or self.config.timeout - if self.automation.browser.is_element_visible(selector, timeout=timeout): - self.automation.browser.click_element(selector) - return True - except: - pass - return False - - def _is_element_visible(self, selector: str, timeout: int = None) -> bool: - """Check if element is visible.""" - try: - timeout = timeout or self.config.timeout - return self.automation.browser.is_element_visible(selector, timeout=timeout) - except: - return False - - def _wait_for_element_any(self, selectors: List[str], timeout: int = None) -> bool: - """Wait for any of the selectors to be visible.""" - timeout = timeout or self.config.timeout - end_time = time.time() + (timeout / 1000) - - while time.time() < end_time: - for selector in selectors: - if self._is_element_visible(selector, timeout=500): - return True - time.sleep(0.5) - - return False - - def _add_human_delay(self): - """Add human-like delay between actions.""" - if hasattr(self.automation, 'human_behavior'): - self.automation.human_behavior.random_delay( - self.config.human_delay_min, - self.config.human_delay_max - ) - else: - time.sleep(self.config.human_delay_min) - - def _emit_progress(self, message: str): - """Emit progress message to UI.""" - if hasattr(self.automation, '_emit_customer_log'): - self.automation._emit_customer_log(message) - logger.info(message) - - def _get_month_name(self, month: int) -> str: - """Convert month number to name.""" - months = { - 1: "Januar", 2: "Februar", 3: "März", 4: "April", - 5: "Mai", 6: "Juni", 7: "Juli", 8: "August", - 9: "September", 10: "Oktober", 11: "November", 12: "Dezember" - } - return months.get(month, str(month)) - - # ========== RESULT CREATION ========== - - def _create_success_result(self, account_data: Dict[str, Any]) -> Dict[str, Any]: - """Create success result dictionary.""" - return { - "success": True, - "stage": RegistrationStage.COMPLETION.value, - "account_data": account_data, - "message": f"Account {account_data['username']} successfully created" - } - - def _create_error_result( - self, - error: str, - stage: Optional[RegistrationStage] = None, - account_data: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: - """Create error result dictionary.""" - return { - "success": False, - "error": error, - "stage": stage.value if stage else "unknown", - "account_data": account_data or {} - } \ No newline at end of file diff --git a/social_networks/tiktok/tiktok_registration_final.py b/social_networks/tiktok/tiktok_registration_final.py deleted file mode 100644 index 3d80143..0000000 --- a/social_networks/tiktok/tiktok_registration_final.py +++ /dev/null @@ -1,115 +0,0 @@ -# social_networks/tiktok/tiktok_registration_final.py - -""" -TikTok-Registrierung - FINALE OPTIMIERTE IMPLEMENTIERUNG -PRODUKTIONSBEREIT mit korrekter Workflow-Reihenfolge und robusten Selektoren. - -KORRIGIERTER WORKFLOW: -1. E-Mail eingeben -2. Code senden Button klicken -3. Code empfangen und eingeben -4. Passwort eingeben -5. Dummy-Input-Trick (beliebige Zahl ins Code-Feld) -6. Weiter Button klicken - -FEATURES: -- Robuste Multi-Level-Selektor-Strategien -- Exponential backoff für E-Mail-Empfang -- Umfassendes Error-Handling mit Retry-Mechanismen -- Anti-Detection durch menschliches Verhalten -- Zukunftssicher durch modulare Architektur -""" - -import time -import random -import re -from typing import Dict, List, Any, Optional, Tuple - -from .tiktok_selectors import TikTokSelectors -from .tiktok_workflow import TikTokWorkflow -from utils.logger import setup_logger - -# Konfiguriere Logger -logger = setup_logger("tiktok_registration") - -class TikTokRegistrationOptimized: - """ - FINALE optimierte Klasse für die Registrierung von TikTok-Konten. - - Diese Implementierung ist zukunftssicher und verwendet robuste - Multi-Level-Selektoren sowie optimierte Workflows. - """ - - def __init__(self, automation): - """ - Initialisiert die optimierte TikTok-Registrierung. - - Args: - automation: Referenz auf die Hauptautomatisierungsklasse - """ - self.automation = automation - self.selectors = TikTokSelectors() - self.workflow = TikTokWorkflow.get_registration_workflow() - - # Konfiguration für robustes Verhalten - self.max_retry_attempts = 3 - self.base_delay = 0.5 - self.max_delay = 5.0 - - logger.info("Optimierte TikTok-Registrierung initialisiert") - - def register_account(self, full_name: str, age: int, registration_method: str = "email", - phone_number: str = None, **kwargs) -> Dict[str, Any]: - """ - Führt den vollständigen optimierten Registrierungsprozess durch. - - Args: - full_name: Vollständiger Name für den Account - age: Alter des Benutzers - registration_method: "email" oder "phone" - phone_number: Telefonnummer (nur bei registration_method="phone") - **kwargs: Weitere optionale Parameter - - Returns: - Dict[str, Any]: Ergebnis der Registrierung mit Status und Account-Daten - """ - # Eingabe-Validierung - validation_result = self._validate_inputs(full_name, age, registration_method, phone_number) - if not validation_result["valid"]: - return self._create_error_result(validation_result["error"], "input_validation") - - # Account-Daten generieren - account_data = self._generate_account_data(full_name, age, registration_method, phone_number, **kwargs) - - logger.info(f"=== STARTE OPTIMIERTEN TIKTOK-REGISTRIERUNGSPROZESS ===") - logger.info(f"Account: {account_data['username']} | Methode: {registration_method}") - - try: - # Phase 1: Navigation und Setup - if not self._execute_navigation_phase(): - return self._create_error_result("Navigation fehlgeschlagen", "navigation", account_data) - - # Phase 2: Registrierungsformular öffnen - if not self._execute_form_opening_phase(): - return self._create_error_result("Formular öffnen fehlgeschlagen", "form_opening", account_data) - - # Phase 3: Geburtsdatum eingeben - self.automation._emit_customer_log("🎂 Geburtsdatum wird festgelegt...") - if not self._execute_birthday_phase(account_data["birthday"]): - return self._create_error_result("Geburtsdatum-Eingabe fehlgeschlagen", "birthday", account_data) - - # Phase 4: OPTIMIERTER HAUPTWORKFLOW - self.automation._emit_customer_log("📝 Persönliche Daten werden übertragen...") - if not self._execute_main_registration_workflow(account_data, registration_method): - return self._create_error_result("Hauptregistrierung fehlgeschlagen", "main_workflow", account_data) - - # Phase 5: Benutzername und Finalisierung - self.automation._emit_customer_log("👤 Benutzername wird erstellt...") - if not self._execute_finalization_phase(account_data): - return self._create_error_result("Finalisierung fehlgeschlagen", "finalization", account_data) - - # Erfolgreiche Registrierung - logger.info(f"=== REGISTRIERUNG ERFOLGREICH ABGESCHLOSSEN ===") - self.automation._emit_customer_log("✅ Account erfolgreich erstellt!") - - return {\n \"success\": True,\n \"stage\": \"completed\",\n \"account_data\": account_data,\n \"message\": f\"Account {account_data['username']} erfolgreich erstellt\"\n }\n \n except Exception as e:\n error_msg = f\"Unerwarteter Fehler: {str(e)}\"\n logger.error(error_msg, exc_info=True)\n return self._create_error_result(error_msg, \"exception\", account_data)\n \n def _execute_main_registration_workflow(self, account_data: Dict[str, Any], registration_method: str) -> bool:\n \"\"\"\n KERN-WORKFLOW: Führt die optimierte Registrierungssequenz aus.\n \n OPTIMIERTE REIHENFOLGE:\n 1. E-Mail/Telefon eingeben\n 2. Passwort eingeben \n 3. Code senden Button klicken\n 4. Code empfangen und eingeben\n \n Args:\n account_data: Account-Daten\n registration_method: \"email\" oder \"phone\"\n \n Returns:\n bool: True bei Erfolg, False bei Fehler\n \"\"\"\n try:\n logger.info(\"=== STARTE OPTIMIERTEN HAUPTWORKFLOW ===\")\n \n if registration_method == \"email\":\n return self._execute_email_main_workflow(account_data)\n elif registration_method == \"phone\":\n return self._execute_phone_main_workflow(account_data)\n else:\n logger.error(f\"Unbekannte Registrierungsmethode: {registration_method}\")\n return False\n \n except Exception as e:\n logger.error(f\"Fehler im Hauptworkflow: {e}\")\n return False\n \n def _execute_email_main_workflow(self, account_data: Dict[str, Any]) -> bool:\n \"\"\"\n KORRIGIERTER E-MAIL-WORKFLOW: E-Mail → Code senden → Code eingeben → Passwort → Dummy-Trick → Weiter\n \n Args:\n account_data: Account-Daten\n \n Returns:\n bool: True bei Erfolg, False bei Fehler\n \"\"\"\n try:\n logger.info(\">>> STARTE E-MAIL-WORKFLOW <<<\")\n \n # SCHRITT 1: E-Mail-Feld ausfüllen\n logger.info(\"[1/4] E-Mail-Adresse eingeben\")\n if not self._fill_email_field_robust(account_data[\"email\"]):\n logger.error(\"E-Mail-Eingabe fehlgeschlagen\")\n return False\n \n # SCHRITT 2: Passwort-Feld ausfüllen (KRITISCH: VOR Code senden!)\n logger.info(\"[2/4] Passwort eingeben (vor Code-Anforderung)\")\n if not self._fill_password_field_robust(account_data[\"password\"]):\n logger.error(\"Passwort-Eingabe fehlgeschlagen\")\n return False\n \n # SCHRITT 3: Code senden Button klicken\n logger.info(\"[3/4] Code senden Button klicken\")\n if not self._click_send_code_button_robust():\n logger.error(\"Code-senden-Button klicken fehlgeschlagen\")\n return False\n \n # SCHRITT 4: Verifizierungscode empfangen und eingeben\n logger.info(\"[4/4] Verifizierungscode empfangen und eingeben\")\n if not self._handle_email_verification_robust(account_data[\"email\"]):\n logger.error(\"E-Mail-Verifizierung fehlgeschlagen\")\n return False\n \n logger.info(\">>> E-MAIL-WORKFLOW ERFOLGREICH ABGESCHLOSSEN <<<\")\n \n # Pause für UI-Updates - Weiter-Button sollte jetzt aktiviert sein\n self.automation.human_behavior.random_delay(1.0, 2.5)\n \n return True\n \n except Exception as e:\n logger.error(f\"Fehler im E-Mail-Workflow: {e}\")\n return False\n \n def _fill_email_field_robust(self, email: str) -> bool:\n \"\"\"\n Robuste E-Mail-Feld-Ausfüllung mit Multi-Level-Selektoren.\n \n Args:\n email: E-Mail-Adresse\n \n Returns:\n bool: True bei Erfolg, False bei Fehler\n \"\"\"\n try:\n logger.debug(f\"Fülle E-Mail-Feld aus: {email}\")\n \n # Robuste Selektor-Strategie\n email_selectors = self.selectors.get_email_field_selectors()\n \n for attempt in range(self.max_retry_attempts):\n logger.debug(f\"E-Mail-Eingabe Versuch {attempt + 1}/{self.max_retry_attempts}\")\n \n # Versuche jeden Selektor\n for i, selector in enumerate(email_selectors):\n try:\n if self.automation.browser.is_element_visible(selector, timeout=2000):\n # Menschliche Eingabe\n success = self.automation.browser.fill_form_field(\n selector, email, human_typing=True\n )\n \n if success:\n logger.info(f\"E-Mail erfolgreich eingegeben mit Selektor {i+1}\")\n self._add_human_delay()\n return True\n \n except Exception as e:\n logger.debug(f\"E-Mail-Selektor {i+1} fehlgeschlagen: {e}\")\n continue\n \n # Fallback: Fuzzy-Matching\n try:\n success = self.automation.ui_helper.fill_field_fuzzy(\n [\"E-Mail-Adresse\", \"Email\", \"E-Mail\"],\n email,\n email_selectors[0]\n )\n \n if success:\n logger.info(\"E-Mail über Fuzzy-Matching eingegeben\")\n self._add_human_delay()\n return True\n \n except Exception as e:\n logger.debug(f\"Fuzzy-Matching fehlgeschlagen: {e}\")\n \n # Retry-Delay\n if attempt < self.max_retry_attempts - 1:\n delay = self.base_delay * (2 ** attempt)\n logger.debug(f\"Retry-Delay: {delay}s\")\n time.sleep(delay)\n \n logger.error(\"E-Mail-Feld konnte nicht ausgefüllt werden\")\n return False\n \n except Exception as e:\n logger.error(f\"Kritischer Fehler bei E-Mail-Eingabe: {e}\")\n return False\n \n def _fill_password_field_robust(self, password: str) -> bool:\n \"\"\"\n Robuste Passwort-Feld-Ausfüllung mit Multi-Level-Selektoren.\n \n Args:\n password: Passwort\n \n Returns:\n bool: True bei Erfolg, False bei Fehler\n \"\"\"\n try:\n logger.debug(\"Fülle Passwort-Feld aus (menschliche Eingabe)\")\n \n # Robuste Selektor-Strategie\n password_selectors = self.selectors.get_password_field_selectors()\n \n for attempt in range(self.max_retry_attempts):\n logger.debug(f\"Passwort-Eingabe Versuch {attempt + 1}/{self.max_retry_attempts}\")\n \n # Versuche jeden Selektor\n for i, selector in enumerate(password_selectors):\n try:\n if self.automation.browser.is_element_visible(selector, timeout=2000):\n # Menschliche Eingabe mit Validierung\n success = self.automation.browser.fill_form_field(\n selector, password, human_typing=True\n )\n \n if success:\n # Validiere Passwort-Eingabe\n if self._validate_password_input(selector, password):\n logger.info(f\"Passwort erfolgreich eingegeben mit Selektor {i+1}\")\n self._add_human_delay()\n return True\n else:\n logger.warning(f\"Passwort-Validierung fehlgeschlagen bei Selektor {i+1}\")\n \n except Exception as e:\n logger.debug(f\"Passwort-Selektor {i+1} fehlgeschlagen: {e}\")\n continue\n \n # Fallback: Fuzzy-Matching\n try:\n success = self.automation.ui_helper.fill_field_fuzzy(\n [\"Passwort\", \"Password\"],\n password,\n password_selectors[0]\n )\n \n if success:\n logger.info(\"Passwort über Fuzzy-Matching eingegeben\")\n self._add_human_delay()\n return True\n \n except Exception as e:\n logger.debug(f\"Passwort Fuzzy-Matching fehlgeschlagen: {e}\")\n \n # Retry-Delay\n if attempt < self.max_retry_attempts - 1:\n delay = self.base_delay * (2 ** attempt)\n logger.debug(f\"Retry-Delay: {delay}s\")\n time.sleep(delay)\n \n logger.error(\"Passwort-Feld konnte nicht ausgefüllt werden\")\n return False\n \n except Exception as e:\n logger.error(f\"Kritischer Fehler bei Passwort-Eingabe: {e}\")\n return False\n \n def _validate_password_input(self, selector: str, expected_password: str) -> bool:\n \"\"\"\n Validiert, ob das Passwort korrekt eingegeben wurde.\n \n Args:\n selector: CSS-Selektor des Passwort-Feldes\n expected_password: Erwartetes Passwort\n \n Returns:\n bool: True wenn Validierung erfolgreich, False sonst\n \"\"\"\n try:\n # Passwort-Feld-Wert abrufen (falls möglich)\n element = self.automation.browser.wait_for_selector(selector, timeout=1000)\n if element:\n actual_value = element.get_attribute(\"value\") or \"\"\n return len(actual_value) == len(expected_password)\n \n # Fallback: Längen-basierte Validierung\n return True # Optimistisch, da direkter Wert-Abruf oft nicht möglich\n \n except Exception as e:\n logger.debug(f\"Passwort-Validierung nicht möglich: {e}\")\n return True # Optimistisch\n \n def _click_send_code_button_robust(self) -> bool:\n \"\"\"\n Robustes Klicken des 'Code senden'-Buttons mit Retry-Logik.\n \n Returns:\n bool: True bei Erfolg, False bei Fehler\n \"\"\"\n try:\n logger.debug(\"Klicke 'Code senden'-Button\")\n \n # Pause vor Button-Klick\n self._add_human_delay()\n \n # Robuste Selektor-Strategie\n send_code_selectors = self.selectors.get_send_code_button_selectors()\n \n for attempt in range(self.max_retry_attempts):\n logger.debug(f\"Send-Code-Button Versuch {attempt + 1}/{self.max_retry_attempts}\")\n \n # Versuche jeden Selektor\n for i, selector in enumerate(send_code_selectors):\n try:\n if self.automation.browser.is_element_visible(selector, timeout=3000):\n # Prüfe, ob Button enabled ist\n element = self.automation.browser.wait_for_selector(selector, timeout=1000)\n if element:\n is_disabled = element.get_attribute(\"disabled\")\n aria_disabled = element.get_attribute(\"aria-disabled\")\n \n if is_disabled or aria_disabled == \"true\":\n logger.debug(f\"Button {i+1} ist disabled, versuche nächsten\")\n continue\n \n # Button klicken\n success = self.automation.browser.click_element(selector)\n if success:\n logger.info(f\"'Code senden'-Button erfolgreich geklickt mit Selektor {i+1}\")\n self._add_human_delay()\n return True\n \n except Exception as e:\n logger.debug(f\"Send-Code-Selektor {i+1} fehlgeschlagen: {e}\")\n continue\n \n # Fallback: Fuzzy-Button-Matching\n try:\n success = self.automation.ui_helper.click_button_fuzzy(\n [\"Code senden\", \"Send code\", \"Senden\"],\n send_code_selectors[0]\n )\n \n if success:\n logger.info(\"'Code senden'-Button über Fuzzy-Matching geklickt\")\n self._add_human_delay()\n return True\n \n except Exception as e:\n logger.debug(f\"Send-Code Fuzzy-Matching fehlgeschlagen: {e}\")\n \n # Retry-Delay\n if attempt < self.max_retry_attempts - 1:\n delay = self.base_delay * (2 ** attempt)\n logger.debug(f\"Retry-Delay: {delay}s\")\n time.sleep(delay)\n \n logger.error(\"'Code senden'-Button konnte nicht geklickt werden\")\n return False\n \n except Exception as e:\n logger.error(f\"Kritischer Fehler beim Send-Code-Button: {e}\")\n return False\n \n def _handle_email_verification_robust(self, email: str) -> bool:\n \"\"\"\n Robuste E-Mail-Verifizierung mit optimiertem Timing und Retry-Logik.\n \n Args:\n email: E-Mail-Adresse\n \n Returns:\n bool: True bei Erfolg, False bei Fehler\n \"\"\"\n try:\n logger.info(\"Starte robuste E-Mail-Verifizierung\")\n \n # E-Mail-Code abrufen mit exponential backoff\n verification_code = self._get_email_code_with_backoff(email)\n \n if not verification_code:\n logger.error(\"Kein Verifizierungscode empfangen\")\n return False\n \n logger.info(f\"Verifizierungscode empfangen: {verification_code}\")\n \n # Verifizierungscode eingeben\n if not self._fill_verification_code_robust(verification_code):\n logger.error(\"Verifizierungscode-Eingabe fehlgeschlagen\")\n return False\n \n logger.info(\"E-Mail-Verifizierung erfolgreich abgeschlossen\")\n \n # Pause für UI-Updates\n self._add_human_delay(1.0, 2.5)\n \n return True\n \n except Exception as e:\n logger.error(f\"Fehler bei E-Mail-Verifizierung: {e}\")\n return False\n \n def _get_email_code_with_backoff(self, email: str, max_attempts: int = 25) -> Optional[str]:\n \"\"\"\n Ruft E-Mail-Verifizierungscode mit exponential backoff ab.\n \n Args:\n email: E-Mail-Adresse\n max_attempts: Maximale Anzahl Versuche\n \n Returns:\n Optional[str]: Verifizierungscode oder None\n \"\"\"\n try:\n logger.info(f\"E-Mail-Code-Abruf gestartet für: {email}\")\n \n for attempt in range(max_attempts):\n # Exponential backoff: 3s, 4.5s, 6.75s, ... (max 20s)\n delay = min(3 * (1.5 ** attempt), 20)\n \n logger.debug(f\"E-Mail-Abruf [{attempt + 1}/{max_attempts}] - Wartezeit: {delay:.1f}s\")\n \n # Code abrufen\n try:\n code = self.automation.email_handler.get_verification_code(\n target_email=email,\n platform=\"tiktok\",\n max_attempts=1,\n delay_seconds=1\n )\n \n if code and len(code) == 6 and code.isdigit():\n logger.info(f\"Gültiger E-Mail-Code nach {attempt + 1} Versuchen empfangen\")\n return code\n \n except Exception as e:\n logger.debug(f\"E-Mail-Abruf-Versuch {attempt + 1} fehlgeschlagen: {e}\")\n \n # Warte vor nächstem Versuch\n if attempt < max_attempts - 1:\n time.sleep(delay)\n \n logger.warning(f\"Kein E-Mail-Code nach {max_attempts} Versuchen empfangen\")\n return None\n \n except Exception as e:\n logger.error(f\"Kritischer Fehler beim E-Mail-Code-Abruf: {e}\")\n return None\n \n def _fill_verification_code_robust(self, code: str) -> bool:\n \"\"\"\n Robuste Verifizierungscode-Eingabe mit Multi-Level-Selektoren.\n \n Args:\n code: Verifizierungscode\n \n Returns:\n bool: True bei Erfolg, False bei Fehler\n \"\"\"\n try:\n logger.debug(f\"Fülle Verifizierungscode aus: {code}\")\n \n # Robuste Selektor-Strategie\n code_selectors = self.selectors.get_verification_code_selectors()\n \n for attempt in range(self.max_retry_attempts):\n logger.debug(f\"Code-Eingabe Versuch {attempt + 1}/{self.max_retry_attempts}\")\n \n # Versuche jeden Selektor\n for i, selector in enumerate(code_selectors):\n try:\n if self.automation.browser.is_element_visible(selector, timeout=3000):\n # Menschliche Code-Eingabe\n success = self.automation.browser.fill_form_field(\n selector, code, human_typing=True\n )\n \n if success:\n logger.info(f\"Verifizierungscode erfolgreich eingegeben mit Selektor {i+1}\")\n self._add_human_delay()\n return True\n \n except Exception as e:\n logger.debug(f\"Code-Selektor {i+1} fehlgeschlagen: {e}\")\n continue\n \n # Fallback: Fuzzy-Matching\n try:\n success = self.automation.ui_helper.fill_field_fuzzy(\n [\"Gib den sechsstelligen Code ein\", \"Enter verification code\", \"Verification code\"],\n code,\n code_selectors[0]\n )\n \n if success:\n logger.info(\"Verifizierungscode über Fuzzy-Matching eingegeben\")\n self._add_human_delay()\n return True\n \n except Exception as e:\n logger.debug(f\"Code Fuzzy-Matching fehlgeschlagen: {e}\")\n \n # Retry-Delay\n if attempt < self.max_retry_attempts - 1:\n delay = self.base_delay * (2 ** attempt)\n logger.debug(f\"Retry-Delay: {delay}s\")\n time.sleep(delay)\n \n logger.error(\"Verifizierungscode-Feld konnte nicht ausgefüllt werden\")\n return False\n \n except Exception as e:\n logger.error(f\"Kritischer Fehler bei Code-Eingabe: {e}\")\n return False\n \n # HILFSMETHODEN\n \n def _add_human_delay(self, min_delay: float = None, max_delay: float = None):\n \"\"\"\n Fügt menschliche Verzögerung hinzu.\n \n Args:\n min_delay: Minimale Verzögerung\n max_delay: Maximale Verzögerung\n \"\"\"\n min_d = min_delay or self.base_delay\n max_d = max_delay or self.max_delay\n self.automation.human_behavior.random_delay(min_d, max_d)\n \n def _validate_inputs(self, full_name: str, age: int, registration_method: str, phone_number: str) -> Dict[str, Any]:\n \"\"\"Validiert Eingabeparameter.\"\"\"\n if not full_name or len(full_name) < 3:\n return {\"valid\": False, \"error\": \"Ungültiger vollständiger Name\"}\n \n if age < 13:\n return {\"valid\": False, \"error\": \"Benutzer muss mindestens 13 Jahre alt sein\"}\n \n if registration_method not in [\"email\", \"phone\"]:\n return {\"valid\": False, \"error\": f\"Ungültige Registrierungsmethode: {registration_method}\"}\n \n if registration_method == \"phone\" and not phone_number:\n return {\"valid\": False, \"error\": \"Telefonnummer erforderlich für Telefon-Registrierung\"}\n \n return {\"valid\": True}\n \n def _generate_account_data(self, full_name: str, age: int, registration_method: str, phone_number: str, **kwargs) -> Dict[str, Any]:\n \"\"\"Generiert Account-Daten.\"\"\"\n username = kwargs.get(\"username\") or self.automation.username_generator.generate_username(\"tiktok\", full_name)\n password = kwargs.get(\"password\") or self.automation.password_generator.generate_password(\"tiktok\")\n \n email = None\n if registration_method == \"email\":\n email_prefix = username.lower().replace(\".\", \"\").replace(\"_\", \"\")\n email = f\"{email_prefix}@{self.automation.email_domain}\"\n \n birthday = self.automation.birthday_generator.generate_birthday_components(\"tiktok\", age)\n \n return {\n \"username\": username,\n \"password\": password,\n \"full_name\": full_name,\n \"email\": email,\n \"phone\": phone_number,\n \"birthday\": birthday,\n \"age\": age,\n \"registration_method\": registration_method\n }\n \n def _create_error_result(self, error_msg: str, stage: str, account_data: Dict[str, Any] = None) -> Dict[str, Any]:\n \"\"\"Erstellt standardisiertes Fehler-Result.\"\"\"\n result = {\n \"success\": False,\n \"error\": error_msg,\n \"stage\": stage\n }\n if account_data:\n result[\"account_data\"] = account_data\n return result\n \n # PLATZHALTER für weitere Phasen (werden aus ursprünglicher Implementierung übernommen)\n \n def _execute_navigation_phase(self) -> bool:\n \"\"\"Führt die Navigation zur TikTok-Startseite durch.\"\"\"\n # TODO: Implementierung aus ursprünglicher Datei übernehmen\n return True\n \n def _execute_form_opening_phase(self) -> bool:\n \"\"\"Öffnet das Registrierungsformular.\"\"\"\n # TODO: Implementierung aus ursprünglicher Datei übernehmen\n return True\n \n def _execute_birthday_phase(self, birthday: Dict[str, Any]) -> bool:\n \"\"\"Gibt das Geburtsdatum ein.\"\"\"\n # TODO: Implementierung aus ursprünglicher Datei übernehmen\n return True\n \n def _execute_finalization_phase(self, account_data: Dict[str, Any]) -> bool:\n \"\"\"Finalisiert die Registrierung (Benutzername, etc.).\"\"\"\n # TODO: Implementierung aus ursprünglicher Datei übernehmen\n return True\n \n def _execute_phone_main_workflow(self, account_data: Dict[str, Any]) -> bool:\n \"\"\"Führt den Telefon-Workflow aus.\"\"\"\n # TODO: Telefon-spezifische Implementierung\n logger.warning(\"Telefon-Workflow noch nicht vollständig implementiert\")\n return False \ No newline at end of file diff --git a/social_networks/tiktok/tiktok_registration_new.py b/social_networks/tiktok/tiktok_registration_new.py deleted file mode 100644 index bc0c72e..0000000 --- a/social_networks/tiktok/tiktok_registration_new.py +++ /dev/null @@ -1,801 +0,0 @@ -# social_networks/tiktok/tiktok_registration_new.py - -""" -TikTok-Registrierung - Optimierte Klasse für die Kontoerstellung bei TikTok -NEUE IMPLEMENTIERUNG mit korrekter Workflow-Reihenfolge für maximale Stabilität. - -OPTIMIERTER WORKFLOW: -1. E-Mail eingeben -2. Passwort eingeben -3. Code senden Button klicken -4. Code empfangen und eingeben -5. Weiter Button wird automatisch aktiviert -""" - -import time -import random -import re -from typing import Dict, List, Any, Optional, Tuple - -from .tiktok_selectors import TikTokSelectors -from .tiktok_workflow import TikTokWorkflow -from utils.logger import setup_logger - -# Konfiguriere Logger -logger = setup_logger("tiktok_registration") - -class TikTokRegistration: - """ - Optimierte Klasse für die Registrierung von TikTok-Konten. - Implementiert einen robusten, zukunftssicheren Workflow. - """ - - def __init__(self, automation): - """ - Initialisiert die TikTok-Registrierung. - - Args: - automation: Referenz auf die Hauptautomatisierungsklasse - """ - self.automation = automation - self.selectors = TikTokSelectors() - self.workflow = TikTokWorkflow.get_registration_workflow() - - logger.debug("TikTok-Registrierung initialisiert") - - def register_account(self, full_name: str, age: int, registration_method: str = "email", - phone_number: str = None, **kwargs) -> Dict[str, Any]: - """ - Führt den vollständigen Registrierungsprozess für einen TikTok-Account durch. - - Args: - full_name: Vollständiger Name für den Account - age: Alter des Benutzers - registration_method: "email" oder "phone" - phone_number: Telefonnummer (nur bei registration_method="phone") - **kwargs: Weitere optionale Parameter - - Returns: - Dict[str, Any]: Ergebnis der Registrierung mit Status und Account-Daten - """ - # Validiere die Eingaben - if not self._validate_registration_inputs(full_name, age, registration_method, phone_number): - return { - "success": False, - "error": "Ungültige Eingabeparameter", - "stage": "input_validation" - } - - # Account-Daten generieren - account_data = self._generate_account_data(full_name, age, registration_method, phone_number, **kwargs) - - # Starte den Registrierungsprozess - logger.info(f"Starte optimierten TikTok-Registrierungsprozess für {account_data['username']} via {registration_method}") - - try: - # 1. Zur Startseite navigieren - self.automation._emit_customer_log("🌐 Mit TikTok verbinden...") - if not self._navigate_to_homepage(): - return self._create_error_result("Konnte nicht zur TikTok-Startseite navigieren", "navigation", account_data) - - # 2. Cookie-Banner behandeln - self.automation._emit_customer_log("⚙️ Einstellungen werden vorbereitet...") - self._handle_cookie_banner() - - # 3. Anmelden-Button klicken - self.automation._emit_customer_log("📋 Registrierungsformular wird geöffnet...") - if not self._click_login_button(): - return self._create_error_result("Konnte nicht auf Anmelden-Button klicken", "login_button", account_data) - - # 4. Registrieren-Link klicken - if not self._click_register_link(): - return self._create_error_result("Konnte nicht auf Registrieren-Link klicken", "register_link", account_data) - - # 5. Telefon/E-Mail-Option auswählen - if not self._click_phone_email_option(): - return self._create_error_result("Konnte nicht auf Telefon/E-Mail-Option klicken", "phone_email_option", account_data) - - # 6. E-Mail oder Telefon als Registrierungsmethode wählen - if not self._select_registration_method(registration_method): - return self._create_error_result(f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen", "registration_method", account_data) - - # 7. Geburtsdatum eingeben - self.automation._emit_customer_log("🎂 Geburtsdatum wird festgelegt...") - if not self._enter_birthday(account_data["birthday"]): - return self._create_error_result("Fehler beim Eingeben des Geburtsdatums", "birthday", account_data) - - # 8. OPTIMIERTER REGISTRIERUNGSWORKFLOW - self.automation._emit_customer_log("📝 Persönliche Daten werden übertragen...") - if not self._execute_optimized_registration_workflow(account_data, registration_method): - return self._create_error_result("Fehler im Registrierungsworkflow", "registration_workflow", account_data) - - # 9. Benutzernamen erstellen - self.automation._emit_customer_log("👤 Benutzername wird erstellt...") - if not self._create_username(account_data): - return self._create_error_result("Fehler beim Erstellen des Benutzernamens", "username", account_data) - - # 10. Erfolgreiche Registrierung überprüfen - self.automation._emit_customer_log("🔍 Account wird finalisiert...") - if not self._check_registration_success(): - return self._create_error_result("Registrierung fehlgeschlagen oder konnte nicht verifiziert werden", "final_check", account_data) - - # Registrierung erfolgreich abgeschlossen - logger.info(f"TikTok-Account {account_data['username']} erfolgreich erstellt") - self.automation._emit_customer_log("✅ Account erfolgreich erstellt!") - - return { - "success": True, - "stage": "completed", - "account_data": account_data - } - - except Exception as e: - error_msg = f"Unerwarteter Fehler bei der TikTok-Registrierung: {str(e)}" - logger.error(error_msg, exc_info=True) - - return { - "success": False, - "error": error_msg, - "stage": "exception", - "account_data": account_data - } - - def _execute_optimized_registration_workflow(self, account_data: Dict[str, Any], registration_method: str) -> bool: - """ - Führt den optimierten Registrierungsworkflow aus. - - KORRIGIERTE REIHENFOLGE für E-Mail-Registrierung: - 1. E-Mail eingeben - 2. Code senden Button klicken - 3. Code empfangen und eingeben - 4. Passwort eingeben - 5. Dummy-Input-Trick anwenden - 6. Weiter Button klicken - - Args: - account_data: Account-Daten - registration_method: "email" oder "phone" - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - if registration_method == "email": - return self._execute_email_workflow(account_data) - elif registration_method == "phone": - return self._execute_phone_workflow(account_data) - else: - logger.error(f"Unbekannte Registrierungsmethode: {registration_method}") - return False - - except Exception as e: - logger.error(f"Fehler im optimierten Registrierungsworkflow: {e}") - return False - - def _execute_email_workflow(self, account_data: Dict[str, Any]) -> bool: - """ - Führt den optimierten E-Mail-Registrierungsworkflow aus. - - KORRIGIERTER WORKFLOW: E-Mail → Code senden → Code eingeben → Passwort → Dummy-Trick → Weiter - - Args: - account_data: Account-Daten - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - logger.info("=== STARTE OPTIMIERTEN E-MAIL-WORKFLOW ===") - - # SCHRITT 1: E-Mail-Feld ausfüllen - logger.info("SCHRITT 1/6: E-Mail-Adresse eingeben") - if not self._fill_email_field(account_data["email"]): - logger.error("Fehler beim Ausfüllen des E-Mail-Feldes") - return False - - # SCHRITT 2: Code senden Button klicken (VOR Passwort!) - logger.info("SCHRITT 2/6: Code senden Button klicken") - if not self._click_send_code_button(): - logger.error("Fehler beim Klicken des Code-senden-Buttons") - return False - - # SCHRITT 3: Verifizierungscode empfangen und eingeben - logger.info("SCHRITT 3/6: Auf Code warten und eingeben") - if not self._handle_email_verification(account_data["email"]): - logger.error("Fehler bei der E-Mail-Verifizierung") - return False - - # SCHRITT 4: Passwort-Feld ausfüllen (NACH Code-Eingabe!) - logger.info("SCHRITT 4/6: Passwort eingeben (nach Code-Verifizierung)") - if not self._fill_password_field(account_data["password"]): - logger.error("Fehler beim Ausfüllen des Passwort-Feldes") - return False - - # SCHRITT 5: Dummy-Input-Trick anwenden - logger.info("SCHRITT 5/6: Dummy-Input-Trick anwenden") - if not self._apply_dummy_input_trick(): - logger.error("Fehler beim Dummy-Input-Trick") - return False - - # SCHRITT 6: Weiter Button klicken - logger.info("SCHRITT 6/6: Weiter Button klicken") - if not self._click_continue_button(): - logger.error("Fehler beim Klicken des Weiter-Buttons") - return False - - logger.info("=== E-MAIL-WORKFLOW ERFOLGREICH ABGESCHLOSSEN ===") - - # Kurze Pause für UI-Updates - das Weiter-Button sollte jetzt aktiviert sein - self.automation.human_behavior.random_delay(1.0, 2.0) - - return True - - except Exception as e: - logger.error(f"Fehler im E-Mail-Workflow: {e}") - return False - - def _fill_email_field(self, email: str) -> bool: - """ - Füllt das E-Mail-Feld mit robusten Selektoren aus. - - Args: - email: E-Mail-Adresse - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Robuste E-Mail-Feld-Selektoren (in Prioritätsreihenfolge) - email_selectors = [ - "input[placeholder*='E-Mail']", - "input[placeholder*='Email']", - "input[type='email']", - "input[name='email']", - "input[aria-label*='Email']", - "input[aria-label*='E-Mail']", - self.selectors.EMAIL_FIELD, - self.selectors.EMAIL_FIELD_ALT - ] - - for i, selector in enumerate(email_selectors): - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - success = self.automation.browser.fill_form_field(selector, email, human_typing=True) - if success: - logger.info(f"E-Mail-Feld erfolgreich ausgefüllt mit Selektor {i+1}: {email}") - self.automation.human_behavior.random_delay(0.5, 1.0) - return True - except Exception as e: - logger.debug(f"E-Mail-Selektor {i+1} fehlgeschlagen: {e}") - continue - - # Fallback: Fuzzy-Matching - success = self.automation.ui_helper.fill_field_fuzzy( - ["E-Mail-Adresse", "Email", "E-Mail"], - email, - email_selectors[0] - ) - - if success: - logger.info(f"E-Mail-Feld über Fuzzy-Matching ausgefüllt: {email}") - return True - - logger.error("Konnte E-Mail-Feld mit keinem Selektor ausfüllen") - return False - - except Exception as e: - logger.error(f"Fehler beim Ausfüllen des E-Mail-Feldes: {e}") - return False - - def _fill_password_field(self, password: str) -> bool: - """ - Füllt das Passwort-Feld mit robusten Selektoren aus. - - Args: - password: Passwort - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Robuste Passwort-Feld-Selektoren (in Prioritätsreihenfolge) - password_selectors = [ - "input[type='password'][placeholder*='Passwort']", - "input[type='password'][placeholder*='Password']", - "input[type='password']", - "input[name='password']", - "input[aria-label*='Password']", - "input[aria-label*='Passwort']", - self.selectors.PASSWORD_FIELD, - self.selectors.PASSWORD_FIELD_ALT - ] - - for i, selector in enumerate(password_selectors): - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - success = self.automation.browser.fill_form_field(selector, password, human_typing=True) - if success: - logger.info(f"Passwort-Feld erfolgreich ausgefüllt mit Selektor {i+1}") - self.automation.human_behavior.random_delay(0.5, 1.0) - return True - except Exception as e: - logger.debug(f"Passwort-Selektor {i+1} fehlgeschlagen: {e}") - continue - - # Fallback: Fuzzy-Matching - success = self.automation.ui_helper.fill_field_fuzzy( - ["Passwort", "Password"], - password, - password_selectors[0] - ) - - if success: - logger.info("Passwort-Feld über Fuzzy-Matching ausgefüllt") - return True - - logger.error("Konnte Passwort-Feld mit keinem Selektor ausfüllen") - return False - - except Exception as e: - logger.error(f"Fehler beim Ausfüllen des Passwort-Feldes: {e}") - return False - - def _click_send_code_button(self) -> bool: - """ - Klickt den 'Code senden'-Button mit robusten Selektoren. - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Kurze Pause vor dem Klicken - self.automation.human_behavior.random_delay(0.5, 1.0) - - # Robuste Send-Code-Button-Selektoren - send_code_selectors = [ - "button[data-e2e='send-code-button']", - "button:has-text('Code senden')", - "button:has-text('Send code')", - "button[type='submit']", - "button.css-10nhlj9-Button-StyledButton", - self.selectors.SEND_CODE_BUTTON - ] - - for i, selector in enumerate(send_code_selectors): - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - # Prüfe, ob Button enabled ist - element = self.automation.browser.wait_for_selector(selector, timeout=1000) - if element: - is_disabled = element.get_attribute("disabled") - if is_disabled: - logger.debug(f"Send-Code-Button {i+1} ist disabled, versuche nächsten") - continue - - success = self.automation.browser.click_element(selector) - if success: - logger.info(f"'Code senden'-Button erfolgreich geklickt mit Selektor {i+1}") - self.automation.human_behavior.random_delay(1.0, 2.0) - return True - except Exception as e: - logger.debug(f"Send-Code-Selektor {i+1} fehlgeschlagen: {e}") - continue - - # Fallback: Fuzzy-Button-Matching - success = self.automation.ui_helper.click_button_fuzzy( - ["Code senden", "Send code", "Senden"], - send_code_selectors[0] - ) - - if success: - logger.info("'Code senden'-Button über Fuzzy-Matching geklickt") - return True - - logger.error("Konnte 'Code senden'-Button mit keinem Selektor klicken") - return False - - except Exception as e: - logger.error(f"Fehler beim Klicken des 'Code senden'-Buttons: {e}") - return False - - def _handle_email_verification(self, email: str) -> bool: - """ - Behandelt die E-Mail-Verifizierung mit verbessertem Timing. - - Args: - email: E-Mail-Adresse - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - logger.info("Warte auf E-Mail-Verifizierungscode...") - - # Warte auf den Code mit exponential backoff - verification_code = self._get_email_verification_code_with_retry(email) - - if not verification_code: - logger.error("Konnte keinen Verifizierungscode empfangen") - return False - - logger.info(f"Verifizierungscode empfangen: {verification_code}") - - # Code-Feld ausfüllen - if not self._fill_verification_code_field(verification_code): - logger.error("Konnte Verifizierungscode-Feld nicht ausfüllen") - return False - - logger.info("Verifizierungscode erfolgreich eingegeben") - - # Kurze Pause nach Code-Eingabe - self.automation.human_behavior.random_delay(1.0, 2.0) - - return True - - except Exception as e: - logger.error(f"Fehler bei der E-Mail-Verifizierung: {e}") - return False - - def _get_email_verification_code_with_retry(self, email: str, max_attempts: int = 30) -> Optional[str]: - """ - Ruft den E-Mail-Verifizierungscode mit Retry-Logik ab. - - Args: - email: E-Mail-Adresse - max_attempts: Maximale Anzahl Versuche - - Returns: - Optional[str]: Verifizierungscode oder None - """ - try: - for attempt in range(max_attempts): - # Exponential backoff: 2s, 3s, 4.5s, 6.75s, ... (max 30s) - delay = min(2 * (1.5 ** attempt), 30) - - logger.debug(f"E-Mail-Abruf Versuch {attempt + 1}/{max_attempts} (Wartezeit: {delay:.1f}s)") - - # Versuche Code abzurufen - code = self.automation.email_handler.get_verification_code( - target_email=email, - platform="tiktok", - max_attempts=1, # Nur ein Versuch pro Iteration - delay_seconds=1 - ) - - if code: - logger.info(f"E-Mail-Code nach {attempt + 1} Versuchen empfangen") - return code - - # Warte vor nächstem Versuch - time.sleep(delay) - - logger.warning(f"Kein E-Mail-Code nach {max_attempts} Versuchen empfangen") - return None - - except Exception as e: - logger.error(f"Fehler beim E-Mail-Code-Abruf: {e}") - return None - - def _fill_verification_code_field(self, code: str) -> bool: - """ - Füllt das Verifizierungscode-Feld aus. - - Args: - code: Verifizierungscode - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - # Robuste Verifizierungscode-Feld-Selektoren - code_selectors = [ - "input[placeholder*='sechsstelligen Code']", - "input[placeholder*='verification code']", - "input[placeholder*='Code']", - "input[name='verificationCode']", - "input[type='text'][maxlength='6']", - self.selectors.VERIFICATION_CODE_FIELD, - self.selectors.VERIFICATION_CODE_FIELD_ALT - ] - - for i, selector in enumerate(code_selectors): - try: - if self.automation.browser.is_element_visible(selector, timeout=3000): - # Normale Code-Eingabe (Dummy-Trick wird separat angewendet) - success = self.automation.browser.fill_form_field(selector, code, human_typing=True) - if success: - logger.info(f"Verifizierungscode-Feld erfolgreich ausgefüllt mit Selektor {i+1}") - return True - except Exception as e: - logger.debug(f"Code-Selektor {i+1} fehlgeschlagen: {e}") - continue - - # Fallback: Fuzzy-Matching - success = self.automation.ui_helper.fill_field_fuzzy( - ["Gib den sechsstelligen Code ein", "Enter verification code", "Verification code"], - code, - code_selectors[0] - ) - - if success: - logger.info("Verifizierungscode-Feld über Fuzzy-Matching ausgefüllt") - return True - - logger.error("Konnte Verifizierungscode-Feld mit keinem Selektor ausfüllen") - return False - - except Exception as e: - logger.error(f"Fehler beim Ausfüllen des Verifizierungscode-Feldes: {e}") - return False - - def _execute_phone_workflow(self, account_data: Dict[str, Any]) -> bool: - """ - Führt den Telefon-Registrierungsworkflow aus. - - Args: - account_data: Account-Daten - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - logger.info("=== STARTE TELEFON-WORKFLOW ===") - - # Telefonnummer aufbereiten - phone_number = account_data["phone"] - if phone_number.startswith("+"): - parts = phone_number.split(" ", 1) - if len(parts) > 1: - phone_number = parts[1] - - # Telefonnummer eingeben - phone_success = self.automation.ui_helper.fill_field_fuzzy( - ["Telefonnummer", "Phone number", "Phone"], - phone_number, - self.selectors.PHONE_FIELD - ) - - if not phone_success: - logger.error("Konnte Telefonnummer-Feld nicht ausfüllen") - return False - - logger.info(f"Telefonnummer-Feld ausgefüllt: {phone_number}") - self.automation.human_behavior.random_delay(0.5, 1.5) - - # Code senden - if not self._click_send_code_button(): - return False - - # SMS-Code behandeln (Platzhalter) - logger.warning("SMS-Verifizierung ist noch nicht vollständig implementiert") - - return True - - except Exception as e: - logger.error(f"Fehler im Telefon-Workflow: {e}") - return False - - # Hilfsmethoden für die Basis-Funktionalität - def _validate_registration_inputs(self, full_name: str, age: int, - registration_method: str, phone_number: str) -> bool: - """Validiert die Eingaben für die Registrierung.""" - if not full_name or len(full_name) < 3: - logger.error("Ungültiger vollständiger Name") - return False - - if age < 13: - logger.error("Benutzer muss mindestens 13 Jahre alt sein") - return False - - if registration_method not in ["email", "phone"]: - logger.error(f"Ungültige Registrierungsmethode: {registration_method}") - return False - - if registration_method == "phone" and not phone_number: - logger.error("Telefonnummer erforderlich für Registrierung via Telefon") - return False - - return True - - def _generate_account_data(self, full_name: str, age: int, registration_method: str, - phone_number: str, **kwargs) -> Dict[str, Any]: - """Generiert Account-Daten für die Registrierung.""" - # Benutzername generieren - username = kwargs.get("username") - if not username: - username = self.automation.username_generator.generate_username("tiktok", full_name) - - # Passwort generieren - password = kwargs.get("password") - if not password: - password = self.automation.password_generator.generate_password("tiktok") - - # E-Mail generieren (falls nötig) - email = None - if registration_method == "email": - email_prefix = username.lower().replace(".", "").replace("_", "") - email = f"{email_prefix}@{self.automation.email_domain}" - - # Geburtsdatum generieren - birthday = self.automation.birthday_generator.generate_birthday_components("tiktok", age) - - # Account-Daten zusammenstellen - account_data = { - "username": username, - "password": password, - "full_name": full_name, - "email": email, - "phone": phone_number, - "birthday": birthday, - "age": age, - "registration_method": registration_method - } - - logger.debug(f"Account-Daten generiert: {account_data['username']}") - return account_data - - def _create_error_result(self, error_msg: str, stage: str, account_data: Dict[str, Any]) -> Dict[str, Any]: - """Erstellt ein standardisiertes Fehler-Result.""" - return { - "success": False, - "error": error_msg, - "stage": stage, - "account_data": account_data - } - - # Platzhalter für weitere Methoden (Navigation, etc.) - def _navigate_to_homepage(self) -> bool: - """Navigiert zur TikTok-Startseite.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _handle_cookie_banner(self) -> bool: - """Behandelt den Cookie-Banner.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _click_login_button(self) -> bool: - """Klickt auf den Anmelden-Button.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _click_register_link(self) -> bool: - """Klickt auf den Registrieren-Link.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _click_phone_email_option(self) -> bool: - """Klickt auf die Telefon/E-Mail-Option.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _select_registration_method(self, method: str) -> bool: - """Wählt die Registrierungsmethode aus.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _enter_birthday(self, birthday: Dict[str, Any]) -> bool: - """Gibt das Geburtsdatum ein.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _create_username(self, account_data: Dict[str, Any]) -> bool: - """Erstellt einen Benutzernamen.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _check_registration_success(self) -> bool: - """Überprüft, ob die Registrierung erfolgreich war.""" - # Diese Methode würde aus der ursprünglichen Implementierung übernommen - return True - - def _apply_dummy_input_trick(self) -> bool: - """ - Wendet den Dummy-Input-Trick auf das Code-Feld an. - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - logger.debug("Wende Dummy-Input-Trick an") - - # Code-Feld-Selektoren - code_selectors = [ - "input[placeholder*='sechsstelligen Code']", - "input[placeholder*='verification code']", - "input[placeholder*='Code']", - "input[name='verificationCode']", - "input[type='text'][maxlength='6']", - self.selectors.VERIFICATION_CODE_FIELD, - self.selectors.VERIFICATION_CODE_FIELD_ALT - ] - - for i, selector in enumerate(code_selectors): - try: - if self.automation.browser.is_element_visible(selector, timeout=2000): - # Dummy-Input-Trick anwenden - success = self.automation.browser.fill_form_field_with_dummy_trick( - selector, "123456", timeout=3000 - ) - - if success: - logger.info(f"Dummy-Input-Trick erfolgreich angewendet mit Selektor {i+1}") - self.automation.human_behavior.random_delay(0.5, 1.0) - return True - - except Exception as e: - logger.debug(f"Dummy-Input-Trick Selektor {i+1} fehlgeschlagen: {e}") - continue - - logger.warning("Dummy-Input-Trick konnte nicht angewendet werden") - return True # Nicht kritisch - fortfahren - - except Exception as e: - logger.error(f"Kritischer Fehler beim Dummy-Input-Trick: {e}") - return True # Nicht kritisch - fortfahren - - def _click_continue_button(self) -> bool: - """ - Klickt den Weiter/Continue-Button mit robusten Selektoren. - - Returns: - bool: True bei Erfolg, False bei Fehler - """ - try: - logger.debug("Klicke Weiter-Button") - - # Robuste Continue-Button-Selektoren - continue_selectors = [ - "button[data-e2e='continue-button']", - "button:has-text('Weiter')", - "button:has-text('Continue')", - "button:has-text('Fortfahren')", - "button[type='submit']", - "button.css-10nhlj9-Button-StyledButton:not([disabled])" - ] - - for i, selector in enumerate(continue_selectors): - try: - if self.automation.browser.is_element_visible(selector, timeout=3000): - # Prüfe, ob Button enabled ist - element = self.automation.browser.wait_for_selector(selector, timeout=1000) - if element: - is_disabled = element.get_attribute("disabled") - aria_disabled = element.get_attribute("aria-disabled") - - if is_disabled or aria_disabled == "true": - logger.debug(f"Continue-Button {i+1} ist disabled, versuche nächsten") - continue - - # Button klicken - success = self.automation.browser.click_element(selector) - if success: - logger.info(f"Weiter-Button erfolgreich geklickt mit Selektor {i+1}") - self.automation.human_behavior.random_delay(0.5, 1.0) - return True - - except Exception as e: - logger.debug(f"Continue-Selektor {i+1} fehlgeschlagen: {e}") - continue - - # Fallback: Fuzzy-Button-Matching - try: - success = self.automation.ui_helper.click_button_fuzzy( - ["Weiter", "Continue", "Fortfahren", "Next"], - continue_selectors[0] - ) - - if success: - logger.info("Weiter-Button über Fuzzy-Matching geklickt") - self.automation.human_behavior.random_delay(0.5, 1.0) - return True - - except Exception as e: - logger.debug(f"Continue Fuzzy-Matching fehlgeschlagen: {e}") - - logger.error("Weiter-Button konnte nicht geklickt werden") - return False - - except Exception as e: - logger.error(f"Kritischer Fehler beim Weiter-Button: {e}") - return False \ No newline at end of file diff --git a/social_networks/twitter/__init__.py b/social_networks/twitter/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/social_networks/twitter/twitter_automation.py b/social_networks/twitter/twitter_automation.py deleted file mode 100644 index e69de29..0000000 diff --git a/social_networks/twitter/twitter_login.py b/social_networks/twitter/twitter_login.py deleted file mode 100644 index e69de29..0000000 diff --git a/social_networks/twitter/twitter_registration.py b/social_networks/twitter/twitter_registration.py deleted file mode 100644 index e69de29..0000000 diff --git a/social_networks/twitter/twitter_selectors.py b/social_networks/twitter/twitter_selectors.py deleted file mode 100644 index e69de29..0000000 diff --git a/social_networks/twitter/twitter_ui_helper.py b/social_networks/twitter/twitter_ui_helper.py deleted file mode 100644 index e69de29..0000000 diff --git a/social_networks/twitter/twitter_utils.py b/social_networks/twitter/twitter_utils.py deleted file mode 100644 index e69de29..0000000 diff --git a/social_networks/twitter/twitter_verification.py b/social_networks/twitter/twitter_verification.py deleted file mode 100644 index e69de29..0000000 diff --git a/social_networks/twitter/twitter_workflow.py b/social_networks/twitter/twitter_workflow.py deleted file mode 100644 index e69de29..0000000 diff --git a/utils/update_checker.py b/utils/update_checker.py deleted file mode 100644 index ccbd274..0000000 --- a/utils/update_checker.py +++ /dev/null @@ -1,731 +0,0 @@ - -Alle Projekte -Chimaira -Privat -Projektziel Das Hauptziel des Projekts ist die Entwicklung einer benutzerfreundlichen Software zur automatisierten Erstellung von Social-Media-Accounts. Die Anwendung ermöglicht es Benutzern, Konten für verschiedene Plattformen (Instagram, Facebook, Twitter, TikTok) mit minimaler manueller Intervention zu erstellen, zu verwalten und zu exportieren. Kernfunktionalitäten Automatisierte Account-Erstellung: Erstellen von Benutzerkonten für verschiedene Social-Media-Plattformen Proxy-Unterstützung: Verwendung von Proxies für anonyme Verbindungen und zur Umgehung von Einschränkungen E-Mail-Integration: Automatische Verarbeitung von Bestätigungscodes Datenbankintegration: Speichern und Verwalten erstellter Konten Benutzerfreundliche GUI: Intuitive Benutzeroberfläche mit Dark Mode Robuste Automatisierung: OCR-Fallback-Mechanismen für UI-Änderungen Code-Playwright/ # Social-Media-Account-Generator/ ├── config/ # Konfigurationsverzeichnis │ ├── browser_config.json │ ├── email_config.json │ ├── facebook_config.json │ ├── instagram_config.json │ ├── license_config.json │ ├── proxy_config.json │ ├── stealth_config.json │ ├── tiktok_config.json │ ├── twitter_config.json │ ├── update_config.json │ └── user_agents.json ├── controllers/ # Controller-Logik │ ├── account_controller.py │ ├── main_controller.py │ ├── settings_controller.py │ └── platform_controllers/ │ ├── base_controller.py │ ├── instagram_controller.py │ ├── facebook_controller.py (nicht implementiert) │ ├── twitter_controller.py (nicht implementiert) │ └── tiktok_controller.py (nicht implementiert) ├── database/ # Datenbankfunktionalität │ └── db_manager.py ├── licensing/ # Lizenzverwaltung │ ├── license_manager.py │ └── license_validator.py ├── logs/ # Log-Verzeichnis │ └── screenshots/ # Screenshots für OCR-Fallbacks ├── ocr/ # OCR-Funktionalität │ ├── fallback_actions.py │ ├── screenshot.py │ └── text_detector.py ├── resources/ # Ressourcen │ ├── icons/ # Icons für die UI │ │ ├── instagram.svg │ │ ├── facebook.svg │ │ ├── twitter.svg │ │ ├── tiktok.svg │ │ └── [andere Icons] │ └── themes/ # Theme-Ressourcen │ ├── dark.qss │ └── light.qss ├── social_networks/ # Social-Media-Automatisierung │ ├── base_automation.py │ ├── instagram/ │ │ ├── instagram_automation.py │ │ ├── instagram_selectors.py │ │ └── instagram_workflow.py │ ├── facebook/ # Noch nicht implementiert │ ├── twitter/ # Noch nicht implementiert │ └── tiktok/ # Noch nicht implementiert ├── updates/ # Update-Funktionalität │ └── update_checker.py ├── utils/ # Hilfsfunktionen │ ├── birthday_generator.py │ ├── email_handler.py │ ├── human_behavior.py │ ├── logger.py │ ├── password_generator.py │ ├── proxy_rotator.py │ ├── theme_manager.py │ └── username_generator.py ├── views/ # UI-Komponenten │ ├── main_window.py │ ├── platform_selector.py │ └── tabs/ │ ├── about_tab.py │ ├── accounts_tab.py │ ├── generator_tab.py │ └── settings_tab.py ├── browser/ # Browser-Automatisierung │ ├── playwright_manager.py │ └── stealth_config.py └── main.py # Haupteinstiegspunkt Kernstruktur und MVC-Framework: Grundlegendes MVC-Muster mit klarer Trennung von Daten, Ansicht und Logik Signale und Slots für die Kommunikation zwischen Komponenten Zentrale Logging-Funktionalität Benutzeroberfläche: Hauptfenster mit Plattformauswahl Plattformspezifische Tabs (Generator, Konten, Einstellungen, Über) Dark Mode für alle UI-Komponenten Utility-Klassen: Logger mit GUI-Integration Proxy-Rotator mit Testoption E-Mail-Handler für Verifizierungscodes Passwort-, Benutzernamen- und Geburtsdatumsgeneratoren Human-Behavior-Simulation für natürliche Verzögerungen Lizenzmanager Update-Checker Datenbankintegration: SQLite-Datenbankmanager für Account-Speicherung Import- und Exportfunktionen Suchfunktionen Instagram-Integration: Basis-Automation und Instagram-spezifische Logik Account-Generator-Workflow Stealth-Funktionalität zur Umgehung von Bot-Erkennung Noch ausstehende Aufgaben Plattform-Integration: Implementierung der Facebook-Automatisierung Implementierung der Twitter-Automatisierung Implementierung der TikTok-Automatisierung Model-Klassen: Entwicklung der Datenmodelle für Accounts und Plattformen Integration in die Controller-Logik OCR-Integration: Anpassung der OCR-Komponenten an die neue MVC-Struktur Verbesserung der Fallback-Mechanismen für UI-Änderungen Plattformspezifische Controller: Implementierung der Facebook-, Twitter- und TikTok-Controller Anpassung an spezifische Anforderungen jeder Plattform Erweiterte Funktionen: CAPTCHA-Behandlung mit externen Diensten oder manueller Eingabe SMS-Verifizierung mit SMS-Empfangsdiensten Verbesserte Fehlerbehandlung und Wiederherstellung Tests: Entwicklung von Unit-Tests für die Kernkomponenten End-to-End-Tests für den gesamten Workflow Performance-Optimierungen: Multi-Threading für parallele Account-Erstellung Optimierte Ressourcennutzung bei längeren Automatisierungen Zusammenfassung der Refaktorierung Das ursprüngliche, monolithische Design wurde zu einer modularen MVC-Architektur umgestaltet, die folgende Vorteile bietet: Verbesserte Wartbarkeit: Kleinere, spezialisierte Dateien statt einer großen main.py Einfachere Erweiterbarkeit: Neue Plattformen können durch Ableitung von Basisklassen hinzugefügt werden Bessere Testbarkeit: Komponenten können isoliert getestet werden Wiederverwendbarkeit: Gemeinsame Funktionalität in Basisklassen extrahiert Klare Verantwortlichkeiten: Jede Komponente hat eine spezifische Aufgabe Die größte Verbesserung ist die klare Trennung von Benutzeroberfläche, Geschäftslogik und Datenmanagement, was die Wartung und Erweiterung erheblich erleichtert und einen strukturierten Rahmen für die Implementierung weiterer Plattformen schafft. Nächste Schritte Die nächsten unmittelbaren Schritte sind: Implementierung der Datenmodelle zur Vervollständigung der MVC-Struktur Entwicklung der weiteren plattformspezifischen Controller Anpassung der bestehenden Automatisierungslogik an die neue Struktur Erstellung von Grundtests für die Kernfunktionalität. Nimm für die Pfade NIEMALS absolute Pfade, sondern IMMER relative Pfade - - - - -Unbenannt -Letzte Nachricht vor 21 Sekunden -Deprecation of generate_birthday() function -Letzte Nachricht vor 10 Minuten -Playwright Cookie Banner and Date Parsing Issues -Letzte Nachricht vor 30 Minuten -Troubleshooting Python script error with HumanBehavior class -Letzte Nachricht vor 2 Stunden -Troubleshooting Python code error with Instagram automation -Letzte Nachricht vor 2 Stunden -Troubleshooting Instagram Automation Error -Letzte Nachricht vor 3 Stunden -Modular Localization System for Multilingual App -Letzte Nachricht vor 4 Stunden -Instagram Account Creation Error -Letzte Nachricht vor 1 Tag -Code and Icon Structure Review for Social Media Account Generator -Letzte Nachricht vor 1 Tag -Projektwissen -57 % der Kapazität der Wissensdatenbank genutzt - -instagram_automation.py -1.080 Zeilen - -py - - - -instagram_automation.py -1.075 Zeilen - -py - - - -human_behavior.py -488 Zeilen - -py - - - -main_window.py -168 Zeilen - -py - - - -platform_selector.py -96 Zeilen - -py - - - -platform_button.py -77 Zeilen - -py - - - -theme_manager.py -133 Zeilen - -py - - - -main.py -49 Zeilen - -py - - - -main_controller.py -226 Zeilen - -py - - - -light.qss -255 Zeilen - -text - - - -dark.qss -190 Zeilen - -text - - - -theme.json -47 Zeilen - -json - - - -birthday_generator.py -299 Zeilen - -py - - - -username_generator.py -426 Zeilen - -py - - - -stealth_config.py -216 Zeilen - -py - - - -playwright_manager.py -517 Zeilen - -py - - - -user_agents.json -31 Zeilen - -json - - - -update_config.json -9 Zeilen - -json - - - -stealth_config.json -14 Zeilen - -json - - - -proxy_config.json -15 Zeilen - -json - - - -license_config.json -9 Zeilen - -json - - - -email_config.json -6 Zeilen - -json - - - -instagram_controller.py -186 Zeilen - -py - - - -base_controller.py -130 Zeilen - -py - - - -settings_controller.py -295 Zeilen - -py - - - -account_controller.py -150 Zeilen - -py - - - -license_validator.py -304 Zeilen - -py - - - -license_manager.py -450 Zeilen - -py - - - -instagram_workflow.py -315 Zeilen - -py - - - -instagram_selectors.py -113 Zeilen - -py - - - -base_automation.py -329 Zeilen - -py - - - -version.py -193 Zeilen - -py - - - -update_checker.py -411 Zeilen - -py - - - -proxy_rotator.py -347 Zeilen - -py - - - -password_generator.py -338 Zeilen - -py - - - -logger.py -70 Zeilen - -py - - - -email_handler.py -410 Zeilen - -py - - - -about_tab.py -64 Zeilen - -py - - - -settings_tab.py -302 Zeilen - -py - - - -generator_tab.py -278 Zeilen - -py - - - -accounts_tab.py -138 Zeilen - -py - - -update_checker.py - -Die Formatierung kann von der Quelle abweichen - -""" -Update-Checking-Funktionalität für den Social Media Account Generator. -""" - -import os -import json -import logging -import requests -import shutil -from datetime import datetime -from typing import Dict, List, Any, Optional, Tuple, Union - -logger = logging.getLogger("update_checker") - -class UpdateChecker: - """Klasse zum Überprüfen und Herunterladen von Updates.""" - - CONFIG_FILE = os.path.join("config", "app_version.json") - UPDATE_SERVER_URL = "https://api.example.com/updates" # Platzhalter - in der Produktion anpassen - - def __init__(self): - """Initialisiert den UpdateChecker und lädt die Konfiguration.""" - self.version_info = self.load_version_info() - - # Stelle sicher, dass das Konfigurationsverzeichnis existiert - os.makedirs(os.path.dirname(self.CONFIG_FILE), exist_ok=True) - - # Updates-Verzeichnis für Downloads - os.makedirs("updates", exist_ok=True) - - def load_version_info(self) -> Dict[str, Any]: - """Lädt die Versionsinformationen aus der Konfigurationsdatei.""" - if not os.path.exists(self.CONFIG_FILE): - default_info = { - "current_version": "1.0.0", - "last_check": "", - "channel": "stable", - "auto_check": True, - "auto_download": False - } - - # Standardwerte speichern - try: - with open(self.CONFIG_FILE, "w", encoding="utf-8") as f: - json.dump(default_info, f, indent=2) - except Exception as e: - logger.error(f"Fehler beim Speichern der Standardversionsinformationen: {e}") - - return default_info - - try: - with open(self.CONFIG_FILE, "r", encoding="utf-8") as f: - version_info = json.load(f) - - logger.info(f"Versionsinformationen geladen: {version_info.get('current_version', 'unbekannt')}") - - return version_info - except Exception as e: - logger.error(f"Fehler beim Laden der Versionsinformationen: {e}") - return { - "current_version": "1.0.0", - "last_check": "", - "channel": "stable", - "auto_check": True, - "auto_download": False - } - - def save_version_info(self) -> bool: - """Speichert die Versionsinformationen in die Konfigurationsdatei.""" - try: - with open(self.CONFIG_FILE, "w", encoding="utf-8") as f: - json.dump(self.version_info, f, indent=2) - - logger.info("Versionsinformationen gespeichert") - return True - except Exception as e: - logger.error(f"Fehler beim Speichern der Versionsinformationen: {e}") - return False - - def compare_versions(self, version1: str, version2: str) -> int: - """ - Vergleicht zwei Versionsstrings (semver). - - Args: - version1: Erste Version - version2: Zweite Version - - Returns: - -1, wenn version1 < version2 - 0, wenn version1 == version2 - 1, wenn version1 > version2 - """ - v1_parts = [int(part) for part in version1.split(".")] - v2_parts = [int(part) for part in version2.split(".")] - - # Fülle fehlende Teile mit Nullen auf - while len(v1_parts) < 3: - v1_parts.append(0) - while len(v2_parts) < 3: - v2_parts.append(0) - - # Vergleiche die Teile - for i in range(3): - if v1_parts[i] < v2_parts[i]: - return -1 - elif v1_parts[i] > v2_parts[i]: - return 1 - - return 0 - - def check_for_updates(self, force: bool = False) -> Dict[str, Any]: - """ - Überprüft, ob Updates verfügbar sind. - - Args: - force: Erzwingt eine Überprüfung, auch wenn erst kürzlich geprüft wurde - - Returns: - Dictionary mit Update-Informationen - """ - result = { - "has_update": False, - "current_version": self.version_info["current_version"], - "latest_version": self.version_info["current_version"], - "release_date": "", - "release_notes": "", - "download_url": "", - "error": "" - } - - # Prüfe, ob seit der letzten Überprüfung genügend Zeit vergangen ist (24 Stunden) - if not force and self.version_info.get("last_check"): - try: - last_check = datetime.fromisoformat(self.version_info["last_check"]) - now = datetime.now() - - # Wenn weniger als 24 Stunden seit der letzten Überprüfung vergangen sind - if (now - last_check).total_seconds() < 86400: - logger.info("Update-Überprüfung übersprungen (letzte Überprüfung vor weniger als 24 Stunden)") - return result - except Exception as e: - logger.warning(f"Fehler beim Parsen des letzten Überprüfungsdatums: {e}") - - try: - # Simuliere eine Online-Überprüfung für Entwicklungszwecke - # In der Produktion sollte eine echte API-Anfrage implementiert werden - # response = requests.get( - # f"{self.UPDATE_SERVER_URL}/check", - # params={ - # "version": self.version_info["current_version"], - # "channel": self.version_info["channel"] - # }, - # timeout=10 - # ) - - # For demonstration purposes only - latest_version = "1.1.0" - has_update = self.compare_versions(self.version_info["current_version"], latest_version) < 0 - - if has_update: - result["has_update"] = True - result["latest_version"] = latest_version - result["release_date"] = "2025-05-01" - result["release_notes"] = ( - "Version 1.1.0:\n" - "- Unterstützung für Facebook-Accounts hinzugefügt\n" - "- Verbesserte Proxy-Rotation\n" - "- Bessere Fehlerbehandlung bei der Account-Erstellung\n" - "- Verschiedene Bugfixes und Leistungsverbesserungen" - ) - result["download_url"] = f"{self.UPDATE_SERVER_URL}/download/v1.1.0" - - # Update der letzten Überprüfung - self.version_info["last_check"] = datetime.now().isoformat() - self.save_version_info() - - logger.info(f"Update-Überprüfung abgeschlossen: {result['latest_version']} verfügbar") - - return result - - except requests.RequestException as e: - error_msg = f"Netzwerkfehler bei der Update-Überprüfung: {e}" - logger.error(error_msg) - result["error"] = error_msg - return result - except Exception as e: - error_msg = f"Unerwarteter Fehler bei der Update-Überprüfung: {e}" - logger.error(error_msg) - result["error"] = error_msg - return result - - def download_update(self, download_url: str, version: str) -> Dict[str, Any]: - """ - Lädt ein Update herunter. - - Args: - download_url: URL zum Herunterladen des Updates - version: Version des Updates - - Returns: - Dictionary mit Download-Informationen - """ - result = { - "success": False, - "file_path": "", - "version": version, - "error": "" - } - - try: - # Zieldateiname erstellen - file_name = f"update_v{version}.zip" - file_path = os.path.join("updates", file_name) - - # Simuliere einen Download für Entwicklungszwecke - # In der Produktion sollte ein echter Download implementiert werden - - # response = requests.get(download_url, stream=True, timeout=60) - # if response.status_code == 200: - # with open(file_path, "wb") as f: - # shutil.copyfileobj(response.raw, f) - - # Simulierter Download (erstelle eine leere Datei) - with open(file_path, "w") as f: - f.write(f"Placeholder for version {version} update") - - result["success"] = True - result["file_path"] = file_path - - logger.info(f"Update v{version} heruntergeladen: {file_path}") - - return result - - except requests.RequestException as e: - error_msg = f"Netzwerkfehler beim Herunterladen des Updates: {e}" - logger.error(error_msg) - result["error"] = error_msg - return result - except Exception as e: - error_msg = f"Unerwarteter Fehler beim Herunterladen des Updates: {e}" - logger.error(error_msg) - result["error"] = error_msg - return result - - def is_update_available(self) -> bool: - """ - Überprüft, ob ein Update verfügbar ist. - - Returns: - True, wenn ein Update verfügbar ist, sonst False - """ - update_info = self.check_for_updates() - return update_info["has_update"] - - def get_current_version(self) -> str: - """ - Gibt die aktuelle Version zurück. - - Returns: - Aktuelle Version - """ - return self.version_info["current_version"] - - def set_current_version(self, version: str) -> bool: - """ - Setzt die aktuelle Version. - - Args: - version: Neue Version - - Returns: - True bei Erfolg, False im Fehlerfall - """ - try: - self.version_info["current_version"] = version - return self.save_version_info() - except Exception as e: - logger.error(f"Fehler beim Setzen der aktuellen Version: {e}") - return False - - def set_update_channel(self, channel: str) -> bool: - """ - Setzt den Update-Kanal (stable, beta, dev). - - Args: - channel: Update-Kanal - - Returns: - True bei Erfolg, False im Fehlerfall - """ - if channel not in ["stable", "beta", "dev"]: - logger.warning(f"Ungültiger Update-Kanal: {channel}") - return False - - try: - self.version_info["channel"] = channel - return self.save_version_info() - except Exception as e: - logger.error(f"Fehler beim Setzen des Update-Kanals: {e}") - return False - - def get_update_channel(self) -> str: - """ - Gibt den aktuellen Update-Kanal zurück. - - Returns: - Update-Kanal (stable, beta, dev) - """ - return self.version_info.get("channel", "stable") - - def set_auto_check(self, auto_check: bool) -> bool: - """ - Aktiviert oder deaktiviert die automatische Update-Überprüfung. - - Args: - auto_check: True, um automatische Updates zu aktivieren, False zum Deaktivieren - - Returns: - True bei Erfolg, False im Fehlerfall - """ - try: - self.version_info["auto_check"] = bool(auto_check) - return self.save_version_info() - except Exception as e: - logger.error(f"Fehler beim Setzen der automatischen Update-Überprüfung: {e}") - return False - - def is_auto_check_enabled(self) -> bool: - """ - Überprüft, ob die automatische Update-Überprüfung aktiviert ist. - - Returns: - True, wenn die automatische Update-Überprüfung aktiviert ist, sonst False - """ - return self.version_info.get("auto_check", True) - - def set_auto_download(self, auto_download: bool) -> bool: - """ - Aktiviert oder deaktiviert den automatischen Download von Updates. - - Args: - auto_download: True, um automatische Downloads zu aktivieren, False zum Deaktivieren - - Returns: - True bei Erfolg, False im Fehlerfall - """ - try: - self.version_info["auto_download"] = bool(auto_download) - return self.save_version_info() - except Exception as e: - logger.error(f"Fehler beim Setzen des automatischen Downloads: {e}") - return False - - def is_auto_download_enabled(self) -> bool: - """ - Überprüft, ob der automatische Download von Updates aktiviert ist. - - Returns: - True, wenn der automatische Download aktiviert ist, sonst False - """ - return self.version_info.get("auto_download", False) - - def apply_update(self, update_file: str) -> Dict[str, Any]: - """ - Wendet ein heruntergeladenes Update an. - - Args: - update_file: Pfad zur Update-Datei - - Returns: - Dictionary mit Informationen über die Anwendung des Updates - """ - result = { - "success": False, - "version": "", - "error": "" - } - - if not os.path.exists(update_file): - result["error"] = f"Update-Datei nicht gefunden: {update_file}" - logger.error(result["error"]) - return result - - try: - # In der Produktion sollte hier die tatsächliche Update-Logik implementiert werden - # 1. Extrahieren des Updates - # 2. Sichern der aktuellen Version - # 3. Anwenden der Änderungen - # 4. Aktualisieren der Versionsinformationen - - # Simuliere ein erfolgreiches Update - logger.info(f"Update aus {update_file} erfolgreich angewendet (simuliert)") - - # Extrahiere Version aus dem Dateinamen - file_name = os.path.basename(update_file) - version_match = re.search(r"v([0-9.]+)", file_name) - - if version_match: - new_version = version_match.group(1) - self.set_current_version(new_version) - result["version"] = new_version - - result["success"] = True - - return result - - except Exception as e: - error_msg = f"Fehler beim Anwenden des Updates: {e}" - logger.error(error_msg) - result["error"] = error_msg - return result diff --git a/views/tabs/generator_tab_modern.py b/views/tabs/generator_tab_modern.py deleted file mode 100644 index 52b7b4a..0000000 --- a/views/tabs/generator_tab_modern.py +++ /dev/null @@ -1,508 +0,0 @@ -""" -Moderner Account Generator Tab - Ohne Log-Widget, mit besserem Layout -""" - -import logging -from PyQt5.QtWidgets import ( - QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QGridLayout, - QGroupBox, QLabel, QLineEdit, QSpinBox, QRadioButton, - QCheckBox, QComboBox, QPushButton, QProgressBar, - QMessageBox, QFrame, QScrollArea -) -from PyQt5.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve -from PyQt5.QtGui import QFont, QPalette - -logger = logging.getLogger("generator_tab") - -class ModernGeneratorTab(QWidget): - """Modernes Widget für Account-Generierung ohne Log.""" - - # Signale - start_requested = pyqtSignal(dict) - stop_requested = pyqtSignal() - account_created = pyqtSignal(str, dict) - - def __init__(self, platform_name, language_manager=None): - super().__init__() - self.platform_name = platform_name - self.language_manager = language_manager - self.init_ui() - if self.language_manager: - self.language_manager.language_changed.connect(self.update_texts) - self.update_texts() - - def init_ui(self): - """Initialisiert die moderne Benutzeroberfläche.""" - # Haupt-Layout mit Scroll-Bereich - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - - # Scroll-Bereich für bessere Anpassung - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setFrameShape(QFrame.NoFrame) - scroll_widget = QWidget() - scroll_layout = QVBoxLayout(scroll_widget) - scroll_layout.setContentsMargins(40, 30, 40, 30) - scroll_layout.setSpacing(30) - - # Header mit Titel und Status - header_widget = self.create_header() - scroll_layout.addWidget(header_widget) - - # Haupt-Formular als Card - form_card = self.create_form_card() - scroll_layout.addWidget(form_card) - - # Erweiterte Optionen - options_card = self.create_options_card() - scroll_layout.addWidget(options_card) - - # Action-Bereich mit Buttons - action_widget = self.create_action_area() - scroll_layout.addWidget(action_widget) - - scroll_layout.addStretch() - - scroll.setWidget(scroll_widget) - main_layout.addWidget(scroll) - - # Event-Verbindungen - self.email_radio.toggled.connect(self.toggle_phone_input) - self.phone_radio.toggled.connect(self.toggle_phone_input) - - # Initialer Status - self.toggle_phone_input() - - def create_header(self): - """Erstellt den Header-Bereich mit Titel und Status.""" - header = QWidget() - layout = QHBoxLayout(header) - layout.setContentsMargins(0, 0, 0, 20) - - # Titel - title = QLabel("Account erstellen") - title_font = QFont() - title_font.setPointSize(24) - title_font.setWeight(QFont.Bold) - title.setFont(title_font) - layout.addWidget(title) - - layout.addStretch() - - # Status-Widget entfernt für cleanes Design - - return header - - def create_form_card(self): - """Erstellt die Haupt-Formular-Card.""" - card = QFrame() - card.setObjectName("formCard") - card.setStyleSheet(""" - #formCard { - background-color: white; - border: 1px solid #e0e0e0; - border-radius: 12px; - padding: 30px; - } - """) - - layout = QVBoxLayout(card) - - # Section Title - section_title = QLabel("Account-Informationen") - section_font = QFont() - section_font.setPointSize(16) - section_font.setWeight(QFont.DemiBold) - section_title.setFont(section_font) - layout.addWidget(section_title) - layout.addSpacing(20) - - # Grid-Layout für bessere Anordnung - grid = QGridLayout() - grid.setHorizontalSpacing(20) - grid.setVerticalSpacing(20) - - # Erste Zeile: Vorname und Nachname - self.first_name_label = self.create_label("Vorname") - self.first_name_input = self.create_input("z.B. Max") - grid.addWidget(self.first_name_label, 0, 0) - grid.addWidget(self.first_name_input, 1, 0) - - self.last_name_label = self.create_label("Nachname") - self.last_name_input = self.create_input("z.B. Mustermann") - grid.addWidget(self.last_name_label, 0, 1) - grid.addWidget(self.last_name_input, 1, 1) - - # Zweite Zeile: Alter - self.age_label = self.create_label("Alter") - self.age_input = self.create_input("z.B. 25") - grid.addWidget(self.age_label, 2, 0) - grid.addWidget(self.age_input, 3, 0) - - # Dritte Zeile: Registrierungsmethode - self.reg_method_label = self.create_label("Registrierungsmethode") - grid.addWidget(self.reg_method_label, 4, 0, 1, 2) - - # Radio Buttons in horizontaler Anordnung - radio_widget = QWidget() - radio_layout = QHBoxLayout(radio_widget) - radio_layout.setContentsMargins(0, 0, 0, 0) - - self.email_radio = QRadioButton("E-Mail") - self.phone_radio = QRadioButton("Telefon") - self.email_radio.setChecked(True) - - # Styling für Radio Buttons - radio_style = """ - QRadioButton { - font-size: 14px; - spacing: 8px; - } - QRadioButton::indicator { - width: 18px; - height: 18px; - } - """ - self.email_radio.setStyleSheet(radio_style) - self.phone_radio.setStyleSheet(radio_style) - - radio_layout.addWidget(self.email_radio) - radio_layout.addWidget(self.phone_radio) - radio_layout.addStretch() - - grid.addWidget(radio_widget, 5, 0, 1, 2) - - # Vierte Zeile: Kontaktfeld (E-Mail Domain oder Telefon) - self.email_domain_label = self.create_label("E-Mail Domain") - self.email_domain_input = self.create_input("") - self.email_domain_input.setText("z5m7q9dk3ah2v1plx6ju.com") - grid.addWidget(self.email_domain_label, 6, 0) - grid.addWidget(self.email_domain_input, 7, 0) - - self.phone_label = self.create_label("Telefonnummer") - self.phone_input = self.create_input("z.B. +49123456789") - self.phone_label.hide() - self.phone_input.hide() - grid.addWidget(self.phone_label, 6, 1) - grid.addWidget(self.phone_input, 7, 1) - - layout.addLayout(grid) - - # Plattform-spezifische Felder - self.add_platform_specific_fields(layout) - - return card - - def create_options_card(self): - """Erstellt die Card für erweiterte Optionen.""" - card = QFrame() - card.setObjectName("optionsCard") - card.setStyleSheet(""" - #optionsCard { - background-color: white; - border: 1px solid #e0e0e0; - border-radius: 12px; - padding: 30px; - } - """) - - layout = QVBoxLayout(card) - - # Section Title - section_title = QLabel("Erweiterte Optionen") - section_font = QFont() - section_font.setPointSize(16) - section_font.setWeight(QFont.DemiBold) - section_title.setFont(section_font) - layout.addWidget(section_title) - layout.addSpacing(15) - - # Optionen als moderne Checkboxen - self.headless_check = self.create_checkbox("Browser im Hintergrund ausführen") - self.headless_check.setToolTip("Der Browser wird nicht sichtbar sein") - layout.addWidget(self.headless_check) - - return card - - def create_action_area(self): - """Erstellt den Bereich mit Aktions-Buttons und Progress.""" - widget = QWidget() - layout = QVBoxLayout(widget) - layout.setContentsMargins(0, 0, 0, 0) - - # Progress Bar (initial versteckt) - self.progress_bar = QProgressBar() - self.progress_bar.setMinimumHeight(6) - self.progress_bar.setTextVisible(False) - self.progress_bar.setStyleSheet(""" - QProgressBar { - background-color: #f0f0f0; - border: none; - border-radius: 3px; - } - QProgressBar::chunk { - background-color: #2196F3; - border-radius: 3px; - } - """) - self.progress_bar.hide() - layout.addWidget(self.progress_bar) - layout.addSpacing(20) - - # Buttons - button_layout = QHBoxLayout() - button_layout.addStretch() - - self.stop_button = self.create_button("Abbrechen", primary=False) - self.stop_button.clicked.connect(self.on_stop_clicked) - self.stop_button.setEnabled(False) - self.stop_button.hide() - button_layout.addWidget(self.stop_button) - - self.start_button = self.create_button("Account erstellen", primary=True) - self.start_button.clicked.connect(self.on_start_clicked) - button_layout.addWidget(self.start_button) - - layout.addLayout(button_layout) - - return widget - - def create_label(self, text): - """Erstellt ein modernes Label.""" - label = QLabel(text) - label.setStyleSheet(""" - QLabel { - color: #666; - font-size: 13px; - font-weight: 500; - } - """) - return label - - def create_input(self, placeholder=""): - """Erstellt ein modernes Input-Feld.""" - input_field = QLineEdit() - input_field.setPlaceholderText(placeholder) - input_field.setMinimumHeight(42) - input_field.setStyleSheet(""" - QLineEdit { - border: 2px solid #e0e0e0; - border-radius: 8px; - padding: 10px 15px; - font-size: 14px; - background-color: #fafafa; - } - QLineEdit:focus { - border-color: #2196F3; - background-color: white; - } - QLineEdit:hover { - border-color: #bdbdbd; - } - """) - return input_field - - def create_checkbox(self, text): - """Erstellt eine moderne Checkbox.""" - checkbox = QCheckBox(text) - checkbox.setStyleSheet(""" - QCheckBox { - font-size: 14px; - color: #333; - spacing: 10px; - } - QCheckBox::indicator { - width: 20px; - height: 20px; - border-radius: 4px; - border: 2px solid #e0e0e0; - background-color: white; - } - QCheckBox::indicator:checked { - background-color: #2196F3; - border-color: #2196F3; - image: url(check_white.png); - } - QCheckBox::indicator:hover { - border-color: #2196F3; - } - """) - return checkbox - - def create_button(self, text, primary=False): - """Erstellt einen modernen Button.""" - button = QPushButton(text) - button.setMinimumHeight(45) - button.setCursor(Qt.PointingHandCursor) - - if primary: - button.setStyleSheet(""" - QPushButton { - background-color: #2196F3; - color: white; - border: none; - border-radius: 8px; - padding: 12px 32px; - font-size: 14px; - font-weight: 600; - } - QPushButton:hover { - background-color: #1976D2; - } - QPushButton:pressed { - background-color: #0D47A1; - } - QPushButton:disabled { - background-color: #BBBBBB; - } - """) - else: - button.setStyleSheet(""" - QPushButton { - background-color: white; - color: #666; - border: 2px solid #e0e0e0; - border-radius: 8px; - padding: 12px 32px; - font-size: 14px; - font-weight: 500; - } - QPushButton:hover { - border-color: #bdbdbd; - background-color: #f5f5f5; - } - QPushButton:pressed { - background-color: #eeeeee; - } - QPushButton:disabled { - color: #BBBBBB; - border-color: #eeeeee; - } - """) - - return button - - def add_platform_specific_fields(self, parent_layout): - """Fügt plattformspezifische Felder hinzu.""" - platform = self.platform_name.lower() - - - def toggle_phone_input(self): - """Wechselt zwischen E-Mail und Telefon-Eingabe.""" - if self.email_radio.isChecked(): - self.email_domain_label.show() - self.email_domain_input.show() - self.phone_label.hide() - self.phone_input.hide() - else: - self.email_domain_label.hide() - self.email_domain_input.hide() - self.phone_label.show() - self.phone_input.show() - - def on_start_clicked(self): - """Wird aufgerufen, wenn der Start-Button geklickt wird.""" - params = self.get_params() - - valid, error_msg = self.validate_inputs(params) - if not valid: - self.show_error(error_msg) - return - - # UI für laufenden Prozess anpassen - self.set_running(True) - self.progress_bar.show() - self.progress_bar.setValue(0) - - # Signal auslösen - self.start_requested.emit(params) - - def on_stop_clicked(self): - """Wird aufgerufen, wenn der Stop-Button geklickt wird.""" - self.stop_requested.emit() - - def get_params(self): - """Sammelt alle Parameter für die Account-Erstellung.""" - first_name = self.first_name_input.text().strip() - last_name = self.last_name_input.text().strip() - full_name = f"{first_name} {last_name}" - - params = { - "first_name": first_name, - "last_name": last_name, - "full_name": full_name, - "age_text": self.age_input.text().strip(), - "registration_method": "email" if self.email_radio.isChecked() else "phone", - "headless": self.headless_check.isChecked(), - "debug": True, - "email_domain": self.email_domain_input.text().strip(), - "use_proxy": False - } - - if self.phone_radio.isChecked(): - params["phone_number"] = self.phone_input.text().strip() - - - return params - - def validate_inputs(self, params): - """Validiert die Eingaben.""" - if not params.get("first_name"): - return False, "Bitte geben Sie einen Vornamen ein." - - if not params.get("last_name"): - return False, "Bitte geben Sie einen Nachnamen ein." - - age_text = params.get("age_text", "") - if not age_text: - return False, "Bitte geben Sie ein Alter ein." - - try: - age = int(age_text) - params["age"] = age - except ValueError: - return False, "Das Alter muss eine ganze Zahl sein." - - if age < 13 or age > 99: - return False, "Das Alter muss zwischen 13 und 99 liegen." - - if params.get("registration_method") == "phone" and not params.get("phone_number"): - return False, "Bitte geben Sie eine Telefonnummer ein." - - return True, "" - - def set_running(self, running: bool): - """Setzt den Status auf 'Wird ausgeführt' oder 'Bereit'.""" - if running: - self.start_button.hide() - self.stop_button.show() - self.stop_button.setEnabled(True) - # Status-Dot entfernt - # Status-Text entfernt - else: - self.start_button.show() - self.stop_button.hide() - self.stop_button.setEnabled(False) - self.progress_bar.hide() - # Status-Dot entfernt - # Status-Text entfernt - - def set_progress(self, value: int): - """Setzt den Fortschritt.""" - self.progress_bar.setValue(value) - - def set_status(self, message: str): - """Setzt die Statusnachricht.""" - # Status-Text entfernt - logger.info(f"Status: {message}") - - def show_error(self, message: str): - """Zeigt eine moderne Fehlermeldung an.""" - from views.widgets.modern_message_box import show_error - show_error(self, "Fehler", message) - - def update_texts(self): - """Aktualisiert UI-Texte gemäß der aktuellen Sprache.""" - # Hier würden die Übersetzungen implementiert - pass \ No newline at end of file diff --git a/views/widgets/account_creation_modal_v2.py b/views/widgets/account_creation_modal_v2.py deleted file mode 100644 index 710db80..0000000 --- a/views/widgets/account_creation_modal_v2.py +++ /dev/null @@ -1,625 +0,0 @@ -""" -Account Creation Modal V2 - Verbessertes Design mit eindringlicher Warnung -Basierend auf dem Corporate Design Styleguide -""" - -import logging -from typing import Optional, List, Dict -from datetime import datetime, timedelta -from PyQt5.QtWidgets import ( - QDialog, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QFrame, QWidget, QScrollArea, - QProgressBar, QGraphicsOpacityEffect -) -from PyQt5.QtCore import ( - Qt, QTimer, pyqtSignal, QPropertyAnimation, - QEasingCurve, QRect, QSize -) -from PyQt5.QtGui import QFont, QPainter, QBrush, QColor, QPen, QPixmap - -logger = logging.getLogger("account_creation_modal_v2") - - -class AccountCreationModalV2(QDialog): - """ - Verbessertes Modal für Account-Erstellung mit: - - Prominenter Warnung - - Besserer Step-Visualisierung - - Größerem Fenster - - Klarerer Kommunikation - """ - - # Signale - cancel_clicked = pyqtSignal() - process_completed = pyqtSignal() - - def __init__(self, parent=None, platform: str = "Social Media"): - super().__init__(parent) - self.platform = platform - self.current_step = 0 - self.total_steps = 0 - self.steps = [] - self.start_time = None - self.is_process_running = False - - # Timer für Updates - self.update_timer = QTimer() - self.update_timer.timeout.connect(self.update_time_display) - self.update_timer.setInterval(1000) # Jede Sekunde - - # Animation Timer für Warning - self.warning_animation_timer = QTimer() - self.warning_animation_timer.timeout.connect(self.animate_warning) - self.warning_animation_timer.setInterval(100) # Smooth animation - self.warning_opacity = 1.0 - self.warning_direction = -0.02 - - self.init_ui() - - def init_ui(self): - """Initialisiert die UI mit verbessertem Design""" - # Modal-Eigenschaften - self.setModal(True) - self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) - self.setAttribute(Qt.WA_TranslucentBackground) - - # Größeres Fenster - self.setFixedSize(650, 700) - - # Zentriere auf Parent oder Bildschirm - if self.parent(): - parent_rect = self.parent().geometry() - x = parent_rect.x() + (parent_rect.width() - self.width()) // 2 - y = parent_rect.y() + (parent_rect.height() - self.height()) // 2 - self.move(x, y) - - # Hauptlayout - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - - # Container mit weißem Hintergrund - self.container = QFrame() - self.container.setObjectName("mainContainer") - self.container.setStyleSheet(""" - QFrame#mainContainer { - background-color: #FFFFFF; - border: 1px solid #E2E8F0; - border-radius: 16px; - } - """) - - container_layout = QVBoxLayout(self.container) - container_layout.setContentsMargins(0, 0, 0, 0) - container_layout.setSpacing(0) - - # 1. WARNING BANNER (Sehr auffällig!) - self.warning_banner = self.create_warning_banner() - container_layout.addWidget(self.warning_banner) - - # Content Area mit Padding - content_widget = QWidget() - content_layout = QVBoxLayout(content_widget) - content_layout.setContentsMargins(40, 30, 40, 40) - content_layout.setSpacing(24) - - # 2. Titel-Bereich - self.title_label = QLabel(f"Account wird erstellt für {self.platform}") - self.title_label.setAlignment(Qt.AlignCenter) - self.title_label.setStyleSheet(""" - QLabel { - color: #1A365D; - font-size: 28px; - font-weight: 700; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - margin-bottom: 8px; - } - """) - content_layout.addWidget(self.title_label) - - # 3. Progress-Bereich - progress_widget = self.create_progress_section() - content_layout.addWidget(progress_widget) - - # 4. Steps-Liste - self.steps_widget = self.create_steps_list() - content_layout.addWidget(self.steps_widget) - - # 5. Live-Status - self.status_widget = self.create_status_section() - content_layout.addWidget(self.status_widget) - - # 6. Info-Box - info_box = self.create_info_box() - content_layout.addWidget(info_box) - - # Spacer - content_layout.addStretch() - - # Container zum Hauptlayout hinzufügen - container_layout.addWidget(content_widget) - main_layout.addWidget(self.container) - - def create_warning_banner(self) -> QFrame: - """Erstellt den auffälligen Warning Banner""" - banner = QFrame() - banner.setObjectName("warningBanner") - banner.setFixedHeight(100) - banner.setStyleSheet(""" - QFrame#warningBanner { - background-color: #DC2626; - border-top-left-radius: 16px; - border-top-right-radius: 16px; - border-bottom: none; - } - """) - - layout = QVBoxLayout(banner) - layout.setContentsMargins(40, 20, 40, 20) - layout.setSpacing(8) - - # Warning Icon + Haupttext - main_warning = QLabel("⚠️ NICHT DEN BROWSER BERÜHREN!") - main_warning.setAlignment(Qt.AlignCenter) - main_warning.setStyleSheet(""" - QLabel { - color: #FFFFFF; - font-size: 24px; - font-weight: 700; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - letter-spacing: 1px; - } - """) - - # Untertitel - subtitle = QLabel("Die Automatisierung läuft. Jede Interaktion kann den Prozess unterbrechen.") - subtitle.setAlignment(Qt.AlignCenter) - subtitle.setWordWrap(True) - subtitle.setStyleSheet(""" - QLabel { - color: rgba(255, 255, 255, 0.9); - font-size: 14px; - font-weight: 400; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - } - """) - - layout.addWidget(main_warning) - layout.addWidget(subtitle) - - return banner - - def create_progress_section(self) -> QWidget: - """Erstellt den Progress-Bereich""" - widget = QWidget() - layout = QHBoxLayout(widget) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(24) - - # Progress Bar - progress_container = QWidget() - progress_layout = QVBoxLayout(progress_container) - progress_layout.setContentsMargins(0, 0, 0, 0) - progress_layout.setSpacing(8) - - self.progress_bar = QProgressBar() - self.progress_bar.setFixedHeight(12) - self.progress_bar.setStyleSheet(""" - QProgressBar { - background-color: #E2E8F0; - border: none; - border-radius: 6px; - text-align: center; - } - QProgressBar::chunk { - background-color: #3182CE; - border-radius: 6px; - } - """) - - self.progress_label = QLabel("Schritt 0 von 0") - self.progress_label.setStyleSheet(""" - QLabel { - color: #4A5568; - font-size: 14px; - font-weight: 500; - } - """) - - progress_layout.addWidget(self.progress_bar) - progress_layout.addWidget(self.progress_label) - - # Zeit-Anzeige - time_container = QWidget() - time_container.setFixedWidth(180) - time_layout = QVBoxLayout(time_container) - time_layout.setContentsMargins(0, 0, 0, 0) - time_layout.setSpacing(4) - - self.time_label = QLabel("~2 Min verbleibend") - self.time_label.setAlignment(Qt.AlignRight) - self.time_label.setStyleSheet(""" - QLabel { - color: #1A365D; - font-size: 16px; - font-weight: 600; - font-family: 'Poppins', -apple-system, sans-serif; - } - """) - - self.elapsed_label = QLabel("Verstrichene Zeit: 0:00") - self.elapsed_label.setAlignment(Qt.AlignRight) - self.elapsed_label.setStyleSheet(""" - QLabel { - color: #718096; - font-size: 12px; - font-weight: 400; - } - """) - - time_layout.addWidget(self.time_label) - time_layout.addWidget(self.elapsed_label) - - layout.addWidget(progress_container, 1) - layout.addWidget(time_container) - - return widget - - def create_steps_list(self) -> QWidget: - """Erstellt die visuelle Steps-Liste""" - container = QFrame() - container.setStyleSheet(""" - QFrame { - background-color: #F8FAFC; - border: 1px solid #E2E8F0; - border-radius: 12px; - padding: 20px; - } - """) - - self.steps_layout = QVBoxLayout(container) - self.steps_layout.setSpacing(12) - - # Wird dynamisch befüllt - return container - - def create_status_section(self) -> QWidget: - """Erstellt den Live-Status Bereich""" - container = QFrame() - container.setStyleSheet(""" - QFrame { - background-color: #1A365D; - border-radius: 12px; - padding: 20px; - min-height: 80px; - } - """) - - layout = QVBoxLayout(container) - layout.setSpacing(8) - - status_title = QLabel("Aktueller Status:") - status_title.setStyleSheet(""" - QLabel { - color: rgba(255, 255, 255, 0.7); - font-size: 12px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 1px; - } - """) - - self.current_status_label = QLabel("Initialisiere Browser...") - self.current_status_label.setWordWrap(True) - self.current_status_label.setStyleSheet(""" - QLabel { - color: #FFFFFF; - font-size: 16px; - font-weight: 400; - font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; - line-height: 1.5; - } - """) - - layout.addWidget(status_title) - layout.addWidget(self.current_status_label) - - return container - - def create_info_box(self) -> QWidget: - """Erstellt die Info-Box""" - container = QFrame() - container.setStyleSheet(""" - QFrame { - background-color: #DBEAFE; - border: 1px solid #2563EB; - border-radius: 12px; - padding: 16px; - } - """) - - layout = QHBoxLayout(container) - layout.setSpacing(16) - - # Info Icon - icon_label = QLabel("ℹ️") - icon_label.setStyleSheet(""" - QLabel { - font-size: 24px; - color: #2563EB; - } - """) - - # Info Text - info_layout = QVBoxLayout() - info_layout.setSpacing(4) - - info_title = QLabel("Was passiert gerade?") - info_title.setStyleSheet(""" - QLabel { - color: #1E40AF; - font-size: 14px; - font-weight: 600; - } - """) - - self.info_text = QLabel("Der Browser simuliert menschliches Verhalten beim Ausfüllen des Formulars.") - self.info_text.setWordWrap(True) - self.info_text.setStyleSheet(""" - QLabel { - color: #1E40AF; - font-size: 13px; - font-weight: 400; - line-height: 1.4; - } - """) - - info_layout.addWidget(info_title) - info_layout.addWidget(self.info_text) - - layout.addWidget(icon_label) - layout.addWidget(info_layout, 1) - - return container - - def set_steps(self, steps: List[str]): - """Setzt die Steps und erstellt die visuelle Liste""" - self.steps = steps - self.total_steps = len(steps) - self.current_step = 0 - - # Progress Bar Setup - self.progress_bar.setMaximum(self.total_steps) - self.progress_bar.setValue(0) - - # Clear existing steps - for i in reversed(range(self.steps_layout.count())): - self.steps_layout.itemAt(i).widget().setParent(None) - - # Create step items - self.step_widgets = [] - for i, step in enumerate(steps): - step_widget = self.create_step_item(i, step) - self.steps_layout.addWidget(step_widget) - self.step_widgets.append(step_widget) - - def create_step_item(self, index: int, text: str) -> QWidget: - """Erstellt ein einzelnes Step-Item""" - widget = QWidget() - layout = QHBoxLayout(widget) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(12) - - # Status Icon - icon_label = QLabel() - icon_label.setFixedSize(24, 24) - icon_label.setAlignment(Qt.AlignCenter) - icon_label.setObjectName(f"stepIcon_{index}") - - # Text - text_label = QLabel(text) - text_label.setObjectName(f"stepText_{index}") - - # Initial state (pending) - self.update_step_appearance(widget, 'pending') - - layout.addWidget(icon_label) - layout.addWidget(text_label, 1) - - return widget - - def update_step_appearance(self, widget: QWidget, status: str): - """Aktualisiert das Aussehen eines Steps basierend auf Status""" - icon_label = widget.findChild(QLabel, QRegExp("stepIcon_*")) - text_label = widget.findChild(QLabel, QRegExp("stepText_*")) - - if status == 'completed': - icon_label.setText("✅") - text_label.setStyleSheet(""" - QLabel { - color: #059669; - font-size: 14px; - font-weight: 500; - } - """) - elif status == 'active': - icon_label.setText("🔄") - text_label.setStyleSheet(""" - QLabel { - color: #2563EB; - font-size: 14px; - font-weight: 600; - } - """) - # Rotation animation würde hier hinzugefügt - else: # pending - icon_label.setText("⏳") - text_label.setStyleSheet(""" - QLabel { - color: #718096; - font-size: 14px; - font-weight: 400; - } - """) - - def start_process(self): - """Startet den Account-Erstellungsprozess""" - self.is_process_running = True - self.start_time = datetime.now() - self.update_timer.start() - self.warning_animation_timer.start() - self.show() - - def next_step(self, status_text: str = None, info_text: str = None): - """Geht zum nächsten Schritt""" - if self.current_step < self.total_steps: - # Aktuellen Step als aktiv markieren - if self.current_step > 0: - self.update_step_appearance(self.step_widgets[self.current_step - 1], 'completed') - - if self.current_step < len(self.step_widgets): - self.update_step_appearance(self.step_widgets[self.current_step], 'active') - - self.current_step += 1 - self.progress_bar.setValue(self.current_step) - self.progress_label.setText(f"Schritt {self.current_step} von {self.total_steps}") - - # Update status - if status_text: - self.current_status_label.setText(status_text) - - # Update info - if info_text: - self.info_text.setText(info_text) - - # Update time estimate - self.update_time_estimate() - - def update_time_estimate(self): - """Aktualisiert die Zeitschätzung""" - if self.total_steps > 0 and self.current_step > 0: - elapsed = (datetime.now() - self.start_time).total_seconds() - avg_time_per_step = elapsed / self.current_step - remaining_steps = self.total_steps - self.current_step - estimated_remaining = remaining_steps * avg_time_per_step - - if estimated_remaining < 60: - self.time_label.setText(f"~{int(estimated_remaining)}s verbleibend") - else: - minutes = int(estimated_remaining / 60) - self.time_label.setText(f"~{minutes} Min verbleibend") - - def update_time_display(self): - """Aktualisiert die verstrichene Zeit""" - if self.start_time: - elapsed = datetime.now() - self.start_time - minutes = int(elapsed.total_seconds() / 60) - seconds = int(elapsed.total_seconds() % 60) - self.elapsed_label.setText(f"Verstrichene Zeit: {minutes}:{seconds:02d}") - - def animate_warning(self): - """Animiert den Warning Banner (Pulseffekt)""" - self.warning_opacity += self.warning_direction - - if self.warning_opacity <= 0.7: - self.warning_opacity = 0.7 - self.warning_direction = 0.02 - elif self.warning_opacity >= 1.0: - self.warning_opacity = 1.0 - self.warning_direction = -0.02 - - # Opacity-Effekt anwenden - effect = QGraphicsOpacityEffect() - effect.setOpacity(self.warning_opacity) - self.warning_banner.setGraphicsEffect(effect) - - def set_status(self, status: str, info: str = None): - """Setzt den aktuellen Status""" - self.current_status_label.setText(status) - if info: - self.info_text.setText(info) - - def complete_process(self): - """Beendet den Prozess erfolgreich""" - self.is_process_running = False - self.update_timer.stop() - self.warning_animation_timer.stop() - - # Letzten Step als completed markieren - if self.current_step > 0 and self.current_step <= len(self.step_widgets): - self.update_step_appearance(self.step_widgets[self.current_step - 1], 'completed') - - # Success message - self.current_status_label.setText("✅ Account erfolgreich erstellt!") - self.info_text.setText("Der Prozess wurde erfolgreich abgeschlossen. Das Fenster schließt sich in wenigen Sekunden.") - - # Auto-close nach 3 Sekunden - QTimer.singleShot(3000, self.close) - - def paintEvent(self, event): - """Custom paint event für Transparenz""" - painter = QPainter(self) - painter.setRenderHint(QPainter.Antialiasing) - - # Semi-transparenter Hintergrund - painter.fillRect(self.rect(), QColor(0, 0, 0, 120)) - - def keyPressEvent(self, event): - """Verhindert das Schließen mit ESC""" - if event.key() == Qt.Key_Escape: - event.ignore() - else: - super().keyPressEvent(event) - - def closeEvent(self, event): - """Verhindert das Schließen während der Prozess läuft""" - if self.is_process_running: - event.ignore() - else: - self.update_timer.stop() - self.warning_animation_timer.stop() - super().closeEvent(event) - - -# Beispiel-Verwendung -if __name__ == "__main__": - from PyQt5.QtWidgets import QApplication, QMainWindow - import sys - - app = QApplication(sys.argv) - - # Test window - main_window = QMainWindow() - main_window.setGeometry(100, 100, 800, 600) - main_window.show() - - # Create and show modal - modal = AccountCreationModalV2(main_window, "Instagram") - modal.set_steps([ - "Browser vorbereiten", - "Instagram-Seite laden", - "Registrierungsformular öffnen", - "Benutzerdaten eingeben", - "Account erstellen", - "E-Mail-Verifizierung", - "Profil einrichten" - ]) - - # Simulate process - modal.start_process() - - # Simulate step progression - def next_step(): - modal.next_step( - f"Führe Schritt {modal.current_step + 1} aus...", - "Der Browser füllt automatisch die erforderlichen Felder aus." - ) - - if modal.current_step < modal.total_steps: - QTimer.singleShot(2000, next_step) - else: - modal.complete_process() - - QTimer.singleShot(1000, next_step) - - sys.exit(app.exec_()) \ No newline at end of file diff --git a/views/widgets/forge_animation_widget_v2.py b/views/widgets/forge_animation_widget_v2.py deleted file mode 100644 index 32a41bc..0000000 --- a/views/widgets/forge_animation_widget_v2.py +++ /dev/null @@ -1,370 +0,0 @@ -""" -Forge Animation Widget V2 - Verbessertes Design mit prominenter Warnung -Angepasst an den bestehenden Code mit minimalen Änderungen -""" - -from PyQt5.QtWidgets import QDialog, QWidget, QVBoxLayout, QLabel, QTextEdit, QPushButton, QHBoxLayout, QFrame -from PyQt5.QtCore import Qt, pyqtSignal, QTimer -from PyQt5.QtGui import QFont, QMovie, QPixmap - -class ForgeAnimationDialog(QDialog): - """Modal-Dialog für die Account-Erstellung mit verbessertem Design""" - - # Signal wenn Abbrechen geklickt wird - cancel_clicked = pyqtSignal() - # Signal wenn Dialog geschlossen wird - closed = pyqtSignal() - - def __init__(self, parent=None): - super().__init__(parent) - self.init_ui() - - # 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 - - def init_ui(self): - """Initialisiert die UI mit verbessertem Design""" - # Nur Dialog im Vordergrund, nicht das ganze Hauptfenster - self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) - self.setModal(False) # Nicht modal - blockiert nicht das Hauptfenster - self.setFixedSize(650, 600) # Größer für bessere Sichtbarkeit - - # Styling für Light Theme - self.setStyleSheet(""" - ForgeAnimationDialog { - background-color: #FFFFFF; - border: 1px solid #E2E8F0; - border-radius: 8px; - } - """) - - # Hauptlayout - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - # NEUE WARNUNG OBEN - Sehr auffällig! - warning_banner = QFrame() - warning_banner.setFixedHeight(80) - warning_banner.setStyleSheet(""" - QFrame { - background-color: #DC2626; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - padding: 15px 30px; - } - """) - - warning_layout = QVBoxLayout(warning_banner) - warning_layout.setContentsMargins(0, 10, 0, 10) - - # Großer Warning Text - warning_text = QLabel("⚠️ BROWSER NICHT BERÜHREN!") - warning_text.setAlignment(Qt.AlignCenter) - warning_text.setStyleSheet(""" - QLabel { - color: #FFFFFF; - font-size: 22px; - font-weight: 700; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - letter-spacing: 1px; - } - """) - - warning_subtext = QLabel("Jede Interaktion kann den Prozess unterbrechen") - warning_subtext.setAlignment(Qt.AlignCenter) - warning_subtext.setStyleSheet(""" - QLabel { - color: rgba(255, 255, 255, 0.9); - font-size: 13px; - font-weight: 400; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - } - """) - - warning_layout.addWidget(warning_text) - warning_layout.addWidget(warning_subtext) - - layout.addWidget(warning_banner) - - # Content Container - content_container = QWidget() - content_layout = QVBoxLayout(content_container) - content_layout.setContentsMargins(30, 25, 30, 30) - content_layout.setSpacing(20) - - # Titel (etwas größer) - title_label = QLabel("Account wird erstellt") - title_label.setObjectName("titleLabel") - title_label.setAlignment(Qt.AlignCenter) - title_label.setStyleSheet(""" - QLabel#titleLabel { - color: #1A365D; - font-size: 26px; - font-weight: 600; - font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - padding-bottom: 10px; - border: none; - } - """) - content_layout.addWidget(title_label) - - # Moderne Info-Karte (angepasst) - info_frame = QFrame() - info_frame.setStyleSheet(""" - QFrame { - background: #DBEAFE; - border: 1px solid #2563EB; - border-radius: 12px; - min-height: 100px; - } - """) - - info_layout = QHBoxLayout(info_frame) - info_layout.setContentsMargins(20, 15, 20, 15) - info_layout.setSpacing(15) - - # Icon Container - icon_container = QFrame() - icon_container.setFixedSize(50, 50) - icon_container.setStyleSheet(""" - QFrame { - background: #2563EB; - border-radius: 25px; - } - """) - - icon_layout = QVBoxLayout(icon_container) - icon_layout.setContentsMargins(0, 0, 0, 0) - - icon_label = QLabel("🤖") - icon_label.setAlignment(Qt.AlignCenter) - icon_label.setStyleSheet(""" - QLabel { - font-size: 24px; - background: transparent; - border: none; - color: white; - } - """) - icon_layout.addWidget(icon_label) - - # Text Container - text_container = QFrame() - text_layout = QVBoxLayout(text_container) - text_layout.setContentsMargins(0, 0, 0, 0) - text_layout.setSpacing(5) - - # Titel - info_title = QLabel("Automatisierung läuft") - info_title.setObjectName("infoTitle") - info_title.setStyleSheet(""" - QLabel#infoTitle { - color: #1E40AF; - font-size: 17px; - font-weight: 600; - font-family: 'Segoe UI', -apple-system, sans-serif; - background-color: transparent; - border: none; - } - """) - - # Beschreibung (deutlicher) - info_desc = QLabel("Der Browser arbeitet automatisch. Bitte warten Sie, bis der Vorgang abgeschlossen ist.") - info_desc.setObjectName("infoDesc") - info_desc.setWordWrap(True) - info_desc.setStyleSheet(""" - QLabel#infoDesc { - color: #1E40AF; - font-size: 14px; - font-weight: 500; - font-family: 'Segoe UI', -apple-system, sans-serif; - background-color: transparent; - border: none; - line-height: 20px; - } - """) - - text_layout.addWidget(info_title) - text_layout.addWidget(info_desc) - text_layout.addStretch() - - info_layout.addWidget(icon_container) - info_layout.addWidget(text_container, 1) - - content_layout.addWidget(info_frame) - - # Status Container - status_container = QFrame() - status_container.setStyleSheet(""" - QFrame { - background-color: #1A365D; - border-radius: 8px; - padding: 15px; - } - """) - - status_layout = QVBoxLayout(status_container) - - # Status-Label (größer) - self.status_label = QLabel("Initialisiere...") - self.status_label.setObjectName("statusLabel") - self.status_label.setAlignment(Qt.AlignLeft) - self.status_label.setStyleSheet(""" - QLabel#statusLabel { - color: #FFFFFF; - font-size: 16px; - padding: 5px; - font-weight: 500; - border: none; - font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; - } - """) - status_layout.addWidget(self.status_label) - - content_layout.addWidget(status_container) - - # Log-Ausgabe (größer) - self.log_output = QTextEdit() - self.log_output.setReadOnly(True) - self.log_output.setMaximumHeight(180) # Etwas größer - self.log_output.setStyleSheet(""" - QTextEdit { - background-color: #F8FAFC; - color: #2D3748; - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace; - font-size: 12px; - border: 1px solid #CBD5E0; - border-radius: 8px; - padding: 12px; - } - """) - content_layout.addWidget(self.log_output) - - # Button-Container - button_layout = QHBoxLayout() - button_layout.addStretch() - - # Abbrechen-Button (weniger prominent) - self.cancel_button = QPushButton("Abbrechen") - self.cancel_button.setStyleSheet(""" - QPushButton { - background-color: #F0F4F8; - color: #4A5568; - font-size: 14px; - font-weight: 500; - padding: 8px 24px; - border-radius: 24px; - border: 1px solid #E2E8F0; - min-height: 40px; - } - QPushButton:hover { - background-color: #FEE2E2; - color: #DC2626; - border-color: #DC2626; - } - QPushButton:pressed { - background-color: #DC2626; - color: #FFFFFF; - } - """) - self.cancel_button.clicked.connect(self.cancel_clicked.emit) - button_layout.addWidget(self.cancel_button) - - button_layout.addStretch() - content_layout.addLayout(button_layout) - - layout.addWidget(content_container) - self.setLayout(layout) - - # Volle Sichtbarkeit - self.setWindowOpacity(1.0) - - def start_animation(self): - """Zeigt den Dialog an""" - self.status_label.setText("Initialisiere...") - self.raise_timer.start() # Starte Timer für Always-on-Top - - def stop_animation(self): - """Stoppt die Animation und den Timer""" - self.raise_timer.stop() - - def set_status(self, status: str): - """Aktualisiert den Status-Text""" - self.status_label.setText(status) - - def add_log(self, message: str): - """Fügt eine Log-Nachricht hinzu""" - self.log_output.append(message) - # Auto-scroll zum Ende - scrollbar = self.log_output.verticalScrollBar() - scrollbar.setValue(scrollbar.maximum()) - - def clear_log(self): - """Löscht alle Log-Nachrichten""" - self.log_output.clear() - - def set_progress(self, value: int): - """Setzt den Fortschritt (0-100) - wird ignoriert da wir Spinner nutzen""" - pass - - def closeEvent(self, event): - """Wird aufgerufen wenn der Dialog geschlossen wird""" - self.stop_animation() - self.closed.emit() - event.accept() - - def keyPressEvent(self, event): - """Verhindert das Schließen mit ESC""" - if event.key() == Qt.Key_Escape: - event.ignore() - else: - super().keyPressEvent(event) - - def _raise_to_front(self): - """Holt den Dialog in den Vordergrund""" - self.raise_() - - def show(self): - """Überschreibt show() um den Dialog richtig zu positionieren""" - super().show() - self._raise_to_front() # Initial in den Vordergrund holen - - -# Zusätzliche Widget-Klasse für den AccountForger -class ForgeAnimationWidget(QLabel): - """ - Einfaches Animation Widget für den Progress Modal - Kann einen Spinner oder andere Animation anzeigen - """ - def __init__(self): - super().__init__() - self.setText("⚙️") # Placeholder Icon - self.setAlignment(Qt.AlignCenter) - self.setStyleSheet(""" - QLabel { - font-size: 48px; - color: #3182CE; - } - """) - - # Animation Timer - self.animation_timer = QTimer() - self.animation_timer.timeout.connect(self.rotate_icon) - self.rotation_state = 0 - - def start_animation(self): - """Startet die Animation""" - self.animation_timer.start(100) - - def stop_animation(self): - """Stoppt die Animation""" - self.animation_timer.stop() - - def rotate_icon(self): - """Einfache Rotation Animation""" - icons = ["⚙️", "🔧", "🔨", "⚒️"] - self.setText(icons[self.rotation_state % len(icons)]) - self.rotation_state += 1 \ No newline at end of file