# social_networks/x/x_utils.py """ X (Twitter) Utils - Utility-Funktionen für X-Automatisierung """ import re import time import random from typing import Dict, List, Any, Optional, Tuple from datetime import datetime, timedelta from utils.logger import setup_logger # Konfiguriere Logger logger = setup_logger("x_utils") class XUtils: """ Utility-Klasse mit Hilfsfunktionen für X-Automatisierung. """ def __init__(self, automation): """ Initialisiert X Utils. Args: automation: Referenz auf die Hauptautomatisierungsklasse """ self.automation = automation logger.debug("X Utils initialisiert") @staticmethod def validate_username(username: str) -> Tuple[bool, Optional[str]]: """ Validiert einen X-Benutzernamen. Args: username: Zu validierender Benutzername Returns: Tuple[bool, Optional[str]]: (Gültig, Fehlermeldung wenn ungültig) """ # Längenprüfung if len(username) < 1: return False, "Benutzername ist zu kurz (mindestens 1 Zeichen)" if len(username) > 15: return False, "Benutzername ist zu lang (maximal 15 Zeichen)" # Zeichenprüfung (nur Buchstaben, Zahlen und Unterstrich) if not re.match(r'^[a-zA-Z0-9_]+$', username): return False, "Benutzername darf nur Buchstaben, Zahlen und Unterstriche enthalten" # Verbotene Muster forbidden_patterns = ["twitter", "admin", "x.com", "root", "system"] username_lower = username.lower() for pattern in forbidden_patterns: if pattern in username_lower: return False, f"Benutzername darf '{pattern}' nicht enthalten" return True, None @staticmethod def validate_email(email: str) -> Tuple[bool, Optional[str]]: """ Validiert eine E-Mail-Adresse für X. Args: email: Zu validierende E-Mail Returns: Tuple[bool, Optional[str]]: (Gültig, Fehlermeldung wenn ungültig) """ # Grundlegendes E-Mail-Pattern email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' if not re.match(email_pattern, email): return False, "Ungültiges E-Mail-Format" # Verbotene Domains forbidden_domains = ["example.com", "test.com", "temp-mail.com"] domain = email.split('@')[1].lower() for forbidden in forbidden_domains: if forbidden in domain: return False, f"E-Mail-Domain '{forbidden}' ist nicht erlaubt" return True, None @staticmethod def validate_password(password: str) -> Tuple[bool, Optional[str]]: """ Validiert ein Passwort für X. Args: password: Zu validierendes Passwort Returns: Tuple[bool, Optional[str]]: (Gültig, Fehlermeldung wenn ungültig) """ # Längenprüfung if len(password) < 8: return False, "Passwort muss mindestens 8 Zeichen lang sein" if len(password) > 128: return False, "Passwort darf maximal 128 Zeichen lang sein" # Mindestens ein Kleinbuchstabe if not re.search(r'[a-z]', password): return False, "Passwort muss mindestens einen Kleinbuchstaben enthalten" # Mindestens eine Zahl if not re.search(r'\d', password): return False, "Passwort muss mindestens eine Zahl enthalten" return True, None @staticmethod def generate_device_info() -> Dict[str, Any]: """ Generiert realistische Geräteinformationen. Returns: Dict[str, Any]: Geräteinformationen """ devices = [ { "type": "desktop", "os": "Windows", "browser": "Chrome", "screen": {"width": 1920, "height": 1080}, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }, { "type": "desktop", "os": "macOS", "browser": "Safari", "screen": {"width": 2560, "height": 1440}, "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" }, { "type": "mobile", "os": "iOS", "browser": "Safari", "screen": {"width": 414, "height": 896}, "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X)" }, { "type": "mobile", "os": "Android", "browser": "Chrome", "screen": {"width": 412, "height": 915}, "user_agent": "Mozilla/5.0 (Linux; Android 11; SM-G991B) AppleWebKit/537.36" } ] return random.choice(devices) @staticmethod def generate_session_id() -> str: """ Generiert eine realistische Session-ID. Returns: str: Session-ID """ chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' return ''.join(random.choice(chars) for _ in range(32)) def detect_language(self) -> str: """ Erkennt die aktuelle Sprache der X-Oberfläche. Returns: str: Sprachcode (z.B. "de", "en") """ try: page = self.automation.browser.page # Prüfe HTML lang-Attribut lang = page.evaluate("() => document.documentElement.lang") if lang: return lang.split('-')[0] # z.B. "de" aus "de-DE" # Fallback: Prüfe bekannte Texte if page.is_visible('text="Anmelden"'): return "de" elif page.is_visible('text="Log in"'): return "en" return "en" # Standard except Exception as e: logger.error(f"Fehler bei Spracherkennung: {e}") return "en" @staticmethod def format_phone_number(phone: str, country_code: str = "+49") -> str: """ Formatiert eine Telefonnummer für X. Args: phone: Rohe Telefonnummer country_code: Ländervorwahl Returns: str: Formatierte Telefonnummer """ # Entferne alle Nicht-Ziffern digits = re.sub(r'\D', '', phone) # Füge Ländervorwahl hinzu wenn nicht vorhanden if not phone.startswith('+'): return f"{country_code}{digits}" return f"+{digits}" @staticmethod def parse_error_message(error_text: str) -> Dict[str, Any]: """ Analysiert X-Fehlermeldungen. Args: error_text: Fehlermeldungstext Returns: Dict[str, Any]: Analysierte Fehlerinformationen """ error_info = { "type": "unknown", "message": error_text, "recoverable": True, "action": "retry" } # Rate Limit if "zu viele" in error_text.lower() or "too many" in error_text.lower(): error_info.update({ "type": "rate_limit", "recoverable": True, "action": "wait", "wait_time": 900 # 15 Minuten }) # Account gesperrt elif "gesperrt" in error_text.lower() or "suspended" in error_text.lower(): error_info.update({ "type": "suspended", "recoverable": False, "action": "abort" }) # Ungültige Anmeldedaten elif "passwort" in error_text.lower() or "password" in error_text.lower(): error_info.update({ "type": "invalid_credentials", "recoverable": True, "action": "check_credentials" }) # E-Mail bereits verwendet elif "bereits verwendet" in error_text.lower() or "already" in error_text.lower(): error_info.update({ "type": "duplicate", "recoverable": True, "action": "use_different_email" }) return error_info def wait_for_rate_limit(self, wait_time: int = None): """ Wartet bei Rate Limiting mit visueller Anzeige. Args: wait_time: Wartezeit in Sekunden (None für zufällige Zeit) """ if wait_time is None: wait_time = random.randint(300, 600) # 5-10 Minuten logger.info(f"Rate Limit erkannt - warte {wait_time} Sekunden") self.automation._emit_customer_log(f"⏳ Rate Limit - warte {wait_time // 60} Minuten...") # Warte in Intervallen mit Status-Updates intervals = min(10, wait_time // 10) for i in range(intervals): time.sleep(wait_time // intervals) remaining = wait_time - (i + 1) * (wait_time // intervals) if remaining > 60: self.automation._emit_customer_log(f"⏳ Noch {remaining // 60} Minuten...") @staticmethod def generate_bio() -> str: """ Generiert eine realistische Bio für ein X-Profil. Returns: str: Generierte Bio """ templates = [ "✈️ Explorer | 📚 Book lover | ☕ Coffee enthusiast", "Life is a journey 🌟 | Making memories 📸", "Student 📖 | Dreamer 💭 | Music lover 🎵", "Tech enthusiast 💻 | Always learning 🎯", "Living life one day at a time ✨", "Passionate about {interest} | {city} 📍", "Just here to share thoughts 💭", "{hobby} in my free time | DM for collabs", "Spreading positivity 🌈 | {emoji} lover" ] interests = ["photography", "travel", "coding", "art", "fitness", "cooking"] cities = ["Berlin", "Munich", "Hamburg", "Frankfurt", "Cologne"] hobbies = ["Gaming", "Reading", "Hiking", "Painting", "Yoga"] emojis = ["🎨", "🎮", "📚", "🎯", "🌸", "⭐"] bio = random.choice(templates) bio = bio.replace("{interest}", random.choice(interests)) bio = bio.replace("{city}", random.choice(cities)) bio = bio.replace("{hobby}", random.choice(hobbies)) bio = bio.replace("{emoji}", random.choice(emojis)) return bio @staticmethod def calculate_age_from_birthday(birthday: Dict[str, int]) -> int: """ Berechnet das Alter aus einem Geburtstagsdatum. Args: birthday: Dictionary mit 'day', 'month', 'year' Returns: int: Berechnetes Alter """ birth_date = datetime(birthday['year'], birthday['month'], birthday['day']) today = datetime.now() age = today.year - birth_date.year # Prüfe ob Geburtstag dieses Jahr schon war if (today.month, today.day) < (birth_date.month, birth_date.day): age -= 1 return age def check_account_restrictions(self) -> Dict[str, Any]: """ Prüft auf Account-Einschränkungen. Returns: Dict[str, Any]: Informationen über Einschränkungen """ try: page = self.automation.browser.page restrictions = { "limited": False, "locked": False, "suspended": False, "verification_required": False } # Prüfe auf verschiedene Einschränkungen if page.is_visible('text="Dein Account ist eingeschränkt"', timeout=1000): restrictions["limited"] = True logger.warning("Account ist eingeschränkt") if page.is_visible('text="Account gesperrt"', timeout=1000): restrictions["suspended"] = True logger.error("Account ist gesperrt") if page.is_visible('text="Verifizierung erforderlich"', timeout=1000): restrictions["verification_required"] = True logger.warning("Verifizierung erforderlich") return restrictions except Exception as e: logger.error(f"Fehler bei Einschränkungsprüfung: {e}") return {"error": str(e)}