1096 Zeilen
45 KiB
Python
1096 Zeilen
45 KiB
Python
# social_networks/x/x_registration.py
|
|
|
|
"""
|
|
X (Twitter) Registrierung - Klasse für die Kontoerstellung bei X
|
|
"""
|
|
|
|
import time
|
|
import random
|
|
import re
|
|
from typing import Dict, List, Any, Optional, Tuple
|
|
|
|
from .x_selectors import XSelectors
|
|
from .x_workflow import XWorkflow
|
|
from utils.logger import setup_logger
|
|
|
|
# Konfiguriere Logger
|
|
logger = setup_logger("x_registration")
|
|
|
|
class XRegistration:
|
|
"""
|
|
Klasse für die Registrierung von X-Konten.
|
|
Enthält alle Methoden zur Kontoerstellung.
|
|
"""
|
|
|
|
def __init__(self, automation):
|
|
"""
|
|
Initialisiert die X-Registrierung.
|
|
|
|
Args:
|
|
automation: Referenz auf die Hauptautomatisierungsklasse
|
|
"""
|
|
self.automation = automation
|
|
# Browser wird direkt von automation verwendet
|
|
self.selectors = XSelectors()
|
|
self.workflow = XWorkflow.get_registration_workflow()
|
|
|
|
logger.debug("X-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 X-Account durch.
|
|
|
|
Args:
|
|
full_name: Vollständiger Name für den Account
|
|
age: Alter des Benutzers
|
|
registration_method: "email" oder "phone"
|
|
phone_number: Telefonnummer (nur bei registration_method="phone")
|
|
**kwargs: Weitere optionale Parameter
|
|
|
|
Returns:
|
|
Dict[str, Any]: Ergebnis der Registrierung mit Status und Account-Daten
|
|
"""
|
|
# Browser wird direkt von automation verwendet
|
|
|
|
# Validiere die Eingaben
|
|
if not self._validate_registration_inputs(full_name, age, registration_method, phone_number):
|
|
return {
|
|
"success": False,
|
|
"error": "Ungültige Eingabeparameter",
|
|
"stage": "input_validation"
|
|
}
|
|
|
|
# Account-Daten generieren
|
|
account_data = self._generate_account_data(full_name, age, registration_method, phone_number, **kwargs)
|
|
|
|
# Account-Daten als Instanzvariable speichern für späteren Zugriff
|
|
self._current_account_data = account_data
|
|
|
|
# Starte den Registrierungsprozess
|
|
logger.info(f"Starte X-Registrierung für {account_data['username']} via {registration_method}")
|
|
|
|
try:
|
|
# 1. Zur Startseite navigieren
|
|
self.automation._emit_customer_log("🌐 Mit X verbinden...")
|
|
if not self._navigate_to_homepage():
|
|
return {
|
|
"success": False,
|
|
"error": "Konnte nicht zur X-Startseite navigieren",
|
|
"stage": "navigation",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# 2. Cookie-Banner behandeln
|
|
self.automation._emit_customer_log("⚙️ Einstellungen werden vorbereitet...")
|
|
self._handle_cookie_banner()
|
|
|
|
# 3. Account erstellen Button klicken
|
|
self.automation._emit_customer_log("📋 Registrierungsformular wird geöffnet...")
|
|
if not self._click_create_account_button():
|
|
return {
|
|
"success": False,
|
|
"error": "Konnte nicht auf Account erstellen Button klicken",
|
|
"stage": "create_account_button",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# 4. Registrierungsformular ausfüllen (Name und E-Mail)
|
|
self.automation._emit_customer_log("📝 Persönliche Daten werden übertragen...")
|
|
if not self._fill_initial_registration_form(account_data):
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler beim Ausfüllen des initialen Registrierungsformulars",
|
|
"stage": "initial_registration_form",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# 5. Geburtsdatum eingeben
|
|
self.automation._emit_customer_log("🎂 Geburtsdatum wird festgelegt...")
|
|
if not self._enter_birthday(account_data["birthday"]):
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler beim Eingeben des Geburtsdatums",
|
|
"stage": "birthday",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# 6. Weiter klicken nach Geburtsdatum
|
|
if not self._click_next_after_birthday():
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler beim Fortfahren nach Geburtsdatum",
|
|
"stage": "next_after_birthday",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# 7. Zweiter Weiter-Button (Einstellungen)
|
|
if not self._click_next_settings():
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler beim Fortfahren in den Einstellungen",
|
|
"stage": "next_settings",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# 8. E-Mail-Verifizierung
|
|
self.automation._emit_customer_log("📧 E-Mail-Verifizierung wird durchgeführt...")
|
|
|
|
# Screenshot vor Verifizierung
|
|
self.automation._take_screenshot("before_verification")
|
|
|
|
verification_code = self._get_verification_code(account_data["email"])
|
|
if not verification_code:
|
|
return {
|
|
"success": False,
|
|
"error": "Konnte keinen Verifizierungscode erhalten",
|
|
"stage": "verification_code",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# Screenshot nach Code-Abruf
|
|
self.automation._take_screenshot("after_code_retrieval")
|
|
|
|
if not self._enter_verification_code(verification_code):
|
|
# Screenshot bei Fehler
|
|
self.automation._take_screenshot("verification_input_error")
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler beim Eingeben des Verifizierungscodes",
|
|
"stage": "enter_verification_code",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# 9. Passwort festlegen
|
|
self.automation._emit_customer_log("🔐 Passwort wird festgelegt...")
|
|
if not self._enter_password(account_data["password"]):
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler beim Festlegen des Passworts",
|
|
"stage": "password",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# 10. Profilbild überspringen
|
|
self.automation._emit_customer_log("🖼️ Profilbild-Schritt wird übersprungen...")
|
|
if not self._skip_profile_picture():
|
|
logger.warning("Konnte Profilbild-Schritt nicht überspringen, fahre trotzdem fort")
|
|
|
|
# 11. Benutzername überspringen (X generiert automatisch einen)
|
|
self.automation._emit_customer_log("👤 Benutzername-Schritt wird übersprungen...")
|
|
if not self._skip_username():
|
|
logger.warning("Konnte Benutzername-Schritt nicht überspringen, fahre trotzdem fort")
|
|
|
|
# 12. Benachrichtigungen überspringen
|
|
self.automation._emit_customer_log("🔔 Benachrichtigungen werden übersprungen...")
|
|
if not self._skip_notifications():
|
|
logger.warning("Konnte Benachrichtigungen nicht überspringen, fahre trotzdem fort")
|
|
|
|
# 13. Erfolgreiche Registrierung überprüfen
|
|
self.automation._emit_customer_log("🔍 Account wird finalisiert...")
|
|
if not self._check_registration_success():
|
|
return {
|
|
"success": False,
|
|
"error": "Registrierung fehlgeschlagen oder konnte nicht verifiziert werden",
|
|
"stage": "final_check",
|
|
"account_data": account_data
|
|
}
|
|
|
|
# Registrierung erfolgreich abgeschlossen
|
|
final_username = account_data.get('x_generated_username', account_data.get('username', 'unbekannt'))
|
|
logger.info(f"X-Account {final_username} ({account_data['email']}) erfolgreich erstellt")
|
|
self.automation._emit_customer_log(f"✅ Account erfolgreich erstellt! Benutzername: @{final_username}")
|
|
|
|
return {
|
|
"success": True,
|
|
"stage": "completed",
|
|
"account_data": account_data
|
|
}
|
|
|
|
except Exception as e:
|
|
error_msg = f"Unerwarteter Fehler bei der X-Registrierung: {str(e)}"
|
|
logger.error(error_msg, exc_info=True)
|
|
|
|
return {
|
|
"success": False,
|
|
"error": error_msg,
|
|
"stage": "exception",
|
|
"account_data": account_data
|
|
}
|
|
|
|
def _validate_registration_inputs(self, full_name: str, age: int,
|
|
registration_method: str, phone_number: str) -> bool:
|
|
"""
|
|
Validiert die Eingaben für die Registrierung.
|
|
|
|
Args:
|
|
full_name: Vollständiger Name für den Account
|
|
age: Alter des Benutzers
|
|
registration_method: "email" oder "phone"
|
|
phone_number: Telefonnummer (nur bei registration_method="phone")
|
|
|
|
Returns:
|
|
bool: True wenn alle Eingaben gültig sind, False sonst
|
|
"""
|
|
# Name validieren
|
|
if not full_name or len(full_name.strip()) < 2:
|
|
logger.error("Ungültiger Name für die Registrierung")
|
|
return False
|
|
|
|
# Alter validieren (X erfordert mindestens 13 Jahre)
|
|
if age < 13:
|
|
logger.error("Alter muss mindestens 13 Jahre sein für X")
|
|
return False
|
|
|
|
# Registrierungsmethode validieren
|
|
if registration_method not in ["email", "phone"]:
|
|
logger.error(f"Ungültige Registrierungsmethode: {registration_method}")
|
|
return False
|
|
|
|
# Telefonnummer validieren, falls benötigt
|
|
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 die notwendigen Account-Daten für die Registrierung.
|
|
|
|
Args:
|
|
full_name: Vollständiger Name
|
|
age: Alter
|
|
registration_method: "email" oder "phone"
|
|
phone_number: Telefonnummer (optional)
|
|
**kwargs: Weitere optionale Parameter
|
|
|
|
Returns:
|
|
Dict[str, Any]: Generierte Account-Daten
|
|
"""
|
|
# Geburtsdatum generieren
|
|
birthday = self.automation.birthday_generator.generate_birthday_components("x", age)
|
|
|
|
# Passwort generieren
|
|
password = kwargs.get("password") or self.automation.password_generator.generate_password()
|
|
|
|
# E-Mail generieren (auch wenn per Telefon registriert wird, für spätere Verwendung)
|
|
email = kwargs.get("email") or self._generate_email(full_name)
|
|
|
|
# Benutzername generieren (X generiert automatisch einen, aber wir bereiten einen vor)
|
|
username = kwargs.get("username") or self.automation.username_generator.generate_username(full_name)
|
|
|
|
account_data = {
|
|
"full_name": full_name,
|
|
"username": username,
|
|
"email": email,
|
|
"password": password,
|
|
"birthday": birthday,
|
|
"age": age,
|
|
"registration_method": registration_method
|
|
}
|
|
|
|
if phone_number:
|
|
account_data["phone_number"] = phone_number
|
|
|
|
logger.debug(f"Account-Daten generiert: {account_data['username']}")
|
|
|
|
return account_data
|
|
|
|
def _generate_email(self, full_name: str) -> str:
|
|
"""
|
|
Generiert eine E-Mail-Adresse basierend auf dem Namen.
|
|
|
|
Args:
|
|
full_name: Vollständiger Name
|
|
|
|
Returns:
|
|
str: Generierte E-Mail-Adresse
|
|
"""
|
|
# Entferne Sonderzeichen und konvertiere zu lowercase
|
|
clean_name = re.sub(r'[^a-zA-Z0-9]', '', full_name.lower())
|
|
|
|
# Füge zufällige Ziffern hinzu
|
|
random_suffix = ''.join([str(random.randint(0, 9)) for _ in range(4)])
|
|
|
|
# Erstelle E-Mail
|
|
email = f"{clean_name}{random_suffix}@{self.automation.email_domain}"
|
|
|
|
logger.debug(f"E-Mail generiert: {email}")
|
|
return email
|
|
|
|
def _navigate_to_homepage(self) -> bool:
|
|
"""
|
|
Navigiert zur X-Startseite.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Zur X-Startseite navigieren
|
|
logger.info("Navigiere zur X-Startseite")
|
|
page.goto("https://x.com/", wait_until="domcontentloaded", timeout=30000)
|
|
|
|
# Warte auf Seitenladung
|
|
self.automation.human_behavior.random_delay(2, 4)
|
|
|
|
# Screenshot
|
|
self.automation._take_screenshot("x_homepage")
|
|
|
|
# Log current URL to verify we're on the right page
|
|
current_url = page.url
|
|
logger.info(f"Aktuelle URL: {current_url}")
|
|
|
|
# Check if page loaded correctly
|
|
if "x.com" not in current_url and "twitter.com" not in current_url:
|
|
logger.error(f"Unerwartete URL: {current_url}")
|
|
return False
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Navigieren zur X-Startseite: {e}")
|
|
return False
|
|
|
|
def _handle_cookie_banner(self):
|
|
"""
|
|
Behandelt eventuelle Cookie-Banner.
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Versuche Cookie-Banner zu akzeptieren (wenn vorhanden)
|
|
cookie_selectors = [
|
|
"button:has-text('Accept all')",
|
|
"button:has-text('Alle akzeptieren')",
|
|
"button:has-text('Accept')",
|
|
"button:has-text('Akzeptieren')"
|
|
]
|
|
|
|
for selector in cookie_selectors:
|
|
try:
|
|
if page.is_visible(selector): # is_visible hat kein timeout parameter
|
|
logger.info(f"Cookie-Banner gefunden: {selector}")
|
|
page.click(selector)
|
|
self.automation.human_behavior.random_delay(1, 2)
|
|
break
|
|
except:
|
|
continue
|
|
|
|
except Exception as e:
|
|
logger.debug(f"Kein Cookie-Banner gefunden oder Fehler: {e}")
|
|
|
|
def _click_create_account_button(self) -> bool:
|
|
"""
|
|
Klickt auf den "Account erstellen" Button.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Warte auf und klicke "Account erstellen" Button
|
|
# Versuche mehrere Selektoren für bessere Robustheit
|
|
create_account_selectors = [
|
|
'text="Account erstellen"', # Am robustesten - findet jeden klickbaren Text
|
|
'span:has-text("Account erstellen")', # Falls der Text in einem span ist
|
|
'div[dir="ltr"] >> text="Account erstellen"', # Spezifischer
|
|
'[role="button"]:has-text("Account erstellen")' # Falls es ein button role hat
|
|
]
|
|
|
|
logger.info("Warte auf 'Account erstellen' Button")
|
|
|
|
# Versuche jeden Selektor
|
|
button_found = False
|
|
for selector in create_account_selectors:
|
|
try:
|
|
# Warte kurz, um sicherzustellen, dass die Seite geladen ist
|
|
page.wait_for_timeout(500)
|
|
|
|
# Versuche wait_for_selector statt is_visible für bessere Zuverlässigkeit
|
|
element = page.wait_for_selector(selector, timeout=3000, state="visible")
|
|
if element:
|
|
create_account_selector = selector
|
|
button_found = True
|
|
logger.info(f"Button gefunden mit Selektor: {selector}")
|
|
break
|
|
except Exception as e:
|
|
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
|
|
continue
|
|
|
|
if not button_found:
|
|
logger.error("'Account erstellen' Button nicht gefunden")
|
|
return False
|
|
|
|
page.wait_for_selector(create_account_selector, timeout=5000)
|
|
|
|
# Menschliches Verhalten simulieren
|
|
self.automation.human_behavior.random_delay(0.5, 1.5)
|
|
|
|
# Screenshot vor dem Klick
|
|
self.automation._take_screenshot("before_account_create_click")
|
|
|
|
# Button klicken
|
|
page.click(create_account_selector)
|
|
logger.info("'Account erstellen' Button geklickt")
|
|
|
|
# Screenshot nach dem Klick
|
|
self.automation.human_behavior.random_delay(1, 2)
|
|
self.automation._take_screenshot("after_account_create_click")
|
|
|
|
# Warte auf Formular
|
|
self.automation.human_behavior.random_delay(1, 2)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Klicken auf 'Account erstellen': {e}")
|
|
return False
|
|
|
|
def _fill_initial_registration_form(self, account_data: Dict[str, Any]) -> bool:
|
|
"""
|
|
Füllt das initiale Registrierungsformular aus (Name und E-Mail).
|
|
|
|
Args:
|
|
account_data: Account-Daten
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Name eingeben
|
|
name_selectors = [
|
|
'input[name="name"]',
|
|
'input[autocomplete="name"]',
|
|
'input[placeholder*="Name"]',
|
|
'input[data-testid="ocfEnterTextTextInput"]', # X-spezifischer Selektor
|
|
'input[type="text"]:first-of-type'
|
|
]
|
|
|
|
name_entered = False
|
|
for selector in name_selectors:
|
|
try:
|
|
element = page.wait_for_selector(selector, timeout=2000, state="visible")
|
|
if element:
|
|
logger.info(f"Name-Input gefunden mit Selektor: {selector}")
|
|
# Klicke zuerst auf das Feld
|
|
element.click()
|
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
|
|
|
# Lösche eventuell vorhandenen Inhalt
|
|
element.fill('')
|
|
|
|
# Tippe den Namen langsam ein
|
|
for char in account_data['full_name']:
|
|
element.type(char)
|
|
self.automation.human_behavior.random_delay(0.05, 0.15) # Zwischen Zeichen
|
|
|
|
logger.info(f"Name eingegeben: {account_data['full_name']}")
|
|
name_entered = True
|
|
self.automation.human_behavior.random_delay(0.5, 1)
|
|
break
|
|
except Exception as e:
|
|
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
|
|
continue
|
|
|
|
if not name_entered:
|
|
logger.error("Konnte Name nicht eingeben")
|
|
return False
|
|
|
|
# E-Mail eingeben
|
|
email_selectors = [
|
|
'input[name="email"]',
|
|
'input[autocomplete="email"]',
|
|
'input[type="email"]',
|
|
'input[placeholder*="Mail"]',
|
|
'input[placeholder*="mail"]',
|
|
'input[data-testid="ocfEnterTextTextInput"]:nth-of-type(2)'
|
|
]
|
|
|
|
email_entered = False
|
|
for selector in email_selectors:
|
|
try:
|
|
element = page.wait_for_selector(selector, timeout=2000, state="visible")
|
|
if element:
|
|
logger.info(f"E-Mail-Input gefunden mit Selektor: {selector}")
|
|
# Klicke zuerst auf das Feld
|
|
element.click()
|
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
|
|
|
# Lösche eventuell vorhandenen Inhalt
|
|
element.fill('')
|
|
|
|
# Tippe die E-Mail langsam ein
|
|
for char in account_data['email']:
|
|
element.type(char)
|
|
self.automation.human_behavior.random_delay(0.05, 0.15) # Zwischen Zeichen
|
|
|
|
logger.info(f"E-Mail eingegeben: {account_data['email']}")
|
|
email_entered = True
|
|
self.automation.human_behavior.random_delay(0.5, 1)
|
|
break
|
|
except Exception as e:
|
|
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
|
|
continue
|
|
|
|
if not email_entered:
|
|
logger.error("Konnte E-Mail nicht eingeben")
|
|
return False
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {e}")
|
|
return False
|
|
|
|
def _enter_birthday(self, birthday: Dict[str, int]) -> bool:
|
|
"""
|
|
Gibt das Geburtsdatum in die Dropdown-Felder ein.
|
|
|
|
Args:
|
|
birthday: Dictionary mit 'day', 'month', 'year'
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Monat auswählen
|
|
month_selector = 'select#SELECTOR_1'
|
|
month_select = page.wait_for_selector(month_selector, timeout=5000)
|
|
if month_select:
|
|
logger.info(f"Wähle Monat: {birthday['month']}")
|
|
page.select_option(month_selector, str(birthday['month']))
|
|
self.automation.human_behavior.random_delay(0.3, 0.7)
|
|
|
|
# Tag auswählen
|
|
day_selector = 'select#SELECTOR_2'
|
|
day_select = page.wait_for_selector(day_selector, timeout=5000)
|
|
if day_select:
|
|
logger.info(f"Wähle Tag: {birthday['day']}")
|
|
page.select_option(day_selector, str(birthday['day']))
|
|
self.automation.human_behavior.random_delay(0.3, 0.7)
|
|
|
|
# Jahr auswählen
|
|
year_selector = 'select#SELECTOR_3'
|
|
year_select = page.wait_for_selector(year_selector, timeout=5000)
|
|
if year_select:
|
|
logger.info(f"Wähle Jahr: {birthday['year']}")
|
|
page.select_option(year_selector, str(birthday['year']))
|
|
self.automation.human_behavior.random_delay(0.5, 1)
|
|
|
|
# Screenshot nach Geburtstagseingabe
|
|
self.automation._take_screenshot("birthday_page")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Eingeben des Geburtsdatums: {e}")
|
|
return False
|
|
|
|
def _click_next_after_birthday(self) -> bool:
|
|
"""
|
|
Klickt auf den Weiter-Button nach der Geburtstagseingabe.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Weiter-Button klicken
|
|
next_button = 'button[data-testid="ocfSignupNextLink"]'
|
|
|
|
logger.info("Klicke auf Weiter nach Geburtsdatum")
|
|
page.wait_for_selector(next_button, timeout=5000)
|
|
page.click(next_button)
|
|
|
|
self.automation.human_behavior.random_delay(1, 2)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Klicken auf Weiter nach Geburtsdatum: {e}")
|
|
return False
|
|
|
|
def _click_next_settings(self) -> bool:
|
|
"""
|
|
Klickt auf den zweiten Weiter-Button (Einstellungen).
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Zweiter Weiter-Button
|
|
next_button = 'button[data-testid="ocfSettingsListNextButton"]'
|
|
|
|
logger.info("Klicke auf Weiter in den Einstellungen")
|
|
page.wait_for_selector(next_button, timeout=5000)
|
|
page.click(next_button)
|
|
|
|
self.automation.human_behavior.random_delay(2, 3)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Klicken auf Weiter in Einstellungen: {e}")
|
|
return False
|
|
|
|
def _get_verification_code(self, email: str) -> Optional[str]:
|
|
"""
|
|
Holt den Verifizierungscode aus der E-Mail.
|
|
|
|
Args:
|
|
email: E-Mail-Adresse
|
|
|
|
Returns:
|
|
Optional[str]: Verifizierungscode oder None
|
|
"""
|
|
try:
|
|
logger.info(f"Warte auf Verifizierungs-E-Mail für {email}")
|
|
|
|
# Debug: Log die E-Mail-Adresse
|
|
logger.info(f"Rufe Verifizierungscode ab für E-Mail: {email}")
|
|
logger.info(f"Platform: x, Domain: {email.split('@')[1] if '@' in email else 'unknown'}")
|
|
|
|
# Verwende die gleiche Methode wie Instagram
|
|
verification_code = self.automation.email_handler.get_verification_code(
|
|
target_email=email,
|
|
platform="x", # Platform name für X (nicht "twitter"!)
|
|
max_attempts=90, # 90 Versuche * 2 Sekunden = 180 Sekunden (3 Minuten)
|
|
delay_seconds=2
|
|
)
|
|
|
|
if verification_code:
|
|
logger.info(f"Verifizierungscode erhalten: {verification_code}")
|
|
return verification_code
|
|
else:
|
|
logger.error("Konnte keinen Verifizierungscode erhalten")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Abrufen des Verifizierungscodes: {e}")
|
|
return None
|
|
|
|
def _enter_verification_code(self, code: str) -> bool:
|
|
"""
|
|
Gibt den Verifizierungscode ein.
|
|
|
|
Args:
|
|
code: Verifizierungscode
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Warte kurz, damit die Seite sich aktualisiert
|
|
logger.info("Warte auf das Erscheinen des Verifizierungsfeldes...")
|
|
self.automation.human_behavior.random_delay(2, 3)
|
|
|
|
# Screenshot um zu sehen was auf der Seite ist
|
|
self.automation._take_screenshot("verification_page_state")
|
|
|
|
# Log alle sichtbaren Input-Felder
|
|
try:
|
|
all_inputs = page.query_selector_all('input:visible')
|
|
logger.info(f"Gefundene sichtbare Input-Felder: {len(all_inputs)}")
|
|
for i, input_elem in enumerate(all_inputs):
|
|
input_type = input_elem.get_attribute('type') or 'text'
|
|
input_name = input_elem.get_attribute('name') or 'unnamed'
|
|
input_placeholder = input_elem.get_attribute('placeholder') or 'no placeholder'
|
|
input_testid = input_elem.get_attribute('data-testid') or 'no testid'
|
|
logger.info(f"Input {i}: type={input_type}, name={input_name}, placeholder={input_placeholder}, data-testid={input_testid}")
|
|
except Exception as e:
|
|
logger.debug(f"Konnte Input-Felder nicht loggen: {e}")
|
|
|
|
# Verifizierungscode-Eingabefeld mit mehreren Selektoren
|
|
code_selectors = [
|
|
# X-spezifische Selektoren
|
|
'input[data-testid="ocfEnterTextTextInput"]',
|
|
'input[autocomplete="one-time-code"]',
|
|
'input[name="verification_code"]',
|
|
'input[name="code"]',
|
|
# Placeholder-basierte Selektoren
|
|
'input[placeholder*="Code"]',
|
|
'input[placeholder*="code"]',
|
|
'input[placeholder*="Gib"]',
|
|
'input[placeholder*="Enter"]',
|
|
# Type-basierte Selektoren
|
|
'input[type="text"]:not([name="name"]):not([name="email"]):not([type="password"])',
|
|
'input[type="text"][data-testid*="verification"]',
|
|
'input[type="number"]',
|
|
# Allgemeine Selektoren
|
|
'input:visible:not([name="name"]):not([name="email"]):not([type="password"])',
|
|
'div[role="textbox"] input',
|
|
# Nth-child falls es das dritte Input-Feld ist
|
|
'input[type="text"]:nth-of-type(3)',
|
|
'input:nth-of-type(3)'
|
|
]
|
|
|
|
# Prüfe ob wir vielleicht noch auf der vorherigen Seite sind
|
|
if page.is_visible('button[data-testid="ocfSettingsListNextButton"]'):
|
|
logger.info("Noch auf Einstellungen-Seite, klicke erneut auf Weiter")
|
|
try:
|
|
page.click('button[data-testid="ocfSettingsListNextButton"]')
|
|
self.automation.human_behavior.random_delay(2, 3)
|
|
except:
|
|
pass
|
|
|
|
code_entered = False
|
|
# Versuche zuerst, das neueste Input-Feld zu finden
|
|
try:
|
|
# Finde alle Input-Felder und nimm das letzte/neueste
|
|
all_inputs = page.query_selector_all('input[type="text"]:visible, input[type="tel"]:visible, input:not([type]):visible')
|
|
if all_inputs and len(all_inputs) > 0:
|
|
# Nimm das letzte sichtbare Input-Feld
|
|
last_input = all_inputs[-1]
|
|
logger.info(f"Versuche letztes Input-Feld ({len(all_inputs)} gefunden)")
|
|
|
|
# Klicke und fülle es
|
|
last_input.click()
|
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
|
last_input.fill('')
|
|
|
|
# Tippe den Code
|
|
for char in code:
|
|
last_input.type(char)
|
|
self.automation.human_behavior.random_delay(0.05, 0.15)
|
|
|
|
logger.info("Verifizierungscode in letztes Input-Feld eingegeben")
|
|
code_entered = True
|
|
self.automation.human_behavior.random_delay(0.5, 1)
|
|
except Exception as e:
|
|
logger.debug(f"Konnte nicht über letztes Input-Feld eingeben: {e}")
|
|
|
|
# Falls das nicht funktioniert hat, versuche die spezifischen Selektoren
|
|
if not code_entered:
|
|
for selector in code_selectors:
|
|
try:
|
|
element = page.wait_for_selector(selector, timeout=2000, state="visible")
|
|
if element:
|
|
logger.info(f"Verifizierungscode-Input gefunden mit Selektor: {selector}")
|
|
# Klicke zuerst auf das Feld
|
|
element.click()
|
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
|
|
|
# Lösche eventuell vorhandenen Inhalt
|
|
element.fill('')
|
|
|
|
# Tippe den Code langsam ein
|
|
for char in code:
|
|
element.type(char)
|
|
self.automation.human_behavior.random_delay(0.05, 0.15) # Zwischen Zeichen
|
|
|
|
logger.info(f"Verifizierungscode eingegeben: {code}")
|
|
code_entered = True
|
|
self.automation.human_behavior.random_delay(0.5, 1)
|
|
break
|
|
except Exception as e:
|
|
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
|
|
continue
|
|
|
|
if not code_entered:
|
|
logger.error("Konnte Verifizierungscode nicht eingeben")
|
|
return False
|
|
|
|
# Weiter klicken - NACH erfolgreicher Code-Eingabe
|
|
logger.info("Suche nach Weiter-Button...")
|
|
next_selectors = [
|
|
'button:has-text("Weiter")',
|
|
'text="Weiter"',
|
|
'button:has-text("Next")', # Englische Version
|
|
'text="Next"',
|
|
'div[role="button"]:has-text("Weiter")',
|
|
'div[role="button"]:has-text("Next")',
|
|
'[data-testid*="next"]',
|
|
'button[type="submit"]',
|
|
# X-spezifische Selektoren
|
|
'button[data-testid="LoginForm_Login_Button"]',
|
|
'div[data-testid="LoginForm_Login_Button"]'
|
|
]
|
|
|
|
next_clicked = False
|
|
for selector in next_selectors:
|
|
try:
|
|
element = page.wait_for_selector(selector, timeout=2000, state="visible")
|
|
if element:
|
|
logger.info(f"Weiter-Button gefunden mit Selektor: {selector}")
|
|
page.click(selector)
|
|
self.automation.human_behavior.random_delay(1, 2)
|
|
next_clicked = True
|
|
break
|
|
except Exception as e:
|
|
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
|
|
continue
|
|
|
|
if not next_clicked:
|
|
logger.warning("Weiter-Button nach Verifizierungscode nicht gefunden")
|
|
# Screenshot für Debugging
|
|
self.automation._take_screenshot("no_next_button_after_code")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Eingeben des Verifizierungscodes: {e}")
|
|
return False
|
|
|
|
def _enter_password(self, password: str) -> bool:
|
|
"""
|
|
Legt das Passwort fest.
|
|
|
|
Args:
|
|
password: Passwort
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Screenshot vor Passwort-Eingabe
|
|
self.automation._take_screenshot("before_password_input")
|
|
|
|
# Passwort-Eingabefeld mit mehreren Selektoren
|
|
password_selectors = [
|
|
'input[type="password"]',
|
|
'input[name="password"]',
|
|
'input[autocomplete="new-password"]',
|
|
'input[autocomplete="current-password"]',
|
|
'input[placeholder*="Passwort"]',
|
|
'input[placeholder*="Password"]'
|
|
]
|
|
|
|
password_entered = False
|
|
for selector in password_selectors:
|
|
try:
|
|
element = page.wait_for_selector(selector, timeout=3000, state="visible")
|
|
if element:
|
|
logger.info(f"Passwort-Input gefunden mit Selektor: {selector}")
|
|
# Klicke zuerst auf das Feld
|
|
element.click()
|
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
|
|
|
# Lösche eventuell vorhandenen Inhalt
|
|
element.fill('')
|
|
|
|
# Tippe das Passwort langsam ein
|
|
for char in password:
|
|
element.type(char)
|
|
self.automation.human_behavior.random_delay(0.05, 0.15) # Zwischen Zeichen
|
|
|
|
logger.info("Passwort eingegeben")
|
|
password_entered = True
|
|
self.automation.human_behavior.random_delay(0.5, 1)
|
|
break
|
|
except Exception as e:
|
|
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
|
|
continue
|
|
|
|
if not password_entered:
|
|
logger.error("Konnte Passwort nicht eingeben")
|
|
return False
|
|
|
|
# Registrieren-Button klicken
|
|
logger.info("Suche nach Registrieren-Button...")
|
|
register_selectors = [
|
|
'button[data-testid="LoginForm_Login_Button"]',
|
|
'button:has-text("Registrieren")',
|
|
'button:has-text("Sign up")',
|
|
'button:has-text("Create account")',
|
|
'button:has-text("Weiter")',
|
|
'button:has-text("Next")',
|
|
'button[type="submit"]',
|
|
'div[role="button"]:has-text("Registrieren")'
|
|
]
|
|
|
|
register_clicked = False
|
|
for selector in register_selectors:
|
|
try:
|
|
element = page.wait_for_selector(selector, timeout=2000, state="visible")
|
|
if element:
|
|
logger.info(f"Registrieren-Button gefunden mit Selektor: {selector}")
|
|
page.click(selector)
|
|
self.automation.human_behavior.random_delay(2, 3)
|
|
register_clicked = True
|
|
break
|
|
except Exception as e:
|
|
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
|
|
continue
|
|
|
|
if not register_clicked:
|
|
logger.warning("Registrieren-Button nicht gefunden")
|
|
# Screenshot für Debugging
|
|
self.automation._take_screenshot("no_register_button")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Festlegen des Passworts: {e}")
|
|
return False
|
|
|
|
def _skip_profile_picture(self) -> bool:
|
|
"""
|
|
Überspringt den Profilbild-Schritt.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# "Vorerst überspringen" Button
|
|
skip_button = page.wait_for_selector('button[data-testid="ocfSelectAvatarSkipForNowButton"]', timeout=5000)
|
|
|
|
if skip_button:
|
|
logger.info("Überspringe Profilbild")
|
|
skip_button.click()
|
|
self.automation.human_behavior.random_delay(1, 2)
|
|
return True
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.debug(f"Konnte Profilbild nicht überspringen: {e}")
|
|
return False
|
|
|
|
def _skip_username(self) -> bool:
|
|
"""
|
|
Liest den von X generierten Benutzernamen aus und überspringt dann den Schritt.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Zuerst den generierten Benutzernamen auslesen
|
|
username_input = page.wait_for_selector('input[name="username"]', timeout=5000)
|
|
|
|
if username_input:
|
|
# Benutzername aus dem value-Attribut auslesen
|
|
generated_username = username_input.get_attribute("value")
|
|
if generated_username:
|
|
logger.info(f"Von X generierter Benutzername ausgelesen: {generated_username}")
|
|
# Den generierten Benutzernamen in den Account-Daten speichern
|
|
if hasattr(self, '_current_account_data'):
|
|
self._current_account_data['username'] = generated_username
|
|
self._current_account_data['x_generated_username'] = generated_username
|
|
else:
|
|
logger.warning("Konnte Account-Daten nicht aktualisieren")
|
|
else:
|
|
logger.warning("Konnte Benutzernamen nicht aus Input-Feld auslesen")
|
|
|
|
# Dann den "Nicht jetzt" Button klicken
|
|
skip_button = page.wait_for_selector('button[data-testid="ocfEnterUsernameSkipButton"]', timeout=5000)
|
|
|
|
if skip_button:
|
|
logger.info("Überspringe Benutzername-Änderung")
|
|
skip_button.click()
|
|
self.automation.human_behavior.random_delay(1, 2)
|
|
return True
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.debug(f"Fehler beim Benutzername-Schritt: {e}")
|
|
return False
|
|
|
|
def _skip_notifications(self) -> bool:
|
|
"""
|
|
Überspringt die Benachrichtigungseinstellungen.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# "Vorerst überspringen" Button für Benachrichtigungen
|
|
skip_selectors = [
|
|
'div[dir="ltr"]:has-text("Vorerst überspringen")',
|
|
'div.css-146c3p1:has-text("Vorerst überspringen")',
|
|
'button:has-text("Vorerst überspringen")',
|
|
'text="Vorerst überspringen"',
|
|
'span:has-text("Vorerst überspringen")',
|
|
'[role="button"]:has-text("Vorerst überspringen")'
|
|
]
|
|
|
|
skip_button = None
|
|
for selector in skip_selectors:
|
|
try:
|
|
skip_button = page.wait_for_selector(selector, timeout=1000)
|
|
if skip_button:
|
|
logger.info(f"'Vorerst überspringen' Button gefunden mit Selektor: {selector}")
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if skip_button:
|
|
logger.info("Überspringe Benachrichtigungen")
|
|
# Warte kurz, damit der Button wirklich klickbar ist
|
|
self.automation.human_behavior.random_delay(0.5, 1)
|
|
skip_button.click()
|
|
self.automation.human_behavior.random_delay(1, 2)
|
|
return True
|
|
else:
|
|
logger.debug("'Vorerst überspringen' Button nicht gefunden")
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.debug(f"Konnte Benachrichtigungen nicht überspringen: {e}")
|
|
return False
|
|
|
|
def _check_registration_success(self) -> bool:
|
|
"""
|
|
Überprüft, ob die Registrierung erfolgreich war.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg, False bei Fehler
|
|
"""
|
|
try:
|
|
page = self.automation.browser.page
|
|
|
|
# Warte etwas
|
|
self.automation.human_behavior.random_delay(2, 3)
|
|
|
|
# Prüfe auf Zeichen erfolgreicher Registrierung
|
|
# Z.B. Home-Timeline, Tweet-Button, etc.
|
|
success_indicators = [
|
|
'a[href="/home"]',
|
|
'a[data-testid="SideNav_NewTweet_Button"]',
|
|
'button[data-testid="tweetButtonInline"]',
|
|
'nav[aria-label="Primary"]'
|
|
]
|
|
|
|
for indicator in success_indicators:
|
|
try:
|
|
# Warte kurz und prüfe dann ohne timeout
|
|
page.wait_for_timeout(1000)
|
|
if page.is_visible(indicator):
|
|
logger.info(f"Registrierung erfolgreich - Indikator gefunden: {indicator}")
|
|
|
|
# Finaler Screenshot
|
|
self.automation._take_screenshot("registration_final")
|
|
|
|
return True
|
|
except:
|
|
continue
|
|
|
|
logger.error("Keine Erfolgsindikatoren gefunden")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Überprüfen des Registrierungserfolgs: {e}")
|
|
return False |