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