RollBack Punkt 2025-09-13

Dieser Commit ist enthalten in:
Claude Project Manager
2025-09-13 22:01:58 +02:00
Ursprung ca005ab9f0
Commit ba0ba3fcec
25 geänderte Dateien mit 140 neuen und 6194 gelöschten Zeilen

Datei anzeigen

@ -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"
]

Datei anzeigen

@ -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

114
README.md
Datei anzeigen

@ -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 SocialMediaAccounts. Die grafische Oberfläche basiert auf **PyQt5**, die BrowserAutomatisierung erfolgt mit **Playwright**. Der Code ist modular aufgebaut und kann leicht um weitere Plattformen erweitert werden.
DesktopAnwendung zur Erstellung und Verwaltung von SocialMediaAccounts. 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 UpdateManagement. 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 IMAPKonfiguration 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.

Datei anzeigen

@ -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": ""
}
}

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -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
- Keine zirkulären Abhängigkeiten entstehen

Datei anzeigen

@ -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'

8
init Normale Datei
Datei anzeigen

@ -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.

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -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 {}
}

Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist

Datei anzeigen

@ -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

Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist

Datei anzeigen

@ -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

Datei anzeigen

@ -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_())

Datei anzeigen

@ -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