Initial commit
Dieser Commit ist enthalten in:
0
social_networks/gmail/__init__.py
Normale Datei
0
social_networks/gmail/__init__.py
Normale Datei
336
social_networks/gmail/gmail_automation.py
Normale Datei
336
social_networks/gmail/gmail_automation.py
Normale Datei
@ -0,0 +1,336 @@
|
||||
"""
|
||||
Gmail Automatisierung - Hauptklasse
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict, Optional, Tuple, Any
|
||||
from playwright.sync_api import Page
|
||||
|
||||
from social_networks.base_automation import BaseAutomation
|
||||
from social_networks.gmail import gmail_selectors as selectors
|
||||
from social_networks.gmail.gmail_ui_helper import GmailUIHelper
|
||||
from social_networks.gmail.gmail_registration import GmailRegistration
|
||||
from social_networks.gmail.gmail_login import GmailLogin
|
||||
from social_networks.gmail.gmail_verification import GmailVerification
|
||||
from social_networks.gmail.gmail_utils import GmailUtils
|
||||
|
||||
logger = logging.getLogger("gmail_automation")
|
||||
|
||||
class GmailAutomation(BaseAutomation):
|
||||
"""
|
||||
Gmail/Google Account-spezifische Automatisierung
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialisiert die Gmail-Automatisierung
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.platform_name = "gmail"
|
||||
self.ui_helper = None
|
||||
self.registration = None
|
||||
self.login_helper = None
|
||||
self.verification = None
|
||||
self.utils = None
|
||||
|
||||
def _initialize_helpers(self, page: Page):
|
||||
"""
|
||||
Initialisiert die Hilfsklassen
|
||||
"""
|
||||
self.ui_helper = GmailUIHelper(page, self.screenshots_dir, self.save_screenshots)
|
||||
self.registration = GmailRegistration(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
|
||||
self.login_helper = GmailLogin(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
|
||||
self.verification = GmailVerification(page, self.ui_helper, self.email_handler, self.screenshots_dir, self.save_screenshots)
|
||||
self.utils = GmailUtils()
|
||||
|
||||
def register_account(self, full_name: str, age: int, registration_method: str = "email",
|
||||
phone_number: str = None, **kwargs) -> Dict[str, any]:
|
||||
"""
|
||||
Erstellt einen neuen Gmail/Google Account
|
||||
|
||||
Args:
|
||||
full_name: Vollständiger Name für den Account
|
||||
age: Alter des Benutzers
|
||||
registration_method: Registrierungsmethode (nur "email" für Gmail)
|
||||
phone_number: Telefonnummer (optional, aber oft erforderlich)
|
||||
**kwargs: Weitere optionale Parameter
|
||||
"""
|
||||
try:
|
||||
logger.info(f"[GMAIL AUTOMATION] register_account aufgerufen")
|
||||
logger.info(f"[GMAIL AUTOMATION] full_name: {full_name}")
|
||||
logger.info(f"[GMAIL AUTOMATION] age: {age}")
|
||||
logger.info(f"[GMAIL AUTOMATION] phone_number: {phone_number}")
|
||||
logger.info(f"[GMAIL AUTOMATION] kwargs: {kwargs}")
|
||||
|
||||
# Erstelle account_data aus den Parametern
|
||||
account_data = {
|
||||
"full_name": full_name,
|
||||
"first_name": kwargs.get("first_name", full_name.split()[0] if full_name else ""),
|
||||
"last_name": kwargs.get("last_name", full_name.split()[-1] if full_name and len(full_name.split()) > 1 else ""),
|
||||
"age": age,
|
||||
"birthday": kwargs.get("birthday", self._generate_birthday(age)),
|
||||
"gender": kwargs.get("gender", random.choice(["male", "female"])),
|
||||
"username": kwargs.get("username", ""),
|
||||
"password": kwargs.get("password", ""),
|
||||
"phone": phone_number,
|
||||
"recovery_email": kwargs.get("recovery_email", "")
|
||||
}
|
||||
|
||||
# Initialisiere Browser, falls noch nicht geschehen
|
||||
logger.info(f"[GMAIL AUTOMATION] Prüfe Browser-Status...")
|
||||
logger.info(f"[GMAIL AUTOMATION] self.browser: {self.browser}")
|
||||
if self.browser:
|
||||
logger.info(f"[GMAIL AUTOMATION] hasattr(self.browser, 'page'): {hasattr(self.browser, 'page')}")
|
||||
|
||||
if not self.browser or not hasattr(self.browser, 'page'):
|
||||
logger.info(f"[GMAIL AUTOMATION] Browser muss initialisiert werden")
|
||||
if not self._initialize_browser():
|
||||
logger.error(f"[GMAIL AUTOMATION] Browser-Initialisierung fehlgeschlagen!")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Browser konnte nicht initialisiert werden",
|
||||
"message": "Browser-Initialisierung fehlgeschlagen"
|
||||
}
|
||||
logger.info(f"[GMAIL AUTOMATION] Browser erfolgreich initialisiert")
|
||||
|
||||
# Page-Objekt holen
|
||||
page = self.browser.page
|
||||
self._initialize_helpers(page)
|
||||
|
||||
# Direkt zur Registrierungs-URL navigieren
|
||||
logger.info("Navigiere zur Gmail Registrierungsseite")
|
||||
page.goto(selectors.REGISTRATION_URL, wait_until="networkidle")
|
||||
|
||||
# Warte auf vollständiges Laden der Seite
|
||||
logger.info("Warte auf vollständiges Laden der Seite...")
|
||||
time.sleep(random.uniform(5, 7))
|
||||
|
||||
# Prüfe ob wir auf der richtigen Seite sind
|
||||
current_url = page.url
|
||||
logger.info(f"Aktuelle URL nach Navigation: {current_url}")
|
||||
|
||||
# Screenshot der Startseite
|
||||
self.ui_helper.take_screenshot("gmail_start_page")
|
||||
|
||||
# Finde und klicke auf "Konto erstellen" Button (Dropdown)
|
||||
try:
|
||||
# Warte bis die Seite interaktiv ist
|
||||
logger.info("Warte auf vollständiges Laden der Gmail Workspace Seite...")
|
||||
page.wait_for_load_state("networkidle")
|
||||
time.sleep(2)
|
||||
|
||||
# Debug: Alle sichtbaren Links/Buttons mit "Konto" ausgeben
|
||||
try:
|
||||
konto_elements = page.locator("*:has-text('Konto')").all()
|
||||
logger.info(f"Gefundene Elemente mit 'Konto': {len(konto_elements)}")
|
||||
for i, elem in enumerate(konto_elements[:5]): # Erste 5 Elemente
|
||||
try:
|
||||
tag = elem.evaluate("el => el.tagName")
|
||||
text = elem.inner_text()
|
||||
logger.info(f"Element {i}: <{tag}> - {text}")
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug(f"Debug-Ausgabe fehlgeschlagen: {e}")
|
||||
|
||||
# Schritt 1: Klicke auf "Konto erstellen" Dropdown
|
||||
create_account_selectors = [
|
||||
"[aria-label='Konto erstellen']",
|
||||
"div[aria-label='Konto erstellen']",
|
||||
"[data-g-action='create an account']",
|
||||
"button:has-text('Konto erstellen')",
|
||||
"a:has-text('Konto erstellen')",
|
||||
"*:has-text('Konto erstellen')", # Beliebiges Element mit dem Text
|
||||
"[slot='label']:has-text('Konto erstellen')" # Spezifisch für Web Components
|
||||
]
|
||||
|
||||
clicked_dropdown = False
|
||||
for selector in create_account_selectors:
|
||||
try:
|
||||
elements = page.locator(selector).all()
|
||||
logger.info(f"Selector {selector}: {len(elements)} Elemente gefunden")
|
||||
|
||||
if page.locator(selector).is_visible(timeout=3000):
|
||||
# Versuche normale Klick-Methode
|
||||
try:
|
||||
page.locator(selector).first.click()
|
||||
logger.info(f"Dropdown 'Konto erstellen' geklickt mit: {selector}")
|
||||
clicked_dropdown = True
|
||||
break
|
||||
except:
|
||||
# Versuche JavaScript-Klick als Fallback
|
||||
page.locator(selector).first.evaluate("el => el.click()")
|
||||
logger.info(f"Dropdown 'Konto erstellen' via JS geklickt mit: {selector}")
|
||||
clicked_dropdown = True
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(f"Fehler mit Selector {selector}: {e}")
|
||||
continue
|
||||
|
||||
if not clicked_dropdown:
|
||||
logger.error("Konnte 'Konto erstellen' Dropdown nicht finden")
|
||||
self.ui_helper.take_screenshot("konto_erstellen_not_found")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Konto erstellen Dropdown nicht gefunden",
|
||||
"message": "Navigation fehlgeschlagen"
|
||||
}
|
||||
|
||||
# Kurz warten bis Dropdown geöffnet ist
|
||||
time.sleep(1)
|
||||
|
||||
# Schritt 2: Klicke auf "Für die private Nutzung"
|
||||
private_use_selectors = [
|
||||
"a[aria-label='Gmail - Für die private Nutzung']",
|
||||
"a:has-text('Für die private Nutzung')",
|
||||
"[data-g-action='für die private nutzung']",
|
||||
"span:has-text('Für die private Nutzung')"
|
||||
]
|
||||
|
||||
clicked_private = False
|
||||
for selector in private_use_selectors:
|
||||
try:
|
||||
if page.locator(selector).is_visible(timeout=2000):
|
||||
page.locator(selector).click()
|
||||
logger.info(f"'Für die private Nutzung' geklickt mit: {selector}")
|
||||
clicked_private = True
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not clicked_private:
|
||||
logger.error("Konnte 'Für die private Nutzung' nicht finden")
|
||||
self.ui_helper.take_screenshot("private_nutzung_not_found")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Für die private Nutzung Option nicht gefunden",
|
||||
"message": "Navigation fehlgeschlagen"
|
||||
}
|
||||
|
||||
# Warte auf die Registrierungsseite
|
||||
time.sleep(random.uniform(3, 5))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Navigieren zur Registrierung: {e}")
|
||||
|
||||
# Screenshot der Registrierungsseite
|
||||
self.ui_helper.take_screenshot("gmail_registration_page")
|
||||
|
||||
# Registrierungsprozess starten
|
||||
registration_result = self.registration.start_registration_flow(account_data)
|
||||
if not registration_result["success"]:
|
||||
return registration_result
|
||||
|
||||
# Nach erfolgreicher Registrierung
|
||||
logger.info("Gmail Account-Registrierung erfolgreich abgeschlossen")
|
||||
return {
|
||||
"success": True,
|
||||
"username": registration_result.get("username"),
|
||||
"password": account_data.get("password"),
|
||||
"email": registration_result.get("email"),
|
||||
"phone": account_data.get("phone"),
|
||||
"recovery_email": account_data.get("recovery_email"),
|
||||
"message": "Account erfolgreich erstellt"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Gmail-Registrierung: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Registrierung fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
finally:
|
||||
self._close_browser()
|
||||
|
||||
def login(self, username: str, password: str) -> Dict[str, any]:
|
||||
"""
|
||||
Meldet sich bei einem bestehenden Gmail/Google Account an
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starte Gmail Login für {username}")
|
||||
|
||||
# Initialisiere Browser, falls noch nicht geschehen
|
||||
if not self.browser or not hasattr(self.browser, 'page'):
|
||||
if not self._initialize_browser():
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Browser konnte nicht initialisiert werden",
|
||||
"message": "Browser-Initialisierung fehlgeschlagen"
|
||||
}
|
||||
|
||||
# Page-Objekt holen
|
||||
page = self.browser.page
|
||||
self._initialize_helpers(page)
|
||||
|
||||
# Login durchführen
|
||||
return self.login_helper.login(username, password)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Gmail Login: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Login fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
finally:
|
||||
self._close_browser()
|
||||
|
||||
def get_account_info(self) -> Dict[str, any]:
|
||||
"""
|
||||
Ruft Informationen über den aktuellen Account ab
|
||||
"""
|
||||
# TODO: Implementierung
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Noch nicht implementiert"
|
||||
}
|
||||
|
||||
def logout(self) -> bool:
|
||||
"""
|
||||
Meldet sich vom aktuellen Account ab
|
||||
"""
|
||||
# TODO: Implementierung
|
||||
return False
|
||||
|
||||
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
Meldet sich bei einem bestehenden Gmail Account an.
|
||||
Implementiert die abstrakte Methode aus BaseAutomation.
|
||||
"""
|
||||
return self.login(username_or_email, password)
|
||||
|
||||
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
Verifiziert einen Gmail Account mit einem Bestätigungscode.
|
||||
Implementiert die abstrakte Methode aus BaseAutomation.
|
||||
"""
|
||||
try:
|
||||
if self.verification:
|
||||
return self.verification.verify_with_code(verification_code)
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Verification helper nicht initialisiert",
|
||||
"message": "Verifizierung kann nicht durchgeführt werden"
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Verifizierung fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
|
||||
def _generate_birthday(self, age: int) -> str:
|
||||
"""
|
||||
Generiert ein Geburtsdatum basierend auf dem Alter
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
today = datetime.now()
|
||||
birth_year = today.year - age
|
||||
# Zufälliger Tag im Jahr
|
||||
random_days = random.randint(0, 364)
|
||||
birthday = datetime(birth_year, 1, 1) + timedelta(days=random_days)
|
||||
return birthday.strftime("%Y-%m-%d")
|
||||
157
social_networks/gmail/gmail_login.py
Normale Datei
157
social_networks/gmail/gmail_login.py
Normale Datei
@ -0,0 +1,157 @@
|
||||
"""
|
||||
Gmail Login - Handhabt den Login-Prozess
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict
|
||||
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_login")
|
||||
|
||||
class GmailLogin:
|
||||
"""
|
||||
Handhabt den Gmail/Google Account Login-Prozess
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page, ui_helper: GmailUIHelper, screenshots_dir: str = None, save_screenshots: bool = True):
|
||||
"""
|
||||
Initialisiert den Login Handler
|
||||
"""
|
||||
self.page = page
|
||||
self.ui_helper = ui_helper
|
||||
self.screenshots_dir = screenshots_dir
|
||||
self.save_screenshots = save_screenshots
|
||||
|
||||
def login(self, username: str, password: str) -> Dict[str, any]:
|
||||
"""
|
||||
Führt den Login durch
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starte Gmail Login für {username}")
|
||||
|
||||
# Navigiere zur Login-Seite
|
||||
self.page.goto(selectors.LOGIN_URL, wait_until="domcontentloaded")
|
||||
time.sleep(random.uniform(2, 3))
|
||||
|
||||
self.ui_helper.take_screenshot("login_page")
|
||||
|
||||
# Email eingeben
|
||||
if self.ui_helper.wait_for_element(selectors.LOGIN_EMAIL_INPUT, timeout=10000):
|
||||
logger.info("Gebe Email-Adresse ein")
|
||||
# Füge @gmail.com hinzu falls nicht vorhanden
|
||||
email = username if "@" in username else f"{username}@gmail.com"
|
||||
self.ui_helper.type_with_delay(selectors.LOGIN_EMAIL_INPUT, email)
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
|
||||
# Screenshot vor dem Weiter-Klick
|
||||
self.ui_helper.take_screenshot("email_entered")
|
||||
|
||||
# Weiter klicken
|
||||
logger.info("Klicke auf Weiter")
|
||||
self.ui_helper.click_with_retry(selectors.LOGIN_NEXT_BUTTON)
|
||||
self.ui_helper.wait_for_loading_to_finish()
|
||||
time.sleep(random.uniform(2, 3))
|
||||
|
||||
# Passwort eingeben
|
||||
if self.ui_helper.wait_for_element(selectors.LOGIN_PASSWORD_INPUT, timeout=10000):
|
||||
logger.info("Gebe Passwort ein")
|
||||
self.ui_helper.type_with_delay(selectors.LOGIN_PASSWORD_INPUT, password)
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
|
||||
# Screenshot vor dem Login
|
||||
self.ui_helper.take_screenshot("password_entered")
|
||||
|
||||
# Login Button klicken
|
||||
logger.info("Klicke auf Weiter")
|
||||
self.ui_helper.click_with_retry(selectors.LOGIN_NEXT_BUTTON)
|
||||
|
||||
# Warte auf Navigation
|
||||
self.ui_helper.wait_for_navigation()
|
||||
time.sleep(random.uniform(3, 5))
|
||||
|
||||
# Prüfe ob Login erfolgreich war
|
||||
if self._check_login_success():
|
||||
logger.info("Login erfolgreich")
|
||||
self.ui_helper.take_screenshot("login_success")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Login erfolgreich"
|
||||
}
|
||||
else:
|
||||
error_msg = self._get_error_message()
|
||||
logger.error(f"Login fehlgeschlagen: {error_msg}")
|
||||
self.ui_helper.take_screenshot("login_failed")
|
||||
return {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"message": f"Login fehlgeschlagen: {error_msg}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Login: {e}")
|
||||
self.ui_helper.take_screenshot("login_error")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Login fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
|
||||
def _check_login_success(self) -> bool:
|
||||
"""
|
||||
Prüft ob der Login erfolgreich war
|
||||
"""
|
||||
try:
|
||||
# Prüfe ob wir auf einer Google-Seite sind
|
||||
current_url = self.page.url
|
||||
success_indicators = [
|
||||
"myaccount.google.com",
|
||||
"mail.google.com",
|
||||
"youtube.com",
|
||||
"google.com/webhp",
|
||||
"accounts.google.com/b/"
|
||||
]
|
||||
|
||||
for indicator in success_indicators:
|
||||
if indicator in current_url:
|
||||
return True
|
||||
|
||||
# Prüfe ob Login-Formular noch sichtbar ist
|
||||
if self.ui_helper.is_element_visible(selectors.LOGIN_EMAIL_INPUT):
|
||||
return False
|
||||
|
||||
# Prüfe auf Fehlermeldung
|
||||
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler bei der Login-Prüfung: {e}")
|
||||
return False
|
||||
|
||||
def _get_error_message(self) -> str:
|
||||
"""
|
||||
Holt die Fehlermeldung falls vorhanden
|
||||
"""
|
||||
try:
|
||||
# Prüfe verschiedene Fehlermeldungs-Selektoren
|
||||
error_selectors = [
|
||||
selectors.ERROR_MESSAGE,
|
||||
selectors.ERROR_MESSAGE_ALT,
|
||||
selectors.FORM_ERROR
|
||||
]
|
||||
|
||||
for selector in error_selectors:
|
||||
if self.ui_helper.is_element_visible(selector):
|
||||
error_text = self.ui_helper.get_element_text(selector)
|
||||
if error_text:
|
||||
return error_text
|
||||
|
||||
return "Login fehlgeschlagen"
|
||||
except:
|
||||
return "Unbekannter Fehler"
|
||||
548
social_networks/gmail/gmail_registration.py
Normale Datei
548
social_networks/gmail/gmail_registration.py
Normale Datei
@ -0,0 +1,548 @@
|
||||
"""
|
||||
Gmail Registrierung - Handhabt den Registrierungsprozess
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict
|
||||
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):
|
||||
"""
|
||||
Initialisiert die Registrierung
|
||||
"""
|
||||
self.page = page
|
||||
self.ui_helper = ui_helper
|
||||
self.screenshots_dir = screenshots_dir
|
||||
self.save_screenshots = save_screenshots
|
||||
|
||||
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: Telefonnummer (optional/erforderlich)
|
||||
phone_result = self._handle_phone_verification(account_data)
|
||||
if not phone_result["success"]:
|
||||
return phone_result
|
||||
|
||||
# Schritt 6: Recovery Email (optional)
|
||||
recovery_result = self._handle_recovery_email(account_data)
|
||||
if not recovery_result["success"]:
|
||||
return recovery_result
|
||||
|
||||
# Schritt 7: Nutzungsbedingungen akzeptieren
|
||||
terms_result = self._accept_terms()
|
||||
if not terms_result["success"]:
|
||||
return terms_result
|
||||
|
||||
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
|
||||
logger.info(f"Gebe Geburtstag ein: {day}")
|
||||
self.ui_helper.type_with_delay(selectors.BIRTHDAY_DAY, day.lstrip("0"))
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
|
||||
# Monat auswählen
|
||||
logger.info(f"Wähle Geburtsmonat: {month}")
|
||||
month_value = str(int(month)) # Entferne führende Null
|
||||
self.ui_helper.select_dropdown_option(selectors.BIRTHDAY_MONTH, month_value)
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
|
||||
# Jahr eingeben
|
||||
logger.info(f"Gebe Geburtsjahr ein: {year}")
|
||||
self.ui_helper.type_with_delay(selectors.BIRTHDAY_YEAR, year)
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
|
||||
# Geschlecht auswählen
|
||||
gender = account_data.get("gender", "male").lower()
|
||||
gender_value = "1" if gender == "male" else "2" # 1=männlich, 2=weiblich
|
||||
logger.info(f"Wähle Geschlecht: {gender}")
|
||||
self.ui_helper.select_dropdown_option(selectors.GENDER_SELECT, gender_value)
|
||||
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")
|
||||
|
||||
# Prüfe ob wir einen Benutzernamen eingeben können
|
||||
if self.ui_helper.wait_for_element(selectors.GMAIL_USERNAME_INPUT, timeout=10000):
|
||||
username = account_data.get("username", "")
|
||||
if not username:
|
||||
# Generiere einen Benutzernamen
|
||||
from social_networks.gmail.gmail_utils import GmailUtils
|
||||
utils = GmailUtils()
|
||||
username = utils.generate_gmail_username(
|
||||
account_data.get("first_name", ""),
|
||||
account_data.get("last_name", "")
|
||||
)
|
||||
|
||||
logger.info(f"Gebe Gmail-Benutzernamen ein: {username}")
|
||||
self.ui_helper.type_with_delay(selectors.GMAIL_USERNAME_INPUT, username)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# 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))
|
||||
|
||||
# Prüfe auf Fehler (Benutzername bereits vergeben)
|
||||
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
|
||||
error_text = self.ui_helper.get_element_text(selectors.ERROR_MESSAGE)
|
||||
logger.warning(f"Benutzername-Fehler: {error_text}")
|
||||
# TODO: Implementiere alternative Benutzernamen-Vorschläge
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"username": username,
|
||||
"email": f"{username}@gmail.com"
|
||||
}
|
||||
|
||||
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-Verifizierung (falls erforderlich)
|
||||
"""
|
||||
try:
|
||||
logger.info("Prüfe auf Telefonnummer-Verifizierung")
|
||||
|
||||
# Prüfe ob Telefonnummer erforderlich ist
|
||||
if not self.ui_helper.wait_for_element(selectors.PHONE_INPUT, timeout=5000):
|
||||
logger.info("Telefonnummer nicht erforderlich")
|
||||
return {"success": True}
|
||||
|
||||
# Wenn Telefonnummer optional ist, überspringe
|
||||
if self.ui_helper.is_element_visible(selectors.SKIP_BUTTON):
|
||||
logger.info("Überspringe Telefonnummer")
|
||||
self.ui_helper.click_with_retry(selectors.SKIP_BUTTON)
|
||||
time.sleep(random.uniform(2, 3))
|
||||
return {"success": True}
|
||||
|
||||
# Telefonnummer eingeben falls vorhanden
|
||||
phone = account_data.get("phone", "")
|
||||
if phone:
|
||||
logger.info(f"Gebe Telefonnummer ein: {phone}")
|
||||
self.ui_helper.type_with_delay(selectors.PHONE_INPUT, phone)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# Weiter klicken
|
||||
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
||||
self.ui_helper.wait_for_loading_to_finish()
|
||||
|
||||
# TODO: SMS-Verifizierung implementieren
|
||||
logger.warning("SMS-Verifizierung noch nicht implementiert")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Telefonnummer-Schritt abgeschlossen"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Telefonnummer-Verifizierung: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def _handle_recovery_email(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
||||
"""
|
||||
Handhabt die Recovery-Email (optional)
|
||||
"""
|
||||
try:
|
||||
logger.info("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 nicht vorhanden")
|
||||
return {"success": True}
|
||||
|
||||
# Überspringe wenn möglich
|
||||
if self.ui_helper.is_element_visible(selectors.SKIP_BUTTON):
|
||||
logger.info("Überspringe Recovery-Email")
|
||||
self.ui_helper.click_with_retry(selectors.SKIP_BUTTON)
|
||||
time.sleep(random.uniform(2, 3))
|
||||
else:
|
||||
# Recovery-Email eingeben falls vorhanden
|
||||
recovery_email = account_data.get("recovery_email", "")
|
||||
if recovery_email:
|
||||
logger.info(f"Gebe Recovery-Email ein: {recovery_email}")
|
||||
self.ui_helper.type_with_delay(selectors.RECOVERY_EMAIL_INPUT, recovery_email)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# Weiter klicken
|
||||
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
||||
self.ui_helper.wait_for_loading_to_finish()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Recovery-Email Schritt abgeschlossen"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Recovery-Email: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
59
social_networks/gmail/gmail_selectors.py
Normale Datei
59
social_networks/gmail/gmail_selectors.py
Normale Datei
@ -0,0 +1,59 @@
|
||||
"""
|
||||
Gmail/Google Account UI Selektoren und URLs
|
||||
"""
|
||||
|
||||
# URLs
|
||||
BASE_URL = "https://accounts.google.com/"
|
||||
REGISTRATION_URL = "https://workspace.google.com/intl/de/gmail/"
|
||||
LOGIN_URL = "https://accounts.google.com/ServiceLogin"
|
||||
|
||||
# Name Eingabe (Erster Schritt)
|
||||
FIRST_NAME_INPUT = "input[name='firstName']"
|
||||
LAST_NAME_INPUT = "input[name='lastName']"
|
||||
NEXT_BUTTON = "button[jsname='LgbsSe']"
|
||||
NEXT_BUTTON_SPAN = "span:has-text('Weiter')"
|
||||
NEXT_BUTTON_MATERIAL = "div.VfPpkd-RLmnJb" # Material Design Weiter-Button
|
||||
|
||||
# Geburtsdatum und Geschlecht
|
||||
BIRTHDAY_DAY = "input[name='day']"
|
||||
BIRTHDAY_MONTH = "select[name='month']"
|
||||
BIRTHDAY_YEAR = "input[name='year']"
|
||||
GENDER_SELECT = "select[name='gender']"
|
||||
|
||||
# Gmail-Adresse erstellen
|
||||
CREATE_GMAIL_RADIO = "div[data-value='createAccount']"
|
||||
GMAIL_USERNAME_INPUT = "input[name='Username']"
|
||||
|
||||
# Passwort
|
||||
PASSWORD_INPUT = "input[name='Passwd']"
|
||||
PASSWORD_CONFIRM_INPUT = "input[name='PasswdAgain']"
|
||||
|
||||
# Telefonnummer Verifizierung
|
||||
PHONE_INPUT = "input[id='phoneNumberId']"
|
||||
PHONE_COUNTRY_SELECT = "select[data-id='countryList']"
|
||||
|
||||
# SMS Verifizierung
|
||||
SMS_CODE_INPUT = "input[name='code']"
|
||||
VERIFY_BUTTON = "button:has-text('Bestätigen')"
|
||||
|
||||
# Recovery Email (Optional)
|
||||
RECOVERY_EMAIL_INPUT = "input[name='recoveryEmail']"
|
||||
SKIP_BUTTON = "button:has-text('Überspringen')"
|
||||
|
||||
# Nutzungsbedingungen
|
||||
AGREE_BUTTON = "button:has-text('Ich stimme zu')"
|
||||
TERMS_CHECKBOX = "input[type='checkbox']"
|
||||
|
||||
# Fehler- und Erfolgsmeldungen
|
||||
ERROR_MESSAGE = "div[jsname='B34EJ'] span"
|
||||
ERROR_MESSAGE_ALT = "div.LXRPh"
|
||||
CAPTCHA_CONTAINER = "div.aCsJod"
|
||||
|
||||
# Login Seite
|
||||
LOGIN_EMAIL_INPUT = "input[type='email']"
|
||||
LOGIN_PASSWORD_INPUT = "input[type='password'][name='password']"
|
||||
LOGIN_NEXT_BUTTON = "button:has-text('Weiter')"
|
||||
|
||||
# Allgemeine Elemente
|
||||
LOADING_SPINNER = "div.ANuIbb"
|
||||
FORM_ERROR = "div[jsname='B34EJ']"
|
||||
151
social_networks/gmail/gmail_ui_helper.py
Normale Datei
151
social_networks/gmail/gmail_ui_helper.py
Normale Datei
@ -0,0 +1,151 @@
|
||||
"""
|
||||
Gmail UI Helper - Hilfsfunktionen für UI-Interaktionen
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
from typing import Optional
|
||||
from playwright.sync_api import Page, ElementHandle
|
||||
|
||||
logger = logging.getLogger("gmail_ui_helper")
|
||||
|
||||
class GmailUIHelper:
|
||||
"""
|
||||
Hilfsklasse für Gmail UI-Interaktionen
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page, screenshots_dir: str = None, save_screenshots: bool = True):
|
||||
"""
|
||||
Initialisiert den UI Helper
|
||||
"""
|
||||
self.page = page
|
||||
self.screenshots_dir = screenshots_dir or "logs/screenshots"
|
||||
self.save_screenshots = save_screenshots
|
||||
|
||||
# Screenshot-Verzeichnis erstellen falls nötig
|
||||
if self.save_screenshots and not os.path.exists(self.screenshots_dir):
|
||||
os.makedirs(self.screenshots_dir)
|
||||
|
||||
def take_screenshot(self, name: str) -> Optional[str]:
|
||||
"""
|
||||
Erstellt einen Screenshot
|
||||
"""
|
||||
if not self.save_screenshots:
|
||||
return None
|
||||
|
||||
try:
|
||||
timestamp = int(time.time())
|
||||
filename = f"{name}_{timestamp}.png"
|
||||
filepath = os.path.join(self.screenshots_dir, filename)
|
||||
self.page.screenshot(path=filepath)
|
||||
logger.debug(f"Screenshot gespeichert: {filepath}")
|
||||
return filepath
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Erstellen des Screenshots: {e}")
|
||||
return None
|
||||
|
||||
def wait_for_element(self, selector: str, timeout: int = 30000) -> bool:
|
||||
"""
|
||||
Wartet auf ein Element
|
||||
"""
|
||||
try:
|
||||
self.page.wait_for_selector(selector, timeout=timeout)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Element {selector} nicht gefunden nach {timeout}ms")
|
||||
return False
|
||||
|
||||
def type_with_delay(self, selector: str, text: str, delay_min: float = 0.05, delay_max: float = 0.15):
|
||||
"""
|
||||
Tippt Text mit menschenähnlicher Verzögerung
|
||||
"""
|
||||
try:
|
||||
element = self.page.locator(selector)
|
||||
element.click()
|
||||
|
||||
for char in text:
|
||||
element.type(char)
|
||||
time.sleep(random.uniform(delay_min, delay_max))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Tippen in {selector}: {e}")
|
||||
raise
|
||||
|
||||
def click_with_retry(self, selector: str, max_attempts: int = 3) -> bool:
|
||||
"""
|
||||
Klickt auf ein Element mit Wiederholungsversuchen
|
||||
"""
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
self.page.click(selector)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Klick-Versuch {attempt + 1} fehlgeschlagen: {e}")
|
||||
if attempt < max_attempts - 1:
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
return False
|
||||
|
||||
def scroll_to_element(self, selector: str):
|
||||
"""
|
||||
Scrollt zu einem Element
|
||||
"""
|
||||
try:
|
||||
self.page.locator(selector).scroll_into_view_if_needed()
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Scrollen zu {selector}: {e}")
|
||||
|
||||
def is_element_visible(self, selector: str) -> bool:
|
||||
"""
|
||||
Prüft ob ein Element sichtbar ist
|
||||
"""
|
||||
try:
|
||||
return self.page.locator(selector).is_visible()
|
||||
except:
|
||||
return False
|
||||
|
||||
def get_element_text(self, selector: str) -> Optional[str]:
|
||||
"""
|
||||
Holt den Text eines Elements
|
||||
"""
|
||||
try:
|
||||
return self.page.locator(selector).text_content()
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Lesen des Texts von {selector}: {e}")
|
||||
return None
|
||||
|
||||
def select_dropdown_option(self, selector: str, value: str):
|
||||
"""
|
||||
Wählt eine Option aus einem Dropdown
|
||||
"""
|
||||
try:
|
||||
self.page.select_option(selector, value)
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Auswählen von {value} in {selector}: {e}")
|
||||
raise
|
||||
|
||||
def wait_for_navigation(self, timeout: int = 30000):
|
||||
"""
|
||||
Wartet auf Navigation
|
||||
"""
|
||||
try:
|
||||
self.page.wait_for_load_state("networkidle", timeout=timeout)
|
||||
except Exception as e:
|
||||
logger.warning(f"Navigation-Timeout nach {timeout}ms: {e}")
|
||||
|
||||
def wait_for_loading_to_finish(self):
|
||||
"""
|
||||
Wartet bis Ladeanimation verschwunden ist
|
||||
"""
|
||||
try:
|
||||
# Warte bis der Loading Spinner nicht mehr sichtbar ist
|
||||
from social_networks.gmail import gmail_selectors as selectors
|
||||
if self.is_element_visible(selectors.LOADING_SPINNER):
|
||||
self.page.wait_for_selector(selectors.LOADING_SPINNER, state="hidden", timeout=10000)
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
except:
|
||||
pass
|
||||
122
social_networks/gmail/gmail_utils.py
Normale Datei
122
social_networks/gmail/gmail_utils.py
Normale Datei
@ -0,0 +1,122 @@
|
||||
"""
|
||||
Gmail Utils - Utility-Funktionen für Gmail
|
||||
"""
|
||||
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger("gmail_utils")
|
||||
|
||||
class GmailUtils:
|
||||
"""
|
||||
Utility-Funktionen für Gmail/Google Accounts
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def generate_gmail_username(first_name: str, last_name: str) -> str:
|
||||
"""
|
||||
Generiert einen Gmail-kompatiblen Benutzernamen
|
||||
"""
|
||||
# Basis aus Vor- und Nachname
|
||||
first_clean = ''.join(c.lower() for c in first_name if c.isalnum())
|
||||
last_clean = ''.join(c.lower() for c in last_name if c.isalnum())
|
||||
|
||||
# Verschiedene Varianten
|
||||
variants = [
|
||||
f"{first_clean}{last_clean}",
|
||||
f"{first_clean}.{last_clean}",
|
||||
f"{last_clean}{first_clean}",
|
||||
f"{first_clean[0]}{last_clean}",
|
||||
f"{first_clean}{last_clean[0]}"
|
||||
]
|
||||
|
||||
# Wähle eine zufällige Variante
|
||||
base = random.choice(variants)
|
||||
|
||||
# Füge zufällige Zahlen hinzu
|
||||
random_suffix = ''.join(random.choices(string.digits, k=random.randint(2, 4)))
|
||||
|
||||
return f"{base}{random_suffix}"
|
||||
|
||||
@staticmethod
|
||||
def generate_secure_password(length: int = 16) -> str:
|
||||
"""
|
||||
Generiert ein sicheres Passwort für Google-Anforderungen
|
||||
- Mindestens 8 Zeichen
|
||||
- Mischung aus Buchstaben, Zahlen und Symbolen
|
||||
"""
|
||||
# Stelle sicher dass alle Zeichentypen enthalten sind
|
||||
password_chars = []
|
||||
|
||||
# Mindestens 2 Kleinbuchstaben
|
||||
password_chars.extend(random.choices(string.ascii_lowercase, k=2))
|
||||
|
||||
# Mindestens 2 Großbuchstaben
|
||||
password_chars.extend(random.choices(string.ascii_uppercase, k=2))
|
||||
|
||||
# Mindestens 2 Zahlen
|
||||
password_chars.extend(random.choices(string.digits, k=2))
|
||||
|
||||
# Mindestens 2 Sonderzeichen
|
||||
special_chars = "!@#$%^&*"
|
||||
password_chars.extend(random.choices(special_chars, k=2))
|
||||
|
||||
# Fülle mit zufälligen Zeichen auf
|
||||
remaining_length = length - len(password_chars)
|
||||
all_chars = string.ascii_letters + string.digits + special_chars
|
||||
password_chars.extend(random.choices(all_chars, k=remaining_length))
|
||||
|
||||
# Mische die Zeichen
|
||||
random.shuffle(password_chars)
|
||||
|
||||
return ''.join(password_chars)
|
||||
|
||||
@staticmethod
|
||||
def is_valid_gmail_address(email: str) -> bool:
|
||||
"""
|
||||
Prüft ob eine Gmail-Adresse gültig ist
|
||||
"""
|
||||
if not email.endswith("@gmail.com"):
|
||||
return False
|
||||
|
||||
username = email.split("@")[0]
|
||||
|
||||
# Gmail-Regeln:
|
||||
# - 6-30 Zeichen
|
||||
# - Buchstaben, Zahlen und Punkte
|
||||
# - Muss mit Buchstabe oder Zahl beginnen
|
||||
# - Kein Punkt am Anfang oder Ende
|
||||
# - Keine aufeinanderfolgenden Punkte
|
||||
|
||||
if len(username) < 6 or len(username) > 30:
|
||||
return False
|
||||
|
||||
if not username[0].isalnum() or not username[-1].isalnum():
|
||||
return False
|
||||
|
||||
if ".." in username:
|
||||
return False
|
||||
|
||||
# Prüfe erlaubte Zeichen
|
||||
for char in username:
|
||||
if not (char.isalnum() or char == "."):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def format_phone_for_google(phone: str, country_code: str = "+1") -> str:
|
||||
"""
|
||||
Formatiert eine Telefonnummer für Google
|
||||
"""
|
||||
# Entferne alle nicht-numerischen Zeichen
|
||||
phone_digits = ''.join(c for c in phone if c.isdigit())
|
||||
|
||||
# Wenn die Nummer bereits mit Ländercode beginnt
|
||||
if phone.startswith("+"):
|
||||
return phone
|
||||
|
||||
# Füge Ländercode hinzu
|
||||
return f"{country_code}{phone_digits}"
|
||||
230
social_networks/gmail/gmail_verification.py
Normale Datei
230
social_networks/gmail/gmail_verification.py
Normale Datei
@ -0,0 +1,230 @@
|
||||
"""
|
||||
Gmail Verification - Handhabt die Verifizierungsprozesse
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict, Optional
|
||||
from playwright.sync_api import Page
|
||||
|
||||
from social_networks.gmail import gmail_selectors as selectors
|
||||
from social_networks.gmail.gmail_ui_helper import GmailUIHelper
|
||||
from utils.email_handler import EmailHandler
|
||||
|
||||
logger = logging.getLogger("gmail_verification")
|
||||
|
||||
class GmailVerification:
|
||||
"""
|
||||
Handhabt die Gmail/Google Account Verifizierung
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page, ui_helper: GmailUIHelper, email_handler: EmailHandler = None,
|
||||
screenshots_dir: str = None, save_screenshots: bool = True):
|
||||
"""
|
||||
Initialisiert den Verification Handler
|
||||
"""
|
||||
self.page = page
|
||||
self.ui_helper = ui_helper
|
||||
self.email_handler = email_handler
|
||||
self.screenshots_dir = screenshots_dir
|
||||
self.save_screenshots = save_screenshots
|
||||
|
||||
def handle_phone_verification(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
||||
"""
|
||||
Handhabt die Telefonnummer-Verifizierung
|
||||
"""
|
||||
try:
|
||||
logger.info("Starte Telefon-Verifizierung")
|
||||
|
||||
# Warte auf Telefonnummer-Eingabefeld
|
||||
if not self.ui_helper.wait_for_element(selectors.PHONE_INPUT, timeout=10000):
|
||||
logger.info("Telefonnummer-Eingabefeld nicht gefunden")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Keine Telefon-Verifizierung erforderlich"
|
||||
}
|
||||
|
||||
self.ui_helper.take_screenshot("phone_verification_page")
|
||||
|
||||
# Telefonnummer eingeben
|
||||
phone = account_data.get("phone", "")
|
||||
if not phone:
|
||||
logger.warning("Keine Telefonnummer vorhanden, überspringe wenn möglich")
|
||||
# Versuche zu überspringen
|
||||
if self.ui_helper.is_element_visible(selectors.SKIP_BUTTON):
|
||||
self.ui_helper.click_with_retry(selectors.SKIP_BUTTON)
|
||||
time.sleep(random.uniform(2, 3))
|
||||
return {"success": True}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Telefonnummer erforderlich aber nicht vorhanden",
|
||||
"message": "Telefonnummer wird benötigt"
|
||||
}
|
||||
|
||||
logger.info(f"Gebe Telefonnummer ein: {phone}")
|
||||
|
||||
# Telefonnummer eingeben
|
||||
self.ui_helper.type_with_delay(selectors.PHONE_INPUT, phone)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# Screenshot vor dem Absenden
|
||||
self.ui_helper.take_screenshot("phone_entered")
|
||||
|
||||
# Absenden
|
||||
if self.ui_helper.wait_for_element(selectors.NEXT_BUTTON, timeout=5000):
|
||||
logger.info("Sende Telefonnummer ab")
|
||||
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
||||
self.ui_helper.wait_for_loading_to_finish()
|
||||
time.sleep(random.uniform(3, 5))
|
||||
|
||||
# Warte auf SMS-Code Eingabefeld
|
||||
if self.ui_helper.wait_for_element(selectors.SMS_CODE_INPUT, timeout=15000):
|
||||
logger.info("SMS-Code Eingabefeld gefunden")
|
||||
self.ui_helper.take_screenshot("sms_code_page")
|
||||
|
||||
# Hier würde normalerweise der SMS-Code abgerufen werden
|
||||
sms_code = self._get_sms_code(phone)
|
||||
|
||||
if not sms_code:
|
||||
logger.error("Kein SMS-Code erhalten")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Kein SMS-Code erhalten",
|
||||
"message": "SMS-Verifizierung fehlgeschlagen"
|
||||
}
|
||||
|
||||
# SMS-Code eingeben
|
||||
logger.info(f"Gebe SMS-Code ein: {sms_code}")
|
||||
self.ui_helper.type_with_delay(selectors.SMS_CODE_INPUT, sms_code)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# Code bestätigen
|
||||
if self.ui_helper.wait_for_element(selectors.VERIFY_BUTTON, timeout=5000):
|
||||
logger.info("Bestätige SMS-Code")
|
||||
self.ui_helper.click_with_retry(selectors.VERIFY_BUTTON)
|
||||
else:
|
||||
# Versuche alternativen Button
|
||||
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
|
||||
|
||||
self.ui_helper.wait_for_loading_to_finish()
|
||||
time.sleep(random.uniform(3, 5))
|
||||
|
||||
# Prüfe auf Erfolg
|
||||
if self._check_verification_success():
|
||||
logger.info("Telefon-Verifizierung erfolgreich")
|
||||
self.ui_helper.take_screenshot("verification_success")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Verifizierung erfolgreich"
|
||||
}
|
||||
else:
|
||||
error_msg = self._get_verification_error()
|
||||
logger.error(f"Verifizierung fehlgeschlagen: {error_msg}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"message": f"Verifizierung fehlgeschlagen: {error_msg}"
|
||||
}
|
||||
|
||||
else:
|
||||
logger.info("Kein SMS-Code erforderlich")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Telefon-Verifizierung abgeschlossen"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Telefon-Verifizierung: {e}")
|
||||
self.ui_helper.take_screenshot("verification_error")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Verifizierung fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
|
||||
def handle_captcha(self) -> Dict[str, any]:
|
||||
"""
|
||||
Handhabt Captcha-Herausforderungen
|
||||
"""
|
||||
try:
|
||||
logger.info("Prüfe auf Captcha")
|
||||
|
||||
if self.ui_helper.is_element_visible(selectors.CAPTCHA_CONTAINER):
|
||||
logger.warning("Captcha erkannt - manuelle Lösung erforderlich")
|
||||
self.ui_helper.take_screenshot("captcha_detected")
|
||||
|
||||
# TODO: Implementiere Captcha-Lösung
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Captcha erkannt",
|
||||
"message": "Manuelle Captcha-Lösung erforderlich"
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Kein Captcha vorhanden"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Captcha-Prüfung: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def _get_sms_code(self, phone: str) -> Optional[str]:
|
||||
"""
|
||||
Ruft den SMS-Code ab
|
||||
TODO: Implementierung für echte SMS-Code Abfrage
|
||||
"""
|
||||
logger.warning("SMS-Code Abruf noch nicht implementiert - verwende Platzhalter")
|
||||
# In einer echten Implementierung würde hier der SMS-Code
|
||||
# von einem SMS-Service abgerufen werden
|
||||
return "123456" # Platzhalter
|
||||
|
||||
def _check_verification_success(self) -> bool:
|
||||
"""
|
||||
Prüft ob die Verifizierung erfolgreich war
|
||||
"""
|
||||
try:
|
||||
# Prüfe ob wir weitergeleitet wurden
|
||||
current_url = self.page.url
|
||||
if any(indicator in current_url for indicator in ["myaccount", "mail.google", "youtube"]):
|
||||
return True
|
||||
|
||||
# Prüfe ob SMS-Code Feld noch sichtbar ist
|
||||
if self.ui_helper.is_element_visible(selectors.SMS_CODE_INPUT):
|
||||
# Prüfe auf Fehlermeldung
|
||||
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
|
||||
return False
|
||||
# Wenn kein Fehler aber noch SMS-Code Feld, warten wir noch
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler bei der Verifizierungs-Prüfung: {e}")
|
||||
return False
|
||||
|
||||
def _get_verification_error(self) -> str:
|
||||
"""
|
||||
Holt die Fehlermeldung falls vorhanden
|
||||
"""
|
||||
try:
|
||||
error_selectors = [
|
||||
selectors.ERROR_MESSAGE,
|
||||
selectors.ERROR_MESSAGE_ALT,
|
||||
selectors.FORM_ERROR
|
||||
]
|
||||
|
||||
for selector in error_selectors:
|
||||
if self.ui_helper.is_element_visible(selector):
|
||||
error_text = self.ui_helper.get_element_text(selector)
|
||||
if error_text:
|
||||
return error_text
|
||||
|
||||
return "Verifizierung fehlgeschlagen"
|
||||
except:
|
||||
return "Unbekannter Fehler"
|
||||
44
social_networks/gmail/gmail_workflow.py
Normale Datei
44
social_networks/gmail/gmail_workflow.py
Normale Datei
@ -0,0 +1,44 @@
|
||||
"""
|
||||
Gmail Workflow - Workflow-Definitionen für Gmail/Google Accounts
|
||||
"""
|
||||
|
||||
# Workflow-Schritte für Gmail
|
||||
REGISTRATION_WORKFLOW = [
|
||||
"navigate_to_registration",
|
||||
"fill_name_form",
|
||||
"fill_birthday_gender",
|
||||
"create_gmail_address",
|
||||
"set_password",
|
||||
"handle_phone_verification",
|
||||
"handle_recovery_email",
|
||||
"accept_terms",
|
||||
"verify_account_creation"
|
||||
]
|
||||
|
||||
LOGIN_WORKFLOW = [
|
||||
"navigate_to_login",
|
||||
"enter_email",
|
||||
"enter_password",
|
||||
"handle_2fa_if_needed",
|
||||
"verify_login_success"
|
||||
]
|
||||
|
||||
# Timeouts in Sekunden
|
||||
TIMEOUTS = {
|
||||
"page_load": 30,
|
||||
"element_wait": 10,
|
||||
"verification_wait": 60,
|
||||
"sms_wait": 120,
|
||||
"captcha_wait": 300
|
||||
}
|
||||
|
||||
# Fehler-Nachrichten
|
||||
ERROR_MESSAGES = {
|
||||
"username_taken": "Dieser Nutzername ist bereits vergeben",
|
||||
"invalid_phone": "Ungültige Telefonnummer",
|
||||
"invalid_code": "Der eingegebene Code ist ungültig",
|
||||
"too_many_attempts": "Zu viele Versuche",
|
||||
"account_suspended": "Dieses Konto wurde gesperrt",
|
||||
"captcha_required": "Bitte lösen Sie das Captcha",
|
||||
"age_restriction": "Sie müssen mindestens 13 Jahre alt sein"
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren