RollBack Punkt 2025-09-13
Dieser Commit ist enthalten in:
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -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
@ -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
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren