953 Zeilen
44 KiB
Python
953 Zeilen
44 KiB
Python
"""
|
|
Gmail Registrierung - Handhabt den Registrierungsprozess
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
import random
|
|
from typing import Dict, Optional, Any
|
|
from playwright.sync_api import Page
|
|
|
|
from social_networks.gmail import gmail_selectors as selectors
|
|
from social_networks.gmail.gmail_ui_helper import GmailUIHelper
|
|
|
|
logger = logging.getLogger("gmail_registration")
|
|
|
|
class GmailRegistration:
|
|
"""
|
|
Handhabt den Gmail/Google Account Registrierungsprozess
|
|
"""
|
|
|
|
def __init__(self, page: Page, ui_helper: GmailUIHelper, screenshots_dir: str = None, save_screenshots: bool = True, phone_service: Optional[Any] = None):
|
|
"""
|
|
Initialisiert die Registrierung
|
|
"""
|
|
self.page = page
|
|
self.ui_helper = ui_helper
|
|
self.screenshots_dir = screenshots_dir
|
|
self.save_screenshots = save_screenshots
|
|
self.phone_service = phone_service
|
|
|
|
def _click_next_button(self) -> bool:
|
|
"""
|
|
Versucht den Weiter-Button mit verschiedenen Selektoren zu klicken
|
|
"""
|
|
logger.info("Versuche Weiter-Button zu klicken")
|
|
|
|
# Liste von Selektoren zum Ausprobieren
|
|
selectors_to_try = [
|
|
("span[jsname='V67aGc']:has-text('Weiter')", "parent_click"), # Der exakte Span mit jsname
|
|
("button:has(span.VfPpkd-vQzf8d:has-text('Weiter'))", "click"), # Button der den Span enthält
|
|
("button:has(div.VfPpkd-RLmnJb)", "click"), # Button mit dem Material Ripple div
|
|
(selectors.NEXT_BUTTON, "click"),
|
|
(selectors.NEXT_BUTTON_MATERIAL, "parent_click"),
|
|
(selectors.NEXT_BUTTON_SPAN, "click"),
|
|
("button:has-text('Weiter')", "click"),
|
|
("button:has-text('Next')", "click")
|
|
]
|
|
|
|
for selector, method in selectors_to_try:
|
|
try:
|
|
if method == "click":
|
|
if self.ui_helper.wait_for_element(selector, timeout=2000):
|
|
self.ui_helper.click_with_retry(selector)
|
|
logger.info(f"Erfolgreich geklickt mit Selektor: {selector}")
|
|
return True
|
|
elif method == "parent_click":
|
|
if self.ui_helper.wait_for_element(selector, timeout=2000):
|
|
# Versuche verschiedene Parent-Ebenen
|
|
for parent_level in ['..', '../..', '../../..']:
|
|
try:
|
|
self.page.locator(selector).locator(parent_level).click()
|
|
logger.info(f"Erfolgreich Parent geklickt mit Selektor: {selector} und Level: {parent_level}")
|
|
return True
|
|
except:
|
|
continue
|
|
except Exception as e:
|
|
logger.debug(f"Konnte nicht mit Selektor {selector} klicken: {e}")
|
|
continue
|
|
|
|
# Letzter Versuch mit Playwright's get_by_role
|
|
try:
|
|
self.page.get_by_role("button", name="Weiter").click()
|
|
logger.info("Erfolgreich mit get_by_role geklickt")
|
|
return True
|
|
except:
|
|
pass
|
|
|
|
logger.error("Konnte Weiter-Button nicht finden/klicken")
|
|
return False
|
|
|
|
def start_registration_flow(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
|
"""
|
|
Startet den Registrierungsflow
|
|
"""
|
|
try:
|
|
logger.info("Starte Gmail Registrierungsflow")
|
|
|
|
# Schritt 1: Name eingeben
|
|
name_result = self._fill_name_form(account_data)
|
|
if not name_result["success"]:
|
|
return name_result
|
|
|
|
# Schritt 2: Geburtsdatum und Geschlecht
|
|
birthday_result = self._fill_birthday_gender(account_data)
|
|
if not birthday_result["success"]:
|
|
return birthday_result
|
|
|
|
# Schritt 3: Gmail-Adresse wählen/erstellen
|
|
gmail_result = self._create_gmail_address(account_data)
|
|
if not gmail_result["success"]:
|
|
return gmail_result
|
|
|
|
# Schritt 4: Passwort festlegen
|
|
password_result = self._set_password(account_data)
|
|
if not password_result["success"]:
|
|
return password_result
|
|
|
|
# Schritt 5: Recovery Email (optional, aber hilft, Phone zu vermeiden)
|
|
recovery_result = self._handle_recovery_email(account_data)
|
|
if not recovery_result["success"]:
|
|
return recovery_result
|
|
|
|
# Schritt 6: Telefonnummer (optional/erforderlich) – wird möglichst übersprungen
|
|
phone_result = self._handle_phone_verification(account_data)
|
|
if not phone_result["success"]:
|
|
return phone_result
|
|
|
|
# Schritt 7: Nutzungsbedingungen akzeptieren
|
|
terms_result = self._accept_terms()
|
|
if not terms_result["success"]:
|
|
return terms_result
|
|
|
|
# Schritt 8: Abschluss verifizieren (Weiterleitung/Willkommensseite)
|
|
verify_ok = self._verify_account_creation()
|
|
if not verify_ok:
|
|
return {
|
|
"success": False,
|
|
"error": "Verifikation der Kontoerstellung fehlgeschlagen",
|
|
"message": "Registrierung nicht bestätigt – bitte erneut versuchen"
|
|
}
|
|
|
|
return {
|
|
"success": True,
|
|
"username": gmail_result.get("username"),
|
|
"email": gmail_result.get("email"),
|
|
"message": "Registrierung erfolgreich abgeschlossen"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler im Registrierungsflow: {e}")
|
|
self.ui_helper.take_screenshot("registration_error")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"message": f"Registrierung fehlgeschlagen: {str(e)}"
|
|
}
|
|
|
|
def _fill_name_form(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
|
"""
|
|
Füllt das Namensformular aus
|
|
"""
|
|
try:
|
|
logger.info("Fülle Namensformular aus")
|
|
|
|
# Screenshot der aktuellen Seite
|
|
self.ui_helper.take_screenshot("before_name_form_search")
|
|
|
|
# Warte kurz, damit die Seite vollständig lädt
|
|
time.sleep(2)
|
|
|
|
# Debug: Aktuelle URL ausgeben
|
|
current_url = self.page.url
|
|
logger.info(f"Aktuelle URL: {current_url}")
|
|
|
|
# Versuche Cookie-Banner zu schließen, falls vorhanden
|
|
try:
|
|
# Suche nach typischen Cookie-Akzeptieren-Buttons
|
|
cookie_selectors = [
|
|
"button:has-text('Alle akzeptieren')",
|
|
"button:has-text('Accept all')",
|
|
"button:has-text('Akzeptieren')",
|
|
"button:has-text('Accept')",
|
|
"[aria-label='Alle akzeptieren']"
|
|
]
|
|
|
|
for selector in cookie_selectors:
|
|
try:
|
|
if self.page.locator(selector).is_visible(timeout=1000):
|
|
self.page.locator(selector).click()
|
|
logger.info(f"Cookie-Banner geschlossen mit: {selector}")
|
|
time.sleep(1)
|
|
break
|
|
except:
|
|
continue
|
|
except Exception as e:
|
|
logger.debug(f"Kein Cookie-Banner gefunden oder Fehler: {e}")
|
|
|
|
# Versuche verschiedene Selektoren für das Vorname-Feld
|
|
first_name_selectors = [
|
|
selectors.FIRST_NAME_INPUT,
|
|
"input[aria-label='Vorname']",
|
|
"input[aria-label='First name']",
|
|
"#firstName",
|
|
"input[type='text'][autocomplete='given-name']"
|
|
]
|
|
|
|
first_name_found = False
|
|
for selector in first_name_selectors:
|
|
if self.ui_helper.wait_for_element(selector, timeout=3000):
|
|
first_name_found = True
|
|
selectors.FIRST_NAME_INPUT = selector # Update für diesen Durchlauf
|
|
logger.info(f"Vorname-Feld gefunden mit Selektor: {selector}")
|
|
break
|
|
|
|
if not first_name_found:
|
|
# Screenshot bei Fehler
|
|
self.ui_helper.take_screenshot("name_form_not_found_error")
|
|
return {
|
|
"success": False,
|
|
"error": "Namensformular nicht gefunden",
|
|
"message": "Registrierungsseite konnte nicht geladen werden"
|
|
}
|
|
|
|
# Vorname eingeben
|
|
first_name = account_data.get("first_name", "")
|
|
logger.info(f"Gebe Vorname ein: {first_name}")
|
|
self.ui_helper.type_with_delay(selectors.FIRST_NAME_INPUT, first_name)
|
|
time.sleep(random.uniform(0.5, 1))
|
|
|
|
# Nachname eingeben - versuche verschiedene Selektoren
|
|
last_name_selectors = [
|
|
selectors.LAST_NAME_INPUT,
|
|
"input[aria-label='Nachname']",
|
|
"input[aria-label='Last name']",
|
|
"#lastName",
|
|
"input[type='text'][autocomplete='family-name']"
|
|
]
|
|
|
|
last_name = account_data.get("last_name", "")
|
|
logger.info(f"Gebe Nachname ein: {last_name}")
|
|
|
|
for selector in last_name_selectors:
|
|
try:
|
|
if self.ui_helper.wait_for_element(selector, timeout=2000):
|
|
self.ui_helper.type_with_delay(selector, last_name)
|
|
logger.info(f"Nachname eingegeben mit Selektor: {selector}")
|
|
break
|
|
except:
|
|
continue
|
|
|
|
time.sleep(random.uniform(0.5, 1))
|
|
|
|
# Screenshot vor dem Weiter-Klick
|
|
self.ui_helper.take_screenshot("name_form_filled")
|
|
|
|
# Weiter Button klicken
|
|
if not self._click_next_button():
|
|
return {
|
|
"success": False,
|
|
"error": "Konnte Weiter-Button nicht klicken",
|
|
"message": "Navigation fehlgeschlagen"
|
|
}
|
|
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(2, 3))
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Namensformular ausgefüllt"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Ausfüllen des Namensformulars: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def _fill_birthday_gender(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
|
"""
|
|
Füllt Geburtsdatum und Geschlecht aus
|
|
"""
|
|
try:
|
|
logger.info("Fülle Geburtsdatum und Geschlecht aus")
|
|
|
|
# Warte auf Formular
|
|
if not self.ui_helper.wait_for_element(selectors.BIRTHDAY_DAY, timeout=10000):
|
|
logger.warning("Geburtsdatum-Formular nicht gefunden, überspringe...")
|
|
return {"success": True}
|
|
|
|
# Geburtsdatum ausfüllen
|
|
birthday = account_data.get("birthday", "1990-01-15")
|
|
year, month, day = birthday.split("-")
|
|
|
|
# Tag eingeben (sicheres Füllen)
|
|
logger.info(f"Gebe Geburtstag ein: {day}")
|
|
try:
|
|
self.ui_helper.safe_fill(selectors.BIRTHDAY_DAY, day.lstrip("0"))
|
|
except Exception:
|
|
self.ui_helper.type_with_delay(selectors.BIRTHDAY_DAY, day.lstrip("0"))
|
|
time.sleep(random.uniform(0.3, 0.6))
|
|
|
|
# Monat auswählen – robust: select_option oder per Label/Text
|
|
logger.info(f"Wähle Geburtsmonat: {month}")
|
|
month_index = max(1, min(12, int(month)))
|
|
month_value = str(month_index) # für <select>
|
|
months_de = [
|
|
"Januar","Februar","März","April","Mai","Juni",
|
|
"Juli","August","September","Oktober","November","Dezember"
|
|
]
|
|
months_en = [
|
|
"January","February","March","April","May","June",
|
|
"July","August","September","October","November","December"
|
|
]
|
|
month_text_candidates = [months_de[month_index-1], months_en[month_index-1], month_value]
|
|
selected_month = False
|
|
try:
|
|
self.ui_helper.select_dropdown_option(selectors.BIRTHDAY_MONTH, month_value)
|
|
selected_month = True
|
|
except Exception:
|
|
# Per Label (Monat/Month)
|
|
if self.ui_helper.select_dropdown_by_label(["Monat", "Geburtsmonat", "Month"], month_text_candidates):
|
|
selected_month = True
|
|
else:
|
|
# Per Selektor – falls kein echtes <select>
|
|
if self.ui_helper.select_dropdown_by_selector(selectors.BIRTHDAY_MONTH, month_text_candidates):
|
|
selected_month = True
|
|
if not selected_month:
|
|
logger.warning("Konnte Monat nicht auswählen – fahre trotzdem fort")
|
|
time.sleep(random.uniform(0.3, 0.6))
|
|
|
|
# Jahr eingeben (sicheres Füllen)
|
|
logger.info(f"Gebe Geburtsjahr ein: {year}")
|
|
try:
|
|
self.ui_helper.safe_fill(selectors.BIRTHDAY_YEAR, year)
|
|
except Exception:
|
|
self.ui_helper.type_with_delay(selectors.BIRTHDAY_YEAR, year)
|
|
time.sleep(random.uniform(0.3, 0.6))
|
|
|
|
# Geschlecht auswählen – robust
|
|
gender = account_data.get("gender", "male").lower()
|
|
logger.info(f"Wähle Geschlecht: {gender}")
|
|
gender_value = "1" if gender == "male" else "2" # für <select>
|
|
gender_texts = [
|
|
("male", ["Männlich", "Mann", "Male"]),
|
|
("female", ["Weiblich", "Frau", "Female"])
|
|
]
|
|
selected_gender = False
|
|
try:
|
|
self.ui_helper.select_dropdown_option(selectors.GENDER_SELECT, gender_value)
|
|
selected_gender = True
|
|
except Exception:
|
|
# Per Label (Geschlecht/Gender)
|
|
texts = next((t for k, t in gender_texts if k == gender), gender_texts[0][1])
|
|
if self.ui_helper.select_dropdown_by_label(["Geschlecht", "Gender"], texts):
|
|
selected_gender = True
|
|
else:
|
|
if self.ui_helper.select_dropdown_by_selector(selectors.GENDER_SELECT, texts):
|
|
selected_gender = True
|
|
if not selected_gender:
|
|
logger.warning("Konnte Geschlecht nicht auswählen – fahre trotzdem fort")
|
|
time.sleep(random.uniform(0.5, 1))
|
|
|
|
# Screenshot vor dem Weiter-Klick
|
|
self.ui_helper.take_screenshot("birthday_gender_filled")
|
|
|
|
# Weiter klicken
|
|
logger.info("Klicke auf Weiter")
|
|
self._click_next_button()
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(2, 3))
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Geburtsdatum und Geschlecht ausgefüllt"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Ausfüllen von Geburtsdatum/Geschlecht: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def _create_gmail_address(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
|
"""
|
|
Erstellt die Gmail-Adresse
|
|
"""
|
|
try:
|
|
logger.info("Erstelle Gmail-Adresse")
|
|
|
|
# Warte auf Gmail-Erstellungsseite
|
|
time.sleep(random.uniform(2, 3))
|
|
self.ui_helper.take_screenshot("gmail_creation_page")
|
|
|
|
# Bevorzugt: Wenn Vorschlags-Radios vorhanden sind, nimm den ersten Vorschlag
|
|
try:
|
|
radios = self.page.locator(selectors.GMAIL_USERNAME_SUGGESTION_RADIOS)
|
|
if radios.count() > 0:
|
|
logger.info("Wähle ersten Gmail-Benutzernamen-Vorschlag aus")
|
|
first_radio = radios.first
|
|
try:
|
|
first_radio.click()
|
|
except Exception:
|
|
# Falls Input nicht direkt klickbar ist: versuche das zugehörige Label
|
|
try:
|
|
label_id = first_radio.get_attribute("aria-labelledby")
|
|
if label_id:
|
|
self.page.locator(f"#{label_id}").click()
|
|
except Exception:
|
|
pass
|
|
# Ermittle den gewählten Wert
|
|
chosen = first_radio.get_attribute("value") or ""
|
|
# Weiter klicken
|
|
if not self._click_next_button():
|
|
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(2, 3))
|
|
return {
|
|
"success": True,
|
|
"username": chosen,
|
|
"email": f"{chosen}@gmail.com" if chosen else ""
|
|
}
|
|
except Exception as e:
|
|
logger.debug(f"Kein Vorschlags-Radio auswählbar: {e}")
|
|
|
|
# Prüfe ob wir einen Benutzernamen eingeben können (Fallback)
|
|
if self.ui_helper.wait_for_element(selectors.GMAIL_USERNAME_INPUT, timeout=10000):
|
|
username = account_data.get("username", "")
|
|
from social_networks.gmail.gmail_utils import GmailUtils
|
|
|
|
# Multi-Attempt Username-Strategie
|
|
max_username_attempts = 5
|
|
username_options = []
|
|
|
|
if username:
|
|
# Verwende bereitgestellten Username als ersten Versuch
|
|
username_options = [username]
|
|
# Generiere zusätzliche Optionen als Fallback
|
|
additional_options = GmailUtils.generate_multiple_username_options(
|
|
account_data.get("first_name", ""),
|
|
account_data.get("last_name", ""),
|
|
count=4
|
|
)
|
|
username_options.extend(additional_options)
|
|
else:
|
|
# Generiere mehrere Username-Optionen
|
|
username_options = GmailUtils.generate_multiple_username_options(
|
|
account_data.get("first_name", ""),
|
|
account_data.get("last_name", ""),
|
|
count=5
|
|
)
|
|
|
|
successful_username = None
|
|
|
|
for attempt, username_candidate in enumerate(username_options[:max_username_attempts], 1):
|
|
logger.info(f"[USERNAME-RETRY] Versuch {attempt}/{max_username_attempts}: {username_candidate}")
|
|
|
|
# Username eingeben
|
|
try:
|
|
# Feld leeren falls bereits gefüllt
|
|
self.ui_helper.safe_fill(selectors.GMAIL_USERNAME_INPUT, "")
|
|
time.sleep(random.uniform(0.2, 0.4))
|
|
|
|
# Neuen Username eingeben
|
|
self.ui_helper.type_with_delay(selectors.GMAIL_USERNAME_INPUT, username_candidate)
|
|
time.sleep(random.uniform(1, 2))
|
|
|
|
# Screenshot vor Weiter-Klick
|
|
self.ui_helper.take_screenshot(f"username_attempt_{attempt}")
|
|
|
|
# Weiter klicken
|
|
if not self._click_next_button():
|
|
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
|
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(2, 3))
|
|
|
|
# Prüfe auf Fehler (Benutzername bereits vergeben)
|
|
username_error_found = False
|
|
error_selectors = [
|
|
selectors.ERROR_MESSAGE,
|
|
selectors.ERROR_MESSAGE_ALT,
|
|
"div[jsname='B34EJ']", # Spezifischer Gmail-Fehler-Selektor
|
|
"div[role='alert']", # ARIA Alert-Role
|
|
"div:has-text('not available')", # English error
|
|
"div:has-text('nicht verfügbar')" # German error
|
|
]
|
|
|
|
error_text = ""
|
|
for error_selector in error_selectors:
|
|
if self.ui_helper.is_element_visible(error_selector, timeout=2000):
|
|
error_text = self.ui_helper.get_element_text(error_selector) or ""
|
|
if error_text.lower() in ["not available", "nicht verfügbar", "bereits verwendet", "already taken", "unavailable"]:
|
|
username_error_found = True
|
|
logger.warning(f"[USERNAME-RETRY] Username '{username_candidate}' bereits vergeben: {error_text}")
|
|
break
|
|
|
|
if not username_error_found:
|
|
# Erfolgreich! Kein Username-Fehler gefunden
|
|
successful_username = username_candidate
|
|
logger.info(f"[USERNAME-RETRY] ✅ Username erfolgreich: {username_candidate}")
|
|
break
|
|
|
|
except Exception as e:
|
|
logger.warning(f"[USERNAME-RETRY] Versuch {attempt} fehlgeschlagen: {e}")
|
|
continue
|
|
|
|
if successful_username:
|
|
return {"success": True, "username": successful_username, "email": f"{successful_username}@gmail.com"}
|
|
else:
|
|
logger.error("[USERNAME-RETRY] ❌ Alle Username-Versuche fehlgeschlagen")
|
|
return {
|
|
"success": False,
|
|
"error": "Alle generierten Usernames bereits vergeben",
|
|
"message": f"Nach {max_username_attempts} Versuchen keinen verfügbaren Username gefunden"
|
|
}
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Gmail-Adresse erstellt"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Erstellen der Gmail-Adresse: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def _set_password(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
|
"""
|
|
Setzt das Passwort
|
|
"""
|
|
try:
|
|
logger.info("Setze Passwort")
|
|
|
|
# Warte auf Passwort-Formular
|
|
if not self.ui_helper.wait_for_element(selectors.PASSWORD_INPUT, timeout=10000):
|
|
return {
|
|
"success": False,
|
|
"error": "Passwort-Formular nicht gefunden"
|
|
}
|
|
|
|
password = account_data.get("password", "")
|
|
if not password:
|
|
# Generiere ein sicheres Passwort
|
|
from social_networks.gmail.gmail_utils import GmailUtils
|
|
utils = GmailUtils()
|
|
password = utils.generate_secure_password()
|
|
|
|
# Passwort eingeben
|
|
logger.info("Gebe Passwort ein")
|
|
self.ui_helper.type_with_delay(selectors.PASSWORD_INPUT, password)
|
|
time.sleep(random.uniform(0.5, 1))
|
|
|
|
# Passwort bestätigen
|
|
if self.ui_helper.wait_for_element(selectors.PASSWORD_CONFIRM_INPUT, timeout=5000):
|
|
logger.info("Bestätige Passwort")
|
|
self.ui_helper.type_with_delay(selectors.PASSWORD_CONFIRM_INPUT, password)
|
|
time.sleep(random.uniform(0.5, 1))
|
|
|
|
# Screenshot vor dem Weiter-Klick
|
|
self.ui_helper.take_screenshot("password_set")
|
|
|
|
# Weiter klicken
|
|
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(2, 3))
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Passwort gesetzt"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Setzen des Passworts: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def _handle_phone_verification(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
|
"""
|
|
Handhabt die Telefonnummer-Seite - ENHANCED SKIP-ANSATZ mit 2025 Techniken
|
|
"""
|
|
try:
|
|
logger.info("[PHONE-BYPASS] Starte enhanced Phone-Verification-Bypass mit 2025 Techniken")
|
|
|
|
# Kurze Wartezeit für Phone-Input - falls es nicht erscheint ist es oft nicht erforderlich
|
|
phone_field_found = self.ui_helper.wait_for_element(selectors.PHONE_INPUT, timeout=3000)
|
|
|
|
if not phone_field_found:
|
|
logger.info("[PHONE-BYPASS] ✅ Telefonnummer-Feld nicht gefunden - nicht erforderlich!")
|
|
return {"success": True, "message": "Telefonnummer nicht erforderlich"}
|
|
|
|
logger.info("[PHONE-BYPASS] 📱 Telefonnummer-Feld gefunden - versuche enhanced Bypass")
|
|
self.ui_helper.take_screenshot("phone_verification_page")
|
|
|
|
# PHASE 1: Enhanced Multi-Language Skip Button Detection (2025 Methods)
|
|
skip_attempts = []
|
|
from social_networks.gmail import gmail_selectors as sel
|
|
skip_selectors = getattr(sel, 'SKIP_BUTTON_VARIANTS', [])
|
|
|
|
# Erweiterte Skip-Selektoren mit 2025 Erkenntnissen
|
|
additional_skip_selectors = [
|
|
# Attribute-based (case-insensitive)
|
|
"[aria-label*='skip' i], [aria-label*='überspringen' i], [aria-label*='später' i]",
|
|
"[aria-label*='not now' i], [aria-label*='maybe later' i]",
|
|
"[aria-label*='remind me later' i], [aria-label*='erinnere mich später' i]",
|
|
# Role-based with expanded text
|
|
"button[role='button']:has-text('Skip')",
|
|
"button[role='button']:has-text('Not now')",
|
|
"button[role='button']:has-text('Maybe later')",
|
|
"button[role='button']:has-text('Remind me later')",
|
|
# Class-based (Google's Material Design patterns)
|
|
".VfPpkd-LgbsSe:has-text('Skip')",
|
|
".VfPpkd-LgbsSe:has-text('Überspringen')",
|
|
".VfPpkd-LgbsSe:has-text('Not now')",
|
|
".VfPpkd-vQzf8d:has-text('Skip')",
|
|
# Link-based
|
|
"a:has-text('Skip'), a:has-text('Überspringen'), a:has-text('Not now')",
|
|
"a:has-text('Use another verification method')",
|
|
# Generic button text matching
|
|
"button:has-text('Skip'), button:has-text('Überspringen')",
|
|
"button:has-text('Not now'), button:has-text('Jetzt nicht')",
|
|
"button:has-text('Maybe later'), button:has-text('Vielleicht später')",
|
|
# 2025 New patterns
|
|
"button:has-text('I'll add it later')",
|
|
"button:has-text('Später hinzufügen')",
|
|
"div[role='button']:has-text('Skip')",
|
|
"span:has-text('Skip for now')"
|
|
]
|
|
|
|
all_skip_selectors = skip_selectors + additional_skip_selectors
|
|
|
|
for skip_sel in all_skip_selectors:
|
|
try:
|
|
if self.ui_helper.is_element_visible(skip_sel, timeout=1000):
|
|
logger.info(f"[PHONE-BYPASS] 🎯 Skip-Button gefunden: {skip_sel}")
|
|
self.ui_helper.click_with_retry(skip_sel)
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(1, 2))
|
|
|
|
# Erfolgsprüfung: Phone-Input nicht mehr sichtbar?
|
|
if not self.ui_helper.is_element_visible(selectors.PHONE_INPUT, timeout=2000):
|
|
logger.info("[PHONE-BYPASS] ✅ Skip erfolgreich - Phone-Input verschwunden!")
|
|
return {"success": True, "message": f"Telefonnummer übersprungen via {skip_sel}"}
|
|
|
|
skip_attempts.append(f"tried: {skip_sel}")
|
|
except Exception as e:
|
|
logger.debug(f"[PHONE-BYPASS] Skip-Versuch fehlgeschlagen {skip_sel}: {e}")
|
|
continue
|
|
|
|
# PHASE 2: Enhanced Empty-Field Strategy mit JavaScript-Manipulation
|
|
logger.info("[PHONE-BYPASS] Phase 2: Enhanced Empty-Field + JavaScript Manipulation")
|
|
try:
|
|
# Stelle sicher, dass Phone-Input leer ist
|
|
phone_input = self.page.locator(selectors.PHONE_INPUT)
|
|
if phone_input.count() > 0:
|
|
phone_input.first.clear()
|
|
phone_input.first.fill("")
|
|
time.sleep(random.uniform(0.5, 1))
|
|
|
|
# JavaScript-Manipulation: Setze Input als optional
|
|
try:
|
|
self.page.evaluate("""
|
|
const phoneInput = document.querySelector('input[id="phoneNumberId"]');
|
|
if (phoneInput) {
|
|
phoneInput.removeAttribute('required');
|
|
phoneInput.removeAttribute('aria-required');
|
|
phoneInput.setAttribute('aria-invalid', 'false');
|
|
phoneInput.value = '';
|
|
}
|
|
""")
|
|
except Exception as js_err:
|
|
logger.debug(f"JS-Manipulation fehlgeschlagen: {js_err}")
|
|
|
|
# Versuche Next/Continue Button mit mehreren Ansätzen
|
|
if not self._click_next_button():
|
|
# Fallback: JavaScript Click
|
|
try:
|
|
self.page.evaluate("""
|
|
const nextBtn = document.querySelector('button[jsname="LgbsSe"]') ||
|
|
document.querySelector('button:has(span:has-text("Weiter"))') ||
|
|
document.querySelector('button:has(span:has-text("Next"))');
|
|
if (nextBtn) nextBtn.click();
|
|
""")
|
|
except:
|
|
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
|
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(2, 4))
|
|
|
|
# Prüfung: SMS-Code Input erschienen? Wenn NEIN = Erfolg!
|
|
if not self.ui_helper.is_element_visible(selectors.SMS_CODE_INPUT, timeout=3000):
|
|
# Zusätzliche Prüfung: Sind wir auf der nächsten Seite?
|
|
current_url = self.page.url
|
|
if "signup" in current_url or "accounts.google.com" in current_url:
|
|
logger.info("[PHONE-BYPASS] ✅ Enhanced Empty-Field-Continue erfolgreich!")
|
|
return {"success": True, "message": "Telefonnummer via Enhanced Empty-Field übersprungen"}
|
|
|
|
except Exception as e:
|
|
logger.debug(f"[PHONE-BYPASS] Enhanced Empty-Field-Continue fehlgeschlagen: {e}")
|
|
|
|
# PHASE 3: Advanced Navigation & URL Manipulation (2025 Techniques)
|
|
logger.info("[PHONE-BYPASS] Phase 3: Advanced Navigation & URL Manipulation")
|
|
try:
|
|
# Trick 1: Back-Forward mit Cookie-Clear
|
|
self.page.go_back()
|
|
time.sleep(random.uniform(1, 2))
|
|
|
|
# Clear session storage während Navigation
|
|
try:
|
|
self.page.evaluate("sessionStorage.clear()")
|
|
except:
|
|
pass
|
|
|
|
self.page.go_forward()
|
|
time.sleep(random.uniform(2, 3))
|
|
|
|
# Prüfen ob Phone-Feld wieder da ist
|
|
if not self.ui_helper.is_element_visible(selectors.PHONE_INPUT, timeout=2000):
|
|
logger.info("[PHONE-BYPASS] ✅ Back-Forward-Trick erfolgreich!")
|
|
return {"success": True, "message": "Telefonnummer via Navigation-Trick übersprungen"}
|
|
|
|
# Trick 2: Direct URL Manipulation - Skip to Terms
|
|
current_url = self.page.url
|
|
if "signup" in current_url:
|
|
# Versuche direkt zu Terms zu springen
|
|
terms_url = current_url.replace("phonenumber", "termsofservice").replace("phone", "terms")
|
|
try:
|
|
self.page.goto(terms_url, wait_until="domcontentloaded")
|
|
time.sleep(2)
|
|
if "terms" in self.page.url.lower() or "agreement" in self.page.url.lower():
|
|
logger.info("[PHONE-BYPASS] ✅ Direct URL jump to Terms erfolgreich!")
|
|
return {"success": True, "message": "Telefonnummer via URL-Jump übersprungen"}
|
|
except:
|
|
pass
|
|
|
|
except Exception as e:
|
|
logger.debug(f"[PHONE-BYPASS] Advanced Navigation fehlgeschlagen: {e}")
|
|
|
|
# PHASE 4: Browser Fingerprint Rotation & Retry
|
|
logger.info("[PHONE-BYPASS] Phase 4: Browser Fingerprint Rotation")
|
|
try:
|
|
# Ändern von User-Agent und Viewport dynamisch
|
|
new_viewport = {
|
|
"width": random.choice([1366, 1440, 1920, 1280]),
|
|
"height": random.choice([768, 900, 1080, 720])
|
|
}
|
|
|
|
# Setze neuen Viewport
|
|
self.page.set_viewport_size(new_viewport)
|
|
|
|
# Refresh die Seite mit neuem Fingerprint
|
|
self.page.reload(wait_until="networkidle")
|
|
time.sleep(random.uniform(2, 3))
|
|
|
|
# Prüfe ob Phone-Feld noch da ist
|
|
if not self.ui_helper.is_element_visible(selectors.PHONE_INPUT, timeout=3000):
|
|
logger.info("[PHONE-BYPASS] ✅ Fingerprint Rotation erfolgreich!")
|
|
return {"success": True, "message": "Telefonnummer via Fingerprint Rotation übersprungen"}
|
|
|
|
# Nochmal Skip-Buttons suchen nach Reload
|
|
for skip_sel in all_skip_selectors[:5]: # Nur Top 5 versuchen
|
|
try:
|
|
if self.ui_helper.is_element_visible(skip_sel, timeout=500):
|
|
self.ui_helper.click_with_retry(skip_sel)
|
|
time.sleep(2)
|
|
if not self.ui_helper.is_element_visible(selectors.PHONE_INPUT, timeout=1000):
|
|
return {"success": True, "message": "Skip nach Fingerprint Rotation erfolgreich"}
|
|
except:
|
|
continue
|
|
|
|
except Exception as e:
|
|
logger.debug(f"[PHONE-BYPASS] Fingerprint Rotation fehlgeschlagen: {e}")
|
|
|
|
# PHASE 5: Als letzter Ausweg - SMS Flow falls Telefonnummer verfügbar
|
|
phone = account_data.get("phone", "")
|
|
if phone:
|
|
logger.warning("[PHONE-BYPASS] ⚠️ Alle Skip-Versuche fehlgeschlagen - führe SMS-Verification aus")
|
|
logger.info(f"[PHONE-BYPASS] Skip-Attempts: {', '.join(skip_attempts)}")
|
|
|
|
from social_networks.gmail.gmail_verification import GmailVerification
|
|
verification = GmailVerification(
|
|
page=self.page,
|
|
ui_helper=self.ui_helper,
|
|
email_handler=None,
|
|
screenshots_dir=self.screenshots_dir,
|
|
save_screenshots=self.save_screenshots,
|
|
phone_service=self.phone_service
|
|
)
|
|
return verification.handle_phone_verification(account_data)
|
|
|
|
# PHASE 5: Failure - keine Telefonnummer und Skip nicht möglich
|
|
logger.error("[PHONE-BYPASS] ❌ Alle Bypass-Versuche fehlgeschlagen")
|
|
logger.info(f"[PHONE-BYPASS] Attempted skips: {', '.join(skip_attempts)}")
|
|
|
|
# Screenshot für Debugging
|
|
self.ui_helper.take_screenshot("phone_bypass_failed")
|
|
|
|
return {
|
|
"success": False,
|
|
"error": "phone required",
|
|
"message": "Google verlangt eine Telefonnummer. Alle Bypass-Versuche fehlgeschlagen. Recovery-Email-Strategie hat nicht ausgereicht."
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"[PHONE-BYPASS] Unerwarteter Fehler: {e}")
|
|
return {"success": False, "error": str(e)}
|
|
|
|
def _handle_recovery_email(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
|
"""
|
|
Handhabt die Recovery-Email - IMMER bereitstellen wenn möglich um Phone-Verification zu umgehen
|
|
"""
|
|
try:
|
|
logger.info("[RECOVERY-EMAIL-FIRST] Prüfe auf Recovery-Email")
|
|
|
|
# Prüfe ob Recovery-Email Feld vorhanden ist
|
|
if not self.ui_helper.wait_for_element(selectors.RECOVERY_EMAIL_INPUT, timeout=5000):
|
|
logger.info("[RECOVERY-EMAIL-FIRST] Recovery-Email Feld nicht vorhanden - überspringe")
|
|
return {"success": True}
|
|
|
|
# STRATEGIE: Immer Recovery-Email bereitstellen wenn möglich (reduziert Phone-Anforderung)
|
|
recovery_email = account_data.get("recovery_email", "")
|
|
|
|
# Falls keine Recovery-Email vorhanden, generiere temporäre für bessere Bypass-Chancen
|
|
if not recovery_email:
|
|
logger.info("[RECOVERY-EMAIL-FIRST] Keine Recovery-Email vorhanden - generiere temporäre")
|
|
# Generiere plausible Recovery-Email basierend auf Account-Daten
|
|
first_name = account_data.get("first_name", "user").lower()
|
|
last_name = account_data.get("last_name", "temp").lower()
|
|
timestamp = str(int(time.time()))[-6:]
|
|
|
|
# Verwende verschiedene Provider für Glaubwürdigkeit
|
|
providers = ["outlook.com", "yahoo.com", "hotmail.com", "icloud.com", "protonmail.com"]
|
|
provider = random.choice(providers)
|
|
|
|
recovery_email = f"{first_name}.{last_name}.{timestamp}@{provider}"
|
|
logger.info(f"[RECOVERY-EMAIL-FIRST] Generierte temporäre Recovery-Email: {recovery_email}")
|
|
|
|
# Recovery-Email eingeben - NIEMALS überspringen!
|
|
if recovery_email:
|
|
logger.info(f"[RECOVERY-EMAIL-FIRST] Gebe Recovery-Email ein: {recovery_email}")
|
|
self.ui_helper.take_screenshot("before_recovery_email")
|
|
|
|
# Eingabe mit Retry-Mechanismus
|
|
input_success = False
|
|
for attempt in range(3):
|
|
try:
|
|
self.ui_helper.safe_fill(selectors.RECOVERY_EMAIL_INPUT, recovery_email)
|
|
time.sleep(random.uniform(0.5, 1.0))
|
|
input_success = True
|
|
break
|
|
except Exception as e:
|
|
logger.warning(f"[RECOVERY-EMAIL-FIRST] Eingabe-Versuch {attempt + 1} fehlgeschlagen: {e}")
|
|
if attempt < 2:
|
|
time.sleep(random.uniform(1, 2))
|
|
else:
|
|
# Fallback: type_with_delay
|
|
try:
|
|
self.ui_helper.type_with_delay(selectors.RECOVERY_EMAIL_INPUT, recovery_email)
|
|
input_success = True
|
|
except Exception:
|
|
pass
|
|
|
|
if not input_success:
|
|
logger.warning("[RECOVERY-EMAIL-FIRST] Recovery-Email Eingabe fehlgeschlagen - fahre trotzdem fort")
|
|
|
|
# Screenshot nach Eingabe
|
|
self.ui_helper.take_screenshot("after_recovery_email")
|
|
|
|
# Weiter klicken (nie überspringen!)
|
|
logger.info("[RECOVERY-EMAIL-FIRST] Klicke Weiter mit Recovery-Email")
|
|
if not self._click_next_button():
|
|
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
|
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(2, 3))
|
|
|
|
logger.info("[RECOVERY-EMAIL-FIRST] Recovery-Email erfolgreich bereitgestellt - sollte Phone-Verification reduzieren")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Recovery-Email bereitgestellt für bessere Phone-Bypass-Chancen"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"[RECOVERY-EMAIL-FIRST] Fehler bei der Recovery-Email: {e}")
|
|
# Bei Recovery-Email-Fehlern nicht abbrechen - fahre fort zu Phone-Verification
|
|
logger.warning("[RECOVERY-EMAIL-FIRST] Recovery-Email Fehler ignoriert - fahre mit Registration fort")
|
|
return {
|
|
"success": True,
|
|
"message": "Recovery-Email Fehler ignoriert"
|
|
}
|
|
|
|
def _accept_terms(self) -> Dict[str, any]:
|
|
"""
|
|
Akzeptiert die Nutzungsbedingungen
|
|
"""
|
|
try:
|
|
logger.info("Akzeptiere Nutzungsbedingungen")
|
|
|
|
# Warte auf Nutzungsbedingungen
|
|
time.sleep(random.uniform(2, 3))
|
|
self.ui_helper.take_screenshot("terms_page")
|
|
|
|
# Scrolle nach unten (simuliere Lesen)
|
|
self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
|
|
time.sleep(random.uniform(2, 4))
|
|
|
|
# Akzeptiere Button suchen und klicken
|
|
if self.ui_helper.wait_for_element(selectors.AGREE_BUTTON, timeout=10000):
|
|
logger.info("Klicke auf 'Ich stimme zu'")
|
|
self.ui_helper.click_with_retry(selectors.AGREE_BUTTON)
|
|
self.ui_helper.wait_for_loading_to_finish()
|
|
time.sleep(random.uniform(3, 5))
|
|
|
|
# Screenshot nach Registrierung
|
|
self.ui_helper.take_screenshot("registration_complete")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Nutzungsbedingungen akzeptiert"
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Akzeptieren der Nutzungsbedingungen: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def _verify_account_creation(self) -> bool:
|
|
"""
|
|
Prüft, ob nach der Registrierung eine erfolgreiche Weiterleitung/Anmeldung erfolgt ist.
|
|
"""
|
|
try:
|
|
# Kurze Wartezeit für Weiterleitung
|
|
time.sleep(random.uniform(2, 4))
|
|
current_url = self.page.url
|
|
# Typische Ziele nach erfolgreicher Erstellung/Anmeldung
|
|
success_indicators = [
|
|
"myaccount.google.com",
|
|
"mail.google.com",
|
|
"accounts.google.com/b/", # Account-Kontext geladen
|
|
"takeout.google.com" # gelegentliche Weiterleitungen
|
|
]
|
|
if any(ind in current_url for ind in success_indicators):
|
|
return True
|
|
|
|
# Falls keine eindeutige URL, prüfe ob ein offensichtliches Fehlerpanel sichtbar ist
|
|
from social_networks.gmail import gmail_selectors as selectors
|
|
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
|
|
return False
|
|
|
|
# Kein Fehler sichtbar – konservativ als Erfolg werten
|
|
return True
|
|
except Exception as e:
|
|
logger.warning(f"Fehler bei der Abschluss-Prüfung: {e}")
|
|
return False
|