Files
AccountForger-neuerUpload/social_networks/x/x_utils.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

379 Zeilen
13 KiB
Python

# 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)}