Dieser Commit ist enthalten in:
Claude Project Manager
2025-07-03 21:11:05 +02:00
Commit 08ed938105
239 geänderte Dateien mit 21554 neuen und 0 gelöschten Zeilen

0
social_networks/__init__.py Normale Datei
Datei anzeigen

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -0,0 +1,487 @@
"""
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
# 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"):
"""
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
"""
self.headless = headless
self.use_proxy = use_proxy
self.proxy_type = proxy_type
self.save_screenshots = save_screenshots
self.slowmo = slowmo
self.debug = debug
self.email_domain = email_domain
# 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
# Status und Ergebnis der Automatisierung
self.status = {
"success": False,
"stage": "initialized",
"error": None,
"account_data": {}
}
# Debug-Logging
if self.debug:
logging.getLogger().setLevel(logging.DEBUG)
logger.info(f"Basis-Automatisierung initialisiert (Proxy: {use_proxy}, Typ: {proxy_type})")
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()
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 _close_browser(self) -> None:
"""Schließt den Browser und gibt Ressourcen frei."""
if self.browser:
self.browser.close()
self.browser = None
logger.info("Browser geschlossen")
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 _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 _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 get_status(self) -> Dict[str, Any]:
"""
Gibt den aktuellen Status der Automatisierung zurück.
Returns:
Dict[str, Any]: Aktueller Status
"""
return self.status
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

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

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -0,0 +1,330 @@
# social_networks/instagram/instagram_automation.py
"""
Instagram-Automatisierung - Hauptklasse für Instagram-Automatisierungsfunktionalität
"""
import logging
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
# 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
# Konfiguriere Logger
logger = logging.getLogger("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):
"""
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
)
# 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)
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
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}")
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 Instagram-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 Instagram-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 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()

Datei anzeigen

@ -0,0 +1,576 @@
# social_networks/instagram/instagram_login.py
"""
Instagram-Login - Klasse für die Anmeldefunktionalitä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
# Konfiguriere Logger
logger = logging.getLogger("instagram_login")
class InstagramLogin:
"""
Klasse für die Anmeldung bei Instagram-Konten.
Enthält alle Methoden für den Login-Prozess.
"""
def __init__(self, automation):
"""
Initialisiert die Instagram-Login-Funktionalität.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
self.selectors = InstagramSelectors()
self.workflow = InstagramWorkflow.get_login_workflow()
logger.debug("Instagram-Login initialisiert")
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
"""
Führt den Login-Prozess für ein Instagram-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
"""
# Hole Browser-Referenz von der Hauptklasse
self.browser = self.automation.browser
# 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 Instagram-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. "Anmeldedaten speichern"-Dialog behandeln
if account_data["skip_save_login"]:
self._handle_save_login_prompt()
# 6. Benachrichtigungsdialog behandeln
self._handle_notifications_prompt()
# 7. 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"Instagram-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 Instagram-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 Instagram-Login-Seite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Zur Login-Seite navigieren
self.browser.navigate_to(InstagramSelectors.LOGIN_URL)
# Warten, bis die Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Screenshot erstellen
self.automation._take_screenshot("login_page")
# Prüfen, ob Login-Formular sichtbar ist
if not self.browser.is_element_visible(InstagramSelectors.LOGIN_USERNAME_FIELD, timeout=5000):
logger.warning("Login-Formular nicht sichtbar")
return False
logger.info("Erfolgreich zur Login-Seite 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.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
# Cookie-Dialog-Erkennung
if self.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.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
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:
# Benutzername/E-Mail eingeben
username_success = self.automation.ui_helper.fill_field_fuzzy(
InstagramSelectors.get_field_labels("username"),
account_data["username"],
InstagramSelectors.LOGIN_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.LOGIN_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("login_form_filled")
# Formular absenden
submit_success = self.automation.ui_helper.click_button_fuzzy(
["Anmelden", "Log in", "Login"],
InstagramSelectors.SUBMIT_BUTTON
)
if not submit_success:
logger.error("Konnte Login-Formular nicht absenden")
return False
# Nach dem Absenden warten
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
# Ü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 _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 = [
InstagramSelectors.ERROR_MESSAGE,
"p[class*='error']",
"div[role='alert']",
"p[data-testid='login-error-message']"
]
for selector in error_selectors:
error_element = self.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.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[aria-label='Sicherheitscode']",
"input[aria-label='Security code']",
"input[placeholder*='code']"
]
for selector in two_fa_selectors:
if self.browser.is_element_visible(selector, timeout=2000):
logger.info("Zwei-Faktor-Authentifizierung erforderlich")
return True, None
# Texte, die auf 2FA hinweisen
two_fa_indicators = InstagramSelectors.get_two_fa_indicators()
# Seiteninhalt durchsuchen
page_content = self.browser.page.content()
for indicator in two_fa_indicators:
if indicator.lower() in page_content.lower():
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[aria-label='Sicherheitscode']",
"input[aria-label='Security code']",
"input[placeholder*='code']"
]
two_fa_field = None
for selector in two_fa_selectors:
element = self.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.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.browser.is_element_visible(selector, timeout=1000):
if self.browser.click_element(selector):
confirm_clicked = True
break
if not confirm_clicked:
# Alternative: Mit Tastendruck bestätigen
self.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_save_login_prompt(self) -> bool:
"""
Behandelt den "Anmeldedaten speichern"-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(), 'Überspringen')]"
]
for selector in not_now_selectors:
if self.browser.is_element_visible(selector, timeout=3000):
if self.browser.click_element(selector):
logger.info("'Anmeldedaten speichern'-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 'Anmeldedaten speichern'-Dialog erkannt")
return True
except Exception as e:
logger.warning(f"Fehler beim Behandeln des 'Anmeldedaten speichern'-Dialogs: {e}")
# Dies ist nicht kritisch, daher geben wir trotzdem True zurück
return True
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.browser.is_element_visible(selector, timeout=3000):
if self.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 = InstagramSelectors.SUCCESS_INDICATORS
for indicator in success_indicators:
if self.browser.is_element_visible(indicator, timeout=2000):
logger.info(f"Login-Erfolgsindikator gefunden: {indicator}")
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 or self.browser.is_element_visible(InstagramSelectors.LOGIN_USERNAME_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 anzeigen

@ -0,0 +1,735 @@
# 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
# Konfiguriere Logger
logger = logging.getLogger("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
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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
"""
# Hole Browser-Referenz von der Hauptklasse
self.browser = self.automation.browser
# 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
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 behandeln
self._handle_cookie_banner()
# 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
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
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
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
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")
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.browser.navigate_to(InstagramSelectors.SIGNUP_URL)
# Warten, bis die Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Screenshot erstellen
self.automation._take_screenshot("signup_page")
# Prüfen, ob Registrierungsformular sichtbar ist
if not self.browser.is_element_visible(InstagramSelectors.EMAIL_PHONE_FIELD, timeout=5000):
logger.warning("Registrierungsformular nicht sichtbar")
return False
logger.info("Erfolgreich zur Registrierungsseite navigiert")
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.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
# Cookie-Dialog-Erkennung
if self.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.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
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.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.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()
# Nach dem Absenden prüfen, ob das Formular für das Geburtsdatum erscheint
birthday_visible = self.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.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.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.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.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.browser.is_element_visible(
InstagramSelectors.CONFIRMATION_CODE_FIELD, timeout=10000
) or self.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.browser.is_element_visible(InstagramSelectors.CONFIRMATION_CODE_FIELD, timeout=5000) and not \
self.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(
email_domain=self.automation.email_domain,
platform="instagram",
timeout=120 # Warte bis zu 2 Minuten auf den Code
)
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.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.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.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,242 @@
"""
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
SUCCESS_INDICATORS = [
"svg[aria-label='Home']",
"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,668 @@
# 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
# Konfiguriere Logger
logger = logging.getLogger("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
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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.browser is None:
self.browser = self.automation.browser
if self.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.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.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.browser.is_element_visible(aria_selector, timeout=1000):
if self.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.browser.is_element_visible(placeholder_selector, timeout=1000):
if self.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.browser.is_element_visible(name_selector, timeout=1000):
if self.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
button_selector = f"button:has-text('{text}')"
button = self.browser.page.query_selector(button_selector)
if button:
logger.info(f"Button mit exaktem Text gefunden: {text}")
button.click()
return True
# Alternativer Selektor für Links und andere klickbare Elemente
link_selector = f"a:has-text('{text}')"
link = self.browser.page.query_selector(link_selector)
if link:
logger.info(f"Link mit exaktem Text gefunden: {text}")
link.click()
return True
# Versuche alle Buttons auf der Seite zu finden und zu prüfen
all_buttons = self.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()
logger.debug(f"Button-Text: '{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}')")
button.click()
return True
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
if fallback_selector:
logger.info(f"Versuche Fallback-Selektor: {fallback_selector}")
if self.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.browser.is_element_visible(aria_selector, timeout=1000):
if self.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.browser.is_element_visible(xpath_selector, timeout=1000):
if self.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.browser.is_element_visible(link_selector, timeout=1000):
if self.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.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 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.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.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.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.browser.is_element_visible(selector, timeout=2000):
logger.warning(f"Captcha erkannt (Selektor): {selector}")
return True
# Nach Texten suchen
page_content = self.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.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.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.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.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.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.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.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.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,549 @@
# 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
# Konfiguriere Logger
logger = logging.getLogger("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
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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.browser is None:
self.browser = self.automation.browser
if self.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.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.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.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.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.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.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.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.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.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.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.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.browser.is_element_visible(selector, timeout=1000):
logger.info("Konto ist privat (Icon gefunden)")
return True
# Texte prüfen
page_content = self.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.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.browser.page.content().lower()
for indicator in ban_indicators:
if indicator in page_content:
# Vollständigen Text der Fehlermeldung extrahieren
error_element = self.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,491 @@
# 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
# Konfiguriere Logger
logger = logging.getLogger("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
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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
"""
# Hole Browser-Referenz von der Hauptklasse
self.browser = self.automation.browser
# 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.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.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 = [
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.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Codefeld gefunden mit Selektor: {selector}")
if self.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.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Submit-Button gefunden mit Selektor: {selector}")
if self.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.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.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.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.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.browser.is_element_visible(selector, timeout=1000):
if self.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.browser.is_element_visible(selector, timeout=2000):
if self.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,454 @@
"""
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
# Konfiguriere Logger
logger = logging.getLogger("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

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -0,0 +1,328 @@
"""
TikTok-Automatisierung - Hauptklasse für TikTok-Automatisierungsfunktionalität
"""
import logging
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
# 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 = logging.getLogger("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):
"""
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)
"""
# 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
)
# 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)
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
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}")
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()

Datei anzeigen

@ -0,0 +1,582 @@
"""
TikTok-Login - Klasse für die Anmeldefunktionalität bei TikTok
"""
import logging
import time
import re
from typing import Dict, List, Any, Optional, Tuple
from .tiktok_selectors import TikTokSelectors
from .tiktok_workflow import TikTokWorkflow
# Konfiguriere Logger
logger = logging.getLogger("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
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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
"""
# Hole Browser-Referenz von der Hauptklasse
self.browser = self.automation.browser
# 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.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Zur Login-Seite navigieren
self.browser.navigate_to(TikTokSelectors.LOGIN_URL)
# Warten, bis die Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Screenshot erstellen
self.automation._take_screenshot("login_page")
# Prüfen, ob Login-Dialog sichtbar ist
if not self.browser.is_element_visible(TikTokSelectors.LOGIN_EMAIL_FIELD, timeout=5000):
logger.warning("Login-Dialog nicht sichtbar, versuche Login-Button zu klicken")
# Versuche, den Login-Button zu klicken, um das Login-Modal zu öffnen
login_buttons = [
TikTokSelectors.LOGIN_BUTTON_TOP,
TikTokSelectors.LOGIN_BUTTON_SIDEBAR
]
button_clicked = False
for button in login_buttons:
if self.browser.is_element_visible(button, timeout=2000):
self.browser.click_element(button)
button_clicked = True
break
if not button_clicked:
logger.warning("Keine Login-Buttons gefunden")
return False
# Warten, bis der Login-Dialog erscheint
self.automation.human_behavior.wait_between_actions("decision", 1.5)
# Erneut prüfen, ob der Login-Dialog sichtbar ist
if not self.browser.is_element_visible(TikTokSelectors.LOGIN_DIALOG, timeout=5000):
logger.warning("Login-Dialog nach dem Klicken auf den Login-Button nicht sichtbar")
return False
logger.info("Erfolgreich zur Login-Seite 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.
Returns:
bool: True wenn Banner behandelt wurde oder nicht existiert, False bei Fehler
"""
# Cookie-Dialog-Erkennung
if self.browser.is_element_visible(TikTokSelectors.COOKIE_DIALOG, timeout=2000):
logger.info("Cookie-Banner erkannt")
# Ablehnen-Button suchen und klicken
reject_success = self.automation.ui_helper.click_button_fuzzy(
TikTokSelectors.get_button_texts("reject_cookies"),
TikTokSelectors.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.browser.click_element(TikTokSelectors.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
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:
# E-Mail/Telefon-Login-Option auswählen
email_phone_option = self.automation.ui_helper.click_button_fuzzy(
["Telefon-Nr./E-Mail/Anmeldename nutzen", "Use phone / email / username"],
TikTokSelectors.LOGIN_EMAIL_PHONE_OPTION
)
if not email_phone_option:
logger.warning("Konnte die E-Mail/Telefon-Option nicht auswählen, versuche direkt das Formular auszufüllen")
self.automation.human_behavior.random_delay(0.5, 1.5)
# E-Mail/Benutzername eingeben
username_success = self.automation.ui_helper.fill_field_fuzzy(
TikTokSelectors.get_field_labels("email_username"),
account_data["username"],
TikTokSelectors.LOGIN_EMAIL_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(
TikTokSelectors.get_field_labels("password"),
account_data["password"],
TikTokSelectors.LOGIN_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("login_form_filled")
# Formular absenden
submit_success = self.automation.ui_helper.click_button_fuzzy(
["Anmelden", "Log in", "Login"],
TikTokSelectors.LOGIN_SUBMIT_BUTTON
)
if not submit_success:
logger.error("Konnte Login-Formular nicht absenden")
return False
# Nach dem Absenden warten
self.automation.human_behavior.wait_for_page_load(multiplier=1.5)
# Ü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 _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.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.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.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.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.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.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.browser.is_element_visible(selector, timeout=1000):
if self.browser.click_element(selector):
confirm_clicked = True
break
if not confirm_clicked:
# Alternative: Mit Tastendruck bestätigen
self.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.browser.is_element_visible(selector, timeout=3000):
if self.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.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.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.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 anzeigen

@ -0,0 +1,987 @@
# social_networks/tiktok/tiktok_registration.py
"""
TikTok-Registrierung - Klasse für die Kontoerstellung bei TikTok
"""
import logging
import time
import random
import re
from typing import Dict, List, Any, Optional, Tuple
from .tiktok_selectors import TikTokSelectors
from .tiktok_workflow import TikTokWorkflow
# Konfiguriere Logger
logger = logging.getLogger("tiktok_registration")
class TikTokRegistration:
"""
Klasse für die Registrierung von TikTok-Konten.
Enthält alle Methoden zur Kontoerstellung.
"""
def __init__(self, automation):
"""
Initialisiert die TikTok-Registrierung.
Args:
automation: Referenz auf die Hauptautomatisierungsklasse
"""
self.automation = automation
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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
"""
# Hole Browser-Referenz von der Hauptklasse
self.browser = self.automation.browser
# 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 TikTok-Registrierung für {account_data['username']} via {registration_method}")
try:
# 1. Zur Startseite navigieren
if not self._navigate_to_homepage():
return {
"success": False,
"error": "Konnte nicht zur TikTok-Startseite navigieren",
"stage": "navigation",
"account_data": account_data
}
# 2. Cookie-Banner behandeln
self._handle_cookie_banner()
# 3. Anmelden-Button klicken
if not self._click_login_button():
return {
"success": False,
"error": "Konnte nicht auf Anmelden-Button klicken",
"stage": "login_button",
"account_data": account_data
}
# 4. Registrieren-Link klicken
if not self._click_register_link():
return {
"success": False,
"error": "Konnte nicht auf Registrieren-Link klicken",
"stage": "register_link",
"account_data": account_data
}
# 5. Telefon/E-Mail-Option auswählen
if not self._click_phone_email_option():
return {
"success": False,
"error": "Konnte nicht auf Telefon/E-Mail-Option klicken",
"stage": "phone_email_option",
"account_data": account_data
}
# 6. E-Mail oder Telefon als 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
}
# 7. Geburtsdatum eingeben
if not self._enter_birthday(account_data["birthday"]):
return {
"success": False,
"error": "Fehler beim Eingeben des Geburtsdatums",
"stage": "birthday",
"account_data": account_data
}
# 8. Registrierungsformular ausfüllen
if not self._fill_registration_form(account_data, registration_method):
return {
"success": False,
"error": "Fehler beim Ausfüllen des Registrierungsformulars",
"stage": "registration_form",
"account_data": account_data
}
# 9. Bestätigungscode abrufen und eingeben
if not self._handle_verification(account_data, registration_method):
return {
"success": False,
"error": "Fehler bei der Verifizierung",
"stage": "verification",
"account_data": account_data
}
# 10. Benutzernamen erstellen
if not self._create_username(account_data):
return {
"success": False,
"error": "Fehler beim Erstellen des Benutzernamens",
"stage": "username",
"account_data": account_data
}
# 11. Erfolgreiche Registrierung überprüfen
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"TikTok-Account {account_data['username']} 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 _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("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 _navigate_to_homepage(self) -> bool:
"""
Navigiert zur TikTok-Startseite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Zur Startseite navigieren
self.browser.navigate_to(TikTokSelectors.BASE_URL)
# Warten, bis die Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Screenshot erstellen
self.automation._take_screenshot("tiktok_homepage")
# Prüfen, ob die Seite korrekt geladen wurde
if not self.browser.is_element_visible(TikTokSelectors.LOGIN_BUTTON, timeout=5000):
logger.warning("TikTok-Startseite nicht korrekt geladen")
return False
logger.info("Erfolgreich zur TikTok-Startseite navigiert")
return True
except Exception as e:
logger.error(f"Fehler beim Navigieren zur TikTok-Startseite: {e}")
return False
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
"""
# Cookie-Dialog-Erkennung
if self.browser.is_element_visible(TikTokSelectors.COOKIE_DIALOG, timeout=2000):
logger.info("Cookie-Banner erkannt")
# Ablehnen-Button suchen und klicken
reject_success = self.automation.ui_helper.click_button_fuzzy(
TikTokSelectors.get_button_texts("reject_cookies"),
TikTokSelectors.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.browser.click_element(TikTokSelectors.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
def _click_login_button(self) -> bool:
"""
Klickt auf den Anmelden-Button auf der Startseite.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Versuche zuerst den Hauptbutton
if self.browser.is_element_visible(TikTokSelectors.LOGIN_BUTTON, timeout=2000):
result = self.browser.click_element(TikTokSelectors.LOGIN_BUTTON)
if result:
logger.info("Anmelden-Button erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche alternativ den Button in der oberen rechten Ecke
if self.browser.is_element_visible(TikTokSelectors.LOGIN_BUTTON_TOP_RIGHT, timeout=2000):
result = self.browser.click_element(TikTokSelectors.LOGIN_BUTTON_TOP_RIGHT)
if result:
logger.info("Anmelden-Button (oben rechts) erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Anmelden", "Log in", "Login"],
TikTokSelectors.LOGIN_BUTTON_FALLBACK
)
if result:
logger.info("Anmelden-Button über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte keinen Anmelden-Button finden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf den Anmelden-Button: {e}")
return False
def _click_register_link(self) -> bool:
"""
Klickt auf den Registrieren-Link im Login-Dialog.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis der Login-Dialog angezeigt wird
self.automation.human_behavior.random_delay(1.0, 2.0)
# Prüfen, ob wir bereits im Registrierungsdialog sind
if self.browser.is_element_visible(TikTokSelectors.REGISTER_DIALOG_TITLE, timeout=2000):
logger.info("Bereits im Registrierungsdialog")
return True
# Versuche, den Registrieren-Link zu finden und zu klicken
if self.browser.is_element_visible(TikTokSelectors.REGISTER_LINK, timeout=2000):
result = self.browser.click_element(TikTokSelectors.REGISTER_LINK)
if result:
logger.info("Registrieren-Link erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Registrieren", "Sign up", "Konto erstellen", "Register"],
TikTokSelectors.REGISTER_LINK_FALLBACK
)
if result:
logger.info("Registrieren-Link über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Prüfe, ob der Text "Du hast noch kein Konto? Registrieren" vorhanden ist
register_link_text = "Du hast noch kein Konto? Registrieren"
elements = self.browser.page.query_selector_all("*")
for element in elements:
if register_link_text in element.inner_text():
# Finde das "Registrieren"-Wort und klicke darauf
matches = re.search(r"(.*?)(Registrieren)$", element.inner_text())
if matches:
# Versuche, nur auf das Wort "Registrieren" zu klicken
element.click()
logger.info("Auf 'Registrieren' Text geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte keinen Registrieren-Link finden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf den Registrieren-Link: {e}")
return False
def _click_phone_email_option(self) -> bool:
"""
Klickt auf die Telefon/E-Mail-Option im Registrierungsdialog.
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis der Registrierungsdialog angezeigt wird
self.automation.human_behavior.random_delay(1.0, 2.0)
# Prüfen, ob wir bereits die Optionen für Telefon/E-Mail sehen
if self.browser.is_element_visible(TikTokSelectors.EMAIL_FIELD, timeout=2000) or \
self.browser.is_element_visible(TikTokSelectors.PHONE_FIELD, timeout=2000):
logger.info("Bereits auf der Telefon/E-Mail-Registrierungsseite")
return True
# Versuche, die Telefon/E-Mail-Option zu finden und zu klicken
if self.browser.is_element_visible(TikTokSelectors.PHONE_EMAIL_OPTION, timeout=2000):
result = self.browser.click_element(TikTokSelectors.PHONE_EMAIL_OPTION)
if result:
logger.info("Telefon/E-Mail-Option erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Telefonnummer oder E-Mail-Adresse nutzen", "Use phone or email", "Phone or email"],
TikTokSelectors.PHONE_EMAIL_OPTION_FALLBACK
)
if result:
logger.info("Telefon/E-Mail-Option über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error("Konnte keine Telefon/E-Mail-Option finden")
return False
except Exception as e:
logger.error(f"Fehler beim Klicken auf die Telefon/E-Mail-Option: {e}")
return False
def _select_registration_method(self, registration_method: str) -> bool:
"""
Wählt die Registrierungsmethode (E-Mail oder Telefon).
Args:
registration_method: "email" oder "phone"
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis die Registrierungsmethoden-Seite geladen ist
self.automation.human_behavior.random_delay(1.0, 2.0)
if registration_method == "email":
# Wenn bereits das E-Mail-Feld sichtbar ist, sind wir schon auf der richtigen Seite
if self.browser.is_element_visible(TikTokSelectors.EMAIL_FIELD, timeout=1000):
logger.info("Bereits auf der E-Mail-Registrierungsseite")
return True
# Suche nach dem "Mit E-Mail-Adresse registrieren" Link
if self.browser.is_element_visible(TikTokSelectors.EMAIL_OPTION, timeout=2000):
result = self.browser.click_element(TikTokSelectors.EMAIL_OPTION)
if result:
logger.info("E-Mail-Registrierungsmethode erfolgreich ausgewählt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Mit E-Mail-Adresse registrieren", "Register with email", "E-Mail-Adresse"],
TikTokSelectors.EMAIL_OPTION_FALLBACK
)
if result:
logger.info("E-Mail-Option über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
elif registration_method == "phone":
# Wenn bereits das Telefon-Feld sichtbar ist, sind wir schon auf der richtigen Seite
if self.browser.is_element_visible(TikTokSelectors.PHONE_FIELD, timeout=1000):
logger.info("Bereits auf der Telefon-Registrierungsseite")
return True
# Suche nach dem "Mit Telefonnummer registrieren" Link
if self.browser.is_element_visible(TikTokSelectors.PHONE_OPTION, timeout=2000):
result = self.browser.click_element(TikTokSelectors.PHONE_OPTION)
if result:
logger.info("Telefon-Registrierungsmethode erfolgreich ausgewählt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
# Versuche es mit Fuzzy-Button-Matching
result = self.automation.ui_helper.click_button_fuzzy(
["Mit Telefonnummer registrieren", "Register with phone", "Telefonnummer"],
TikTokSelectors.PHONE_OPTION_FALLBACK
)
if result:
logger.info("Telefon-Option über Fuzzy-Matching erfolgreich geklickt")
self.automation.human_behavior.random_delay(0.5, 1.5)
return True
logger.error(f"Konnte Registrierungsmethode '{registration_method}' nicht auswählen")
return False
except Exception as e:
logger.error(f"Fehler beim Auswählen der Registrierungsmethode: {e}")
return False
def _enter_birthday(self, birthday: Dict[str, int]) -> bool:
"""
Gibt das Geburtsdatum ein.
Args:
birthday: Dictionary mit 'year', 'month', 'day' Schlüsseln
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis die Geburtstagsauswahl angezeigt wird
self.automation.human_behavior.random_delay(1.0, 2.0)
# Monat auswählen
month_dropdown = self.browser.wait_for_selector(TikTokSelectors.BIRTHDAY_MONTH_DROPDOWN, timeout=5000)
if not month_dropdown:
logger.error("Monat-Dropdown nicht gefunden")
return False
month_dropdown.click()
self.automation.human_behavior.random_delay(0.3, 0.8)
# Monat-Option auswählen
month_option = self.browser.wait_for_selector(
TikTokSelectors.get_month_option_selector(birthday["month"]),
timeout=3000
)
if month_option:
month_option.click()
logger.info(f"Monat {birthday['month']} ausgewählt")
else:
# Fallback: Monat über Select-Funktion auswählen
month_success = self.browser.select_option(
TikTokSelectors.BIRTHDAY_MONTH_DROPDOWN,
str(birthday["month"])
)
if not month_success:
logger.error(f"Konnte Monat {birthday['month']} nicht auswählen")
return False
self.automation.human_behavior.random_delay(0.3, 0.8)
# Tag auswählen
day_dropdown = self.browser.wait_for_selector(TikTokSelectors.BIRTHDAY_DAY_DROPDOWN, timeout=3000)
if not day_dropdown:
logger.error("Tag-Dropdown nicht gefunden")
return False
day_dropdown.click()
self.automation.human_behavior.random_delay(0.3, 0.8)
# Tag-Option auswählen
day_option = self.browser.wait_for_selector(
TikTokSelectors.get_day_option_selector(birthday["day"]),
timeout=3000
)
if day_option:
day_option.click()
logger.info(f"Tag {birthday['day']} ausgewählt")
else:
# Fallback: Tag über Select-Funktion auswählen
day_success = self.browser.select_option(
TikTokSelectors.BIRTHDAY_DAY_DROPDOWN,
str(birthday["day"])
)
if not day_success:
logger.error(f"Konnte Tag {birthday['day']} nicht auswählen")
return False
self.automation.human_behavior.random_delay(0.3, 0.8)
# Jahr auswählen
year_dropdown = self.browser.wait_for_selector(TikTokSelectors.BIRTHDAY_YEAR_DROPDOWN, timeout=3000)
if not year_dropdown:
logger.error("Jahr-Dropdown nicht gefunden")
return False
year_dropdown.click()
self.automation.human_behavior.random_delay(0.3, 0.8)
# Jahr-Option auswählen
year_option = self.browser.wait_for_selector(
TikTokSelectors.get_year_option_selector(birthday["year"]),
timeout=3000
)
if year_option:
year_option.click()
logger.info(f"Jahr {birthday['year']} ausgewählt")
else:
# Fallback: Jahr über Select-Funktion auswählen
year_success = self.browser.select_option(
TikTokSelectors.BIRTHDAY_YEAR_DROPDOWN,
str(birthday["year"])
)
if not year_success:
logger.error(f"Konnte Jahr {birthday['year']} nicht auswählen")
return False
logger.info(f"Geburtsdatum {birthday['month']}/{birthday['day']}/{birthday['year']} erfolgreich eingegeben")
return True
except Exception as e:
logger.error(f"Fehler beim Eingeben des Geburtsdatums: {e}")
return False
def _fill_registration_form(self, account_data: Dict[str, Any], registration_method: str) -> bool:
"""
Füllt das Registrierungsformular aus.
Args:
account_data: Account-Daten für die Registrierung
registration_method: "email" oder "phone"
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Je nach Registrierungsmethode das entsprechende Feld ausfüllen
if registration_method == "email":
# E-Mail-Feld ausfüllen
email_success = self.automation.ui_helper.fill_field_fuzzy(
["E-Mail-Adresse", "Email", "E-Mail"],
account_data["email"],
TikTokSelectors.EMAIL_FIELD
)
if not email_success:
logger.error("Konnte E-Mail-Feld nicht ausfüllen")
return False
logger.info(f"E-Mail-Feld ausgefüllt: {account_data['email']}")
elif registration_method == "phone":
# Telefonnummer-Feld ausfüllen (ohne Ländervorwahl)
phone_number = account_data["phone"]
if phone_number.startswith("+"):
# Entferne Ländervorwahl, wenn vorhanden
parts = phone_number.split(" ", 1)
if len(parts) > 1:
phone_number = parts[1]
phone_success = self.automation.ui_helper.fill_field_fuzzy(
["Telefonnummer", "Phone number", "Phone"],
phone_number,
TikTokSelectors.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)
# Bei E-Mail-Registrierung auch das Passwort-Feld ausfüllen
if registration_method == "email":
password_success = self.automation.ui_helper.fill_field_fuzzy(
["Passwort", "Password"],
account_data["password"],
TikTokSelectors.PASSWORD_FIELD
)
if not password_success:
logger.error("Konnte Passwort-Feld nicht ausfüllen")
return False
logger.info("Passwort-Feld ausgefüllt")
self.automation.human_behavior.random_delay(0.5, 1.5)
# Code senden Button klicken
send_code_success = self.automation.ui_helper.click_button_fuzzy(
["Code senden", "Send code", "Send verification code"],
TikTokSelectors.SEND_CODE_BUTTON
)
if not send_code_success:
logger.error("Konnte 'Code senden'-Button nicht klicken")
return False
logger.info("'Code senden'-Button erfolgreich geklickt")
return True
except Exception as e:
logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {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 der Bestätigungscode gesendet wurde
self.automation.human_behavior.wait_for_page_load()
self.automation.human_behavior.random_delay(2.0, 4.0)
# 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-Feld ausfüllen
code_success = self.automation.ui_helper.fill_field_fuzzy(
["Gib den sechsstelligen Code ein", "Enter verification code", "Verification code"],
verification_code,
TikTokSelectors.VERIFICATION_CODE_FIELD
)
if not code_success:
logger.error("Konnte Verifizierungscode-Feld nicht ausfüllen")
return False
self.automation.human_behavior.random_delay(1.0, 2.0)
# Weiter-Button klicken
continue_success = self.automation.ui_helper.click_button_fuzzy(
["Weiter", "Continue", "Next", "Submit"],
TikTokSelectors.CONTINUE_BUTTON
)
if not continue_success:
logger.error("Konnte 'Weiter'-Button nicht klicken")
return False
logger.info("Verifizierungscode eingegeben und 'Weiter' geklickt")
# Warten nach der Verifizierung
self.automation.human_behavior.wait_for_page_load()
self.automation.human_behavior.random_delay(1.0, 2.0)
return True
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(
email_domain=self.automation.email_domain,
platform="tiktok",
timeout=120 # Warte bis zu 2 Minuten auf den Code
)
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.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 TikTok-Code",
r"(\d{6}) is your TikTok 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 _create_username(self, account_data: Dict[str, Any]) -> bool:
"""
Erstellt einen Benutzernamen.
Args:
account_data: Account-Daten
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Warten, bis die Benutzernamen-Seite geladen ist
self.automation.human_behavior.wait_for_page_load()
# Prüfen, ob wir auf der Benutzernamen-Seite sind
if not self.browser.is_element_visible(TikTokSelectors.USERNAME_FIELD, timeout=5000):
logger.warning("Benutzernamen-Feld nicht gefunden, möglicherweise ist dieser Schritt übersprungen worden")
# Versuche, den "Überspringen"-Button zu klicken, falls vorhanden
skip_visible = self.browser.is_element_visible(TikTokSelectors.SKIP_USERNAME_BUTTON, timeout=2000)
if skip_visible:
self.browser.click_element(TikTokSelectors.SKIP_USERNAME_BUTTON)
logger.info("Benutzernamen-Schritt übersprungen")
return True
# Möglicherweise wurde der Benutzername automatisch erstellt
logger.info("Benutzernamen-Schritt möglicherweise automatisch abgeschlossen")
return True
# Benutzernamen eingeben
username_success = self.automation.ui_helper.fill_field_fuzzy(
["Benutzername", "Username"],
account_data["username"],
TikTokSelectors.USERNAME_FIELD
)
if not username_success:
logger.error("Konnte Benutzernamen-Feld nicht ausfüllen")
return False
logger.info(f"Benutzernamen-Feld ausgefüllt: {account_data['username']}")
self.automation.human_behavior.random_delay(1.0, 2.0)
# Registrieren-Button klicken
register_success = self.automation.ui_helper.click_button_fuzzy(
["Registrieren", "Register", "Sign up", "Submit"],
TikTokSelectors.REGISTER_BUTTON
)
if not register_success:
logger.error("Konnte 'Registrieren'-Button nicht klicken")
return False
logger.info("'Registrieren'-Button erfolgreich geklickt")
# Warten nach der Registrierung
self.automation.human_behavior.wait_for_page_load()
return True
except Exception as e:
logger.error(f"Fehler beim Erstellen des Benutzernamens: {e}")
return False
def _check_registration_success(self) -> bool:
"""
Überprüft, ob die Registrierung erfolgreich war.
Returns:
bool: True wenn erfolgreich, False sonst
"""
try:
# Warten nach der Registrierung
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 = TikTokSelectors.SUCCESS_INDICATORS
for indicator in success_indicators:
if self.browser.is_element_visible(indicator, timeout=3000):
logger.info(f"Erfolgsindikator gefunden: {indicator}")
return True
# Alternativ prüfen, ob wir auf der TikTok-Startseite sind
current_url = self.browser.page.url
if "tiktok.com" in current_url and "/signup" not in current_url and "/login" not in current_url:
logger.info(f"Erfolg basierend auf URL: {current_url}")
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,150 @@
"""
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"
# Anmelden/Registrieren-Buttons Hauptseite
LOGIN_BUTTON_LEFT = "button#header-login-button"
LOGIN_BUTTON_RIGHT = "button#top-right-action-bar-login-button"
SIGNUP_LINK = "a[href*='/signup']"
# Registrierungsdialog - Methoden
REGISTRATION_DIALOG = "div[role='dialog']"
PHONE_EMAIL_BUTTON = "div[data-e2e='channel-item']"
REGISTER_WITH_EMAIL = "a[href*='/signup/phone-or-email/email']"
REGISTER_WITH_PHONE = "a[href*='/signup/phone-or-email/phone']"
# Geburtsdatum-Selektoren
BIRTHDAY_MONTH_SELECT = "div.css-1fi2hzv-DivSelectLabel:contains('Monat')"
BIRTHDAY_DAY_SELECT = "div.css-1fi2hzv-DivSelectLabel:contains('Tag')"
BIRTHDAY_YEAR_SELECT = "div.css-1fi2hzv-DivSelectLabel:contains('Jahr')"
BIRTHDAY_DROPDOWN_OPTION = "div[role='option']"
# Formularfelder - E-Mail-Registrierung
EMAIL_FIELD = "input[placeholder='E-Mail-Adresse']"
PASSWORD_FIELD = "input[placeholder='Passwort']"
VERIFICATION_CODE_FIELD = "input[placeholder*='sechsstelligen Code']"
USERNAME_FIELD = "input[placeholder='Benutzername']"
# 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']"
REGISTER_BUTTON = "button:contains('Registrieren')"
SKIP_BUTTON = "button:contains('Überspringen')"
# Checkbox
NEWSLETTER_CHECKBOX = "input[type='checkbox']"
# 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')"
# 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"
]
}
@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"]

Datei anzeigen

@ -0,0 +1,523 @@
"""
TikTok-UI-Helper - Hilfsmethoden für die Interaktion mit der TikTok-UI
"""
import logging
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
# Konfiguriere Logger
logger = logging.getLogger("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
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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.browser is None:
self.browser = self.automation.browser
if self.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.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.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.browser.is_element_visible(aria_selector, timeout=1000):
if self.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.browser.is_element_visible(placeholder_selector, timeout=1000):
if self.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.browser.is_element_visible(name_selector, timeout=1000):
if self.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.browser.is_element_visible(selector, timeout=1000):
button_element = self.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.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.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.browser.is_element_visible(aria_selector, timeout=1000):
if self.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.browser.is_element_visible(xpath_selector, timeout=1000):
if self.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.browser.is_element_visible(link_selector, timeout=1000):
if self.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.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.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.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.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.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.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.browser.is_element_visible(selector, timeout=2000):
logger.warning(f"Captcha erkannt (Selektor): {selector}")
return True
# Nach Texten suchen
page_content = self.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.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.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Registrierung erfolgreich (Indikator gefunden: {selector})")
return True
# URL überprüfen
current_url = self.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,495 @@
"""
TikTok-Utils - Hilfsfunktionen für die TikTok-Automatisierung.
"""
import logging
import re
import time
import random
from typing import Dict, List, Any, Optional, Tuple, Union
from .tiktok_selectors import TikTokSelectors
# Konfiguriere Logger
logger = logging.getLogger("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
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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.browser is None:
self.browser = self.automation.browser
if self.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.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.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.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.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.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.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.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.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.browser.is_element_visible(selector, timeout=1000):
if self.browser.click_element(selector):
logger.info("Dialog geschlossen")
return True
# Wenn kein Schließen-Button gefunden wurde, Escape-Taste drücken
self.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.browser.is_element_visible(selector, timeout=1000):
if self.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.browser.is_element_visible(selector, timeout=1000):
if self.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.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.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.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Benutzer ist angemeldet (Indikator: {selector})")
return True
# URL überprüfen
current_url = self.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.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.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,457 @@
# social_networks/tiktok/tiktok_verification.py
"""
TikTok-Verifizierung - Klasse für die Verifizierungsfunktionalität bei TikTok
"""
import logging
import time
import re
from typing import Dict, List, Any, Optional, Tuple
from .tiktok_selectors import TikTokSelectors
from .tiktok_workflow import TikTokWorkflow
# Konfiguriere Logger
logger = logging.getLogger("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
self.browser = None # Wird zur Laufzeit auf automation.browser gesetzt
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
"""
# Hole Browser-Referenz von der Hauptklasse
self.browser = self.automation.browser
# 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.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.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,
"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.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Codefeld gefunden mit Selektor: {selector}")
if self.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.WEITER_BUTTON,
"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.browser.is_element_visible(selector, timeout=2000):
logger.info(f"Weiter-Button gefunden mit Selektor: {selector}")
if self.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.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.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.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.browser.is_element_visible(selector, timeout=1000):
if self.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.browser.is_element_visible(selector, timeout=2000):
if self.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
"""
import logging
from typing import Dict, List, Any, Optional, Tuple
import re
from utils.text_similarity import TextSimilarity
# Konfiguriere Logger
logger = logging.getLogger("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