Files
AccountForger-neuerUpload/social_networks/x/x_login.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

669 Zeilen
27 KiB
Python

# social_networks/x/x_login.py
"""
X (Twitter) Login - Klasse für die Anmeldung bei X-Konten
"""
import time
import random
from typing import Dict, Any, Optional
from .x_selectors import XSelectors
from .x_workflow import XWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("x_login")
class XLogin:
"""
Klasse für die Anmeldung bei X-Konten.
Behandelt den kompletten Login-Prozess inklusive möglicher Sicherheitsabfragen.
"""
def __init__(self, automation):
"""
Initialisiert die X-Login-Klasse.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.selectors = XSelectors()
self.workflow = XWorkflow.get_login_workflow()
logger.debug("X-Login initialisiert")
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
"""
Führt den Login-Prozess für einen X-Account durch.
Args:
username_or_email: Benutzername oder E-Mail-Adresse
password: Passwort
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis des Logins mit Status
"""
logger.info(f"Starte X-Login für '{username_or_email}'")
try:
# 1. Zur Startseite navigieren
self.automation._emit_customer_log("🌐 Mit X verbinden...")
if not self._navigate_to_homepage():
return {
"success": False,
"error": "Konnte nicht zur X-Startseite navigieren",
"stage": "navigation"
}
# 2. Cookie-Banner behandeln
self._handle_cookie_banner()
# 3. Login-Button klicken (überspringen, da wir direkt zur Login-Seite navigieren)
# self.automation._emit_customer_log("🔓 Login-Formular wird geöffnet...")
# Der Login-Button ist nicht mehr nötig, da wir direkt auf der Login-Seite sind
# 4. Benutzername/E-Mail eingeben
self.automation._emit_customer_log("📧 Anmeldedaten werden eingegeben...")
if not self._enter_username(username_or_email):
return {
"success": False,
"error": "Fehler beim Eingeben des Benutzernamens/E-Mail",
"stage": "username_input"
}
# 5. Weiter klicken
if not self._click_next():
return {
"success": False,
"error": "Fehler beim Fortfahren nach Benutzername",
"stage": "next_button"
}
# 6. Eventuell nach Telefonnummer/E-Mail fragen (Sicherheitsabfrage)
if self._is_additional_info_required():
logger.info("Zusätzliche Informationen erforderlich")
if not self._handle_additional_info_request(kwargs.get("phone_number"), kwargs.get("email")):
return {
"success": False,
"error": "Konnte zusätzliche Sicherheitsinformationen nicht bereitstellen",
"stage": "additional_info"
}
# 7. Passwort eingeben
self.automation._emit_customer_log("🔐 Passwort wird eingegeben...")
if not self._enter_password(password):
return {
"success": False,
"error": "Fehler beim Eingeben des Passworts",
"stage": "password_input"
}
# 8. Login abschicken
if not self._submit_login():
return {
"success": False,
"error": "Fehler beim Abschicken des Login-Formulars",
"stage": "login_submit"
}
# 9. Auf eventuelle Challenges/Captchas prüfen
self.automation._emit_customer_log("🔍 Überprüfe Login-Status...")
challenge_result = self._handle_login_challenges()
if not challenge_result["success"]:
return {
"success": False,
"error": challenge_result.get("error", "Login-Challenge fehlgeschlagen"),
"stage": "login_challenge"
}
# 10. Erfolgreichen Login verifizieren
if not self._verify_login_success():
return {
"success": False,
"error": "Login scheinbar fehlgeschlagen - keine Erfolgsindikatoren gefunden",
"stage": "verification"
}
# Login erfolgreich
logger.info(f"X-Login für '{username_or_email}' erfolgreich")
self.automation._emit_customer_log("✅ Login erfolgreich!")
return {
"success": True,
"stage": "completed",
"username": username_or_email
}
except Exception as e:
error_msg = f"Unerwarteter Fehler beim X-Login: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"stage": "exception"
}
def _navigate_to_homepage(self) -> bool:
"""
Navigiert zur X-Login-Seite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
logger.info("Navigiere zur X-Login-Seite")
page.goto("https://x.com/i/flow/login?lang=de", wait_until="domcontentloaded", timeout=30000)
# Warte auf Seitenladung
self.automation.human_behavior.random_delay(2, 4)
# Screenshot
self.automation._take_screenshot("x_login_page")
return True
except Exception as e:
logger.error(f"Fehler beim Navigieren zur X-Login-Seite: {e}")
return False
def _handle_cookie_banner(self):
"""
Behandelt eventuelle Cookie-Banner.
"""
try:
page = self.automation.browser.page
for selector in self.selectors.COOKIE_ACCEPT_BUTTONS:
try:
if page.is_visible(selector):
logger.info(f"Cookie-Banner gefunden: {selector}")
page.wait_for_selector(selector, timeout=2000).click()
self.automation.human_behavior.random_delay(1, 2)
break
except:
continue
except Exception as e:
logger.debug(f"Kein Cookie-Banner gefunden oder Fehler: {e}")
def _click_login_button(self) -> bool:
"""
Klickt auf den Login-Button.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
# Versuche verschiedene Login-Button-Selektoren
login_selectors = [
self.selectors.LOGIN["login_button"],
self.selectors.LOGIN["login_button_alt"],
'a[href="/login"]',
'div:has-text("Anmelden")',
'div:has-text("Log in")'
]
for selector in login_selectors:
try:
if page.is_visible(selector, timeout=3000):
logger.info(f"Login-Button gefunden: {selector}")
self.automation.human_behavior.random_delay(0.5, 1.5)
page.click(selector)
self.automation.human_behavior.random_delay(1, 2)
return True
except:
continue
logger.error("Keinen Login-Button gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf Login-Button: {e}")
return False
def _enter_username(self, username_or_email: str) -> bool:
"""
Gibt den Benutzernamen oder die E-Mail-Adresse ein.
Args:
username_or_email: Benutzername oder E-Mail
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
# Warte kurz, bis die Seite vollständig geladen ist
self.automation.human_behavior.random_delay(1, 2)
# Warte auf Eingabefeld - verwende den spezifischen Selektor aus der HTML-Struktur
input_selectors = [
'input[name="text"][autocomplete="username"]', # Spezifischer Selektor
'input[name="text"]', # Fallback
self.selectors.LOGIN["email_or_username_input"],
'input[autocomplete="username"]',
'input[type="text"][name="text"]'
]
for selector in input_selectors:
try:
# Warte auf sichtbares Eingabefeld
input_field = page.wait_for_selector(selector, state="visible", timeout=5000)
if input_field:
logger.info(f"Benutzername-Eingabefeld gefunden: {selector}")
# Klicke zuerst auf das Feld, um es zu fokussieren
input_field.click()
self.automation.human_behavior.random_delay(0.3, 0.5)
# Lösche eventuell vorhandenen Text
input_field.fill("")
# Tippe den Text menschlich ein - simuliere Buchstabe für Buchstabe
for char in username_or_email:
input_field.type(char)
# Zufällige Verzögerung zwischen Zeichen (50-150ms)
delay = random.uniform(0.05, 0.15)
time.sleep(delay)
self.automation.human_behavior.random_delay(0.5, 1)
# Screenshot nach Eingabe
self.automation._take_screenshot("after_username_input")
return True
except Exception as e:
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
continue
# Wenn nichts gefunden wurde, Screenshot für Debugging
self.automation._take_screenshot("username_input_not_found")
logger.error("Kein Benutzername-Eingabefeld gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Eingeben des Benutzernamens: {e}")
return False
def _click_next(self) -> bool:
"""
Klickt auf den Weiter-Button nach Benutzername-Eingabe.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
# Warte kurz
self.automation.human_behavior.random_delay(0.5, 1)
# Weiter-Button Selektoren - erweitert um spezifische Selektoren
next_selectors = [
'button[role="button"]:has-text("Weiter")', # Spezifisch für button mit role
'button:has-text("Weiter")', # Generischer button
'div[role="button"] span:has-text("Weiter")', # Span innerhalb div
'//button[contains(., "Weiter")]', # XPath Alternative
self.selectors.LOGIN["next_button"],
self.selectors.LOGIN["next_button_en"],
'div[role="button"]:has-text("Weiter")',
'div[role="button"]:has-text("Next")',
'button:has-text("Next")',
'[role="button"][type="button"]' # Generisch basierend auf Attributen
]
for selector in next_selectors:
try:
# Versuche verschiedene Methoden
if selector.startswith('//'):
# XPath
element = page.locator(selector).first
if element.is_visible(timeout=2000):
logger.info(f"Weiter-Button gefunden (XPath): {selector}")
element.click()
self.automation.human_behavior.random_delay(1, 2)
return True
else:
# CSS Selector
if page.is_visible(selector, timeout=2000):
logger.info(f"Weiter-Button gefunden: {selector}")
page.click(selector)
self.automation.human_behavior.random_delay(1, 2)
return True
except Exception as e:
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
continue
# Fallback: Suche nach Button mit Text "Weiter"
try:
button = page.get_by_role("button").filter(has_text="Weiter").first
if button.is_visible():
logger.info("Weiter-Button über get_by_role gefunden")
button.click()
self.automation.human_behavior.random_delay(1, 2)
return True
except:
pass
# Screenshot für Debugging
self.automation._take_screenshot("next_button_not_found")
logger.error("Keinen Weiter-Button gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf Weiter: {e}")
return False
def _is_additional_info_required(self) -> bool:
"""
Prüft, ob zusätzliche Informationen (Telefonnummer/E-Mail) angefordert werden.
Returns:
bool: True wenn zusätzliche Info benötigt wird
"""
try:
page = self.automation.browser.page
# Prüfe auf Sicherheitsabfrage
security_indicators = [
'text="Gib deine Telefonnummer oder E-Mail-Adresse ein"',
'text="Enter your phone number or email address"',
'input[name="text"][placeholder*="Telefon"]',
'input[name="text"][placeholder*="phone"]'
]
for indicator in security_indicators:
if page.is_visible(indicator, timeout=2000):
logger.info("Zusätzliche Sicherheitsinformationen erforderlich")
return True
return False
except Exception as e:
logger.debug(f"Keine zusätzlichen Informationen erforderlich: {e}")
return False
def _handle_additional_info_request(self, phone_number: Optional[str], email: Optional[str]) -> bool:
"""
Behandelt die Anfrage nach zusätzlichen Sicherheitsinformationen.
Args:
phone_number: Optionale Telefonnummer
email: Optionale E-Mail-Adresse
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
if not phone_number and not email:
logger.error("Keine zusätzlichen Informationen verfügbar")
return False
page = self.automation.browser.page
# Eingabefeld finden
input_field = page.wait_for_selector('input[name="text"]', timeout=5000)
if not input_field:
return False
# Bevorzuge Telefonnummer, dann E-Mail
info_to_enter = phone_number if phone_number else email
logger.info(f"Gebe zusätzliche Information ein: {info_to_enter[:3]}...")
self.automation.human_behavior.type_text(input_field, info_to_enter)
self.automation.human_behavior.random_delay(0.5, 1)
# Weiter klicken
return self._click_next()
except Exception as e:
logger.error(f"Fehler bei zusätzlichen Informationen: {e}")
return False
def _enter_password(self, password: str) -> bool:
"""
Gibt das Passwort ein.
Args:
password: Passwort
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
# Warte kurz, bis die Seite vollständig geladen ist
self.automation.human_behavior.random_delay(1, 2)
# Passwort-Eingabefeld Selektoren - erweitert um spezifische Selektoren
password_selectors = [
'input[name="password"][autocomplete="current-password"]', # Spezifischer Selektor
'input[type="password"][name="password"]', # Spezifisch mit beiden Attributen
'input[name="password"]', # Name-basiert
'input[type="password"]', # Type-basiert
self.selectors.LOGIN["password_input"],
self.selectors.LOGIN["password_input_alt"],
'input[autocomplete="current-password"]' # Autocomplete-basiert
]
for selector in password_selectors:
try:
# Warte auf sichtbares Passwortfeld
password_field = page.wait_for_selector(selector, state="visible", timeout=5000)
if password_field:
logger.info(f"Passwort-Eingabefeld gefunden: {selector}")
# Klicke zuerst auf das Feld, um es zu fokussieren
password_field.click()
self.automation.human_behavior.random_delay(0.3, 0.5)
# Lösche eventuell vorhandenen Text
password_field.fill("")
# Tippe das Passwort menschlich ein - Buchstabe für Buchstabe
for char in password:
password_field.type(char)
# Zufällige Verzögerung zwischen Zeichen (30-100ms für Passwörter)
delay = random.uniform(0.03, 0.10)
time.sleep(delay)
self.automation.human_behavior.random_delay(0.5, 1)
# Screenshot nach Eingabe
self.automation._take_screenshot("after_password_input")
return True
except Exception as e:
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
continue
# Wenn nichts gefunden wurde, Screenshot für Debugging
self.automation._take_screenshot("password_input_not_found")
logger.error("Kein Passwort-Eingabefeld gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Eingeben des Passworts: {e}")
return False
def _submit_login(self) -> bool:
"""
Schickt das Login-Formular ab.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
# Warte kurz
self.automation.human_behavior.random_delay(0.5, 1)
# Login-Submit-Button Selektoren - erweitert
submit_selectors = [
'button[role="button"]:has-text("Anmelden")', # Spezifisch für button
'button:has-text("Anmelden")', # Generischer button
'div[role="button"] span:has-text("Anmelden")', # Span innerhalb div
'//button[contains(., "Anmelden")]', # XPath Alternative
self.selectors.LOGIN["login_submit"],
self.selectors.LOGIN["login_submit_en"],
'div[role="button"]:has-text("Anmelden")',
'div[role="button"]:has-text("Log in")',
'button:has-text("Log in")',
'[role="button"][type="button"]' # Generisch basierend auf Attributen
]
for selector in submit_selectors:
try:
# Versuche verschiedene Methoden
if selector.startswith('//'):
# XPath
element = page.locator(selector).first
if element.is_visible(timeout=2000):
logger.info(f"Login-Submit-Button gefunden (XPath): {selector}")
element.click()
self.automation.human_behavior.random_delay(2, 3)
self.automation._take_screenshot("after_login_button_click")
return True
else:
# CSS Selector
if page.is_visible(selector, timeout=2000):
logger.info(f"Login-Submit-Button gefunden: {selector}")
page.click(selector)
self.automation.human_behavior.random_delay(2, 3)
self.automation._take_screenshot("after_login_button_click")
return True
except Exception as e:
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
continue
# Fallback: Suche nach Button mit Text "Anmelden"
try:
button = page.get_by_role("button").filter(has_text="Anmelden").first
if button.is_visible():
logger.info("Login-Submit-Button über get_by_role gefunden")
button.click()
self.automation.human_behavior.random_delay(2, 3)
self.automation._take_screenshot("after_login_button_click")
return True
except:
pass
# Screenshot für Debugging
self.automation._take_screenshot("login_submit_button_not_found")
logger.error("Keinen Login-Submit-Button gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Abschicken des Logins: {e}")
return False
def _handle_login_challenges(self) -> Dict[str, Any]:
"""
Behandelt eventuelle Login-Challenges (Captcha, Verifizierung, etc.).
Returns:
Dict[str, Any]: Ergebnis der Challenge-Behandlung
"""
try:
page = self.automation.browser.page
# Warte kurz auf eventuelle Challenges
self.automation.human_behavior.random_delay(2, 3)
# Prüfe auf Captcha
if page.is_visible(self.selectors.VERIFICATION["captcha_frame"], timeout=2000):
logger.warning("Captcha erkannt - manuelle Lösung erforderlich")
return {
"success": False,
"error": "Captcha erkannt - manuelle Intervention erforderlich"
}
# Prüfe auf Arkose Challenge
if page.is_visible(self.selectors.VERIFICATION["challenge_frame"], timeout=2000):
logger.warning("Arkose Challenge erkannt")
return {
"success": False,
"error": "Arkose Challenge erkannt - manuelle Intervention erforderlich"
}
# Prüfe auf Fehlermeldungen
error_selectors = [
self.selectors.ERRORS["invalid_credentials"],
self.selectors.ERRORS["error_message"],
self.selectors.ERRORS["error_alert"]
]
for error_selector in error_selectors:
if page.is_visible(error_selector, timeout=1000):
error_text = page.text_content(error_selector)
logger.error(f"Login-Fehler: {error_text}")
return {
"success": False,
"error": f"Login fehlgeschlagen: {error_text}"
}
# Keine Challenges erkannt
return {"success": True}
except Exception as e:
logger.error(f"Fehler bei Challenge-Behandlung: {e}")
return {
"success": False,
"error": f"Fehler bei Challenge-Behandlung: {str(e)}"
}
def _verify_login_success(self) -> bool:
"""
Verifiziert, ob der Login erfolgreich war.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
# Warte auf Weiterleitung
self.automation.human_behavior.random_delay(2, 3)
# Erfolgsindikatoren
success_indicators = [
self.selectors.NAVIGATION["home_link"],
self.selectors.NAVIGATION["tweet_button"],
self.selectors.NAVIGATION["primary_nav"],
'a[href="/home"]',
'nav[aria-label="Primary"]',
'div[data-testid="primaryColumn"]'
]
for indicator in success_indicators:
if page.is_visible(indicator, timeout=5000):
logger.info(f"Login erfolgreich - Indikator gefunden: {indicator}")
# Finaler Screenshot
self.automation._take_screenshot("login_success")
return True
# Prüfe URL als letzten Check
current_url = page.url
if "/home" in current_url:
logger.info("Login erfolgreich - Home-URL erreicht")
return True
logger.error("Keine Login-Erfolgsindikatoren gefunden")
return False
except Exception as e:
logger.error(f"Fehler bei Login-Verifizierung: {e}")
return False