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