Initial commit
Dieser Commit ist enthalten in:
0
social_networks/vk/__init__.py
Normale Datei
0
social_networks/vk/__init__.py
Normale Datei
240
social_networks/vk/vk_automation.py
Normale Datei
240
social_networks/vk/vk_automation.py
Normale Datei
@ -0,0 +1,240 @@
|
||||
"""
|
||||
VK Automatisierung - Hauptklasse
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict, Optional, Tuple
|
||||
from playwright.sync_api import Page
|
||||
|
||||
from social_networks.base_automation import BaseAutomation
|
||||
from social_networks.vk import vk_selectors as selectors
|
||||
from social_networks.vk.vk_ui_helper import VKUIHelper
|
||||
from social_networks.vk.vk_registration import VKRegistration
|
||||
from social_networks.vk.vk_login import VKLogin
|
||||
from social_networks.vk.vk_verification import VKVerification
|
||||
from social_networks.vk.vk_utils import VKUtils
|
||||
|
||||
logger = logging.getLogger("vk_automation")
|
||||
|
||||
class VKAutomation(BaseAutomation):
|
||||
"""
|
||||
VK-spezifische Automatisierung
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialisiert die VK-Automatisierung
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.platform_name = "vk"
|
||||
self.ui_helper = None
|
||||
self.registration = None
|
||||
self.login_helper = None
|
||||
self.verification = None
|
||||
self.utils = None
|
||||
|
||||
def _initialize_helpers(self, page: Page):
|
||||
"""
|
||||
Initialisiert die Hilfsklassen
|
||||
"""
|
||||
self.ui_helper = VKUIHelper(page, self.screenshots_dir, self.save_screenshots)
|
||||
self.registration = VKRegistration(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
|
||||
self.login_helper = VKLogin(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
|
||||
self.verification = VKVerification(page, self.ui_helper, self.email_handler, self.screenshots_dir, self.save_screenshots)
|
||||
self.utils = VKUtils()
|
||||
|
||||
def register_account(self, full_name: str, age: int, registration_method: str = "phone",
|
||||
phone_number: str = None, **kwargs) -> Dict[str, any]:
|
||||
"""
|
||||
Erstellt einen neuen VK-Account
|
||||
|
||||
Args:
|
||||
full_name: Vollständiger Name für den Account
|
||||
age: Alter des Benutzers
|
||||
registration_method: Registrierungsmethode (nur "phone" für VK)
|
||||
phone_number: Telefonnummer (erforderlich für VK)
|
||||
**kwargs: Weitere optionale Parameter
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starte VK Account-Registrierung für {full_name}")
|
||||
|
||||
# Erstelle account_data aus den Parametern
|
||||
account_data = {
|
||||
"full_name": full_name,
|
||||
"first_name": kwargs.get("first_name", full_name.split()[0] if full_name else ""),
|
||||
"last_name": kwargs.get("last_name", full_name.split()[-1] if full_name and len(full_name.split()) > 1 else ""),
|
||||
"age": age,
|
||||
"birthday": kwargs.get("birthday", self._generate_birthday(age)),
|
||||
"gender": kwargs.get("gender", random.choice(["male", "female"])),
|
||||
"username": kwargs.get("username", ""),
|
||||
"password": kwargs.get("password", ""),
|
||||
"email": kwargs.get("email", ""),
|
||||
"phone": phone_number or ""
|
||||
}
|
||||
|
||||
# Initialisiere Browser, falls noch nicht geschehen
|
||||
if not self.browser or not hasattr(self.browser, 'page'):
|
||||
if not self._initialize_browser():
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Browser konnte nicht initialisiert werden",
|
||||
"message": "Browser-Initialisierung fehlgeschlagen"
|
||||
}
|
||||
|
||||
# Page-Objekt holen
|
||||
page = self.browser.page
|
||||
self._initialize_helpers(page)
|
||||
|
||||
# VK Homepage öffnen
|
||||
logger.info("Navigiere zu VK Homepage")
|
||||
page.goto(selectors.BASE_URL, wait_until="domcontentloaded")
|
||||
time.sleep(random.uniform(2, 4))
|
||||
|
||||
# Screenshot der Startseite
|
||||
self.ui_helper.take_screenshot("vk_homepage")
|
||||
|
||||
# Cookie Banner handhaben
|
||||
self._handle_cookie_banner(page)
|
||||
|
||||
# "Konto erstellen" Button klicken
|
||||
logger.info("Suche 'Konto erstellen' Button")
|
||||
try:
|
||||
# Versuche verschiedene Selektoren
|
||||
create_button_clicked = False
|
||||
|
||||
# Versuche CSS Selektor
|
||||
if page.locator(selectors.CREATE_ACCOUNT_BUTTON).count() > 0:
|
||||
page.click(selectors.CREATE_ACCOUNT_BUTTON)
|
||||
create_button_clicked = True
|
||||
logger.info("Button mit CSS Selektor gefunden und geklickt")
|
||||
|
||||
# Versuche XPath wenn CSS nicht funktioniert
|
||||
elif page.locator(selectors.CREATE_ACCOUNT_BUTTON_XPATH).count() > 0:
|
||||
page.click(selectors.CREATE_ACCOUNT_BUTTON_XPATH)
|
||||
create_button_clicked = True
|
||||
logger.info("Button mit XPath gefunden und geklickt")
|
||||
|
||||
# Versuche alternativen Selektor
|
||||
elif page.locator(selectors.CREATE_ACCOUNT_BUTTON_ALTERNATE).count() > 0:
|
||||
page.click(selectors.CREATE_ACCOUNT_BUTTON_ALTERNATE)
|
||||
create_button_clicked = True
|
||||
logger.info("Button mit alternativem Selektor gefunden und geklickt")
|
||||
|
||||
if not create_button_clicked:
|
||||
raise Exception("'Konto erstellen' Button nicht gefunden")
|
||||
|
||||
time.sleep(random.uniform(2, 3))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Klicken des 'Konto erstellen' Buttons: {e}")
|
||||
self.ui_helper.take_screenshot("create_account_button_error")
|
||||
raise
|
||||
|
||||
# Registrierungsformular ausfüllen
|
||||
registration_result = self.registration.fill_registration_form(account_data)
|
||||
if not registration_result["success"]:
|
||||
return registration_result
|
||||
|
||||
# Telefonnummer-Verifizierung
|
||||
verification_result = self.verification.handle_phone_verification(account_data)
|
||||
if not verification_result["success"]:
|
||||
return verification_result
|
||||
|
||||
# Erfolg
|
||||
logger.info("VK Account-Registrierung erfolgreich abgeschlossen")
|
||||
return {
|
||||
"success": True,
|
||||
"username": account_data.get("username"),
|
||||
"password": account_data.get("password"),
|
||||
"email": account_data.get("email"),
|
||||
"phone": account_data.get("phone"),
|
||||
"message": "Account erfolgreich erstellt"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der VK-Registrierung: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Registrierung fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
finally:
|
||||
self._close_browser()
|
||||
|
||||
def login(self, username: str, password: str) -> Dict[str, any]:
|
||||
"""
|
||||
Meldet sich bei einem bestehenden VK-Account an
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starte VK Login für {username}")
|
||||
|
||||
# Initialisiere Browser, falls noch nicht geschehen
|
||||
if not self.browser or not hasattr(self.browser, 'page'):
|
||||
if not self._initialize_browser():
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Browser konnte nicht initialisiert werden",
|
||||
"message": "Browser-Initialisierung fehlgeschlagen"
|
||||
}
|
||||
|
||||
# Page-Objekt holen
|
||||
page = self.browser.page
|
||||
self._initialize_helpers(page)
|
||||
|
||||
# Login durchführen
|
||||
return self.login_helper.login(username, password)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim VK Login: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Login fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
finally:
|
||||
self._close_browser()
|
||||
|
||||
def _handle_cookie_banner(self, page: Page):
|
||||
"""
|
||||
Handhabt Cookie-Banner falls vorhanden
|
||||
"""
|
||||
try:
|
||||
if page.locator(selectors.COOKIE_BANNER).count() > 0:
|
||||
logger.info("Cookie Banner gefunden")
|
||||
if page.locator(selectors.COOKIE_ACCEPT_BUTTON).count() > 0:
|
||||
page.click(selectors.COOKIE_ACCEPT_BUTTON)
|
||||
logger.info("Cookie Banner akzeptiert")
|
||||
time.sleep(random.uniform(1, 2))
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Handhaben des Cookie Banners: {e}")
|
||||
|
||||
def get_account_info(self) -> Dict[str, any]:
|
||||
"""
|
||||
Ruft Informationen über den aktuellen Account ab
|
||||
"""
|
||||
# TODO: Implementierung
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Noch nicht implementiert"
|
||||
}
|
||||
|
||||
def logout(self) -> bool:
|
||||
"""
|
||||
Meldet sich vom aktuellen Account ab
|
||||
"""
|
||||
# TODO: Implementierung
|
||||
return False
|
||||
|
||||
def _generate_birthday(self, age: int) -> str:
|
||||
"""
|
||||
Generiert ein Geburtsdatum basierend auf dem Alter
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
today = datetime.now()
|
||||
birth_year = today.year - age
|
||||
# Zufälliger Tag im Jahr
|
||||
random_days = random.randint(0, 364)
|
||||
birthday = datetime(birth_year, 1, 1) + timedelta(days=random_days)
|
||||
return birthday.strftime("%Y-%m-%d")
|
||||
127
social_networks/vk/vk_login.py
Normale Datei
127
social_networks/vk/vk_login.py
Normale Datei
@ -0,0 +1,127 @@
|
||||
"""
|
||||
VK Login - Handhabt den Login-Prozess
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict
|
||||
from playwright.sync_api import Page
|
||||
|
||||
from social_networks.vk import vk_selectors as selectors
|
||||
from social_networks.vk.vk_ui_helper import VKUIHelper
|
||||
|
||||
logger = logging.getLogger("vk_login")
|
||||
|
||||
class VKLogin:
|
||||
"""
|
||||
Handhabt den VK Login-Prozess
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page, ui_helper: VKUIHelper, screenshots_dir: str = None, save_screenshots: bool = True):
|
||||
"""
|
||||
Initialisiert den Login Handler
|
||||
"""
|
||||
self.page = page
|
||||
self.ui_helper = ui_helper
|
||||
self.screenshots_dir = screenshots_dir
|
||||
self.save_screenshots = save_screenshots
|
||||
|
||||
def login(self, username: str, password: str) -> Dict[str, any]:
|
||||
"""
|
||||
Führt den Login durch
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starte VK Login für {username}")
|
||||
|
||||
# Navigiere zur Login-Seite
|
||||
self.page.goto(selectors.LOGIN_URL, wait_until="domcontentloaded")
|
||||
time.sleep(random.uniform(2, 3))
|
||||
|
||||
self.ui_helper.take_screenshot("login_page")
|
||||
|
||||
# Email/Telefonnummer eingeben
|
||||
if self.ui_helper.wait_for_element(selectors.LOGIN_EMAIL_INPUT, timeout=10000):
|
||||
logger.info("Gebe Benutzernamen ein")
|
||||
self.ui_helper.type_with_delay(selectors.LOGIN_EMAIL_INPUT, username)
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
|
||||
# Passwort eingeben
|
||||
if self.ui_helper.wait_for_element(selectors.LOGIN_PASSWORD_INPUT, timeout=5000):
|
||||
logger.info("Gebe Passwort ein")
|
||||
self.ui_helper.type_with_delay(selectors.LOGIN_PASSWORD_INPUT, password)
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
|
||||
# Screenshot vor dem Login
|
||||
self.ui_helper.take_screenshot("login_form_filled")
|
||||
|
||||
# Login Button klicken
|
||||
if self.ui_helper.wait_for_element(selectors.LOGIN_SUBMIT_BUTTON, timeout=5000):
|
||||
logger.info("Klicke auf Login Button")
|
||||
self.ui_helper.click_with_retry(selectors.LOGIN_SUBMIT_BUTTON)
|
||||
|
||||
# Warte auf Navigation
|
||||
self.ui_helper.wait_for_navigation()
|
||||
time.sleep(random.uniform(2, 3))
|
||||
|
||||
# Prüfe ob Login erfolgreich war
|
||||
if self._check_login_success():
|
||||
logger.info("Login erfolgreich")
|
||||
self.ui_helper.take_screenshot("login_success")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Login erfolgreich"
|
||||
}
|
||||
else:
|
||||
error_msg = self._get_error_message()
|
||||
logger.error(f"Login fehlgeschlagen: {error_msg}")
|
||||
self.ui_helper.take_screenshot("login_failed")
|
||||
return {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"message": f"Login fehlgeschlagen: {error_msg}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Login: {e}")
|
||||
self.ui_helper.take_screenshot("login_error")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Login fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
|
||||
def _check_login_success(self) -> bool:
|
||||
"""
|
||||
Prüft ob der Login erfolgreich war
|
||||
"""
|
||||
try:
|
||||
# Prüfe ob wir auf der Hauptseite sind
|
||||
current_url = self.page.url
|
||||
if "feed" in current_url or "id" in current_url:
|
||||
return True
|
||||
|
||||
# Prüfe ob Login-Formular noch sichtbar ist
|
||||
if self.ui_helper.is_element_visible(selectors.LOGIN_EMAIL_INPUT):
|
||||
return False
|
||||
|
||||
# Prüfe auf Fehlermeldung
|
||||
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler bei der Login-Prüfung: {e}")
|
||||
return False
|
||||
|
||||
def _get_error_message(self) -> str:
|
||||
"""
|
||||
Holt die Fehlermeldung falls vorhanden
|
||||
"""
|
||||
try:
|
||||
if self.ui_helper.is_element_visible(selectors.ERROR_MESSAGE):
|
||||
return self.ui_helper.get_element_text(selectors.ERROR_MESSAGE) or "Unbekannter Fehler"
|
||||
return "Login fehlgeschlagen"
|
||||
except:
|
||||
return "Unbekannter Fehler"
|
||||
132
social_networks/vk/vk_registration.py
Normale Datei
132
social_networks/vk/vk_registration.py
Normale Datei
@ -0,0 +1,132 @@
|
||||
"""
|
||||
VK Registrierung - Handhabt den Registrierungsprozess
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict
|
||||
from playwright.sync_api import Page
|
||||
|
||||
from social_networks.vk import vk_selectors as selectors
|
||||
from social_networks.vk.vk_ui_helper import VKUIHelper
|
||||
|
||||
logger = logging.getLogger("vk_registration")
|
||||
|
||||
class VKRegistration:
|
||||
"""
|
||||
Handhabt den VK Registrierungsprozess
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page, ui_helper: VKUIHelper, screenshots_dir: str = None, save_screenshots: bool = True):
|
||||
"""
|
||||
Initialisiert die Registrierung
|
||||
"""
|
||||
self.page = page
|
||||
self.ui_helper = ui_helper
|
||||
self.screenshots_dir = screenshots_dir
|
||||
self.save_screenshots = save_screenshots
|
||||
|
||||
def fill_registration_form(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
||||
"""
|
||||
Füllt das Registrierungsformular aus
|
||||
"""
|
||||
try:
|
||||
logger.info("Fülle VK Registrierungsformular aus")
|
||||
|
||||
# Warte auf Registrierungsseite
|
||||
time.sleep(random.uniform(2, 3))
|
||||
self.ui_helper.take_screenshot("registration_form")
|
||||
|
||||
# Vorname eingeben
|
||||
if self.ui_helper.wait_for_element(selectors.REGISTRATION_FIRST_NAME, timeout=10000):
|
||||
first_name = account_data.get("first_name", "")
|
||||
logger.info(f"Gebe Vorname ein: {first_name}")
|
||||
self.ui_helper.type_with_delay(selectors.REGISTRATION_FIRST_NAME, first_name)
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
|
||||
# Nachname eingeben
|
||||
if self.ui_helper.wait_for_element(selectors.REGISTRATION_LAST_NAME, timeout=5000):
|
||||
last_name = account_data.get("last_name", "")
|
||||
logger.info(f"Gebe Nachname ein: {last_name}")
|
||||
self.ui_helper.type_with_delay(selectors.REGISTRATION_LAST_NAME, last_name)
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
|
||||
# Geburtstag auswählen
|
||||
self._select_birthday(account_data)
|
||||
|
||||
# Geschlecht auswählen
|
||||
self._select_gender(account_data)
|
||||
|
||||
# Screenshot vor dem Fortfahren
|
||||
self.ui_helper.take_screenshot("registration_form_filled")
|
||||
|
||||
# Fortfahren Button klicken
|
||||
if self.ui_helper.wait_for_element(selectors.REGISTRATION_CONTINUE_BUTTON, timeout=5000):
|
||||
logger.info("Klicke auf Fortfahren")
|
||||
self.ui_helper.click_with_retry(selectors.REGISTRATION_CONTINUE_BUTTON)
|
||||
time.sleep(random.uniform(2, 3))
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Registrierungsformular ausgefüllt"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Ausfüllen des Registrierungsformulars: {e}")
|
||||
self.ui_helper.take_screenshot("registration_form_error")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Fehler beim Ausfüllen des Formulars: {str(e)}"
|
||||
}
|
||||
|
||||
def _select_birthday(self, account_data: Dict[str, str]):
|
||||
"""
|
||||
Wählt das Geburtsdatum aus
|
||||
"""
|
||||
try:
|
||||
birthday = account_data.get("birthday", "1990-01-15")
|
||||
year, month, day = birthday.split("-")
|
||||
|
||||
# Tag auswählen
|
||||
if self.ui_helper.wait_for_element(selectors.REGISTRATION_BIRTHDAY_DAY, timeout=5000):
|
||||
logger.info(f"Wähle Geburtstag: {day}")
|
||||
self.ui_helper.select_dropdown_option(selectors.REGISTRATION_BIRTHDAY_DAY, day.lstrip("0"))
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
|
||||
# Monat auswählen
|
||||
if self.ui_helper.wait_for_element(selectors.REGISTRATION_BIRTHDAY_MONTH, timeout=5000):
|
||||
logger.info(f"Wähle Geburtsmonat: {month}")
|
||||
self.ui_helper.select_dropdown_option(selectors.REGISTRATION_BIRTHDAY_MONTH, month.lstrip("0"))
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
|
||||
# Jahr auswählen
|
||||
if self.ui_helper.wait_for_element(selectors.REGISTRATION_BIRTHDAY_YEAR, timeout=5000):
|
||||
logger.info(f"Wähle Geburtsjahr: {year}")
|
||||
self.ui_helper.select_dropdown_option(selectors.REGISTRATION_BIRTHDAY_YEAR, year)
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler bei der Geburtsdatum-Auswahl: {e}")
|
||||
|
||||
def _select_gender(self, account_data: Dict[str, str]):
|
||||
"""
|
||||
Wählt das Geschlecht aus
|
||||
"""
|
||||
try:
|
||||
gender = account_data.get("gender", "male").lower()
|
||||
|
||||
if gender == "female":
|
||||
if self.ui_helper.wait_for_element(selectors.REGISTRATION_GENDER_FEMALE, timeout=5000):
|
||||
logger.info("Wähle Geschlecht: Weiblich")
|
||||
self.ui_helper.check_radio_button(selectors.REGISTRATION_GENDER_FEMALE)
|
||||
else:
|
||||
if self.ui_helper.wait_for_element(selectors.REGISTRATION_GENDER_MALE, timeout=5000):
|
||||
logger.info("Wähle Geschlecht: Männlich")
|
||||
self.ui_helper.check_radio_button(selectors.REGISTRATION_GENDER_MALE)
|
||||
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler bei der Geschlechtsauswahl: {e}")
|
||||
51
social_networks/vk/vk_selectors.py
Normale Datei
51
social_networks/vk/vk_selectors.py
Normale Datei
@ -0,0 +1,51 @@
|
||||
"""
|
||||
VK UI Selektoren und URLs
|
||||
"""
|
||||
|
||||
# URLs
|
||||
BASE_URL = "https://vk.com/"
|
||||
REGISTRATION_URL = "https://vk.com/join"
|
||||
LOGIN_URL = "https://vk.com/login"
|
||||
|
||||
# Startseite
|
||||
CREATE_ACCOUNT_BUTTON = "span.vkuiButton__content:has-text('Konto erstellen')"
|
||||
CREATE_ACCOUNT_BUTTON_XPATH = "//span[@class='vkuiButton__content' and text()='Konto erstellen']"
|
||||
CREATE_ACCOUNT_BUTTON_ALTERNATE = "button:has-text('Konto erstellen')"
|
||||
|
||||
# Login Seite
|
||||
LOGIN_EMAIL_INPUT = "input[name='email']"
|
||||
LOGIN_PASSWORD_INPUT = "input[name='password']"
|
||||
LOGIN_SUBMIT_BUTTON = "button[type='submit']"
|
||||
|
||||
# Registrierungsformular
|
||||
REGISTRATION_FIRST_NAME = "input[name='first_name']"
|
||||
REGISTRATION_LAST_NAME = "input[name='last_name']"
|
||||
REGISTRATION_BIRTHDAY_DAY = "select[name='bday']"
|
||||
REGISTRATION_BIRTHDAY_MONTH = "select[name='bmonth']"
|
||||
REGISTRATION_BIRTHDAY_YEAR = "select[name='byear']"
|
||||
REGISTRATION_GENDER_MALE = "input[value='2']"
|
||||
REGISTRATION_GENDER_FEMALE = "input[value='1']"
|
||||
REGISTRATION_CONTINUE_BUTTON = "button[type='submit']"
|
||||
|
||||
# Telefonnummer Verifizierung
|
||||
PHONE_INPUT = "input[name='phone']"
|
||||
PHONE_COUNTRY_CODE = "div.PhoneInput__countryCode"
|
||||
PHONE_SUBMIT_BUTTON = "button[type='submit']"
|
||||
|
||||
# SMS Verifizierung
|
||||
SMS_CODE_INPUT = "input[name='code']"
|
||||
SMS_SUBMIT_BUTTON = "button[type='submit']"
|
||||
SMS_RESEND_LINK = "a:has-text('Code erneut senden')"
|
||||
|
||||
# Captcha
|
||||
CAPTCHA_IMAGE = "img.vkc__Captcha__image"
|
||||
CAPTCHA_INPUT = "input[name='captcha']"
|
||||
|
||||
# Fehler- und Erfolgsmeldungen
|
||||
ERROR_MESSAGE = "div.error"
|
||||
SUCCESS_MESSAGE = "div.success"
|
||||
PHONE_ERROR = "div.phone_error"
|
||||
|
||||
# Cookies Banner
|
||||
COOKIE_ACCEPT_BUTTON = "button:has-text('Akzeptieren')"
|
||||
COOKIE_BANNER = "div[class*='cookie']"
|
||||
149
social_networks/vk/vk_ui_helper.py
Normale Datei
149
social_networks/vk/vk_ui_helper.py
Normale Datei
@ -0,0 +1,149 @@
|
||||
"""
|
||||
VK UI Helper - Hilfsfunktionen für UI-Interaktionen
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
from typing import Optional
|
||||
from playwright.sync_api import Page, ElementHandle
|
||||
|
||||
logger = logging.getLogger("vk_ui_helper")
|
||||
|
||||
class VKUIHelper:
|
||||
"""
|
||||
Hilfsklasse für VK UI-Interaktionen
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page, screenshots_dir: str = None, save_screenshots: bool = True):
|
||||
"""
|
||||
Initialisiert den UI Helper
|
||||
"""
|
||||
self.page = page
|
||||
self.screenshots_dir = screenshots_dir or "logs/screenshots"
|
||||
self.save_screenshots = save_screenshots
|
||||
|
||||
# Screenshot-Verzeichnis erstellen falls nötig
|
||||
if self.save_screenshots and not os.path.exists(self.screenshots_dir):
|
||||
os.makedirs(self.screenshots_dir)
|
||||
|
||||
def take_screenshot(self, name: str) -> Optional[str]:
|
||||
"""
|
||||
Erstellt einen Screenshot
|
||||
"""
|
||||
if not self.save_screenshots:
|
||||
return None
|
||||
|
||||
try:
|
||||
timestamp = int(time.time())
|
||||
filename = f"{name}_{timestamp}.png"
|
||||
filepath = os.path.join(self.screenshots_dir, filename)
|
||||
self.page.screenshot(path=filepath)
|
||||
logger.debug(f"Screenshot gespeichert: {filepath}")
|
||||
return filepath
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Erstellen des Screenshots: {e}")
|
||||
return None
|
||||
|
||||
def wait_for_element(self, selector: str, timeout: int = 30000) -> bool:
|
||||
"""
|
||||
Wartet auf ein Element
|
||||
"""
|
||||
try:
|
||||
self.page.wait_for_selector(selector, timeout=timeout)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Element {selector} nicht gefunden nach {timeout}ms")
|
||||
return False
|
||||
|
||||
def type_with_delay(self, selector: str, text: str, delay_min: float = 0.05, delay_max: float = 0.15):
|
||||
"""
|
||||
Tippt Text mit menschenähnlicher Verzögerung
|
||||
"""
|
||||
try:
|
||||
element = self.page.locator(selector)
|
||||
element.click()
|
||||
|
||||
for char in text:
|
||||
element.type(char)
|
||||
time.sleep(random.uniform(delay_min, delay_max))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Tippen in {selector}: {e}")
|
||||
raise
|
||||
|
||||
def click_with_retry(self, selector: str, max_attempts: int = 3) -> bool:
|
||||
"""
|
||||
Klickt auf ein Element mit Wiederholungsversuchen
|
||||
"""
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
self.page.click(selector)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Klick-Versuch {attempt + 1} fehlgeschlagen: {e}")
|
||||
if attempt < max_attempts - 1:
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
return False
|
||||
|
||||
def scroll_to_element(self, selector: str):
|
||||
"""
|
||||
Scrollt zu einem Element
|
||||
"""
|
||||
try:
|
||||
self.page.locator(selector).scroll_into_view_if_needed()
|
||||
time.sleep(random.uniform(0.5, 1))
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Scrollen zu {selector}: {e}")
|
||||
|
||||
def is_element_visible(self, selector: str) -> bool:
|
||||
"""
|
||||
Prüft ob ein Element sichtbar ist
|
||||
"""
|
||||
try:
|
||||
return self.page.locator(selector).is_visible()
|
||||
except:
|
||||
return False
|
||||
|
||||
def get_element_text(self, selector: str) -> Optional[str]:
|
||||
"""
|
||||
Holt den Text eines Elements
|
||||
"""
|
||||
try:
|
||||
return self.page.locator(selector).text_content()
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Lesen des Texts von {selector}: {e}")
|
||||
return None
|
||||
|
||||
def select_dropdown_option(self, selector: str, value: str):
|
||||
"""
|
||||
Wählt eine Option aus einem Dropdown
|
||||
"""
|
||||
try:
|
||||
self.page.select_option(selector, value)
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Auswählen von {value} in {selector}: {e}")
|
||||
raise
|
||||
|
||||
def check_radio_button(self, selector: str):
|
||||
"""
|
||||
Wählt einen Radio Button aus
|
||||
"""
|
||||
try:
|
||||
self.page.check(selector)
|
||||
time.sleep(random.uniform(0.2, 0.4))
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Auswählen des Radio Buttons {selector}: {e}")
|
||||
raise
|
||||
|
||||
def wait_for_navigation(self, timeout: int = 30000):
|
||||
"""
|
||||
Wartet auf Navigation
|
||||
"""
|
||||
try:
|
||||
self.page.wait_for_load_state("networkidle", timeout=timeout)
|
||||
except Exception as e:
|
||||
logger.warning(f"Navigation-Timeout nach {timeout}ms: {e}")
|
||||
89
social_networks/vk/vk_utils.py
Normale Datei
89
social_networks/vk/vk_utils.py
Normale Datei
@ -0,0 +1,89 @@
|
||||
"""
|
||||
VK Utils - Utility-Funktionen für VK
|
||||
"""
|
||||
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger("vk_utils")
|
||||
|
||||
class VKUtils:
|
||||
"""
|
||||
Utility-Funktionen für VK
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def generate_vk_username(first_name: str, last_name: str) -> str:
|
||||
"""
|
||||
Generiert einen VK-kompatiblen Benutzernamen
|
||||
"""
|
||||
# VK verwendet normalerweise id123456789 Format
|
||||
# Aber für die URL kann man einen benutzerdefinierten Namen verwenden
|
||||
base = f"{first_name.lower()}{last_name.lower()}"
|
||||
# Entferne Sonderzeichen
|
||||
base = ''.join(c for c in base if c.isalnum())
|
||||
|
||||
# Füge zufällige Zahlen hinzu
|
||||
random_suffix = ''.join(random.choices(string.digits, k=random.randint(2, 4)))
|
||||
|
||||
return f"{base}{random_suffix}"
|
||||
|
||||
@staticmethod
|
||||
def format_phone_number(phone: str, country_code: str = "+7") -> str:
|
||||
"""
|
||||
Formatiert eine Telefonnummer für VK
|
||||
VK ist primär in Russland, daher Standard +7
|
||||
"""
|
||||
# Entferne alle nicht-numerischen Zeichen
|
||||
phone_digits = ''.join(c for c in phone if c.isdigit())
|
||||
|
||||
# Wenn die Nummer bereits mit Ländercode beginnt
|
||||
if phone.startswith("+"):
|
||||
return phone
|
||||
|
||||
# Füge Ländercode hinzu
|
||||
return f"{country_code}{phone_digits}"
|
||||
|
||||
@staticmethod
|
||||
def is_valid_vk_password(password: str) -> bool:
|
||||
"""
|
||||
Prüft ob ein Passwort den VK-Anforderungen entspricht
|
||||
- Mindestens 6 Zeichen
|
||||
- Enthält Buchstaben und Zahlen
|
||||
"""
|
||||
if len(password) < 6:
|
||||
return False
|
||||
|
||||
has_letter = any(c.isalpha() for c in password)
|
||||
has_digit = any(c.isdigit() for c in password)
|
||||
|
||||
return has_letter and has_digit
|
||||
|
||||
@staticmethod
|
||||
def generate_vk_password(length: int = 12) -> str:
|
||||
"""
|
||||
Generiert ein VK-kompatibles Passwort
|
||||
"""
|
||||
# Stelle sicher dass Buchstaben und Zahlen enthalten sind
|
||||
password_chars = []
|
||||
|
||||
# Mindestens 2 Kleinbuchstaben
|
||||
password_chars.extend(random.choices(string.ascii_lowercase, k=2))
|
||||
|
||||
# Mindestens 2 Großbuchstaben
|
||||
password_chars.extend(random.choices(string.ascii_uppercase, k=2))
|
||||
|
||||
# Mindestens 2 Zahlen
|
||||
password_chars.extend(random.choices(string.digits, k=2))
|
||||
|
||||
# Fülle mit zufälligen Zeichen auf
|
||||
remaining_length = length - len(password_chars)
|
||||
all_chars = string.ascii_letters + string.digits
|
||||
password_chars.extend(random.choices(all_chars, k=remaining_length))
|
||||
|
||||
# Mische die Zeichen
|
||||
random.shuffle(password_chars)
|
||||
|
||||
return ''.join(password_chars)
|
||||
186
social_networks/vk/vk_verification.py
Normale Datei
186
social_networks/vk/vk_verification.py
Normale Datei
@ -0,0 +1,186 @@
|
||||
"""
|
||||
VK Verification - Handhabt die Telefon-Verifizierung
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict, Optional
|
||||
from playwright.sync_api import Page
|
||||
|
||||
from social_networks.vk import vk_selectors as selectors
|
||||
from social_networks.vk.vk_ui_helper import VKUIHelper
|
||||
from utils.email_handler import EmailHandler
|
||||
|
||||
logger = logging.getLogger("vk_verification")
|
||||
|
||||
class VKVerification:
|
||||
"""
|
||||
Handhabt die VK Telefon-Verifizierung
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page, ui_helper: VKUIHelper, email_handler: EmailHandler = None,
|
||||
screenshots_dir: str = None, save_screenshots: bool = True):
|
||||
"""
|
||||
Initialisiert den Verification Handler
|
||||
"""
|
||||
self.page = page
|
||||
self.ui_helper = ui_helper
|
||||
self.email_handler = email_handler
|
||||
self.screenshots_dir = screenshots_dir
|
||||
self.save_screenshots = save_screenshots
|
||||
|
||||
def handle_phone_verification(self, account_data: Dict[str, str]) -> Dict[str, any]:
|
||||
"""
|
||||
Handhabt die Telefonnummer-Verifizierung
|
||||
"""
|
||||
try:
|
||||
logger.info("Starte Telefon-Verifizierung")
|
||||
|
||||
# Warte auf Telefonnummer-Eingabefeld
|
||||
if not self.ui_helper.wait_for_element(selectors.PHONE_INPUT, timeout=10000):
|
||||
logger.error("Telefonnummer-Eingabefeld nicht gefunden")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Telefonnummer-Eingabefeld nicht gefunden",
|
||||
"message": "Verifizierung fehlgeschlagen"
|
||||
}
|
||||
|
||||
self.ui_helper.take_screenshot("phone_verification_page")
|
||||
|
||||
# Telefonnummer eingeben
|
||||
phone = account_data.get("phone", "")
|
||||
if not phone:
|
||||
logger.error("Keine Telefonnummer in account_data vorhanden")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Keine Telefonnummer angegeben",
|
||||
"message": "Telefonnummer erforderlich"
|
||||
}
|
||||
|
||||
logger.info(f"Gebe Telefonnummer ein: {phone}")
|
||||
|
||||
# Prüfe ob Ländercode-Auswahl vorhanden ist
|
||||
if self.ui_helper.is_element_visible(selectors.PHONE_COUNTRY_CODE):
|
||||
# TODO: Ländercode auswählen falls nötig
|
||||
pass
|
||||
|
||||
# Telefonnummer eingeben
|
||||
self.ui_helper.type_with_delay(selectors.PHONE_INPUT, phone)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# Screenshot vor dem Absenden
|
||||
self.ui_helper.take_screenshot("phone_entered")
|
||||
|
||||
# Absenden
|
||||
if self.ui_helper.wait_for_element(selectors.PHONE_SUBMIT_BUTTON, timeout=5000):
|
||||
logger.info("Sende Telefonnummer ab")
|
||||
self.ui_helper.click_with_retry(selectors.PHONE_SUBMIT_BUTTON)
|
||||
time.sleep(random.uniform(3, 5))
|
||||
|
||||
# Warte auf SMS-Code Eingabefeld
|
||||
if self.ui_helper.wait_for_element(selectors.SMS_CODE_INPUT, timeout=15000):
|
||||
logger.info("SMS-Code Eingabefeld gefunden")
|
||||
self.ui_helper.take_screenshot("sms_code_page")
|
||||
|
||||
# Hier würde normalerweise der SMS-Code abgerufen werden
|
||||
# Für Demo-Zwecke verwenden wir einen Platzhalter
|
||||
sms_code = self._get_sms_code(phone)
|
||||
|
||||
if not sms_code:
|
||||
logger.error("Kein SMS-Code erhalten")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Kein SMS-Code erhalten",
|
||||
"message": "SMS-Verifizierung fehlgeschlagen"
|
||||
}
|
||||
|
||||
# SMS-Code eingeben
|
||||
logger.info(f"Gebe SMS-Code ein: {sms_code}")
|
||||
self.ui_helper.type_with_delay(selectors.SMS_CODE_INPUT, sms_code)
|
||||
time.sleep(random.uniform(1, 2))
|
||||
|
||||
# Code absenden
|
||||
if self.ui_helper.wait_for_element(selectors.SMS_SUBMIT_BUTTON, timeout=5000):
|
||||
logger.info("Sende SMS-Code ab")
|
||||
self.ui_helper.click_with_retry(selectors.SMS_SUBMIT_BUTTON)
|
||||
time.sleep(random.uniform(3, 5))
|
||||
|
||||
# Prüfe auf Erfolg
|
||||
if self._check_verification_success():
|
||||
logger.info("Telefon-Verifizierung erfolgreich")
|
||||
self.ui_helper.take_screenshot("verification_success")
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Verifizierung erfolgreich"
|
||||
}
|
||||
else:
|
||||
error_msg = self._get_verification_error()
|
||||
logger.error(f"Verifizierung fehlgeschlagen: {error_msg}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"message": f"Verifizierung fehlgeschlagen: {error_msg}"
|
||||
}
|
||||
|
||||
else:
|
||||
logger.error("SMS-Code Eingabefeld nicht gefunden")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "SMS-Code Eingabefeld nicht gefunden",
|
||||
"message": "Verifizierung fehlgeschlagen"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Telefon-Verifizierung: {e}")
|
||||
self.ui_helper.take_screenshot("verification_error")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"Verifizierung fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
|
||||
def _get_sms_code(self, phone: str) -> Optional[str]:
|
||||
"""
|
||||
Ruft den SMS-Code ab
|
||||
TODO: Implementierung für echte SMS-Code Abfrage
|
||||
"""
|
||||
logger.warning("SMS-Code Abruf noch nicht implementiert - verwende Platzhalter")
|
||||
# In einer echten Implementierung würde hier der SMS-Code
|
||||
# von einem SMS-Service abgerufen werden
|
||||
return "123456" # Platzhalter
|
||||
|
||||
def _check_verification_success(self) -> bool:
|
||||
"""
|
||||
Prüft ob die Verifizierung erfolgreich war
|
||||
"""
|
||||
try:
|
||||
# Prüfe ob wir weitergeleitet wurden
|
||||
current_url = self.page.url
|
||||
if "welcome" in current_url or "feed" in current_url:
|
||||
return True
|
||||
|
||||
# Prüfe ob SMS-Code Feld noch sichtbar ist
|
||||
if self.ui_helper.is_element_visible(selectors.SMS_CODE_INPUT):
|
||||
return False
|
||||
|
||||
# Prüfe auf Fehlermeldung
|
||||
if self.ui_helper.is_element_visible(selectors.PHONE_ERROR):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler bei der Verifizierungs-Prüfung: {e}")
|
||||
return False
|
||||
|
||||
def _get_verification_error(self) -> str:
|
||||
"""
|
||||
Holt die Fehlermeldung falls vorhanden
|
||||
"""
|
||||
try:
|
||||
if self.ui_helper.is_element_visible(selectors.PHONE_ERROR):
|
||||
return self.ui_helper.get_element_text(selectors.PHONE_ERROR) or "Verifizierung fehlgeschlagen"
|
||||
return "Verifizierung fehlgeschlagen"
|
||||
except:
|
||||
return "Unbekannter Fehler"
|
||||
37
social_networks/vk/vk_workflow.py
Normale Datei
37
social_networks/vk/vk_workflow.py
Normale Datei
@ -0,0 +1,37 @@
|
||||
"""
|
||||
VK Workflow - Workflow-Definitionen für VK
|
||||
"""
|
||||
|
||||
# Workflow-Schritte für VK
|
||||
REGISTRATION_WORKFLOW = [
|
||||
"navigate_to_homepage",
|
||||
"click_create_account",
|
||||
"fill_registration_form",
|
||||
"handle_phone_verification",
|
||||
"complete_profile",
|
||||
"verify_account_creation"
|
||||
]
|
||||
|
||||
LOGIN_WORKFLOW = [
|
||||
"navigate_to_login",
|
||||
"enter_credentials",
|
||||
"handle_2fa_if_needed",
|
||||
"verify_login_success"
|
||||
]
|
||||
|
||||
# Timeouts in Sekunden
|
||||
TIMEOUTS = {
|
||||
"page_load": 30,
|
||||
"element_wait": 10,
|
||||
"verification_wait": 60,
|
||||
"sms_wait": 120
|
||||
}
|
||||
|
||||
# Fehler-Nachrichten
|
||||
ERROR_MESSAGES = {
|
||||
"phone_already_used": "Diese Telefonnummer wird bereits verwendet",
|
||||
"invalid_phone": "Ungültige Telefonnummer",
|
||||
"invalid_code": "Ungültiger Verifizierungscode",
|
||||
"too_many_attempts": "Zu viele Versuche",
|
||||
"account_blocked": "Account wurde blockiert"
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren