Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,25 @@
# social_networks/x/__init__.py
"""
X (Twitter) Automatisierungsmodul
"""
from .x_automation import XAutomation
from .x_registration import XRegistration
from .x_login import XLogin
from .x_verification import XVerification
from .x_ui_helper import XUIHelper
from .x_utils import XUtils
from .x_selectors import XSelectors
from .x_workflow import XWorkflow
__all__ = [
'XAutomation',
'XRegistration',
'XLogin',
'XVerification',
'XUIHelper',
'XUtils',
'XSelectors',
'XWorkflow'
]

Datei anzeigen

@ -0,0 +1,392 @@
"""
X (Twitter) Automatisierung - Hauptklasse für X-Automatisierungsfunktionalität
"""
import time
import random
from typing import Dict, List, Any, Optional, Tuple
from browser.playwright_manager import PlaywrightManager
from browser.playwright_extensions import PlaywrightExtensions
from social_networks.base_automation import BaseAutomation
from utils.password_generator import PasswordGenerator
from utils.username_generator import UsernameGenerator
from utils.birthday_generator import BirthdayGenerator
from utils.human_behavior import HumanBehavior
from utils.logger import setup_logger
# Importiere Helferklassen
from .x_registration import XRegistration
from .x_login import XLogin
from .x_verification import XVerification
from .x_ui_helper import XUIHelper
from .x_utils import XUtils
# Konfiguriere Logger
logger = setup_logger("x_automation")
class XAutomation(BaseAutomation):
"""
Hauptklasse für die X (Twitter) Automatisierung.
Implementiert die Registrierung und Anmeldung bei X.
"""
def __init__(self,
headless: bool = False,
use_proxy: bool = False,
proxy_type: str = None,
save_screenshots: bool = True,
screenshots_dir: str = None,
slowmo: int = 0,
debug: bool = False,
email_domain: str = "z5m7q9dk3ah2v1plx6ju.com",
enhanced_stealth: bool = True,
fingerprint_noise: float = 0.5,
window_position = None,
fingerprint = None,
auto_close_browser: bool = False):
"""
Initialisiert die X-Automatisierung.
Args:
headless: Ob der Browser im Headless-Modus ausgeführt werden soll
use_proxy: Ob ein Proxy verwendet werden soll
proxy_type: Proxy-Typ ("ipv4", "ipv6", "mobile") oder None für zufälligen Typ
save_screenshots: Ob Screenshots gespeichert werden sollen
screenshots_dir: Verzeichnis für Screenshots
slowmo: Verzögerung zwischen Aktionen in Millisekunden (nützlich für Debugging)
debug: Ob Debug-Informationen angezeigt werden sollen
email_domain: Domain für generierte E-Mail-Adressen
enhanced_stealth: Ob erweiterter Stealth-Modus aktiviert werden soll
fingerprint_noise: Menge an Rauschen für Fingerprint-Verschleierung (0.0-1.0)
window_position: Optional - Fensterposition als Tuple (x, y)
fingerprint: Optional - Vordefinierter Browser-Fingerprint
auto_close_browser: Ob Browser automatisch geschlossen werden soll (Standard: False)
"""
# Initialisiere die Basisklasse
super().__init__(
headless=headless,
use_proxy=use_proxy,
proxy_type=proxy_type,
save_screenshots=save_screenshots,
screenshots_dir=screenshots_dir,
slowmo=slowmo,
debug=debug,
email_domain=email_domain,
window_position=window_position,
auto_close_browser=auto_close_browser
)
# Stealth-Modus-Einstellungen
self.enhanced_stealth = enhanced_stealth
self.fingerprint_noise = max(0.0, min(1.0, fingerprint_noise))
# Initialisiere Helferklassen
self.registration = XRegistration(self)
self.login = XLogin(self)
self.verification = XVerification(self)
self.ui_helper = XUIHelper(self)
self.utils = XUtils(self)
# Zusätzliche Hilfsklassen
self.password_generator = PasswordGenerator()
self.username_generator = UsernameGenerator()
self.birthday_generator = BirthdayGenerator()
self.human_behavior = HumanBehavior(speed_factor=0.8, randomness=0.6)
# Nutze übergebenen Fingerprint wenn vorhanden
self.provided_fingerprint = fingerprint
logger.info("X-Automatisierung initialisiert")
def _initialize_browser(self) -> bool:
"""
Initialisiert den Browser mit den entsprechenden Einstellungen.
Diese Methode überschreibt die Methode der Basisklasse, um den erweiterten
Fingerprint-Schutz zu aktivieren.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Proxy-Konfiguration, falls aktiviert
proxy_config = None
if self.use_proxy:
proxy_config = self.proxy_rotator.get_proxy(self.proxy_type)
if not proxy_config:
logger.warning(f"Kein Proxy vom Typ '{self.proxy_type}' verfügbar, verwende direkten Zugriff")
# Browser initialisieren
self.browser = PlaywrightManager(
headless=self.headless,
proxy=proxy_config,
browser_type="chromium",
screenshots_dir=self.screenshots_dir,
slowmo=self.slowmo
)
# Browser starten
self.browser.start()
# Erweiterten Fingerprint-Schutz aktivieren, wenn gewünscht
if self.enhanced_stealth:
# Erstelle Extensions-Objekt
extensions = PlaywrightExtensions(self.browser)
# Methoden anhängen
extensions.hook_into_playwright_manager()
# Fingerprint-Schutz aktivieren mit angepasster Konfiguration
if self.provided_fingerprint:
# Nutze den bereitgestellten Fingerprint
logger.info("Verwende bereitgestellten Fingerprint für Account-Erstellung")
# Konvertiere Dict zu BrowserFingerprint wenn nötig
if isinstance(self.provided_fingerprint, dict):
from domain.entities.browser_fingerprint import BrowserFingerprint
fingerprint_obj = BrowserFingerprint.from_dict(self.provided_fingerprint)
else:
fingerprint_obj = self.provided_fingerprint
# Wende Fingerprint über FingerprintProtection an
from browser.fingerprint_protection import FingerprintProtection
protection = FingerprintProtection(
context=self.browser.context,
fingerprint_config=fingerprint_obj
)
protection.apply_to_context(self.browser.context)
logger.info(f"Fingerprint {fingerprint_obj.fingerprint_id} angewendet")
else:
# Fallback: Zufällige Fingerprint-Konfiguration
fingerprint_config = {
"noise_level": self.fingerprint_noise,
"canvas_noise": True,
"audio_noise": True,
"webgl_noise": True,
"hardware_concurrency": random.choice([4, 6, 8]),
"device_memory": random.choice([4, 8]),
"timezone_id": "Europe/Berlin"
}
success = self.browser.enable_enhanced_fingerprint_protection(fingerprint_config)
if success:
logger.info("Erweiterter Fingerprint-Schutz erfolgreich aktiviert")
else:
logger.warning("Erweiterter Fingerprint-Schutz konnte nicht aktiviert werden")
logger.info("Browser erfolgreich initialisiert")
return True
except Exception as e:
logger.error(f"Fehler bei der Browser-Initialisierung: {e}")
self.status["error"] = f"Browser-Initialisierungsfehler: {str(e)}"
return False
def register_account(self, full_name: str, age: int, registration_method: str = "email",
phone_number: str = None, **kwargs) -> Dict[str, Any]:
"""
Registriert einen neuen X-Account.
Args:
full_name: Vollständiger Name für den Account
age: Alter des Benutzers
registration_method: "email" oder "phone"
phone_number: Telefonnummer (nur bei registration_method="phone")
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Registrierung mit Status und Account-Daten
"""
logger.info(f"Starte X-Account-Registrierung für '{full_name}' via {registration_method}")
self._emit_customer_log(f"🐦 X-Account wird erstellt für: {full_name}")
try:
# 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"}
# Rotiere Fingerprint vor der Hauptaktivität, um Erkennung weiter zu erschweren
if self.enhanced_stealth and hasattr(self.browser, 'rotate_fingerprint'):
self.browser.rotate_fingerprint()
logger.info("Browser-Fingerprint vor der Registrierung rotiert")
# Delegiere die Hauptregistrierungslogik an die Registration-Klasse
result = self.registration.register_account(full_name, age, registration_method, phone_number, **kwargs)
# Nehme Abschlussfoto auf
self._take_screenshot(f"registration_finished_{int(time.time())}")
# Aktualisiere Status
self.status.update(result)
return result
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der Account-Registrierung: {str(e)}"
logger.error(error_msg, exc_info=True)
# Fehler-Screenshot
self._take_screenshot(f"registration_error_{int(time.time())}")
# Aktualisiere Status
self.status.update({
"success": False,
"error": error_msg,
"stage": "error"
})
return self.status
finally:
# Browser schließen
self._close_browser()
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
"""
Meldet sich bei einem bestehenden X-Account an.
Args:
username_or_email: Benutzername oder E-Mail-Adresse
password: Passwort
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Anmeldung mit Status
"""
logger.info(f"Starte X-Login für '{username_or_email}'")
try:
# 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"}
# Rotiere Fingerprint vor dem Login
if self.enhanced_stealth and hasattr(self.browser, 'rotate_fingerprint'):
self.browser.rotate_fingerprint()
logger.info("Browser-Fingerprint vor dem Login rotiert")
# Delegiere die Hauptlogin-Logik an die Login-Klasse
result = self.login.login_account(username_or_email, password, **kwargs)
# Nehme Abschlussfoto auf
self._take_screenshot(f"login_finished_{int(time.time())}")
# Aktualisiere Status
self.status.update(result)
return result
except Exception as e:
error_msg = f"Unerwarteter Fehler beim Login: {str(e)}"
logger.error(error_msg, exc_info=True)
# Fehler-Screenshot
self._take_screenshot(f"login_error_{int(time.time())}")
# Aktualisiere Status
self.status.update({
"success": False,
"error": error_msg,
"stage": "error"
})
return self.status
finally:
# Browser schließen
self._close_browser()
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
"""
Verifiziert einen X-Account mit einem Bestätigungscode.
Args:
verification_code: Der Bestätigungscode
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Verifizierung mit Status
"""
logger.info(f"Starte X-Account-Verifizierung mit Code: {verification_code}")
try:
# 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"}
# Delegiere die Hauptverifizierungslogik an die Verification-Klasse
result = self.verification.verify_account(verification_code, **kwargs)
# Nehme Abschlussfoto auf
self._take_screenshot(f"verification_finished_{int(time.time())}")
# Aktualisiere Status
self.status.update(result)
return result
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der Account-Verifizierung: {str(e)}"
logger.error(error_msg, exc_info=True)
# Fehler-Screenshot
self._take_screenshot(f"verification_error_{int(time.time())}")
# Aktualisiere Status
self.status.update({
"success": False,
"error": error_msg,
"stage": "error"
})
return self.status
finally:
# Browser schließen
self._close_browser()
def get_fingerprint_status(self) -> Dict[str, Any]:
"""
Gibt den aktuellen Status des Fingerprint-Schutzes zurück.
Returns:
Dict[str, Any]: Status des Fingerprint-Schutzes
"""
if not self.enhanced_stealth or not hasattr(self.browser, 'get_fingerprint_status'):
return {
"active": False,
"message": "Erweiterter Fingerprint-Schutz ist nicht aktiviert"
}
return self.browser.get_fingerprint_status()
def get_current_page(self):
"""
Gibt die aktuelle Playwright Page-Instanz zurück.
Returns:
Page: Die aktuelle Seite oder None
"""
if self.browser and hasattr(self.browser, 'page'):
return self.browser.page
return None
def get_session_data(self) -> Dict[str, Any]:
"""
Extrahiert Session-Daten (Cookies, LocalStorage, etc.) aus dem aktuellen Browser.
Returns:
Dict[str, Any]: Session-Daten
"""
if not self.is_browser_open():
return {}
try:
return {
"cookies": self.browser.page.context.cookies(),
"local_storage": self.browser.page.evaluate("() => Object.assign({}, window.localStorage)"),
"session_storage": self.browser.page.evaluate("() => Object.assign({}, window.sessionStorage)"),
"url": self.browser.page.url
}
except Exception as e:
logger.error(f"Fehler beim Extrahieren der Session-Daten: {e}")
return {}

669
social_networks/x/x_login.py Normale Datei
Datei anzeigen

@ -0,0 +1,669 @@
# 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

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -0,0 +1,178 @@
# social_networks/x/x_selectors.py
"""
X (Twitter) Selektoren - Zentrale Sammlung aller CSS-Selektoren für X
"""
class XSelectors:
"""
Zentrale Klasse für alle X-spezifischen CSS-Selektoren.
Organisiert nach Funktionsbereichen.
"""
# === ALLGEMEINE SELEKTOREN ===
COOKIE_ACCEPT_BUTTONS = [
"button:has-text('Accept all')",
"button:has-text('Alle akzeptieren')",
"button:has-text('Accept')",
"button:has-text('Akzeptieren')"
]
# === REGISTRIERUNG ===
REGISTRATION = {
# Hauptseite
"create_account_button": 'text="Account erstellen"', # Robustester Selektor
"create_account_button_en": 'text="Create account"', # Englische Version
"create_account_button_alt": 'span:has-text("Account erstellen")', # Alternative
"create_account_button_div": 'div[dir="ltr"] >> text="Account erstellen"', # Spezifischer
# Formularfelder
"name_input": 'input[name="name"]',
"email_input": 'input[name="email"]',
"phone_input": 'input[name="phone"]',
# Geburtsdatum Dropdowns
"month_select": 'select#SELECTOR_1',
"day_select": 'select#SELECTOR_2',
"year_select": 'select#SELECTOR_3',
# Buttons
"next_button_birthday": 'button[data-testid="ocfSignupNextLink"]',
"next_button_settings": 'button[data-testid="ocfSettingsListNextButton"]',
"register_button": 'button[data-testid="LoginForm_Login_Button"]',
# Verifizierung
"verification_code_input": 'input[autocomplete="one-time-code"]',
"verification_code_label": 'div:has-text("Verifizierungscode")',
# Passwort
"password_input": 'input[type="password"]',
"password_label": 'div:has-text("Passwort")',
# Skip-Buttons
"skip_profile_picture": 'button[data-testid="ocfSelectAvatarSkipForNowButton"]',
"skip_username": 'button[data-testid="ocfEnterUsernameSkipButton"]',
"skip_notifications": 'button:has-text("Vorerst überspringen")',
"skip_for_now": 'button:has-text("Nicht jetzt")'
}
# === LOGIN ===
LOGIN = {
# Login-Buttons
"login_button": 'a[href="/login"]',
"login_button_alt": 'div:has-text("Anmelden")',
# Formularfelder
"username_input": 'input[autocomplete="username"]',
"email_or_username_input": 'input[name="text"]',
"password_input": 'input[type="password"]',
"password_input_alt": 'input[name="password"]',
# Submit-Buttons
"next_button": 'div[role="button"]:has-text("Weiter")',
"next_button_en": 'div[role="button"]:has-text("Next")',
"login_submit": 'div[role="button"]:has-text("Anmelden")',
"login_submit_en": 'div[role="button"]:has-text("Log in")'
}
# === NAVIGATION ===
NAVIGATION = {
# Hauptnavigation
"home_link": 'a[href="/home"]',
"explore_link": 'a[href="/explore"]',
"notifications_link": 'a[href="/notifications"]',
"messages_link": 'a[href="/messages"]',
"profile_link": 'a[data-testid="AppTabBar_Profile_Link"]',
# Tweet/Post-Buttons
"tweet_button": 'a[data-testid="SideNav_NewTweet_Button"]',
"tweet_button_inline": 'button[data-testid="tweetButtonInline"]',
# Navigation Container
"primary_nav": 'nav[aria-label="Primary"]',
"sidebar": 'div[data-testid="sidebarColumn"]'
}
# === PROFIL ===
PROFILE = {
# Profilbearbeitung
"edit_profile_button": 'button:has-text("Profil bearbeiten")',
"edit_profile_button_en": 'button:has-text("Edit profile")',
# Formularfelder
"display_name_input": 'input[name="displayName"]',
"bio_textarea": 'textarea[name="description"]',
"location_input": 'input[name="location"]',
"website_input": 'input[name="url"]',
# Speichern
"save_button": 'button:has-text("Speichern")',
"save_button_en": 'button:has-text("Save")'
}
# === VERIFIZIERUNG ===
VERIFICATION = {
# Challenge/Captcha
"challenge_frame": 'iframe[title="arkose-challenge"]',
"captcha_frame": 'iframe[src*="recaptcha"]',
# Telefonnummer-Verifizierung
"phone_verification_input": 'input[name="phone_number"]',
"send_code_button": 'button:has-text("Code senden")',
"verification_code_input": 'input[name="verification_code"]'
}
# === FEHLER UND WARNUNGEN ===
ERRORS = {
# Fehlermeldungen
"error_message": 'div[data-testid="toast"]',
"error_alert": 'div[role="alert"]',
"rate_limit_message": 'span:has-text("versuchen Sie es später")',
"suspended_message": 'span:has-text("gesperrt")',
# Spezifische Fehler
"email_taken": 'span:has-text("E-Mail-Adresse wird bereits verwendet")',
"invalid_credentials": 'span:has-text("Falscher Benutzername oder falsches Passwort")'
}
# === MODALE DIALOGE ===
MODALS = {
# Allgemeine Modale
"modal_container": 'div[role="dialog"]',
"modal_close_button": 'div[aria-label="Schließen"]',
"modal_close_button_en": 'div[aria-label="Close"]',
# Bestätigungsdialoge
"confirm_button": 'button:has-text("Bestätigen")',
"cancel_button": 'button:has-text("Abbrechen")'
}
@classmethod
def get_selector(cls, category: str, key: str) -> str:
"""
Holt einen spezifischen Selektor.
Args:
category: Kategorie (z.B. "REGISTRATION", "LOGIN")
key: Schlüssel innerhalb der Kategorie
Returns:
str: CSS-Selektor oder None
"""
category_dict = getattr(cls, category.upper(), {})
if isinstance(category_dict, dict):
return category_dict.get(key)
return None
@classmethod
def get_all_selectors(cls, category: str) -> dict:
"""
Holt alle Selektoren einer Kategorie.
Args:
category: Kategorie
Returns:
dict: Alle Selektoren der Kategorie
"""
return getattr(cls, category.upper(), {})

Datei anzeigen

@ -0,0 +1,424 @@
# social_networks/x/x_ui_helper.py
"""
X (Twitter) UI Helper - Hilfsklasse für UI-Interaktionen bei X
"""
import time
from typing import Optional, List, Dict, Any, Tuple
from playwright.sync_api import Page, ElementHandle
from .x_selectors import XSelectors
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("x_ui_helper")
class XUIHelper:
"""
Hilfsklasse für UI-Interaktionen mit X.
Bietet wiederverwendbare Methoden für häufige UI-Operationen.
"""
def __init__(self, automation):
"""
Initialisiert den X UI Helper.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.selectors = XSelectors()
logger.debug("X UI Helper initialisiert")
def wait_for_element(self, selector: str, timeout: int = 10000,
state: str = "visible") -> Optional[ElementHandle]:
"""
Wartet auf ein Element und gibt es zurück.
Args:
selector: CSS-Selektor
timeout: Timeout in Millisekunden
state: Gewünschter Zustand ("visible", "attached", "detached", "hidden")
Returns:
Optional[ElementHandle]: Element oder None
"""
try:
page = self.automation.browser.page
element = page.wait_for_selector(selector, timeout=timeout, state=state)
return element
except Exception as e:
logger.debug(f"Element nicht gefunden: {selector} - {e}")
return None
def click_element_safely(self, selector: str, timeout: int = 10000,
retry_count: int = 3) -> bool:
"""
Klickt sicher auf ein Element mit Retry-Logik.
Args:
selector: CSS-Selektor
timeout: Timeout in Millisekunden
retry_count: Anzahl der Wiederholungsversuche
Returns:
bool: True bei Erfolg, False bei Fehler
"""
page = self.automation.browser.page
for attempt in range(retry_count):
try:
# Warte auf Element
element = self.wait_for_element(selector, timeout)
if not element:
logger.warning(f"Element nicht gefunden beim {attempt + 1}. Versuch: {selector}")
continue
# Scrolle zum Element
element.scroll_into_view_if_needed()
# Warte kurz
self.automation.human_behavior.random_delay(0.3, 0.7)
# Klicke
element.click()
logger.info(f"Element erfolgreich geklickt: {selector}")
return True
except Exception as e:
logger.warning(f"Fehler beim Klicken (Versuch {attempt + 1}): {e}")
if attempt < retry_count - 1:
self.automation.human_behavior.random_delay(1, 2)
continue
logger.error(f"Konnte Element nicht klicken nach {retry_count} Versuchen: {selector}")
return False
def type_text_safely(self, selector: str, text: str, clear_first: bool = True,
timeout: int = 10000) -> bool:
"""
Gibt Text sicher in ein Eingabefeld ein.
Args:
selector: CSS-Selektor
text: Einzugebender Text
clear_first: Ob das Feld zuerst geleert werden soll
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
element = self.wait_for_element(selector, timeout)
if not element:
logger.error(f"Eingabefeld nicht gefunden: {selector}")
return False
# Fokussiere das Element
element.focus()
self.automation.human_behavior.random_delay(0.2, 0.5)
# Leere das Feld wenn gewünscht
if clear_first:
element.click(click_count=3) # Alles auswählen
self.automation.human_behavior.random_delay(0.1, 0.3)
element.press("Delete")
self.automation.human_behavior.random_delay(0.2, 0.5)
# Tippe den Text
self.automation.human_behavior.type_text(element, text)
logger.info(f"Text erfolgreich eingegeben in: {selector}")
return True
except Exception as e:
logger.error(f"Fehler beim Texteingeben: {e}")
return False
def select_dropdown_option(self, selector: str, value: str, timeout: int = 10000) -> bool:
"""
Wählt eine Option aus einem Dropdown-Menü.
Args:
selector: CSS-Selektor des Select-Elements
value: Wert der zu wählenden Option
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
# Warte auf Select-Element
select = self.wait_for_element(selector, timeout)
if not select:
logger.error(f"Dropdown nicht gefunden: {selector}")
return False
# Wähle Option
page.select_option(selector, value)
logger.info(f"Option '{value}' ausgewählt in: {selector}")
return True
except Exception as e:
logger.error(f"Fehler beim Auswählen der Dropdown-Option: {e}")
return False
def handle_modal(self, action: str = "accept", timeout: int = 5000) -> bool:
"""
Behandelt modale Dialoge.
Args:
action: "accept", "dismiss" oder "close"
timeout: Timeout in Millisekunden
Returns:
bool: True wenn Modal behandelt wurde, False sonst
"""
try:
page = self.automation.browser.page
# Prüfe ob Modal vorhanden
modal = self.wait_for_element(self.selectors.MODALS["modal_container"], timeout)
if not modal:
logger.debug("Kein Modal gefunden")
return False
if action == "accept":
# Suche Bestätigen-Button
if self.click_element_safely(self.selectors.MODALS["confirm_button"], timeout=3000):
logger.info("Modal bestätigt")
return True
elif action == "dismiss":
# Suche Abbrechen-Button
if self.click_element_safely(self.selectors.MODALS["cancel_button"], timeout=3000):
logger.info("Modal abgebrochen")
return True
elif action == "close":
# Suche Schließen-Button
close_selectors = [
self.selectors.MODALS["modal_close_button"],
self.selectors.MODALS["modal_close_button_en"]
]
for selector in close_selectors:
if self.click_element_safely(selector, timeout=3000):
logger.info("Modal geschlossen")
return True
logger.warning(f"Konnte Modal nicht mit Aktion '{action}' behandeln")
return False
except Exception as e:
logger.error(f"Fehler bei Modal-Behandlung: {e}")
return False
def check_for_errors(self, timeout: int = 2000) -> Optional[str]:
"""
Prüft auf Fehlermeldungen auf der Seite.
Args:
timeout: Timeout in Millisekunden
Returns:
Optional[str]: Fehlermeldung wenn gefunden, sonst None
"""
try:
page = self.automation.browser.page
# Prüfe alle Fehler-Selektoren
for category, selector in [
("error_message", self.selectors.ERRORS["error_message"]),
("error_alert", self.selectors.ERRORS["error_alert"]),
("rate_limit", self.selectors.ERRORS["rate_limit_message"]),
("suspended", self.selectors.ERRORS["suspended_message"]),
("email_taken", self.selectors.ERRORS["email_taken"]),
("invalid_credentials", self.selectors.ERRORS["invalid_credentials"])
]:
error_element = self.wait_for_element(selector, timeout=timeout)
if error_element:
error_text = error_element.text_content()
logger.warning(f"Fehler gefunden ({category}): {error_text}")
return error_text
return None
except Exception as e:
logger.debug(f"Fehler bei Fehlerprüfung: {e}")
return None
def wait_for_navigation(self, timeout: int = 30000) -> bool:
"""
Wartet auf Navigation/Seitenwechsel.
Args:
timeout: Timeout in Millisekunden
Returns:
bool: True wenn Navigation erfolgt ist
"""
try:
page = self.automation.browser.page
with page.expect_navigation(timeout=timeout):
pass
logger.info("Navigation abgeschlossen")
return True
except Exception as e:
logger.debug(f"Keine Navigation erkannt: {e}")
return False
def scroll_to_bottom(self, smooth: bool = True, pause_time: float = 1.0):
"""
Scrollt zum Ende der Seite.
Args:
smooth: Ob sanft gescrollt werden soll
pause_time: Pausenzeit nach dem Scrollen
"""
try:
page = self.automation.browser.page
if smooth:
# Sanftes Scrollen in Schritten
page.evaluate("""
async () => {
const distance = 100;
const delay = 50;
const timer = setInterval(() => {
window.scrollBy(0, distance);
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
clearInterval(timer);
}
}, delay);
}
""")
else:
# Direktes Scrollen
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
time.sleep(pause_time)
logger.debug("Zum Seitenende gescrollt")
except Exception as e:
logger.error(f"Fehler beim Scrollen: {e}")
def take_element_screenshot(self, selector: str, filename: str,
timeout: int = 10000) -> bool:
"""
Macht einen Screenshot von einem spezifischen Element.
Args:
selector: CSS-Selektor
filename: Dateiname für den Screenshot
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
element = self.wait_for_element(selector, timeout)
if not element:
logger.error(f"Element für Screenshot nicht gefunden: {selector}")
return False
# Screenshot vom Element
element.screenshot(path=f"{self.automation.screenshots_dir}/{filename}")
logger.info(f"Element-Screenshot gespeichert: {filename}")
return True
except Exception as e:
logger.error(f"Fehler beim Element-Screenshot: {e}")
return False
def get_element_text(self, selector: str, timeout: int = 10000) -> Optional[str]:
"""
Holt den Text eines Elements.
Args:
selector: CSS-Selektor
timeout: Timeout in Millisekunden
Returns:
Optional[str]: Text des Elements oder None
"""
try:
element = self.wait_for_element(selector, timeout)
if element:
return element.text_content()
return None
except Exception as e:
logger.error(f"Fehler beim Abrufen des Element-Texts: {e}")
return None
def is_element_visible(self, selector: str, timeout: int = 1000) -> bool:
"""
Prüft ob ein Element sichtbar ist.
Args:
selector: CSS-Selektor
timeout: Timeout in Millisekunden
Returns:
bool: True wenn sichtbar, False sonst
"""
try:
page = self.automation.browser.page
# is_visible hat kein timeout parameter in Playwright
# Verwende wait_for_selector für timeout-Funktionalität
try:
element = page.wait_for_selector(selector, timeout=timeout, state="visible")
return element is not None
except:
return False
except Exception as e:
logger.debug(f"Element nicht sichtbar: {selector} - {e}")
return False
def wait_for_any_selector(self, selectors: List[str], timeout: int = 10000) -> Optional[Tuple[str, ElementHandle]]:
"""
Wartet auf eines von mehreren Elementen.
Args:
selectors: Liste von CSS-Selektoren
timeout: Timeout in Millisekunden
Returns:
Optional[Tuple[str, ElementHandle]]: Tuple aus Selektor und Element oder None
"""
try:
page = self.automation.browser.page
# Erstelle Promise für jeden Selektor
promises = []
for selector in selectors:
promises.append(page.wait_for_selector(selector, timeout=timeout))
# Warte auf das erste Element
element = page.evaluate(f"""
() => {{
const selectors = {selectors};
for (const selector of selectors) {{
const element = document.querySelector(selector);
if (element) return {{selector, found: true}};
}}
return {{found: false}};
}}
""")
if element["found"]:
actual_element = page.query_selector(element["selector"])
return (element["selector"], actual_element)
return None
except Exception as e:
logger.debug(f"Keines der Elemente gefunden: {e}")
return None

379
social_networks/x/x_utils.py Normale Datei
Datei anzeigen

@ -0,0 +1,379 @@
# social_networks/x/x_utils.py
"""
X (Twitter) Utils - Utility-Funktionen für X-Automatisierung
"""
import re
import time
import random
from typing import Dict, List, Any, Optional, Tuple
from datetime import datetime, timedelta
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("x_utils")
class XUtils:
"""
Utility-Klasse mit Hilfsfunktionen für X-Automatisierung.
"""
def __init__(self, automation):
"""
Initialisiert X Utils.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
logger.debug("X Utils initialisiert")
@staticmethod
def validate_username(username: str) -> Tuple[bool, Optional[str]]:
"""
Validiert einen X-Benutzernamen.
Args:
username: Zu validierender Benutzername
Returns:
Tuple[bool, Optional[str]]: (Gültig, Fehlermeldung wenn ungültig)
"""
# Längenprüfung
if len(username) < 1:
return False, "Benutzername ist zu kurz (mindestens 1 Zeichen)"
if len(username) > 15:
return False, "Benutzername ist zu lang (maximal 15 Zeichen)"
# Zeichenprüfung (nur Buchstaben, Zahlen und Unterstrich)
if not re.match(r'^[a-zA-Z0-9_]+$', username):
return False, "Benutzername darf nur Buchstaben, Zahlen und Unterstriche enthalten"
# Verbotene Muster
forbidden_patterns = ["twitter", "admin", "x.com", "root", "system"]
username_lower = username.lower()
for pattern in forbidden_patterns:
if pattern in username_lower:
return False, f"Benutzername darf '{pattern}' nicht enthalten"
return True, None
@staticmethod
def validate_email(email: str) -> Tuple[bool, Optional[str]]:
"""
Validiert eine E-Mail-Adresse für X.
Args:
email: Zu validierende E-Mail
Returns:
Tuple[bool, Optional[str]]: (Gültig, Fehlermeldung wenn ungültig)
"""
# Grundlegendes E-Mail-Pattern
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, email):
return False, "Ungültiges E-Mail-Format"
# Verbotene Domains
forbidden_domains = ["example.com", "test.com", "temp-mail.com"]
domain = email.split('@')[1].lower()
for forbidden in forbidden_domains:
if forbidden in domain:
return False, f"E-Mail-Domain '{forbidden}' ist nicht erlaubt"
return True, None
@staticmethod
def validate_password(password: str) -> Tuple[bool, Optional[str]]:
"""
Validiert ein Passwort für X.
Args:
password: Zu validierendes Passwort
Returns:
Tuple[bool, Optional[str]]: (Gültig, Fehlermeldung wenn ungültig)
"""
# Längenprüfung
if len(password) < 8:
return False, "Passwort muss mindestens 8 Zeichen lang sein"
if len(password) > 128:
return False, "Passwort darf maximal 128 Zeichen lang sein"
# Mindestens ein Kleinbuchstabe
if not re.search(r'[a-z]', password):
return False, "Passwort muss mindestens einen Kleinbuchstaben enthalten"
# Mindestens eine Zahl
if not re.search(r'\d', password):
return False, "Passwort muss mindestens eine Zahl enthalten"
return True, None
@staticmethod
def generate_device_info() -> Dict[str, Any]:
"""
Generiert realistische Geräteinformationen.
Returns:
Dict[str, Any]: Geräteinformationen
"""
devices = [
{
"type": "desktop",
"os": "Windows",
"browser": "Chrome",
"screen": {"width": 1920, "height": 1080},
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
},
{
"type": "desktop",
"os": "macOS",
"browser": "Safari",
"screen": {"width": 2560, "height": 1440},
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
},
{
"type": "mobile",
"os": "iOS",
"browser": "Safari",
"screen": {"width": 414, "height": 896},
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X)"
},
{
"type": "mobile",
"os": "Android",
"browser": "Chrome",
"screen": {"width": 412, "height": 915},
"user_agent": "Mozilla/5.0 (Linux; Android 11; SM-G991B) AppleWebKit/537.36"
}
]
return random.choice(devices)
@staticmethod
def generate_session_id() -> str:
"""
Generiert eine realistische Session-ID.
Returns:
str: Session-ID
"""
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
return ''.join(random.choice(chars) for _ in range(32))
def detect_language(self) -> str:
"""
Erkennt die aktuelle Sprache der X-Oberfläche.
Returns:
str: Sprachcode (z.B. "de", "en")
"""
try:
page = self.automation.browser.page
# Prüfe HTML lang-Attribut
lang = page.evaluate("() => document.documentElement.lang")
if lang:
return lang.split('-')[0] # z.B. "de" aus "de-DE"
# Fallback: Prüfe bekannte Texte
if page.is_visible('text="Anmelden"'):
return "de"
elif page.is_visible('text="Log in"'):
return "en"
return "en" # Standard
except Exception as e:
logger.error(f"Fehler bei Spracherkennung: {e}")
return "en"
@staticmethod
def format_phone_number(phone: str, country_code: str = "+49") -> str:
"""
Formatiert eine Telefonnummer für X.
Args:
phone: Rohe Telefonnummer
country_code: Ländervorwahl
Returns:
str: Formatierte Telefonnummer
"""
# Entferne alle Nicht-Ziffern
digits = re.sub(r'\D', '', phone)
# Füge Ländervorwahl hinzu wenn nicht vorhanden
if not phone.startswith('+'):
return f"{country_code}{digits}"
return f"+{digits}"
@staticmethod
def parse_error_message(error_text: str) -> Dict[str, Any]:
"""
Analysiert X-Fehlermeldungen.
Args:
error_text: Fehlermeldungstext
Returns:
Dict[str, Any]: Analysierte Fehlerinformationen
"""
error_info = {
"type": "unknown",
"message": error_text,
"recoverable": True,
"action": "retry"
}
# Rate Limit
if "zu viele" in error_text.lower() or "too many" in error_text.lower():
error_info.update({
"type": "rate_limit",
"recoverable": True,
"action": "wait",
"wait_time": 900 # 15 Minuten
})
# Account gesperrt
elif "gesperrt" in error_text.lower() or "suspended" in error_text.lower():
error_info.update({
"type": "suspended",
"recoverable": False,
"action": "abort"
})
# Ungültige Anmeldedaten
elif "passwort" in error_text.lower() or "password" in error_text.lower():
error_info.update({
"type": "invalid_credentials",
"recoverable": True,
"action": "check_credentials"
})
# E-Mail bereits verwendet
elif "bereits verwendet" in error_text.lower() or "already" in error_text.lower():
error_info.update({
"type": "duplicate",
"recoverable": True,
"action": "use_different_email"
})
return error_info
def wait_for_rate_limit(self, wait_time: int = None):
"""
Wartet bei Rate Limiting mit visueller Anzeige.
Args:
wait_time: Wartezeit in Sekunden (None für zufällige Zeit)
"""
if wait_time is None:
wait_time = random.randint(300, 600) # 5-10 Minuten
logger.info(f"Rate Limit erkannt - warte {wait_time} Sekunden")
self.automation._emit_customer_log(f"⏳ Rate Limit - warte {wait_time // 60} Minuten...")
# Warte in Intervallen mit Status-Updates
intervals = min(10, wait_time // 10)
for i in range(intervals):
time.sleep(wait_time // intervals)
remaining = wait_time - (i + 1) * (wait_time // intervals)
if remaining > 60:
self.automation._emit_customer_log(f"⏳ Noch {remaining // 60} Minuten...")
@staticmethod
def generate_bio() -> str:
"""
Generiert eine realistische Bio für ein X-Profil.
Returns:
str: Generierte Bio
"""
templates = [
"✈️ Explorer | 📚 Book lover | ☕ Coffee enthusiast",
"Life is a journey 🌟 | Making memories 📸",
"Student 📖 | Dreamer 💭 | Music lover 🎵",
"Tech enthusiast 💻 | Always learning 🎯",
"Living life one day at a time ✨",
"Passionate about {interest} | {city} 📍",
"Just here to share thoughts 💭",
"{hobby} in my free time | DM for collabs",
"Spreading positivity 🌈 | {emoji} lover"
]
interests = ["photography", "travel", "coding", "art", "fitness", "cooking"]
cities = ["Berlin", "Munich", "Hamburg", "Frankfurt", "Cologne"]
hobbies = ["Gaming", "Reading", "Hiking", "Painting", "Yoga"]
emojis = ["🎨", "🎮", "📚", "🎯", "🌸", ""]
bio = random.choice(templates)
bio = bio.replace("{interest}", random.choice(interests))
bio = bio.replace("{city}", random.choice(cities))
bio = bio.replace("{hobby}", random.choice(hobbies))
bio = bio.replace("{emoji}", random.choice(emojis))
return bio
@staticmethod
def calculate_age_from_birthday(birthday: Dict[str, int]) -> int:
"""
Berechnet das Alter aus einem Geburtstagsdatum.
Args:
birthday: Dictionary mit 'day', 'month', 'year'
Returns:
int: Berechnetes Alter
"""
birth_date = datetime(birthday['year'], birthday['month'], birthday['day'])
today = datetime.now()
age = today.year - birth_date.year
# Prüfe ob Geburtstag dieses Jahr schon war
if (today.month, today.day) < (birth_date.month, birth_date.day):
age -= 1
return age
def check_account_restrictions(self) -> Dict[str, Any]:
"""
Prüft auf Account-Einschränkungen.
Returns:
Dict[str, Any]: Informationen über Einschränkungen
"""
try:
page = self.automation.browser.page
restrictions = {
"limited": False,
"locked": False,
"suspended": False,
"verification_required": False
}
# Prüfe auf verschiedene Einschränkungen
if page.is_visible('text="Dein Account ist eingeschränkt"', timeout=1000):
restrictions["limited"] = True
logger.warning("Account ist eingeschränkt")
if page.is_visible('text="Account gesperrt"', timeout=1000):
restrictions["suspended"] = True
logger.error("Account ist gesperrt")
if page.is_visible('text="Verifizierung erforderlich"', timeout=1000):
restrictions["verification_required"] = True
logger.warning("Verifizierung erforderlich")
return restrictions
except Exception as e:
logger.error(f"Fehler bei Einschränkungsprüfung: {e}")
return {"error": str(e)}

Datei anzeigen

@ -0,0 +1,511 @@
# social_networks/x/x_verification.py
"""
X (Twitter) Verification - Klasse für Account-Verifizierungsprozesse bei X
"""
import time
import re
from typing import Dict, Any, Optional, List
from .x_selectors import XSelectors
from .x_workflow import XWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("x_verification")
class XVerification:
"""
Klasse für die Verifizierung von X-Konten.
Behandelt verschiedene Verifizierungsmethoden und Sicherheitsabfragen.
"""
def __init__(self, automation):
"""
Initialisiert die X-Verifizierung.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.selectors = XSelectors()
self.workflow = XWorkflow.get_verification_workflow()
logger.debug("X-Verifizierung initialisiert")
def verify_account(self, verification_code: str = None, **kwargs) -> Dict[str, Any]:
"""
Führt den Verifizierungsprozess für einen X-Account durch.
Args:
verification_code: Optionaler Verifizierungscode
**kwargs: Weitere Parameter (method, phone_number, email)
Returns:
Dict[str, Any]: Ergebnis der Verifizierung
"""
logger.info("Starte X-Account-Verifizierung")
try:
# Prüfe welche Art von Verifizierung benötigt wird
verification_type = self._detect_verification_type()
if not verification_type:
logger.info("Keine Verifizierung erforderlich")
return {
"success": True,
"message": "Keine Verifizierung erforderlich"
}
logger.info(f"Verifizierungstyp erkannt: {verification_type}")
self.automation._emit_customer_log(f"🔐 Verifizierung erforderlich: {verification_type}")
# Führe entsprechende Verifizierung durch
if verification_type == "email":
return self._handle_email_verification(verification_code, **kwargs)
elif verification_type == "phone":
return self._handle_phone_verification(verification_code, **kwargs)
elif verification_type == "captcha":
return self._handle_captcha_verification()
elif verification_type == "arkose":
return self._handle_arkose_challenge()
else:
return {
"success": False,
"error": f"Unbekannter Verifizierungstyp: {verification_type}"
}
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der Verifizierung: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"stage": "exception"
}
def _detect_verification_type(self) -> Optional[str]:
"""
Erkennt welche Art von Verifizierung benötigt wird.
Returns:
Optional[str]: Verifizierungstyp oder None
"""
try:
page = self.automation.browser.page
# E-Mail-Verifizierung
if page.is_visible(self.selectors.REGISTRATION["verification_code_input"], timeout=2000):
return "email"
# Telefon-Verifizierung
if page.is_visible(self.selectors.VERIFICATION["phone_verification_input"], timeout=2000):
return "phone"
# Captcha
if page.is_visible(self.selectors.VERIFICATION["captcha_frame"], timeout=2000):
return "captcha"
# Arkose Challenge
if page.is_visible(self.selectors.VERIFICATION["challenge_frame"], timeout=2000):
return "arkose"
# Prüfe auf Text-Hinweise
if page.is_visible('text="Verifiziere deine E-Mail"', timeout=1000):
return "email"
if page.is_visible('text="Verifiziere deine Telefonnummer"', timeout=1000):
return "phone"
return None
except Exception as e:
logger.error(f"Fehler bei Verifizierungstyp-Erkennung: {e}")
return None
def _handle_email_verification(self, verification_code: str = None, **kwargs) -> Dict[str, Any]:
"""
Behandelt E-Mail-Verifizierung.
Args:
verification_code: Verifizierungscode
**kwargs: Weitere Parameter (email)
Returns:
Dict[str, Any]: Ergebnis der Verifizierung
"""
try:
page = self.automation.browser.page
# Wenn kein Code übergeben wurde, versuche ihn abzurufen
if not verification_code:
email = kwargs.get("email")
if not email:
return {
"success": False,
"error": "E-Mail-Adresse für Verifizierung fehlt"
}
self.automation._emit_customer_log("📧 Warte auf Verifizierungs-E-Mail...")
verification_code = self._retrieve_email_code(email)
if not verification_code:
return {
"success": False,
"error": "Konnte keinen Verifizierungscode aus E-Mail abrufen"
}
# Code eingeben
self.automation._emit_customer_log(f"✍️ Gebe Verifizierungscode ein: {verification_code}")
if not self._enter_verification_code(verification_code):
return {
"success": False,
"error": "Fehler beim Eingeben des Verifizierungscodes"
}
# Bestätigen
if not self._submit_verification():
return {
"success": False,
"error": "Fehler beim Bestätigen der Verifizierung"
}
# Warte auf Erfolg
if self._check_verification_success():
logger.info("E-Mail-Verifizierung erfolgreich")
self.automation._emit_customer_log("✅ E-Mail erfolgreich verifiziert!")
return {
"success": True,
"method": "email",
"code": verification_code
}
else:
return {
"success": False,
"error": "Verifizierung fehlgeschlagen"
}
except Exception as e:
logger.error(f"Fehler bei E-Mail-Verifizierung: {e}")
return {
"success": False,
"error": f"E-Mail-Verifizierung fehlgeschlagen: {str(e)}"
}
def _handle_phone_verification(self, verification_code: str = None, **kwargs) -> Dict[str, Any]:
"""
Behandelt Telefon-Verifizierung.
Args:
verification_code: Verifizierungscode
**kwargs: Weitere Parameter (phone_number)
Returns:
Dict[str, Any]: Ergebnis der Verifizierung
"""
try:
page = self.automation.browser.page
# Telefonnummer eingeben wenn erforderlich
phone_number = kwargs.get("phone_number")
if phone_number and page.is_visible(self.selectors.VERIFICATION["phone_verification_input"]):
logger.info(f"Gebe Telefonnummer ein: {phone_number}")
phone_input = page.wait_for_selector(self.selectors.VERIFICATION["phone_verification_input"])
self.automation.human_behavior.type_text(phone_input, phone_number)
# Code senden
if page.is_visible(self.selectors.VERIFICATION["send_code_button"]):
page.click(self.selectors.VERIFICATION["send_code_button"])
self.automation.human_behavior.random_delay(2, 3)
# Wenn kein Code übergeben wurde, warte auf manuelle Eingabe
if not verification_code:
self.automation._emit_customer_log("📱 Bitte gib den SMS-Code manuell ein...")
# Warte bis Code eingegeben wurde (max 5 Minuten)
start_time = time.time()
while time.time() - start_time < 300: # 5 Minuten
code_input = page.query_selector(self.selectors.VERIFICATION["verification_code_input"])
if code_input:
current_value = code_input.get_attribute("value")
if current_value and len(current_value) >= 6:
verification_code = current_value
break
time.sleep(2)
if not verification_code:
return {
"success": False,
"error": "Kein SMS-Code eingegeben (Timeout)"
}
else:
# Code eingeben
self.automation._emit_customer_log(f"✍️ Gebe SMS-Code ein: {verification_code}")
if not self._enter_verification_code(verification_code):
return {
"success": False,
"error": "Fehler beim Eingeben des SMS-Codes"
}
# Bestätigen
if not self._submit_verification():
return {
"success": False,
"error": "Fehler beim Bestätigen der Telefon-Verifizierung"
}
# Warte auf Erfolg
if self._check_verification_success():
logger.info("Telefon-Verifizierung erfolgreich")
self.automation._emit_customer_log("✅ Telefonnummer erfolgreich verifiziert!")
return {
"success": True,
"method": "phone",
"phone_number": phone_number
}
else:
return {
"success": False,
"error": "Telefon-Verifizierung fehlgeschlagen"
}
except Exception as e:
logger.error(f"Fehler bei Telefon-Verifizierung: {e}")
return {
"success": False,
"error": f"Telefon-Verifizierung fehlgeschlagen: {str(e)}"
}
def _handle_captcha_verification(self) -> Dict[str, Any]:
"""
Behandelt Captcha-Verifizierung.
Returns:
Dict[str, Any]: Ergebnis der Verifizierung
"""
try:
logger.warning("Captcha-Verifizierung erkannt")
self.automation._emit_customer_log("🤖 Captcha erkannt - manuelle Lösung erforderlich")
# Screenshot für Debugging
self.automation._take_screenshot("captcha_challenge")
# Hier könnte Integration mit Captcha-Solving-Service erfolgen
# Für jetzt: Warte auf manuelle Lösung
return {
"success": False,
"error": "Captcha-Lösung erforderlich - bitte manuell lösen",
"type": "captcha",
"manual_intervention_required": True
}
except Exception as e:
logger.error(f"Fehler bei Captcha-Behandlung: {e}")
return {
"success": False,
"error": f"Captcha-Behandlung fehlgeschlagen: {str(e)}"
}
def _handle_arkose_challenge(self) -> Dict[str, Any]:
"""
Behandelt Arkose Labs Challenge.
Returns:
Dict[str, Any]: Ergebnis der Verifizierung
"""
try:
logger.warning("Arkose Challenge erkannt")
self.automation._emit_customer_log("🛡️ Arkose Challenge erkannt - erweiterte Verifizierung erforderlich")
# Screenshot für Debugging
self.automation._take_screenshot("arkose_challenge")
return {
"success": False,
"error": "Arkose Challenge erkannt - manuelle Intervention erforderlich",
"type": "arkose",
"manual_intervention_required": True
}
except Exception as e:
logger.error(f"Fehler bei Arkose Challenge: {e}")
return {
"success": False,
"error": f"Arkose Challenge fehlgeschlagen: {str(e)}"
}
def _retrieve_email_code(self, email: str) -> Optional[str]:
"""
Ruft Verifizierungscode aus E-Mail ab.
Args:
email: E-Mail-Adresse
Returns:
Optional[str]: Verifizierungscode oder None
"""
try:
logger.info(f"Rufe Verifizierungscode für {email} ab")
# Warte auf E-Mail
self.automation.human_behavior.random_delay(5, 10)
# Hole E-Mails
emails = self.automation.email_handler.get_emails(
email,
subject_filter="X Verifizierungscode"
)
if not emails:
# Versuche alternative Betreff-Filter
emails = self.automation.email_handler.get_emails(
email,
subject_filter="Twitter"
)
if not emails:
logger.error("Keine Verifizierungs-E-Mail gefunden")
return None
# Extrahiere Code aus der neuesten E-Mail
latest_email = emails[0]
subject = latest_email.get('subject', '')
body = latest_email.get('body', '')
# Suche nach 6-stelligem Code
# Erst im Betreff
code_match = re.search(r'(\d{6})', subject)
if code_match:
code = code_match.group(1)
logger.info(f"Code aus Betreff extrahiert: {code}")
return code
# Dann im Body
code_match = re.search(r'(\d{6})', body)
if code_match:
code = code_match.group(1)
logger.info(f"Code aus E-Mail-Body extrahiert: {code}")
return code
logger.error("Konnte keinen Code aus E-Mail extrahieren")
return None
except Exception as e:
logger.error(f"Fehler beim Abrufen des E-Mail-Codes: {e}")
return None
def _enter_verification_code(self, code: str) -> bool:
"""
Gibt einen Verifizierungscode ein.
Args:
code: Verifizierungscode
Returns:
bool: True bei Erfolg
"""
try:
page = self.automation.browser.page
# Finde Code-Eingabefeld
code_selectors = [
self.selectors.REGISTRATION["verification_code_input"],
self.selectors.VERIFICATION["verification_code_input"],
'input[autocomplete="one-time-code"]',
'input[name="code"]'
]
for selector in code_selectors:
try:
code_input = page.wait_for_selector(selector, timeout=3000)
if code_input:
logger.info(f"Code-Eingabefeld gefunden: {selector}")
self.automation.human_behavior.type_text(code_input, code)
self.automation.human_behavior.random_delay(0.5, 1)
return True
except:
continue
logger.error("Kein Code-Eingabefeld gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Eingeben des Codes: {e}")
return False
def _submit_verification(self) -> bool:
"""
Bestätigt die Verifizierung.
Returns:
bool: True bei Erfolg
"""
try:
page = self.automation.browser.page
# Submit-Button Selektoren
submit_selectors = [
'button:has-text("Weiter")',
'button:has-text("Bestätigen")',
'button:has-text("Verifizieren")',
'button:has-text("Next")',
'button:has-text("Verify")',
'button:has-text("Submit")'
]
for selector in submit_selectors:
if page.is_visible(selector, timeout=2000):
logger.info(f"Submit-Button gefunden: {selector}")
page.click(selector)
self.automation.human_behavior.random_delay(2, 3)
return True
# Alternativ: Enter drücken
page.keyboard.press("Enter")
logger.info("Enter gedrückt zur Bestätigung")
return True
except Exception as e:
logger.error(f"Fehler beim Bestätigen: {e}")
return False
def _check_verification_success(self) -> bool:
"""
Prüft ob Verifizierung erfolgreich war.
Returns:
bool: True bei Erfolg
"""
try:
page = self.automation.browser.page
# Warte auf Weiterleitung oder Erfolgsmeldung
self.automation.human_behavior.random_delay(2, 3)
# Erfolgsindikatoren
success_indicators = [
# Fehlen von Verifizierungsfeldern
lambda: not page.is_visible(self.selectors.REGISTRATION["verification_code_input"], timeout=2000),
# Vorhandensein von Account-Elementen
lambda: page.is_visible(self.selectors.NAVIGATION["home_link"], timeout=2000),
# URL-Änderung
lambda: "/home" in page.url or "/welcome" in page.url
]
for indicator in success_indicators:
if indicator():
logger.info("Verifizierung erfolgreich")
return True
# Prüfe auf Fehlermeldungen
error_msg = self.automation.ui_helper.check_for_errors()
if error_msg:
logger.error(f"Verifizierungsfehler: {error_msg}")
return False
return False
except Exception as e:
logger.error(f"Fehler bei Erfolgsprüfung: {e}")
return False

Datei anzeigen

@ -0,0 +1,329 @@
# social_networks/x/x_workflow.py
"""
X (Twitter) Workflow - Definiert die Arbeitsabläufe für X-Automatisierung
"""
from typing import Dict, List, Any
class XWorkflow:
"""
Definiert strukturierte Workflows für verschiedene X-Operationen.
"""
@staticmethod
def get_registration_workflow() -> Dict[str, Any]:
"""
Gibt den Workflow für die Account-Registrierung zurück.
Returns:
Dict[str, Any]: Workflow-Definition
"""
return {
"name": "X Account Registration",
"steps": [
{
"id": "navigate",
"name": "Navigate to X",
"description": "Zur X-Startseite navigieren",
"required": True,
"retry_count": 3
},
{
"id": "cookie_banner",
"name": "Handle Cookie Banner",
"description": "Cookie-Banner akzeptieren falls vorhanden",
"required": False,
"retry_count": 1
},
{
"id": "create_account",
"name": "Click Create Account",
"description": "Account erstellen Button klicken",
"required": True,
"retry_count": 2
},
{
"id": "fill_initial_form",
"name": "Fill Initial Form",
"description": "Name und E-Mail eingeben",
"required": True,
"retry_count": 2
},
{
"id": "enter_birthday",
"name": "Enter Birthday",
"description": "Geburtsdatum auswählen",
"required": True,
"retry_count": 2
},
{
"id": "next_birthday",
"name": "Continue After Birthday",
"description": "Weiter nach Geburtsdatum klicken",
"required": True,
"retry_count": 2
},
{
"id": "next_settings",
"name": "Continue Settings",
"description": "Weiter in Einstellungen klicken",
"required": True,
"retry_count": 2
},
{
"id": "email_verification",
"name": "Email Verification",
"description": "E-Mail-Verifizierungscode eingeben",
"required": True,
"retry_count": 3
},
{
"id": "set_password",
"name": "Set Password",
"description": "Passwort festlegen",
"required": True,
"retry_count": 2
},
{
"id": "skip_profile_picture",
"name": "Skip Profile Picture",
"description": "Profilbild überspringen",
"required": False,
"retry_count": 1
},
{
"id": "skip_username",
"name": "Skip Username",
"description": "Benutzername überspringen",
"required": False,
"retry_count": 1
},
{
"id": "skip_notifications",
"name": "Skip Notifications",
"description": "Benachrichtigungen überspringen",
"required": False,
"retry_count": 1
},
{
"id": "verify_success",
"name": "Verify Success",
"description": "Erfolgreiche Registrierung überprüfen",
"required": True,
"retry_count": 2
}
],
"timeout": 600, # 10 Minuten Gesamttimeout
"checkpoints": ["fill_initial_form", "email_verification", "verify_success"]
}
@staticmethod
def get_login_workflow() -> Dict[str, Any]:
"""
Gibt den Workflow für den Account-Login zurück.
Returns:
Dict[str, Any]: Workflow-Definition
"""
return {
"name": "X Account Login",
"steps": [
{
"id": "navigate",
"name": "Navigate to X",
"description": "Zur X-Startseite navigieren",
"required": True,
"retry_count": 3
},
{
"id": "cookie_banner",
"name": "Handle Cookie Banner",
"description": "Cookie-Banner akzeptieren falls vorhanden",
"required": False,
"retry_count": 1
},
{
"id": "click_login",
"name": "Click Login",
"description": "Anmelden Button klicken",
"required": True,
"retry_count": 2
},
{
"id": "enter_username",
"name": "Enter Username/Email",
"description": "Benutzername oder E-Mail eingeben",
"required": True,
"retry_count": 2
},
{
"id": "click_next",
"name": "Click Next",
"description": "Weiter klicken nach Benutzername",
"required": True,
"retry_count": 2
},
{
"id": "enter_password",
"name": "Enter Password",
"description": "Passwort eingeben",
"required": True,
"retry_count": 2
},
{
"id": "submit_login",
"name": "Submit Login",
"description": "Anmelden klicken",
"required": True,
"retry_count": 2
},
{
"id": "handle_challenges",
"name": "Handle Challenges",
"description": "Eventuelle Sicherheitsabfragen behandeln",
"required": False,
"retry_count": 3
},
{
"id": "verify_success",
"name": "Verify Success",
"description": "Erfolgreichen Login überprüfen",
"required": True,
"retry_count": 2
}
],
"timeout": 300, # 5 Minuten Gesamttimeout
"checkpoints": ["enter_username", "submit_login", "verify_success"]
}
@staticmethod
def get_verification_workflow() -> Dict[str, Any]:
"""
Gibt den Workflow für die Account-Verifizierung zurück.
Returns:
Dict[str, Any]: Workflow-Definition
"""
return {
"name": "X Account Verification",
"steps": [
{
"id": "check_verification_needed",
"name": "Check Verification",
"description": "Prüfen ob Verifizierung erforderlich",
"required": True,
"retry_count": 1
},
{
"id": "select_method",
"name": "Select Method",
"description": "Verifizierungsmethode auswählen",
"required": True,
"retry_count": 2
},
{
"id": "request_code",
"name": "Request Code",
"description": "Verifizierungscode anfordern",
"required": True,
"retry_count": 3
},
{
"id": "enter_code",
"name": "Enter Code",
"description": "Verifizierungscode eingeben",
"required": True,
"retry_count": 3
},
{
"id": "submit_verification",
"name": "Submit Verification",
"description": "Verifizierung abschließen",
"required": True,
"retry_count": 2
},
{
"id": "verify_success",
"name": "Verify Success",
"description": "Erfolgreiche Verifizierung überprüfen",
"required": True,
"retry_count": 2
}
],
"timeout": 300, # 5 Minuten Gesamttimeout
"checkpoints": ["enter_code", "verify_success"]
}
@staticmethod
def get_error_recovery_strategies() -> Dict[str, List[str]]:
"""
Gibt Fehlerbehandlungsstrategien zurück.
Returns:
Dict[str, List[str]]: Fehler und ihre Behandlungsstrategien
"""
return {
"rate_limit": [
"wait_exponential_backoff",
"rotate_proxy",
"change_user_agent",
"abort_with_retry_later"
],
"captcha": [
"solve_captcha_manual",
"solve_captcha_service",
"rotate_proxy_retry",
"abort_with_manual_intervention"
],
"suspended_account": [
"log_suspension",
"mark_account_suspended",
"abort_immediately"
],
"network_error": [
"retry_with_backoff",
"check_proxy_health",
"switch_proxy",
"retry_direct_connection"
],
"element_not_found": [
"wait_longer",
"refresh_page",
"check_alternate_selectors",
"take_screenshot_debug"
]
}
@staticmethod
def get_validation_rules() -> Dict[str, Any]:
"""
Gibt Validierungsregeln für verschiedene Eingaben zurück.
Returns:
Dict[str, Any]: Validierungsregeln
"""
return {
"username": {
"min_length": 1,
"max_length": 15,
"allowed_chars": r"^[a-zA-Z0-9_]+$",
"forbidden_patterns": ["twitter", "admin", "x.com"]
},
"password": {
"min_length": 8,
"max_length": 128,
"require_uppercase": False,
"require_lowercase": True,
"require_number": True,
"require_special": False
},
"email": {
"pattern": r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
"forbidden_domains": ["example.com", "test.com"]
},
"age": {
"minimum": 13,
"maximum": 120
}
}