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

0
social_networks/__init__.py Normale Datei
Datei anzeigen

Datei anzeigen

@ -0,0 +1,899 @@
"""
Basis-Automatisierungsklasse für soziale Netzwerke
"""
import os
import logging
import time
import random
from typing import Dict, List, Optional, Any, Tuple
from abc import ABC, abstractmethod
from browser.playwright_manager import PlaywrightManager
from utils.proxy_rotator import ProxyRotator
from utils.email_handler import EmailHandler
from utils.text_similarity import TextSimilarity, fuzzy_find_element, click_fuzzy_button
from domain.value_objects.browser_protection_style import BrowserProtectionStyle, ProtectionLevel
# Konfiguriere Logger
logger = logging.getLogger("base_automation")
class BaseAutomation(ABC):
"""
Abstrakte Basisklasse für die Automatisierung von sozialen Netzwerken.
Definiert die gemeinsame Schnittstelle für alle Implementierungen.
"""
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",
session_manager=None,
window_position: Optional[Tuple[int, int]] = None,
auto_close_browser: bool = False):
"""
Initialisiert die Basis-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
session_manager: Optional - Session Manager für Ein-Klick-Login
window_position: Optional - Fensterposition als Tuple (x, y)
auto_close_browser: Ob Browser automatisch geschlossen werden soll (Standard: False)
"""
self.headless = headless
self.use_proxy = use_proxy
self.proxy_type = proxy_type
self.session_manager = session_manager
self.save_screenshots = save_screenshots
self.slowmo = slowmo
self.debug = debug
self.email_domain = email_domain
self.auto_close_browser = auto_close_browser
# Verzeichnis für Screenshots
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.screenshots_dir = screenshots_dir or os.path.join(base_dir, "logs", "screenshots")
os.makedirs(self.screenshots_dir, exist_ok=True)
# Initialisiere Hilfsklassen
self.proxy_rotator = ProxyRotator()
self.email_handler = EmailHandler()
# Initialisiere TextSimilarity für robustes UI-Element-Matching
self.text_similarity = TextSimilarity(default_threshold=0.75)
# Playwright-Manager wird bei Bedarf initialisiert
self.browser = None
# Session-bezogene Attribute
self.current_session = None
self.current_fingerprint = None
self.session_restored = False
# Fensterposition
self.window_position = window_position
# Status und Ergebnis der Automatisierung
self.status = {
"success": False,
"stage": "initialized",
"error": None,
"account_data": {}
}
# Customer log callback (wird vom Worker Thread gesetzt)
self.customer_log_callback = None
# Status update callback für Login-Progress
self.status_update_callback = None
self.log_update_callback = None
# Debug-Logging
if self.debug:
logging.getLogger().setLevel(logging.DEBUG)
logger.info(f"Basis-Automatisierung initialisiert (Proxy: {use_proxy}, Typ: {proxy_type})")
def set_customer_log_callback(self, callback):
"""Setzt den Callback für kundenfreundliche Log-Nachrichten."""
self.customer_log_callback = callback
def _emit_customer_log(self, message: str):
"""Sendet eine kundenfreundliche Log-Nachricht."""
if self.customer_log_callback:
self.customer_log_callback(message)
def _initialize_browser(self) -> bool:
"""
Initialisiert den Browser mit den entsprechenden Einstellungen.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
self._emit_customer_log("🔄 Sichere Verbindung wird aufgebaut...")
# Proxy-Konfiguration, falls aktiviert
proxy_config = None
if self.use_proxy:
self._emit_customer_log("🌐 Optimale Verbindung wird ausgewählt...")
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")
# Prüfe ob Session wiederhergestellt werden soll
if self.current_session and self.current_fingerprint:
# Verwende Session-Aware Playwright Manager
from browser.session_aware_playwright_manager import SessionAwarePlaywrightManager
self.browser = SessionAwarePlaywrightManager(
session=self.current_session,
fingerprint=self.current_fingerprint,
headless=self.headless,
proxy=proxy_config,
browser_type="chromium",
screenshots_dir=self.screenshots_dir,
slowmo=self.slowmo,
window_position=self.window_position
)
# Browser mit Session starten
self.browser.start_with_session()
self.session_restored = True
logger.info("Browser mit wiederhergestellter Session initialisiert")
else:
# Normaler Browser ohne Session
self.browser = PlaywrightManager(
headless=self.headless,
proxy=proxy_config,
browser_type="chromium",
screenshots_dir=self.screenshots_dir,
slowmo=self.slowmo,
window_position=self.window_position
)
# Browser starten
self.browser.start()
logger.info("Browser erfolgreich initialisiert")
# Browser-Schutz anwenden wenn nicht headless
if not self.headless:
self._emit_customer_log("🛡️ Sicherheitseinstellungen werden konfiguriert...")
# TEMPORÄR DEAKTIVIERT zum Testen
# self._apply_browser_protection()
logger.info("Browser-Schutz wurde temporär deaktiviert")
self._emit_customer_log("✅ Verbindung erfolgreich hergestellt")
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 _close_browser(self) -> None:
"""
Schließt den Browser und gibt Ressourcen frei.
Berücksichtigt die auto_close_browser Einstellung.
"""
if self.auto_close_browser and self.browser:
self.browser.close()
self.browser = None
logger.info("Browser automatisch geschlossen")
elif self.browser and not self.auto_close_browser:
logger.info("Browser bleibt geöffnet (auto_close_browser=False)")
def close_browser(self) -> None:
"""
Explizite Methode zum manuellen Schließen des Browsers.
Ignoriert die auto_close_browser Einstellung.
"""
if self.browser:
self.browser.close()
self.browser = None
logger.info("Browser manuell geschlossen")
def is_browser_open(self) -> bool:
"""
Prüft, ob der Browser noch geöffnet ist.
Returns:
bool: True wenn Browser geöffnet ist, False sonst
"""
return self.browser is not None and hasattr(self.browser, 'page')
def get_browser(self) -> Optional[PlaywrightManager]:
"""
Gibt die Browser-Instanz zurück für weitere Operationen.
Returns:
Optional[PlaywrightManager]: Browser-Instanz oder None
"""
return self.browser
def _take_screenshot(self, name: str) -> Optional[str]:
"""
Erstellt einen Screenshot der aktuellen Seite.
Args:
name: Name für den Screenshot (ohne Dateierweiterung)
Returns:
Optional[str]: Pfad zum erstellten Screenshot oder None bei Fehler
"""
if not self.save_screenshots:
return None
try:
if self.browser and hasattr(self.browser, 'take_screenshot'):
return self.browser.take_screenshot(name)
except Exception as e:
logger.warning(f"Fehler beim Erstellen eines Screenshots: {e}")
return None
def _send_status_update(self, status: str) -> None:
"""
Sendet ein Status-Update über den Callback.
Args:
status: Status-Nachricht
"""
if self.status_update_callback:
try:
self.status_update_callback(status)
except Exception as e:
logger.error(f"Fehler beim Senden des Status-Updates: {e}")
def _send_log_update(self, message: str) -> None:
"""
Sendet ein Log-Update über den Callback.
Args:
message: Log-Nachricht
"""
if self.log_update_callback:
try:
self.log_update_callback(message)
except Exception as e:
logger.error(f"Fehler beim Senden des Log-Updates: {e}")
# Auch über customer_log_callback senden für Kompatibilität
if self.customer_log_callback:
try:
self.customer_log_callback(message)
except Exception as e:
logger.error(f"Fehler beim Senden des Customer-Log-Updates: {e}")
def _random_delay(self, min_seconds: float = 1.0, max_seconds: float = 3.0) -> None:
"""
Führt eine zufällige Wartezeit aus, um menschliches Verhalten zu simulieren.
Args:
min_seconds: Minimale Wartezeit in Sekunden
max_seconds: Maximale Wartezeit in Sekunden
"""
delay = random.uniform(min_seconds, max_seconds)
logger.debug(f"Zufällige Wartezeit: {delay:.2f} Sekunden")
time.sleep(delay)
def _fill_field_fuzzy(self, field_labels: List[str], value: str, fallback_selector: str = None) -> bool:
"""
Füllt ein Formularfeld mit Fuzzy-Text-Matching aus.
Args:
field_labels: Liste mit möglichen Bezeichnungen des Feldes
value: Einzugebender Wert
fallback_selector: CSS-Selektor für Fallback
Returns:
bool: True bei Erfolg, False bei Fehler
"""
# Versuche, das Feld mit Fuzzy-Matching zu finden
field = fuzzy_find_element(self.browser.page, field_labels, selector_type="input", threshold=0.6, wait_time=3000)
if field:
try:
field.fill(value)
return True
except Exception as e:
logger.warning(f"Fehler beim Ausfüllen des Feldes mit Fuzzy-Match: {e}")
# Fallback auf normales Ausfüllen
# Fallback: Versuche mit dem angegebenen Selektor
if fallback_selector:
field_success = self.browser.fill_form_field(fallback_selector, value)
if field_success:
return True
return False
def _click_button_fuzzy(self, button_texts: List[str], fallback_selector: str = None) -> bool:
"""
Klickt einen Button mit Fuzzy-Text-Matching.
Args:
button_texts: Liste mit möglichen Button-Texten
fallback_selector: CSS-Selektor für Fallback
Returns:
bool: True bei Erfolg, False bei Fehler
"""
# Versuche, den Button mit Fuzzy-Matching zu finden
success = click_fuzzy_button(self.browser.page, button_texts, threshold=0.6, timeout=3000)
if success:
return True
# Fallback: Versuche mit dem angegebenen Selektor
if fallback_selector:
return self.browser.click_element(fallback_selector)
return False
def _find_element_by_text(self, texts: List[str], selector_type: str = "any", threshold: float = 0.7) -> Optional[Any]:
"""
Findet ein Element basierend auf Textähnlichkeit.
Args:
texts: Liste mit möglichen Texten
selector_type: Art des Elements ("button", "link", "input", "any")
threshold: Ähnlichkeitsschwellenwert
Returns:
Das gefundene Element oder None
"""
return fuzzy_find_element(self.browser.page, texts, selector_type, threshold, wait_time=3000)
def _check_for_text_on_page(self, texts: List[str], threshold: float = 0.7) -> bool:
"""
Prüft, ob ein Text auf der Seite vorhanden ist.
Args:
texts: Liste mit zu suchenden Texten
threshold: Ähnlichkeitsschwellenwert
Returns:
True wenn einer der Texte gefunden wurde, False sonst
"""
# Hole den gesamten Seiteninhalt
try:
page_content = self.browser.page.content()
if not page_content:
return False
# Versuche, Text im HTML zu finden (einfache Suche)
for text in texts:
if text.lower() in page_content.lower():
return True
# Wenn nicht gefunden, versuche über alle sichtbaren Textelemente
elements = self.browser.page.query_selector_all("p, h1, h2, h3, h4, h5, h6, span, div, button, a, label")
for element in elements:
element_text = element.inner_text()
if not element_text:
continue
element_text = element_text.strip()
# Prüfe die Textähnlichkeit mit jedem der gesuchten Texte
for text in texts:
if self.text_similarity.is_similar(text, element_text, threshold=threshold):
return True
return False
except Exception as e:
logger.error(f"Fehler beim Prüfen auf Text auf der Seite: {e}")
return False
def _check_for_error(self, error_selectors: List[str], error_texts: List[str]) -> Optional[str]:
"""
Prüft, ob Fehlermeldungen angezeigt werden.
Args:
error_selectors: Liste mit CSS-Selektoren für Fehlermeldungen
error_texts: Liste mit typischen Fehlertexten
Returns:
Die Fehlermeldung oder None, wenn keine Fehler gefunden wurden
"""
try:
# Prüfe selektoren
for selector in error_selectors:
element = self.browser.wait_for_selector(selector, timeout=2000)
if element:
error_text = element.text_content()
if error_text:
return error_text.strip()
# Fuzzy-Suche nach Fehlermeldungen
elements = self.browser.page.query_selector_all("p, div[role='alert'], span.error, .error-message")
for element in elements:
element_text = element.inner_text()
if not element_text:
continue
element_text = element_text.strip()
# Prüfe, ob der Text einem Fehlermuster ähnelt
for error_text in error_texts:
if self.text_similarity.is_similar(error_text, element_text, threshold=0.6):
return element_text
return None
except Exception as e:
logger.error(f"Fehler beim Prüfen auf Fehlermeldungen: {e}")
return None
def _attempt_ocr_fallback(self, action_name: str, target_text: str = None, value: str = None) -> bool:
"""
Versucht, eine Aktion mit OCR-Fallback durchzuführen, wenn Playwright fehlschlägt.
Args:
action_name: Name der Aktion ("click", "type", "select")
target_text: Text, nach dem gesucht werden soll
value: Wert, der eingegeben werden soll (bei "type" oder "select")
Returns:
bool: True bei Erfolg, False bei Fehler
"""
# Diese Methode wird in abgeleiteten Klassen implementiert
logger.warning(f"OCR-Fallback für '{action_name}' wurde aufgerufen, aber nicht implementiert")
return False
def _rotate_proxy(self) -> bool:
"""
Rotiert den Proxy und aktualisiert die Browser-Sitzung.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self.use_proxy:
return False
try:
# Browser schließen
self._close_browser()
# Proxy rotieren
new_proxy = self.proxy_rotator.rotate_proxy(self.proxy_type)
if not new_proxy:
logger.warning("Konnte Proxy nicht rotieren")
return False
# Browser neu initialisieren
success = self._initialize_browser()
logger.info(f"Proxy rotiert zu: {new_proxy['server']}")
return success
except Exception as e:
logger.error(f"Fehler bei der Proxy-Rotation: {e}")
return False
def _generate_random_email(self, length: int = 10) -> str:
"""
Generiert eine zufällige E-Mail-Adresse.
Args:
length: Länge des lokalen Teils der E-Mail
Returns:
str: Die generierte E-Mail-Adresse
"""
import string
local_chars = string.ascii_lowercase + string.digits
local_part = ''.join(random.choice(local_chars) for _ in range(length))
return f"{local_part}@{self.email_domain}"
def _get_confirmation_code(self, email_address: str, search_criteria: str,
max_attempts: int = 30, delay_seconds: int = 2) -> Optional[str]:
"""
Ruft einen Bestätigungscode aus einer E-Mail ab.
Args:
email_address: E-Mail-Adresse, an die der Code gesendet wurde
search_criteria: Suchkriterium für die E-Mail
max_attempts: Maximale Anzahl an Versuchen
delay_seconds: Verzögerung zwischen Versuchen in Sekunden
Returns:
Optional[str]: Der Bestätigungscode oder None, wenn nicht gefunden
"""
logger.info(f"Suche nach Bestätigungscode für {email_address}")
code = self.email_handler.get_confirmation_code(
expected_email=email_address,
search_criteria=search_criteria,
max_attempts=max_attempts,
delay_seconds=delay_seconds
)
if code:
logger.info(f"Bestätigungscode gefunden: {code}")
else:
logger.warning(f"Kein Bestätigungscode für {email_address} gefunden")
return code
def _apply_browser_protection(self):
"""Wendet Browser-Schutz an, um versehentliche Interaktionen zu verhindern."""
try:
# Lade Schutz-Einstellungen aus stealth_config.json
import json
from pathlib import Path
protection_config = None
try:
config_file = Path(__file__).parent.parent / "config" / "stealth_config.json"
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
stealth_config = json.load(f)
protection_config = stealth_config.get("browser_protection", {})
except Exception as e:
logger.warning(f"Konnte Browser-Schutz-Konfiguration nicht laden: {e}")
# Nutze Konfiguration oder Standardwerte
if protection_config and protection_config.get("enabled", True):
level_mapping = {
"none": ProtectionLevel.NONE,
"light": ProtectionLevel.LIGHT,
"medium": ProtectionLevel.MEDIUM,
"strong": ProtectionLevel.STRONG
}
protection_style = BrowserProtectionStyle(
level=level_mapping.get(protection_config.get("level", "medium"), ProtectionLevel.MEDIUM),
show_border=protection_config.get("show_border", True),
show_badge=protection_config.get("show_badge", True),
blur_effect=protection_config.get("blur_effect", False),
opacity=protection_config.get("opacity", 0.1),
badge_text=protection_config.get("badge_text", "🔒 Account wird erstellt - Bitte nicht eingreifen"),
badge_position=protection_config.get("badge_position", "top-right"),
border_color=protection_config.get("border_color", "rgba(255, 0, 0, 0.5)")
)
# Wende Schutz an
if hasattr(self.browser, 'apply_protection'):
self.browser.apply_protection(protection_style)
logger.info("Browser-Schutz aktiviert")
except Exception as e:
# Browser-Schutz ist optional, Fehler nicht kritisch
logger.warning(f"Browser-Schutz konnte nicht aktiviert werden: {str(e)}")
def _is_text_similar(self, text1: str, text2: str, threshold: float = None) -> bool:
"""
Prüft, ob zwei Texte ähnlich sind.
Args:
text1: Erster Text
text2: Zweiter Text
threshold: Ähnlichkeitsschwellenwert (None für Standardwert)
Returns:
True wenn die Texte ähnlich sind, False sonst
"""
return self.text_similarity.is_similar(text1, text2, threshold)
@abstractmethod
def register_account(self, full_name: str, age: int, registration_method: str = "email",
phone_number: str = None, **kwargs) -> Dict[str, Any]:
"""
Registriert einen neuen Account im sozialen Netzwerk.
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
"""
pass
@abstractmethod
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
"""
Meldet sich bei einem bestehenden 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
"""
pass
@abstractmethod
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
"""
Verifiziert einen Account mit einem Bestätigungscode.
Args:
verification_code: Der Bestätigungscode
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Verifizierung mit Status
"""
pass
def login_with_session(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Template Method für Ein-Klick-Login mit gespeicherter Session.
Args:
session_data: Session-Daten vom OneClickLoginUseCase
Returns:
Dict mit Login-Ergebnis
"""
try:
# Session und Fingerprint setzen
self.current_session = session_data.get('session')
self.current_fingerprint = session_data.get('fingerprint')
# Browser mit Session initialisieren
if not self._initialize_browser():
return {
'success': False,
'error': 'Browser-Initialisierung fehlgeschlagen'
}
# Plattform-spezifische Login-Seite aufrufen
login_url = self._get_login_url()
self.browser.page.goto(login_url)
# Warte kurz für Session-Wiederherstellung
time.sleep(2)
# Prüfe ob Login erfolgreich
if self._check_logged_in_state():
logger.info("Ein-Klick-Login erfolgreich")
return {
'success': True,
'message': 'Erfolgreich mit Session eingeloggt',
'session_restored': True
}
else:
logger.warning("Session abgelaufen oder ungültig")
return {
'success': False,
'error': 'Session abgelaufen',
'requires_manual_login': True
}
except Exception as e:
logger.error(f"Fehler beim Session-Login: {e}")
return {
'success': False,
'error': str(e)
}
finally:
self._close_browser()
def create_with_session_persistence(self, **kwargs) -> Dict[str, Any]:
"""
Template Method für Account-Erstellung mit Session-Speicherung.
Args:
**kwargs: Plattformspezifische Parameter
Returns:
Dict mit Ergebnis und Session-Daten
"""
# Normale Account-Erstellung
result = self.register_account(**kwargs)
if result.get('success') and self.browser:
try:
# Session-Daten extrahieren
from browser.session_aware_playwright_manager import SessionAwarePlaywrightManager
if isinstance(self.browser, SessionAwarePlaywrightManager):
session = self.browser.save_current_session()
platform_data = self.browser.extract_platform_session_data(
self._get_platform_name()
)
result['session_data'] = {
'session': session,
'platform_data': platform_data,
'fingerprint': self.current_fingerprint
}
logger.info("Session-Daten für späteren Login gespeichert")
except Exception as e:
logger.error(f"Fehler beim Speichern der Session: {e}")
return result
def _get_login_url(self) -> str:
"""
Gibt die Login-URL der Plattform zurück.
Kann von Unterklassen überschrieben werden.
"""
# Standard-URLs für bekannte Plattformen
urls = {
'instagram': 'https://www.instagram.com/accounts/login/',
'facebook': 'https://www.facebook.com/',
'twitter': 'https://twitter.com/login',
'tiktok': 'https://www.tiktok.com/login'
}
platform = self._get_platform_name().lower()
return urls.get(platform, '')
def _check_logged_in_state(self) -> bool:
"""
Prüft ob der Benutzer eingeloggt ist.
Kann von Unterklassen überschrieben werden.
"""
# Basis-Implementation: Prüfe auf typische Login-Indikatoren
logged_in_indicators = [
'logout', 'log out', 'sign out', 'abmelden',
'profile', 'profil', 'dashboard', 'home'
]
# Prüfe URL
current_url = self.browser.page.url.lower()
if any(indicator in current_url for indicator in ['home', 'feed', 'dashboard']):
return True
# Prüfe Seiteninhalte
return self._check_for_text_on_page(logged_in_indicators, threshold=0.7)
def _get_platform_name(self) -> str:
"""
Gibt den Namen der Plattform zurück.
Sollte von Unterklassen überschrieben werden.
"""
# Versuche aus Klassennamen zu extrahieren
class_name = self.__class__.__name__.lower()
if 'instagram' in class_name:
return 'instagram'
elif 'facebook' in class_name:
return 'facebook'
elif 'twitter' in class_name:
return 'twitter'
elif 'tiktok' in class_name:
return 'tiktok'
return 'unknown'
def get_status(self) -> Dict[str, Any]:
"""
Gibt den aktuellen Status der Automatisierung zurück.
Returns:
Dict[str, Any]: Aktueller Status
"""
return self.status
def get_browser_context_data(self) -> Dict[str, Any]:
"""
Extrahiert Browser-Context-Daten für Session-Speicherung.
Returns:
Dict[str, Any]: Browser-Context-Daten
"""
try:
if not self.browser or not hasattr(self.browser, 'page'):
return {}
# Cookies extrahieren
cookies = self.browser.page.context.cookies()
# User Agent und Viewport
user_agent = self.browser.page.evaluate("() => navigator.userAgent")
viewport = self.browser.page.viewport_size
# URL
current_url = self.browser.page.url
return {
'cookies': cookies,
'user_agent': user_agent,
'viewport_size': viewport,
'current_url': current_url,
'context_id': getattr(self.browser.page.context, '_guid', None)
}
except Exception as e:
logger.error(f"Fehler beim Extrahieren der Browser-Context-Daten: {e}")
return {}
def extract_platform_session_data(self, platform: str) -> Dict[str, Any]:
"""
Extrahiert plattform-spezifische Session-Daten.
Args:
platform: Zielplattform
Returns:
Dict[str, Any]: Plattform-Session-Daten
"""
try:
if not self.browser or not hasattr(self.browser, 'page'):
return {}
# Local Storage extrahieren
local_storage = {}
try:
local_storage_script = """
() => {
const storage = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
storage[key] = localStorage.getItem(key);
}
return storage;
}
"""
local_storage = self.browser.page.evaluate(local_storage_script)
except Exception as e:
logger.warning(f"Konnte Local Storage nicht extrahieren: {e}")
# Session Storage extrahieren
session_storage = {}
try:
session_storage_script = """
() => {
const storage = {};
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
storage[key] = sessionStorage.getItem(key);
}
return storage;
}
"""
session_storage = self.browser.page.evaluate(session_storage_script)
except Exception as e:
logger.warning(f"Konnte Session Storage nicht extrahieren: {e}")
return {
'platform': platform,
'local_storage': local_storage,
'session_storage': session_storage,
'url': self.browser.page.url,
'created_at': time.time()
}
except Exception as e:
logger.error(f"Fehler beim Extrahieren der Plattform-Session-Daten: {e}")
return {}
def __enter__(self):
"""Kontext-Manager-Eintritt."""
self._initialize_browser()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Kontext-Manager-Austritt."""
self._close_browser()
# Wenn direkt ausgeführt, zeige Informationen zur Klasse
if __name__ == "__main__":
print("Dies ist eine abstrakte Basisklasse und kann nicht direkt instanziiert werden.")
print("Bitte verwende eine konkrete Implementierung wie InstagramAutomation.")

Datei anzeigen

Datei anzeigen

Datei anzeigen

@ -0,0 +1,336 @@
"""
Gmail Automatisierung - Hauptklasse
"""
import logging
import time
import random
from typing import Dict, Optional, Tuple, Any
from playwright.sync_api import Page
from social_networks.base_automation import BaseAutomation
from social_networks.gmail import gmail_selectors as selectors
from social_networks.gmail.gmail_ui_helper import GmailUIHelper
from social_networks.gmail.gmail_registration import GmailRegistration
from social_networks.gmail.gmail_login import GmailLogin
from social_networks.gmail.gmail_verification import GmailVerification
from social_networks.gmail.gmail_utils import GmailUtils
logger = logging.getLogger("gmail_automation")
class GmailAutomation(BaseAutomation):
"""
Gmail/Google Account-spezifische Automatisierung
"""
def __init__(self, **kwargs):
"""
Initialisiert die Gmail-Automatisierung
"""
super().__init__(**kwargs)
self.platform_name = "gmail"
self.ui_helper = None
self.registration = None
self.login_helper = None
self.verification = None
self.utils = None
def _initialize_helpers(self, page: Page):
"""
Initialisiert die Hilfsklassen
"""
self.ui_helper = GmailUIHelper(page, self.screenshots_dir, self.save_screenshots)
self.registration = GmailRegistration(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
self.login_helper = GmailLogin(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
self.verification = GmailVerification(page, self.ui_helper, self.email_handler, self.screenshots_dir, self.save_screenshots)
self.utils = GmailUtils()
def register_account(self, full_name: str, age: int, registration_method: str = "email",
phone_number: str = None, **kwargs) -> Dict[str, any]:
"""
Erstellt einen neuen Gmail/Google Account
Args:
full_name: Vollständiger Name für den Account
age: Alter des Benutzers
registration_method: Registrierungsmethode (nur "email" für Gmail)
phone_number: Telefonnummer (optional, aber oft erforderlich)
**kwargs: Weitere optionale Parameter
"""
try:
logger.info(f"[GMAIL AUTOMATION] register_account aufgerufen")
logger.info(f"[GMAIL AUTOMATION] full_name: {full_name}")
logger.info(f"[GMAIL AUTOMATION] age: {age}")
logger.info(f"[GMAIL AUTOMATION] phone_number: {phone_number}")
logger.info(f"[GMAIL AUTOMATION] kwargs: {kwargs}")
# Erstelle account_data aus den Parametern
account_data = {
"full_name": full_name,
"first_name": kwargs.get("first_name", full_name.split()[0] if full_name else ""),
"last_name": kwargs.get("last_name", full_name.split()[-1] if full_name and len(full_name.split()) > 1 else ""),
"age": age,
"birthday": kwargs.get("birthday", self._generate_birthday(age)),
"gender": kwargs.get("gender", random.choice(["male", "female"])),
"username": kwargs.get("username", ""),
"password": kwargs.get("password", ""),
"phone": phone_number,
"recovery_email": kwargs.get("recovery_email", "")
}
# Initialisiere Browser, falls noch nicht geschehen
logger.info(f"[GMAIL AUTOMATION] Prüfe Browser-Status...")
logger.info(f"[GMAIL AUTOMATION] self.browser: {self.browser}")
if self.browser:
logger.info(f"[GMAIL AUTOMATION] hasattr(self.browser, 'page'): {hasattr(self.browser, 'page')}")
if not self.browser or not hasattr(self.browser, 'page'):
logger.info(f"[GMAIL AUTOMATION] Browser muss initialisiert werden")
if not self._initialize_browser():
logger.error(f"[GMAIL AUTOMATION] Browser-Initialisierung fehlgeschlagen!")
return {
"success": False,
"error": "Browser konnte nicht initialisiert werden",
"message": "Browser-Initialisierung fehlgeschlagen"
}
logger.info(f"[GMAIL AUTOMATION] Browser erfolgreich initialisiert")
# Page-Objekt holen
page = self.browser.page
self._initialize_helpers(page)
# Direkt zur Registrierungs-URL navigieren
logger.info("Navigiere zur Gmail Registrierungsseite")
page.goto(selectors.REGISTRATION_URL, wait_until="networkidle")
# Warte auf vollständiges Laden der Seite
logger.info("Warte auf vollständiges Laden der Seite...")
time.sleep(random.uniform(5, 7))
# Prüfe ob wir auf der richtigen Seite sind
current_url = page.url
logger.info(f"Aktuelle URL nach Navigation: {current_url}")
# Screenshot der Startseite
self.ui_helper.take_screenshot("gmail_start_page")
# Finde und klicke auf "Konto erstellen" Button (Dropdown)
try:
# Warte bis die Seite interaktiv ist
logger.info("Warte auf vollständiges Laden der Gmail Workspace Seite...")
page.wait_for_load_state("networkidle")
time.sleep(2)
# Debug: Alle sichtbaren Links/Buttons mit "Konto" ausgeben
try:
konto_elements = page.locator("*:has-text('Konto')").all()
logger.info(f"Gefundene Elemente mit 'Konto': {len(konto_elements)}")
for i, elem in enumerate(konto_elements[:5]): # Erste 5 Elemente
try:
tag = elem.evaluate("el => el.tagName")
text = elem.inner_text()
logger.info(f"Element {i}: <{tag}> - {text}")
except:
pass
except Exception as e:
logger.debug(f"Debug-Ausgabe fehlgeschlagen: {e}")
# Schritt 1: Klicke auf "Konto erstellen" Dropdown
create_account_selectors = [
"[aria-label='Konto erstellen']",
"div[aria-label='Konto erstellen']",
"[data-g-action='create an account']",
"button:has-text('Konto erstellen')",
"a:has-text('Konto erstellen')",
"*:has-text('Konto erstellen')", # Beliebiges Element mit dem Text
"[slot='label']:has-text('Konto erstellen')" # Spezifisch für Web Components
]
clicked_dropdown = False
for selector in create_account_selectors:
try:
elements = page.locator(selector).all()
logger.info(f"Selector {selector}: {len(elements)} Elemente gefunden")
if page.locator(selector).is_visible(timeout=3000):
# Versuche normale Klick-Methode
try:
page.locator(selector).first.click()
logger.info(f"Dropdown 'Konto erstellen' geklickt mit: {selector}")
clicked_dropdown = True
break
except:
# Versuche JavaScript-Klick als Fallback
page.locator(selector).first.evaluate("el => el.click()")
logger.info(f"Dropdown 'Konto erstellen' via JS geklickt mit: {selector}")
clicked_dropdown = True
break
except Exception as e:
logger.debug(f"Fehler mit Selector {selector}: {e}")
continue
if not clicked_dropdown:
logger.error("Konnte 'Konto erstellen' Dropdown nicht finden")
self.ui_helper.take_screenshot("konto_erstellen_not_found")
return {
"success": False,
"error": "Konto erstellen Dropdown nicht gefunden",
"message": "Navigation fehlgeschlagen"
}
# Kurz warten bis Dropdown geöffnet ist
time.sleep(1)
# Schritt 2: Klicke auf "Für die private Nutzung"
private_use_selectors = [
"a[aria-label='Gmail - Für die private Nutzung']",
"a:has-text('Für die private Nutzung')",
"[data-g-action='für die private nutzung']",
"span:has-text('Für die private Nutzung')"
]
clicked_private = False
for selector in private_use_selectors:
try:
if page.locator(selector).is_visible(timeout=2000):
page.locator(selector).click()
logger.info(f"'Für die private Nutzung' geklickt mit: {selector}")
clicked_private = True
break
except:
continue
if not clicked_private:
logger.error("Konnte 'Für die private Nutzung' nicht finden")
self.ui_helper.take_screenshot("private_nutzung_not_found")
return {
"success": False,
"error": "Für die private Nutzung Option nicht gefunden",
"message": "Navigation fehlgeschlagen"
}
# Warte auf die Registrierungsseite
time.sleep(random.uniform(3, 5))
except Exception as e:
logger.error(f"Fehler beim Navigieren zur Registrierung: {e}")
# Screenshot der Registrierungsseite
self.ui_helper.take_screenshot("gmail_registration_page")
# Registrierungsprozess starten
registration_result = self.registration.start_registration_flow(account_data)
if not registration_result["success"]:
return registration_result
# Nach erfolgreicher Registrierung
logger.info("Gmail Account-Registrierung erfolgreich abgeschlossen")
return {
"success": True,
"username": registration_result.get("username"),
"password": account_data.get("password"),
"email": registration_result.get("email"),
"phone": account_data.get("phone"),
"recovery_email": account_data.get("recovery_email"),
"message": "Account erfolgreich erstellt"
}
except Exception as e:
logger.error(f"Fehler bei der Gmail-Registrierung: {str(e)}")
return {
"success": False,
"error": str(e),
"message": f"Registrierung fehlgeschlagen: {str(e)}"
}
finally:
self._close_browser()
def login(self, username: str, password: str) -> Dict[str, any]:
"""
Meldet sich bei einem bestehenden Gmail/Google Account an
"""
try:
logger.info(f"Starte Gmail Login für {username}")
# Initialisiere Browser, falls noch nicht geschehen
if not self.browser or not hasattr(self.browser, 'page'):
if not self._initialize_browser():
return {
"success": False,
"error": "Browser konnte nicht initialisiert werden",
"message": "Browser-Initialisierung fehlgeschlagen"
}
# Page-Objekt holen
page = self.browser.page
self._initialize_helpers(page)
# Login durchführen
return self.login_helper.login(username, password)
except Exception as e:
logger.error(f"Fehler beim Gmail Login: {str(e)}")
return {
"success": False,
"error": str(e),
"message": f"Login fehlgeschlagen: {str(e)}"
}
finally:
self._close_browser()
def get_account_info(self) -> Dict[str, any]:
"""
Ruft Informationen über den aktuellen Account ab
"""
# TODO: Implementierung
return {
"success": False,
"message": "Noch nicht implementiert"
}
def logout(self) -> bool:
"""
Meldet sich vom aktuellen Account ab
"""
# TODO: Implementierung
return False
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
"""
Meldet sich bei einem bestehenden Gmail Account an.
Implementiert die abstrakte Methode aus BaseAutomation.
"""
return self.login(username_or_email, password)
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
"""
Verifiziert einen Gmail Account mit einem Bestätigungscode.
Implementiert die abstrakte Methode aus BaseAutomation.
"""
try:
if self.verification:
return self.verification.verify_with_code(verification_code)
else:
return {
"success": False,
"error": "Verification helper nicht initialisiert",
"message": "Verifizierung kann nicht durchgeführt werden"
}
except Exception as e:
return {
"success": False,
"error": str(e),
"message": f"Verifizierung fehlgeschlagen: {str(e)}"
}
def _generate_birthday(self, age: int) -> str:
"""
Generiert ein Geburtsdatum basierend auf dem Alter
"""
from datetime import datetime, timedelta
today = datetime.now()
birth_year = today.year - age
# Zufälliger Tag im Jahr
random_days = random.randint(0, 364)
birthday = datetime(birth_year, 1, 1) + timedelta(days=random_days)
return birthday.strftime("%Y-%m-%d")

Datei anzeigen

@ -0,0 +1,157 @@
"""
Gmail Login - Handhabt den Login-Prozess
"""
import logging
import time
import random
from typing import Dict
from playwright.sync_api import Page
from social_networks.gmail import gmail_selectors as selectors
from social_networks.gmail.gmail_ui_helper import GmailUIHelper
logger = logging.getLogger("gmail_login")
class GmailLogin:
"""
Handhabt den Gmail/Google Account Login-Prozess
"""
def __init__(self, page: Page, ui_helper: GmailUIHelper, screenshots_dir: str = None, save_screenshots: bool = True):
"""
Initialisiert den Login Handler
"""
self.page = page
self.ui_helper = ui_helper
self.screenshots_dir = screenshots_dir
self.save_screenshots = save_screenshots
def login(self, username: str, password: str) -> Dict[str, any]:
"""
Führt den Login durch
"""
try:
logger.info(f"Starte Gmail Login für {username}")
# Navigiere zur Login-Seite
self.page.goto(selectors.LOGIN_URL, wait_until="domcontentloaded")
time.sleep(random.uniform(2, 3))
self.ui_helper.take_screenshot("login_page")
# Email eingeben
if self.ui_helper.wait_for_element(selectors.LOGIN_EMAIL_INPUT, timeout=10000):
logger.info("Gebe Email-Adresse ein")
# Füge @gmail.com hinzu falls nicht vorhanden
email = username if "@" in username else f"{username}@gmail.com"
self.ui_helper.type_with_delay(selectors.LOGIN_EMAIL_INPUT, email)
time.sleep(random.uniform(0.5, 1))
# Screenshot vor dem Weiter-Klick
self.ui_helper.take_screenshot("email_entered")
# Weiter klicken
logger.info("Klicke auf Weiter")
self.ui_helper.click_with_retry(selectors.LOGIN_NEXT_BUTTON)
self.ui_helper.wait_for_loading_to_finish()
time.sleep(random.uniform(2, 3))
# Passwort eingeben
if self.ui_helper.wait_for_element(selectors.LOGIN_PASSWORD_INPUT, timeout=10000):
logger.info("Gebe Passwort ein")
self.ui_helper.type_with_delay(selectors.LOGIN_PASSWORD_INPUT, password)
time.sleep(random.uniform(0.5, 1))
# Screenshot vor dem Login
self.ui_helper.take_screenshot("password_entered")
# Login Button klicken
logger.info("Klicke auf Weiter")
self.ui_helper.click_with_retry(selectors.LOGIN_NEXT_BUTTON)
# Warte auf Navigation
self.ui_helper.wait_for_navigation()
time.sleep(random.uniform(3, 5))
# Prüfe ob Login erfolgreich war
if self._check_login_success():
logger.info("Login erfolgreich")
self.ui_helper.take_screenshot("login_success")
return {
"success": True,
"message": "Login erfolgreich"
}
else:
error_msg = self._get_error_message()
logger.error(f"Login fehlgeschlagen: {error_msg}")
self.ui_helper.take_screenshot("login_failed")
return {
"success": False,
"error": error_msg,
"message": f"Login fehlgeschlagen: {error_msg}"
}
except Exception as e:
logger.error(f"Fehler beim Login: {e}")
self.ui_helper.take_screenshot("login_error")
return {
"success": False,
"error": str(e),
"message": f"Login fehlgeschlagen: {str(e)}"
}
def _check_login_success(self) -> bool:
"""
Prüft ob der Login erfolgreich war
"""
try:
# Prüfe ob wir auf einer Google-Seite sind
current_url = self.page.url
success_indicators = [
"myaccount.google.com",
"mail.google.com",
"youtube.com",
"google.com/webhp",
"accounts.google.com/b/"
]
for indicator in success_indicators:
if indicator in current_url:
return True
# Prüfe ob Login-Formular noch sichtbar ist
if self.ui_helper.is_element_visible(selectors.LOGIN_EMAIL_INPUT):
return False
# Prüfe auf Fehlermeldung
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
return False
return True
except Exception as e:
logger.warning(f"Fehler bei der Login-Prüfung: {e}")
return False
def _get_error_message(self) -> str:
"""
Holt die Fehlermeldung falls vorhanden
"""
try:
# Prüfe verschiedene Fehlermeldungs-Selektoren
error_selectors = [
selectors.ERROR_MESSAGE,
selectors.ERROR_MESSAGE_ALT,
selectors.FORM_ERROR
]
for selector in error_selectors:
if self.ui_helper.is_element_visible(selector):
error_text = self.ui_helper.get_element_text(selector)
if error_text:
return error_text
return "Login fehlgeschlagen"
except:
return "Unbekannter Fehler"

Datei anzeigen

@ -0,0 +1,548 @@
"""
Gmail Registrierung - Handhabt den Registrierungsprozess
"""
import logging
import time
import random
from typing import Dict
from playwright.sync_api import Page
from social_networks.gmail import gmail_selectors as selectors
from social_networks.gmail.gmail_ui_helper import GmailUIHelper
logger = logging.getLogger("gmail_registration")
class GmailRegistration:
"""
Handhabt den Gmail/Google Account Registrierungsprozess
"""
def __init__(self, page: Page, ui_helper: GmailUIHelper, screenshots_dir: str = None, save_screenshots: bool = True):
"""
Initialisiert die Registrierung
"""
self.page = page
self.ui_helper = ui_helper
self.screenshots_dir = screenshots_dir
self.save_screenshots = save_screenshots
def _click_next_button(self) -> bool:
"""
Versucht den Weiter-Button mit verschiedenen Selektoren zu klicken
"""
logger.info("Versuche Weiter-Button zu klicken")
# Liste von Selektoren zum Ausprobieren
selectors_to_try = [
("span[jsname='V67aGc']:has-text('Weiter')", "parent_click"), # Der exakte Span mit jsname
("button:has(span.VfPpkd-vQzf8d:has-text('Weiter'))", "click"), # Button der den Span enthält
("button:has(div.VfPpkd-RLmnJb)", "click"), # Button mit dem Material Ripple div
(selectors.NEXT_BUTTON, "click"),
(selectors.NEXT_BUTTON_MATERIAL, "parent_click"),
(selectors.NEXT_BUTTON_SPAN, "click"),
("button:has-text('Weiter')", "click"),
("button:has-text('Next')", "click")
]
for selector, method in selectors_to_try:
try:
if method == "click":
if self.ui_helper.wait_for_element(selector, timeout=2000):
self.ui_helper.click_with_retry(selector)
logger.info(f"Erfolgreich geklickt mit Selektor: {selector}")
return True
elif method == "parent_click":
if self.ui_helper.wait_for_element(selector, timeout=2000):
# Versuche verschiedene Parent-Ebenen
for parent_level in ['..', '../..', '../../..']:
try:
self.page.locator(selector).locator(parent_level).click()
logger.info(f"Erfolgreich Parent geklickt mit Selektor: {selector} und Level: {parent_level}")
return True
except:
continue
except Exception as e:
logger.debug(f"Konnte nicht mit Selektor {selector} klicken: {e}")
continue
# Letzter Versuch mit Playwright's get_by_role
try:
self.page.get_by_role("button", name="Weiter").click()
logger.info("Erfolgreich mit get_by_role geklickt")
return True
except:
pass
logger.error("Konnte Weiter-Button nicht finden/klicken")
return False
def start_registration_flow(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Startet den Registrierungsflow
"""
try:
logger.info("Starte Gmail Registrierungsflow")
# Schritt 1: Name eingeben
name_result = self._fill_name_form(account_data)
if not name_result["success"]:
return name_result
# Schritt 2: Geburtsdatum und Geschlecht
birthday_result = self._fill_birthday_gender(account_data)
if not birthday_result["success"]:
return birthday_result
# Schritt 3: Gmail-Adresse wählen/erstellen
gmail_result = self._create_gmail_address(account_data)
if not gmail_result["success"]:
return gmail_result
# Schritt 4: Passwort festlegen
password_result = self._set_password(account_data)
if not password_result["success"]:
return password_result
# Schritt 5: Telefonnummer (optional/erforderlich)
phone_result = self._handle_phone_verification(account_data)
if not phone_result["success"]:
return phone_result
# Schritt 6: Recovery Email (optional)
recovery_result = self._handle_recovery_email(account_data)
if not recovery_result["success"]:
return recovery_result
# Schritt 7: Nutzungsbedingungen akzeptieren
terms_result = self._accept_terms()
if not terms_result["success"]:
return terms_result
return {
"success": True,
"username": gmail_result.get("username"),
"email": gmail_result.get("email"),
"message": "Registrierung erfolgreich abgeschlossen"
}
except Exception as e:
logger.error(f"Fehler im Registrierungsflow: {e}")
self.ui_helper.take_screenshot("registration_error")
return {
"success": False,
"error": str(e),
"message": f"Registrierung fehlgeschlagen: {str(e)}"
}
def _fill_name_form(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Füllt das Namensformular aus
"""
try:
logger.info("Fülle Namensformular aus")
# Screenshot der aktuellen Seite
self.ui_helper.take_screenshot("before_name_form_search")
# Warte kurz, damit die Seite vollständig lädt
time.sleep(2)
# Debug: Aktuelle URL ausgeben
current_url = self.page.url
logger.info(f"Aktuelle URL: {current_url}")
# Versuche Cookie-Banner zu schließen, falls vorhanden
try:
# Suche nach typischen Cookie-Akzeptieren-Buttons
cookie_selectors = [
"button:has-text('Alle akzeptieren')",
"button:has-text('Accept all')",
"button:has-text('Akzeptieren')",
"button:has-text('Accept')",
"[aria-label='Alle akzeptieren']"
]
for selector in cookie_selectors:
try:
if self.page.locator(selector).is_visible(timeout=1000):
self.page.locator(selector).click()
logger.info(f"Cookie-Banner geschlossen mit: {selector}")
time.sleep(1)
break
except:
continue
except Exception as e:
logger.debug(f"Kein Cookie-Banner gefunden oder Fehler: {e}")
# Versuche verschiedene Selektoren für das Vorname-Feld
first_name_selectors = [
selectors.FIRST_NAME_INPUT,
"input[aria-label='Vorname']",
"input[aria-label='First name']",
"#firstName",
"input[type='text'][autocomplete='given-name']"
]
first_name_found = False
for selector in first_name_selectors:
if self.ui_helper.wait_for_element(selector, timeout=3000):
first_name_found = True
selectors.FIRST_NAME_INPUT = selector # Update für diesen Durchlauf
logger.info(f"Vorname-Feld gefunden mit Selektor: {selector}")
break
if not first_name_found:
# Screenshot bei Fehler
self.ui_helper.take_screenshot("name_form_not_found_error")
return {
"success": False,
"error": "Namensformular nicht gefunden",
"message": "Registrierungsseite konnte nicht geladen werden"
}
# Vorname eingeben
first_name = account_data.get("first_name", "")
logger.info(f"Gebe Vorname ein: {first_name}")
self.ui_helper.type_with_delay(selectors.FIRST_NAME_INPUT, first_name)
time.sleep(random.uniform(0.5, 1))
# Nachname eingeben - versuche verschiedene Selektoren
last_name_selectors = [
selectors.LAST_NAME_INPUT,
"input[aria-label='Nachname']",
"input[aria-label='Last name']",
"#lastName",
"input[type='text'][autocomplete='family-name']"
]
last_name = account_data.get("last_name", "")
logger.info(f"Gebe Nachname ein: {last_name}")
for selector in last_name_selectors:
try:
if self.ui_helper.wait_for_element(selector, timeout=2000):
self.ui_helper.type_with_delay(selector, last_name)
logger.info(f"Nachname eingegeben mit Selektor: {selector}")
break
except:
continue
time.sleep(random.uniform(0.5, 1))
# Screenshot vor dem Weiter-Klick
self.ui_helper.take_screenshot("name_form_filled")
# Weiter Button klicken
if not self._click_next_button():
return {
"success": False,
"error": "Konnte Weiter-Button nicht klicken",
"message": "Navigation fehlgeschlagen"
}
self.ui_helper.wait_for_loading_to_finish()
time.sleep(random.uniform(2, 3))
return {
"success": True,
"message": "Namensformular ausgefüllt"
}
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des Namensformulars: {e}")
return {
"success": False,
"error": str(e)
}
def _fill_birthday_gender(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Füllt Geburtsdatum und Geschlecht aus
"""
try:
logger.info("Fülle Geburtsdatum und Geschlecht aus")
# Warte auf Formular
if not self.ui_helper.wait_for_element(selectors.BIRTHDAY_DAY, timeout=10000):
logger.warning("Geburtsdatum-Formular nicht gefunden, überspringe...")
return {"success": True}
# Geburtsdatum ausfüllen
birthday = account_data.get("birthday", "1990-01-15")
year, month, day = birthday.split("-")
# Tag eingeben
logger.info(f"Gebe Geburtstag ein: {day}")
self.ui_helper.type_with_delay(selectors.BIRTHDAY_DAY, day.lstrip("0"))
time.sleep(random.uniform(0.3, 0.6))
# Monat auswählen
logger.info(f"Wähle Geburtsmonat: {month}")
month_value = str(int(month)) # Entferne führende Null
self.ui_helper.select_dropdown_option(selectors.BIRTHDAY_MONTH, month_value)
time.sleep(random.uniform(0.3, 0.6))
# Jahr eingeben
logger.info(f"Gebe Geburtsjahr ein: {year}")
self.ui_helper.type_with_delay(selectors.BIRTHDAY_YEAR, year)
time.sleep(random.uniform(0.3, 0.6))
# Geschlecht auswählen
gender = account_data.get("gender", "male").lower()
gender_value = "1" if gender == "male" else "2" # 1=männlich, 2=weiblich
logger.info(f"Wähle Geschlecht: {gender}")
self.ui_helper.select_dropdown_option(selectors.GENDER_SELECT, gender_value)
time.sleep(random.uniform(0.5, 1))
# Screenshot vor dem Weiter-Klick
self.ui_helper.take_screenshot("birthday_gender_filled")
# Weiter klicken
logger.info("Klicke auf Weiter")
self._click_next_button()
self.ui_helper.wait_for_loading_to_finish()
time.sleep(random.uniform(2, 3))
return {
"success": True,
"message": "Geburtsdatum und Geschlecht ausgefüllt"
}
except Exception as e:
logger.error(f"Fehler beim Ausfüllen von Geburtsdatum/Geschlecht: {e}")
return {
"success": False,
"error": str(e)
}
def _create_gmail_address(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Erstellt die Gmail-Adresse
"""
try:
logger.info("Erstelle Gmail-Adresse")
# Warte auf Gmail-Erstellungsseite
time.sleep(random.uniform(2, 3))
self.ui_helper.take_screenshot("gmail_creation_page")
# Prüfe ob wir einen Benutzernamen eingeben können
if self.ui_helper.wait_for_element(selectors.GMAIL_USERNAME_INPUT, timeout=10000):
username = account_data.get("username", "")
if not username:
# Generiere einen Benutzernamen
from social_networks.gmail.gmail_utils import GmailUtils
utils = GmailUtils()
username = utils.generate_gmail_username(
account_data.get("first_name", ""),
account_data.get("last_name", "")
)
logger.info(f"Gebe Gmail-Benutzernamen ein: {username}")
self.ui_helper.type_with_delay(selectors.GMAIL_USERNAME_INPUT, username)
time.sleep(random.uniform(1, 2))
# Weiter klicken
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
self.ui_helper.wait_for_loading_to_finish()
time.sleep(random.uniform(2, 3))
# Prüfe auf Fehler (Benutzername bereits vergeben)
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
error_text = self.ui_helper.get_element_text(selectors.ERROR_MESSAGE)
logger.warning(f"Benutzername-Fehler: {error_text}")
# TODO: Implementiere alternative Benutzernamen-Vorschläge
return {
"success": True,
"username": username,
"email": f"{username}@gmail.com"
}
return {
"success": True,
"message": "Gmail-Adresse erstellt"
}
except Exception as e:
logger.error(f"Fehler beim Erstellen der Gmail-Adresse: {e}")
return {
"success": False,
"error": str(e)
}
def _set_password(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Setzt das Passwort
"""
try:
logger.info("Setze Passwort")
# Warte auf Passwort-Formular
if not self.ui_helper.wait_for_element(selectors.PASSWORD_INPUT, timeout=10000):
return {
"success": False,
"error": "Passwort-Formular nicht gefunden"
}
password = account_data.get("password", "")
if not password:
# Generiere ein sicheres Passwort
from social_networks.gmail.gmail_utils import GmailUtils
utils = GmailUtils()
password = utils.generate_secure_password()
# Passwort eingeben
logger.info("Gebe Passwort ein")
self.ui_helper.type_with_delay(selectors.PASSWORD_INPUT, password)
time.sleep(random.uniform(0.5, 1))
# Passwort bestätigen
if self.ui_helper.wait_for_element(selectors.PASSWORD_CONFIRM_INPUT, timeout=5000):
logger.info("Bestätige Passwort")
self.ui_helper.type_with_delay(selectors.PASSWORD_CONFIRM_INPUT, password)
time.sleep(random.uniform(0.5, 1))
# Screenshot vor dem Weiter-Klick
self.ui_helper.take_screenshot("password_set")
# Weiter klicken
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
self.ui_helper.wait_for_loading_to_finish()
time.sleep(random.uniform(2, 3))
return {
"success": True,
"message": "Passwort gesetzt"
}
except Exception as e:
logger.error(f"Fehler beim Setzen des Passworts: {e}")
return {
"success": False,
"error": str(e)
}
def _handle_phone_verification(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Handhabt die Telefonnummer-Verifizierung (falls erforderlich)
"""
try:
logger.info("Prüfe auf Telefonnummer-Verifizierung")
# Prüfe ob Telefonnummer erforderlich ist
if not self.ui_helper.wait_for_element(selectors.PHONE_INPUT, timeout=5000):
logger.info("Telefonnummer nicht erforderlich")
return {"success": True}
# Wenn Telefonnummer optional ist, überspringe
if self.ui_helper.is_element_visible(selectors.SKIP_BUTTON):
logger.info("Überspringe Telefonnummer")
self.ui_helper.click_with_retry(selectors.SKIP_BUTTON)
time.sleep(random.uniform(2, 3))
return {"success": True}
# Telefonnummer eingeben falls vorhanden
phone = account_data.get("phone", "")
if phone:
logger.info(f"Gebe Telefonnummer ein: {phone}")
self.ui_helper.type_with_delay(selectors.PHONE_INPUT, phone)
time.sleep(random.uniform(1, 2))
# Weiter klicken
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
self.ui_helper.wait_for_loading_to_finish()
# TODO: SMS-Verifizierung implementieren
logger.warning("SMS-Verifizierung noch nicht implementiert")
return {
"success": True,
"message": "Telefonnummer-Schritt abgeschlossen"
}
except Exception as e:
logger.error(f"Fehler bei der Telefonnummer-Verifizierung: {e}")
return {
"success": False,
"error": str(e)
}
def _handle_recovery_email(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Handhabt die Recovery-Email (optional)
"""
try:
logger.info("Prüfe auf Recovery-Email")
# Prüfe ob Recovery-Email Feld vorhanden ist
if not self.ui_helper.wait_for_element(selectors.RECOVERY_EMAIL_INPUT, timeout=5000):
logger.info("Recovery-Email nicht vorhanden")
return {"success": True}
# Überspringe wenn möglich
if self.ui_helper.is_element_visible(selectors.SKIP_BUTTON):
logger.info("Überspringe Recovery-Email")
self.ui_helper.click_with_retry(selectors.SKIP_BUTTON)
time.sleep(random.uniform(2, 3))
else:
# Recovery-Email eingeben falls vorhanden
recovery_email = account_data.get("recovery_email", "")
if recovery_email:
logger.info(f"Gebe Recovery-Email ein: {recovery_email}")
self.ui_helper.type_with_delay(selectors.RECOVERY_EMAIL_INPUT, recovery_email)
time.sleep(random.uniform(1, 2))
# Weiter klicken
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
self.ui_helper.wait_for_loading_to_finish()
return {
"success": True,
"message": "Recovery-Email Schritt abgeschlossen"
}
except Exception as e:
logger.error(f"Fehler bei der Recovery-Email: {e}")
return {
"success": False,
"error": str(e)
}
def _accept_terms(self) -> Dict[str, any]:
"""
Akzeptiert die Nutzungsbedingungen
"""
try:
logger.info("Akzeptiere Nutzungsbedingungen")
# Warte auf Nutzungsbedingungen
time.sleep(random.uniform(2, 3))
self.ui_helper.take_screenshot("terms_page")
# Scrolle nach unten (simuliere Lesen)
self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
time.sleep(random.uniform(2, 4))
# Akzeptiere Button suchen und klicken
if self.ui_helper.wait_for_element(selectors.AGREE_BUTTON, timeout=10000):
logger.info("Klicke auf 'Ich stimme zu'")
self.ui_helper.click_with_retry(selectors.AGREE_BUTTON)
self.ui_helper.wait_for_loading_to_finish()
time.sleep(random.uniform(3, 5))
# Screenshot nach Registrierung
self.ui_helper.take_screenshot("registration_complete")
return {
"success": True,
"message": "Nutzungsbedingungen akzeptiert"
}
except Exception as e:
logger.error(f"Fehler beim Akzeptieren der Nutzungsbedingungen: {e}")
return {
"success": False,
"error": str(e)
}

Datei anzeigen

@ -0,0 +1,59 @@
"""
Gmail/Google Account UI Selektoren und URLs
"""
# URLs
BASE_URL = "https://accounts.google.com/"
REGISTRATION_URL = "https://workspace.google.com/intl/de/gmail/"
LOGIN_URL = "https://accounts.google.com/ServiceLogin"
# Name Eingabe (Erster Schritt)
FIRST_NAME_INPUT = "input[name='firstName']"
LAST_NAME_INPUT = "input[name='lastName']"
NEXT_BUTTON = "button[jsname='LgbsSe']"
NEXT_BUTTON_SPAN = "span:has-text('Weiter')"
NEXT_BUTTON_MATERIAL = "div.VfPpkd-RLmnJb" # Material Design Weiter-Button
# Geburtsdatum und Geschlecht
BIRTHDAY_DAY = "input[name='day']"
BIRTHDAY_MONTH = "select[name='month']"
BIRTHDAY_YEAR = "input[name='year']"
GENDER_SELECT = "select[name='gender']"
# Gmail-Adresse erstellen
CREATE_GMAIL_RADIO = "div[data-value='createAccount']"
GMAIL_USERNAME_INPUT = "input[name='Username']"
# Passwort
PASSWORD_INPUT = "input[name='Passwd']"
PASSWORD_CONFIRM_INPUT = "input[name='PasswdAgain']"
# Telefonnummer Verifizierung
PHONE_INPUT = "input[id='phoneNumberId']"
PHONE_COUNTRY_SELECT = "select[data-id='countryList']"
# SMS Verifizierung
SMS_CODE_INPUT = "input[name='code']"
VERIFY_BUTTON = "button:has-text('Bestätigen')"
# Recovery Email (Optional)
RECOVERY_EMAIL_INPUT = "input[name='recoveryEmail']"
SKIP_BUTTON = "button:has-text('Überspringen')"
# Nutzungsbedingungen
AGREE_BUTTON = "button:has-text('Ich stimme zu')"
TERMS_CHECKBOX = "input[type='checkbox']"
# Fehler- und Erfolgsmeldungen
ERROR_MESSAGE = "div[jsname='B34EJ'] span"
ERROR_MESSAGE_ALT = "div.LXRPh"
CAPTCHA_CONTAINER = "div.aCsJod"
# Login Seite
LOGIN_EMAIL_INPUT = "input[type='email']"
LOGIN_PASSWORD_INPUT = "input[type='password'][name='password']"
LOGIN_NEXT_BUTTON = "button:has-text('Weiter')"
# Allgemeine Elemente
LOADING_SPINNER = "div.ANuIbb"
FORM_ERROR = "div[jsname='B34EJ']"

Datei anzeigen

@ -0,0 +1,151 @@
"""
Gmail UI Helper - Hilfsfunktionen für UI-Interaktionen
"""
import logging
import time
import random
import os
from typing import Optional
from playwright.sync_api import Page, ElementHandle
logger = logging.getLogger("gmail_ui_helper")
class GmailUIHelper:
"""
Hilfsklasse für Gmail UI-Interaktionen
"""
def __init__(self, page: Page, screenshots_dir: str = None, save_screenshots: bool = True):
"""
Initialisiert den UI Helper
"""
self.page = page
self.screenshots_dir = screenshots_dir or "logs/screenshots"
self.save_screenshots = save_screenshots
# Screenshot-Verzeichnis erstellen falls nötig
if self.save_screenshots and not os.path.exists(self.screenshots_dir):
os.makedirs(self.screenshots_dir)
def take_screenshot(self, name: str) -> Optional[str]:
"""
Erstellt einen Screenshot
"""
if not self.save_screenshots:
return None
try:
timestamp = int(time.time())
filename = f"{name}_{timestamp}.png"
filepath = os.path.join(self.screenshots_dir, filename)
self.page.screenshot(path=filepath)
logger.debug(f"Screenshot gespeichert: {filepath}")
return filepath
except Exception as e:
logger.warning(f"Fehler beim Erstellen des Screenshots: {e}")
return None
def wait_for_element(self, selector: str, timeout: int = 30000) -> bool:
"""
Wartet auf ein Element
"""
try:
self.page.wait_for_selector(selector, timeout=timeout)
return True
except Exception as e:
logger.error(f"Element {selector} nicht gefunden nach {timeout}ms")
return False
def type_with_delay(self, selector: str, text: str, delay_min: float = 0.05, delay_max: float = 0.15):
"""
Tippt Text mit menschenähnlicher Verzögerung
"""
try:
element = self.page.locator(selector)
element.click()
for char in text:
element.type(char)
time.sleep(random.uniform(delay_min, delay_max))
except Exception as e:
logger.error(f"Fehler beim Tippen in {selector}: {e}")
raise
def click_with_retry(self, selector: str, max_attempts: int = 3) -> bool:
"""
Klickt auf ein Element mit Wiederholungsversuchen
"""
for attempt in range(max_attempts):
try:
self.page.click(selector)
return True
except Exception as e:
logger.warning(f"Klick-Versuch {attempt + 1} fehlgeschlagen: {e}")
if attempt < max_attempts - 1:
time.sleep(random.uniform(1, 2))
return False
def scroll_to_element(self, selector: str):
"""
Scrollt zu einem Element
"""
try:
self.page.locator(selector).scroll_into_view_if_needed()
time.sleep(random.uniform(0.5, 1))
except Exception as e:
logger.warning(f"Fehler beim Scrollen zu {selector}: {e}")
def is_element_visible(self, selector: str) -> bool:
"""
Prüft ob ein Element sichtbar ist
"""
try:
return self.page.locator(selector).is_visible()
except:
return False
def get_element_text(self, selector: str) -> Optional[str]:
"""
Holt den Text eines Elements
"""
try:
return self.page.locator(selector).text_content()
except Exception as e:
logger.warning(f"Fehler beim Lesen des Texts von {selector}: {e}")
return None
def select_dropdown_option(self, selector: str, value: str):
"""
Wählt eine Option aus einem Dropdown
"""
try:
self.page.select_option(selector, value)
time.sleep(random.uniform(0.3, 0.6))
except Exception as e:
logger.error(f"Fehler beim Auswählen von {value} in {selector}: {e}")
raise
def wait_for_navigation(self, timeout: int = 30000):
"""
Wartet auf Navigation
"""
try:
self.page.wait_for_load_state("networkidle", timeout=timeout)
except Exception as e:
logger.warning(f"Navigation-Timeout nach {timeout}ms: {e}")
def wait_for_loading_to_finish(self):
"""
Wartet bis Ladeanimation verschwunden ist
"""
try:
# Warte bis der Loading Spinner nicht mehr sichtbar ist
from social_networks.gmail import gmail_selectors as selectors
if self.is_element_visible(selectors.LOADING_SPINNER):
self.page.wait_for_selector(selectors.LOADING_SPINNER, state="hidden", timeout=10000)
time.sleep(random.uniform(0.5, 1))
except:
pass

Datei anzeigen

@ -0,0 +1,122 @@
"""
Gmail Utils - Utility-Funktionen für Gmail
"""
import logging
import random
import string
from typing import Optional
logger = logging.getLogger("gmail_utils")
class GmailUtils:
"""
Utility-Funktionen für Gmail/Google Accounts
"""
@staticmethod
def generate_gmail_username(first_name: str, last_name: str) -> str:
"""
Generiert einen Gmail-kompatiblen Benutzernamen
"""
# Basis aus Vor- und Nachname
first_clean = ''.join(c.lower() for c in first_name if c.isalnum())
last_clean = ''.join(c.lower() for c in last_name if c.isalnum())
# Verschiedene Varianten
variants = [
f"{first_clean}{last_clean}",
f"{first_clean}.{last_clean}",
f"{last_clean}{first_clean}",
f"{first_clean[0]}{last_clean}",
f"{first_clean}{last_clean[0]}"
]
# Wähle eine zufällige Variante
base = random.choice(variants)
# Füge zufällige Zahlen hinzu
random_suffix = ''.join(random.choices(string.digits, k=random.randint(2, 4)))
return f"{base}{random_suffix}"
@staticmethod
def generate_secure_password(length: int = 16) -> str:
"""
Generiert ein sicheres Passwort für Google-Anforderungen
- Mindestens 8 Zeichen
- Mischung aus Buchstaben, Zahlen und Symbolen
"""
# Stelle sicher dass alle Zeichentypen enthalten sind
password_chars = []
# Mindestens 2 Kleinbuchstaben
password_chars.extend(random.choices(string.ascii_lowercase, k=2))
# Mindestens 2 Großbuchstaben
password_chars.extend(random.choices(string.ascii_uppercase, k=2))
# Mindestens 2 Zahlen
password_chars.extend(random.choices(string.digits, k=2))
# Mindestens 2 Sonderzeichen
special_chars = "!@#$%^&*"
password_chars.extend(random.choices(special_chars, k=2))
# Fülle mit zufälligen Zeichen auf
remaining_length = length - len(password_chars)
all_chars = string.ascii_letters + string.digits + special_chars
password_chars.extend(random.choices(all_chars, k=remaining_length))
# Mische die Zeichen
random.shuffle(password_chars)
return ''.join(password_chars)
@staticmethod
def is_valid_gmail_address(email: str) -> bool:
"""
Prüft ob eine Gmail-Adresse gültig ist
"""
if not email.endswith("@gmail.com"):
return False
username = email.split("@")[0]
# Gmail-Regeln:
# - 6-30 Zeichen
# - Buchstaben, Zahlen und Punkte
# - Muss mit Buchstabe oder Zahl beginnen
# - Kein Punkt am Anfang oder Ende
# - Keine aufeinanderfolgenden Punkte
if len(username) < 6 or len(username) > 30:
return False
if not username[0].isalnum() or not username[-1].isalnum():
return False
if ".." in username:
return False
# Prüfe erlaubte Zeichen
for char in username:
if not (char.isalnum() or char == "."):
return False
return True
@staticmethod
def format_phone_for_google(phone: str, country_code: str = "+1") -> str:
"""
Formatiert eine Telefonnummer für Google
"""
# Entferne alle nicht-numerischen Zeichen
phone_digits = ''.join(c for c in phone if c.isdigit())
# Wenn die Nummer bereits mit Ländercode beginnt
if phone.startswith("+"):
return phone
# Füge Ländercode hinzu
return f"{country_code}{phone_digits}"

Datei anzeigen

@ -0,0 +1,230 @@
"""
Gmail Verification - Handhabt die Verifizierungsprozesse
"""
import logging
import time
import random
from typing import Dict, Optional
from playwright.sync_api import Page
from social_networks.gmail import gmail_selectors as selectors
from social_networks.gmail.gmail_ui_helper import GmailUIHelper
from utils.email_handler import EmailHandler
logger = logging.getLogger("gmail_verification")
class GmailVerification:
"""
Handhabt die Gmail/Google Account Verifizierung
"""
def __init__(self, page: Page, ui_helper: GmailUIHelper, email_handler: EmailHandler = None,
screenshots_dir: str = None, save_screenshots: bool = True):
"""
Initialisiert den Verification Handler
"""
self.page = page
self.ui_helper = ui_helper
self.email_handler = email_handler
self.screenshots_dir = screenshots_dir
self.save_screenshots = save_screenshots
def handle_phone_verification(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Handhabt die Telefonnummer-Verifizierung
"""
try:
logger.info("Starte Telefon-Verifizierung")
# Warte auf Telefonnummer-Eingabefeld
if not self.ui_helper.wait_for_element(selectors.PHONE_INPUT, timeout=10000):
logger.info("Telefonnummer-Eingabefeld nicht gefunden")
return {
"success": True,
"message": "Keine Telefon-Verifizierung erforderlich"
}
self.ui_helper.take_screenshot("phone_verification_page")
# Telefonnummer eingeben
phone = account_data.get("phone", "")
if not phone:
logger.warning("Keine Telefonnummer vorhanden, überspringe wenn möglich")
# Versuche zu überspringen
if self.ui_helper.is_element_visible(selectors.SKIP_BUTTON):
self.ui_helper.click_with_retry(selectors.SKIP_BUTTON)
time.sleep(random.uniform(2, 3))
return {"success": True}
else:
return {
"success": False,
"error": "Telefonnummer erforderlich aber nicht vorhanden",
"message": "Telefonnummer wird benötigt"
}
logger.info(f"Gebe Telefonnummer ein: {phone}")
# Telefonnummer eingeben
self.ui_helper.type_with_delay(selectors.PHONE_INPUT, phone)
time.sleep(random.uniform(1, 2))
# Screenshot vor dem Absenden
self.ui_helper.take_screenshot("phone_entered")
# Absenden
if self.ui_helper.wait_for_element(selectors.NEXT_BUTTON, timeout=5000):
logger.info("Sende Telefonnummer ab")
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
self.ui_helper.wait_for_loading_to_finish()
time.sleep(random.uniform(3, 5))
# Warte auf SMS-Code Eingabefeld
if self.ui_helper.wait_for_element(selectors.SMS_CODE_INPUT, timeout=15000):
logger.info("SMS-Code Eingabefeld gefunden")
self.ui_helper.take_screenshot("sms_code_page")
# Hier würde normalerweise der SMS-Code abgerufen werden
sms_code = self._get_sms_code(phone)
if not sms_code:
logger.error("Kein SMS-Code erhalten")
return {
"success": False,
"error": "Kein SMS-Code erhalten",
"message": "SMS-Verifizierung fehlgeschlagen"
}
# SMS-Code eingeben
logger.info(f"Gebe SMS-Code ein: {sms_code}")
self.ui_helper.type_with_delay(selectors.SMS_CODE_INPUT, sms_code)
time.sleep(random.uniform(1, 2))
# Code bestätigen
if self.ui_helper.wait_for_element(selectors.VERIFY_BUTTON, timeout=5000):
logger.info("Bestätige SMS-Code")
self.ui_helper.click_with_retry(selectors.VERIFY_BUTTON)
else:
# Versuche alternativen Button
self.ui_helper.click_with_retry(selectors.NEXT_BUTTON)
self.ui_helper.wait_for_loading_to_finish()
time.sleep(random.uniform(3, 5))
# Prüfe auf Erfolg
if self._check_verification_success():
logger.info("Telefon-Verifizierung erfolgreich")
self.ui_helper.take_screenshot("verification_success")
return {
"success": True,
"message": "Verifizierung erfolgreich"
}
else:
error_msg = self._get_verification_error()
logger.error(f"Verifizierung fehlgeschlagen: {error_msg}")
return {
"success": False,
"error": error_msg,
"message": f"Verifizierung fehlgeschlagen: {error_msg}"
}
else:
logger.info("Kein SMS-Code erforderlich")
return {
"success": True,
"message": "Telefon-Verifizierung abgeschlossen"
}
except Exception as e:
logger.error(f"Fehler bei der Telefon-Verifizierung: {e}")
self.ui_helper.take_screenshot("verification_error")
return {
"success": False,
"error": str(e),
"message": f"Verifizierung fehlgeschlagen: {str(e)}"
}
def handle_captcha(self) -> Dict[str, any]:
"""
Handhabt Captcha-Herausforderungen
"""
try:
logger.info("Prüfe auf Captcha")
if self.ui_helper.is_element_visible(selectors.CAPTCHA_CONTAINER):
logger.warning("Captcha erkannt - manuelle Lösung erforderlich")
self.ui_helper.take_screenshot("captcha_detected")
# TODO: Implementiere Captcha-Lösung
return {
"success": False,
"error": "Captcha erkannt",
"message": "Manuelle Captcha-Lösung erforderlich"
}
return {
"success": True,
"message": "Kein Captcha vorhanden"
}
except Exception as e:
logger.error(f"Fehler bei der Captcha-Prüfung: {e}")
return {
"success": False,
"error": str(e)
}
def _get_sms_code(self, phone: str) -> Optional[str]:
"""
Ruft den SMS-Code ab
TODO: Implementierung für echte SMS-Code Abfrage
"""
logger.warning("SMS-Code Abruf noch nicht implementiert - verwende Platzhalter")
# In einer echten Implementierung würde hier der SMS-Code
# von einem SMS-Service abgerufen werden
return "123456" # Platzhalter
def _check_verification_success(self) -> bool:
"""
Prüft ob die Verifizierung erfolgreich war
"""
try:
# Prüfe ob wir weitergeleitet wurden
current_url = self.page.url
if any(indicator in current_url for indicator in ["myaccount", "mail.google", "youtube"]):
return True
# Prüfe ob SMS-Code Feld noch sichtbar ist
if self.ui_helper.is_element_visible(selectors.SMS_CODE_INPUT):
# Prüfe auf Fehlermeldung
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
return False
# Wenn kein Fehler aber noch SMS-Code Feld, warten wir noch
return False
return True
except Exception as e:
logger.warning(f"Fehler bei der Verifizierungs-Prüfung: {e}")
return False
def _get_verification_error(self) -> str:
"""
Holt die Fehlermeldung falls vorhanden
"""
try:
error_selectors = [
selectors.ERROR_MESSAGE,
selectors.ERROR_MESSAGE_ALT,
selectors.FORM_ERROR
]
for selector in error_selectors:
if self.ui_helper.is_element_visible(selector):
error_text = self.ui_helper.get_element_text(selector)
if error_text:
return error_text
return "Verifizierung fehlgeschlagen"
except:
return "Unbekannter Fehler"

Datei anzeigen

@ -0,0 +1,44 @@
"""
Gmail Workflow - Workflow-Definitionen für Gmail/Google Accounts
"""
# Workflow-Schritte für Gmail
REGISTRATION_WORKFLOW = [
"navigate_to_registration",
"fill_name_form",
"fill_birthday_gender",
"create_gmail_address",
"set_password",
"handle_phone_verification",
"handle_recovery_email",
"accept_terms",
"verify_account_creation"
]
LOGIN_WORKFLOW = [
"navigate_to_login",
"enter_email",
"enter_password",
"handle_2fa_if_needed",
"verify_login_success"
]
# Timeouts in Sekunden
TIMEOUTS = {
"page_load": 30,
"element_wait": 10,
"verification_wait": 60,
"sms_wait": 120,
"captcha_wait": 300
}
# Fehler-Nachrichten
ERROR_MESSAGES = {
"username_taken": "Dieser Nutzername ist bereits vergeben",
"invalid_phone": "Ungültige Telefonnummer",
"invalid_code": "Der eingegebene Code ist ungültig",
"too_many_attempts": "Zu viele Versuche",
"account_suspended": "Dieses Konto wurde gesperrt",
"captcha_required": "Bitte lösen Sie das Captcha",
"age_restriction": "Sie müssen mindestens 13 Jahre alt sein"
}

Datei anzeigen

@ -0,0 +1,4 @@
# social_networks/instagram/__init__.py
from .instagram_automation import InstagramAutomation
__all__ = ['InstagramAutomation']

Datei anzeigen

@ -0,0 +1,992 @@
# social_networks/instagram/instagram_automation.py
"""
Instagram-Automatisierung - Hauptklasse für Instagram-Automatisierungsfunktionalität
"""
import logging
import time
import random
from datetime import datetime
from typing import Dict, List, Any, Optional, Tuple
from browser.playwright_manager import PlaywrightManager
from browser.playwright_extensions import PlaywrightExtensions
from browser.fingerprint_protection import FingerprintProtection
from social_networks.base_automation import BaseAutomation
from infrastructure.services.advanced_fingerprint_service import AdvancedFingerprintService
from infrastructure.repositories.fingerprint_repository import FingerprintRepository
from utils.password_generator import PasswordGenerator
from utils.username_generator import UsernameGenerator
from utils.birthday_generator import BirthdayGenerator
from utils.human_behavior import HumanBehavior
# Importiere Helferklassen
from .instagram_registration import InstagramRegistration
from .instagram_login import InstagramLogin
from .instagram_verification import InstagramVerification
from .instagram_ui_helper import InstagramUIHelper
from .instagram_utils import InstagramUtils
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("instagram_automation")
class InstagramAutomation(BaseAutomation):
"""
Hauptklasse für die Instagram-Automatisierung.
Implementiert die Registrierung und Anmeldung bei Instagram.
"""
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):
"""
Initialisiert die Instagram-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)
"""
# 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
)
# Stealth-Modus-Einstellungen
self.enhanced_stealth = enhanced_stealth
self.fingerprint_noise = max(0.0, min(1.0, fingerprint_noise))
# Initialisiere Helferklassen
self.registration = InstagramRegistration(self)
self.login = InstagramLogin(self)
self.verification = InstagramVerification(self)
self.ui_helper = InstagramUIHelper(self)
self.utils = InstagramUtils(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)
# Fingerprint Service für Account-gebundene Fingerprints
self.fingerprint_service = AdvancedFingerprintService(FingerprintRepository())
self.account_fingerprint = None
# Nutze übergebenen Fingerprint wenn vorhanden
self.provided_fingerprint = fingerprint
logger.info("Instagram-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 Instagram-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 Instagram-Account-Registrierung für '{full_name}' via {registration_method}")
self._emit_customer_log(f"📱 Instagram-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())}")
# Session-Daten extrahieren wenn Registrierung erfolgreich
if result.get("success") and self.browser and hasattr(self.browser, 'page') and self.browser.page:
try:
if session_data:
result["session_data"] = session_data
logger.info("Session-Daten erfolgreich extrahiert")
except Exception as e:
logger.warning(f"Konnte Session-Daten nicht extrahieren: {e}")
# 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:
# GEÄNDERT: Browser NICHT schließen bei Account-Registrierung - User soll Kontrolle behalten
logger.info("Account-Registrierung abgeschlossen - Browser bleibt offen für User-Kontrolle")
# self._close_browser()
def _initialize_browser_with_fingerprint(self, account_id: str) -> bool:
"""
Initialize browser with account-bound fingerprint.
Args:
account_id: The account ID to load fingerprint for
Returns:
bool: True if successful, False otherwise
"""
try:
logger.info(f"Initializing browser with fingerprint for account: {account_id}")
# Get or create fingerprint for account
logger.debug(f"Looking up fingerprint for account: {account_id}")
fingerprint = self.fingerprint_service.get_account_fingerprint(account_id)
if not fingerprint:
logger.info(f"No fingerprint found for account {account_id}, creating new one")
proxy_location = None
if self.use_proxy:
try:
proxy_info = self.proxy_rotator.get_proxy(self.proxy_type)
proxy_location = proxy_info.get('location') if proxy_info else None
logger.debug(f"Using proxy location: {proxy_location}")
except Exception as e:
logger.warning(f"Could not get proxy location: {e}")
fingerprint = self.fingerprint_service.create_account_fingerprint(
account_id=account_id,
profile_type="desktop",
proxy_location=proxy_location
)
logger.info(f"Created new fingerprint {fingerprint.fingerprint_id} for account {account_id}")
else:
logger.info(f"Found existing fingerprint {fingerprint.fingerprint_id} for account {account_id}")
# Load fingerprint for current session
logger.debug(f"Loading session fingerprint for {fingerprint.fingerprint_id}")
self.account_fingerprint = self.fingerprint_service.load_for_session(fingerprint.fingerprint_id)
logger.info(f"Successfully loaded fingerprint {fingerprint.fingerprint_id} for account {account_id}")
# Initialize browser with standard settings
if not self._initialize_browser():
return False
# Apply account-bound fingerprint if enhanced stealth is enabled
if self.enhanced_stealth and self.account_fingerprint:
# Create FingerprintProtection with loaded fingerprint
protection = FingerprintProtection(
context=self.browser.context,
stealth_config={
"noise_level": self.fingerprint_noise
},
fingerprint_config=self.account_fingerprint
)
protection.apply_to_context()
logger.info(f"Applied account-bound fingerprint protection for {account_id}")
return True
except Exception as e:
error_msg = f"Failed to initialize browser with fingerprint for account {account_id}: {str(e)}"
logger.error(error_msg, exc_info=True)
print(f"[ERROR] {error_msg}")
# Try to continue without account-bound fingerprint
fallback_msg = f"Falling back to standard browser initialization for account {account_id}"
logger.warning(fallback_msg)
print(f"[WARNING] {fallback_msg}")
try:
if self._initialize_browser():
print(f"[INFO] Fallback browser initialization successful")
return True
else:
print(f"[ERROR] Fallback browser initialization failed")
return False
except Exception as fallback_error:
fallback_error_msg = f"Fallback browser initialization also failed: {fallback_error}"
logger.error(fallback_error_msg)
print(f"[ERROR] {fallback_error_msg}")
return False
def login_account(self, username_or_email: str, password: str, account_id: Optional[str] = None, **kwargs) -> Dict[str, Any]:
"""
Meldet sich bei einem bestehenden Instagram-Account an.
Args:
username_or_email: Benutzername oder E-Mail-Adresse
password: Passwort
account_id: Optional account ID for loading saved fingerprint
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Anmeldung mit Status
"""
logger.info(f"Starte Instagram-Login für '{username_or_email}'")
try:
# Initialize browser with account fingerprint if account_id provided
# Prüfe ob Browser bereits offen ist (z.B. von fehlgeschlagenem Session-Login)
if not self.browser or not hasattr(self.browser, 'page') or (hasattr(self.browser, 'page') and not self.browser.page):
if account_id:
# Use account-specific fingerprint
if not self._initialize_browser_with_fingerprint(account_id):
return {"success": False, "error": "Browser konnte nicht mit Account-Fingerprint initialisiert werden"}
else:
# Use random fingerprint
if not self._initialize_browser():
return {"success": False, "error": "Browser konnte nicht initialisiert werden"}
else:
logger.info("Verwende bereits geöffneten Browser für Login")
# No fingerprint rotation needed when using account-bound fingerprint
if not account_id and 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())}")
# Session-Speicherung entfernt - nur normaler Login
# Update fingerprint statistics if account-bound fingerprint was used
if account_id and self.account_fingerprint and result.get("success"):
try:
self.fingerprint_service.update_fingerprint_stats(
self.account_fingerprint.fingerprint_id,
account_id,
success=True
)
logger.info(f"Updated fingerprint statistics for successful login")
except Exception as e:
logger.warning(f"Failed to update fingerprint statistics: {e}")
# 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"
})
# Update fingerprint statistics for failure if account-bound fingerprint was used
if account_id and self.account_fingerprint:
try:
self.fingerprint_service.update_fingerprint_stats(
self.account_fingerprint.fingerprint_id,
account_id,
success=False
)
except Exception as e:
logger.warning(f"Failed to update fingerprint statistics: {e}")
# KRITISCH: Session speichern VOR Browser-Schließung
return self.status
finally:
# GEÄNDERT: Browser NICHT schließen bei Login - User soll Kontrolle behalten
logger.info("Login abgeschlossen - Browser bleibt offen für User-Kontrolle")
# self._close_browser()
def login_with_session(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Führt Login mit einer gespeicherten Session durch.
Args:
session_data: Session-Daten vom OneClickLoginUseCase
Returns:
Dict[str, Any]: Login-Ergebnis
"""
try:
logger.info("Starte Session-basierten Login")
# Session-Daten extrahieren
fingerprint = session_data.get('fingerprint')
platform_session_data = session_data.get('platform_session_data')
browser_config = session_data.get('browser_config', {})
if not fingerprint:
return {
"success": False,
"error": "Kein Fingerprint in Session-Daten vorhanden",
"stage": "session_validation"
}
# Browser mit Session und Fingerprint initialisieren
logger.info("Initialisiere Browser mit Session und Fingerprint")
# Fingerprint als Dictionary für Browser-Initialisierung
fingerprint_dict = {
'canvas_noise': getattr(fingerprint, 'canvas_noise', 0.5),
'webgl_vendor': getattr(fingerprint, 'webgl_vendor', 'Intel Inc.'),
'webgl_renderer': getattr(fingerprint, 'webgl_renderer', 'Intel Iris OpenGL Engine'),
'audio_context_base_latency': getattr(fingerprint, 'audio_context_base_latency', 0.01),
'user_agent': browser_config.get('user_agent', ''),
'viewport_size': browser_config.get('viewport_size', (1920, 1080)),
'timezone': browser_config.get('timezone', 'Europe/Berlin'),
'locale': browser_config.get('locale', 'de-DE')
}
# Prüfen ob überhaupt Session-Daten vorhanden sind
has_cookies = False
cookie_count = 0
logger.debug(f"Checking session data for cookies...")
logger.debug(f"Platform session data exists: {platform_session_data is not None}")
if platform_session_data:
logger.debug(f"Platform session data type: {type(platform_session_data)}")
logger.debug(f"Has cookies attribute: {hasattr(platform_session_data, 'cookies')}")
if hasattr(platform_session_data, 'cookies'):
cookies_obj = platform_session_data.cookies
logger.debug(f"Cookies object type: {type(cookies_obj)}")
if hasattr(cookies_obj, 'cookies'):
cookie_count = len(cookies_obj.cookies)
has_cookies = cookie_count > 0
logger.debug(f"Found {cookie_count} cookies in cookies.cookies")
elif isinstance(cookies_obj, list):
cookie_count = len(cookies_obj)
has_cookies = cookie_count > 0
logger.debug(f"Found {cookie_count} cookies in list format")
elif isinstance(cookies_obj, dict):
cookie_count = len(cookies_obj)
has_cookies = cookie_count > 0
logger.debug(f"Found {cookie_count} cookies in dict format")
else:
logger.warning(f"Unknown cookies format: {type(cookies_obj)}")
else:
logger.warning(f"Platform session data has no 'cookies' attribute")
else:
logger.warning(f"No platform session data provided")
# ROBUSTE Session-Cookie-Validierung (FIX)
critical_cookies_found = []
critical_cookies_needed = ['sessionid', 'csrftoken', 'ds_user_id']
if has_cookies and platform_session_data and hasattr(platform_session_data, 'cookies'):
cookies_obj = platform_session_data.cookies
cookies_to_check = []
# Extrahiere Cookies je nach Format
if hasattr(cookies_obj, 'cookies'):
cookies_to_check = cookies_obj.cookies
elif isinstance(cookies_obj, list):
cookies_to_check = cookies_obj
# Prüfe auf kritische Session-Cookies
for cookie in cookies_to_check:
cookie_name = getattr(cookie, 'name', None) or cookie.get('name', '')
if cookie_name in critical_cookies_needed:
critical_cookies_found.append(cookie_name)
logger.debug(f"Kritischer Session-Cookie gefunden: {cookie_name}")
logger.info(f"Session validation: has_cookies={has_cookies}, cookie_count={cookie_count}")
logger.info(f"Kritische Session-Cookies gefunden: {critical_cookies_found}")
# Prüfe ob mindestens sessionid vorhanden ist
if not has_cookies or 'sessionid' not in critical_cookies_found:
missing_cookies = [c for c in critical_cookies_needed if c not in critical_cookies_found]
logger.warning(f"KRITISCHE Session-Cookies fehlen: {missing_cookies}")
logger.info("Session-Login nicht möglich - führe normalen Login durch")
return {
"success": False,
"error": f"Kritische Session-Cookies fehlen: {missing_cookies} (gefunden: {critical_cookies_found})",
"stage": "session_validation",
"fallback_to_normal_login": True,
"cookie_count": cookie_count,
"critical_cookies_found": critical_cookies_found,
"critical_cookies_missing": missing_cookies
}
# Browser mit Session-Awareness starten
success = self._initialize_browser_with_session(
fingerprint_dict=fingerprint_dict,
session_data=platform_session_data
)
if not success:
return {
"success": False,
"error": "Browser-Initialisierung mit Session fehlgeschlagen",
"stage": "browser_init"
}
# Zur Instagram-Hauptseite navigieren
self.browser.navigate_to("https://www.instagram.com/")
self.human_behavior.wait_for_page_load()
# Cookie-Consent behandeln falls nötig
from browser.cookie_consent_handler import CookieConsentHandler
try:
if self.browser.page:
consent_handled = CookieConsentHandler.check_and_handle_consent(self.browser.page, "instagram")
if consent_handled:
logger.info("Cookie-Consent bei Session-Wiederherstellung automatisch behandelt")
# Warte kurz nach Consent-Behandlung
time.sleep(3)
# Seite neu laden nach Cookie-Consent für bessere Session-Erkennung
logger.info("Lade Seite neu nach Cookie-Consent...")
self.browser.page.reload(wait_until='networkidle')
time.sleep(2)
except Exception as e:
logger.warning(f"Fehler bei Cookie-Consent-Behandlung: {e}")
# Screenshot nach Session-Wiederherstellung
self._take_screenshot("session_restored")
# Prüfen ob bereits eingeloggt (mit strengerer Validierung)
if self._check_login_success_strict():
logger.info("Session-basierter Login erfolgreich")
return {
"success": True,
"stage": "session_login_completed",
"message": "Mit gespeicherter Session erfolgreich eingeloggt"
}
else:
logger.warning("Session konnte nicht wiederhergestellt werden - falle zurück auf normalen Login")
# Browser NICHT schließen - wir verwenden ihn für den normalen Login weiter!
# Der Browser ist bereits auf Instagram und hat eventuell schon Cookies akzeptiert
return {
"success": False,
"error": "Session-Wiederherstellung fehlgeschlagen - Session möglicherweise abgelaufen",
"stage": "session_validation",
"fallback_to_normal_login": True,
"browser_already_open": True # Signal dass Browser offen ist
}
except Exception as e:
error_msg = f"Fehler beim Session-basierten Login: {str(e)}"
logger.error(error_msg, exc_info=True)
# Fehler-Screenshot
self._take_screenshot(f"session_login_error_{int(time.time())}")
return {
"success": False,
"error": error_msg,
"stage": "session_login_error"
}
def _initialize_browser_with_session(self, fingerprint_dict: Dict[str, Any], session_data: Any) -> bool:
"""
Initialisiert Browser mit Session-Wiederherstellung.
Args:
fingerprint_dict: Fingerprint-Daten
session_data: Session-Daten zum Wiederherstellen
Returns:
bool: True bei Erfolg
"""
try:
# Session-Aware Browser Manager verwenden
from browser.session_aware_playwright_manager import SessionAwarePlaywrightManager
from domain.entities.browser_session import BrowserSession
# BrowserSession-Objekt aus platform_session_data erstellen
if session_data:
# Session aus PlatformSessionData wiederherstellen
browser_session = BrowserSession()
# Debug: Session-Daten-Struktur loggen
logger.debug(f"Session-Daten-Typ: {type(session_data)}")
logger.debug(f"Session-Daten-Attribute: {dir(session_data) if hasattr(session_data, '__dict__') else 'Keine Attribute'}")
# Cookies setzen - robustere Behandlung
cookies_data = None
if hasattr(session_data, 'cookies'):
cookies_data = session_data.cookies
elif isinstance(session_data, dict) and 'cookies' in session_data:
cookies_data = session_data['cookies']
if cookies_data:
logger.debug(f"Cookies-Daten-Typ: {type(cookies_data)}")
from domain.entities.browser_session import Cookie, SessionCookies
session_cookies = SessionCookies()
# Behandle verschiedene Cookie-Formate
try:
if isinstance(cookies_data, list):
# Liste von Cookie-Dictionaries
for cookie_data in cookies_data:
if isinstance(cookie_data, dict):
cookie = Cookie(
name=cookie_data.get('name', ''),
value=cookie_data.get('value', ''),
domain=cookie_data.get('domain', ''),
path=cookie_data.get('path', '/'),
secure=cookie_data.get('secure', False),
http_only=cookie_data.get('httpOnly', False),
same_site=cookie_data.get('sameSite', 'Lax')
)
session_cookies.add_cookie(cookie)
elif hasattr(cookies_data, 'cookies'):
# SessionCookies-Objekt
if isinstance(cookies_data.cookies, list):
for cookie in cookies_data.cookies:
session_cookies.add_cookie(cookie)
else:
logger.debug("SessionCookies.cookies ist keine Liste")
elif isinstance(cookies_data, dict):
# Dictionary-Format
for name, value in cookies_data.items():
cookie = Cookie(
name=name,
value=str(value),
domain='.instagram.com',
path='/'
)
session_cookies.add_cookie(cookie)
browser_session.cookies = session_cookies
logger.debug(f"Successfully converted {len(session_cookies.cookies)} cookies")
# Warnung wenn keine Cookies gefunden wurden
if len(session_cookies.cookies) == 0:
logger.warning("Keine Cookies in Session gefunden - Session ist möglicherweise leer oder abgelaufen")
except Exception as cookie_error:
logger.error(f"Fehler beim Konvertieren der Cookies: {cookie_error}")
# Fallback: Empty cookies
browser_session.cookies = SessionCookies()
# Local/Session Storage setzen - robuste Behandlung
try:
if hasattr(session_data, 'local_storage') and session_data.local_storage:
if isinstance(session_data.local_storage, dict):
browser_session.local_storage.data = session_data.local_storage
elif hasattr(session_data.local_storage, 'data'):
# LocalStorageData-Objekt
browser_session.local_storage.data = session_data.local_storage.data
else:
logger.warning(f"Local Storage hat unerwartetes Format: {type(session_data.local_storage)}")
if hasattr(session_data, 'session_storage') and session_data.session_storage:
if isinstance(session_data.session_storage, dict):
browser_session.session_storage.data = session_data.session_storage
elif hasattr(session_data.session_storage, 'data'):
# SessionStorageData-Objekt
browser_session.session_storage.data = session_data.session_storage.data
else:
logger.warning(f"Session Storage hat unerwartetes Format: {type(session_data.session_storage)}")
except Exception as storage_error:
logger.error(f"Fehler beim Setzen von Storage-Daten: {storage_error}")
# Session-Aware Manager erstellen
try:
self.browser = SessionAwarePlaywrightManager(
headless=self.headless,
user_agent=fingerprint_dict.get('user_agent'),
proxy=getattr(self, 'proxy', None),
session=browser_session,
fingerprint=getattr(self, 'current_fingerprint', None)
)
logger.debug("SessionAwarePlaywrightManager erfolgreich erstellt")
except Exception as manager_error:
logger.error(f"Fehler beim Erstellen des SessionAwarePlaywrightManager: {manager_error}")
# Fallback auf normalen Browser
self.browser = PlaywrightManager(
headless=self.headless,
user_agent=fingerprint_dict.get('user_agent'),
proxy=getattr(self, 'proxy', None)
)
else:
# Fallback auf normalen Browser
self.browser = PlaywrightManager(
headless=self.headless,
user_agent=fingerprint_dict.get('user_agent'),
proxy=getattr(self, 'proxy', None)
)
# Browser starten - prüfe ob Session-Methode vorhanden ist
try:
if hasattr(self.browser, 'start_with_session'):
self.browser.start_with_session()
else:
# Fallback auf normalen Start
self.browser.start()
logger.warning("Session-Wiederherstellung nicht verfügbar, verwende normalen Browser-Start")
except Exception as start_error:
logger.error(f"Fehler beim Browser-Start: {start_error}")
# Letzter Fallback
self.browser = PlaywrightManager(
headless=self.headless,
user_agent=fingerprint_dict.get('user_agent'),
proxy=getattr(self, 'proxy', None)
)
self.browser.start()
# Human Behavior initialisieren
self.human_behavior = HumanBehavior()
logger.info("Browser mit Session erfolgreich initialisiert")
return True
except Exception as e:
logger.error(f"Fehler bei Browser-Initialisierung mit Session: {e}")
return False
def _check_login_success(self) -> bool:
"""Prüft ob der Login erfolgreich war (wiederverwendet von InstagramLogin)."""
try:
# Wiederverwendung der Logik aus instagram_login.py
from .instagram_selectors import InstagramSelectors
# Erfolg anhand verschiedener Indikatoren prüfen (Updated UI)
success_indicators = InstagramSelectors.SUCCESS_INDICATORS
found_indicators = 0
for indicator in success_indicators:
if self.browser.is_element_visible(indicator, timeout=2000):
logger.debug(f"Login-Indikator gefunden: {indicator}")
found_indicators += 1
if found_indicators >= 1: # Mindestens ein Indikator reicht
logger.info(f"Login erfolgreich - {found_indicators} Indikator(en) gefunden")
return True
# Alternativ prüfen, ob wir auf der Instagram-Startseite sind
current_url = self.browser.page.url
if "instagram.com" in current_url and "/accounts/login" not in current_url:
logger.info(f"Login-Erfolg basierend auf URL: {current_url}")
return True
# Prüfen, ob immer noch auf der Login-Seite
if "/accounts/login" in current_url:
logger.warning("Immer noch auf der Login-Seite, Login fehlgeschlagen")
return False
return False
except Exception as e:
logger.error(f"Fehler beim Prüfen des Login-Erfolgs: {e}")
return False
def _check_login_success_strict(self) -> bool:
"""Strenge Prüfung ob wirklich eingeloggt (nicht nur Homepage erreicht)."""
try:
from .instagram_selectors import InstagramSelectors
current_url = self.browser.page.url
# 1. Nicht auf Login-Seite sein
if "/accounts/login" in current_url:
logger.debug("Noch auf Login-Seite")
return False
# 2. Spezifische Login-Indikatoren prüfen (Deutsch und Englisch)
login_indicators = [
"//a[@aria-label='Home']", # Home-Button
"//a[@aria-label='Startseite']", # Home-Button Deutsch
"//a[@aria-label='Search']", # Suche-Button
"//a[@aria-label='Suchen']", # Suche-Button Deutsch
"//a[@aria-label='New post']", # Neuer Post Button
"//button[@aria-label='New post']", # Alternativer Neuer Post Button
"//a[@aria-label='Neuer Beitrag']", # Neuer Post Deutsch
"//button[@aria-label='Neuer Beitrag']", # Neuer Post Button Deutsch
"//svg[@aria-label='Instagram']", # Instagram Logo im Header
"//a[contains(@href, '/direct/')]", # Direct Messages
"//input[@placeholder='Search']", # Suchfeld
"//input[@placeholder='Suchen']", # Suchfeld Deutsch
"//span[contains(text(), 'Stories')]", # Stories-Text
"//span[contains(text(), 'Story')]" # Story-Text Deutsch
]
found_indicators = 0
for indicator in login_indicators:
if self.browser.is_element_visible(indicator, timeout=1000):
found_indicators += 1
logger.debug(f"Login-Indikator gefunden: {indicator}")
# Mindestens 2 Indikatoren für validen Login
if found_indicators >= 2:
logger.info(f"Session-Login bestätigt ({found_indicators} Indikatoren gefunden)")
return True
logger.warning(f"Nur {found_indicators} Login-Indikatoren gefunden, Login unsicher")
return False
except Exception as e:
logger.error(f"Fehler bei strenger Login-Prüfung: {e}")
return False
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
"""
Verifiziert einen Instagram-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 Instagram-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 _extract_session_data(self) -> Optional[Dict[str, Any]]:
"""
Extrahiert Session-Daten aus dem aktuellen Browser-Kontext.
Returns:
Dict[str, Any]: Session-Daten oder None bei Fehler
"""
try:
if not self.browser or not hasattr(self.browser, 'page') or not self.browser.page:
return None
# Browser-Kontext-Daten sammeln
browser_context_data = {
"cookies": self.browser.context.cookies() if hasattr(self.browser, 'context') else [],
"local_storage": {},
"session_storage": {}
}
# Local Storage und Session Storage extrahieren
try:
local_storage = self.browser.page.evaluate("""() => {
const items = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
items[key] = localStorage.getItem(key);
}
return items;
}""")
browser_context_data["local_storage"] = local_storage
session_storage = self.browser.page.evaluate("""() => {
const items = {};
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
items[key] = sessionStorage.getItem(key);
}
return items;
}""")
browser_context_data["session_storage"] = session_storage
except Exception as e:
logger.warning(f"Konnte Storage-Daten nicht extrahieren: {e}")
# Fingerprint-Daten
fingerprint = None
if self.enhanced_stealth and hasattr(self.browser, 'get_current_fingerprint'):
fingerprint = self.browser.get_current_fingerprint()
# Plattform-spezifische Daten
platform_session_data = {
"user_agent": self.browser.page.evaluate("() => navigator.userAgent") if self.browser.page else None,
"viewport": self.browser.page.viewport_size if hasattr(self.browser.page, 'viewport_size') else None,
"timestamp": time.time()
}
return {
"browser_context": browser_context_data,
"fingerprint": fingerprint,
"platform_data": platform_session_data
}
except Exception as e:
logger.error(f"Fehler beim Extrahieren der Session-Daten: {e}")
return None
# Session-Speicherung entfernt

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

Datei anzeigen

@ -0,0 +1,779 @@
# social_networks/instagram/instagram_registration.py
"""
Instagram-Registrierung - Klasse für die Kontoerstellung bei Instagram
"""
import logging
import time
import random
import re
from typing import Dict, List, Any, Optional, Tuple
from .instagram_selectors import InstagramSelectors
from .instagram_workflow import InstagramWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("instagram_registration")
class InstagramRegistration:
"""
Klasse für die Registrierung von Instagram-Konten.
Enthält alle Methoden zur Kontoerstellung.
"""
def __init__(self, automation):
"""
Initialisiert die Instagram-Registrierung.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
# Browser wird direkt von automation verwendet
self.selectors = InstagramSelectors()
self.workflow = InstagramWorkflow.get_registration_workflow()
logger.debug("Instagram-Registrierung initialisiert")
def register_account(self, full_name: str, age: int, registration_method: str = "email",
phone_number: str = None, **kwargs) -> Dict[str, Any]:
"""
Führt den vollständigen Registrierungsprozess für einen Instagram-Account durch.
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
"""
# Browser wird direkt von automation verwendet
# Validiere die Eingaben
if not self._validate_registration_inputs(full_name, age, registration_method, phone_number):
return {
"success": False,
"error": "Ungültige Eingabeparameter",
"stage": "input_validation"
}
# Account-Daten generieren
account_data = self._generate_account_data(full_name, age, registration_method, phone_number, **kwargs)
# Starte den Registrierungsprozess
logger.info(f"Starte Instagram-Registrierung für {account_data['username']} via {registration_method}")
try:
# 1. Zur Registrierungsseite navigieren
self.automation._emit_customer_log("🌐 Mit Instagram verbinden...")
if not self._navigate_to_signup_page():
return {
"success": False,
"error": "Konnte nicht zur Registrierungsseite navigieren",
"stage": "navigation",
"account_data": account_data
}
# 2. Cookie-Banner bereits in _navigate_to_signup_page behandelt (TIMING-FIX)
self.automation._emit_customer_log("⚙️ Einstellungen wurden vorbereitet...")
# 3. Registrierungsmethode wählen
if not self._select_registration_method(registration_method):
return {
"success": False,
"error": f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen",
"stage": "registration_method",
"account_data": account_data
}
# 4. Registrierungsformular ausfüllen
self.automation._emit_customer_log("📝 Persönliche Daten werden übertragen...")
if not self._fill_registration_form(account_data):
return {
"success": False,
"error": "Fehler beim Ausfüllen des Registrierungsformulars",
"stage": "registration_form",
"account_data": account_data
}
# 5. Geburtsdatum eingeben
self.automation._emit_customer_log("🎂 Geburtsdatum wird festgelegt...")
if not self._select_birthday(account_data["birthday"]):
return {
"success": False,
"error": "Fehler beim Eingeben des Geburtsdatums",
"stage": "birthday",
"account_data": account_data
}
# 6. Bestätigungscode abrufen und eingeben
self.automation._emit_customer_log("📧 Auf Bestätigungscode warten...")
if not self._handle_verification(account_data, registration_method):
return {
"success": False,
"error": "Fehler bei der Verifizierung",
"stage": "verification",
"account_data": account_data
}
# 7. Erfolgreiche Registrierung überprüfen
self.automation._emit_customer_log("🔍 Account wird finalisiert...")
if not self._check_registration_success():
return {
"success": False,
"error": "Registrierung fehlgeschlagen oder konnte nicht verifiziert werden",
"stage": "final_check",
"account_data": account_data
}
# Registrierung erfolgreich abgeschlossen
logger.info(f"Instagram-Account {account_data['username']} erfolgreich erstellt")
self.automation._emit_customer_log("✅ Account erfolgreich erstellt!")
# 8. Cookie-Consent behandeln (falls am Ende der Registrierung angezeigt)
try:
logger.info("Prüfe auf Cookie-Consent Dialog nach Registrierung...")
time.sleep(2) # Kurz warten, falls Dialog erscheint
# Import Cookie Handler (nur wenn benötigt)
from browser.cookie_consent_handler import CookieConsentHandler
# Cookie-Consent behandeln
if CookieConsentHandler.check_and_handle_consent(self.automation.browser.page, "instagram"):
logger.info("Cookie-Consent nach Registrierung behandelt")
self.automation._emit_customer_log("🍪 Cookie-Einstellungen akzeptiert")
time.sleep(1) # Kurz warten nach Cookie-Consent
except Exception as e:
logger.warning(f"Fehler bei Cookie-Consent Behandlung: {e}")
# Kein kritischer Fehler - Registrierung war bereits erfolgreich
return {
"success": True,
"stage": "completed",
"account_data": account_data
}
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der Instagram-Registrierung: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"stage": "exception",
"account_data": account_data
}
def _validate_registration_inputs(self, full_name: str, age: int,
registration_method: str, phone_number: str) -> bool:
"""
Validiert die Eingaben für die Registrierung.
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")
Returns:
bool: True wenn alle Eingaben gültig sind, False sonst
"""
# Vollständiger Name prüfen
if not full_name or len(full_name) < 3:
logger.error("Ungültiger vollständiger Name")
return False
# Alter prüfen
if age < 13:
logger.error("Benutzer muss mindestens 13 Jahre alt sein")
return False
# Registrierungsmethode prüfen
if registration_method not in ["email", "phone"]:
logger.error(f"Ungültige Registrierungsmethode: {registration_method}")
return False
# Telefonnummer prüfen, falls erforderlich
if registration_method == "phone" and not phone_number:
logger.error("Telefonnummer erforderlich für Registrierung via Telefon")
return False
return True
def _generate_account_data(self, full_name: str, age: int, registration_method: str,
phone_number: str, **kwargs) -> Dict[str, Any]:
"""
Generiert Account-Daten für die Registrierung.
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]: Generierte Account-Daten
"""
# Benutzername generieren
username = kwargs.get("username")
if not username:
username = self.automation.username_generator.generate_username("instagram", full_name)
# Passwort generieren
password = kwargs.get("password")
if not password:
password = self.automation.password_generator.generate_password("instagram")
# E-Mail generieren (falls nötig)
email = None
if registration_method == "email":
email_prefix = username.lower().replace(".", "").replace("_", "")
email = f"{email_prefix}@{self.automation.email_domain}"
# Geburtsdatum generieren
birthday = self.automation.birthday_generator.generate_birthday_components("instagram", age)
# Account-Daten zusammenstellen
account_data = {
"username": username,
"password": password,
"full_name": full_name,
"email": email,
"phone": phone_number,
"birthday": birthday,
"age": age,
"registration_method": registration_method
}
logger.debug(f"Account-Daten generiert: {account_data['username']}")
return account_data
def _navigate_to_signup_page(self) -> bool:
"""
Navigiert zur Instagram-Registrierungsseite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Zur Registrierungsseite navigieren
self.automation.browser.navigate_to(InstagramSelectors.SIGNUP_URL)
# Warten, bis die Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# SOFORT Cookie-Banner behandeln BEVOR weitere Aktionen (TIMING-FIX)
logger.info("Behandle Cookie-Banner SOFORT nach Navigation für korrekte Session-Cookies")
cookie_handled = self._handle_cookie_banner()
if not cookie_handled:
logger.warning("Cookie-Banner konnte nicht behandelt werden - Session könnte beeinträchtigt sein")
# Kurz warten damit Cookies gesetzt werden können
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot erstellen
self.automation._take_screenshot("signup_page")
# Prüfen, ob Registrierungsformular sichtbar ist
if not self.automation.browser.is_element_visible(InstagramSelectors.EMAIL_PHONE_FIELD, timeout=5000):
logger.warning("Registrierungsformular nicht sichtbar")
return False
logger.info("Erfolgreich zur Registrierungsseite navigiert und Cookies akzeptiert")
return True
except Exception as e:
logger.error(f"Fehler beim Navigieren zur Registrierungsseite: {e}")
return False
def _handle_cookie_banner(self) -> bool:
"""
Behandelt den Cookie-Banner, falls angezeigt.
Akzeptiert IMMER Cookies für vollständiges Session-Management bei der Registrierung.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
# Cookie-Dialog-Erkennung
if self.automation.browser.is_element_visible(InstagramSelectors.COOKIE_DIALOG, timeout=2000):
logger.info("Cookie-Banner erkannt - akzeptiere alle Cookies für Session-Management")
# Akzeptieren-Button suchen und klicken (PRIMÄR für Registrierung)
accept_success = self.automation.ui_helper.click_button_fuzzy(
InstagramSelectors.get_button_texts("accept_cookies"),
InstagramSelectors.COOKIE_ACCEPT_BUTTON
)
if accept_success:
logger.info("Cookie-Banner erfolgreich akzeptiert - Session-Cookies werden gespeichert")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
else:
logger.warning("Konnte Cookie-Banner nicht akzeptieren, versuche alternativen Akzeptieren-Button")
# Alternative Akzeptieren-Selektoren versuchen
alternative_accept_selectors = [
"//button[contains(text(), 'Alle akzeptieren')]",
"//button[contains(text(), 'Accept All')]",
"//button[contains(text(), 'Zulassen')]",
"//button[contains(text(), 'Allow All')]",
"//button[contains(@aria-label, 'Accept')]",
"[data-testid='accept-all-button']"
]
for selector in alternative_accept_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info("Cookie-Banner mit alternativem Selector akzeptiert")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte Cookie-Banner nicht akzeptieren - Session-Management könnte beeinträchtigt sein")
return False
else:
logger.debug("Kein Cookie-Banner erkannt")
return True
def _select_registration_method(self, method: str) -> bool:
"""
Wählt die Registrierungsmethode (E-Mail oder Telefon).
Args:
method: "email" oder "phone"
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
if method == "email":
# Prüfen, ob der E-Mail-Tab ausgewählt werden muss
if self.automation.browser.is_element_visible("//button[contains(text(), 'E-Mail') or contains(text(), 'Email')]"):
success = self.automation.ui_helper.click_button_fuzzy(
InstagramSelectors.get_tab_texts("email"),
InstagramSelectors.EMAIL_TAB
)
if success:
logger.info("E-Mail-Registrierungsmethode ausgewählt")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
else:
logger.warning("Konnte E-Mail-Tab nicht auswählen")
return False
# E-Mail ist vermutlich bereits ausgewählt
logger.debug("E-Mail-Registrierung erscheint bereits ausgewählt")
return True
elif method == "phone":
# Prüfen, ob der Telefon-Tab sichtbar ist und klicken
if self.automation.browser.is_element_visible("//button[contains(text(), 'Telefon') or contains(text(), 'Phone')]"):
success = self.automation.ui_helper.click_button_fuzzy(
InstagramSelectors.get_tab_texts("phone"),
InstagramSelectors.PHONE_TAB
)
if success:
logger.info("Telefon-Registrierungsmethode ausgewählt")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
else:
logger.warning("Konnte Telefon-Tab nicht auswählen")
return False
# Telefon ist möglicherweise bereits ausgewählt
logger.debug("Telefon-Registrierung erscheint bereits ausgewählt")
return True
logger.error(f"Ungültige Registrierungsmethode: {method}")
return False
except Exception as e:
logger.error(f"Fehler beim Auswählen der Registrierungsmethode: {e}")
return False
def _fill_registration_form(self, account_data: Dict[str, Any]) -> bool:
"""
Füllt das Registrierungsformular aus und sendet es ab.
Args:
account_data: Account-Daten für die Registrierung
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# E-Mail/Telefon eingeben
if account_data["registration_method"] == "email":
email_phone_value = account_data["email"]
else:
email_phone_value = account_data["phone"]
# E-Mail-/Telefon-Feld ausfüllen
email_phone_success = self.automation.ui_helper.fill_field_fuzzy(
InstagramSelectors.get_field_labels("email_phone"),
email_phone_value,
InstagramSelectors.EMAIL_PHONE_FIELD
)
if not email_phone_success:
logger.error("Konnte E-Mail/Telefon-Feld nicht ausfüllen")
return False
self.automation.human_behavior.random_delay(0.5, 1.5)
# Vollständigen Namen eingeben
name_success = self.automation.ui_helper.fill_field_fuzzy(
InstagramSelectors.get_field_labels("full_name"),
account_data["full_name"],
InstagramSelectors.FULLNAME_FIELD
)
if not name_success:
logger.error("Konnte Vollständiger-Name-Feld nicht ausfüllen")
return False
self.automation.human_behavior.random_delay(0.5, 1.5)
# Benutzernamen eingeben
username_success = self.automation.ui_helper.fill_field_fuzzy(
InstagramSelectors.get_field_labels("username"),
account_data["username"],
InstagramSelectors.USERNAME_FIELD
)
if not username_success:
logger.error("Konnte Benutzername-Feld nicht ausfüllen")
return False
self.automation.human_behavior.random_delay(0.5, 1.5)
# Passwort eingeben
password_success = self.automation.ui_helper.fill_field_fuzzy(
InstagramSelectors.get_field_labels("password"),
account_data["password"],
InstagramSelectors.PASSWORD_FIELD
)
if not password_success:
logger.error("Konnte Passwort-Feld nicht ausfüllen")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot vorm Absenden
self.automation._take_screenshot("registration_form_filled")
# Formular absenden
submit_success = self.automation.ui_helper.click_button_fuzzy(
InstagramSelectors.get_button_texts("submit"),
InstagramSelectors.SUBMIT_BUTTON
)
if not submit_success:
logger.error("Konnte Registrierungsformular nicht absenden")
return False
# Prüfen, ob es Fehler gab
self.automation.human_behavior.wait_for_page_load()
self.automation._emit_customer_log("⏳ Daten werden überprüft...")
# Nach dem Absenden prüfen, ob das Formular für das Geburtsdatum erscheint
birthday_visible = self.automation.browser.is_element_visible(InstagramSelectors.BIRTHDAY_MONTH_SELECT, timeout=10000)
if not birthday_visible:
# Auf mögliche Fehlermeldung prüfen
error_message = self.automation.ui_helper.check_for_error(
error_selectors=[InstagramSelectors.ERROR_MESSAGE],
error_texts=InstagramSelectors.get_error_indicators()
)
if error_message:
logger.error(f"Fehler beim Absenden des Formulars: {error_message}")
return False
logger.error("Geburtstagsformular nicht sichtbar nach Absenden")
return False
logger.info("Registrierungsformular erfolgreich ausgefüllt und abgesendet")
return True
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {e}")
return False
def _select_birthday(self, birthday: Dict[str, int]) -> bool:
"""
Wählt das Geburtsdatum aus den Dropdown-Menüs aus.
Args:
birthday: Geburtsdatum als Dictionary mit 'year', 'month', 'day' Keys
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis die Dropdowns sichtbar sind
if not self.automation.browser.is_element_visible(InstagramSelectors.BIRTHDAY_MONTH_SELECT, timeout=5000):
logger.error("Geburtstags-Dropdowns nicht sichtbar")
return False
# Screenshot zu Beginn
self.automation._take_screenshot("birthday_form")
# Monat auswählen
month_success = self.automation.browser.select_option(
InstagramSelectors.BIRTHDAY_MONTH_SELECT,
str(birthday["month"])
)
if not month_success:
logger.error(f"Konnte Monat nicht auswählen: {birthday['month']}")
return False
self.automation.human_behavior.random_delay(0.3, 0.8)
# Tag auswählen
day_success = self.automation.browser.select_option(
InstagramSelectors.BIRTHDAY_DAY_SELECT,
str(birthday["day"])
)
if not day_success:
logger.error(f"Konnte Tag nicht auswählen: {birthday['day']}")
return False
self.automation.human_behavior.random_delay(0.3, 0.8)
# Jahr auswählen
year_success = self.automation.browser.select_option(
InstagramSelectors.BIRTHDAY_YEAR_SELECT,
str(birthday["year"])
)
if not year_success:
logger.error(f"Konnte Jahr nicht auswählen: {birthday['year']}")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot vor dem Absenden
self.automation._take_screenshot("birthday_selected")
# Weiter-Button klicken
next_success = self.automation.ui_helper.click_button_fuzzy(
InstagramSelectors.get_button_texts("next"),
InstagramSelectors.NEXT_BUTTON
)
if not next_success:
logger.error("Konnte Weiter-Button nicht klicken")
return False
# Prüfen, ob es zum Bestätigungscode-Formular weitergeht
self.automation.human_behavior.wait_for_page_load()
# Nach dem Absenden auf den Bestätigungscode-Bildschirm warten
confirmation_visible = self.automation.browser.is_element_visible(
InstagramSelectors.CONFIRMATION_CODE_FIELD, timeout=10000
) or self.automation.browser.is_element_visible(
InstagramSelectors.ALT_CONFIRMATION_CODE_FIELD, timeout=2000
)
if not confirmation_visible:
# Auf mögliche Fehlermeldung prüfen
error_message = self.automation.ui_helper.check_for_error(
error_selectors=[InstagramSelectors.ERROR_MESSAGE],
error_texts=InstagramSelectors.get_error_indicators()
)
if error_message:
logger.error(f"Fehler nach dem Absenden des Geburtsdatums: {error_message}")
return False
logger.error("Bestätigungscode-Formular nicht sichtbar nach Absenden des Geburtsdatums")
return False
logger.info("Geburtsdatum erfolgreich ausgewählt und abgesendet")
return True
except Exception as e:
logger.error(f"Fehler beim Auswählen des Geburtsdatums: {e}")
return False
def _handle_verification(self, account_data: Dict[str, Any], registration_method: str) -> bool:
"""
Behandelt den Verifizierungsprozess (E-Mail/SMS).
Args:
account_data: Account-Daten mit E-Mail/Telefon
registration_method: "email" oder "phone"
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis die Bestätigungscode-Eingabe sichtbar ist
if not self.automation.browser.is_element_visible(InstagramSelectors.CONFIRMATION_CODE_FIELD, timeout=5000) and not \
self.automation.browser.is_element_visible(InstagramSelectors.ALT_CONFIRMATION_CODE_FIELD, timeout=1000):
logger.error("Bestätigungscode-Eingabe nicht sichtbar")
return False
# Screenshot erstellen
self.automation._take_screenshot("verification_page")
# Verifizierungscode je nach Methode abrufen
if registration_method == "email":
# Verifizierungscode von E-Mail abrufen
verification_code = self._get_email_confirmation_code(account_data["email"])
else:
# Verifizierungscode von SMS abrufen
verification_code = self._get_sms_confirmation_code(account_data["phone"])
if not verification_code:
logger.error("Konnte keinen Verifizierungscode abrufen")
return False
logger.info(f"Verifizierungscode erhalten: {verification_code}")
# Verifizierungscode eingeben und absenden
return self.automation.verification.enter_and_submit_verification_code(verification_code)
except Exception as e:
logger.error(f"Fehler bei der Verifizierung: {e}")
return False
def _get_email_confirmation_code(self, email: str) -> Optional[str]:
"""
Ruft den Bestätigungscode von einer E-Mail ab.
Args:
email: E-Mail-Adresse, an die der Code gesendet wurde
Returns:
Optional[str]: Der Bestätigungscode oder None, wenn nicht gefunden
"""
try:
# Warte auf die E-Mail
verification_code = self.automation.email_handler.get_verification_code(
target_email=email, # Verwende die vollständige E-Mail-Adresse
platform="instagram",
max_attempts=60, # 60 Versuche * 2 Sekunden = 120 Sekunden
delay_seconds=2
)
if verification_code:
return verification_code
# Wenn kein Code gefunden wurde, prüfen, ob der Code vielleicht direkt angezeigt wird
verification_code = self._extract_code_from_page()
if verification_code:
logger.info(f"Verifizierungscode direkt von der Seite extrahiert: {verification_code}")
return verification_code
logger.warning(f"Konnte keinen Verifizierungscode für {email} finden")
return None
except Exception as e:
logger.error(f"Fehler beim Abrufen des E-Mail-Bestätigungscodes: {e}")
return None
def _get_sms_confirmation_code(self, phone: str) -> Optional[str]:
"""
Ruft den Bestätigungscode aus einer SMS ab.
Hier müsste ein SMS-Empfangs-Service eingebunden werden.
Args:
phone: Telefonnummer, an die der Code gesendet wurde
Returns:
Optional[str]: Der Bestätigungscode oder None, wenn nicht gefunden
"""
# Diese Implementierung ist ein Platzhalter
# In einer echten Implementierung würde hier ein SMS-Empfangs-Service verwendet
logger.warning("SMS-Verifizierung ist noch nicht implementiert")
# Versuche, den Code trotzdem zu extrahieren, falls er auf der Seite angezeigt wird
return self._extract_code_from_page()
def _extract_code_from_page(self) -> Optional[str]:
"""
Versucht, einen Bestätigungscode direkt von der Seite zu extrahieren.
Returns:
Optional[str]: Der extrahierte Code oder None, wenn nicht gefunden
"""
try:
# Gesamten Seiteninhalt abrufen
page_content = self.automation.browser.page.content()
# Mögliche Regex-Muster für Bestätigungscodes
patterns = [
r"Dein Code ist (\d{6})",
r"Your code is (\d{6})",
r"Bestätigungscode: (\d{6})",
r"Confirmation code: (\d{6})",
r"(\d{6}) ist dein Instagram-Code",
r"(\d{6}) is your Instagram code"
]
for pattern in patterns:
match = re.search(pattern, page_content)
if match:
return match.group(1)
return None
except Exception as e:
logger.error(f"Fehler beim Extrahieren des Codes von der Seite: {e}")
return None
def _check_registration_success(self) -> bool:
"""
Überprüft, ob die Registrierung erfolgreich war.
Returns:
bool: True wenn erfolgreich, False sonst
"""
try:
# Warten nach der Verifizierung
self.automation.human_behavior.wait_for_page_load(multiplier=2.0)
# Screenshot erstellen
self.automation._take_screenshot("registration_final")
# Erfolg anhand verschiedener Indikatoren prüfen
success_indicators = InstagramSelectors.SUCCESS_INDICATORS
for indicator in success_indicators:
if self.automation.browser.is_element_visible(indicator, timeout=2000):
logger.info(f"Erfolgsindikator gefunden: {indicator}")
return True
# Alternativ prüfen, ob wir auf der Instagram-Startseite sind
current_url = self.automation.browser.page.url
if "instagram.com" in current_url and "/accounts/" not in current_url:
logger.info(f"Erfolg basierend auf URL: {current_url}")
return True
# Prüfen, ob wir auf weitere Einrichtungsschritte weitergeleitet wurden
# (z.B. "Folge anderen Benutzern" oder "Füge ein Profilbild hinzu")
# Diese sind optional und gelten als erfolgreiche Registrierung
if self.automation.ui_helper.check_for_next_steps():
logger.info("Auf weitere Einrichtungsschritte weitergeleitet, Registrierung erfolgreich")
return True
logger.warning("Keine Erfolgsindikatoren gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Überprüfen des Registrierungserfolgs: {e}")
return False

Datei anzeigen

@ -0,0 +1,263 @@
"""
Instagram-Selektoren - CSS-Selektoren und XPath-Ausdrücke für die Instagram-Automatisierung
Mit zusätzlichen Text-Matching-Funktionen für robustere Element-Erkennung
"""
from typing import List, Dict, Optional, Any
class InstagramSelectors:
"""
Zentrale Sammlung aller Selektoren für die Instagram-Automatisierung.
Bei Änderungen der Instagram-Webseite müssen nur hier Anpassungen vorgenommen werden.
Enthält auch Fuzzy-Text-Matching-Daten für robustere Element-Erkennung.
"""
# URL-Konstanten
BASE_URL = "https://www.instagram.com"
SIGNUP_URL = "https://www.instagram.com/accounts/emailsignup/"
LOGIN_URL = "https://www.instagram.com/accounts/login"
# Cookie-Banner
COOKIE_DIALOG = "div[role='dialog']"
COOKIE_REJECT_BUTTON = "//button[contains(text(), 'Ablehnen') or contains(text(), 'Nur erforderliche') or contains(text(), 'Reject')]"
COOKIE_ACCEPT_BUTTON = "//button[contains(text(), 'Akzeptieren') or contains(text(), 'Accept') or contains(text(), 'Zulassen')]"
# Registrierungsformular - Hauptfelder
EMAIL_PHONE_FIELD = "input[name='emailOrPhone']"
FULLNAME_FIELD = "input[name='fullName']"
USERNAME_FIELD = "input[name='username']"
PASSWORD_FIELD = "input[name='password']"
# Registrierungsformular - Alternative Selektoren
ALT_EMAIL_FIELD = "//input[@aria-label='Handynummer oder E-Mail-Adresse']"
ALT_FULLNAME_FIELD = "//input[@aria-label='Vollständiger Name']"
ALT_USERNAME_FIELD = "//input[@aria-label='Benutzername']"
ALT_PASSWORD_FIELD = "//input[@aria-label='Passwort']"
# Geburtsdatum-Selektoren
BIRTHDAY_MONTH_SELECT = "//select[@title='Monat:']"
BIRTHDAY_DAY_SELECT = "//select[@title='Tag:']"
BIRTHDAY_YEAR_SELECT = "//select[@title='Jahr:']"
# Buttons
SUBMIT_BUTTON = "button[type='submit']"
NEXT_BUTTON = "//button[contains(text(), 'Weiter') or contains(text(), 'Next')]"
CONFIRMATION_BUTTON = "//button[contains(text(), 'Confirm') or contains(text(), 'Verify') or contains(text(), 'Weiter')]"
# Bestätigungscode
CONFIRMATION_CODE_FIELD = "input[name='confirmationCode']"
ALT_CONFIRMATION_CODE_FIELD = "//input[@aria-label='Bestätigungscode']"
RESEND_CODE_BUTTON = "//button[contains(text(), 'Code erneut senden') or contains(text(), 'Resend code')]"
# Fehlermeldungen
ERROR_MESSAGE = "p[class*='error'], div[role='alert']"
CAPTCHA_CONTAINER = "div[class*='captcha']"
# Registrierungsmethode umschalten
EMAIL_TAB = "//button[contains(text(), 'E-Mail') or contains(text(), 'Email')]"
PHONE_TAB = "//button[contains(text(), 'Telefon') or contains(text(), 'Phone')]"
# Login-Felder
LOGIN_USERNAME_FIELD = "input[name='username']"
LOGIN_PASSWORD_FIELD = "input[name='password']"
# Erfolgs-Indikatoren für Login/Registrierung (Updated für 2025 Instagram-UI)
SUCCESS_INDICATORS = [
# Home-Indikatoren
"svg[aria-label='Home'], svg[aria-label='Startseite']",
"a[href='/']",
# Navigation-Indikatoren
"svg[aria-label='Search'], svg[aria-label='Suchen']",
"svg[aria-label='New post'], svg[aria-label='Neuer Beitrag']",
"svg[aria-label='Direct'], svg[aria-label='Nachrichten']",
# Profil-Indikatoren
"a[href*='/'] img[alt*='profilbild'], a[href*='/'] img[alt*='profile picture']",
# Navigation-Container
"nav[role='navigation'], div[role='navigation']",
# Instagram-Logo
"a[href='/'] svg[aria-label*='Instagram']",
# Story-Elemente
"div[role='button'] canvas, div[role='button'] img[alt*='story']",
# Legacy-Support
"img[alt='Instagram']",
"a[href='/direct/inbox/']",
"a[href='/explore/']"
]
# OCR-Fallback-Texte
OCR_TEXTS = {
"email_field": ["Handynummer oder E-Mail", "Mobile number or email"],
"fullname_field": ["Vollständiger Name", "Full name"],
"username_field": ["Benutzername", "Username"],
"password_field": ["Passwort", "Password"],
"birthday_day": ["Tag", "Day"],
"birthday_month": ["Monat", "Month"],
"birthday_year": ["Jahr", "Year"],
"confirmation_code": ["Bestätigungscode", "Confirmation code"],
"next_button": ["Weiter", "Next"],
"submit_button": ["Registrieren", "Sign up"],
"confirm_button": ["Bestätigen", "Confirm"],
"cookie_reject": ["Nur erforderliche Cookies erlauben", "Optionale Cookies ablehnen", "Reject"]
}
# Text-Matching-Parameter für Fuzzy-Matching
TEXT_MATCH = {
# Formularfelder
"form_fields": {
"email_phone": ["Handynummer oder E-Mail-Adresse", "Mobile Number or Email",
"Phone number or email", "E-Mail-Adresse oder Telefonnummer"],
"full_name": ["Vollständiger Name", "Full Name", "Name"],
"username": ["Benutzername", "Username"],
"password": ["Passwort", "Password"],
"confirmation_code": ["Bestätigungscode", "Verification code", "Confirmation code", "Code"],
},
# Buttons
"buttons": {
"submit": ["Weiter", "Registrieren", "Next", "Sign up", "Continue", "Anmelden"],
"confirm": ["Bestätigen", "Confirm", "Verify", "Weiter", "Next"],
"next": ["Weiter", "Next", "Continue", "Fortfahren"],
"reject_cookies": ["Ablehnen", "Nur erforderliche", "Reject", "Not now", "Decline",
"Optionale Cookies ablehnen", "Nur notwendige Cookies"],
"accept_cookies": ["Akzeptieren", "Accept", "Allow", "Zulassen", "Alle Cookies akzeptieren"],
"skip": ["Überspringen", "Skip", "Später", "Later", "Nicht jetzt", "Not now"],
},
# Tabs
"tabs": {
"email": ["E-Mail", "Email", "E-mail", "Mail"],
"phone": ["Telefon", "Telefonnummer", "Phone", "Mobile"],
},
# Erfolgs-Indikatoren
"success_indicators": [
"Home", "Startseite", "Feed", "Timeline", "Nachrichten",
"Profil", "Suche", "Entdecken", "Explore"
],
# Fehler-Indikatoren
"error_indicators": [
"Fehler", "Error", "Leider", "Ungültig", "Invalid", "Nicht verfügbar",
"Fehlgeschlagen", "Problem", "Failed", "Nicht möglich", "Bereits verwendet"
],
# 2FA-Indikatoren
"two_fa_indicators": [
"Bestätigungscode", "Verifizierungscode", "Sicherheitscode",
"2-Faktor", "Verification code", "Two-factor", "2FA"
]
}
@classmethod
def birthday_month_option(cls, month_num: int) -> str:
"""
Erstellt einen Selektor für eine bestimmte Monatsoption.
Args:
month_num: Monatsnummer (1-12)
Returns:
str: XPath-Selektor für die Monatsoption
"""
return f"//select[@title='Monat:']/option[@value='{month_num}']"
@classmethod
def birthday_day_option(cls, day_num: int) -> str:
"""
Erstellt einen Selektor für eine bestimmte Tagesoption.
Args:
day_num: Tagesnummer (1-31)
Returns:
str: XPath-Selektor für die Tagesoption
"""
return f"//select[@title='Tag:']/option[@value='{day_num}']"
@classmethod
def birthday_year_option(cls, year: int) -> str:
"""
Erstellt einen Selektor für eine bestimmte Jahresoption.
Args:
year: Jahr (z.B. 1990)
Returns:
str: XPath-Selektor für die Jahresoption
"""
return f"//select[@title='Jahr:']/option[@value='{year}']"
@classmethod
def get_field_labels(cls, field_type: str) -> List[str]:
"""
Gibt die möglichen Bezeichnungen für ein Formularfeld zurück.
Args:
field_type: Typ des Formularfelds (z.B. "email_phone", "full_name")
Returns:
List[str]: Liste mit möglichen Bezeichnungen
"""
return cls.TEXT_MATCH["form_fields"].get(field_type, [])
@classmethod
def get_button_texts(cls, button_type: str) -> List[str]:
"""
Gibt die möglichen Texte für einen Button zurück.
Args:
button_type: Typ des Buttons (z.B. "submit", "confirm")
Returns:
List[str]: Liste mit möglichen Button-Texten
"""
return cls.TEXT_MATCH["buttons"].get(button_type, [])
@classmethod
def get_tab_texts(cls, tab_type: str) -> List[str]:
"""
Gibt die möglichen Texte für einen Tab zurück.
Args:
tab_type: Typ des Tabs (z.B. "email", "phone")
Returns:
List[str]: Liste mit möglichen Tab-Texten
"""
return cls.TEXT_MATCH["tabs"].get(tab_type, [])
@classmethod
def get_success_indicators(cls) -> List[str]:
"""
Gibt die möglichen Texte für Erfolgsindikatoren zurück.
Returns:
List[str]: Liste mit möglichen Erfolgsindikator-Texten
"""
return cls.TEXT_MATCH["success_indicators"]
@classmethod
def get_error_indicators(cls) -> List[str]:
"""
Gibt die möglichen Texte für Fehlerindikatoren zurück.
Returns:
List[str]: Liste mit möglichen Fehlerindikator-Texten
"""
return cls.TEXT_MATCH["error_indicators"]
@classmethod
def get_two_fa_indicators(cls) -> List[str]:
"""
Gibt die möglichen Texte für 2FA-Indikatoren zurück.
Returns:
List[str]: Liste mit möglichen 2FA-Indikator-Texten
"""
return cls.TEXT_MATCH["two_fa_indicators"]

Datei anzeigen

@ -0,0 +1,823 @@
# social_networks/instagram/instagram_ui_helper.py
"""
Instagram-UI-Helper - Hilfsmethoden für die Interaktion mit der Instagram-UI
"""
import logging
import re
import time
from typing import Dict, List, Any, Optional, Tuple, Union, Callable
from .instagram_selectors import InstagramSelectors
from utils.text_similarity import TextSimilarity, fuzzy_find_element, click_fuzzy_button
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("instagram_ui_helper")
class InstagramUIHelper:
"""
Hilfsmethoden für die Interaktion mit der Instagram-Benutzeroberfläche.
Bietet robuste Funktionen zum Finden und Interagieren mit UI-Elementen.
"""
def __init__(self, automation):
"""
Initialisiert den Instagram-UI-Helper.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
# Browser wird direkt von automation verwendet
self.selectors = InstagramSelectors()
# Initialisiere TextSimilarity für Fuzzy-Matching
self.text_similarity = TextSimilarity(default_threshold=0.7)
logger.debug("Instagram-UI-Helper initialisiert")
def _ensure_browser(self) -> bool:
"""
Stellt sicher, dass die Browser-Referenz verfügbar ist.
Returns:
bool: True wenn Browser verfügbar, False sonst
"""
if self.automation.browser is None:
logger.error("Browser-Referenz nicht verfügbar")
return False
return True
def fill_field_fuzzy(self, field_labels: Union[str, List[str]],
value: str, fallback_selector: str = None,
threshold: float = 0.7, timeout: int = 5000) -> bool:
"""
Füllt ein Formularfeld mit Fuzzy-Text-Matching aus.
Args:
field_labels: Bezeichner oder Liste von Bezeichnern des Feldes
value: Einzugebender Wert
fallback_selector: CSS-Selektor für Fallback
threshold: Schwellenwert für die Textähnlichkeit (0-1)
timeout: Zeitlimit für die Suche in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Normalisiere field_labels zu einer Liste
if isinstance(field_labels, str):
field_labels = [field_labels]
# Versuche, das Feld mit Fuzzy-Matching zu finden
element = fuzzy_find_element(
self.automation.browser.page,
field_labels,
selector_type="input",
threshold=threshold,
wait_time=timeout
)
if element:
# Versuche, das Feld zu fokussieren und den Wert einzugeben
element.focus()
time.sleep(0.1)
element.fill("") # Leere das Feld zuerst
time.sleep(0.2)
# Text menschenähnlich eingeben
for char in value:
element.type(char, delay=self.automation.human_behavior.delays["typing_per_char"] * 1000)
time.sleep(0.01)
logger.info(f"Feld mit Fuzzy-Matching gefüllt: {value}")
return True
# Fuzzy-Matching fehlgeschlagen, versuche über Attribute
if fallback_selector:
field_success = self.automation.browser.fill_form_field(fallback_selector, value)
if field_success:
logger.info(f"Feld mit Fallback-Selektor gefüllt: {fallback_selector}")
return True
# Versuche noch alternative Selektoren basierend auf field_labels
for label in field_labels:
# Versuche aria-label Attribut
aria_selector = f"input[aria-label='{label}'], textarea[aria-label='{label}']"
if self.automation.browser.is_element_visible(aria_selector, timeout=1000):
if self.automation.browser.fill_form_field(aria_selector, value):
logger.info(f"Feld über aria-label gefüllt: {label}")
return True
# Versuche placeholder Attribut
placeholder_selector = f"input[placeholder*='{label}'], textarea[placeholder*='{label}']"
if self.automation.browser.is_element_visible(placeholder_selector, timeout=1000):
if self.automation.browser.fill_form_field(placeholder_selector, value):
logger.info(f"Feld über placeholder gefüllt: {label}")
return True
# Versuche name Attribut
name_selector = f"input[name='{label.lower().replace(' ', '')}']"
if self.automation.browser.is_element_visible(name_selector, timeout=1000):
if self.automation.browser.fill_form_field(name_selector, value):
logger.info(f"Feld über name-Attribut gefüllt: {label}")
return True
logger.warning(f"Konnte kein Feld für '{field_labels}' finden oder ausfüllen")
return False
except Exception as e:
logger.error(f"Fehler beim Fuzzy-Ausfüllen des Feldes: {e}")
return False
def click_button_fuzzy(self, button_texts: Union[str, List[str]],
fallback_selector: str = None, threshold: float = 0.7,
timeout: int = 5000) -> bool:
"""
Klickt einen Button mit Fuzzy-Text-Matching.
Args:
button_texts: Text oder Liste von Texten des Buttons
fallback_selector: CSS-Selektor für Fallback
threshold: Schwellenwert für die Textähnlichkeit (0-1)
timeout: Zeitlimit für die Suche in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Normalisiere button_texts zu einer Liste
if isinstance(button_texts, str):
button_texts = [button_texts]
# Logging der Suche
logger.info(f"Suche nach Button mit Texten: {button_texts}")
if not button_texts or button_texts == [[]]:
logger.warning("Leere Button-Text-Liste angegeben!")
return False
# Versuche, den Button mit Fuzzy-Matching zu finden und zu klicken
try:
# Direktes Matching für alle angegebenen Texte
for text in button_texts:
if not text or text == "[]":
continue
# Direkte Selektoren versuchen mit robusten Click-Methoden
button_selector = f"button:has-text('{text}')"
if self.automation.browser.is_element_visible(button_selector, timeout=1000):
logger.info(f"Button mit exaktem Text gefunden: {text}")
# Verwende robuste Click-Strategien für Instagram Anti-Bot Bypass
if hasattr(self.automation.browser, 'robust_click'):
return self.automation.browser.robust_click(button_selector)
else:
return self.automation.browser.click_element(button_selector)
# Alternativer Selektor für Links und andere klickbare Elemente
link_selector = f"a:has-text('{text}')"
if self.automation.browser.is_element_visible(link_selector, timeout=1000):
logger.info(f"Link mit exaktem Text gefunden: {text}")
# Verwende robuste Click-Strategien für Instagram Anti-Bot Bypass
if hasattr(self.automation.browser, 'robust_click'):
return self.automation.browser.robust_click(link_selector)
else:
return self.automation.browser.click_element(link_selector)
# Versuche alle Buttons auf der Seite zu finden und zu prüfen
all_buttons = self.automation.browser.page.query_selector_all("button, [role='button'], a.button, input[type='submit']")
logger.info(f"Gefundene Button-ähnliche Elemente: {len(all_buttons)}")
for button in all_buttons:
button_text = button.inner_text()
if not button_text:
continue
button_text = button_text.strip()
# Remove emojis and special characters for logging to avoid encoding errors
safe_button_text = ''.join(c for c in button_text if ord(c) < 127)[:50]
logger.debug(f"Button-Text: '{safe_button_text}'")
# Prüfe auf Textähnlichkeit
for search_text in button_texts:
if not search_text:
continue
if self.text_similarity.is_similar(search_text, button_text, threshold=threshold):
logger.info(f"Button mit ähnlichem Text gefunden: '{button_text}' (ähnlich zu '{search_text}')")
# Verwende robuste Click-Strategien für Instagram Anti-Bot Bypass
try:
# Versuche zuerst den Element-Click
button.click()
return True
except Exception as click_error:
logger.warning(f"Standard-Click fehlgeschlagen, verwende robuste Methoden: {click_error}")
# Fallback zu robusten Click-Strategien
if hasattr(self.automation.browser, '_strategy_javascript_click'):
# Erstelle Selektor für das Element
try:
# Versuche JavaScript-Click als Fallback
element_id = button.get_attribute('id')
element_class = button.get_attribute('class')
if element_id:
selector = f"#{element_id}"
elif element_class:
selector = f".{element_class.split()[0]}"
else:
# Verwende Text-basierten Selektor
selector = f"button:has-text('{search_text}')"
return self.automation.browser._strategy_javascript_click(selector)
except Exception as js_error:
logger.error(f"JavaScript-Click auch fehlgeschlagen: {js_error}")
return False
logger.warning(f"Kein Button mit Text ähnlich zu '{button_texts}' gefunden")
except Exception as inner_e:
logger.error(f"Innerer Fehler beim Fuzzy-Klicken: {inner_e}")
# Wenn Fuzzy-Matching fehlschlägt, versuche mit fallback_selector + robuste Methoden
if fallback_selector:
logger.info(f"Versuche Fallback-Selektor mit robusten Click-Methoden: {fallback_selector}")
# Zuerst robuste Click-Methoden versuchen
if hasattr(self.automation.browser, 'robust_click'):
if self.automation.browser.robust_click(fallback_selector):
logger.info(f"Button mit Fallback-Selektor und robusten Methoden geklickt: {fallback_selector}")
return True
# Standard-Click als Fallback
if self.automation.browser.click_element(fallback_selector):
logger.info(f"Button mit Fallback-Selektor geklickt: {fallback_selector}")
return True
# Versuche alternative Methoden
# 1. Versuche über aria-label
for text in button_texts:
if not text:
continue
aria_selector = f"button[aria-label*='{text}'], [role='button'][aria-label*='{text}']"
if self.automation.browser.is_element_visible(aria_selector, timeout=1000):
# Versuche robuste Click-Methoden
if hasattr(self.automation.browser, 'robust_click'):
if self.automation.browser.robust_click(aria_selector):
logger.info(f"Button über aria-label mit robusten Methoden geklickt: {text}")
return True
# Standard-Click als Fallback
if self.automation.browser.click_element(aria_selector):
logger.info(f"Button über aria-label geklickt: {text}")
return True
# 2. Versuche über role='button' mit Text
for text in button_texts:
if not text:
continue
xpath_selector = f"//div[@role='button' and contains(., '{text}')]"
if self.automation.browser.is_element_visible(xpath_selector, timeout=1000):
# Versuche robuste Click-Methoden
if hasattr(self.automation.browser, 'robust_click'):
if self.automation.browser.robust_click(xpath_selector):
logger.info(f"Button über role+text mit robusten Methoden geklickt: {text}")
return True
# Standard-Click als Fallback
if self.automation.browser.click_element(xpath_selector):
logger.info(f"Button über role+text geklickt: {text}")
return True
# 3. Versuche über Link-Text
for text in button_texts:
if not text:
continue
link_selector = f"//a[contains(text(), '{text}')]"
if self.automation.browser.is_element_visible(link_selector, timeout=1000):
# Versuche robuste Click-Methoden
if hasattr(self.automation.browser, 'robust_click'):
if self.automation.browser.robust_click(link_selector):
logger.info(f"Link mit passendem Text und robusten Methoden geklickt: {text}")
return True
# Standard-Click als Fallback
if self.automation.browser.click_element(link_selector):
logger.info(f"Link mit passendem Text geklickt: {text}")
return True
# 4. Als letzten Versuch mit robusten Methoden
logger.warning("Kein spezifischer Button gefunden, versuche robuste Click-Methoden auf alle sichtbaren Buttons")
# Versuche alle Buttons mit robusten Methoden
buttons = self.automation.browser.page.query_selector_all("button")
if buttons and len(buttons) > 0:
for i, button in enumerate(buttons):
try:
if button.is_visible():
button_text = button.inner_text()[:30] # Erste 30 Zeichen für Logging
logger.info(f"Versuche robusten Click auf Button {i}: '{button_text}'")
# Robuste Click-Strategien direkt anwenden
success = self._try_robust_click_on_element(button, f"button:nth-child({i+1})")
if success:
logger.info(f"Button erfolgreich mit robusten Methoden geklickt: '{button_text}'")
return True
except Exception as e:
logger.debug(f"Fehler bei Button {i}: {e}")
continue
logger.warning(f"Konnte keinen Button für '{button_texts}' finden oder klicken")
return False
except Exception as e:
logger.error(f"Fehler beim Fuzzy-Klicken des Buttons: {e}")
return False
def _try_robust_click_on_element(self, element, selector_fallback: str) -> bool:
"""
Versucht robuste Click-Strategien auf einem Element.
Args:
element: Das Playwright Element
selector_fallback: Fallback CSS-Selektor für das Element
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Strategie 1: Standard Element Click
try:
element.click(timeout=2000)
return True
except Exception as e:
logger.debug(f"Standard Element Click fehlgeschlagen: {e}")
# Strategie 2: Force Click
try:
element.click(force=True, timeout=2000)
return True
except Exception as e:
logger.debug(f"Force Click fehlgeschlagen: {e}")
# Strategie 3: JavaScript Click via Element
try:
result = self.automation.browser.page.evaluate("""
(element) => {
try {
element.click();
return true;
} catch (e) {
return false;
}
}
""", element)
if result:
return True
except Exception as e:
logger.debug(f"JavaScript Element Click fehlgeschlagen: {e}")
# Strategie 4: Event Dispatch auf Element
try:
result = self.automation.browser.page.evaluate("""
(element) => {
try {
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
element.dispatchEvent(event);
return true;
} catch (e) {
return false;
}
}
""", element)
if result:
return True
except Exception as e:
logger.debug(f"Event Dispatch fehlgeschlagen: {e}")
# Strategie 5: Verwende Browser's robuste Click-Methoden falls verfügbar
if hasattr(self.automation.browser, 'robust_click') and selector_fallback:
try:
if self.automation.browser.robust_click(selector_fallback):
return True
except Exception as e:
logger.debug(f"Browser robust_click fehlgeschlagen: {e}")
# Strategie 6: Focus + Enter
try:
element.focus()
self.automation.browser.page.keyboard.press("Enter")
return True
except Exception as e:
logger.debug(f"Focus + Enter fehlgeschlagen: {e}")
return False
except Exception as e:
logger.error(f"Alle robusten Click-Strategien fehlgeschlagen: {e}")
return False
def check_for_error(self, error_selectors: List[str] = None,
error_texts: List[str] = None) -> Optional[str]:
"""
Überprüft, ob Fehlermeldungen angezeigt werden.
Args:
error_selectors: Liste mit CSS-Selektoren für Fehlermeldungen
error_texts: Liste mit typischen Fehlertexten
Returns:
Optional[str]: Die Fehlermeldung oder None, wenn keine Fehler gefunden wurden
"""
if not self._ensure_browser():
return None
try:
# Standardselektoren verwenden, wenn keine angegeben sind
if error_selectors is None:
error_selectors = [
InstagramSelectors.ERROR_MESSAGE,
"p[class*='error']",
"div[role='alert']",
"span[class*='error']",
".error-message"
]
# Standardfehlertexte verwenden, wenn keine angegeben sind
if error_texts is None:
error_texts = InstagramSelectors.get_error_indicators()
# 1. Nach Fehlerselektoren suchen
for selector in error_selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if element:
error_text = element.text_content()
if error_text and len(error_text.strip()) > 0:
logger.info(f"Fehlermeldung gefunden (Selektor): {error_text.strip()}")
return error_text.strip()
# 2. Alle Texte auf der Seite durchsuchen
page_content = self.automation.browser.page.content()
for error_text in error_texts:
if error_text.lower() in page_content.lower():
# Versuche, den genauen Fehlertext zu extrahieren
matches = re.findall(r'<[^>]*>([^<]*' + re.escape(error_text.lower()) + '[^<]*)<', page_content.lower())
if matches:
full_error = matches[0].strip()
logger.info(f"Fehlermeldung gefunden (Text): {full_error}")
return full_error
else:
logger.info(f"Fehlermeldung gefunden (Allgemein): {error_text}")
return error_text
# 3. Nach weiteren Fehlerelementen suchen
elements = self.automation.browser.page.query_selector_all("p, div, span")
for element in elements:
element_text = element.inner_text()
if not element_text:
continue
element_text = element_text.strip()
# Prüfe Textähnlichkeit mit Fehlertexten
for error_text in error_texts:
if self.text_similarity.is_similar(error_text, element_text, threshold=0.7) or \
self.text_similarity.contains_similar_text(element_text, error_texts, threshold=0.7):
logger.info(f"Fehlermeldung gefunden (Ähnlichkeit): {element_text}")
return element_text
return None
except Exception as e:
logger.error(f"Fehler beim Prüfen auf Fehlermeldungen: {e}")
return None
def check_for_captcha(self) -> bool:
"""
Überprüft, ob ein Captcha angezeigt wird.
Returns:
bool: True wenn Captcha erkannt, False sonst
"""
if not self._ensure_browser():
return False
try:
# Selektoren für Captcha-Erkennung
captcha_selectors = [
InstagramSelectors.CAPTCHA_CONTAINER,
"div[class*='captcha']",
"iframe[src*='captcha']",
"iframe[title*='captcha']",
"iframe[title*='reCAPTCHA']"
]
# Captcha-Texte für textbasierte Erkennung
captcha_texts = [
"captcha", "recaptcha", "sicherheitsüberprüfung", "security check",
"i'm not a robot", "ich bin kein roboter", "verify you're human",
"bestätige, dass du ein mensch bist"
]
# Nach Selektoren suchen
for selector in captcha_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.warning(f"Captcha erkannt (Selektor): {selector}")
return True
# Nach Texten suchen
page_content = self.automation.browser.page.content().lower()
for text in captcha_texts:
if text in page_content:
logger.warning(f"Captcha erkannt (Text): {text}")
return True
return False
except Exception as e:
logger.error(f"Fehler bei der Captcha-Erkennung: {e}")
return False
def check_for_next_steps(self) -> bool:
"""
Überprüft, ob Elemente für weitere Einrichtungsschritte angezeigt werden.
Returns:
bool: True wenn weitere Schritte erkannt, False sonst
"""
if not self._ensure_browser():
return False
try:
# Texte, die auf weitere Einrichtungsschritte hinweisen
next_step_texts = [
"profilbild hinzufügen", "add profile photo",
"freunde finden", "find friends",
"konten folgen", "follow accounts",
"profilbild hochladen", "upload profile picture",
"profil einrichten", "set up your profile",
"willkommen bei instagram", "welcome to instagram",
"personalisieren", "personalize",
"für dich empfohlen", "recommended for you"
]
# Seiteninhalt durchsuchen
page_content = self.automation.browser.page.content().lower()
for text in next_step_texts:
if text in page_content:
logger.info(f"Weitere Einrichtungsschritte erkannt (Text): {text}")
return True
# Nach typischen Buttons für weitere Schritte suchen
next_step_button_texts = [
"überspringen", "skip",
"später", "later",
"folgen", "follow",
"hinzufügen", "add",
"hochladen", "upload",
"weiter", "next",
"fertig", "done"
]
# Textähnlichkeit mit Button-Texten prüfen
elements = self.automation.browser.page.query_selector_all("button, [role='button'], a")
for element in elements:
element_text = element.inner_text()
if not element_text:
continue
element_text = element_text.strip().lower()
for button_text in next_step_button_texts:
if button_text in element_text or \
self.text_similarity.is_similar(button_text, element_text, threshold=0.8):
logger.info(f"Button für weitere Einrichtungsschritte erkannt: {element_text}")
return True
return False
except Exception as e:
logger.error(f"Fehler bei der Erkennung weiterer Einrichtungsschritte: {e}")
return False
def find_element_by_text(self, text: Union[str, List[str]],
element_type: str = "any", threshold: float = 0.7) -> Optional[Any]:
"""
Findet ein Element basierend auf Textähnlichkeit.
Args:
text: Zu suchender Text oder Liste von Texten
element_type: Art des Elements ("button", "link", "input", "any")
threshold: Schwellenwert für die Textähnlichkeit (0-1)
Returns:
Optional[Any]: Das gefundene Element oder None, wenn keines gefunden wurde
"""
if not self._ensure_browser():
return None
try:
# Normalisiere text zu einer Liste
if isinstance(text, str):
text = [text]
# Verwende die Fuzzy-Find-Funktion
element = fuzzy_find_element(
self.automation.browser.page,
text,
selector_type=element_type,
threshold=threshold,
wait_time=5000
)
if element:
logger.info(f"Element mit Text '{text}' gefunden")
return element
logger.warning(f"Kein Element mit Text '{text}' gefunden")
return None
except Exception as e:
logger.error(f"Fehler beim Suchen nach Element mit Text '{text}': {e}")
return None
def is_text_on_page(self, text: Union[str, List[str]], threshold: float = 0.7) -> bool:
"""
Überprüft, ob ein Text auf der Seite vorhanden ist.
Args:
text: Zu suchender Text oder Liste von Texten
threshold: Schwellenwert für die Textähnlichkeit (0-1)
Returns:
bool: True wenn der Text gefunden wurde, False sonst
"""
if not self._ensure_browser():
return False
try:
# Normalisiere text zu einer Liste
if isinstance(text, str):
text = [text]
# Hole den gesamten Seiteninhalt
page_content = self.automation.browser.page.content().lower()
# Direkte Textsuche
for t in text:
if t.lower() in page_content:
logger.info(f"Text '{t}' auf der Seite gefunden (exakte Übereinstimmung)")
return True
# Suche in allen sichtbaren Textelementen mit Ähnlichkeitsvergleich
elements = self.automation.browser.page.query_selector_all("p, h1, h2, h3, h4, h5, h6, span, div, button, a, label")
for element in elements:
element_text = element.inner_text()
if not element_text:
continue
element_text = element_text.strip().lower()
for t in text:
if self.text_similarity.is_similar(t.lower(), element_text, threshold=threshold) or \
self.text_similarity.contains_similar_text(element_text, [t.lower()], threshold=threshold):
logger.info(f"Text '{t}' auf der Seite gefunden (Ähnlichkeit)")
return True
logger.info(f"Text '{text}' nicht auf der Seite gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Suchen nach Text auf der Seite: {e}")
return False
def wait_for_element(self, selectors: Union[str, List[str]],
timeout: int = 10000, check_interval: int = 500) -> Optional[Any]:
"""
Wartet auf das Erscheinen eines Elements.
Args:
selectors: CSS-Selektor oder Liste von Selektoren
timeout: Zeitlimit in Millisekunden
check_interval: Intervall zwischen den Prüfungen in Millisekunden
Returns:
Optional[Any]: Das gefundene Element oder None, wenn die Zeit abgelaufen ist
"""
if not self._ensure_browser():
return None
try:
# Normalisiere selectors zu einer Liste
if isinstance(selectors, str):
selectors = [selectors]
start_time = time.time()
end_time = start_time + (timeout / 1000)
while time.time() < end_time:
for selector in selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=check_interval)
if element:
logger.info(f"Element mit Selektor '{selector}' gefunden")
return element
# Kurze Pause vor der nächsten Prüfung
time.sleep(check_interval / 1000)
logger.warning(f"Zeitüberschreitung beim Warten auf Element mit Selektoren '{selectors}'")
return None
except Exception as e:
logger.error(f"Fehler beim Warten auf Element: {e}")
return None
def is_page_loading(self) -> bool:
"""
Überprüft, ob die Seite noch lädt.
Returns:
bool: True wenn die Seite lädt, False sonst
"""
if not self._ensure_browser():
return False
try:
# Lade-Indikatoren auf Instagram
loading_selectors = [
"div[class*='spinner']",
"div[class*='loading']",
"div[role='progressbar']",
"svg[aria-label='Loading...']",
"svg[aria-label='Wird geladen...']"
]
for selector in loading_selectors:
if self.automation.browser.is_element_visible(selector, timeout=500):
return True
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung des Ladestatus: {e}")
return False
def wait_for_page_load(self, timeout: int = 30000, check_interval: int = 500) -> bool:
"""
Wartet, bis die Seite vollständig geladen ist.
Args:
timeout: Zeitlimit in Millisekunden
check_interval: Intervall zwischen den Prüfungen in Millisekunden
Returns:
bool: True wenn die Seite geladen wurde, False bei Zeitüberschreitung
"""
if not self._ensure_browser():
return False
try:
# Warten auf Netzwerk-Idle
self.automation.browser.page.wait_for_load_state("networkidle", timeout=timeout)
# Zusätzlich auf das Verschwinden der Ladeindikatoren warten
start_time = time.time()
end_time = start_time + (timeout / 1000)
while time.time() < end_time:
if not self.is_page_loading():
# Noch eine kurze Pause für Animationen
time.sleep(0.5)
logger.info("Seite vollständig geladen")
return True
# Kurze Pause vor der nächsten Prüfung
time.sleep(check_interval / 1000)
logger.warning("Zeitüberschreitung beim Warten auf das Laden der Seite")
return False
except Exception as e:
logger.error(f"Fehler beim Warten auf das Laden der Seite: {e}")
return False

Datei anzeigen

@ -0,0 +1,547 @@
# social_networks/instagram/instagram_utils.py
"""
Instagram-Utils - Hilfsfunktionen für die Instagram-Automatisierung
"""
import logging
import time
import re
import random
from typing import Dict, List, Any, Optional, Tuple, Union
from .instagram_selectors import InstagramSelectors
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("instagram_utils")
class InstagramUtils:
"""
Hilfsfunktionen für die Instagram-Automatisierung.
Enthält allgemeine Hilfsmethoden und kleinere Funktionen.
"""
def __init__(self, automation):
"""
Initialisiert die Instagram-Utils.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
# Browser wird direkt von automation verwendet
self.selectors = InstagramSelectors()
logger.debug("Instagram-Utils initialisiert")
def _ensure_browser(self) -> bool:
"""
Stellt sicher, dass die Browser-Referenz verfügbar ist.
Returns:
bool: True wenn Browser verfügbar, False sonst
"""
if self.automation.browser is None:
logger.error("Browser-Referenz nicht verfügbar")
return False
return True
def handle_cookie_banner(self) -> bool:
"""
Behandelt den Cookie-Banner, falls angezeigt.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Cookie-Dialog-Erkennung
if self.automation.browser.is_element_visible(InstagramSelectors.COOKIE_DIALOG, timeout=2000):
logger.info("Cookie-Banner erkannt")
# Ablehnen-Button suchen und klicken
reject_success = self.automation.ui_helper.click_button_fuzzy(
InstagramSelectors.get_button_texts("reject_cookies"),
InstagramSelectors.COOKIE_REJECT_BUTTON
)
if reject_success:
logger.info("Cookie-Banner erfolgreich abgelehnt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
else:
logger.warning("Konnte Cookie-Banner nicht ablehnen, versuche zu akzeptieren")
# Akzeptieren-Button als Fallback
accept_success = self.automation.browser.click_element(InstagramSelectors.COOKIE_ACCEPT_BUTTON)
if accept_success:
logger.info("Cookie-Banner erfolgreich akzeptiert")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
else:
logger.error("Konnte Cookie-Banner weder ablehnen noch akzeptieren")
return False
else:
logger.debug("Kein Cookie-Banner erkannt")
return True
except Exception as e:
logger.error(f"Fehler beim Behandeln des Cookie-Banners: {e}")
return False
def is_logged_in(self) -> bool:
"""
Überprüft, ob der Benutzer bei Instagram angemeldet ist.
Returns:
bool: True wenn angemeldet, False sonst
"""
if not self._ensure_browser():
return False
try:
# Login-Status über verschiedene Indikatoren prüfen
# 1. URL prüfen
current_url = self.automation.browser.page.url
if "/accounts/login" in current_url:
logger.debug("Nicht angemeldet (Auf Login-Seite)")
return False
# 2. Erfolgs-Indikatoren prüfen
success_indicators = InstagramSelectors.SUCCESS_INDICATORS
for indicator in success_indicators:
if self.automation.browser.is_element_visible(indicator, timeout=1000):
logger.debug(f"Angemeldet (Indikator: {indicator})")
return True
# 3. Profil-Icon prüfen
profile_selectors = [
"img[data-testid='user-avatar']",
"span[role='link'][aria-label*='profil']",
"span[role='link'][aria-label*='profile']"
]
for selector in profile_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
logger.debug(f"Angemeldet (Profil-Icon sichtbar: {selector})")
return True
# 4. Login-Formular prüfen (umgekehrte Logik)
login_selectors = [
InstagramSelectors.LOGIN_USERNAME_FIELD,
InstagramSelectors.LOGIN_PASSWORD_FIELD
]
for selector in login_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
logger.debug(f"Nicht angemeldet (Login-Formular sichtbar: {selector})")
return False
# Wenn wir hier ankommen, können wir den Status nicht eindeutig bestimmen
# Wir gehen davon aus, dass wir nicht angemeldet sind
logger.debug("Login-Status konnte nicht eindeutig bestimmt werden, nehme 'nicht angemeldet' an")
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung des Login-Status: {e}")
return False
def check_for_captcha(self) -> bool:
"""
Überprüft, ob ein Captcha angezeigt wird.
Returns:
bool: True wenn Captcha erkannt, False sonst
"""
if not self._ensure_browser():
return False
try:
# Delegiere an UI-Helper
return self.automation.ui_helper.check_for_captcha()
except Exception as e:
logger.error(f"Fehler bei der Captcha-Erkennung: {e}")
return False
def handle_rate_limiting(self, rotate_proxy: bool = True) -> bool:
"""
Behandelt eine Rate-Limiting-Situation.
Args:
rotate_proxy: Ob der Proxy rotiert werden soll
Returns:
bool: True wenn erfolgreich behandelt, False sonst
"""
if not self._ensure_browser():
return False
try:
logger.warning("Rate-Limiting erkannt, warte und versuche es erneut")
# Screenshot erstellen
self.automation._take_screenshot("rate_limit_detected")
# Proxy rotieren, falls gewünscht
if rotate_proxy and self.automation.use_proxy:
success = self.automation._rotate_proxy()
if not success:
logger.warning("Konnte Proxy nicht rotieren")
# Längere Wartezeit
wait_time = random.uniform(120, 300) # 2-5 Minuten
logger.info(f"Warte {wait_time:.1f} Sekunden vor dem nächsten Versuch")
time.sleep(wait_time)
# Seite neuladen
self.automation.browser.page.reload()
self.automation.human_behavior.wait_for_page_load()
# Prüfen, ob Rate-Limiting noch aktiv ist
rate_limit_texts = [
"bitte warte einige minuten",
"please wait a few minutes",
"try again later",
"versuche es später erneut",
"zu viele anfragen",
"too many requests"
]
page_content = self.automation.browser.page.content().lower()
still_rate_limited = False
for text in rate_limit_texts:
if text in page_content:
still_rate_limited = True
break
if still_rate_limited:
logger.warning("Immer noch Rate-Limited nach dem Warten")
return False
else:
logger.info("Rate-Limiting scheint aufgehoben zu sein")
return True
except Exception as e:
logger.error(f"Fehler bei der Behandlung des Rate-Limitings: {e}")
return False
def scroll_page(self, direction: str = "down", amount: int = 3) -> bool:
"""
Scrollt die Seite.
Args:
direction: "up" oder "down"
amount: Scroll-Menge (1=wenig, 5=viel)
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Scroll-Faktor je nach Richtung
scroll_factor = 1 if direction == "down" else -1
# Scroll-Menge in Pixeln
pixel_amount = amount * 300 * scroll_factor
# Scroll ausführen
self.automation.browser.page.evaluate(f"window.scrollBy(0, {pixel_amount})")
# Menschliche Verzögerung
self.automation.human_behavior.random_delay(0.5, 1.0)
logger.debug(f"Seite ge{direction}scrollt um {abs(pixel_amount)} Pixel")
return True
except Exception as e:
logger.error(f"Fehler beim Scrollen der Seite: {e}")
return False
def extract_username_from_url(self, url: str) -> Optional[str]:
"""
Extrahiert den Benutzernamen aus einer Instagram-URL.
Args:
url: Die Instagram-URL
Returns:
Optional[str]: Der extrahierte Benutzername oder None
"""
try:
# Muster für Profil-URLs
patterns = [
r'instagram\.com/([a-zA-Z0-9._]+)/?(?:$|\?|#)',
r'instagram\.com/([a-zA-Z0-9._]+)/(?:saved|tagged|reels)/?',
r'instagram\.com/p/[^/]+/(?:by|from)/([a-zA-Z0-9._]+)/?'
]
for pattern in patterns:
match = re.search(pattern, url)
if match:
username = match.group(1)
# Einige Ausnahmen filtern
if username not in ["explore", "accounts", "p", "reel", "stories", "direct"]:
return username
return None
except Exception as e:
logger.error(f"Fehler beim Extrahieren des Benutzernamens aus der URL: {e}")
return None
def get_current_username(self) -> Optional[str]:
"""
Versucht, den Benutzernamen des aktuell angemeldeten Kontos zu ermitteln.
Returns:
Optional[str]: Der Benutzername oder None, wenn nicht gefunden
"""
if not self._ensure_browser():
return None
try:
# Verschiedene Methoden zur Erkennung des Benutzernamens
# 1. Benutzername aus URL des Profils
profile_link_selectors = [
"a[href*='/profile/']",
"a[href*='instagram.com/'][role='link']",
"a[href*='instagram.com/']:not([href*='/explore/']):not([href*='/direct/'])"
]
for selector in profile_link_selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if element:
href = element.get_attribute("href")
if href:
username = self.extract_username_from_url(href)
if username:
logger.info(f"Benutzername aus Profil-Link ermittelt: {username}")
return username
# 2. Benutzername aus aria-label Attributen
profile_aria_selectors = [
"img[data-testid='user-avatar']",
"span[role='link'][aria-label*='profil']",
"span[role='link'][aria-label*='profile']"
]
for selector in profile_aria_selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if element:
aria_label = element.get_attribute("aria-label")
if aria_label:
# Verschiedene Formate: "profil von username", "username's profile"
for pattern in [r'profil von ([a-zA-Z0-9._]+)', r"([a-zA-Z0-9._]+)'s profile"]:
match = re.search(pattern, aria_label, re.IGNORECASE)
if match:
username = match.group(1)
logger.info(f"Benutzername aus aria-label ermittelt: {username}")
return username
# 3. Fallback: Zur Profilseite navigieren und aus URL extrahieren
# Dies sollte nur gemacht werden, wenn ein wichtiger Grund dafür besteht
# und kein anderer Weg verfügbar ist, den Benutzernamen zu ermitteln
logger.warning("Konnte Benutzernamen nicht ermitteln")
return None
except Exception as e:
logger.error(f"Fehler bei der Ermittlung des Benutzernamens: {e}")
return None
def is_account_private(self) -> Optional[bool]:
"""
Überprüft, ob das aktuelle Konto privat ist.
Returns:
Optional[bool]: True wenn privat, False wenn öffentlich, None wenn nicht erkennbar
"""
if not self._ensure_browser():
return None
try:
# Texte, die auf ein privates Konto hinweisen
private_indicators = [
"this account is private",
"dieses konto ist privat",
"must be following",
"musst diesem konto folgen"
]
# Privat-Icon suchen
private_icon_selectors = [
"svg[aria-label='Private Account']",
"svg[aria-label='Privates Konto']"
]
for selector in private_icon_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
logger.info("Konto ist privat (Icon gefunden)")
return True
# Texte prüfen
page_content = self.automation.browser.page.content().lower()
for indicator in private_indicators:
if indicator in page_content:
logger.info(f"Konto ist privat (Text gefunden: {indicator})")
return True
# Wenn keine Privat-Indikatoren gefunden wurden, gehen wir davon aus, dass das Konto öffentlich ist
# Dies ist jedoch nicht 100% sicher
logger.debug("Konto scheint öffentlich zu sein")
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung des Privat-Status: {e}")
return None
def count_followers(self) -> Optional[int]:
"""
Versucht, die Anzahl der Follower des aktuellen Kontos zu ermitteln.
Returns:
Optional[int]: Die Anzahl der Follower oder None, wenn nicht ermittelbar
"""
if not self._ensure_browser():
return None
try:
# Selektoren für Follower-Zähler
follower_selectors = [
"a[href*='/followers/'] span",
"a[href*='/followers/']",
"ul li:nth-child(2) span" # Typisches Layout auf Profilseiten
]
for selector in follower_selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if element:
follower_text = element.inner_text()
if follower_text:
# Zahlen extrahieren (möglicherweise mit K, M, B für Tausend, Million, Milliarde)
follower_text = follower_text.strip().replace(',', '').replace('.', '')
# Direkter Zahlenwert
if follower_text.isdigit():
return int(follower_text)
# Wert mit K (Tausend)
if 'k' in follower_text.lower():
value = float(follower_text.lower().replace('k', '')) * 1000
return int(value)
# Wert mit M (Million)
if 'm' in follower_text.lower():
value = float(follower_text.lower().replace('m', '')) * 1000000
return int(value)
# Wert mit B (Milliarde)
if 'b' in follower_text.lower():
value = float(follower_text.lower().replace('b', '')) * 1000000000
return int(value)
logger.warning("Konnte Follower-Anzahl nicht ermitteln")
return None
except Exception as e:
logger.error(f"Fehler bei der Ermittlung der Follower-Anzahl: {e}")
return None
def get_account_stats(self) -> Dict[str, Any]:
"""
Sammelt verfügbare Statistiken zum aktuellen Konto.
Returns:
Dict[str, Any]: Konto-Statistiken
"""
if not self._ensure_browser():
return {}
try:
stats = {}
# Benutzername ermitteln
username = self.get_current_username()
if username:
stats["username"] = username
# Privat-Status prüfen
is_private = self.is_account_private()
if is_private is not None:
stats["is_private"] = is_private
# Follower-Anzahl
followers = self.count_followers()
if followers is not None:
stats["followers"] = followers
# Optional: Weitere Statistiken sammeln
# ...
return stats
except Exception as e:
logger.error(f"Fehler beim Sammeln der Konto-Statistiken: {e}")
return {}
def check_login_banned(self) -> Tuple[bool, Optional[str]]:
"""
Überprüft, ob der Login gesperrt wurde.
Returns:
Tuple[bool, Optional[str]]: (Gesperrt, Fehlermeldung falls vorhanden)
"""
if not self._ensure_browser():
return False, None
try:
# Texte, die auf eine Sperrung hinweisen
ban_indicators = [
"your account has been disabled",
"dein konto wurde deaktiviert",
"your account has been locked",
"dein konto wurde gesperrt",
"suspicious activity",
"verdächtige aktivität",
"we've detected suspicious activity",
"wir haben verdächtige aktivitäten festgestellt",
"your account has been temporarily locked",
"dein konto wurde vorübergehend gesperrt"
]
# Seiteninhalt durchsuchen
page_content = self.automation.browser.page.content().lower()
for indicator in ban_indicators:
if indicator in page_content:
# Vollständigen Text der Fehlermeldung extrahieren
error_element = self.automation.browser.wait_for_selector("p[data-testid='login-error-message'], div[role='alert'], p[class*='error']", timeout=2000)
error_message = None
if error_element:
error_message = error_element.inner_text().strip()
if not error_message:
error_message = f"Konto gesperrt oder eingeschränkt (Indikator: {indicator})"
logger.warning(f"Konto-Sperrung erkannt: {error_message}")
return True, error_message
return False, None
except Exception as e:
logger.error(f"Fehler bei der Überprüfung auf Konto-Sperrung: {e}")
return False, None

Datei anzeigen

@ -0,0 +1,492 @@
# social_networks/instagram/instagram_verification.py
"""
Instagram-Verifizierung - Klasse für die Verifizierungsfunktionalität bei Instagram
"""
import logging
import time
import re
from typing import Dict, List, Any, Optional, Tuple
from .instagram_selectors import InstagramSelectors
from .instagram_workflow import InstagramWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("instagram_verification")
class InstagramVerification:
"""
Klasse für die Verifizierung von Instagram-Konten.
Enthält alle Methoden für den Verifizierungsprozess.
"""
def __init__(self, automation):
"""
Initialisiert die Instagram-Verifizierung.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
# Browser wird direkt von automation verwendet
self.selectors = InstagramSelectors()
self.workflow = InstagramWorkflow.get_verification_workflow()
logger.debug("Instagram-Verifizierung initialisiert")
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
"""
Führt den Verifizierungsprozess für ein Instagram-Konto durch.
Args:
verification_code: Der Bestätigungscode
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Verifizierung mit Status
"""
# Browser wird direkt von automation verwendet
# Validiere den Verifizierungscode
if not self._validate_verification_code(verification_code):
return {
"success": False,
"error": "Ungültiger Verifizierungscode",
"stage": "code_validation"
}
try:
# 1. Überprüfen, ob wir auf der Verifizierungsseite sind
if not self._is_on_verification_page():
# Versuche, zur Verifizierungsseite zu navigieren, falls möglich
# Direktnavigation ist jedoch normalerweise nicht möglich
return {
"success": False,
"error": "Nicht auf der Verifizierungsseite",
"stage": "page_check"
}
# 2. Verifizierungscode eingeben und absenden
if not self.enter_and_submit_verification_code(verification_code):
return {
"success": False,
"error": "Fehler beim Eingeben oder Absenden des Verifizierungscodes",
"stage": "code_entry"
}
# 3. Überprüfen, ob die Verifizierung erfolgreich war
success, error_message = self._check_verification_success()
if not success:
return {
"success": False,
"error": f"Verifizierung fehlgeschlagen: {error_message or 'Unbekannter Fehler'}",
"stage": "verification_check"
}
# 4. Zusätzliche Dialoge behandeln
self._handle_post_verification_dialogs()
# Verifizierung erfolgreich
logger.info("Instagram-Verifizierung erfolgreich abgeschlossen")
return {
"success": True,
"stage": "completed"
}
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der Instagram-Verifizierung: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"stage": "exception"
}
def _validate_verification_code(self, verification_code: str) -> bool:
"""
Validiert den Verifizierungscode.
Args:
verification_code: Der zu validierende Code
Returns:
bool: True wenn der Code gültig ist, False sonst
"""
# Leerer Code
if not verification_code:
logger.error("Verifizierungscode ist leer")
return False
# Code-Format prüfen (normalerweise 6-stellige Zahl)
if not re.match(r"^\d{6}$", verification_code):
logger.warning(f"Verifizierungscode hat unerwartetes Format: {verification_code}")
# Wir geben trotzdem True zurück, da einige Codes andere Formate haben könnten
return True
return True
def _is_on_verification_page(self) -> bool:
"""
Überprüft, ob wir auf der Verifizierungsseite sind.
Returns:
bool: True wenn auf der Verifizierungsseite, False sonst
"""
try:
# Screenshot erstellen
self.automation._take_screenshot("verification_page_check")
# Nach Verifizierungsfeld suchen
verification_selectors = [
InstagramSelectors.CONFIRMATION_CODE_FIELD,
InstagramSelectors.ALT_CONFIRMATION_CODE_FIELD,
"input[name='confirmationCode']",
"input[aria-label='Bestätigungscode']",
"input[aria-label='Confirmation code']",
"input[aria-label='Verification code']"
]
for selector in verification_selectors:
if self.automation.browser.is_element_visible(selector, timeout=3000):
logger.info("Auf Verifizierungsseite")
return True
# Textbasierte Erkennung
verification_texts = [
"Bestätigungscode",
"Confirmation code",
"Verification code",
"Sicherheitscode",
"Security code",
"Enter the code we sent to"
]
page_content = self.automation.browser.page.content().lower()
for text in verification_texts:
if text.lower() in page_content:
logger.info(f"Auf Verifizierungsseite (erkannt durch Text: {text})")
return True
logger.warning("Nicht auf der Verifizierungsseite")
return False
except Exception as e:
logger.error(f"Fehler beim Überprüfen der Verifizierungsseite: {e}")
return False
def enter_and_submit_verification_code(self, verification_code: str) -> bool:
"""
Gibt den Verifizierungscode ein und sendet ihn ab.
Args:
verification_code: Der einzugebende Verifizierungscode
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.info(f"Versuche Verifizierungscode einzugeben: {verification_code}")
self.automation._emit_customer_log("🔐 Bestätigungscode wird eingegeben...")
# Mögliche Selektoren für das Verifizierungscode-Feld
code_field_selectors = [
InstagramSelectors.CONFIRMATION_CODE_FIELD,
InstagramSelectors.ALT_CONFIRMATION_CODE_FIELD,
"input[name='email_confirmation_code']",
"input[name='confirmationCode']",
"input[aria-label='Bestätigungscode']",
"input[aria-label='Confirmation code']",
"input[placeholder*='code']",
"input[placeholder='Bestätigungscode']"
]
# Versuche, das Feld zu finden und auszufüllen
code_field_found = False
for selector in code_field_selectors:
logger.debug(f"Versuche Selektor: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Codefeld gefunden mit Selektor: {selector}")
if self.automation.browser.fill_form_field(selector, verification_code):
code_field_found = True
logger.info(f"Verifizierungscode eingegeben: {verification_code}")
break
# Versuche es mit der Fuzzy-Matching-Methode, wenn direkte Selektoren fehlschlagen
if not code_field_found:
logger.info("Versuche Fuzzy-Matching für Codefeld")
code_field_found = self.automation.ui_helper.fill_field_fuzzy(
["Bestätigungscode", "Confirmation code", "Verification code", "Code"],
verification_code
)
if not code_field_found:
logger.error("Konnte Verifizierungscode-Feld nicht finden oder ausfüllen")
# Erstelle einen Screenshot zum Debuggen
self.automation._take_screenshot("code_field_not_found")
return False
# Menschliche Verzögerung vor dem Absenden
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot erstellen
self.automation._take_screenshot("verification_code_entered")
# Absenden-Button finden und klicken
submit_button_selectors = [
"button[type='submit']",
InstagramSelectors.CONFIRMATION_BUTTON,
"//button[contains(text(), 'Bestätigen')]",
"//button[contains(text(), 'Confirm')]",
"//button[contains(text(), 'Verify')]",
"//button[contains(text(), 'Next')]",
"//button[contains(text(), 'Weiter')]",
"button" # Falls alle anderen fehlschlagen, probiere jeden Button
]
submit_button_found = False
logger.info("Suche nach Submit-Button")
for selector in submit_button_selectors:
logger.debug(f"Versuche Submit-Button-Selektor: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Submit-Button gefunden mit Selektor: {selector}")
if self.automation.browser.click_element(selector):
submit_button_found = True
logger.info("Verifizierungscode-Formular abgesendet")
break
# Versuche es mit der Fuzzy-Matching-Methode, wenn direkte Selektoren fehlschlagen
if not submit_button_found:
logger.info("Versuche Fuzzy-Matching für Submit-Button")
weiter_buttons = ["Weiter", "Next", "Continue", "Bestätigen", "Confirm", "Submit", "Verify", "Senden"]
submit_button_found = self.automation.ui_helper.click_button_fuzzy(
weiter_buttons
)
if not submit_button_found:
# Erstelle einen Screenshot zum Debuggen
self.automation._take_screenshot("submit_button_not_found")
# Versuche es mit Enter-Taste als letzten Ausweg
logger.info("Konnte Submit-Button nicht finden, versuche Enter-Taste")
self.automation.browser.page.keyboard.press("Enter")
logger.info("Enter-Taste zur Bestätigung des Verifizierungscodes gedrückt")
submit_button_found = True
# Warten nach dem Absenden
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
return submit_button_found
except Exception as e:
logger.error(f"Fehler beim Eingeben und Absenden des Verifizierungscodes: {e}")
return False
def _check_verification_success(self) -> Tuple[bool, Optional[str]]:
"""
Überprüft, ob die Verifizierung erfolgreich war.
Returns:
Tuple[bool, Optional[str]]: (Erfolg, Fehlermeldung falls vorhanden)
"""
try:
# Warten nach der Verifizierung
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
# Screenshot erstellen
self.automation._take_screenshot("verification_result")
# Immer noch auf der Verifizierungsseite?
still_on_verification = self._is_on_verification_page()
if still_on_verification:
# Fehlermeldung suchen
error_message = self.automation.ui_helper.check_for_error(
error_selectors=[InstagramSelectors.ERROR_MESSAGE],
error_texts=InstagramSelectors.get_error_indicators()
)
if error_message:
logger.error(f"Verifizierungsfehler: {error_message}")
return False, error_message
else:
logger.error("Verifizierung fehlgeschlagen, immer noch auf der Verifizierungsseite")
return False, "Immer noch auf der Verifizierungsseite"
# Erfolg anhand verschiedener Indikatoren prüfen
current_url = self.automation.browser.page.url
# Wenn wir auf der Startseite sind
if "instagram.com" in current_url and "/accounts/" not in current_url:
logger.info("Verifizierung erfolgreich, jetzt auf der Startseite")
return True, None
# Prüfe auf weitere Einrichtungsschritte (auch ein Erfolgszeichen)
setup_indicators = [
"add profile photo",
"profilbild hinzufügen",
"find friends",
"freunde finden",
"follow accounts",
"konten folgen",
"set up your profile",
"einrichten deines profils"
]
page_content = self.automation.browser.page.content().lower()
for indicator in setup_indicators:
if indicator in page_content:
logger.info(f"Verifizierung erfolgreich, jetzt bei weiteren Einrichtungsschritten: {indicator}")
return True, None
# Erfolg anhand von UI-Elementen prüfen
success_indicators = InstagramSelectors.SUCCESS_INDICATORS
for indicator in success_indicators:
if self.automation.browser.is_element_visible(indicator, timeout=2000):
logger.info(f"Verifizierung erfolgreich, Erfolgsindikator gefunden: {indicator}")
return True, None
# Wenn keine eindeutigen Indikatoren gefunden wurden, aber auch keine Fehler
logger.warning("Keine eindeutigen Erfolgsindikatoren für die Verifizierung gefunden")
return True, None # Wir gehen davon aus, dass es erfolgreich war
except Exception as e:
logger.error(f"Fehler beim Überprüfen des Verifizierungserfolgs: {e}")
return False, f"Fehler bei der Erfolgsprüfung: {str(e)}"
def _handle_post_verification_dialogs(self) -> None:
"""
Behandelt Dialoge, die nach erfolgreicher Verifizierung erscheinen können.
"""
try:
# Liste der möglichen Dialoge und wie man sie überspringt
dialogs_to_handle = [
{
"name": "profile_photo",
"skip_texts": ["Überspringen", "Skip"],
"skip_selectors": ["//button[contains(text(), 'Überspringen')]", "//button[contains(text(), 'Skip')]"]
},
{
"name": "find_friends",
"skip_texts": ["Nicht jetzt", "Später", "Not now", "Later"],
"skip_selectors": ["//button[contains(text(), 'Nicht jetzt')]", "//button[contains(text(), 'Not now')]"]
},
{
"name": "follow_accounts",
"skip_texts": ["Überspringen", "Skip"],
"skip_selectors": ["//button[contains(text(), 'Überspringen')]", "//button[contains(text(), 'Skip')]"]
},
{
"name": "save_login_info",
"skip_texts": ["Nicht jetzt", "Not now"],
"skip_selectors": ["//button[contains(text(), 'Nicht jetzt')]", "//button[contains(text(), 'Not now')]"]
},
{
"name": "notifications",
"skip_texts": ["Nicht jetzt", "Not now"],
"skip_selectors": ["//button[contains(text(), 'Nicht jetzt')]", "//button[contains(text(), 'Not now')]"]
}
]
# Versuche, jeden möglichen Dialog zu behandeln
for dialog in dialogs_to_handle:
self._try_skip_dialog(dialog)
logger.info("Nachverifikations-Dialoge behandelt")
except Exception as e:
logger.warning(f"Fehler beim Behandeln der Nachverifikations-Dialoge: {e}")
# Nicht kritisch, daher keine Fehlerbehandlung
def _try_skip_dialog(self, dialog: Dict[str, Any]) -> bool:
"""
Versucht, einen bestimmten Dialog zu überspringen.
Args:
dialog: Informationen zum Dialog
Returns:
bool: True wenn Dialog gefunden und übersprungen, False sonst
"""
try:
# Zuerst mit Fuzzy-Matching versuchen
skip_clicked = self.automation.ui_helper.click_button_fuzzy(
dialog["skip_texts"],
threshold=0.7,
timeout=3000
)
if skip_clicked:
logger.info(f"Dialog '{dialog['name']}' mit Fuzzy-Matching übersprungen")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
# Wenn Fuzzy-Matching fehlschlägt, direkte Selektoren versuchen
for selector in dialog["skip_selectors"]:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info(f"Dialog '{dialog['name']}' mit direktem Selektor übersprungen")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
return False
except Exception as e:
logger.warning(f"Fehler beim Versuch, Dialog '{dialog['name']}' zu überspringen: {e}")
return False
def resend_verification_code(self) -> bool:
"""
Versucht, den Verifizierungscode erneut senden zu lassen.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Resend-Button suchen und klicken
resend_selectors = [
InstagramSelectors.RESEND_CODE_BUTTON,
"//button[contains(text(), 'Code erneut senden')]",
"//button[contains(text(), 'Resend code')]",
"//button[contains(text(), 'Send again')]",
"//button[contains(text(), 'Noch einmal senden')]",
"a[href*='resend']"
]
for selector in resend_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
if self.automation.browser.click_element(selector):
logger.info("Code erneut angefordert")
self.automation.human_behavior.wait_between_actions("decision", 1.5)
return True
# Fuzzy-Matching versuchen
resend_texts = ["Code erneut senden", "Resend code", "Send again", "Noch einmal senden"]
resend_clicked = self.automation.ui_helper.click_button_fuzzy(
resend_texts,
threshold=0.7,
timeout=3000
)
if resend_clicked:
logger.info("Code erneut angefordert (über Fuzzy-Matching)")
self.automation.human_behavior.wait_between_actions("decision", 1.5)
return True
logger.warning("Konnte keinen 'Code erneut senden'-Button finden")
return False
except Exception as e:
logger.error(f"Fehler beim erneuten Anfordern des Codes: {e}")
return False

Datei anzeigen

@ -0,0 +1,455 @@
"""
Instagram-Workflow - Definiert die Schritte für die Instagram-Anmeldung und -Registrierung
Mit TextSimilarity-Integration für robusteres Element-Matching
"""
import logging
from typing import Dict, List, Any, Optional, Tuple
import re
from utils.text_similarity import TextSimilarity
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("instagram_workflow")
class InstagramWorkflow:
"""
Definiert die Workflow-Schritte für verschiedene Instagram-Aktionen
wie Registrierung, Anmeldung und Verifizierung.
Enthält TextSimilarity-Integration für robusteres Element-Matching.
"""
# Text-Ähnlichkeits-Threshold für Fuzzy-Matching
SIMILARITY_THRESHOLD = 0.7
# Initialisiere TextSimilarity für Matching
text_similarity = TextSimilarity(default_threshold=SIMILARITY_THRESHOLD)
# Mögliche alternative Texte für verschiedene UI-Elemente
TEXT_ALTERNATIVES = {
"email": ["E-Mail", "Email", "E-mail", "Mail", "email"],
"phone": ["Telefon", "Telefonnummer", "Phone", "Mobile", "mobile"],
"fullname": ["Vollständiger Name", "Full Name", "Name", "full name"],
"username": ["Benutzername", "Username", "user name"],
"password": ["Passwort", "Password", "pass"],
"submit": ["Registrieren", "Sign up", "Anmelden", "Login", "Log in", "Submit"],
"next": ["Weiter", "Next", "Continue", "Fortfahren"],
"confirm": ["Bestätigen", "Confirm", "Verify", "Verifizieren"],
"reject_cookies": ["Ablehnen", "Nur erforderliche", "Decline", "Reject", "Only necessary"],
"skip": ["Überspringen", "Skip", "Later", "Später", "Not now", "Nicht jetzt"]
}
@staticmethod
def get_registration_workflow(registration_method: str = "email") -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die Instagram-Registrierung zurück.
Args:
registration_method: "email" oder "phone"
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
# Basisschritte für beide Methoden
common_steps = [
{
"name": "navigate_to_signup",
"description": "Zur Registrierungsseite navigieren",
"url": "https://www.instagram.com/accounts/emailsignup/",
"wait_for": ["input[name='emailOrPhone']", "div[role='dialog']"],
"fuzzy_match": None # Kein Fuzzy-Matching für die Navigation
},
{
"name": "handle_cookie_banner",
"description": "Cookie-Banner behandeln",
"action": "click",
"target": "//button[contains(text(), 'Ablehnen') or contains(text(), 'Nur erforderliche') or contains(text(), 'Reject')]",
"optional": True,
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["reject_cookies"] # Fuzzy-Matching für Cookie-Ablehnung
}
]
# Registrierungsmethode wechseln, falls nötig
method_steps = []
if registration_method == "phone":
method_steps.append({
"name": "switch_to_phone",
"description": "Zur Telefon-Registrierungsmethode wechseln",
"action": "click",
"target": "//button[contains(text(), 'Telefon') or contains(text(), 'Phone')]",
"wait_for": ["input[name='emailOrPhone']"],
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["phone"] # Fuzzy-Matching für Telefon-Tab
})
elif registration_method == "email":
method_steps.append({
"name": "switch_to_email",
"description": "Zur E-Mail-Registrierungsmethode wechseln",
"action": "click",
"target": "//button[contains(text(), 'E-Mail') or contains(text(), 'Email')]",
"wait_for": ["input[name='emailOrPhone']"],
"optional": True, # Meist Standard
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["email"] # Fuzzy-Matching für E-Mail-Tab
})
# Formularausfüllung für E-Mail/Telefon
form_steps = [
{
"name": "fill_email_or_phone",
"description": f"E-Mail/Telefon eingeben ({registration_method})",
"action": "fill",
"target": "input[name='emailOrPhone']",
"value": "{EMAIL_OR_PHONE}",
"fuzzy_match": ["Handynummer oder E-Mail-Adresse", "Mobile Number or Email",
"Phone number or email", "E-Mail-Adresse oder Telefonnummer"]
},
{
"name": "fill_fullname",
"description": "Vollständigen Namen eingeben",
"action": "fill",
"target": "input[name='fullName']",
"value": "{FULL_NAME}",
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["fullname"]
},
{
"name": "fill_username",
"description": "Benutzernamen eingeben",
"action": "fill",
"target": "input[name='username']",
"value": "{USERNAME}",
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["username"]
},
{
"name": "fill_password",
"description": "Passwort eingeben",
"action": "fill",
"target": "input[name='password']",
"value": "{PASSWORD}",
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["password"]
},
{
"name": "submit_form",
"description": "Formular absenden",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["select[title='Monat:']", "select[title='Tag:']", "select[title='Jahr:']"],
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["submit"]
}
]
# Geburtsdatumschritte
birthday_steps = [
{
"name": "select_month",
"description": "Monat auswählen",
"action": "select",
"target": "select[title='Monat:']",
"value": "{MONTH}",
"fuzzy_match": ["Monat", "Month"]
},
{
"name": "select_day",
"description": "Tag auswählen",
"action": "select",
"target": "select[title='Tag:']",
"value": "{DAY}",
"fuzzy_match": ["Tag", "Day"]
},
{
"name": "select_year",
"description": "Jahr auswählen",
"action": "select",
"target": "select[title='Jahr:']",
"value": "{YEAR}",
"fuzzy_match": ["Jahr", "Year"]
},
{
"name": "submit_birthday",
"description": "Geburtsdatum bestätigen",
"action": "click",
"target": "//button[contains(text(), 'Weiter') or contains(text(), 'Next')]",
"wait_for": ["input[name='confirmationCode']", "input[aria-label='Bestätigungscode']"],
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["next"]
}
]
# Bestätigungscodeschritte
verification_steps = [
{
"name": "enter_confirmation_code",
"description": "Bestätigungscode eingeben",
"action": "fill",
"target": "input[name='confirmationCode']",
"alternative_target": "input[aria-label='Bestätigungscode']",
"value": "{CONFIRMATION_CODE}",
"fuzzy_match": ["Bestätigungscode", "Confirmation code", "Verification code", "Code"]
},
{
"name": "submit_verification",
"description": "Bestätigungscode absenden",
"action": "click",
"target": "//button[contains(text(), 'Confirm') or contains(text(), 'Verify') or contains(text(), 'Weiter')]",
"wait_for": ["img[alt='Instagram']"],
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["confirm"]
}
]
# Vollständigen Workflow zusammenstellen
workflow = common_steps + method_steps + form_steps + birthday_steps + verification_steps
return workflow
@staticmethod
def get_login_workflow() -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die Instagram-Anmeldung zurück.
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
login_steps = [
{
"name": "navigate_to_login",
"description": "Zur Anmeldeseite navigieren",
"url": "https://www.instagram.com/accounts/login/",
"wait_for": ["input[name='username']", "div[role='dialog']"],
"fuzzy_match": None # Kein Fuzzy-Matching für die Navigation
},
{
"name": "handle_cookie_banner",
"description": "Cookie-Banner behandeln",
"action": "click",
"target": "//button[contains(text(), 'Ablehnen') or contains(text(), 'Nur erforderliche') or contains(text(), 'Reject')]",
"optional": True,
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["reject_cookies"]
},
{
"name": "fill_username_or_email",
"description": "Benutzername oder E-Mail eingeben",
"action": "fill",
"target": "input[name='username']",
"value": "{USERNAME_OR_EMAIL}",
"fuzzy_match": ["Benutzername", "Username", "E-Mail", "Email", "Telefonnummer", "Phone number"]
},
{
"name": "fill_password",
"description": "Passwort eingeben",
"action": "fill",
"target": "input[name='password']",
"value": "{PASSWORD}",
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["password"]
},
{
"name": "submit_login",
"description": "Anmeldung absenden",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["svg[aria-label='Home']", "img[alt='Instagram']"],
"fuzzy_match": ["Anmelden", "Log in", "Einloggen", "Login"]
},
{
"name": "handle_save_info_prompt",
"description": "Optional: 'Anmeldedaten speichern'-Prompt ablehnen",
"action": "click",
"target": "//button[contains(text(), 'Nicht jetzt') or contains(text(), 'Not now')]",
"optional": True,
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["skip"]
},
{
"name": "handle_notifications_prompt",
"description": "Optional: Benachrichtigungen-Prompt ablehnen",
"action": "click",
"target": "//button[contains(text(), 'Nicht jetzt') or contains(text(), 'Not now')]",
"optional": True,
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["skip"]
}
]
return login_steps
@staticmethod
def get_verification_workflow(verification_method: str = "email") -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die Instagram-Verifizierung zurück.
Args:
verification_method: "email" oder "phone"
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
verification_steps = [
{
"name": "wait_for_verification_page",
"description": "Auf Verifizierungsseite warten",
"wait_for": ["input[name='confirmationCode']", "input[aria-label='Bestätigungscode']"],
"fuzzy_match": ["Bestätigungscode", "Confirmation code", "Verification code", "Code"]
},
{
"name": "enter_verification_code",
"description": f"Verifizierungscode eingeben ({verification_method})",
"action": "fill",
"target": "input[name='confirmationCode']",
"alternative_target": "input[aria-label='Bestätigungscode']",
"value": "{VERIFICATION_CODE}",
"fuzzy_match": ["Bestätigungscode", "Confirmation code", "Verification code", "Code"]
},
{
"name": "submit_verification",
"description": "Verifizierungscode absenden",
"action": "click",
"target": "//button[contains(text(), 'Confirm') or contains(text(), 'Verify') or contains(text(), 'Weiter')]",
"wait_for": ["img[alt='Instagram']"],
"fuzzy_match": InstagramWorkflow.TEXT_ALTERNATIVES["confirm"]
}
]
return verification_steps
@staticmethod
def identify_current_step(page_title: str, page_url: str, visible_elements: List[str]) -> str:
"""
Identifiziert den aktuellen Schritt basierend auf dem Seitentitel, der URL und sichtbaren Elementen.
Args:
page_title: Titel der Seite
page_url: URL der Seite
visible_elements: Liste sichtbarer Elemente (Selektoren)
Returns:
str: Name des identifizierten Schritts
"""
# Registrierungsseite
if "signup" in page_url:
if "input[name='emailOrPhone']" in visible_elements:
return "fill_registration_form"
elif "select[title='Monat:']" in visible_elements:
return "select_birthday"
elif "input[name='confirmationCode']" in visible_elements:
return "enter_confirmation_code"
# Anmeldeseite
elif "login" in page_url:
return "fill_login_form"
# Verifizierungsseite - robuste Erkennung mit Text-Matching
elif any(element for element in visible_elements if
InstagramWorkflow.text_similarity.contains_similar_text(element,
["Bestätigungscode", "Verification", "Code"],
threshold=InstagramWorkflow.SIMILARITY_THRESHOLD)):
return "enter_verification_code"
# Verifizierungsseite - Fallback mit Selektoren
elif "input[name='confirmationCode']" in visible_elements:
return "enter_verification_code"
# Startseite / Dashboard - robuste Erkennung mit Text-Matching
elif "instagram.com/" in page_url and any(element for element in visible_elements if
InstagramWorkflow.text_similarity.contains_similar_text(element,
["Home", "Feed", "Startseite"],
threshold=InstagramWorkflow.SIMILARITY_THRESHOLD)):
return "logged_in"
# Startseite / Dashboard - Fallback mit Selektoren
elif "instagram.com/" in page_url and ("svg[aria-label='Home']" in visible_elements or "img[alt='Instagram']" in visible_elements):
return "logged_in"
# Nicht identifizierbar
return "unknown"
@staticmethod
def find_similar_element(elements: List[Dict[str, str]], target_text: str, threshold: float = None) -> Optional[Dict[str, str]]:
"""
Findet ein Element, das dem Zieltext ähnlich ist.
Args:
elements: Liste von Elementen mit Text-Eigenschaft
target_text: Zu suchender Text
threshold: Ähnlichkeitsschwellenwert (None für Standardwert)
Returns:
Element oder None, wenn keines gefunden wurde
"""
if threshold is None:
threshold = InstagramWorkflow.SIMILARITY_THRESHOLD
for element in elements:
element_text = element.get("text", "")
if not element_text:
continue
if InstagramWorkflow.text_similarity.is_similar(target_text, element_text, threshold=threshold):
return element
return None
@staticmethod
def get_alternative_texts(element_type: str) -> List[str]:
"""
Gibt alternative Texte für einen Elementtyp zurück.
Args:
element_type: Typ des Elements (z.B. "email", "submit")
Returns:
Liste mit alternativen Texten
"""
return InstagramWorkflow.TEXT_ALTERNATIVES.get(element_type, [])
@staticmethod
def fuzzy_find_step_by_name(workflow: List[Dict[str, Any]], step_name_pattern: str) -> Optional[Dict[str, Any]]:
"""
Findet einen Workflow-Schritt anhand eines Namensmusters.
Args:
workflow: Workflow-Schritte
step_name_pattern: Name oder Muster für den gesuchten Schritt
Returns:
Der gefundene Schritt oder None
"""
# Exakte Übereinstimmung prüfen
for step in workflow:
if step["name"] == step_name_pattern:
return step
# Mustersuche mit regulären Ausdrücken
pattern = re.compile(step_name_pattern, re.IGNORECASE)
for step in workflow:
if pattern.search(step["name"]) or pattern.search(step.get("description", "")):
return step
# Fuzzy-Matching als letzter Ausweg
for step in workflow:
name_similarity = InstagramWorkflow.text_similarity.similarity_ratio(step_name_pattern, step["name"])
desc_similarity = InstagramWorkflow.text_similarity.similarity_ratio(step_name_pattern, step.get("description", ""))
if name_similarity > 0.8 or desc_similarity > 0.8:
return step
return None
# Beispielnutzung, wenn direkt ausgeführt
if __name__ == "__main__":
# Konfiguriere Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Beispiel für Workflow-Generierung
email_workflow = InstagramWorkflow.get_registration_workflow("email")
phone_workflow = InstagramWorkflow.get_registration_workflow("phone")
login_workflow = InstagramWorkflow.get_login_workflow()
print(f"E-Mail-Registrierung: {len(email_workflow)} Schritte")
print(f"Telefon-Registrierung: {len(phone_workflow)} Schritte")
print(f"Anmeldung: {len(login_workflow)} Schritte")
# Beispiel für Workflow-Details
print("\nDetails zum E-Mail-Registrierungs-Workflow:")
for i, step in enumerate(email_workflow):
print(f"{i+1}. {step['name']}: {step['description']}")
if 'fuzzy_match' in step and step['fuzzy_match']:
print(f" Fuzzy-Match-Texte: {step['fuzzy_match']}")

Datei anzeigen

@ -0,0 +1,7 @@
"""
OK.ru (Odnoklassniki) automation package.
"""
from .ok_ru_automation import OkRuAutomation
__all__ = ['OkRuAutomation']

Datei anzeigen

@ -0,0 +1,303 @@
"""
OK.ru (Odnoklassniki) Automatisierung - Hauptklasse für OK.ru-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 .ok_ru_registration import OkRuRegistration
from .ok_ru_login import OkRuLogin
from .ok_ru_verification import OkRuVerification
from .ok_ru_ui_helper import OkRuUIHelper
from .ok_ru_utils import OkRuUtils
# Konfiguriere Logger
logger = setup_logger("ok_ru_automation")
class OkRuAutomation(BaseAutomation):
"""
Hauptklasse für die OK.ru (Odnoklassniki) Automatisierung.
Implementiert die Registrierung und Anmeldung bei OK.ru.
"""
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 OK.ru-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 = OkRuRegistration(self)
self.login = OkRuLogin(self)
self.verification = OkRuVerification(self)
self.ui_helper = OkRuUIHelper(self)
self.utils = OkRuUtils(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("OK.ru-Automatisierung initialisiert")
def _initialize_browser(self) -> bool:
"""
Initialisiert den Browser mit den entsprechenden Einstellungen.
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/Moscow" # Russische Zeitzone für OK.ru
}
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 = "phone",
phone_number: str = None, **kwargs) -> Dict[str, Any]:
"""
Registriert einen neuen OK.ru-Account.
Args:
full_name: Vollständiger Name für den Account
age: Alter des Benutzers
registration_method: "phone" (OK.ru unterstützt hauptsächlich Telefon-Registrierung)
phone_number: Telefonnummer (erforderlich)
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Registrierung mit Status und Account-Daten
"""
logger.info(f"Starte OK.ru-Account-Registrierung für '{full_name}'")
self._emit_customer_log(f"📱 OK.ru-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 Registrierung
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=full_name,
age=age,
registration_method=registration_method,
phone_number=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 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": "exception"
})
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 OK.ru-Account an.
Args:
username_or_email: Benutzername, E-Mail oder Telefonnummer
password: Passwort
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Anmeldung mit Status
"""
logger.info(f"Starte OK.ru-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": "exception"
})
return self.status
finally:
# Browser schließen wenn auto_close aktiviert
if self.auto_close_browser:
self._close_browser()

Datei anzeigen

@ -0,0 +1,53 @@
# social_networks/ok_ru/ok_ru_login.py
"""
OK.ru Login - Klasse für die Anmeldung bei OK.ru-Konten
"""
import time
from typing import Dict, Any, Optional
from .ok_ru_selectors import OkRuSelectors
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("ok_ru_login")
class OkRuLogin:
"""
Klasse für die Anmeldung bei OK.ru-Konten.
Behandelt den kompletten Login-Prozess.
"""
def __init__(self, automation):
"""
Initialisiert die OK.ru-Login-Klasse.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.selectors = OkRuSelectors()
logger.debug("OK.ru-Login initialisiert")
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
"""
Führt den Login-Prozess für einen OK.ru-Account durch.
Args:
username_or_email: Benutzername, E-Mail oder Telefonnummer
password: Passwort
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis des Logins mit Status
"""
logger.info(f"Starte OK.ru-Login für '{username_or_email}'")
# Temporäre Implementierung
return {
"success": False,
"error": "OK.ru Login noch nicht implementiert",
"stage": "not_implemented"
}

Datei anzeigen

@ -0,0 +1,178 @@
# social_networks/ok_ru/ok_ru_registration.py
"""
OK.ru Registration - Klasse für die Registrierung bei OK.ru
"""
import time
from typing import Dict, Any, Optional
from .ok_ru_selectors import OkRuSelectors
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("ok_ru_registration")
class OkRuRegistration:
"""
Klasse für die Registrierung bei OK.ru.
Behandelt den kompletten Registrierungsprozess.
"""
def __init__(self, automation):
"""
Initialisiert die OK.ru-Registration-Klasse.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.selectors = OkRuSelectors()
logger.debug("OK.ru-Registration initialisiert")
def register_account(self, full_name: str, age: int, registration_method: str = "phone",
phone_number: str = None, **kwargs) -> Dict[str, Any]:
"""
Führt den Registrierungsprozess für einen OK.ru-Account durch.
Args:
full_name: Vollständiger Name
age: Alter
registration_method: Registrierungsmethode (normalerweise "phone")
phone_number: Telefonnummer
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Registrierung mit Status
"""
logger.info(f"Starte OK.ru-Registrierung für '{full_name}'")
try:
# 1. Zur OK.ru mobilen Seite navigieren
self.automation._emit_customer_log("🌐 Verbinde mit OK.ru...")
if not self._navigate_to_homepage():
return {
"success": False,
"error": "Konnte nicht zur OK.ru-Startseite navigieren",
"stage": "navigation"
}
# 2. Cookie-Banner behandeln (falls vorhanden)
self._handle_cookie_banner()
# 3. Registrieren-Button klicken
self.automation._emit_customer_log("📝 Öffne Registrierungsformular...")
if not self._click_register_button():
return {
"success": False,
"error": "Konnte Registrieren-Button nicht finden oder klicken",
"stage": "register_button"
}
# Weitere Schritte müssen noch implementiert werden
return {
"success": False,
"error": "OK.ru Registrierung in Entwicklung - weitere Schritte folgen",
"stage": "in_development"
}
except Exception as e:
error_msg = f"Unerwarteter Fehler bei OK.ru-Registrierung: {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 OK.ru Startseite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
logger.info("Navigiere zur OK.ru Startseite")
page.goto("https://m.ok.ru/", wait_until="domcontentloaded", timeout=30000)
# Warte auf Seitenladung
self.automation.human_behavior.random_delay(2, 4)
# Screenshot
self.automation._take_screenshot("ok_ru_homepage")
return True
except Exception as e:
logger.error(f"Fehler beim Navigieren zur OK.ru Startseite: {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_register_button(self) -> bool:
"""
Klickt auf den Registrieren-Button.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
page = self.automation.browser.page
# Warte kurz
self.automation.human_behavior.random_delay(1, 2)
# Versuche verschiedene Registrieren-Button-Selektoren
register_selectors = [
self.selectors.REGISTRATION["register_button"],
self.selectors.REGISTRATION["register_button_alt"],
self.selectors.REGISTRATION["register_button_data"]
]
for selector in register_selectors:
try:
if page.is_visible(selector, timeout=3000):
logger.info(f"Registrieren-Button gefunden: {selector}")
self.automation.human_behavior.random_delay(0.5, 1.5)
page.click(selector)
self.automation.human_behavior.random_delay(2, 3)
# Screenshot nach Klick
self.automation._take_screenshot("after_register_click")
return True
except Exception as e:
logger.debug(f"Selektor {selector} nicht gefunden: {e}")
continue
# Screenshot für Debugging
self.automation._take_screenshot("register_button_not_found")
logger.error("Keinen Registrieren-Button gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf Registrieren-Button: {e}")
return False

Datei anzeigen

@ -0,0 +1,168 @@
# social_networks/ok_ru/ok_ru_selectors.py
"""
OK.ru (Odnoklassniki) Selektoren - Zentrale Sammlung aller CSS-Selektoren für OK.ru
"""
class OkRuSelectors:
"""
Zentrale Klasse für alle OK.ru-spezifischen CSS-Selektoren.
Optimiert für die mobile Version (m.ok.ru).
"""
# === ALLGEMEINE SELEKTOREN ===
COOKIE_ACCEPT_BUTTONS = [
"button:has-text('Принять')",
"button:has-text('Accept')",
"button:has-text('OK')",
"[class*='cookie'] button"
]
# === REGISTRIERUNG ===
REGISTRATION = {
# Hauptseite
"register_button": 'input[name="registerButton"][value="Registrieren"]',
"register_button_alt": 'input[type="submit"][value="Registrieren"]',
"register_button_data": 'input[data-log-click*="register"]',
# Formularfelder
"phone_input": 'input[name="st.email"]',
"phone_input_alt": 'input[type="tel"]',
"country_code_select": 'select[name="st.countryCode"]',
# Buttons
"get_code_button": 'input[type="submit"][value*="Получить код"]',
"get_code_button_alt": 'button:has-text("Получить код")',
# Verifizierung
"verification_code_input": 'input[name="st.smsCode"]',
"verification_code_inputs": 'input[maxlength="1"]', # Einzelne Ziffern-Inputs
# Profil-Erstellung
"first_name_input": 'input[name="st.firstName"]',
"last_name_input": 'input[name="st.lastName"]',
"password_input": 'input[name="st.password"]',
"password_confirm_input": 'input[name="st.passwordConfirm"]',
# Geburtsdatum
"birth_day_select": 'select[name="st.bday"]',
"birth_month_select": 'select[name="st.bmonth"]',
"birth_year_select": 'select[name="st.byear"]',
# Geschlecht
"gender_male": 'input[name="st.gender"][value="1"]',
"gender_female": 'input[name="st.gender"][value="2"]',
# Submit
"register_submit": 'input[type="submit"][value*="Зарегистрироваться"]',
"register_submit_alt": 'button:has-text("Зарегистрироваться")'
}
# === LOGIN ===
LOGIN = {
# Login-Link
"login_link": 'a[href*="st.cmd=anonymMain&st.login"]',
"login_link_alt": 'a:has-text("Войти")',
"login_link_en": 'a:has-text("Log in")',
# Formularfelder
"username_input": 'input[name="st.email"]',
"username_input_alt": 'input[type="text"][name="st.email"]',
"password_input": 'input[name="st.password"]',
"password_input_alt": 'input[type="password"]',
# Submit
"login_submit": 'input[type="submit"][value*="Войти"]',
"login_submit_alt": 'button:has-text("Войти")',
"login_submit_en": 'button:has-text("Log in")'
}
# === NAVIGATION ===
NAVIGATION = {
# Hauptnavigation
"home_link": 'a[href*="/main"]',
"profile_link": 'a[href*="/profile"]',
"friends_link": 'a[href*="/friends"]',
"messages_link": 'a[href*="/messages"]',
"groups_link": 'a[href*="/groups"]',
# Mobile Menü
"menu_button": '[class*="menu-button"]',
"mobile_menu": '[class*="mobile-menu"]'
}
# === PROFIL ===
PROFILE = {
# Profilbearbeitung
"edit_profile_button": 'a:has-text("Редактировать")',
"edit_profile_button_en": 'a:has-text("Edit")',
# Avatar
"avatar_upload": 'input[type="file"]',
"avatar_container": '[class*="avatar"]'
}
# === VERIFIZIERUNG ===
VERIFICATION = {
# SMS-Verifizierung
"resend_code_link": 'a:has-text("Отправить код еще раз")',
"resend_code_timer": '[class*="timer"]',
# Captcha
"captcha_container": '[class*="captcha"]',
"captcha_input": 'input[name*="captcha"]'
}
# === FEHLER UND WARNUNGEN ===
ERRORS = {
# Fehlermeldungen
"error_message": '[class*="error"]',
"error_text": '[class*="error-text"]',
"validation_error": '[class*="validation-error"]',
# Spezifische Fehler
"phone_taken": 'text="Этот номер уже используется"',
"invalid_code": 'text="Неверный код"',
"weak_password": 'text="Слишком простой пароль"'
}
# === MODALE DIALOGE ===
MODALS = {
# Allgemeine Modale
"modal_container": '[class*="modal"]',
"modal_close": '[class*="modal-close"]',
# Bestätigung
"confirm_button": 'button:has-text("Подтвердить")',
"cancel_button": 'button:has-text("Отмена")'
}
@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,14 @@
# social_networks/ok_ru/ok_ru_ui_helper.py
"""
OK.ru UI Helper - Hilfsfunktionen für UI-Interaktionen
"""
from utils.logger import setup_logger
logger = setup_logger("ok_ru_ui_helper")
class OkRuUIHelper:
def __init__(self, automation):
self.automation = automation
logger.debug("OK.ru-UIHelper initialisiert")

Datei anzeigen

@ -0,0 +1,14 @@
# social_networks/ok_ru/ok_ru_utils.py
"""
OK.ru Utils - Hilfsfunktionen für OK.ru
"""
from utils.logger import setup_logger
logger = setup_logger("ok_ru_utils")
class OkRuUtils:
def __init__(self, automation):
self.automation = automation
logger.debug("OK.ru-Utils initialisiert")

Datei anzeigen

@ -0,0 +1,14 @@
# social_networks/ok_ru/ok_ru_verification.py
"""
OK.ru Verification - Klasse für Verifizierungsprozesse
"""
from utils.logger import setup_logger
logger = setup_logger("ok_ru_verification")
class OkRuVerification:
def __init__(self, automation):
self.automation = automation
logger.debug("OK.ru-Verification initialisiert")

Datei anzeigen

Datei anzeigen

@ -0,0 +1,392 @@
"""
TikTok-Automatisierung - Hauptklasse für TikTok-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 .tiktok_registration import TikTokRegistration
from .tiktok_login import TikTokLogin
from .tiktok_verification import TikTokVerification
from .tiktok_ui_helper import TikTokUIHelper
from .tiktok_utils import TikTokUtils
# Konfiguriere Logger
logger = setup_logger("tiktok_automation")
class TikTokAutomation(BaseAutomation):
"""
Hauptklasse für die TikTok-Automatisierung.
Implementiert die Registrierung und Anmeldung bei TikTok.
"""
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 TikTok-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 = TikTokRegistration(self)
self.login = TikTokLogin(self)
self.verification = TikTokVerification(self)
self.ui_helper = TikTokUIHelper(self)
self.utils = TikTokUtils(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("TikTok-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 TikTok-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 TikTok-Account-Registrierung für '{full_name}' via {registration_method}")
self._emit_customer_log(f"🎵 TikTok-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 TikTok-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 TikTok-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 TikTok-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 TikTok-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 {}

Datei anzeigen

@ -0,0 +1,825 @@
"""
TikTok-Login - Klasse für die Anmeldefunktionalität bei TikTok
"""
import time
import re
from typing import Dict, List, Any, Optional, Tuple
from .tiktok_selectors import TikTokSelectors
from .tiktok_workflow import TikTokWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("tiktok_login")
class TikTokLogin:
"""
Klasse für die Anmeldung bei TikTok-Konten.
Enthält alle Methoden für den Login-Prozess.
"""
def __init__(self, automation):
"""
Initialisiert die TikTok-Login-Funktionalität.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
# Browser wird direkt von automation verwendet
self.selectors = TikTokSelectors()
self.workflow = TikTokWorkflow.get_login_workflow()
logger.debug("TikTok-Login initialisiert")
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
"""
Führt den Login-Prozess für ein TikTok-Konto 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
"""
# Browser wird direkt von automation verwendet
# Validiere die Eingaben
if not self._validate_login_inputs(username_or_email, password):
return {
"success": False,
"error": "Ungültige Login-Eingaben",
"stage": "input_validation"
}
# Account-Daten für die Anmeldung
account_data = {
"username": username_or_email,
"password": password,
"handle_2fa": kwargs.get("handle_2fa", False),
"two_factor_code": kwargs.get("two_factor_code"),
"skip_save_login": kwargs.get("skip_save_login", True)
}
logger.info(f"Starte TikTok-Login für {username_or_email}")
try:
# 1. Zur Login-Seite navigieren
if not self._navigate_to_login_page():
return {
"success": False,
"error": "Konnte nicht zur Login-Seite navigieren",
"stage": "navigation"
}
# 2. Cookie-Banner behandeln
self._handle_cookie_banner()
# 3. Login-Formular ausfüllen
if not self._fill_login_form(account_data):
return {
"success": False,
"error": "Fehler beim Ausfüllen des Login-Formulars",
"stage": "login_form"
}
# 4. Auf 2FA prüfen und behandeln, falls nötig
needs_2fa, two_fa_error = self._check_needs_two_factor_auth()
if needs_2fa:
if not account_data["handle_2fa"]:
return {
"success": False,
"error": "Zwei-Faktor-Authentifizierung erforderlich, aber nicht aktiviert",
"stage": "two_factor_required"
}
# 2FA behandeln
if not self._handle_two_factor_auth(account_data["two_factor_code"]):
return {
"success": False,
"error": "Fehler bei der Zwei-Faktor-Authentifizierung",
"stage": "two_factor_auth"
}
# 5. Benachrichtigungserlaubnis-Dialog behandeln
self._handle_notifications_prompt()
# 6. Erfolgreichen Login überprüfen
if not self._check_login_success():
error_message = self._get_login_error()
return {
"success": False,
"error": f"Login fehlgeschlagen: {error_message or 'Unbekannter Fehler'}",
"stage": "login_check"
}
# Login erfolgreich
logger.info(f"TikTok-Login für {username_or_email} erfolgreich")
return {
"success": True,
"stage": "completed",
"username": username_or_email
}
except Exception as e:
error_msg = f"Unerwarteter Fehler beim TikTok-Login: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"stage": "exception"
}
def _validate_login_inputs(self, username_or_email: str, password: str) -> bool:
"""
Validiert die Eingaben für den Login.
Args:
username_or_email: Benutzername oder E-Mail-Adresse
password: Passwort
Returns:
bool: True wenn alle Eingaben gültig sind, False sonst
"""
if not username_or_email or len(username_or_email) < 3:
logger.error("Ungültiger Benutzername oder E-Mail")
return False
if not password or len(password) < 6:
logger.error("Ungültiges Passwort")
return False
return True
def _navigate_to_login_page(self) -> bool:
"""
Navigiert zur TikTok-Login-Seite über die Explore-Seite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Zur Explore-Seite navigieren
logger.info("Navigiere zur TikTok Explore-Seite")
self.automation.browser.navigate_to(TikTokSelectors.EXPLORE_URL)
# Warten, bis die Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Screenshot erstellen
self.automation._take_screenshot("tiktok_explore_page")
# Login-Button auf der Explore-Seite suchen und klicken
logger.info("Suche Anmelden-Button auf Explore-Seite")
login_button_selectors = [
"button#header-login-button",
"button.TUXButton--primary",
"button:has-text('Anmelden')",
TikTokSelectors.LOGIN_BUTTON_HEADER,
"button[class*='StyledLeftSidePrimaryButton']"
]
button_clicked = False
for selector in login_button_selectors:
logger.debug(f"Versuche Login-Button: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=2000):
# Kurz warten vor dem Klick
self.automation.human_behavior.random_delay(0.5, 1.0)
if self.automation.browser.click_element(selector):
button_clicked = True
logger.info(f"Anmelden-Button erfolgreich geklickt: {selector}")
break
if not button_clicked:
logger.error("Konnte keinen Anmelden-Button auf der Explore-Seite finden")
self.automation._take_screenshot("no_login_button_found")
return False
# Warten, bis der Login-Dialog erscheint
logger.info("Warte auf Login-Dialog")
self.automation.human_behavior.random_delay(2.0, 3.0)
# Prüfen, ob der Login-Dialog sichtbar ist
dialog_visible = False
dialog_selectors = [
"div[role='dialog']",
TikTokSelectors.LOGIN_DIALOG,
"div[class*='login-modal']",
"div[class*='DivLoginContainer']"
]
for dialog_selector in dialog_selectors:
if self.automation.browser.is_element_visible(dialog_selector, timeout=5000):
dialog_visible = True
logger.info(f"Login-Dialog erschienen: {dialog_selector}")
break
if not dialog_visible:
logger.error("Login-Dialog ist nach 5 Sekunden nicht erschienen")
self.automation._take_screenshot("no_login_dialog")
return False
# Screenshot vom geöffneten Dialog
self.automation._take_screenshot("login_dialog_opened")
logger.info("Erfolgreich zum Login-Dialog navigiert")
return True
except Exception as e:
logger.error(f"Fehler beim Navigieren zur Login-Seite: {e}")
return False
def _handle_cookie_banner(self) -> bool:
"""
Behandelt den Cookie-Banner, falls angezeigt.
Akzeptiert IMMER Cookies für vollständiges Session-Management beim Login.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
# Cookie-Dialog-Erkennung
if self.automation.browser.is_element_visible(TikTokSelectors.COOKIE_DIALOG, timeout=2000):
logger.info("Cookie-Banner erkannt - akzeptiere alle Cookies für Session-Management")
# Akzeptieren-Button suchen und klicken (PRIMÄR für Login)
accept_success = self.automation.ui_helper.click_button_fuzzy(
TikTokSelectors.get_button_texts("accept_cookies"),
TikTokSelectors.COOKIE_ACCEPT_BUTTON
)
if accept_success:
logger.info("Cookie-Banner erfolgreich akzeptiert - Session-Cookies werden gespeichert")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
else:
logger.warning("Konnte Cookie-Banner nicht akzeptieren, versuche alternativen Akzeptieren-Button")
# Alternative Akzeptieren-Selektoren versuchen
alternative_accept_selectors = [
"//button[contains(text(), 'Alle akzeptieren')]",
"//button[contains(text(), 'Accept All')]",
"//button[contains(text(), 'Zulassen')]",
"//button[contains(text(), 'Allow All')]",
"//button[contains(@aria-label, 'Accept')]",
"[data-testid='accept-all-button']"
]
for selector in alternative_accept_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info("Cookie-Banner mit alternativem Selector akzeptiert")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte Cookie-Banner nicht akzeptieren - Session-Management könnte beeinträchtigt sein")
return False
else:
logger.debug("Kein Cookie-Banner erkannt")
return True
def _fill_login_form(self, account_data: Dict[str, Any]) -> bool:
"""
Füllt das Login-Formular aus und sendet es ab.
Args:
account_data: Account-Daten für den Login
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# 1. E-Mail/Telefon-Login-Option auswählen
logger.info("Klicke auf 'Telefon-Nr./E-Mail/Anmeldename nutzen'")
# Warte kurz, damit Dialog vollständig geladen ist
self.automation.human_behavior.random_delay(1.0, 1.5)
# Selektoren für die Option - spezifischer für E-Mail/Telefon
phone_email_selectors = [
"div[data-e2e='channel-item']:has(p:has-text('Telefon-Nr./E-Mail/Anmeldename nutzen'))",
"div[role='link']:has-text('Telefon-Nr./E-Mail/Anmeldename nutzen')",
"//div[@data-e2e='channel-item'][.//p[contains(text(), 'Telefon-Nr./E-Mail/Anmeldename nutzen')]]",
"div.css-17hparj-DivBoxContainer:has-text('Telefon-Nr./E-Mail/Anmeldename nutzen')",
"//div[contains(@class, 'DivBoxContainer') and contains(., 'Telefon-Nr./E-Mail/Anmeldename nutzen')]"
]
email_phone_clicked = False
for selector in phone_email_selectors:
logger.debug(f"Versuche E-Mail/Telefon-Selektor: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=2000):
# Kurz warten vor dem Klick
self.automation.human_behavior.random_delay(0.3, 0.5)
if self.automation.browser.click_element(selector):
email_phone_clicked = True
logger.info(f"E-Mail/Telefon-Option erfolgreich geklickt: {selector}")
break
else:
logger.warning(f"Klick fehlgeschlagen für Selektor: {selector}")
if not email_phone_clicked:
logger.error("Konnte 'Telefon-Nr./E-Mail/Anmeldename nutzen' nicht klicken")
self.automation._take_screenshot("phone_email_option_not_found")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# 2. "Mit E-Mail-Adresse oder Benutzernamen anmelden" Link klicken
logger.info("Klicke auf 'Mit E-Mail-Adresse oder Benutzernamen anmelden'")
email_link_selectors = [
"a[href='/login/phone-or-email/email']",
"a.css-1mgli76-ALink-StyledLink",
"a:has-text('Mit E-Mail-Adresse oder Benutzernamen anmelden')",
"//a[contains(text(), 'Mit E-Mail-Adresse oder Benutzernamen anmelden')]"
]
email_login_clicked = False
for selector in email_link_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
if self.automation.browser.click_element(selector):
email_login_clicked = True
logger.info(f"E-Mail-Login-Link geklickt: {selector}")
break
if not email_login_clicked:
logger.error("Konnte E-Mail-Login-Link nicht klicken")
self.automation._take_screenshot("email_login_link_not_found")
return False
self.automation.human_behavior.random_delay(1.5, 2.5)
# 3. E-Mail/Benutzername eingeben (Character-by-Character)
logger.info(f"Gebe E-Mail/Benutzername ein: {account_data['username']}")
# Warte bis Formular geladen ist
self.automation.human_behavior.random_delay(0.5, 1.0)
username_selectors = [
"input[name='username']",
"input[placeholder='E-Mail-Adresse oder Benutzername']",
"input.css-11to27l-InputContainer[name='username']",
"input[type='text'][autocomplete='webauthn']"
]
username_success = False
for selector in username_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
username_success = self._fill_username_field_character_by_character(selector, account_data["username"])
if username_success:
logger.info(f"Benutzername erfolgreich eingegeben mit Selektor: {selector}")
break
if not username_success:
logger.error("Konnte Benutzername-Feld nicht ausfüllen")
self.automation._take_screenshot("username_field_not_found")
return False
self.automation.human_behavior.random_delay(0.5, 1.0)
# 4. Passwort eingeben (mit Character-by-Character für bessere Kompatibilität)
logger.info("Gebe Passwort ein")
password_selectors = [
"input[type='password']",
"input[placeholder='Passwort']",
"input.css-wv3bkt-InputContainer[type='password']",
"input[autocomplete='new-password']"
]
password_success = False
for selector in password_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
# Verwende character-by-character Eingabe
password_success = self._fill_password_field_character_by_character(selector, account_data["password"])
if password_success:
logger.info(f"Passwort erfolgreich eingegeben mit Selektor: {selector}")
break
if not password_success:
logger.error("Konnte Passwort-Feld nicht ausfüllen")
self.automation._take_screenshot("password_field_not_found")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot vorm Absenden
self.automation._take_screenshot("login_form_filled")
# 5. Prüfe ob Login-Button aktiviert ist
logger.info("Prüfe Login-Button Status")
login_button_selectors = [
"button[data-e2e='login-button']",
"button[type='submit'][data-e2e='login-button']",
"button.css-11sviba-Button-StyledButton",
"button:has-text('Anmelden')"
]
button_ready = False
active_selector = None
for selector in login_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
element = self.automation.browser.page.locator(selector).first
is_disabled = element.get_attribute("disabled")
if not is_disabled:
button_ready = True
active_selector = selector
logger.info(f"Login-Button ist aktiviert: {selector}")
break
else:
logger.warning(f"Login-Button ist disabled: {selector}")
if not button_ready:
logger.warning("Login-Button ist nicht bereit, warte zusätzlich")
self.automation.human_behavior.random_delay(2.0, 3.0)
# Nochmal prüfen
for selector in login_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
element = self.automation.browser.page.locator(selector).first
is_disabled = element.get_attribute("disabled")
if not is_disabled:
button_ready = True
active_selector = selector
break
# 6. Login-Button klicken
logger.info("Klicke auf Anmelden-Button")
if button_ready and active_selector:
if self.automation.browser.click_element(active_selector):
logger.info(f"Login-Button erfolgreich geklickt: {active_selector}")
else:
logger.error("Klick auf Login-Button fehlgeschlagen")
return False
else:
logger.error("Konnte keinen aktivierten Login-Button finden")
self.automation._take_screenshot("no_active_login_button")
return False
# Nach dem Absenden warten
self.automation.human_behavior.wait_for_page_load(multiplier=2.0)
# Überprüfen, ob es eine Fehlermeldung gab
error_message = self._get_login_error()
if error_message:
logger.error(f"Login-Fehler erkannt: {error_message}")
return False
logger.info("Login-Formular erfolgreich ausgefüllt und abgesendet")
return True
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des Login-Formulars: {e}")
return False
def _fill_username_field_character_by_character(self, selector: str, username: str) -> bool:
"""
Füllt das Benutzername-Feld Zeichen für Zeichen aus.
Args:
selector: CSS-Selektor für das Username-Feld
username: Der einzugebende Benutzername
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
element = self.automation.browser.page.locator(selector).first
if not element.is_visible():
return False
logger.info("Verwende Character-by-Character Eingabe für Benutzername-Feld")
# Fokussiere und lösche das Feld
element.click()
self.automation.human_behavior.random_delay(0.1, 0.2)
# Lösche existierenden Inhalt
element.select_text()
element.press("Delete")
self.automation.human_behavior.random_delay(0.1, 0.2)
# Tippe jeden Buchstaben einzeln
import random
for i, char in enumerate(username):
element.type(char, delay=random.randint(50, 150)) # Zufällige Tippgeschwindigkeit
# Nach jedem 4. Zeichen eine kleine Pause (simuliert echtes Tippen)
if (i + 1) % 4 == 0:
self.automation.human_behavior.random_delay(0.1, 0.3)
# Fokus verlassen
self.automation.human_behavior.random_delay(0.2, 0.4)
element.press("Tab")
logger.info(f"Benutzername character-by-character eingegeben: {len(username)} Zeichen")
return True
except Exception as e:
logger.error(f"Fehler bei Character-by-Character Benutzername-Eingabe: {e}")
return False
def _fill_password_field_character_by_character(self, selector: str, password: str) -> bool:
"""
Füllt das Passwort-Feld Zeichen für Zeichen aus, um React's State korrekt zu aktualisieren.
Args:
selector: CSS-Selektor für das Passwort-Feld
password: Das einzugebende Passwort
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
element = self.automation.browser.page.locator(selector).first
if not element.is_visible():
return False
logger.info("Verwende Character-by-Character Eingabe für Passwort-Feld")
# Fokussiere und lösche das Feld
element.click()
self.automation.human_behavior.random_delay(0.1, 0.2)
# Lösche existierenden Inhalt
element.select_text()
element.press("Delete")
self.automation.human_behavior.random_delay(0.1, 0.2)
# Tippe jeden Buchstaben einzeln
import random
for i, char in enumerate(password):
element.type(char, delay=random.randint(50, 150)) # Zufällige Tippgeschwindigkeit
# Nach jedem 3. Zeichen eine kleine Pause (simuliert echtes Tippen)
if (i + 1) % 3 == 0:
self.automation.human_behavior.random_delay(0.1, 0.3)
# Fokus verlassen, um Validierung zu triggern
self.automation.human_behavior.random_delay(0.2, 0.4)
element.press("Tab")
logger.info(f"Passwort character-by-character eingegeben: {len(password)} Zeichen")
return True
except Exception as e:
logger.error(f"Fehler bei Character-by-Character Passwort-Eingabe: {e}")
return False
def _get_login_error(self) -> Optional[str]:
"""
Überprüft, ob eine Login-Fehlermeldung angezeigt wird.
Returns:
Optional[str]: Fehlermeldung oder None, wenn keine gefunden wurde
"""
try:
# Auf Fehlermeldungen prüfen
error_selectors = [
TikTokSelectors.ERROR_MESSAGE,
"p[class*='error']",
"div[role='alert']",
"div[class*='error']",
"div[class*='Error']"
]
for selector in error_selectors:
error_element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if error_element:
error_text = error_element.text_content()
if error_text and len(error_text.strip()) > 0:
return error_text.strip()
# Wenn keine spezifische Fehlermeldung gefunden wurde, nach bekannten Fehlermustern suchen
error_texts = [
"Falsches Passwort",
"Benutzername nicht gefunden",
"incorrect password",
"username you entered doesn't belong",
"please wait a few minutes",
"try again later",
"Bitte warte einige Minuten",
"versuche es später noch einmal"
]
page_content = self.automation.browser.page.content()
for error_text in error_texts:
if error_text.lower() in page_content.lower():
return f"Erkannter Fehler: {error_text}"
return None
except Exception as e:
logger.error(f"Fehler beim Prüfen auf Login-Fehler: {e}")
return None
def _check_needs_two_factor_auth(self) -> Tuple[bool, Optional[str]]:
"""
Überprüft, ob eine Zwei-Faktor-Authentifizierung erforderlich ist.
Returns:
Tuple[bool, Optional[str]]: (2FA erforderlich, Fehlermeldung falls vorhanden)
"""
try:
# Nach 2FA-Indikatoren suchen
two_fa_selectors = [
"input[name='verificationCode']",
"input[placeholder*='code']",
"input[placeholder*='Code']",
"div[class*='verification-code']",
"div[class*='two-factor']"
]
for selector in two_fa_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info("Zwei-Faktor-Authentifizierung erforderlich")
return True, None
# Texte, die auf 2FA hinweisen
two_fa_indicators = [
"Verifizierungscode",
"Verification code",
"Sicherheitscode",
"Security code",
"zwei-faktor",
"two-factor",
"2FA"
]
# Seiteninhalt durchsuchen
page_content = self.automation.browser.page.content().lower()
for indicator in two_fa_indicators:
if indicator.lower() in page_content:
logger.info(f"Zwei-Faktor-Authentifizierung erkannt durch Text: {indicator}")
return True, None
return False, None
except Exception as e:
logger.error(f"Fehler beim Prüfen auf 2FA: {e}")
return False, f"Fehler bei der 2FA-Erkennung: {str(e)}"
def _handle_two_factor_auth(self, two_factor_code: Optional[str] = None) -> bool:
"""
Behandelt die Zwei-Faktor-Authentifizierung.
Args:
two_factor_code: Optional vorhandener 2FA-Code
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Screenshot erstellen
self.automation._take_screenshot("two_factor_auth")
# 2FA-Eingabefeld finden
two_fa_selectors = [
"input[name='verificationCode']",
"input[placeholder*='code']",
"input[placeholder*='Code']"
]
two_fa_field = None
for selector in two_fa_selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if element:
two_fa_field = selector
break
if not two_fa_field:
logger.error("Konnte 2FA-Eingabefeld nicht finden")
return False
# Wenn kein Code bereitgestellt wurde, Benutzer auffordern
if not two_factor_code:
logger.warning("Kein 2FA-Code bereitgestellt, kann nicht fortfahren")
return False
# 2FA-Code eingeben
code_success = self.automation.browser.fill_form_field(two_fa_field, two_factor_code)
if not code_success:
logger.error("Konnte 2FA-Code nicht eingeben")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# Bestätigen-Button finden und klicken
confirm_button_selectors = [
"button[type='submit']",
"//button[contains(text(), 'Bestätigen')]",
"//button[contains(text(), 'Confirm')]",
"//button[contains(text(), 'Verify')]"
]
confirm_clicked = False
for selector in confirm_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
confirm_clicked = True
break
if not confirm_clicked:
# Alternative: Mit Tastendruck bestätigen
self.automation.browser.page.keyboard.press("Enter")
logger.info("Enter-Taste gedrückt, um 2FA zu bestätigen")
# Warten nach der Bestätigung
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
# Überprüfen, ob 2FA erfolgreich war
still_on_2fa = self._check_needs_two_factor_auth()[0]
if still_on_2fa:
# Prüfen, ob Fehlermeldung angezeigt wird
error_message = self._get_login_error()
if error_message:
logger.error(f"2FA-Fehler: {error_message}")
else:
logger.error("2FA fehlgeschlagen, immer noch auf 2FA-Seite")
return False
logger.info("Zwei-Faktor-Authentifizierung erfolgreich")
return True
except Exception as e:
logger.error(f"Fehler bei der Zwei-Faktor-Authentifizierung: {e}")
return False
def _handle_notifications_prompt(self) -> bool:
"""
Behandelt den Benachrichtigungen-Dialog.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Nach "Nicht jetzt"-Button suchen
not_now_selectors = [
"//button[contains(text(), 'Nicht jetzt')]",
"//button[contains(text(), 'Not now')]",
"//button[contains(text(), 'Skip')]",
"//button[contains(text(), 'Später')]",
"//button[contains(text(), 'Later')]"
]
for selector in not_now_selectors:
if self.automation.browser.is_element_visible(selector, timeout=3000):
if self.automation.browser.click_element(selector):
logger.info("Benachrichtigungen-Dialog übersprungen")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
# Wenn kein Button gefunden wurde, ist der Dialog wahrscheinlich nicht vorhanden
logger.debug("Kein Benachrichtigungen-Dialog erkannt")
return True
except Exception as e:
logger.warning(f"Fehler beim Behandeln des Benachrichtigungen-Dialogs: {e}")
# Dies ist nicht kritisch, daher geben wir trotzdem True zurück
return True
def _check_login_success(self) -> bool:
"""
Überprüft, ob der Login erfolgreich war.
Returns:
bool: True wenn erfolgreich, False sonst
"""
try:
# Warten nach dem Login
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
# Screenshot erstellen
self.automation._take_screenshot("login_final")
# Erfolg anhand verschiedener Indikatoren prüfen
success_indicators = TikTokSelectors.SUCCESS_INDICATORS
for indicator in success_indicators:
if self.automation.browser.is_element_visible(indicator, timeout=2000):
logger.info(f"Login-Erfolgsindikator gefunden: {indicator}")
return True
# Alternativ prüfen, ob wir auf der TikTok-Startseite sind
current_url = self.automation.browser.page.url
if "tiktok.com" in current_url and "/login" not in current_url:
logger.info(f"Login-Erfolg basierend auf URL: {current_url}")
return True
# Prüfen, ob immer noch auf der Login-Seite
if "/login" in current_url or self.automation.browser.is_element_visible(TikTokSelectors.LOGIN_EMAIL_FIELD, timeout=1000):
logger.warning("Immer noch auf der Login-Seite, Login fehlgeschlagen")
return False
logger.warning("Keine Login-Erfolgsindikatoren gefunden")
return False
except Exception as e:
logger.error(f"Fehler beim Überprüfen des Login-Erfolgs: {e}")
return False

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

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

Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist

Datei anzeigen

@ -0,0 +1,801 @@
# social_networks/tiktok/tiktok_registration_new.py
"""
TikTok-Registrierung - Optimierte Klasse für die Kontoerstellung bei TikTok
NEUE IMPLEMENTIERUNG mit korrekter Workflow-Reihenfolge für maximale Stabilität.
OPTIMIERTER WORKFLOW:
1. E-Mail eingeben
2. Passwort eingeben
3. Code senden Button klicken
4. Code empfangen und eingeben
5. Weiter Button wird automatisch aktiviert
"""
import time
import random
import re
from typing import Dict, List, Any, Optional, Tuple
from .tiktok_selectors import TikTokSelectors
from .tiktok_workflow import TikTokWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("tiktok_registration")
class TikTokRegistration:
"""
Optimierte Klasse für die Registrierung von TikTok-Konten.
Implementiert einen robusten, zukunftssicheren Workflow.
"""
def __init__(self, automation):
"""
Initialisiert die TikTok-Registrierung.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.selectors = TikTokSelectors()
self.workflow = TikTokWorkflow.get_registration_workflow()
logger.debug("TikTok-Registrierung initialisiert")
def register_account(self, full_name: str, age: int, registration_method: str = "email",
phone_number: str = None, **kwargs) -> Dict[str, Any]:
"""
Führt den vollständigen Registrierungsprozess für einen TikTok-Account durch.
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
"""
# Validiere die Eingaben
if not self._validate_registration_inputs(full_name, age, registration_method, phone_number):
return {
"success": False,
"error": "Ungültige Eingabeparameter",
"stage": "input_validation"
}
# Account-Daten generieren
account_data = self._generate_account_data(full_name, age, registration_method, phone_number, **kwargs)
# Starte den Registrierungsprozess
logger.info(f"Starte optimierten TikTok-Registrierungsprozess für {account_data['username']} via {registration_method}")
try:
# 1. Zur Startseite navigieren
self.automation._emit_customer_log("🌐 Mit TikTok verbinden...")
if not self._navigate_to_homepage():
return self._create_error_result("Konnte nicht zur TikTok-Startseite navigieren", "navigation", account_data)
# 2. Cookie-Banner behandeln
self.automation._emit_customer_log("⚙️ Einstellungen werden vorbereitet...")
self._handle_cookie_banner()
# 3. Anmelden-Button klicken
self.automation._emit_customer_log("📋 Registrierungsformular wird geöffnet...")
if not self._click_login_button():
return self._create_error_result("Konnte nicht auf Anmelden-Button klicken", "login_button", account_data)
# 4. Registrieren-Link klicken
if not self._click_register_link():
return self._create_error_result("Konnte nicht auf Registrieren-Link klicken", "register_link", account_data)
# 5. Telefon/E-Mail-Option auswählen
if not self._click_phone_email_option():
return self._create_error_result("Konnte nicht auf Telefon/E-Mail-Option klicken", "phone_email_option", account_data)
# 6. E-Mail oder Telefon als Registrierungsmethode wählen
if not self._select_registration_method(registration_method):
return self._create_error_result(f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen", "registration_method", account_data)
# 7. Geburtsdatum eingeben
self.automation._emit_customer_log("🎂 Geburtsdatum wird festgelegt...")
if not self._enter_birthday(account_data["birthday"]):
return self._create_error_result("Fehler beim Eingeben des Geburtsdatums", "birthday", account_data)
# 8. OPTIMIERTER REGISTRIERUNGSWORKFLOW
self.automation._emit_customer_log("📝 Persönliche Daten werden übertragen...")
if not self._execute_optimized_registration_workflow(account_data, registration_method):
return self._create_error_result("Fehler im Registrierungsworkflow", "registration_workflow", account_data)
# 9. Benutzernamen erstellen
self.automation._emit_customer_log("👤 Benutzername wird erstellt...")
if not self._create_username(account_data):
return self._create_error_result("Fehler beim Erstellen des Benutzernamens", "username", account_data)
# 10. Erfolgreiche Registrierung überprüfen
self.automation._emit_customer_log("🔍 Account wird finalisiert...")
if not self._check_registration_success():
return self._create_error_result("Registrierung fehlgeschlagen oder konnte nicht verifiziert werden", "final_check", account_data)
# Registrierung erfolgreich abgeschlossen
logger.info(f"TikTok-Account {account_data['username']} erfolgreich erstellt")
self.automation._emit_customer_log("✅ Account erfolgreich erstellt!")
return {
"success": True,
"stage": "completed",
"account_data": account_data
}
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der TikTok-Registrierung: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"stage": "exception",
"account_data": account_data
}
def _execute_optimized_registration_workflow(self, account_data: Dict[str, Any], registration_method: str) -> bool:
"""
Führt den optimierten Registrierungsworkflow aus.
KORRIGIERTE REIHENFOLGE für E-Mail-Registrierung:
1. E-Mail eingeben
2. Code senden Button klicken
3. Code empfangen und eingeben
4. Passwort eingeben
5. Dummy-Input-Trick anwenden
6. Weiter Button klicken
Args:
account_data: Account-Daten
registration_method: "email" oder "phone"
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
if registration_method == "email":
return self._execute_email_workflow(account_data)
elif registration_method == "phone":
return self._execute_phone_workflow(account_data)
else:
logger.error(f"Unbekannte Registrierungsmethode: {registration_method}")
return False
except Exception as e:
logger.error(f"Fehler im optimierten Registrierungsworkflow: {e}")
return False
def _execute_email_workflow(self, account_data: Dict[str, Any]) -> bool:
"""
Führt den optimierten E-Mail-Registrierungsworkflow aus.
KORRIGIERTER WORKFLOW: E-Mail → Code senden → Code eingeben → Passwort → Dummy-Trick → Weiter
Args:
account_data: Account-Daten
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.info("=== STARTE OPTIMIERTEN E-MAIL-WORKFLOW ===")
# SCHRITT 1: E-Mail-Feld ausfüllen
logger.info("SCHRITT 1/6: E-Mail-Adresse eingeben")
if not self._fill_email_field(account_data["email"]):
logger.error("Fehler beim Ausfüllen des E-Mail-Feldes")
return False
# SCHRITT 2: Code senden Button klicken (VOR Passwort!)
logger.info("SCHRITT 2/6: Code senden Button klicken")
if not self._click_send_code_button():
logger.error("Fehler beim Klicken des Code-senden-Buttons")
return False
# SCHRITT 3: Verifizierungscode empfangen und eingeben
logger.info("SCHRITT 3/6: Auf Code warten und eingeben")
if not self._handle_email_verification(account_data["email"]):
logger.error("Fehler bei der E-Mail-Verifizierung")
return False
# SCHRITT 4: Passwort-Feld ausfüllen (NACH Code-Eingabe!)
logger.info("SCHRITT 4/6: Passwort eingeben (nach Code-Verifizierung)")
if not self._fill_password_field(account_data["password"]):
logger.error("Fehler beim Ausfüllen des Passwort-Feldes")
return False
# SCHRITT 5: Dummy-Input-Trick anwenden
logger.info("SCHRITT 5/6: Dummy-Input-Trick anwenden")
if not self._apply_dummy_input_trick():
logger.error("Fehler beim Dummy-Input-Trick")
return False
# SCHRITT 6: Weiter Button klicken
logger.info("SCHRITT 6/6: Weiter Button klicken")
if not self._click_continue_button():
logger.error("Fehler beim Klicken des Weiter-Buttons")
return False
logger.info("=== E-MAIL-WORKFLOW ERFOLGREICH ABGESCHLOSSEN ===")
# Kurze Pause für UI-Updates - das Weiter-Button sollte jetzt aktiviert sein
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
except Exception as e:
logger.error(f"Fehler im E-Mail-Workflow: {e}")
return False
def _fill_email_field(self, email: str) -> bool:
"""
Füllt das E-Mail-Feld mit robusten Selektoren aus.
Args:
email: E-Mail-Adresse
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Robuste E-Mail-Feld-Selektoren (in Prioritätsreihenfolge)
email_selectors = [
"input[placeholder*='E-Mail']",
"input[placeholder*='Email']",
"input[type='email']",
"input[name='email']",
"input[aria-label*='Email']",
"input[aria-label*='E-Mail']",
self.selectors.EMAIL_FIELD,
self.selectors.EMAIL_FIELD_ALT
]
for i, selector in enumerate(email_selectors):
try:
if self.automation.browser.is_element_visible(selector, timeout=2000):
success = self.automation.browser.fill_form_field(selector, email, human_typing=True)
if success:
logger.info(f"E-Mail-Feld erfolgreich ausgefüllt mit Selektor {i+1}: {email}")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
except Exception as e:
logger.debug(f"E-Mail-Selektor {i+1} fehlgeschlagen: {e}")
continue
# Fallback: Fuzzy-Matching
success = self.automation.ui_helper.fill_field_fuzzy(
["E-Mail-Adresse", "Email", "E-Mail"],
email,
email_selectors[0]
)
if success:
logger.info(f"E-Mail-Feld über Fuzzy-Matching ausgefüllt: {email}")
return True
logger.error("Konnte E-Mail-Feld mit keinem Selektor ausfüllen")
return False
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des E-Mail-Feldes: {e}")
return False
def _fill_password_field(self, password: str) -> bool:
"""
Füllt das Passwort-Feld mit robusten Selektoren aus.
Args:
password: Passwort
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Robuste Passwort-Feld-Selektoren (in Prioritätsreihenfolge)
password_selectors = [
"input[type='password'][placeholder*='Passwort']",
"input[type='password'][placeholder*='Password']",
"input[type='password']",
"input[name='password']",
"input[aria-label*='Password']",
"input[aria-label*='Passwort']",
self.selectors.PASSWORD_FIELD,
self.selectors.PASSWORD_FIELD_ALT
]
for i, selector in enumerate(password_selectors):
try:
if self.automation.browser.is_element_visible(selector, timeout=2000):
success = self.automation.browser.fill_form_field(selector, password, human_typing=True)
if success:
logger.info(f"Passwort-Feld erfolgreich ausgefüllt mit Selektor {i+1}")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
except Exception as e:
logger.debug(f"Passwort-Selektor {i+1} fehlgeschlagen: {e}")
continue
# Fallback: Fuzzy-Matching
success = self.automation.ui_helper.fill_field_fuzzy(
["Passwort", "Password"],
password,
password_selectors[0]
)
if success:
logger.info("Passwort-Feld über Fuzzy-Matching ausgefüllt")
return True
logger.error("Konnte Passwort-Feld mit keinem Selektor ausfüllen")
return False
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des Passwort-Feldes: {e}")
return False
def _click_send_code_button(self) -> bool:
"""
Klickt den 'Code senden'-Button mit robusten Selektoren.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Kurze Pause vor dem Klicken
self.automation.human_behavior.random_delay(0.5, 1.0)
# Robuste Send-Code-Button-Selektoren
send_code_selectors = [
"button[data-e2e='send-code-button']",
"button:has-text('Code senden')",
"button:has-text('Send code')",
"button[type='submit']",
"button.css-10nhlj9-Button-StyledButton",
self.selectors.SEND_CODE_BUTTON
]
for i, selector in enumerate(send_code_selectors):
try:
if self.automation.browser.is_element_visible(selector, timeout=2000):
# Prüfe, ob Button enabled ist
element = self.automation.browser.wait_for_selector(selector, timeout=1000)
if element:
is_disabled = element.get_attribute("disabled")
if is_disabled:
logger.debug(f"Send-Code-Button {i+1} ist disabled, versuche nächsten")
continue
success = self.automation.browser.click_element(selector)
if success:
logger.info(f"'Code senden'-Button erfolgreich geklickt mit Selektor {i+1}")
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
except Exception as e:
logger.debug(f"Send-Code-Selektor {i+1} fehlgeschlagen: {e}")
continue
# Fallback: Fuzzy-Button-Matching
success = self.automation.ui_helper.click_button_fuzzy(
["Code senden", "Send code", "Senden"],
send_code_selectors[0]
)
if success:
logger.info("'Code senden'-Button über Fuzzy-Matching geklickt")
return True
logger.error("Konnte 'Code senden'-Button mit keinem Selektor klicken")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken des 'Code senden'-Buttons: {e}")
return False
def _handle_email_verification(self, email: str) -> bool:
"""
Behandelt die E-Mail-Verifizierung mit verbessertem Timing.
Args:
email: E-Mail-Adresse
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.info("Warte auf E-Mail-Verifizierungscode...")
# Warte auf den Code mit exponential backoff
verification_code = self._get_email_verification_code_with_retry(email)
if not verification_code:
logger.error("Konnte keinen Verifizierungscode empfangen")
return False
logger.info(f"Verifizierungscode empfangen: {verification_code}")
# Code-Feld ausfüllen
if not self._fill_verification_code_field(verification_code):
logger.error("Konnte Verifizierungscode-Feld nicht ausfüllen")
return False
logger.info("Verifizierungscode erfolgreich eingegeben")
# Kurze Pause nach Code-Eingabe
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
except Exception as e:
logger.error(f"Fehler bei der E-Mail-Verifizierung: {e}")
return False
def _get_email_verification_code_with_retry(self, email: str, max_attempts: int = 30) -> Optional[str]:
"""
Ruft den E-Mail-Verifizierungscode mit Retry-Logik ab.
Args:
email: E-Mail-Adresse
max_attempts: Maximale Anzahl Versuche
Returns:
Optional[str]: Verifizierungscode oder None
"""
try:
for attempt in range(max_attempts):
# Exponential backoff: 2s, 3s, 4.5s, 6.75s, ... (max 30s)
delay = min(2 * (1.5 ** attempt), 30)
logger.debug(f"E-Mail-Abruf Versuch {attempt + 1}/{max_attempts} (Wartezeit: {delay:.1f}s)")
# Versuche Code abzurufen
code = self.automation.email_handler.get_verification_code(
target_email=email,
platform="tiktok",
max_attempts=1, # Nur ein Versuch pro Iteration
delay_seconds=1
)
if code:
logger.info(f"E-Mail-Code nach {attempt + 1} Versuchen empfangen")
return code
# Warte vor nächstem Versuch
time.sleep(delay)
logger.warning(f"Kein E-Mail-Code nach {max_attempts} Versuchen empfangen")
return None
except Exception as e:
logger.error(f"Fehler beim E-Mail-Code-Abruf: {e}")
return None
def _fill_verification_code_field(self, code: str) -> bool:
"""
Füllt das Verifizierungscode-Feld aus.
Args:
code: Verifizierungscode
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Robuste Verifizierungscode-Feld-Selektoren
code_selectors = [
"input[placeholder*='sechsstelligen Code']",
"input[placeholder*='verification code']",
"input[placeholder*='Code']",
"input[name='verificationCode']",
"input[type='text'][maxlength='6']",
self.selectors.VERIFICATION_CODE_FIELD,
self.selectors.VERIFICATION_CODE_FIELD_ALT
]
for i, selector in enumerate(code_selectors):
try:
if self.automation.browser.is_element_visible(selector, timeout=3000):
# Normale Code-Eingabe (Dummy-Trick wird separat angewendet)
success = self.automation.browser.fill_form_field(selector, code, human_typing=True)
if success:
logger.info(f"Verifizierungscode-Feld erfolgreich ausgefüllt mit Selektor {i+1}")
return True
except Exception as e:
logger.debug(f"Code-Selektor {i+1} fehlgeschlagen: {e}")
continue
# Fallback: Fuzzy-Matching
success = self.automation.ui_helper.fill_field_fuzzy(
["Gib den sechsstelligen Code ein", "Enter verification code", "Verification code"],
code,
code_selectors[0]
)
if success:
logger.info("Verifizierungscode-Feld über Fuzzy-Matching ausgefüllt")
return True
logger.error("Konnte Verifizierungscode-Feld mit keinem Selektor ausfüllen")
return False
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des Verifizierungscode-Feldes: {e}")
return False
def _execute_phone_workflow(self, account_data: Dict[str, Any]) -> bool:
"""
Führt den Telefon-Registrierungsworkflow aus.
Args:
account_data: Account-Daten
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.info("=== STARTE TELEFON-WORKFLOW ===")
# Telefonnummer aufbereiten
phone_number = account_data["phone"]
if phone_number.startswith("+"):
parts = phone_number.split(" ", 1)
if len(parts) > 1:
phone_number = parts[1]
# Telefonnummer eingeben
phone_success = self.automation.ui_helper.fill_field_fuzzy(
["Telefonnummer", "Phone number", "Phone"],
phone_number,
self.selectors.PHONE_FIELD
)
if not phone_success:
logger.error("Konnte Telefonnummer-Feld nicht ausfüllen")
return False
logger.info(f"Telefonnummer-Feld ausgefüllt: {phone_number}")
self.automation.human_behavior.random_delay(0.5, 1.5)
# Code senden
if not self._click_send_code_button():
return False
# SMS-Code behandeln (Platzhalter)
logger.warning("SMS-Verifizierung ist noch nicht vollständig implementiert")
return True
except Exception as e:
logger.error(f"Fehler im Telefon-Workflow: {e}")
return False
# Hilfsmethoden für die Basis-Funktionalität
def _validate_registration_inputs(self, full_name: str, age: int,
registration_method: str, phone_number: str) -> bool:
"""Validiert die Eingaben für die Registrierung."""
if not full_name or len(full_name) < 3:
logger.error("Ungültiger vollständiger Name")
return False
if age < 13:
logger.error("Benutzer muss mindestens 13 Jahre alt sein")
return False
if registration_method not in ["email", "phone"]:
logger.error(f"Ungültige Registrierungsmethode: {registration_method}")
return False
if registration_method == "phone" and not phone_number:
logger.error("Telefonnummer erforderlich für Registrierung via Telefon")
return False
return True
def _generate_account_data(self, full_name: str, age: int, registration_method: str,
phone_number: str, **kwargs) -> Dict[str, Any]:
"""Generiert Account-Daten für die Registrierung."""
# Benutzername generieren
username = kwargs.get("username")
if not username:
username = self.automation.username_generator.generate_username("tiktok", full_name)
# Passwort generieren
password = kwargs.get("password")
if not password:
password = self.automation.password_generator.generate_password("tiktok")
# E-Mail generieren (falls nötig)
email = None
if registration_method == "email":
email_prefix = username.lower().replace(".", "").replace("_", "")
email = f"{email_prefix}@{self.automation.email_domain}"
# Geburtsdatum generieren
birthday = self.automation.birthday_generator.generate_birthday_components("tiktok", age)
# Account-Daten zusammenstellen
account_data = {
"username": username,
"password": password,
"full_name": full_name,
"email": email,
"phone": phone_number,
"birthday": birthday,
"age": age,
"registration_method": registration_method
}
logger.debug(f"Account-Daten generiert: {account_data['username']}")
return account_data
def _create_error_result(self, error_msg: str, stage: str, account_data: Dict[str, Any]) -> Dict[str, Any]:
"""Erstellt ein standardisiertes Fehler-Result."""
return {
"success": False,
"error": error_msg,
"stage": stage,
"account_data": account_data
}
# Platzhalter für weitere Methoden (Navigation, etc.)
def _navigate_to_homepage(self) -> bool:
"""Navigiert zur TikTok-Startseite."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _handle_cookie_banner(self) -> bool:
"""Behandelt den Cookie-Banner."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _click_login_button(self) -> bool:
"""Klickt auf den Anmelden-Button."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _click_register_link(self) -> bool:
"""Klickt auf den Registrieren-Link."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _click_phone_email_option(self) -> bool:
"""Klickt auf die Telefon/E-Mail-Option."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _select_registration_method(self, method: str) -> bool:
"""Wählt die Registrierungsmethode aus."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _enter_birthday(self, birthday: Dict[str, Any]) -> bool:
"""Gibt das Geburtsdatum ein."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _create_username(self, account_data: Dict[str, Any]) -> bool:
"""Erstellt einen Benutzernamen."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _check_registration_success(self) -> bool:
"""Überprüft, ob die Registrierung erfolgreich war."""
# Diese Methode würde aus der ursprünglichen Implementierung übernommen
return True
def _apply_dummy_input_trick(self) -> bool:
"""
Wendet den Dummy-Input-Trick auf das Code-Feld an.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.debug("Wende Dummy-Input-Trick an")
# Code-Feld-Selektoren
code_selectors = [
"input[placeholder*='sechsstelligen Code']",
"input[placeholder*='verification code']",
"input[placeholder*='Code']",
"input[name='verificationCode']",
"input[type='text'][maxlength='6']",
self.selectors.VERIFICATION_CODE_FIELD,
self.selectors.VERIFICATION_CODE_FIELD_ALT
]
for i, selector in enumerate(code_selectors):
try:
if self.automation.browser.is_element_visible(selector, timeout=2000):
# Dummy-Input-Trick anwenden
success = self.automation.browser.fill_form_field_with_dummy_trick(
selector, "123456", timeout=3000
)
if success:
logger.info(f"Dummy-Input-Trick erfolgreich angewendet mit Selektor {i+1}")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
except Exception as e:
logger.debug(f"Dummy-Input-Trick Selektor {i+1} fehlgeschlagen: {e}")
continue
logger.warning("Dummy-Input-Trick konnte nicht angewendet werden")
return True # Nicht kritisch - fortfahren
except Exception as e:
logger.error(f"Kritischer Fehler beim Dummy-Input-Trick: {e}")
return True # Nicht kritisch - fortfahren
def _click_continue_button(self) -> bool:
"""
Klickt den Weiter/Continue-Button mit robusten Selektoren.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.debug("Klicke Weiter-Button")
# Robuste Continue-Button-Selektoren
continue_selectors = [
"button[data-e2e='continue-button']",
"button:has-text('Weiter')",
"button:has-text('Continue')",
"button:has-text('Fortfahren')",
"button[type='submit']",
"button.css-10nhlj9-Button-StyledButton:not([disabled])"
]
for i, selector in enumerate(continue_selectors):
try:
if self.automation.browser.is_element_visible(selector, timeout=3000):
# Prüfe, ob Button enabled ist
element = self.automation.browser.wait_for_selector(selector, timeout=1000)
if element:
is_disabled = element.get_attribute("disabled")
aria_disabled = element.get_attribute("aria-disabled")
if is_disabled or aria_disabled == "true":
logger.debug(f"Continue-Button {i+1} ist disabled, versuche nächsten")
continue
# Button klicken
success = self.automation.browser.click_element(selector)
if success:
logger.info(f"Weiter-Button erfolgreich geklickt mit Selektor {i+1}")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
except Exception as e:
logger.debug(f"Continue-Selektor {i+1} fehlgeschlagen: {e}")
continue
# Fallback: Fuzzy-Button-Matching
try:
success = self.automation.ui_helper.click_button_fuzzy(
["Weiter", "Continue", "Fortfahren", "Next"],
continue_selectors[0]
)
if success:
logger.info("Weiter-Button über Fuzzy-Matching geklickt")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
except Exception as e:
logger.debug(f"Continue Fuzzy-Matching fehlgeschlagen: {e}")
logger.error("Weiter-Button konnte nicht geklickt werden")
return False
except Exception as e:
logger.error(f"Kritischer Fehler beim Weiter-Button: {e}")
return False

Datei anzeigen

@ -0,0 +1,225 @@
"""
TikTok-Selektoren - CSS-Selektoren und XPath-Ausdrücke für die TikTok-Automatisierung
Mit Text-Matching-Funktionen für robuste Element-Erkennung
"""
from typing import List, Dict, Optional, Any
class TikTokSelectors:
"""
Zentrale Sammlung aller Selektoren für die TikTok-Automatisierung.
Bei Änderungen der TikTok-Webseite müssen nur hier Anpassungen vorgenommen werden.
Enthält auch Fuzzy-Text-Matching-Daten für robustere Element-Erkennung.
"""
# URL-Konstanten
BASE_URL = "https://www.tiktok.com"
SIGNUP_URL = "https://www.tiktok.com/signup"
LOGIN_URL = "https://www.tiktok.com/login"
EXPLORE_URL = "https://www.tiktok.com/explore"
# Anmelden/Registrieren-Buttons Hauptseite
LOGIN_BUTTON = "button#header-login-button"
LOGIN_BUTTON_HEADER = "button#header-login-button"
LOGIN_BUTTON_CLASS = "button.TUXButton:has-text('Anmelden')"
LOGIN_BUTTON_LEFT = "button#header-login-button"
LOGIN_BUTTON_RIGHT = "button#top-right-action-bar-login-button"
LOGIN_BUTTON_TOP = "button#header-login-button"
LOGIN_BUTTON_TOP_RIGHT = "button#top-right-action-bar-login-button"
LOGIN_BUTTON_SIDEBAR = "button[data-e2e='login-button-sidebar']"
LOGIN_BUTTON_FALLBACK = "//button[contains(text(), 'Anmelden')]"
SIGNUP_LINK = "span[data-e2e='bottom-sign-up']"
SIGNUP_LINK_FALLBACK = "a[href*='/signup']"
# Login-Dialog Optionen
LOGIN_DIALOG = "div[role='dialog']"
LOGIN_EMAIL_PHONE_OPTION = "div[data-e2e='channel-item']"
LOGIN_EMAIL_USERNAME_LINK = "a[href='/login/phone-or-email/email']"
# Cookie-Dialog
COOKIE_DIALOG = "div[role='dialog'][data-testid='cookie-banner']"
COOKIE_ACCEPT_BUTTON = "button[data-testid='accept-all-cookies']"
# Registrierungsdialog - Methoden
REGISTER_DIALOG_TITLE = "h1:contains('Registrieren')"
REGISTER_LINK = "a:contains('Registrieren')"
REGISTER_LINK_FALLBACK = "//a[contains(text(), 'Registrieren')]"
REGISTRATION_DIALOG = "div[role='dialog']"
PHONE_EMAIL_BUTTON = "div[data-e2e='channel-item']"
PHONE_EMAIL_OPTION = "div[data-e2e='channel-item']"
PHONE_EMAIL_OPTION_FALLBACK = "//div[contains(text(), 'Telefonnummer oder E-Mail')]"
EMAIL_OPTION = "a[href*='/signup/phone-or-email/email']"
EMAIL_OPTION_FALLBACK = "//a[contains(text(), 'E-Mail')]"
PHONE_OPTION = "a[href*='/signup/phone-or-email/phone']"
PHONE_OPTION_FALLBACK = "//a[contains(text(), 'Telefon')]"
REGISTER_WITH_EMAIL = "a[href*='/signup/phone-or-email/email']"
REGISTER_WITH_PHONE = "a[href*='/signup/phone-or-email/phone']"
# Geburtsdatum-Selektoren
BIRTHDAY_MONTH_DROPDOWN = "select[name='month']"
BIRTHDAY_DAY_DROPDOWN = "select[name='day']"
BIRTHDAY_YEAR_DROPDOWN = "select[name='year']"
BIRTHDAY_MONTH_SELECT = "div.css-1leicpq-DivSelectLabel:contains('Monat')"
BIRTHDAY_DAY_SELECT = "div.css-1leicpq-DivSelectLabel:contains('Tag')"
BIRTHDAY_YEAR_SELECT = "div.css-1leicpq-DivSelectLabel:contains('Jahr')"
BIRTHDAY_DROPDOWN_OPTION = "div[role='option']"
BIRTHDAY_DROPDOWN_CONTAINER = "div.css-1leicpq-DivSelectLabel"
BIRTHDAY_ARROW = "svg.css-gz151e-StyledArrowTriangleDownLargeFill"
# Formularfelder - E-Mail-Registrierung
EMAIL_FIELD = "input[placeholder='E-Mail-Adresse']"
EMAIL_FIELD_ALT = "input[name='email']"
PASSWORD_FIELD = "input[placeholder='Passwort']"
PASSWORD_FIELD_ALT = "input[type='password']"
VERIFICATION_CODE_FIELD = "input[placeholder*='sechsstelligen Code']"
VERIFICATION_CODE_FIELD_ALT = "input[placeholder='Gib den sechsstelligen Code ein']"
USERNAME_FIELD = "input[placeholder='Benutzername']"
USERNAME_FIELD_ALT = "input[name='new-username']"
# Formularfelder - Telefon-Registrierung
COUNTRY_CODE_SELECT = "div[role='combobox']"
PHONE_FIELD = "input[placeholder='Telefonnummer']"
# Buttons
SEND_CODE_BUTTON = "button[data-e2e='send-code-button']"
RESEND_CODE_BUTTON = "button:contains('Code erneut senden')"
CONTINUE_BUTTON = "button[type='submit']"
CONTINUE_BUTTON_ALT = "button.e1w6iovg0"
REGISTER_BUTTON = "button:contains('Registrieren')"
REGISTER_BUTTON_ALT = "button[type='submit']:contains('Registrieren')"
SKIP_BUTTON = "div:contains('Überspringen')"
SKIP_BUTTON_ALT = "div.css-4y1w75-DivTextContainer"
SKIP_USERNAME_BUTTON = "button:contains('Überspringen')"
# Checkbox
NEWSLETTER_CHECKBOX = "input[type='checkbox']"
# Login-Formularfelder
LOGIN_EMAIL_FIELD = "input[name='username'][placeholder='E-Mail-Adresse oder Benutzername']"
LOGIN_EMAIL_FIELD_ALT = "input.tiktok-11to27l-InputContainer[name='username']"
LOGIN_PASSWORD_FIELD = "input[type='password'][placeholder='Passwort']"
LOGIN_PASSWORD_FIELD_ALT = "input.tiktok-wv3bkt-InputContainer[type='password']"
LOGIN_SUBMIT_BUTTON = "button[type='submit'][data-e2e='login-button']"
LOGIN_SUBMIT_BUTTON_ALT = "button.tiktok-11sviba-Button-StyledButton[data-e2e='login-button']"
# Erfolgs-Indikatoren für Registrierung
SUCCESS_INDICATORS = [
"a[href='/foryou']",
"a[href='/explore']",
"button[data-e2e='profile-icon']",
"svg[data-e2e='profile-icon']"
]
# Links für Nutzungsbedingungen und Datenschutz
TERMS_LINK = "a:contains('Nutzungsbedingungen')"
PRIVACY_LINK = "a:contains('Datenschutzerklärung')"
# Login-Fehler
ERROR_MESSAGE = "span.error-message"
LOGIN_ERROR_CONTAINER = "div[class*='error']"
# Text-Matching-Parameter für Fuzzy-Matching
TEXT_MATCH = {
# Formularfelder
"form_fields": {
"email": ["E-Mail-Adresse", "E-Mail", "Email", "Mail"],
"phone": ["Telefonnummer", "Telefon", "Phone", "Mobile"],
"password": ["Passwort", "Password"],
"verification_code": ["Bestätigungscode", "Code", "Verifizierungscode", "Sicherheitscode"],
"username": ["Benutzername", "Username", "Name"]
},
# Buttons
"buttons": {
"send_code": ["Code senden", "Senden", "Send code", "Verification code", "Send"],
"continue": ["Weiter", "Continue", "Next", "Fortfahren"],
"register": ["Registrieren", "Register", "Sign up", "Konto erstellen"],
"skip": ["Überspringen", "Skip", "Later", "Später", "Nicht jetzt"],
},
# Fehler-Indikatoren
"error_indicators": [
"Fehler", "Error", "Leider", "Ungültig", "Invalid", "Nicht verfügbar",
"Fehlgeschlagen", "Problem", "Failed", "Nicht möglich", "Bereits verwendet",
"Too many attempts", "Zu viele Versuche", "Rate limit", "Bitte warte"
],
# Bestätigungscode-Texte in E-Mails
"email_verification_patterns": [
"ist dein Bestätigungscode",
"ist dein TikTok-Code",
"is your TikTok code",
"is your verification code",
"Dein Bestätigungscode lautet",
"Your verification code is"
],
# E-Mail-Betreff-Muster für TikTok
"email_subject_patterns": [
"ist dein Bestätigungscode",
"is your confirmation code",
"TikTok verification code",
"TikTok Bestätigungscode"
]
}
@classmethod
def get_field_labels(cls, field_type: str) -> List[str]:
"""
Gibt die möglichen Bezeichnungen für ein Formularfeld zurück.
Args:
field_type: Typ des Formularfelds (z.B. "email", "phone")
Returns:
List[str]: Liste mit möglichen Bezeichnungen
"""
return cls.TEXT_MATCH["form_fields"].get(field_type, [])
@classmethod
def get_button_texts(cls, button_type: str) -> List[str]:
"""
Gibt die möglichen Texte für einen Button zurück.
Args:
button_type: Typ des Buttons (z.B. "send_code", "continue")
Returns:
List[str]: Liste mit möglichen Button-Texten
"""
return cls.TEXT_MATCH["buttons"].get(button_type, [])
@classmethod
def get_error_indicators(cls) -> List[str]:
"""
Gibt die möglichen Texte für Fehlerindikatoren zurück.
Returns:
List[str]: Liste mit möglichen Fehlerindikator-Texten
"""
return cls.TEXT_MATCH["error_indicators"]
@classmethod
def get_email_verification_patterns(cls) -> List[str]:
"""
Gibt die möglichen Texte für Bestätigungscodes in E-Mails zurück.
Returns:
List[str]: Liste mit möglichen E-Mail-Bestätigungscode-Texten
"""
return cls.TEXT_MATCH["email_verification_patterns"]
@classmethod
def get_month_option_selector(cls, month: int) -> str:
"""Returns selector for month option."""
return f"option[value='{month}']"
@classmethod
def get_day_option_selector(cls, day: int) -> str:
"""Returns selector for day option."""
return f"option[value='{day}']"
@classmethod
def get_year_option_selector(cls, year: int) -> str:
"""Returns selector for year option."""
return f"option[value='{year}']"

Datei anzeigen

@ -0,0 +1,520 @@
"""
TikTok-UI-Helper - Hilfsmethoden für die Interaktion mit der TikTok-UI
"""
import time
import re
from typing import Dict, List, Any, Optional, Tuple, Union, Callable
from .tiktok_selectors import TikTokSelectors
from utils.text_similarity import TextSimilarity, fuzzy_find_element, click_fuzzy_button
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("tiktok_ui_helper")
class TikTokUIHelper:
"""
Hilfsmethoden für die Interaktion mit der TikTok-Benutzeroberfläche.
Bietet robuste Funktionen zum Finden und Interagieren mit UI-Elementen.
"""
def __init__(self, automation):
"""
Initialisiert den TikTok-UI-Helper.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
# Browser wird direkt von automation verwendet
self.selectors = TikTokSelectors()
# Initialisiere TextSimilarity für Fuzzy-Matching
self.text_similarity = TextSimilarity(default_threshold=0.7)
logger.debug("TikTok-UI-Helper initialisiert")
def _ensure_browser(self) -> bool:
"""
Stellt sicher, dass die Browser-Referenz verfügbar ist.
Returns:
bool: True wenn Browser verfügbar, False sonst
"""
if self.automation.browser is None:
logger.error("Browser-Referenz nicht verfügbar")
return False
return True
def fill_field_fuzzy(self, field_labels: Union[str, List[str]],
value: str, fallback_selector: str = None,
threshold: float = 0.7, timeout: int = 5000) -> bool:
"""
Füllt ein Formularfeld mit Fuzzy-Text-Matching aus.
Args:
field_labels: Bezeichner oder Liste von Bezeichnern des Feldes
value: Einzugebender Wert
fallback_selector: CSS-Selektor für Fallback
threshold: Schwellenwert für die Textähnlichkeit (0-1)
timeout: Zeitlimit für die Suche in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Normalisiere field_labels zu einer Liste
if isinstance(field_labels, str):
field_labels = [field_labels]
# Versuche, das Feld mit Fuzzy-Matching zu finden
element = fuzzy_find_element(
self.automation.browser.page,
field_labels,
selector_type="input",
threshold=threshold,
wait_time=timeout
)
if element:
# Versuche, das Feld zu fokussieren und den Wert einzugeben
element.focus()
time.sleep(0.1)
element.fill("") # Leere das Feld zuerst
time.sleep(0.2)
# Text menschenähnlich eingeben
for char in value:
element.type(char, delay=self.automation.human_behavior.delays["typing_per_char"] * 1000)
time.sleep(0.01)
logger.info(f"Feld mit Fuzzy-Matching gefüllt: {value}")
return True
# Fuzzy-Matching fehlgeschlagen, versuche über Attribute
if fallback_selector:
field_success = self.automation.browser.fill_form_field(fallback_selector, value)
if field_success:
logger.info(f"Feld mit Fallback-Selektor gefüllt: {fallback_selector}")
return True
# Versuche noch alternative Selektoren basierend auf field_labels
for label in field_labels:
# Versuche aria-label Attribut
aria_selector = f"input[aria-label='{label}'], textarea[aria-label='{label}']"
if self.automation.browser.is_element_visible(aria_selector, timeout=1000):
if self.automation.browser.fill_form_field(aria_selector, value):
logger.info(f"Feld über aria-label gefüllt: {label}")
return True
# Versuche placeholder Attribut
placeholder_selector = f"input[placeholder*='{label}'], textarea[placeholder*='{label}']"
if self.automation.browser.is_element_visible(placeholder_selector, timeout=1000):
if self.automation.browser.fill_form_field(placeholder_selector, value):
logger.info(f"Feld über placeholder gefüllt: {label}")
return True
# Versuche name Attribut
name_selector = f"input[name='{label.lower().replace(' ', '')}']"
if self.automation.browser.is_element_visible(name_selector, timeout=1000):
if self.automation.browser.fill_form_field(name_selector, value):
logger.info(f"Feld über name-Attribut gefüllt: {label}")
return True
logger.warning(f"Konnte kein Feld für '{field_labels}' finden oder ausfüllen")
return False
except Exception as e:
logger.error(f"Fehler beim Fuzzy-Ausfüllen des Feldes: {e}")
return False
def click_button_fuzzy(self, button_texts: Union[str, List[str]],
fallback_selector: str = None, threshold: float = 0.7,
timeout: int = 5000) -> bool:
"""
Klickt einen Button mit Fuzzy-Text-Matching.
Args:
button_texts: Text oder Liste von Texten des Buttons
fallback_selector: CSS-Selektor für Fallback
threshold: Schwellenwert für die Textähnlichkeit (0-1)
timeout: Zeitlimit für die Suche in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Normalisiere button_texts zu einer Liste
if isinstance(button_texts, str):
button_texts = [button_texts]
# Logging der Suche
logger.info(f"Suche nach Button mit Texten: {button_texts}")
if not button_texts or button_texts == [[]]:
logger.warning("Leere Button-Text-Liste angegeben!")
return False
# TikTok-spezifische Selektoren zuerst prüfen
# Diese Selektoren sind häufig in TikTok's UI zu finden
tiktok_button_selectors = [
"button[type='submit']",
"button[data-e2e='send-code-button']",
"button.e1w6iovg0",
"button.css-10nhlj9-Button-StyledButton"
]
for selector in tiktok_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
button_element = self.automation.browser.wait_for_selector(selector, timeout=1000)
if button_element:
button_text = button_element.inner_text().strip()
# Überprüfe, ob der Button-Text mit einem der gesuchten Texte übereinstimmt
for text in button_texts:
if self.text_similarity.is_similar(text, button_text, threshold=threshold):
logger.info(f"Button mit passendem Text gefunden: '{button_text}'")
button_element.click()
return True
# Die allgemeine fuzzy_click_button-Funktion verwenden
result = click_fuzzy_button(
self.automation.browser.page,
button_texts,
threshold=threshold,
timeout=timeout
)
if result:
logger.info(f"Button mit Fuzzy-Matching geklickt")
return True
# Wenn Fuzzy-Matching fehlschlägt, versuche mit fallback_selector
if fallback_selector:
logger.info(f"Versuche Fallback-Selektor: {fallback_selector}")
if self.automation.browser.click_element(fallback_selector):
logger.info(f"Button mit Fallback-Selektor geklickt: {fallback_selector}")
return True
# Versuche alternative Methoden
# 1. Versuche über aria-label
for text in button_texts:
if not text:
continue
aria_selector = f"button[aria-label*='{text}'], [role='button'][aria-label*='{text}']"
if self.automation.browser.is_element_visible(aria_selector, timeout=1000):
if self.automation.browser.click_element(aria_selector):
logger.info(f"Button über aria-label geklickt: {text}")
return True
# 2. Versuche über role='button' mit Text
for text in button_texts:
if not text:
continue
xpath_selector = f"//div[@role='button' and contains(., '{text}')]"
if self.automation.browser.is_element_visible(xpath_selector, timeout=1000):
if self.automation.browser.click_element(xpath_selector):
logger.info(f"Button über role+text geklickt: {text}")
return True
# 3. Versuche über Link-Text
for text in button_texts:
if not text:
continue
link_selector = f"//a[contains(text(), '{text}')]"
if self.automation.browser.is_element_visible(link_selector, timeout=1000):
if self.automation.browser.click_element(link_selector):
logger.info(f"Link mit passendem Text geklickt: {text}")
return True
# 4. Als letzten Versuch, klicke auf einen beliebigen Button
logger.warning("Kein spezifischer Button gefunden, versuche beliebigen Button zu klicken")
buttons = self.automation.browser.page.query_selector_all("button")
if buttons and len(buttons) > 0:
for button in buttons:
visible = button.is_visible()
if visible:
logger.info("Klicke auf beliebigen sichtbaren Button")
button.click()
return True
logger.warning(f"Konnte keinen Button für '{button_texts}' finden oder klicken")
return False
except Exception as e:
logger.error(f"Fehler beim Fuzzy-Klicken des Buttons: {e}")
return False
def select_dropdown_option(self, dropdown_selector: str, option_value: str,
option_type: str = "text", timeout: int = 5000) -> bool:
"""
Wählt eine Option aus einer Dropdown-Liste aus.
Args:
dropdown_selector: Selektor für das Dropdown-Element
option_value: Wert oder Text der auszuwählenden Option
option_type: "text" für Text-Matching, "value" für Wert-Matching
timeout: Zeitlimit in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Auf Dropdown-Element klicken, um die Optionen anzuzeigen
dropdown_element = self.automation.browser.wait_for_selector(dropdown_selector, timeout=timeout)
if not dropdown_element:
logger.warning(f"Dropdown-Element nicht gefunden: {dropdown_selector}")
return False
# Dropdown öffnen
dropdown_element.click()
time.sleep(0.5) # Kurz warten, damit die Optionen angezeigt werden
# Optionen suchen
option_selector = "div[role='option']"
options = self.automation.browser.page.query_selector_all(option_selector)
if not options or len(options) == 0:
logger.warning(f"Keine Optionen gefunden für Dropdown: {dropdown_selector}")
return False
# Option nach Text oder Wert suchen
selected = False
for option in options:
option_text = option.inner_text().strip()
if option_type == "text":
if option_text == option_value or self.text_similarity.is_similar(option_text, option_value, threshold=0.9):
option.click()
selected = True
break
elif option_type == "value":
option_val = option.get_attribute("value") or ""
if option_val == option_value:
option.click()
selected = True
break
if not selected:
logger.warning(f"Keine passende Option für '{option_value}' gefunden")
return False
logger.info(f"Option '{option_value}' im Dropdown ausgewählt")
return True
except Exception as e:
logger.error(f"Fehler bei der Auswahl der Dropdown-Option: {e}")
return False
def check_for_error(self, error_selectors: List[str] = None,
error_texts: List[str] = None) -> Optional[str]:
"""
Überprüft, ob Fehlermeldungen angezeigt werden.
Args:
error_selectors: Liste mit CSS-Selektoren für Fehlermeldungen
error_texts: Liste mit typischen Fehlertexten
Returns:
Optional[str]: Die Fehlermeldung oder None, wenn keine Fehler gefunden wurden
"""
if not self._ensure_browser():
return None
try:
# Standardselektoren verwenden, wenn keine angegeben sind
if error_selectors is None:
error_selectors = [
"div[role='alert']",
"p[class*='error']",
"span[class*='error']",
".error-message"
]
# Standardfehlertexte verwenden, wenn keine angegeben sind
if error_texts is None:
error_texts = TikTokSelectors.get_error_indicators()
# 1. Nach Fehlerselektoren suchen
for selector in error_selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if element:
error_text = element.text_content()
if error_text and len(error_text.strip()) > 0:
logger.info(f"Fehlermeldung gefunden (Selektor): {error_text.strip()}")
return error_text.strip()
# 2. Alle Texte auf der Seite durchsuchen
page_content = self.automation.browser.page.content()
for error_text in error_texts:
if error_text.lower() in page_content.lower():
# Versuche, den genauen Fehlertext zu extrahieren
matches = re.findall(r'<[^>]*>([^<]*' + re.escape(error_text.lower()) + '[^<]*)<', page_content.lower())
if matches:
full_error = matches[0].strip()
logger.info(f"Fehlermeldung gefunden (Text): {full_error}")
return full_error
else:
logger.info(f"Fehlermeldung gefunden (Allgemein): {error_text}")
return error_text
# 3. Nach weiteren Fehlerelementen suchen
elements = self.automation.browser.page.query_selector_all("p, div, span")
for element in elements:
element_text = element.inner_text()
if not element_text:
continue
element_text = element_text.strip()
# Prüfe Textähnlichkeit mit Fehlertexten
for error_text in error_texts:
if self.text_similarity.is_similar(error_text, element_text, threshold=0.7) or \
self.text_similarity.contains_similar_text(element_text, error_texts, threshold=0.7):
logger.info(f"Fehlermeldung gefunden (Ähnlichkeit): {element_text}")
return element_text
return None
except Exception as e:
logger.error(f"Fehler beim Prüfen auf Fehlermeldungen: {e}")
return None
def check_for_captcha(self) -> bool:
"""
Überprüft, ob ein Captcha angezeigt wird.
Returns:
bool: True wenn Captcha erkannt, False sonst
"""
if not self._ensure_browser():
return False
try:
# Selektoren für Captcha-Erkennung
captcha_selectors = [
"div[data-testid='captcha']",
"iframe[src*='captcha']",
"iframe[title*='captcha']",
"iframe[title*='reCAPTCHA']"
]
# Captcha-Texte für textbasierte Erkennung
captcha_texts = [
"captcha", "recaptcha", "sicherheitsüberprüfung", "security check",
"i'm not a robot", "ich bin kein roboter", "verify you're human",
"bestätige, dass du ein mensch bist"
]
# Nach Selektoren suchen
for selector in captcha_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.warning(f"Captcha erkannt (Selektor): {selector}")
return True
# Nach Texten suchen
page_content = self.automation.browser.page.content().lower()
for text in captcha_texts:
if text in page_content:
logger.warning(f"Captcha erkannt (Text): {text}")
return True
return False
except Exception as e:
logger.error(f"Fehler bei der Captcha-Erkennung: {e}")
return False
def wait_for_element(self, selectors: Union[str, List[str]],
timeout: int = 10000, check_interval: int = 500) -> Optional[Any]:
"""
Wartet auf das Erscheinen eines Elements.
Args:
selectors: CSS-Selektor oder Liste von Selektoren
timeout: Zeitlimit in Millisekunden
check_interval: Intervall zwischen den Prüfungen in Millisekunden
Returns:
Optional[Any]: Das gefundene Element oder None, wenn die Zeit abgelaufen ist
"""
if not self._ensure_browser():
return None
try:
# Normalisiere selectors zu einer Liste
if isinstance(selectors, str):
selectors = [selectors]
start_time = time.time()
end_time = start_time + (timeout / 1000)
while time.time() < end_time:
for selector in selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=check_interval)
if element:
logger.info(f"Element mit Selektor '{selector}' gefunden")
return element
# Kurze Pause vor der nächsten Prüfung
time.sleep(check_interval / 1000)
logger.warning(f"Zeitüberschreitung beim Warten auf Element mit Selektoren '{selectors}'")
return None
except Exception as e:
logger.error(f"Fehler beim Warten auf Element: {e}")
return None
def is_registration_successful(self) -> bool:
"""
Überprüft, ob die Registrierung erfolgreich war.
Returns:
bool: True wenn erfolgreich, False sonst
"""
try:
# Erfolgsindikatoren überprüfen
success_indicators = TikTokSelectors.SUCCESS_INDICATORS
for selector in success_indicators:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Registrierung erfolgreich (Indikator gefunden: {selector})")
return True
# URL überprüfen
current_url = self.automation.browser.page.url
if "/foryou" in current_url or "tiktok.com/explore" in current_url:
logger.info("Registrierung erfolgreich (Erfolgreiche Navigation erkannt)")
return True
# Überprüfen, ob Fehler angezeigt werden
error_message = self.check_for_error()
if error_message:
logger.warning(f"Registrierung nicht erfolgreich: {error_message}")
return False
logger.warning("Konnte Registrierungserfolg nicht bestätigen")
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung des Registrierungserfolgs: {e}")
return False

Datei anzeigen

@ -0,0 +1,492 @@
"""
TikTok-Utils - Hilfsfunktionen für die TikTok-Automatisierung.
"""
import re
import time
import random
from typing import Dict, List, Any, Optional, Tuple, Union
from .tiktok_selectors import TikTokSelectors
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("tiktok_utils")
class TikTokUtils:
"""
Hilfsfunktionen für die TikTok-Automatisierung.
Enthält allgemeine Hilfsmethoden und kleinere Funktionen.
"""
def __init__(self, automation):
"""
Initialisiert die TikTok-Utils.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
# Browser wird direkt von automation verwendet
self.selectors = TikTokSelectors()
logger.debug("TikTok-Utils initialisiert")
def _ensure_browser(self) -> bool:
"""
Stellt sicher, dass die Browser-Referenz verfügbar ist.
Returns:
bool: True wenn Browser verfügbar, False sonst
"""
if self.automation.browser is None:
logger.error("Browser-Referenz nicht verfügbar")
return False
return True
def handle_cookie_banner(self) -> bool:
"""
Behandelt den Cookie-Banner, falls angezeigt.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
if not self._ensure_browser():
return False
try:
# Cookie-Dialoge in TikTok prüfen
cookie_selectors = [
"button[data-e2e='cookie-banner-reject']",
"button:contains('Ablehnen')",
"button:contains('Nur erforderliche')",
"button:contains('Reject')",
"button[data-e2e='cookie-banner-accept']"
]
for selector in cookie_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Cookie-Banner erkannt: {selector}")
# Versuche, den Ablehnen-Button zu klicken
if "reject" in selector.lower() or "ablehnen" in selector.lower() or "erforderliche" in selector.lower():
if self.automation.browser.click_element(selector):
logger.info("Cookie-Banner erfolgreich abgelehnt")
time.sleep(random.uniform(0.5, 1.5))
return True
# Fallback: Akzeptieren-Button klicken, wenn Ablehnen nicht funktioniert
else:
if self.automation.browser.click_element(selector):
logger.info("Cookie-Banner erfolgreich akzeptiert")
time.sleep(random.uniform(0.5, 1.5))
return True
# Wenn kein Cookie-Banner gefunden wurde
logger.debug("Kein Cookie-Banner erkannt")
return True
except Exception as e:
logger.error(f"Fehler beim Behandeln des Cookie-Banners: {e}")
return False
def extract_username_from_url(self, url: str) -> Optional[str]:
"""
Extrahiert den Benutzernamen aus einer TikTok-URL.
Args:
url: Die TikTok-URL
Returns:
Optional[str]: Der extrahierte Benutzername oder None
"""
try:
# Muster für Profil-URLs
patterns = [
r'tiktok\.com/@([a-zA-Z0-9._]+)/?(?:$|\?|#)',
r'tiktok\.com/user/([a-zA-Z0-9._]+)/?',
r'tiktok\.com/video/[^/]+/by/([a-zA-Z0-9._]+)/?'
]
for pattern in patterns:
match = re.search(pattern, url)
if match:
username = match.group(1)
# Einige Ausnahmen filtern
if username not in ["explore", "accounts", "video", "foryou", "trending"]:
return username
return None
except Exception as e:
logger.error(f"Fehler beim Extrahieren des Benutzernamens aus der URL: {e}")
return None
def get_current_username(self) -> Optional[str]:
"""
Versucht, den Benutzernamen des aktuell angemeldeten Kontos zu ermitteln.
Returns:
Optional[str]: Der Benutzername oder None, wenn nicht gefunden
"""
if not self._ensure_browser():
return None
try:
# Verschiedene Methoden zur Erkennung des Benutzernamens
# 1. Benutzername aus URL des Profils
profile_link_selectors = [
"a[href*='/@']",
"a[href*='/user/']"
]
for selector in profile_link_selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if element:
href = element.get_attribute("href")
if href:
username = self.extract_username_from_url(href)
if username:
logger.info(f"Benutzername aus Profil-Link ermittelt: {username}")
return username
# 2. Profilicon prüfen auf data-e2e-Attribut
profile_icon_selectors = [
"button[data-e2e='profile-icon']",
"svg[data-e2e='profile-icon']"
]
for selector in profile_icon_selectors:
element = self.automation.browser.wait_for_selector(selector, timeout=2000)
if element:
# Prüfen, ob ein Elternelement möglicherweise ein data-e2e-Attribut mit dem Benutzernamen hat
parent = element.evaluate("node => node.parentElement")
if parent:
data_e2e = parent.get_attribute("data-e2e")
if data_e2e and "profile" in data_e2e:
username_match = re.search(r'profile-([a-zA-Z0-9._]+)', data_e2e)
if username_match:
username = username_match.group(1)
logger.info(f"Benutzername aus data-e2e-Attribut ermittelt: {username}")
return username
# 3. TikTok-spezifisches Element mit Benutzername suchen
username_element = self.automation.browser.wait_for_selector("h1[data-e2e='user-title']", timeout=2000)
if username_element:
username = username_element.inner_text().strip()
if username:
logger.info(f"Benutzername aus user-title-Element ermittelt: {username}")
return username
logger.warning("Konnte Benutzernamen nicht ermitteln")
return None
except Exception as e:
logger.error(f"Fehler bei der Ermittlung des Benutzernamens: {e}")
return None
def wait_for_navigation(self, expected_url_pattern: str = None,
timeout: int = 30000, check_interval: int = 500) -> bool:
"""
Wartet, bis die Seite zu einer URL mit einem bestimmten Muster navigiert.
Args:
expected_url_pattern: Erwartetes Muster der URL (Regex)
timeout: Zeitlimit in Millisekunden
check_interval: Intervall zwischen den Prüfungen in Millisekunden
Returns:
bool: True wenn die Navigation erfolgreich war, False sonst
"""
if not self._ensure_browser():
return False
try:
start_time = time.time()
end_time = start_time + (timeout / 1000)
while time.time() < end_time:
current_url = self.automation.browser.page.url
if expected_url_pattern and re.search(expected_url_pattern, current_url):
logger.info(f"Navigation zu URL mit Muster '{expected_url_pattern}' erfolgreich")
return True
# Kurze Pause vor der nächsten Prüfung
time.sleep(check_interval / 1000)
logger.warning(f"Zeitüberschreitung bei Navigation zu URL mit Muster '{expected_url_pattern}'")
return False
except Exception as e:
logger.error(f"Fehler beim Warten auf Navigation: {e}")
return False
def handle_dialog_or_popup(self, expected_text: Union[str, List[str]] = None,
action: str = "close", timeout: int = 5000) -> bool:
"""
Behandelt einen Dialog oder Popup.
Args:
expected_text: Erwarteter Text im Dialog oder Liste von Texten
action: Aktion ("close", "confirm", "cancel")
timeout: Zeitlimit in Millisekunden
Returns:
bool: True wenn der Dialog erfolgreich behandelt wurde, False sonst
"""
if not self._ensure_browser():
return False
try:
# Dialog-Element suchen
dialog_selector = "div[role='dialog']"
dialog_element = self.automation.browser.wait_for_selector(dialog_selector, timeout=timeout)
if not dialog_element:
logger.debug("Kein Dialog gefunden")
return False
logger.info("Dialog gefunden")
# Text im Dialog prüfen, falls angegeben
if expected_text:
if isinstance(expected_text, str):
expected_text = [expected_text]
dialog_text = dialog_element.inner_text()
text_found = False
for text in expected_text:
if text in dialog_text:
logger.info(f"Erwarteter Text im Dialog gefunden: '{text}'")
text_found = True
break
if not text_found:
logger.warning(f"Erwarteter Text nicht im Dialog gefunden: {expected_text}")
return False
# Aktion ausführen
if action == "close":
# Schließen-Button suchen und klicken
close_button_selectors = [
"button[data-e2e='modal-close']",
"svg[data-e2e='modal-close']",
"button.css-1afoydx-StyledCloseButton",
"div[role='dialog'] button:first-child"
]
for selector in close_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info("Dialog geschlossen")
return True
# Wenn kein Schließen-Button gefunden wurde, Escape-Taste drücken
self.automation.browser.page.keyboard.press("Escape")
logger.info("Dialog mit Escape-Taste geschlossen")
elif action == "confirm":
# Bestätigen-Button suchen und klicken
confirm_button_selectors = [
"button[type='submit']",
"button:contains('OK')",
"button:contains('Ja')",
"button:contains('Yes')",
"button:contains('Bestätigen')",
"button:contains('Confirm')"
]
for selector in confirm_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info("Dialog bestätigt")
return True
elif action == "cancel":
# Abbrechen-Button suchen und klicken
cancel_button_selectors = [
"button:contains('Abbrechen')",
"button:contains('Cancel')",
"button:contains('Nein')",
"button:contains('No')"
]
for selector in cancel_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info("Dialog abgebrochen")
return True
logger.warning(f"Konnte keine {action}-Aktion für den Dialog ausführen")
return False
except Exception as e:
logger.error(f"Fehler bei der Dialog-Behandlung: {e}")
return False
def handle_rate_limiting(self, rotate_proxy: bool = True) -> bool:
"""
Behandelt eine Rate-Limiting-Situation.
Args:
rotate_proxy: Ob der Proxy rotiert werden soll
Returns:
bool: True wenn erfolgreich behandelt, False sonst
"""
if not self._ensure_browser():
return False
try:
logger.warning("Rate-Limiting erkannt, warte und versuche es erneut")
# Screenshot erstellen
self.automation._take_screenshot("rate_limit_detected")
# Proxy rotieren, falls gewünscht
if rotate_proxy and self.automation.use_proxy:
success = self.automation._rotate_proxy()
if not success:
logger.warning("Konnte Proxy nicht rotieren")
# Längere Wartezeit
wait_time = random.uniform(120, 300) # 2-5 Minuten
logger.info(f"Warte {wait_time:.1f} Sekunden vor dem nächsten Versuch")
time.sleep(wait_time)
# Seite neuladen
self.automation.browser.page.reload()
self.automation.human_behavior.wait_for_page_load()
# Prüfen, ob Rate-Limiting noch aktiv ist
rate_limit_texts = [
"bitte warte einige minuten",
"please wait a few minutes",
"try again later",
"versuche es später erneut",
"zu viele anfragen",
"too many requests"
]
page_content = self.automation.browser.page.content().lower()
still_rate_limited = False
for text in rate_limit_texts:
if text in page_content:
still_rate_limited = True
break
if still_rate_limited:
logger.warning("Immer noch Rate-Limited nach dem Warten")
return False
else:
logger.info("Rate-Limiting scheint aufgehoben zu sein")
return True
except Exception as e:
logger.error(f"Fehler bei der Behandlung des Rate-Limitings: {e}")
return False
def is_logged_in(self) -> bool:
"""
Überprüft, ob der Benutzer bei TikTok angemeldet ist.
Returns:
bool: True wenn angemeldet, False sonst
"""
if not self._ensure_browser():
return False
try:
# Erfolgsindikatoren überprüfen
success_indicators = TikTokSelectors.SUCCESS_INDICATORS
for selector in success_indicators:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Benutzer ist angemeldet (Indikator: {selector})")
return True
# URL überprüfen
current_url = self.automation.browser.page.url
if "/foryou" in current_url or "tiktok.com/explore" in current_url:
logger.info("Benutzer ist angemeldet (URL-Check)")
return True
# Anmelden-Button prüfen - wenn sichtbar, dann nicht angemeldet
login_button_selectors = [
TikTokSelectors.LOGIN_BUTTON_LEFT,
TikTokSelectors.LOGIN_BUTTON_RIGHT
]
for selector in login_button_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info("Benutzer ist nicht angemeldet (Anmelde-Button sichtbar)")
return False
# Profilicon checken - wenn sichtbar, dann angemeldet
profile_selectors = [
"button[data-e2e='profile-icon']",
"svg[data-e2e='profile-icon']"
]
for selector in profile_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info("Benutzer ist angemeldet (Profilicon sichtbar)")
return True
logger.warning("Konnte Login-Status nicht eindeutig bestimmen")
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung des Login-Status: {e}")
return False
def extract_verification_code_from_email(self, email_body: str) -> Optional[str]:
"""
Extrahiert den Verifizierungscode aus einer E-Mail.
Args:
email_body: Der E-Mail-Text
Returns:
Optional[str]: Der Verifizierungscode oder None, wenn nicht gefunden
"""
try:
# Muster für TikTok-Verifizierungscodes
patterns = [
r'(\d{6}) ist dein Bestätigungscode',
r'(\d{6}) ist dein TikTok-Code',
r'(\d{6}) is your TikTok code',
r'(\d{6}) is your verification code',
r'Dein Bestätigungscode lautet (\d{6})',
r'Your verification code is (\d{6})',
r'Verification code: (\d{6})',
r'Bestätigungscode: (\d{6})',
r'TikTok code: (\d{6})',
r'TikTok-Code: (\d{6})'
]
for pattern in patterns:
match = re.search(pattern, email_body)
if match:
code = match.group(1)
logger.info(f"Verifizierungscode aus E-Mail extrahiert: {code}")
return code
# Allgemeine Suche nach 6-stelligen Zahlen, wenn keine spezifischen Muster passen
general_match = re.search(r'[^\d](\d{6})[^\d]', email_body)
if general_match:
code = general_match.group(1)
logger.info(f"6-stelliger Code aus E-Mail extrahiert: {code}")
return code
logger.warning("Kein Verifizierungscode in der E-Mail gefunden")
return None
except Exception as e:
logger.error(f"Fehler beim Extrahieren des Verifizierungscodes aus der E-Mail: {e}")
return None

Datei anzeigen

@ -0,0 +1,458 @@
# social_networks/tiktok/tiktok_verification.py
"""
TikTok-Verifizierung - Klasse für die Verifizierungsfunktionalität bei TikTok
"""
import time
import re
from typing import Dict, List, Any, Optional, Tuple
from .tiktok_selectors import TikTokSelectors
from .tiktok_workflow import TikTokWorkflow
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("tiktok_verification")
class TikTokVerification:
"""
Klasse für die Verifizierung von TikTok-Konten.
Enthält alle Methoden für den Verifizierungsprozess.
"""
def __init__(self, automation):
"""
Initialisiert die TikTok-Verifizierung.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
# Browser wird direkt von automation verwendet
self.selectors = TikTokSelectors()
self.workflow = TikTokWorkflow.get_verification_workflow()
logger.debug("TikTok-Verifizierung initialisiert")
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
"""
Führt den Verifizierungsprozess für ein TikTok-Konto durch.
Args:
verification_code: Der Bestätigungscode
**kwargs: Weitere optionale Parameter
Returns:
Dict[str, Any]: Ergebnis der Verifizierung mit Status
"""
# Browser wird direkt von automation verwendet
# Validiere den Verifizierungscode
if not self._validate_verification_code(verification_code):
return {
"success": False,
"error": "Ungültiger Verifizierungscode",
"stage": "code_validation"
}
try:
# 1. Überprüfen, ob wir auf der Verifizierungsseite sind
if not self._is_on_verification_page():
# Versuche, zur Verifizierungsseite zu navigieren, falls möglich
# Direktnavigation ist jedoch normalerweise nicht möglich
return {
"success": False,
"error": "Nicht auf der Verifizierungsseite",
"stage": "page_check"
}
# 2. Verifizierungscode eingeben und absenden
if not self.enter_and_submit_verification_code(verification_code):
return {
"success": False,
"error": "Fehler beim Eingeben oder Absenden des Verifizierungscodes",
"stage": "code_entry"
}
# 3. Überprüfen, ob die Verifizierung erfolgreich war
success, error_message = self._check_verification_success()
if not success:
return {
"success": False,
"error": f"Verifizierung fehlgeschlagen: {error_message or 'Unbekannter Fehler'}",
"stage": "verification_check"
}
# 4. Zusätzliche Dialoge behandeln
self._handle_post_verification_dialogs()
# Verifizierung erfolgreich
logger.info("TikTok-Verifizierung erfolgreich abgeschlossen")
return {
"success": True,
"stage": "completed"
}
except Exception as e:
error_msg = f"Unerwarteter Fehler bei der TikTok-Verifizierung: {str(e)}"
logger.error(error_msg, exc_info=True)
return {
"success": False,
"error": error_msg,
"stage": "exception"
}
def _validate_verification_code(self, verification_code: str) -> bool:
"""
Validiert den Verifizierungscode.
Args:
verification_code: Der zu validierende Code
Returns:
bool: True wenn der Code gültig ist, False sonst
"""
# Leerer Code
if not verification_code:
logger.error("Verifizierungscode ist leer")
return False
# Code-Format prüfen (normalerweise 6-stellige Zahl)
if not re.match(r"^\d{6}$", verification_code):
logger.warning(f"Verifizierungscode hat unerwartetes Format: {verification_code}")
# Wir geben trotzdem True zurück, da einige Codes andere Formate haben könnten
return True
return True
def _is_on_verification_page(self) -> bool:
"""
Überprüft, ob wir auf der Verifizierungsseite sind.
Returns:
bool: True wenn auf der Verifizierungsseite, False sonst
"""
try:
# Screenshot erstellen
self.automation._take_screenshot("verification_page_check")
# Nach Verifizierungsfeld suchen
verification_selectors = [
TikTokSelectors.VERIFICATION_CODE_FIELD,
"input[placeholder*='Code']",
"input[placeholder*='code']",
"input[placeholder*='sechsstelligen']",
"input[data-e2e='verification-code-input']"
]
for selector in verification_selectors:
if self.automation.browser.is_element_visible(selector, timeout=3000):
logger.info("Auf Verifizierungsseite")
return True
# Textbasierte Erkennung
verification_texts = [
"Bestätigungscode",
"Verification code",
"sechsstelligen Code",
"6-digit code",
"Code senden",
"Send code"
]
page_content = self.automation.browser.page.content().lower()
for text in verification_texts:
if text.lower() in page_content:
logger.info(f"Auf Verifizierungsseite (erkannt durch Text: {text})")
return True
logger.warning("Nicht auf der Verifizierungsseite")
return False
except Exception as e:
logger.error(f"Fehler beim Überprüfen der Verifizierungsseite: {e}")
return False
def enter_and_submit_verification_code(self, verification_code: str) -> bool:
"""
Gibt den Verifizierungscode ein und sendet ihn ab.
Args:
verification_code: Der einzugebende Verifizierungscode
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
logger.info(f"Versuche Verifizierungscode einzugeben: {verification_code}")
# Mögliche Selektoren für das Verifizierungscode-Feld
code_field_selectors = [
TikTokSelectors.VERIFICATION_CODE_FIELD,
TikTokSelectors.VERIFICATION_CODE_FIELD_ALT,
"input[placeholder*='Code']",
"input[placeholder*='sechsstelligen Code']",
"input[data-e2e='verification-code-input']"
]
# Versuche, das Feld zu finden und auszufüllen
code_field_found = False
for selector in code_field_selectors:
logger.debug(f"Versuche Selektor: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Codefeld gefunden mit Selektor: {selector}")
if self.automation.browser.fill_form_field(selector, verification_code):
code_field_found = True
logger.info(f"Verifizierungscode eingegeben: {verification_code}")
break
# Versuche es mit der Fuzzy-Matching-Methode, wenn direkte Selektoren fehlschlagen
if not code_field_found:
logger.info("Versuche Fuzzy-Matching für Codefeld")
code_field_found = self.automation.ui_helper.fill_field_fuzzy(
["Bestätigungscode", "Code eingeben", "Verification code", "6-digit code"],
verification_code
)
if not code_field_found:
logger.error("Konnte Verifizierungscode-Feld nicht finden oder ausfüllen")
# Erstelle einen Screenshot zum Debuggen
self.automation._take_screenshot("code_field_not_found")
return False
# Menschliche Verzögerung vor dem Absenden
self.automation.human_behavior.random_delay(1.0, 2.0)
# Screenshot erstellen
self.automation._take_screenshot("verification_code_entered")
# "Weiter"-Button finden und klicken
weiter_button_selectors = [
TikTokSelectors.CONTINUE_BUTTON,
TikTokSelectors.CONTINUE_BUTTON_ALT,
"button[type='submit']",
"button.e1w6iovg0",
"button[data-e2e='next-button']",
"//button[contains(text(), 'Weiter')]"
]
weiter_button_found = False
logger.info("Suche nach Weiter-Button")
for selector in weiter_button_selectors:
logger.debug(f"Versuche Weiter-Button-Selektor: {selector}")
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Weiter-Button gefunden mit Selektor: {selector}")
if self.automation.browser.click_element(selector):
weiter_button_found = True
logger.info("Verifizierungscode-Formular abgesendet")
break
# Versuche es mit der Fuzzy-Matching-Methode, wenn direkte Selektoren fehlschlagen
if not weiter_button_found:
logger.info("Versuche Fuzzy-Matching für Weiter-Button")
weiter_buttons = ["Weiter", "Next", "Continue", "Fertig", "Submit", "Verify", "Senden"]
weiter_button_found = self.automation.ui_helper.click_button_fuzzy(
weiter_buttons
)
if not weiter_button_found:
# Erstelle einen Screenshot zum Debuggen
self.automation._take_screenshot("weiter_button_not_found")
# Versuche es mit Enter-Taste als letzten Ausweg
logger.info("Konnte Weiter-Button nicht finden, versuche Enter-Taste")
self.automation.browser.page.keyboard.press("Enter")
logger.info("Enter-Taste zur Bestätigung des Verifizierungscodes gedrückt")
weiter_button_found = True
# Warten nach dem Absenden
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
return weiter_button_found
except Exception as e:
logger.error(f"Fehler beim Eingeben und Absenden des Verifizierungscodes: {e}")
return False
def _check_verification_success(self) -> Tuple[bool, Optional[str]]:
"""
Überprüft, ob die Verifizierung erfolgreich war.
Returns:
Tuple[bool, Optional[str]]: (Erfolg, Fehlermeldung falls vorhanden)
"""
try:
# Warten nach der Verifizierung
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
# Screenshot erstellen
self.automation._take_screenshot("verification_result")
# Immer noch auf der Verifizierungsseite?
still_on_verification = self._is_on_verification_page()
if still_on_verification:
# Fehlermeldung suchen
error_message = self.automation.ui_helper.check_for_error()
if error_message:
logger.error(f"Verifizierungsfehler: {error_message}")
return False, error_message
else:
logger.error("Verifizierung fehlgeschlagen, immer noch auf der Verifizierungsseite")
return False, "Immer noch auf der Verifizierungsseite"
# Prüfe, ob wir zur Benutzernamen-Erstellung weitergeleitet wurden
username_selectors = [
"input[placeholder='Benutzername']",
"input[name='new-username']",
"//input[@placeholder='Benutzername']"
]
for selector in username_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
logger.info("Verifizierung erfolgreich, zur Benutzernamenauswahl weitergeleitet")
return True, None
# Prüfe auf TikTok-Startseite
current_url = self.automation.browser.page.url
if "tiktok.com" in current_url and "/login" not in current_url and "/signup" not in current_url:
logger.info("Verifizierung erfolgreich, jetzt auf der Startseite")
return True, None
# Wenn keine eindeutigen Indikatoren gefunden wurden, aber auch keine Fehler
logger.warning("Keine eindeutigen Erfolgsindikatoren für die Verifizierung gefunden")
return True, None # Wir gehen davon aus, dass es erfolgreich war
except Exception as e:
logger.error(f"Fehler beim Überprüfen des Verifizierungserfolgs: {e}")
return False, f"Fehler bei der Erfolgsprüfung: {str(e)}"
def _handle_post_verification_dialogs(self) -> None:
"""
Behandelt Dialoge, die nach erfolgreicher Verifizierung erscheinen können.
"""
try:
# Liste der möglichen Dialoge und wie man sie überspringt
dialogs_to_handle = [
{
"name": "username_setup",
"skip_texts": ["Überspringen", "Skip"],
"skip_selectors": ["//button[contains(text(), 'Überspringen')]", "//button[contains(text(), 'Skip')]"]
},
{
"name": "interests",
"skip_texts": ["Überspringen", "Skip"],
"skip_selectors": ["//button[contains(text(), 'Überspringen')]", "//button[contains(text(), 'Skip')]"]
},
{
"name": "follow_accounts",
"skip_texts": ["Überspringen", "Skip"],
"skip_selectors": ["//button[contains(text(), 'Überspringen')]", "//button[contains(text(), 'Skip')]"]
},
{
"name": "notifications",
"skip_texts": ["Später", "Nein", "Nicht jetzt", "Later", "No", "Not now"],
"skip_selectors": ["//button[contains(text(), 'Später')]", "//button[contains(text(), 'Not now')]"]
}
]
# Versuche, jeden möglichen Dialog zu behandeln
for dialog in dialogs_to_handle:
self._try_skip_dialog(dialog)
logger.info("Nachverifizierungs-Dialoge behandelt")
except Exception as e:
logger.warning(f"Fehler beim Behandeln der Nachverifizierungs-Dialoge: {e}")
# Nicht kritisch, daher keine Fehlerbehandlung
def _try_skip_dialog(self, dialog: Dict[str, Any]) -> bool:
"""
Versucht, einen bestimmten Dialog zu überspringen.
Args:
dialog: Informationen zum Dialog
Returns:
bool: True wenn Dialog gefunden und übersprungen, False sonst
"""
try:
# Zuerst mit Fuzzy-Matching versuchen
skip_clicked = self.automation.ui_helper.click_button_fuzzy(
dialog["skip_texts"],
threshold=0.7,
timeout=3000
)
if skip_clicked:
logger.info(f"Dialog '{dialog['name']}' mit Fuzzy-Matching übersprungen")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
# Wenn Fuzzy-Matching fehlschlägt, direkte Selektoren versuchen
for selector in dialog["skip_selectors"]:
if self.automation.browser.is_element_visible(selector, timeout=1000):
if self.automation.browser.click_element(selector):
logger.info(f"Dialog '{dialog['name']}' mit direktem Selektor übersprungen")
self.automation.human_behavior.random_delay(0.5, 1.0)
return True
return False
except Exception as e:
logger.warning(f"Fehler beim Versuch, Dialog '{dialog['name']}' zu überspringen: {e}")
return False
def resend_verification_code(self) -> bool:
"""
Versucht, den Verifizierungscode erneut senden zu lassen.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Resend-Button suchen und klicken
resend_selectors = [
"button[data-e2e='send-code-button']",
"//button[contains(text(), 'Code senden')]",
"//button[contains(text(), 'Code erneut senden')]",
"//button[contains(text(), 'Erneut senden')]",
"a[data-e2e='resend-code-link']"
]
for selector in resend_selectors:
if self.automation.browser.is_element_visible(selector, timeout=2000):
if self.automation.browser.click_element(selector):
logger.info("Code erneut angefordert")
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
# Fuzzy-Matching versuchen
resend_texts = ["Code senden", "Code erneut senden", "Erneut senden", "Resend code", "Send again"]
resend_clicked = self.automation.ui_helper.click_button_fuzzy(
resend_texts,
threshold=0.7,
timeout=3000
)
if resend_clicked:
logger.info("Code erneut angefordert (über Fuzzy-Matching)")
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
logger.warning("Konnte keinen 'Code erneut senden'-Button finden")
return False
except Exception as e:
logger.error(f"Fehler beim erneuten Anfordern des Codes: {e}")
return False

Datei anzeigen

@ -0,0 +1,427 @@
"""
TikTok-Workflow - Definiert die Schritte für die TikTok-Anmeldung und -Registrierung
"""
from typing import Dict, List, Any, Optional, Tuple
import re
from utils.text_similarity import TextSimilarity
from utils.logger import setup_logger
# Konfiguriere Logger
logger = setup_logger("tiktok_workflow")
class TikTokWorkflow:
"""
Definiert die Workflow-Schritte für verschiedene TikTok-Aktionen
wie Registrierung, Anmeldung und Verifizierung.
"""
# Text-Ähnlichkeits-Threshold für Fuzzy-Matching
SIMILARITY_THRESHOLD = 0.7
# Initialisiere TextSimilarity für Matching
text_similarity = TextSimilarity(default_threshold=SIMILARITY_THRESHOLD)
# Mögliche alternative Texte für verschiedene UI-Elemente
TEXT_ALTERNATIVES = {
"email": ["E-Mail", "Email", "E-mail", "Mail", "email"],
"phone": ["Telefon", "Telefonnummer", "Phone", "Mobile", "mobile"],
"password": ["Passwort", "Password", "pass"],
"code": ["Code", "Bestätigungscode", "Verification code", "Sicherheitscode"],
"username": ["Benutzername", "Username", "user name"],
"submit": ["Registrieren", "Sign up", "Anmelden", "Login", "Log in", "Submit"],
"next": ["Weiter", "Next", "Continue", "Fortfahren"],
"skip": ["Überspringen", "Skip", "Later", "Später", "Not now", "Nicht jetzt"]
}
@staticmethod
def get_registration_workflow(registration_method: str = "email") -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die TikTok-Registrierung zurück.
Args:
registration_method: "email" oder "phone"
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
# Basisschritte für beide Methoden
common_steps = [
{
"name": "navigate_to_signup",
"description": "Zur TikTok-Startseite navigieren",
"url": "https://www.tiktok.com",
"wait_for": ["button#header-login-button", "button#top-right-action-bar-login-button"],
"fuzzy_match": None
},
{
"name": "click_login_button",
"description": "Anmelden-Button klicken",
"action": "click",
"target": "button#top-right-action-bar-login-button",
"wait_for": ["a[href*='/signup']", "div[role='dialog']"],
"fuzzy_match": ["Anmelden", "Sign in", "Log in"]
},
{
"name": "click_register_link",
"description": "Registrieren-Link klicken",
"action": "click",
"target": "a[href*='/signup']",
"wait_for": ["div[data-e2e='channel-item']"],
"fuzzy_match": ["Registrieren", "Sign up", "Register"]
},
{
"name": "click_phone_email_button",
"description": "Telefon/E-Mail-Option auswählen",
"action": "click",
"target": "div[data-e2e='channel-item']",
"wait_for": ["a[href*='/signup/phone-or-email/email']", "a[href*='/signup/phone-or-email/phone']"],
"fuzzy_match": ["Telefonnummer oder E-Mail-Adresse", "Phone or Email"]
}
]
# Spezifische Schritte je nach Registrierungsmethode
method_steps = []
if registration_method == "email":
method_steps.append({
"name": "click_email_registration",
"description": "Mit E-Mail registrieren auswählen",
"action": "click",
"target": "a[href*='/signup/phone-or-email/email']",
"wait_for": ["input[placeholder='E-Mail-Adresse']"],
"fuzzy_match": ["Mit E-Mail-Adresse registrieren", "Email", "E-Mail"]
})
else: # phone
method_steps.append({
"name": "click_phone_registration",
"description": "Mit Telefonnummer registrieren auswählen",
"action": "click",
"target": "a[href*='/signup/phone-or-email/phone']",
"wait_for": ["input[placeholder='Telefonnummer']"],
"fuzzy_match": ["Mit Telefonnummer registrieren", "Phone", "Telefon"]
})
# Geburtsdatum-Schritte
birthday_steps = [
{
"name": "select_birth_month",
"description": "Geburtsmonat auswählen",
"action": "click",
"target": "div.css-1fi2hzv-DivSelectLabel:contains('Monat')",
"wait_for": ["div[role='option']"],
"fuzzy_match": ["Monat", "Month"]
},
{
"name": "select_month_option",
"description": "Monats-Option auswählen",
"action": "select_option",
"target": "div[role='option']",
"value": "{MONTH_NAME}",
"wait_for": [],
"fuzzy_match": None
},
{
"name": "select_birth_day",
"description": "Geburtstag auswählen",
"action": "click",
"target": "div.css-1fi2hzv-DivSelectLabel:contains('Tag')",
"wait_for": ["div[role='option']"],
"fuzzy_match": ["Tag", "Day"]
},
{
"name": "select_day_option",
"description": "Tags-Option auswählen",
"action": "select_option",
"target": "div[role='option']",
"value": "{DAY}",
"wait_for": [],
"fuzzy_match": None
},
{
"name": "select_birth_year",
"description": "Geburtsjahr auswählen",
"action": "click",
"target": "div.css-1fi2hzv-DivSelectLabel:contains('Jahr')",
"wait_for": ["div[role='option']"],
"fuzzy_match": ["Jahr", "Year"]
},
{
"name": "select_year_option",
"description": "Jahres-Option auswählen",
"action": "select_option",
"target": "div[role='option']",
"value": "{YEAR}",
"wait_for": [],
"fuzzy_match": None
}
]
# Formularschritte für E-Mail
email_form_steps = [
{
"name": "fill_email",
"description": "E-Mail-Adresse eingeben",
"action": "fill",
"target": "input[placeholder='E-Mail-Adresse']",
"value": "{EMAIL}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["email"]
},
{
"name": "fill_password",
"description": "Passwort eingeben",
"action": "fill",
"target": "input[placeholder='Passwort']",
"value": "{PASSWORD}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["password"]
},
{
"name": "click_send_code",
"description": "Code senden klicken",
"action": "click",
"target": "button[data-e2e='send-code-button']",
"wait_for": ["input[placeholder*='sechsstelligen Code']"],
"fuzzy_match": ["Code senden", "Send code", "Senden"]
},
{
"name": "fill_verification_code",
"description": "Bestätigungscode eingeben",
"action": "fill",
"target": "input[placeholder*='sechsstelligen Code']",
"value": "{VERIFICATION_CODE}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["code"]
},
{
"name": "click_continue",
"description": "Weiter klicken",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["input[placeholder='Benutzername']"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["next"]
}
]
# Formularschritte für Telefon
phone_form_steps = [
{
"name": "select_country_code",
"description": "Ländervorwahl auswählen",
"action": "click",
"target": "div[role='combobox']",
"wait_for": ["div[role='option']"],
"fuzzy_match": None
},
{
"name": "select_country_option",
"description": "Land auswählen",
"action": "select_option",
"target": "div[role='option']",
"value": "{COUNTRY_NAME}",
"wait_for": [],
"fuzzy_match": None
},
{
"name": "fill_phone",
"description": "Telefonnummer eingeben",
"action": "fill",
"target": "input[placeholder='Telefonnummer']",
"value": "{PHONE}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["phone"]
},
{
"name": "click_send_code",
"description": "Code senden klicken",
"action": "click",
"target": "button[data-e2e='send-code-button']",
"wait_for": ["input[placeholder*='sechsstelligen Code']"],
"fuzzy_match": ["Code senden", "Send code", "Senden"]
},
{
"name": "fill_verification_code",
"description": "Bestätigungscode eingeben",
"action": "fill",
"target": "input[placeholder*='sechsstelligen Code']",
"value": "{VERIFICATION_CODE}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["code"]
},
{
"name": "click_continue",
"description": "Weiter klicken",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["input[placeholder='Benutzername']"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["next"]
}
]
# Benutzername-Schritte
username_steps = [
{
"name": "fill_username",
"description": "Benutzernamen eingeben",
"action": "fill",
"target": "input[placeholder='Benutzername']",
"value": "{USERNAME}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["username"]
},
{
"name": "click_register",
"description": "Registrieren klicken",
"action": "click",
"target": "button:contains('Registrieren')",
"wait_for": ["a[href='/foryou']", "button:contains('Überspringen')"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["submit"]
},
{
"name": "handle_skip_option",
"description": "Optional: Überspringen klicken",
"action": "click",
"target": "button:contains('Überspringen')",
"optional": True,
"wait_for": ["a[href='/foryou']"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["skip"]
}
]
# Vollständigen Workflow zusammenstellen
if registration_method == "email":
workflow = common_steps + method_steps + birthday_steps + email_form_steps + username_steps
else: # phone
workflow = common_steps + method_steps + birthday_steps + phone_form_steps + username_steps
return workflow
@staticmethod
def get_login_workflow() -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die TikTok-Anmeldung zurück.
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
login_steps = [
{
"name": "navigate_to_login",
"description": "Zur TikTok-Startseite navigieren",
"url": "https://www.tiktok.com",
"wait_for": ["button#header-login-button", "button#top-right-action-bar-login-button"],
"fuzzy_match": None
},
{
"name": "click_login_button",
"description": "Anmelden-Button klicken",
"action": "click",
"target": "button#top-right-action-bar-login-button",
"wait_for": ["div[role='dialog']"],
"fuzzy_match": ["Anmelden", "Sign in", "Log in"]
},
{
"name": "click_phone_email_button",
"description": "Telefon/E-Mail-Option auswählen",
"action": "click",
"target": "div[data-e2e='channel-item']",
"wait_for": ["input[type='text']"],
"fuzzy_match": ["Telefon-Nr./E-Mail/Anmeldename", "Phone or Email"]
},
{
"name": "fill_login_field",
"description": "Benutzername/E-Mail/Telefon eingeben",
"action": "fill",
"target": "input[type='text']",
"value": "{USERNAME_OR_EMAIL}",
"fuzzy_match": ["Email", "Benutzername", "Telefon"]
},
{
"name": "fill_password",
"description": "Passwort eingeben",
"action": "fill",
"target": "input[type='password']",
"value": "{PASSWORD}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["password"]
},
{
"name": "click_login",
"description": "Anmelden klicken",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["a[href='/foryou']"],
"fuzzy_match": ["Anmelden", "Log in", "Login"]
}
]
return login_steps
@staticmethod
def get_verification_workflow() -> List[Dict[str, Any]]:
"""
Gibt den Workflow für die TikTok-Verifizierung zurück.
Returns:
List[Dict[str, Any]]: Liste von Workflow-Schritten
"""
verification_steps = [
{
"name": "fill_verification_code",
"description": "Bestätigungscode eingeben",
"action": "fill",
"target": "input[placeholder*='sechsstelligen Code']",
"value": "{VERIFICATION_CODE}",
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["code"]
},
{
"name": "click_continue",
"description": "Weiter klicken",
"action": "click",
"target": "button[type='submit']",
"wait_for": ["input[placeholder='Benutzername']", "a[href='/foryou']"],
"fuzzy_match": TikTokWorkflow.TEXT_ALTERNATIVES["next"]
}
]
return verification_steps
@staticmethod
def identify_current_step(page_title: str, page_url: str, visible_elements: List[str]) -> str:
"""
Identifiziert den aktuellen Schritt basierend auf dem Seitentitel, der URL und sichtbaren Elementen.
Args:
page_title: Titel der Seite
page_url: URL der Seite
visible_elements: Liste sichtbarer Elemente (Selektoren)
Returns:
str: Name des identifizierten Schritts
"""
# Auf der Startseite
if "tiktok.com" in page_url and not "/signup" in page_url and not "/login" in page_url:
return "navigate_to_signup"
# Anmelde-/Registrierungsauswahl
if "signup" in page_url or "login" in page_url:
if any("channel-item" in element for element in visible_elements):
return "click_phone_email_button"
# Geburtsdatum
if "Monat" in page_title or "Month" in page_title or any("Geburtsdatum" in element for element in visible_elements):
return "select_birth_month"
# E-Mail-/Telefon-Eingabe
if any("E-Mail-Adresse" in element for element in visible_elements):
return "fill_email"
if any("Telefonnummer" in element for element in visible_elements):
return "fill_phone"
# Bestätigungscode
if any("sechsstelligen Code" in element for element in visible_elements):
return "fill_verification_code"
# Benutzernamen-Erstellung
if any("Benutzername" in element for element in visible_elements):
return "fill_username"
# Erfolgreiche Anmeldung
if "foryou" in page_url or any("Für dich" in element for element in visible_elements):
return "logged_in"
return "unknown"

Datei anzeigen

Datei anzeigen

Datei anzeigen

Datei anzeigen

Datei anzeigen

@ -0,0 +1,240 @@
"""
VK Automatisierung - Hauptklasse
"""
import logging
import time
import random
from typing import Dict, Optional, Tuple
from playwright.sync_api import Page
from social_networks.base_automation import BaseAutomation
from social_networks.vk import vk_selectors as selectors
from social_networks.vk.vk_ui_helper import VKUIHelper
from social_networks.vk.vk_registration import VKRegistration
from social_networks.vk.vk_login import VKLogin
from social_networks.vk.vk_verification import VKVerification
from social_networks.vk.vk_utils import VKUtils
logger = logging.getLogger("vk_automation")
class VKAutomation(BaseAutomation):
"""
VK-spezifische Automatisierung
"""
def __init__(self, **kwargs):
"""
Initialisiert die VK-Automatisierung
"""
super().__init__(**kwargs)
self.platform_name = "vk"
self.ui_helper = None
self.registration = None
self.login_helper = None
self.verification = None
self.utils = None
def _initialize_helpers(self, page: Page):
"""
Initialisiert die Hilfsklassen
"""
self.ui_helper = VKUIHelper(page, self.screenshots_dir, self.save_screenshots)
self.registration = VKRegistration(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
self.login_helper = VKLogin(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
self.verification = VKVerification(page, self.ui_helper, self.email_handler, self.screenshots_dir, self.save_screenshots)
self.utils = VKUtils()
def register_account(self, full_name: str, age: int, registration_method: str = "phone",
phone_number: str = None, **kwargs) -> Dict[str, any]:
"""
Erstellt einen neuen VK-Account
Args:
full_name: Vollständiger Name für den Account
age: Alter des Benutzers
registration_method: Registrierungsmethode (nur "phone" für VK)
phone_number: Telefonnummer (erforderlich für VK)
**kwargs: Weitere optionale Parameter
"""
try:
logger.info(f"Starte VK Account-Registrierung für {full_name}")
# Erstelle account_data aus den Parametern
account_data = {
"full_name": full_name,
"first_name": kwargs.get("first_name", full_name.split()[0] if full_name else ""),
"last_name": kwargs.get("last_name", full_name.split()[-1] if full_name and len(full_name.split()) > 1 else ""),
"age": age,
"birthday": kwargs.get("birthday", self._generate_birthday(age)),
"gender": kwargs.get("gender", random.choice(["male", "female"])),
"username": kwargs.get("username", ""),
"password": kwargs.get("password", ""),
"email": kwargs.get("email", ""),
"phone": phone_number or ""
}
# Initialisiere Browser, falls noch nicht geschehen
if not self.browser or not hasattr(self.browser, 'page'):
if not self._initialize_browser():
return {
"success": False,
"error": "Browser konnte nicht initialisiert werden",
"message": "Browser-Initialisierung fehlgeschlagen"
}
# Page-Objekt holen
page = self.browser.page
self._initialize_helpers(page)
# VK Homepage öffnen
logger.info("Navigiere zu VK Homepage")
page.goto(selectors.BASE_URL, wait_until="domcontentloaded")
time.sleep(random.uniform(2, 4))
# Screenshot der Startseite
self.ui_helper.take_screenshot("vk_homepage")
# Cookie Banner handhaben
self._handle_cookie_banner(page)
# "Konto erstellen" Button klicken
logger.info("Suche 'Konto erstellen' Button")
try:
# Versuche verschiedene Selektoren
create_button_clicked = False
# Versuche CSS Selektor
if page.locator(selectors.CREATE_ACCOUNT_BUTTON).count() > 0:
page.click(selectors.CREATE_ACCOUNT_BUTTON)
create_button_clicked = True
logger.info("Button mit CSS Selektor gefunden und geklickt")
# Versuche XPath wenn CSS nicht funktioniert
elif page.locator(selectors.CREATE_ACCOUNT_BUTTON_XPATH).count() > 0:
page.click(selectors.CREATE_ACCOUNT_BUTTON_XPATH)
create_button_clicked = True
logger.info("Button mit XPath gefunden und geklickt")
# Versuche alternativen Selektor
elif page.locator(selectors.CREATE_ACCOUNT_BUTTON_ALTERNATE).count() > 0:
page.click(selectors.CREATE_ACCOUNT_BUTTON_ALTERNATE)
create_button_clicked = True
logger.info("Button mit alternativem Selektor gefunden und geklickt")
if not create_button_clicked:
raise Exception("'Konto erstellen' Button nicht gefunden")
time.sleep(random.uniform(2, 3))
except Exception as e:
logger.error(f"Fehler beim Klicken des 'Konto erstellen' Buttons: {e}")
self.ui_helper.take_screenshot("create_account_button_error")
raise
# Registrierungsformular ausfüllen
registration_result = self.registration.fill_registration_form(account_data)
if not registration_result["success"]:
return registration_result
# Telefonnummer-Verifizierung
verification_result = self.verification.handle_phone_verification(account_data)
if not verification_result["success"]:
return verification_result
# Erfolg
logger.info("VK Account-Registrierung erfolgreich abgeschlossen")
return {
"success": True,
"username": account_data.get("username"),
"password": account_data.get("password"),
"email": account_data.get("email"),
"phone": account_data.get("phone"),
"message": "Account erfolgreich erstellt"
}
except Exception as e:
logger.error(f"Fehler bei der VK-Registrierung: {str(e)}")
return {
"success": False,
"error": str(e),
"message": f"Registrierung fehlgeschlagen: {str(e)}"
}
finally:
self._close_browser()
def login(self, username: str, password: str) -> Dict[str, any]:
"""
Meldet sich bei einem bestehenden VK-Account an
"""
try:
logger.info(f"Starte VK Login für {username}")
# Initialisiere Browser, falls noch nicht geschehen
if not self.browser or not hasattr(self.browser, 'page'):
if not self._initialize_browser():
return {
"success": False,
"error": "Browser konnte nicht initialisiert werden",
"message": "Browser-Initialisierung fehlgeschlagen"
}
# Page-Objekt holen
page = self.browser.page
self._initialize_helpers(page)
# Login durchführen
return self.login_helper.login(username, password)
except Exception as e:
logger.error(f"Fehler beim VK Login: {str(e)}")
return {
"success": False,
"error": str(e),
"message": f"Login fehlgeschlagen: {str(e)}"
}
finally:
self._close_browser()
def _handle_cookie_banner(self, page: Page):
"""
Handhabt Cookie-Banner falls vorhanden
"""
try:
if page.locator(selectors.COOKIE_BANNER).count() > 0:
logger.info("Cookie Banner gefunden")
if page.locator(selectors.COOKIE_ACCEPT_BUTTON).count() > 0:
page.click(selectors.COOKIE_ACCEPT_BUTTON)
logger.info("Cookie Banner akzeptiert")
time.sleep(random.uniform(1, 2))
except Exception as e:
logger.warning(f"Fehler beim Handhaben des Cookie Banners: {e}")
def get_account_info(self) -> Dict[str, any]:
"""
Ruft Informationen über den aktuellen Account ab
"""
# TODO: Implementierung
return {
"success": False,
"message": "Noch nicht implementiert"
}
def logout(self) -> bool:
"""
Meldet sich vom aktuellen Account ab
"""
# TODO: Implementierung
return False
def _generate_birthday(self, age: int) -> str:
"""
Generiert ein Geburtsdatum basierend auf dem Alter
"""
from datetime import datetime, timedelta
today = datetime.now()
birth_year = today.year - age
# Zufälliger Tag im Jahr
random_days = random.randint(0, 364)
birthday = datetime(birth_year, 1, 1) + timedelta(days=random_days)
return birthday.strftime("%Y-%m-%d")

127
social_networks/vk/vk_login.py Normale Datei
Datei anzeigen

@ -0,0 +1,127 @@
"""
VK Login - Handhabt den Login-Prozess
"""
import logging
import time
import random
from typing import Dict
from playwright.sync_api import Page
from social_networks.vk import vk_selectors as selectors
from social_networks.vk.vk_ui_helper import VKUIHelper
logger = logging.getLogger("vk_login")
class VKLogin:
"""
Handhabt den VK Login-Prozess
"""
def __init__(self, page: Page, ui_helper: VKUIHelper, screenshots_dir: str = None, save_screenshots: bool = True):
"""
Initialisiert den Login Handler
"""
self.page = page
self.ui_helper = ui_helper
self.screenshots_dir = screenshots_dir
self.save_screenshots = save_screenshots
def login(self, username: str, password: str) -> Dict[str, any]:
"""
Führt den Login durch
"""
try:
logger.info(f"Starte VK Login für {username}")
# Navigiere zur Login-Seite
self.page.goto(selectors.LOGIN_URL, wait_until="domcontentloaded")
time.sleep(random.uniform(2, 3))
self.ui_helper.take_screenshot("login_page")
# Email/Telefonnummer eingeben
if self.ui_helper.wait_for_element(selectors.LOGIN_EMAIL_INPUT, timeout=10000):
logger.info("Gebe Benutzernamen ein")
self.ui_helper.type_with_delay(selectors.LOGIN_EMAIL_INPUT, username)
time.sleep(random.uniform(0.5, 1))
# Passwort eingeben
if self.ui_helper.wait_for_element(selectors.LOGIN_PASSWORD_INPUT, timeout=5000):
logger.info("Gebe Passwort ein")
self.ui_helper.type_with_delay(selectors.LOGIN_PASSWORD_INPUT, password)
time.sleep(random.uniform(0.5, 1))
# Screenshot vor dem Login
self.ui_helper.take_screenshot("login_form_filled")
# Login Button klicken
if self.ui_helper.wait_for_element(selectors.LOGIN_SUBMIT_BUTTON, timeout=5000):
logger.info("Klicke auf Login Button")
self.ui_helper.click_with_retry(selectors.LOGIN_SUBMIT_BUTTON)
# Warte auf Navigation
self.ui_helper.wait_for_navigation()
time.sleep(random.uniform(2, 3))
# Prüfe ob Login erfolgreich war
if self._check_login_success():
logger.info("Login erfolgreich")
self.ui_helper.take_screenshot("login_success")
return {
"success": True,
"message": "Login erfolgreich"
}
else:
error_msg = self._get_error_message()
logger.error(f"Login fehlgeschlagen: {error_msg}")
self.ui_helper.take_screenshot("login_failed")
return {
"success": False,
"error": error_msg,
"message": f"Login fehlgeschlagen: {error_msg}"
}
except Exception as e:
logger.error(f"Fehler beim Login: {e}")
self.ui_helper.take_screenshot("login_error")
return {
"success": False,
"error": str(e),
"message": f"Login fehlgeschlagen: {str(e)}"
}
def _check_login_success(self) -> bool:
"""
Prüft ob der Login erfolgreich war
"""
try:
# Prüfe ob wir auf der Hauptseite sind
current_url = self.page.url
if "feed" in current_url or "id" in current_url:
return True
# Prüfe ob Login-Formular noch sichtbar ist
if self.ui_helper.is_element_visible(selectors.LOGIN_EMAIL_INPUT):
return False
# Prüfe auf Fehlermeldung
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
return False
return True
except Exception as e:
logger.warning(f"Fehler bei der Login-Prüfung: {e}")
return False
def _get_error_message(self) -> str:
"""
Holt die Fehlermeldung falls vorhanden
"""
try:
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
return self.ui_helper.get_element_text(selectors.ERROR_MESSAGE) or "Unbekannter Fehler"
return "Login fehlgeschlagen"
except:
return "Unbekannter Fehler"

Datei anzeigen

@ -0,0 +1,132 @@
"""
VK Registrierung - Handhabt den Registrierungsprozess
"""
import logging
import time
import random
from typing import Dict
from playwright.sync_api import Page
from social_networks.vk import vk_selectors as selectors
from social_networks.vk.vk_ui_helper import VKUIHelper
logger = logging.getLogger("vk_registration")
class VKRegistration:
"""
Handhabt den VK Registrierungsprozess
"""
def __init__(self, page: Page, ui_helper: VKUIHelper, screenshots_dir: str = None, save_screenshots: bool = True):
"""
Initialisiert die Registrierung
"""
self.page = page
self.ui_helper = ui_helper
self.screenshots_dir = screenshots_dir
self.save_screenshots = save_screenshots
def fill_registration_form(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Füllt das Registrierungsformular aus
"""
try:
logger.info("Fülle VK Registrierungsformular aus")
# Warte auf Registrierungsseite
time.sleep(random.uniform(2, 3))
self.ui_helper.take_screenshot("registration_form")
# Vorname eingeben
if self.ui_helper.wait_for_element(selectors.REGISTRATION_FIRST_NAME, timeout=10000):
first_name = account_data.get("first_name", "")
logger.info(f"Gebe Vorname ein: {first_name}")
self.ui_helper.type_with_delay(selectors.REGISTRATION_FIRST_NAME, first_name)
time.sleep(random.uniform(0.5, 1))
# Nachname eingeben
if self.ui_helper.wait_for_element(selectors.REGISTRATION_LAST_NAME, timeout=5000):
last_name = account_data.get("last_name", "")
logger.info(f"Gebe Nachname ein: {last_name}")
self.ui_helper.type_with_delay(selectors.REGISTRATION_LAST_NAME, last_name)
time.sleep(random.uniform(0.5, 1))
# Geburtstag auswählen
self._select_birthday(account_data)
# Geschlecht auswählen
self._select_gender(account_data)
# Screenshot vor dem Fortfahren
self.ui_helper.take_screenshot("registration_form_filled")
# Fortfahren Button klicken
if self.ui_helper.wait_for_element(selectors.REGISTRATION_CONTINUE_BUTTON, timeout=5000):
logger.info("Klicke auf Fortfahren")
self.ui_helper.click_with_retry(selectors.REGISTRATION_CONTINUE_BUTTON)
time.sleep(random.uniform(2, 3))
return {
"success": True,
"message": "Registrierungsformular ausgefüllt"
}
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {e}")
self.ui_helper.take_screenshot("registration_form_error")
return {
"success": False,
"error": str(e),
"message": f"Fehler beim Ausfüllen des Formulars: {str(e)}"
}
def _select_birthday(self, account_data: Dict[str, str]):
"""
Wählt das Geburtsdatum aus
"""
try:
birthday = account_data.get("birthday", "1990-01-15")
year, month, day = birthday.split("-")
# Tag auswählen
if self.ui_helper.wait_for_element(selectors.REGISTRATION_BIRTHDAY_DAY, timeout=5000):
logger.info(f"Wähle Geburtstag: {day}")
self.ui_helper.select_dropdown_option(selectors.REGISTRATION_BIRTHDAY_DAY, day.lstrip("0"))
time.sleep(random.uniform(0.3, 0.6))
# Monat auswählen
if self.ui_helper.wait_for_element(selectors.REGISTRATION_BIRTHDAY_MONTH, timeout=5000):
logger.info(f"Wähle Geburtsmonat: {month}")
self.ui_helper.select_dropdown_option(selectors.REGISTRATION_BIRTHDAY_MONTH, month.lstrip("0"))
time.sleep(random.uniform(0.3, 0.6))
# Jahr auswählen
if self.ui_helper.wait_for_element(selectors.REGISTRATION_BIRTHDAY_YEAR, timeout=5000):
logger.info(f"Wähle Geburtsjahr: {year}")
self.ui_helper.select_dropdown_option(selectors.REGISTRATION_BIRTHDAY_YEAR, year)
time.sleep(random.uniform(0.3, 0.6))
except Exception as e:
logger.warning(f"Fehler bei der Geburtsdatum-Auswahl: {e}")
def _select_gender(self, account_data: Dict[str, str]):
"""
Wählt das Geschlecht aus
"""
try:
gender = account_data.get("gender", "male").lower()
if gender == "female":
if self.ui_helper.wait_for_element(selectors.REGISTRATION_GENDER_FEMALE, timeout=5000):
logger.info("Wähle Geschlecht: Weiblich")
self.ui_helper.check_radio_button(selectors.REGISTRATION_GENDER_FEMALE)
else:
if self.ui_helper.wait_for_element(selectors.REGISTRATION_GENDER_MALE, timeout=5000):
logger.info("Wähle Geschlecht: Männlich")
self.ui_helper.check_radio_button(selectors.REGISTRATION_GENDER_MALE)
time.sleep(random.uniform(0.3, 0.6))
except Exception as e:
logger.warning(f"Fehler bei der Geschlechtsauswahl: {e}")

Datei anzeigen

@ -0,0 +1,51 @@
"""
VK UI Selektoren und URLs
"""
# URLs
BASE_URL = "https://vk.com/"
REGISTRATION_URL = "https://vk.com/join"
LOGIN_URL = "https://vk.com/login"
# Startseite
CREATE_ACCOUNT_BUTTON = "span.vkuiButton__content:has-text('Konto erstellen')"
CREATE_ACCOUNT_BUTTON_XPATH = "//span[@class='vkuiButton__content' and text()='Konto erstellen']"
CREATE_ACCOUNT_BUTTON_ALTERNATE = "button:has-text('Konto erstellen')"
# Login Seite
LOGIN_EMAIL_INPUT = "input[name='email']"
LOGIN_PASSWORD_INPUT = "input[name='password']"
LOGIN_SUBMIT_BUTTON = "button[type='submit']"
# Registrierungsformular
REGISTRATION_FIRST_NAME = "input[name='first_name']"
REGISTRATION_LAST_NAME = "input[name='last_name']"
REGISTRATION_BIRTHDAY_DAY = "select[name='bday']"
REGISTRATION_BIRTHDAY_MONTH = "select[name='bmonth']"
REGISTRATION_BIRTHDAY_YEAR = "select[name='byear']"
REGISTRATION_GENDER_MALE = "input[value='2']"
REGISTRATION_GENDER_FEMALE = "input[value='1']"
REGISTRATION_CONTINUE_BUTTON = "button[type='submit']"
# Telefonnummer Verifizierung
PHONE_INPUT = "input[name='phone']"
PHONE_COUNTRY_CODE = "div.PhoneInput__countryCode"
PHONE_SUBMIT_BUTTON = "button[type='submit']"
# SMS Verifizierung
SMS_CODE_INPUT = "input[name='code']"
SMS_SUBMIT_BUTTON = "button[type='submit']"
SMS_RESEND_LINK = "a:has-text('Code erneut senden')"
# Captcha
CAPTCHA_IMAGE = "img.vkc__Captcha__image"
CAPTCHA_INPUT = "input[name='captcha']"
# Fehler- und Erfolgsmeldungen
ERROR_MESSAGE = "div.error"
SUCCESS_MESSAGE = "div.success"
PHONE_ERROR = "div.phone_error"
# Cookies Banner
COOKIE_ACCEPT_BUTTON = "button:has-text('Akzeptieren')"
COOKIE_BANNER = "div[class*='cookie']"

Datei anzeigen

@ -0,0 +1,149 @@
"""
VK UI Helper - Hilfsfunktionen für UI-Interaktionen
"""
import logging
import time
import random
import os
from typing import Optional
from playwright.sync_api import Page, ElementHandle
logger = logging.getLogger("vk_ui_helper")
class VKUIHelper:
"""
Hilfsklasse für VK UI-Interaktionen
"""
def __init__(self, page: Page, screenshots_dir: str = None, save_screenshots: bool = True):
"""
Initialisiert den UI Helper
"""
self.page = page
self.screenshots_dir = screenshots_dir or "logs/screenshots"
self.save_screenshots = save_screenshots
# Screenshot-Verzeichnis erstellen falls nötig
if self.save_screenshots and not os.path.exists(self.screenshots_dir):
os.makedirs(self.screenshots_dir)
def take_screenshot(self, name: str) -> Optional[str]:
"""
Erstellt einen Screenshot
"""
if not self.save_screenshots:
return None
try:
timestamp = int(time.time())
filename = f"{name}_{timestamp}.png"
filepath = os.path.join(self.screenshots_dir, filename)
self.page.screenshot(path=filepath)
logger.debug(f"Screenshot gespeichert: {filepath}")
return filepath
except Exception as e:
logger.warning(f"Fehler beim Erstellen des Screenshots: {e}")
return None
def wait_for_element(self, selector: str, timeout: int = 30000) -> bool:
"""
Wartet auf ein Element
"""
try:
self.page.wait_for_selector(selector, timeout=timeout)
return True
except Exception as e:
logger.error(f"Element {selector} nicht gefunden nach {timeout}ms")
return False
def type_with_delay(self, selector: str, text: str, delay_min: float = 0.05, delay_max: float = 0.15):
"""
Tippt Text mit menschenähnlicher Verzögerung
"""
try:
element = self.page.locator(selector)
element.click()
for char in text:
element.type(char)
time.sleep(random.uniform(delay_min, delay_max))
except Exception as e:
logger.error(f"Fehler beim Tippen in {selector}: {e}")
raise
def click_with_retry(self, selector: str, max_attempts: int = 3) -> bool:
"""
Klickt auf ein Element mit Wiederholungsversuchen
"""
for attempt in range(max_attempts):
try:
self.page.click(selector)
return True
except Exception as e:
logger.warning(f"Klick-Versuch {attempt + 1} fehlgeschlagen: {e}")
if attempt < max_attempts - 1:
time.sleep(random.uniform(1, 2))
return False
def scroll_to_element(self, selector: str):
"""
Scrollt zu einem Element
"""
try:
self.page.locator(selector).scroll_into_view_if_needed()
time.sleep(random.uniform(0.5, 1))
except Exception as e:
logger.warning(f"Fehler beim Scrollen zu {selector}: {e}")
def is_element_visible(self, selector: str) -> bool:
"""
Prüft ob ein Element sichtbar ist
"""
try:
return self.page.locator(selector).is_visible()
except:
return False
def get_element_text(self, selector: str) -> Optional[str]:
"""
Holt den Text eines Elements
"""
try:
return self.page.locator(selector).text_content()
except Exception as e:
logger.warning(f"Fehler beim Lesen des Texts von {selector}: {e}")
return None
def select_dropdown_option(self, selector: str, value: str):
"""
Wählt eine Option aus einem Dropdown
"""
try:
self.page.select_option(selector, value)
time.sleep(random.uniform(0.3, 0.6))
except Exception as e:
logger.error(f"Fehler beim Auswählen von {value} in {selector}: {e}")
raise
def check_radio_button(self, selector: str):
"""
Wählt einen Radio Button aus
"""
try:
self.page.check(selector)
time.sleep(random.uniform(0.2, 0.4))
except Exception as e:
logger.error(f"Fehler beim Auswählen des Radio Buttons {selector}: {e}")
raise
def wait_for_navigation(self, timeout: int = 30000):
"""
Wartet auf Navigation
"""
try:
self.page.wait_for_load_state("networkidle", timeout=timeout)
except Exception as e:
logger.warning(f"Navigation-Timeout nach {timeout}ms: {e}")

Datei anzeigen

@ -0,0 +1,89 @@
"""
VK Utils - Utility-Funktionen für VK
"""
import logging
import random
import string
from typing import Optional
logger = logging.getLogger("vk_utils")
class VKUtils:
"""
Utility-Funktionen für VK
"""
@staticmethod
def generate_vk_username(first_name: str, last_name: str) -> str:
"""
Generiert einen VK-kompatiblen Benutzernamen
"""
# VK verwendet normalerweise id123456789 Format
# Aber für die URL kann man einen benutzerdefinierten Namen verwenden
base = f"{first_name.lower()}{last_name.lower()}"
# Entferne Sonderzeichen
base = ''.join(c for c in base if c.isalnum())
# Füge zufällige Zahlen hinzu
random_suffix = ''.join(random.choices(string.digits, k=random.randint(2, 4)))
return f"{base}{random_suffix}"
@staticmethod
def format_phone_number(phone: str, country_code: str = "+7") -> str:
"""
Formatiert eine Telefonnummer für VK
VK ist primär in Russland, daher Standard +7
"""
# Entferne alle nicht-numerischen Zeichen
phone_digits = ''.join(c for c in phone if c.isdigit())
# Wenn die Nummer bereits mit Ländercode beginnt
if phone.startswith("+"):
return phone
# Füge Ländercode hinzu
return f"{country_code}{phone_digits}"
@staticmethod
def is_valid_vk_password(password: str) -> bool:
"""
Prüft ob ein Passwort den VK-Anforderungen entspricht
- Mindestens 6 Zeichen
- Enthält Buchstaben und Zahlen
"""
if len(password) < 6:
return False
has_letter = any(c.isalpha() for c in password)
has_digit = any(c.isdigit() for c in password)
return has_letter and has_digit
@staticmethod
def generate_vk_password(length: int = 12) -> str:
"""
Generiert ein VK-kompatibles Passwort
"""
# Stelle sicher dass Buchstaben und Zahlen enthalten sind
password_chars = []
# Mindestens 2 Kleinbuchstaben
password_chars.extend(random.choices(string.ascii_lowercase, k=2))
# Mindestens 2 Großbuchstaben
password_chars.extend(random.choices(string.ascii_uppercase, k=2))
# Mindestens 2 Zahlen
password_chars.extend(random.choices(string.digits, k=2))
# Fülle mit zufälligen Zeichen auf
remaining_length = length - len(password_chars)
all_chars = string.ascii_letters + string.digits
password_chars.extend(random.choices(all_chars, k=remaining_length))
# Mische die Zeichen
random.shuffle(password_chars)
return ''.join(password_chars)

Datei anzeigen

@ -0,0 +1,186 @@
"""
VK Verification - Handhabt die Telefon-Verifizierung
"""
import logging
import time
import random
from typing import Dict, Optional
from playwright.sync_api import Page
from social_networks.vk import vk_selectors as selectors
from social_networks.vk.vk_ui_helper import VKUIHelper
from utils.email_handler import EmailHandler
logger = logging.getLogger("vk_verification")
class VKVerification:
"""
Handhabt die VK Telefon-Verifizierung
"""
def __init__(self, page: Page, ui_helper: VKUIHelper, email_handler: EmailHandler = None,
screenshots_dir: str = None, save_screenshots: bool = True):
"""
Initialisiert den Verification Handler
"""
self.page = page
self.ui_helper = ui_helper
self.email_handler = email_handler
self.screenshots_dir = screenshots_dir
self.save_screenshots = save_screenshots
def handle_phone_verification(self, account_data: Dict[str, str]) -> Dict[str, any]:
"""
Handhabt die Telefonnummer-Verifizierung
"""
try:
logger.info("Starte Telefon-Verifizierung")
# Warte auf Telefonnummer-Eingabefeld
if not self.ui_helper.wait_for_element(selectors.PHONE_INPUT, timeout=10000):
logger.error("Telefonnummer-Eingabefeld nicht gefunden")
return {
"success": False,
"error": "Telefonnummer-Eingabefeld nicht gefunden",
"message": "Verifizierung fehlgeschlagen"
}
self.ui_helper.take_screenshot("phone_verification_page")
# Telefonnummer eingeben
phone = account_data.get("phone", "")
if not phone:
logger.error("Keine Telefonnummer in account_data vorhanden")
return {
"success": False,
"error": "Keine Telefonnummer angegeben",
"message": "Telefonnummer erforderlich"
}
logger.info(f"Gebe Telefonnummer ein: {phone}")
# Prüfe ob Ländercode-Auswahl vorhanden ist
if self.ui_helper.is_element_visible(selectors.PHONE_COUNTRY_CODE):
# TODO: Ländercode auswählen falls nötig
pass
# Telefonnummer eingeben
self.ui_helper.type_with_delay(selectors.PHONE_INPUT, phone)
time.sleep(random.uniform(1, 2))
# Screenshot vor dem Absenden
self.ui_helper.take_screenshot("phone_entered")
# Absenden
if self.ui_helper.wait_for_element(selectors.PHONE_SUBMIT_BUTTON, timeout=5000):
logger.info("Sende Telefonnummer ab")
self.ui_helper.click_with_retry(selectors.PHONE_SUBMIT_BUTTON)
time.sleep(random.uniform(3, 5))
# Warte auf SMS-Code Eingabefeld
if self.ui_helper.wait_for_element(selectors.SMS_CODE_INPUT, timeout=15000):
logger.info("SMS-Code Eingabefeld gefunden")
self.ui_helper.take_screenshot("sms_code_page")
# Hier würde normalerweise der SMS-Code abgerufen werden
# Für Demo-Zwecke verwenden wir einen Platzhalter
sms_code = self._get_sms_code(phone)
if not sms_code:
logger.error("Kein SMS-Code erhalten")
return {
"success": False,
"error": "Kein SMS-Code erhalten",
"message": "SMS-Verifizierung fehlgeschlagen"
}
# SMS-Code eingeben
logger.info(f"Gebe SMS-Code ein: {sms_code}")
self.ui_helper.type_with_delay(selectors.SMS_CODE_INPUT, sms_code)
time.sleep(random.uniform(1, 2))
# Code absenden
if self.ui_helper.wait_for_element(selectors.SMS_SUBMIT_BUTTON, timeout=5000):
logger.info("Sende SMS-Code ab")
self.ui_helper.click_with_retry(selectors.SMS_SUBMIT_BUTTON)
time.sleep(random.uniform(3, 5))
# Prüfe auf Erfolg
if self._check_verification_success():
logger.info("Telefon-Verifizierung erfolgreich")
self.ui_helper.take_screenshot("verification_success")
return {
"success": True,
"message": "Verifizierung erfolgreich"
}
else:
error_msg = self._get_verification_error()
logger.error(f"Verifizierung fehlgeschlagen: {error_msg}")
return {
"success": False,
"error": error_msg,
"message": f"Verifizierung fehlgeschlagen: {error_msg}"
}
else:
logger.error("SMS-Code Eingabefeld nicht gefunden")
return {
"success": False,
"error": "SMS-Code Eingabefeld nicht gefunden",
"message": "Verifizierung fehlgeschlagen"
}
except Exception as e:
logger.error(f"Fehler bei der Telefon-Verifizierung: {e}")
self.ui_helper.take_screenshot("verification_error")
return {
"success": False,
"error": str(e),
"message": f"Verifizierung fehlgeschlagen: {str(e)}"
}
def _get_sms_code(self, phone: str) -> Optional[str]:
"""
Ruft den SMS-Code ab
TODO: Implementierung für echte SMS-Code Abfrage
"""
logger.warning("SMS-Code Abruf noch nicht implementiert - verwende Platzhalter")
# In einer echten Implementierung würde hier der SMS-Code
# von einem SMS-Service abgerufen werden
return "123456" # Platzhalter
def _check_verification_success(self) -> bool:
"""
Prüft ob die Verifizierung erfolgreich war
"""
try:
# Prüfe ob wir weitergeleitet wurden
current_url = self.page.url
if "welcome" in current_url or "feed" in current_url:
return True
# Prüfe ob SMS-Code Feld noch sichtbar ist
if self.ui_helper.is_element_visible(selectors.SMS_CODE_INPUT):
return False
# Prüfe auf Fehlermeldung
if self.ui_helper.is_element_visible(selectors.PHONE_ERROR):
return False
return True
except Exception as e:
logger.warning(f"Fehler bei der Verifizierungs-Prüfung: {e}")
return False
def _get_verification_error(self) -> str:
"""
Holt die Fehlermeldung falls vorhanden
"""
try:
if self.ui_helper.is_element_visible(selectors.PHONE_ERROR):
return self.ui_helper.get_element_text(selectors.PHONE_ERROR) or "Verifizierung fehlgeschlagen"
return "Verifizierung fehlgeschlagen"
except:
return "Unbekannter Fehler"

Datei anzeigen

@ -0,0 +1,37 @@
"""
VK Workflow - Workflow-Definitionen für VK
"""
# Workflow-Schritte für VK
REGISTRATION_WORKFLOW = [
"navigate_to_homepage",
"click_create_account",
"fill_registration_form",
"handle_phone_verification",
"complete_profile",
"verify_account_creation"
]
LOGIN_WORKFLOW = [
"navigate_to_login",
"enter_credentials",
"handle_2fa_if_needed",
"verify_login_success"
]
# Timeouts in Sekunden
TIMEOUTS = {
"page_load": 30,
"element_wait": 10,
"verification_wait": 60,
"sms_wait": 120
}
# Fehler-Nachrichten
ERROR_MESSAGES = {
"phone_already_used": "Diese Telefonnummer wird bereits verwendet",
"invalid_phone": "Ungültige Telefonnummer",
"invalid_code": "Ungültiger Verifizierungscode",
"too_many_attempts": "Zu viele Versuche",
"account_blocked": "Account wurde blockiert"
}

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
}
}