1063 Zeilen
46 KiB
Python
1063 Zeilen
46 KiB
Python
# social_networks/facebook/facebook_registration.py
|
|
|
|
"""
|
|
Facebook-Registrierung - Klasse für die Registrierungsfunktionalität bei Facebook
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
import re
|
|
from typing import Dict, List, Any, Optional, Tuple
|
|
|
|
from .facebook_selectors import FacebookSelectors
|
|
from .facebook_workflow import FacebookWorkflow
|
|
from utils.logger import setup_logger
|
|
|
|
# Konfiguriere Logger
|
|
logger = setup_logger("facebook_registration")
|
|
|
|
class FacebookRegistration:
|
|
"""
|
|
Klasse für die Registrierung neuer Facebook-Konten.
|
|
Enthält alle Methoden für den Registrierungsprozess.
|
|
"""
|
|
|
|
def __init__(self, automation):
|
|
"""
|
|
Initialisiert die Facebook-Registrierungs-Funktionalität.
|
|
|
|
Args:
|
|
automation: Referenz auf die Hauptautomatisierungsklasse
|
|
"""
|
|
self.automation = automation
|
|
self.selectors = FacebookSelectors()
|
|
self.workflow = FacebookWorkflow.get_registration_workflow()
|
|
|
|
logger.debug("Facebook-Registrierung initialisiert")
|
|
|
|
def register_account(self, account_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Führt den Registrierungsprozess für einen neuen Facebook-Account durch.
|
|
|
|
Args:
|
|
account_data: Dictionary mit Account-Daten
|
|
|
|
Returns:
|
|
Dict[str, Any]: Ergebnis der Registrierung
|
|
"""
|
|
logger.info(f"Starte Facebook-Registrierung für {account_data['first_name']} {account_data['last_name']}")
|
|
|
|
try:
|
|
# 1. Zur Facebook-Hauptseite navigieren
|
|
self.automation._send_status_update("Öffne Facebook-Webseite")
|
|
self.automation._send_log_update("Navigiere zu Facebook...")
|
|
|
|
if not self._navigate_to_facebook():
|
|
return {
|
|
"success": False,
|
|
"error": "Konnte nicht zu Facebook navigieren",
|
|
"stage": "navigation"
|
|
}
|
|
|
|
# 2. Cookie-Consent behandeln
|
|
self.automation._send_status_update("Behandle Cookie-Einstellungen")
|
|
self.automation._send_log_update("Lehne optionale Cookies ab...")
|
|
|
|
if not self._handle_cookie_consent():
|
|
logger.warning("Cookie-Consent konnte nicht behandelt werden, fahre trotzdem fort")
|
|
|
|
# 3. Registrierungsformular öffnen
|
|
self.automation._send_status_update("Öffne Registrierungsformular")
|
|
self.automation._send_log_update("Klicke auf 'Neues Konto erstellen'...")
|
|
|
|
if not self._open_registration_form():
|
|
return {
|
|
"success": False,
|
|
"error": "Konnte Registrierungsformular nicht öffnen",
|
|
"stage": "open_form"
|
|
}
|
|
|
|
# 4. Registrierungsformular ausfüllen
|
|
self.automation._send_status_update("Fülle Registrierungsformular aus")
|
|
self.automation._send_log_update("Gebe persönliche Daten ein...")
|
|
|
|
if not self._fill_registration_form(account_data):
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler beim Ausfüllen des Registrierungsformulars",
|
|
"stage": "fill_form"
|
|
}
|
|
|
|
# 5. Formular absenden
|
|
self.automation._send_status_update("Sende Registrierung ab")
|
|
self.automation._send_log_update("Klicke auf 'Registrieren'...")
|
|
|
|
if not self._submit_registration():
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler beim Absenden der Registrierung",
|
|
"stage": "submit"
|
|
}
|
|
|
|
# 6. E-Mail-Verifikation behandeln
|
|
needs_verification = self._check_needs_verification()
|
|
|
|
if needs_verification:
|
|
self.automation._send_status_update("E-Mail-Verifikation erforderlich")
|
|
self.automation._send_log_update("Warte auf Verifikationscode...")
|
|
|
|
# Warte auf Verifikationscode
|
|
verification_code = self._wait_for_verification_code(account_data.get("email"))
|
|
|
|
if verification_code:
|
|
if not self._enter_verification_code(verification_code):
|
|
return {
|
|
"success": False,
|
|
"error": "Fehler bei der E-Mail-Verifikation",
|
|
"stage": "verification"
|
|
}
|
|
else:
|
|
return {
|
|
"success": False,
|
|
"error": "Verifikationscode nicht erhalten",
|
|
"stage": "verification_timeout"
|
|
}
|
|
|
|
# 7. Erfolgreiche Registrierung überprüfen
|
|
success_check = self._check_registration_success()
|
|
|
|
# 8. Wenn nicht erfolgreich: Prüfe auf Cookie-Consent oder andere Zwischen-Schritte
|
|
if not success_check:
|
|
current_url = self.automation.browser.page.url
|
|
logger.info(f"Success-Check fehlgeschlagen, prüfe Zwischen-Schritte: {current_url}")
|
|
|
|
# Behandle Cookie-Consent falls vorhanden
|
|
if "privacy/consent" in current_url or "user_cookie_choice" in current_url:
|
|
logger.info("Cookie-Consent Dialog erkannt (nach Verifikation)")
|
|
self.automation._send_log_update("Behandle Cookie-Einstellungen...")
|
|
|
|
if self._handle_cookie_consent():
|
|
logger.info("Cookie-Consent erfolgreich behandelt")
|
|
# Warte nach Cookie-Ablehnung
|
|
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
|
|
|
|
# Prüfe auf finalen Dialog/Layer mit "Schließen"-Button
|
|
if self._handle_final_dialogs():
|
|
logger.info("Finale Dialogs behandelt")
|
|
|
|
# Erneuter Success-Check
|
|
success_check = self._check_registration_success()
|
|
|
|
if not success_check:
|
|
return {
|
|
"success": False,
|
|
"error": "Registrierung fehlgeschlagen (nach Zwischen-Schritten)",
|
|
"stage": "final_check"
|
|
}
|
|
|
|
# Registrierung erfolgreich
|
|
logger.info(f"Facebook-Account erfolgreich erstellt")
|
|
self.automation._send_status_update("Registrierung erfolgreich!")
|
|
self.automation._send_log_update("Account wurde erfolgreich erstellt")
|
|
|
|
# Account-Daten für Rückgabe vorbereiten
|
|
account_data["platform"] = "facebook"
|
|
account_data["created_at"] = time.time()
|
|
|
|
# Username generieren falls nicht vorhanden (Facebook gibt keinen zurück)
|
|
if not account_data.get("username"):
|
|
username = self._generate_username_from_email(
|
|
account_data.get("email", ""),
|
|
account_data.get("first_name", ""),
|
|
account_data.get("last_name", "")
|
|
)
|
|
# Username ist GARANTIERT vorhanden (Fallback-Strategien)
|
|
account_data["username"] = username
|
|
logger.info(f"Username generiert: {username}")
|
|
|
|
# KRITISCHE VALIDIERUNG: Stelle sicher dass Username vorhanden ist
|
|
if not account_data.get("username") or account_data["username"] == "":
|
|
error_msg = "KRITISCHER FEHLER: Username konnte nicht generiert werden"
|
|
logger.error(error_msg)
|
|
logger.error(f"Account-Daten: {account_data}")
|
|
return {
|
|
"success": False,
|
|
"error": error_msg,
|
|
"stage": "username_validation_failed",
|
|
"account_data": account_data
|
|
}
|
|
|
|
return {
|
|
"success": True,
|
|
"stage": "completed",
|
|
"account_data": account_data
|
|
}
|
|
|
|
except Exception as e:
|
|
error_msg = f"Unerwarteter Fehler bei der Facebook-Registrierung: {str(e)}"
|
|
logger.error(error_msg, exc_info=True)
|
|
|
|
return {
|
|
"success": False,
|
|
"error": error_msg,
|
|
"stage": "exception",
|
|
"account_data": account_data
|
|
}
|
|
|
|
def _navigate_to_facebook(self) -> bool:
|
|
"""
|
|
Navigiert zur Facebook-Hauptseite.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg
|
|
"""
|
|
try:
|
|
logger.info(f"Navigiere zu {self.automation.base_url}")
|
|
|
|
# Navigiere zur Facebook-Seite
|
|
navigation_success = self.automation.browser.navigate_to(self.automation.base_url)
|
|
|
|
if not navigation_success:
|
|
logger.error(f"Navigation zu {self.automation.base_url} fehlgeschlagen")
|
|
return False
|
|
|
|
# Warte auf Seitenladung
|
|
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
|
|
|
|
# Screenshot
|
|
self.automation._take_screenshot("facebook_homepage")
|
|
|
|
# Prüfe ob wir auf Facebook sind
|
|
current_url = self.automation.browser.page.url
|
|
logger.info(f"Aktuelle URL nach Navigation: {current_url}")
|
|
|
|
# Debug: Prüfe sichtbare Elemente
|
|
logger.debug(f"Sichtbare Buttons: {self.automation.browser.page.locator('button').count()}")
|
|
logger.debug(f"Sichtbare Links: {self.automation.browser.page.locator('a').count()}")
|
|
|
|
if "facebook.com" in current_url:
|
|
logger.info("Erfolgreich zu Facebook navigiert")
|
|
return True
|
|
else:
|
|
logger.error(f"Nicht auf Facebook gelandet: {current_url}")
|
|
# Zusätzliche Debug-Info
|
|
page_title = self.automation.browser.page.title()
|
|
logger.debug(f"Seiten-Titel: {page_title}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei Navigation zu Facebook: {e}")
|
|
return False
|
|
|
|
def _handle_cookie_consent(self) -> bool:
|
|
"""
|
|
Behandelt den Cookie-Consent-Dialog.
|
|
|
|
Returns:
|
|
bool: True wenn behandelt
|
|
"""
|
|
try:
|
|
logger.info("Prüfe auf Cookie-Consent-Dialog")
|
|
|
|
# Warte kurz auf möglichen Cookie-Dialog
|
|
self.automation.human_behavior.random_delay(1.0, 2.0)
|
|
|
|
# Versuche "Optionale Cookies ablehnen" zu klicken
|
|
decline_clicked = False
|
|
|
|
# Methode 1: Direkter Selektor
|
|
if self.automation.browser.is_element_visible(self.selectors.COOKIE_DECLINE_BUTTON, timeout=2000):
|
|
if self.automation.browser.click_element(self.selectors.COOKIE_DECLINE_BUTTON):
|
|
logger.info("Cookie-Consent abgelehnt (Methode 1)")
|
|
decline_clicked = True
|
|
|
|
# Methode 2: Alternative Selektoren
|
|
if not decline_clicked:
|
|
for selector in [self.selectors.COOKIE_DECLINE_BUTTON_ALT,
|
|
"button:has-text('Optionale Cookies ablehnen')",
|
|
"//button[contains(., 'Optionale Cookies ablehnen')]"]:
|
|
try:
|
|
if self.automation.browser.click_element(selector, timeout=1000):
|
|
logger.info(f"Cookie-Consent abgelehnt mit Selektor: {selector}")
|
|
decline_clicked = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
# Methode 3: Fuzzy Button Click
|
|
if not decline_clicked:
|
|
if self.automation.ui_helper.click_button_fuzzy(
|
|
self.selectors.get_button_texts("decline_cookies"),
|
|
fallback_selector="button"
|
|
):
|
|
logger.info("Cookie-Consent abgelehnt (Fuzzy Match)")
|
|
decline_clicked = True
|
|
|
|
if decline_clicked:
|
|
# Warte auf Dialog-Schließung
|
|
self.automation.human_behavior.random_delay(1.0, 2.0)
|
|
self.automation._take_screenshot("after_cookie_consent")
|
|
return True
|
|
else:
|
|
logger.debug("Kein Cookie-Consent-Dialog gefunden oder bereits behandelt")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei Cookie-Consent-Behandlung: {e}")
|
|
return False
|
|
|
|
def _open_registration_form(self) -> bool:
|
|
"""
|
|
Öffnet das Registrierungsformular.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg
|
|
"""
|
|
try:
|
|
logger.info("Öffne Registrierungsformular")
|
|
|
|
# Versuche "Neues Konto erstellen" zu klicken
|
|
button_clicked = False
|
|
|
|
# Methode 1: data-testid Selektor
|
|
if self.automation.browser.is_element_visible(self.selectors.CREATE_ACCOUNT_BUTTON, timeout=3000):
|
|
if self.automation.browser.click_element(self.selectors.CREATE_ACCOUNT_BUTTON):
|
|
logger.info("Registrierungsformular geöffnet (data-testid)")
|
|
button_clicked = True
|
|
|
|
# Methode 2: Alternative Selektoren
|
|
if not button_clicked:
|
|
for selector in [self.selectors.CREATE_ACCOUNT_BUTTON_ALT,
|
|
"a:has-text('Neues Konto erstellen')",
|
|
"a[href*='/r.php']"]:
|
|
try:
|
|
if self.automation.browser.click_element(selector, timeout=2000):
|
|
logger.info(f"Registrierungsformular geöffnet mit: {selector}")
|
|
button_clicked = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
# Methode 3: Fuzzy Button Click
|
|
if not button_clicked:
|
|
if self.automation.ui_helper.click_button_fuzzy(
|
|
self.selectors.get_button_texts("create_account")
|
|
):
|
|
logger.info("Registrierungsformular geöffnet (Fuzzy Match)")
|
|
button_clicked = True
|
|
|
|
if button_clicked:
|
|
# Warte auf Formular-Ladung mit längerer Wartezeit
|
|
self.automation.human_behavior.wait_for_page_load(multiplier=2.0)
|
|
|
|
# Extra Wartezeit für Facebook's React-Rendering
|
|
self.automation.human_behavior.random_delay(2.0, 3.0)
|
|
|
|
# Prüfe ob Browser noch aktiv ist
|
|
if not self.automation.browser or not self.automation.browser.page:
|
|
logger.error("Browser wurde unerwartet geschlossen")
|
|
return False
|
|
|
|
try:
|
|
self.automation._take_screenshot("registration_form")
|
|
except Exception as e:
|
|
logger.warning(f"Screenshot fehlgeschlagen: {e}")
|
|
|
|
# Prüfe ob wir auf der Registrierungsseite sind
|
|
try:
|
|
current_url = self.automation.browser.page.url
|
|
if "/r.php" in current_url or "registration" in current_url:
|
|
logger.info("Registrierungsformular erfolgreich geöffnet")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Abrufen der URL: {e}")
|
|
return False
|
|
|
|
logger.error("Konnte Registrierungsformular nicht öffnen")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Öffnen des Registrierungsformulars: {e}")
|
|
return False
|
|
|
|
def _fill_registration_form(self, account_data: Dict[str, Any]) -> bool:
|
|
"""
|
|
Füllt das Registrierungsformular aus.
|
|
Verbesserte Version mit Fallback-Selektoren und robusterem Handling.
|
|
|
|
Args:
|
|
account_data: Account-Daten
|
|
|
|
Returns:
|
|
bool: True bei Erfolg
|
|
"""
|
|
try:
|
|
logger.info("Fülle Registrierungsformular aus")
|
|
|
|
# Prüfe ob Browser noch aktiv ist
|
|
if not self.automation.browser or not self.automation.browser.page:
|
|
logger.error("Browser ist nicht mehr aktiv vor Formular-Ausfüllung")
|
|
return False
|
|
|
|
# Debug: Prüfe welche Input-Felder sichtbar sind
|
|
try:
|
|
visible_inputs = self.automation.browser.page.locator("input[type='text']").count()
|
|
logger.debug(f"Anzahl sichtbarer Text-Input-Felder: {visible_inputs}")
|
|
|
|
# Liste alle sichtbaren Inputs mit Namen auf
|
|
all_inputs = self.automation.browser.page.locator("input").all()
|
|
for idx, inp in enumerate(all_inputs[:10]): # Nur erste 10 zur Sicherheit
|
|
try:
|
|
name = inp.get_attribute("name")
|
|
placeholder = inp.get_attribute("placeholder")
|
|
aria_label = inp.get_attribute("aria-label")
|
|
logger.debug(f"Input {idx}: name='{name}', placeholder='{placeholder}', aria-label='{aria_label}'")
|
|
except:
|
|
pass
|
|
except Exception as e:
|
|
logger.warning(f"Debug-Ausgabe fehlgeschlagen: {e}")
|
|
|
|
# Vorname mit Fallback
|
|
firstname_selectors = [
|
|
self.selectors.REG_FIRSTNAME_FIELD,
|
|
self.selectors.REG_FIRSTNAME_FIELD_ALT,
|
|
"input[placeholder*='Vorname']",
|
|
"input[name='firstname']", # Direkter Name-Selektor
|
|
"input[aria-label*='Vorname' i]" # Case-insensitive aria-label
|
|
]
|
|
firstname_filled = False
|
|
for selector in firstname_selectors:
|
|
logger.debug(f"Versuche Vorname-Selektor: {selector}")
|
|
try:
|
|
if self.automation.browser.is_element_visible(selector, timeout=1000):
|
|
logger.debug(f"Selektor {selector} ist sichtbar")
|
|
if self.automation.ui_helper.type_text_human_like(selector, account_data["first_name"]):
|
|
logger.info(f"Vorname eingegeben mit: {selector}")
|
|
firstname_filled = True
|
|
break
|
|
else:
|
|
logger.debug(f"Selektor {selector} nicht sichtbar")
|
|
except Exception as e:
|
|
logger.debug(f"Fehler bei Selektor {selector}: {e}")
|
|
|
|
if not firstname_filled:
|
|
logger.error(f"Fehler beim Eingeben des Vornamens. Getestete Selektoren: {firstname_selectors}")
|
|
# Zusätzliche Debug-Info
|
|
try:
|
|
current_url = self.automation.browser.page.url
|
|
logger.error(f"Aktuelle URL beim Fehler: {current_url}")
|
|
except:
|
|
pass
|
|
return False
|
|
|
|
# Nachname mit Fallback
|
|
lastname_selectors = [
|
|
self.selectors.REG_LASTNAME_FIELD,
|
|
self.selectors.REG_LASTNAME_FIELD_ALT,
|
|
"input[placeholder*='Nachname']"
|
|
]
|
|
lastname_filled = False
|
|
for selector in lastname_selectors:
|
|
if self.automation.browser.is_element_visible(selector, timeout=1000):
|
|
if self.automation.ui_helper.type_text_human_like(selector, account_data["last_name"]):
|
|
logger.info(f"Nachname eingegeben mit: {selector}")
|
|
lastname_filled = True
|
|
break
|
|
|
|
if not lastname_filled:
|
|
logger.error("Fehler beim Eingeben des Nachnamens")
|
|
return False
|
|
|
|
# Geburtsdatum - Verbesserte Dropdown-Behandlung
|
|
birth_date = account_data["birth_date"]
|
|
|
|
# Tag auswählen mit Fallback
|
|
day_selectors = [
|
|
self.selectors.REG_BIRTHDAY_DAY,
|
|
self.selectors.REG_BIRTHDAY_DAY_ALT,
|
|
"select[aria-label='Tag']",
|
|
"select[title='Tag']"
|
|
]
|
|
day_selected = False
|
|
for selector in day_selectors:
|
|
try:
|
|
if self.automation.browser.select_option(selector, str(birth_date["day"])):
|
|
logger.info(f"Tag {birth_date['day']} ausgewählt mit: {selector}")
|
|
day_selected = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if not day_selected:
|
|
logger.error("Fehler beim Auswählen des Geburtstags")
|
|
return False
|
|
|
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
|
|
|
# Monat auswählen mit Fallback
|
|
month_selectors = [
|
|
self.selectors.REG_BIRTHDAY_MONTH,
|
|
self.selectors.REG_BIRTHDAY_MONTH_ALT,
|
|
"select[aria-label='Monat']",
|
|
"select[title='Monat']"
|
|
]
|
|
month_selected = False
|
|
for selector in month_selectors:
|
|
try:
|
|
if self.automation.browser.select_option(selector, str(birth_date["month"])):
|
|
logger.info(f"Monat {birth_date['month']} ausgewählt mit: {selector}")
|
|
month_selected = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if not month_selected:
|
|
logger.error("Fehler beim Auswählen des Geburtsmonats")
|
|
return False
|
|
|
|
self.automation.human_behavior.random_delay(0.3, 0.5)
|
|
|
|
# Jahr auswählen mit Fallback
|
|
year_selectors = [
|
|
self.selectors.REG_BIRTHDAY_YEAR,
|
|
self.selectors.REG_BIRTHDAY_YEAR_ALT,
|
|
"select[aria-label='Jahr']",
|
|
"select[title='Jahr']"
|
|
]
|
|
year_selected = False
|
|
for selector in year_selectors:
|
|
try:
|
|
if self.automation.browser.select_option(selector, str(birth_date["year"])):
|
|
logger.info(f"Jahr {birth_date['year']} ausgewählt mit: {selector}")
|
|
year_selected = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if not year_selected:
|
|
logger.error("Fehler beim Auswählen des Geburtsjahrs")
|
|
return False
|
|
|
|
self.automation.human_behavior.random_delay(0.5, 1.0)
|
|
|
|
# Geschlecht auswählen - Verbesserte Radio-Button Behandlung
|
|
gender_selector = self.selectors.get_gender_selector(account_data["gender"])
|
|
gender_selected = False
|
|
|
|
# Versuche erst direkten Click auf das Input-Element
|
|
if self.automation.browser.click_element(gender_selector):
|
|
logger.info(f"Geschlecht direkt ausgewählt: {account_data['gender']}")
|
|
gender_selected = True
|
|
else:
|
|
# Fallback: Klicke auf das Label
|
|
label_selectors = [
|
|
f"label:has-text('{'Weiblich' if account_data['gender'] == 'female' else 'Männlich' if account_data['gender'] == 'male' else 'Divers'}')",
|
|
f"label._58mt:has(input[value='{'1' if account_data['gender'] == 'female' else '2' if account_data['gender'] == 'male' else '-1'}'])"
|
|
]
|
|
for selector in label_selectors:
|
|
try:
|
|
if self.automation.browser.click_element(selector):
|
|
logger.info(f"Geschlecht über Label ausgewählt: {account_data['gender']}")
|
|
gender_selected = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if not gender_selected:
|
|
logger.error(f"Fehler beim Auswählen des Geschlechts: {account_data['gender']}")
|
|
return False
|
|
|
|
self.automation.human_behavior.random_delay(0.5, 1.0)
|
|
|
|
# E-Mail eingeben mit Fallback
|
|
email_field = account_data.get("email")
|
|
if not email_field:
|
|
logger.error("Keine E-Mail-Adresse angegeben")
|
|
return False
|
|
|
|
email_selectors = [
|
|
self.selectors.REG_EMAIL_OR_PHONE,
|
|
self.selectors.REG_EMAIL_OR_PHONE_ALT,
|
|
"input[placeholder*='E-Mail']",
|
|
"input[placeholder*='Handynummer oder E-Mail']"
|
|
]
|
|
email_filled = False
|
|
for selector in email_selectors:
|
|
if self.automation.browser.is_element_visible(selector, timeout=1000):
|
|
if self.automation.ui_helper.type_text_human_like(selector, email_field):
|
|
logger.info(f"E-Mail eingegeben mit: {selector}")
|
|
email_filled = True
|
|
break
|
|
|
|
if not email_filled:
|
|
logger.error("Fehler beim Eingeben der E-Mail")
|
|
return False
|
|
|
|
# Warte auf mögliches E-Mail-Bestätigungsfeld
|
|
self.automation.human_behavior.random_delay(1.0, 2.0)
|
|
|
|
# E-Mail-Bestätigung falls erforderlich
|
|
if self.automation.browser.is_element_visible(self.selectors.REG_EMAIL_CONFIRM, timeout=2000):
|
|
logger.info("E-Mail-Bestätigungsfeld erkannt")
|
|
if not self.automation.ui_helper.type_text_human_like(
|
|
self.selectors.REG_EMAIL_CONFIRM,
|
|
email_field
|
|
):
|
|
logger.warning("Fehler beim Bestätigen der E-Mail")
|
|
|
|
# Passwort mit Fallback
|
|
password_selectors = [
|
|
self.selectors.REG_PASSWORD,
|
|
self.selectors.REG_PASSWORD_ALT,
|
|
self.selectors.REG_PASSWORD_ALT2,
|
|
"input[type='password']",
|
|
"input[autocomplete='new-password']"
|
|
]
|
|
password_filled = False
|
|
for selector in password_selectors:
|
|
if self.automation.browser.is_element_visible(selector, timeout=1000):
|
|
if self.automation.ui_helper.type_text_human_like(selector, account_data["password"]):
|
|
logger.info(f"Passwort eingegeben mit: {selector}")
|
|
password_filled = True
|
|
break
|
|
|
|
if not password_filled:
|
|
logger.error("Fehler beim Eingeben des Passworts")
|
|
return False
|
|
|
|
# Screenshot des ausgefüllten Formulars
|
|
self.automation._take_screenshot("filled_registration_form")
|
|
|
|
logger.info("Registrierungsformular erfolgreich ausgefüllt")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {e}")
|
|
return False
|
|
|
|
def _submit_registration(self) -> bool:
|
|
"""
|
|
Sendet das Registrierungsformular ab.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg
|
|
"""
|
|
try:
|
|
logger.info("Sende Registrierungsformular ab")
|
|
|
|
# Versuche Submit-Button zu klicken
|
|
submit_clicked = False
|
|
|
|
# Methode 1: Name-Attribut
|
|
if self.automation.browser.is_element_visible(self.selectors.REG_SUBMIT_BUTTON, timeout=2000):
|
|
if self.automation.browser.click_element(self.selectors.REG_SUBMIT_BUTTON):
|
|
logger.info("Registrierung abgesendet (name-Attribut)")
|
|
submit_clicked = True
|
|
|
|
# Methode 2: Text-basiert
|
|
if not submit_clicked:
|
|
for text in self.selectors.get_button_texts("register"):
|
|
selector = f"button:has-text('{text}')"
|
|
try:
|
|
if self.automation.browser.click_element(selector, timeout=1000):
|
|
logger.info(f"Registrierung abgesendet mit: {selector}")
|
|
submit_clicked = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
# Methode 3: Fuzzy Match
|
|
if not submit_clicked:
|
|
if self.automation.ui_helper.click_button_fuzzy(
|
|
self.selectors.get_button_texts("register")
|
|
):
|
|
logger.info("Registrierung abgesendet (Fuzzy Match)")
|
|
submit_clicked = True
|
|
|
|
if submit_clicked:
|
|
# Warte auf Navigation
|
|
self.automation.human_behavior.wait_for_page_load(multiplier=2.0)
|
|
self.automation._take_screenshot("after_submit")
|
|
return True
|
|
else:
|
|
logger.error("Konnte Registrierung nicht absenden")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Absenden der Registrierung: {e}")
|
|
return False
|
|
|
|
def _check_needs_verification(self) -> bool:
|
|
"""
|
|
Prüft ob eine E-Mail-Verifikation erforderlich ist.
|
|
Wartet auf Navigation zur Verifikationsseite falls nötig.
|
|
|
|
Returns:
|
|
bool: True wenn Verifikation erforderlich
|
|
"""
|
|
try:
|
|
# Warte auf Page-Load nach Submit
|
|
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
|
|
|
|
# Warte zusätzlich auf mögliche Navigation zur Verifikationsseite
|
|
# Versuche bis zu 10 Sekunden lang die URL zu prüfen
|
|
max_wait_time = 10 # Sekunden
|
|
check_interval = 0.5 # Sekunden
|
|
attempts = int(max_wait_time / check_interval)
|
|
|
|
for attempt in range(attempts):
|
|
current_url = self.automation.browser.page.url
|
|
logger.debug(f"Prüfe URL (Versuch {attempt + 1}/{attempts}): {current_url}")
|
|
|
|
# Prüfe URL auf Verifikations-Pattern
|
|
if "confirmemail" in current_url or "confirm" in current_url:
|
|
logger.info(f"E-Mail-Verifikation erforderlich (URL-Check): {current_url}")
|
|
return True
|
|
|
|
# Prüfe auf Verifikations-Input
|
|
if self.automation.browser.is_element_visible(self.selectors.VERIFICATION_CODE_INPUT, timeout=500):
|
|
logger.info("E-Mail-Verifikation erforderlich (Input-Field gefunden)")
|
|
return True
|
|
|
|
# Prüfe auf Erfolgs-Indikatoren NUR wenn URL eindeutig KEINE Verifikations-URL ist
|
|
# WICHTIG: Verifikationsseiten haben auch div[role='navigation'], deshalb URL-Check zuerst!
|
|
if "privacy/consent" not in current_url and "user_cookie_choice" not in current_url:
|
|
for indicator in self.selectors.SUCCESS_INDICATORS:
|
|
if self.automation.browser.is_element_visible(indicator, timeout=500):
|
|
# Finale Prüfung: Ist es wirklich eine Erfolgsseite?
|
|
if "facebook.com/?" in current_url or "facebook.com/home" in current_url or "feed" in current_url:
|
|
logger.info(f"Keine Verifikation nötig (Erfolgs-Indikator gefunden: {indicator})")
|
|
return False
|
|
|
|
# Kurze Pause vor nächstem Versuch
|
|
time.sleep(check_interval)
|
|
|
|
# Nach max_wait_time: Finale Prüfung mit Keywords
|
|
page_content = self.automation.browser.page.content().lower()
|
|
verification_keywords = ["bestätigungscode", "verification code", "confirm email", "code eingeben"]
|
|
|
|
for keyword in verification_keywords:
|
|
if keyword in page_content:
|
|
logger.info(f"E-Mail-Verifikation erforderlich (Keyword: {keyword})")
|
|
return True
|
|
|
|
logger.info("Keine E-Mail-Verifikation erforderlich")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei Verifikations-Check: {e}")
|
|
return False
|
|
|
|
def _wait_for_verification_code(self, email: str) -> Optional[str]:
|
|
"""
|
|
Wartet auf den Verifikationscode aus der E-Mail.
|
|
|
|
Args:
|
|
email: E-Mail-Adresse
|
|
|
|
Returns:
|
|
Optional[str]: Verifikationscode oder None
|
|
"""
|
|
logger.info(f"Warte auf Verifikationscode für {email}")
|
|
|
|
# Delegiere an Verification-Klasse
|
|
return self.automation.verification.wait_for_email_code(email, timeout=120)
|
|
|
|
def _enter_verification_code(self, code: str) -> bool:
|
|
"""
|
|
Gibt den Verifikationscode ein.
|
|
Verbesserte Version für 5-stelligen Code mit Fallback-Selektoren.
|
|
|
|
Args:
|
|
code: Verifikationscode (5-stellig, nur Zahlen)
|
|
|
|
Returns:
|
|
bool: True bei Erfolg
|
|
"""
|
|
try:
|
|
# Extrahiere nur Zahlen aus dem Code (falls FB- prefix vorhanden)
|
|
import re
|
|
code_digits = re.sub(r'\D', '', code)
|
|
if len(code_digits) == 5:
|
|
code = code_digits
|
|
logger.info(f"Gebe Verifikationscode ein: {code}")
|
|
|
|
# Versuche verschiedene Selektoren für das Code-Eingabefeld
|
|
code_selectors = [
|
|
self.selectors.VERIFICATION_CODE_INPUT, # input#code_in_cliff
|
|
self.selectors.VERIFICATION_CODE_INPUT_ALT, # input[name='code']
|
|
self.selectors.VERIFICATION_CODE_INPUT_ALT2, # input[maxlength='5']
|
|
"input[size='5']", # 5-stelliges Feld
|
|
"input[type='text'][maxlength='5']", # Text-Input mit 5 Zeichen
|
|
"input[placeholder*='Code']", # Placeholder mit 'Code'
|
|
]
|
|
|
|
code_entered = False
|
|
for selector in code_selectors:
|
|
try:
|
|
if self.automation.browser.is_element_visible(selector, timeout=1000):
|
|
# Stelle sicher, dass das Feld leer ist
|
|
self.automation.browser.page.locator(selector).first.clear()
|
|
self.automation.human_behavior.random_delay(0.2, 0.4)
|
|
|
|
# Gebe Code ein
|
|
if self.automation.ui_helper.type_text_human_like(selector, code):
|
|
logger.info(f"Verifikationscode eingegeben mit: {selector}")
|
|
code_entered = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if not code_entered:
|
|
logger.error("Fehler beim Eingeben des Verifikationscodes - kein Eingabefeld gefunden")
|
|
return False
|
|
|
|
self.automation.human_behavior.random_delay(0.5, 1.0)
|
|
|
|
# Weiter-Button klicken
|
|
continue_clicked = False
|
|
|
|
# Versuche verschiedene Button-Selektoren
|
|
continue_selectors = [
|
|
self.selectors.VERIFICATION_CONTINUE_BUTTON, # button:has-text('Weiter')
|
|
self.selectors.VERIFICATION_CONTINUE_BUTTON_EN, # button:has-text('Continue')
|
|
"button[type='submit']", # Submit-Button
|
|
"button:has-text('Bestätigen')", # Bestätigen
|
|
"button:has-text('Confirm')", # Englisch
|
|
"button:has-text('OK')", # OK
|
|
]
|
|
|
|
for selector in continue_selectors:
|
|
try:
|
|
if self.automation.browser.click_element(selector, timeout=1000):
|
|
logger.info(f"Verifikation fortgesetzt mit: {selector}")
|
|
continue_clicked = True
|
|
break
|
|
except:
|
|
continue
|
|
|
|
# Fallback: Enter drücken
|
|
if not continue_clicked:
|
|
self.automation.browser.page.keyboard.press("Enter")
|
|
logger.info("Verifikation mit Enter fortgesetzt")
|
|
continue_clicked = True
|
|
|
|
if not continue_clicked:
|
|
logger.error("Konnte Verifikation nicht fortsetzen")
|
|
return False
|
|
|
|
# Warte auf Navigation/Verarbeitung
|
|
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
|
|
|
|
# Prüfe auf OK-Button (Popup nach erfolgreicher Verifikation)
|
|
# Warte länger, da das Popup verzögert erscheinen kann
|
|
self.automation.human_behavior.random_delay(1.0, 2.0)
|
|
|
|
ok_selectors = [
|
|
self.selectors.VERIFICATION_OK_BUTTON, # a.layerCancel:has-text('OK')
|
|
self.selectors.VERIFICATION_OK_BUTTON_ALT, # a[role='button']:has-text('OK')
|
|
"a.layerCancel", # Nur Klasse (falls Text-Match fehlschlägt)
|
|
"a[role='button']._42ft._42fu.layerCancel", # Spezifische Klassen
|
|
"a:has-text('OK')", # Allgemeiner Link
|
|
"button:has-text('OK')" # Button-Fallback
|
|
]
|
|
|
|
ok_button_clicked = False
|
|
for selector in ok_selectors:
|
|
try:
|
|
if self.automation.browser.is_element_visible(selector, timeout=5000):
|
|
logger.info(f"OK-Button gefunden mit: {selector}")
|
|
if self.automation.browser.click_element(selector, timeout=2000):
|
|
logger.info(f"OK-Button nach Verifikation erfolgreich geklickt: {selector}")
|
|
ok_button_clicked = True
|
|
break
|
|
else:
|
|
logger.warning(f"OK-Button Click fehlgeschlagen für: {selector}")
|
|
except Exception as e:
|
|
logger.debug(f"Fehler beim OK-Button Click mit {selector}: {e}")
|
|
continue
|
|
|
|
if ok_button_clicked:
|
|
# Warte nach OK-Click auf finale Navigation
|
|
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
|
|
else:
|
|
logger.warning("OK-Button nach Verifikation nicht gefunden oder Click fehlgeschlagen")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Eingeben des Verifikationscodes: {e}")
|
|
return False
|
|
|
|
def _handle_final_dialogs(self) -> bool:
|
|
"""
|
|
Behandelt finale Dialogs/Layer nach Cookie-Consent.
|
|
Z.B. "Schließen"-Button.
|
|
|
|
Returns:
|
|
bool: True wenn Dialog behandelt wurde
|
|
"""
|
|
try:
|
|
self.automation.human_behavior.random_delay(0.5, 1.0)
|
|
|
|
close_button_selectors = [
|
|
"button:has-text('Schließen')",
|
|
"button:has-text('Close')",
|
|
self.selectors.DIALOG_CLOSE_BUTTON, # div[aria-label='Schließen']
|
|
self.selectors.DIALOG_CLOSE_BUTTON_EN, # div[aria-label='Close']
|
|
"div[role='button']:has-text('Schließen')",
|
|
"div[role='button']:has-text('Close')",
|
|
"a[role='button']:has-text('Schließen')",
|
|
"a[role='button']:has-text('Close')"
|
|
]
|
|
|
|
for selector in close_button_selectors:
|
|
try:
|
|
if self.automation.browser.is_element_visible(selector, timeout=2000):
|
|
if self.automation.browser.click_element(selector, timeout=1000):
|
|
logger.info(f"Finaler Dialog geschlossen mit: {selector}")
|
|
self.automation._send_log_update("Schließe Dialog...")
|
|
# Kurze Pause nach Schließen
|
|
self.automation.human_behavior.random_delay(0.5, 1.0)
|
|
return True
|
|
except Exception as e:
|
|
logger.debug(f"Fehler beim Schließen-Button mit {selector}: {e}")
|
|
continue
|
|
|
|
logger.debug("Kein finaler Dialog gefunden")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Behandeln finaler Dialogs: {e}")
|
|
return False
|
|
|
|
def _generate_username_from_email(self, email: str, first_name: str = "", last_name: str = "") -> str:
|
|
"""
|
|
Generiert einen Username aus der E-Mail-Adresse.
|
|
Facebook gibt keinen Username während der Registrierung zurück.
|
|
Mit Fallback-Strategien falls E-Mail ungültig ist.
|
|
|
|
Args:
|
|
email: E-Mail-Adresse
|
|
first_name: Vorname als Fallback
|
|
last_name: Nachname als Fallback
|
|
|
|
Returns:
|
|
str: Generierter Username (GARANTIERT nicht leer!)
|
|
"""
|
|
try:
|
|
# Strategie 1: Aus E-Mail generieren
|
|
if email and "@" in email:
|
|
# Extrahiere Teil vor @
|
|
username_part = email.split("@")[0]
|
|
|
|
# Ersetze Punkte durch Underscores
|
|
username = username_part.replace(".", "_")
|
|
|
|
# Stelle sicher, dass es gültige Zeichen sind
|
|
import re
|
|
username = re.sub(r'[^a-zA-Z0-9_]', '_', username)
|
|
|
|
if username and len(username) >= 3: # Mindestens 3 Zeichen
|
|
logger.debug(f"Username aus E-Mail generiert: {email} -> {username}")
|
|
return username
|
|
|
|
# Strategie 2: Aus Vor- und Nachnamen generieren
|
|
if first_name and last_name:
|
|
import re
|
|
clean_first = re.sub(r'[^a-zA-Z0-9]', '', first_name.lower())
|
|
clean_last = re.sub(r'[^a-zA-Z0-9]', '', last_name.lower())
|
|
|
|
if clean_first and clean_last:
|
|
import random
|
|
username = f"{clean_first}_{clean_last}_{random.randint(100, 999)}"
|
|
logger.info(f"Username aus Namen generiert (Fallback): {username}")
|
|
return username
|
|
|
|
# Strategie 3: Timestamp-basierter Name (letzter Fallback)
|
|
import time
|
|
import random
|
|
timestamp = int(time.time() * 1000) % 1000000 # Letzte 6 Stellen
|
|
random_suffix = random.randint(100, 999)
|
|
username = f"fb_user_{timestamp}_{random_suffix}"
|
|
logger.warning(f"Username mit Timestamp generiert (Notfall-Fallback): {username}")
|
|
return username
|
|
|
|
except Exception as e:
|
|
# ABSOLUTER NOTFALL: Garantiere IMMER einen Username
|
|
import time
|
|
import random
|
|
timestamp = int(time.time() * 1000) % 1000000
|
|
random_suffix = random.randint(100, 999)
|
|
emergency_username = f"fb_emergency_{timestamp}_{random_suffix}"
|
|
logger.error(f"Fehler beim Generieren des Usernames: {e}")
|
|
logger.error(f"Notfall-Username generiert: {emergency_username}")
|
|
return emergency_username
|
|
|
|
def _check_registration_success(self) -> bool:
|
|
"""
|
|
Überprüft ob die Registrierung erfolgreich war.
|
|
|
|
Returns:
|
|
bool: True bei Erfolg
|
|
"""
|
|
try:
|
|
# Warte auf finale Navigation
|
|
self.automation.human_behavior.wait_for_page_load(multiplier=2.0)
|
|
|
|
# Screenshot
|
|
self.automation._take_screenshot("registration_final")
|
|
|
|
# Prüfe URL
|
|
current_url = self.automation.browser.page.url
|
|
logger.info(f"Finale URL: {current_url}")
|
|
|
|
# WICHTIG: Prüfe ZUERST auf Zwischen-Schritte die KEINE Erfolgs-Indikatoren sind
|
|
intermediate_patterns = [
|
|
"privacy/consent",
|
|
"user_cookie_choice",
|
|
"confirmemail"
|
|
]
|
|
|
|
for pattern in intermediate_patterns:
|
|
if pattern in current_url:
|
|
logger.info(f"Zwischen-Schritt erkannt (noch nicht fertig): {pattern}")
|
|
return False # Noch nicht erfolgreich, weitere Schritte nötig
|
|
|
|
# Erfolgs-URLs
|
|
success_patterns = [
|
|
"facebook.com/?",
|
|
"facebook.com/home",
|
|
"facebook.com/feed",
|
|
"welcome",
|
|
"onboarding"
|
|
]
|
|
|
|
for pattern in success_patterns:
|
|
if pattern in current_url:
|
|
logger.info(f"Registrierung erfolgreich (URL-Pattern: {pattern})")
|
|
return True
|
|
|
|
# Prüfe auf Erfolgs-Indikatoren
|
|
for indicator in self.selectors.SUCCESS_INDICATORS:
|
|
if self.automation.browser.is_element_visible(indicator, timeout=2000):
|
|
logger.info(f"Registrierung erfolgreich (Indikator: {indicator})")
|
|
return True
|
|
|
|
# Prüfe auf Fehler
|
|
if self.automation.browser.is_element_visible(self.selectors.ERROR_MESSAGE, timeout=1000):
|
|
error_text = self.automation.browser.get_text(self.selectors.ERROR_MESSAGE)
|
|
logger.error(f"Registrierungsfehler: {error_text}")
|
|
return False
|
|
|
|
# Bei Unsicherheit als erfolgreich werten wenn keine Login-Form mehr da ist
|
|
if not self.automation.browser.is_element_visible(self.selectors.REG_FIRSTNAME_FIELD, timeout=1000):
|
|
logger.info("Registrierung wahrscheinlich erfolgreich (kein Formular mehr sichtbar)")
|
|
return True
|
|
|
|
logger.warning("Registrierungsstatus unklar")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei Erfolgs-Check: {e}")
|
|
return False |