465 Zeilen
22 KiB
Python
465 Zeilen
22 KiB
Python
# utils/username_generator.py
|
|
|
|
"""
|
|
Benutzernamen-Generator für den Social Media Account Generator.
|
|
"""
|
|
|
|
import random
|
|
import string
|
|
import re
|
|
import logging
|
|
from typing import Dict, List, Any, Optional, Tuple, Union
|
|
|
|
logger = logging.getLogger("username_generator")
|
|
|
|
class UsernameGenerator:
|
|
"""Klasse zur Generierung von Benutzernamen für verschiedene Plattformen."""
|
|
|
|
def __init__(self):
|
|
"""Initialisiert den UsernameGenerator."""
|
|
# Plattformspezifische Richtlinien
|
|
self.platform_policies = {
|
|
"instagram": {
|
|
"min_length": 1,
|
|
"max_length": 30,
|
|
"allowed_chars": string.ascii_letters + string.digits + "._",
|
|
"allowed_start_chars": string.ascii_letters + string.digits,
|
|
"allowed_end_chars": string.ascii_letters + string.digits + ".",
|
|
"allowed_consecutive_special": False,
|
|
"disallowed_words": ["instagram", "admin", "official"],
|
|
"auto_suggestions": True
|
|
},
|
|
"facebook": {
|
|
"min_length": 5,
|
|
"max_length": 50,
|
|
"allowed_chars": string.ascii_letters + string.digits + ".-",
|
|
"allowed_start_chars": string.ascii_letters,
|
|
"allowed_end_chars": string.ascii_letters + string.digits,
|
|
"allowed_consecutive_special": True,
|
|
"disallowed_words": ["facebook", "meta", "admin"],
|
|
"auto_suggestions": False
|
|
},
|
|
"twitter": {
|
|
"min_length": 4,
|
|
"max_length": 15,
|
|
"allowed_chars": string.ascii_letters + string.digits + "_",
|
|
"allowed_start_chars": string.ascii_letters + string.digits,
|
|
"allowed_end_chars": string.ascii_letters + string.digits + "_",
|
|
"allowed_consecutive_special": True,
|
|
"disallowed_words": ["twitter", "admin", "official"],
|
|
"auto_suggestions": True
|
|
},
|
|
"tiktok": {
|
|
"min_length": 2,
|
|
"max_length": 24,
|
|
"allowed_chars": string.ascii_letters + string.digits + "._",
|
|
"allowed_start_chars": string.ascii_letters + string.digits,
|
|
"allowed_end_chars": string.ascii_letters + string.digits,
|
|
"allowed_consecutive_special": False,
|
|
"disallowed_words": ["tiktok", "admin", "official"],
|
|
"auto_suggestions": True
|
|
},
|
|
"default": {
|
|
"min_length": 3,
|
|
"max_length": 20,
|
|
"allowed_chars": string.ascii_letters + string.digits + "._-",
|
|
"allowed_start_chars": string.ascii_letters,
|
|
"allowed_end_chars": string.ascii_letters + string.digits,
|
|
"allowed_consecutive_special": False,
|
|
"disallowed_words": ["admin", "root", "system"],
|
|
"auto_suggestions": True
|
|
}
|
|
}
|
|
|
|
# Liste von Adjektiven und Substantiven für zufällige Benutzernamen
|
|
self.adjectives = [
|
|
"happy", "sunny", "clever", "brave", "mighty", "gentle", "wild", "calm", "bright",
|
|
"quiet", "swift", "bold", "wise", "fancy", "little", "big", "smart", "cool", "hot",
|
|
"super", "mega", "epic", "magic", "golden", "silver", "bronze", "shiny", "dark",
|
|
"light", "fast", "slow", "strong", "soft", "hard", "sweet", "sour", "tasty", "fresh",
|
|
"green", "blue", "red", "purple", "yellow", "orange", "pink", "white", "black"
|
|
]
|
|
|
|
self.nouns = [
|
|
"tiger", "eagle", "lion", "wolf", "bear", "fox", "owl", "hawk", "falcon", "dolphin",
|
|
"shark", "whale", "turtle", "panda", "koala", "monkey", "cat", "dog", "horse", "pony",
|
|
"unicorn", "dragon", "phoenix", "wizard", "knight", "warrior", "ninja", "samurai",
|
|
"queen", "king", "prince", "princess", "hero", "legend", "star", "moon", "sun", "sky",
|
|
"ocean", "river", "mountain", "forest", "tree", "flower", "rose", "tulip", "daisy"
|
|
]
|
|
|
|
# Internationaler Wortschatz (nur mit ASCII-Zeichen) für verschiedene Sprachen
|
|
# Jeweils 100 kurze, neutrale Begriffe pro Sprache
|
|
self.international_words = {
|
|
# Deutsch - 100 kurze, neutrale Begriffe ohne Umlaute oder Sonderzeichen
|
|
"de": [
|
|
"wald", "berg", "fluss", "tal", "see", "meer", "boot", "schiff", "haus", "dach",
|
|
"tuer", "fenster", "glas", "holz", "stein", "sand", "erde", "weg", "pfad", "strasse",
|
|
"auto", "rad", "ball", "spiel", "tisch", "stuhl", "bett", "kissen", "lampe", "licht",
|
|
"tag", "nacht", "sonne", "mond", "stern", "himmel", "wolke", "regen", "schnee", "wind",
|
|
"baum", "blume", "gras", "blatt", "frucht", "apfel", "brot", "wasser", "milch", "kaffee",
|
|
"buch", "brief", "stift", "musik", "lied", "tanz", "film", "bild", "farbe", "kunst",
|
|
"hand", "fuss", "kopf", "auge", "ohr", "nase", "mund", "zahn", "haar", "herz",
|
|
"zeit", "jahr", "monat", "woche", "tag", "stunde", "minute", "uhr", "zahl", "wort",
|
|
"name", "freund", "kind", "tier", "vogel", "fisch", "stadt", "land", "dorf", "garten",
|
|
"feld", "werk", "kraft", "geld", "gold", "bank", "markt", "preis", "karte", "punkt"
|
|
],
|
|
|
|
# Englisch - 100 kurze, neutrale Begriffe
|
|
"en": [
|
|
"wood", "hill", "river", "valley", "lake", "sea", "boat", "ship", "house", "roof",
|
|
"door", "window", "glass", "wood", "stone", "sand", "earth", "way", "path", "road",
|
|
"car", "wheel", "ball", "game", "table", "chair", "bed", "pillow", "lamp", "light",
|
|
"day", "night", "sun", "moon", "star", "sky", "cloud", "rain", "snow", "wind",
|
|
"tree", "flower", "grass", "leaf", "fruit", "apple", "bread", "water", "milk", "coffee",
|
|
"book", "letter", "pen", "music", "song", "dance", "film", "image", "color", "art",
|
|
"hand", "foot", "head", "eye", "ear", "nose", "mouth", "tooth", "hair", "heart",
|
|
"time", "year", "month", "week", "day", "hour", "minute", "clock", "number", "word",
|
|
"name", "friend", "child", "animal", "bird", "fish", "city", "country", "village", "garden",
|
|
"field", "work", "power", "money", "gold", "bank", "market", "price", "card", "point"
|
|
],
|
|
|
|
# Französisch - 100 kurze, neutrale Begriffe (ohne Akzente oder Sonderzeichen)
|
|
"fr": [
|
|
"bois", "mont", "fleuve", "vallee", "lac", "mer", "bateau", "navire", "maison", "toit",
|
|
"porte", "fenetre", "verre", "bois", "pierre", "sable", "terre", "voie", "sentier", "route",
|
|
"auto", "roue", "balle", "jeu", "table", "chaise", "lit", "coussin", "lampe", "lumiere",
|
|
"jour", "nuit", "soleil", "lune", "etoile", "ciel", "nuage", "pluie", "neige", "vent",
|
|
"arbre", "fleur", "herbe", "feuille", "fruit", "pomme", "pain", "eau", "lait", "cafe",
|
|
"livre", "lettre", "stylo", "musique", "chanson", "danse", "film", "image", "couleur", "art",
|
|
"main", "pied", "tete", "oeil", "oreille", "nez", "bouche", "dent", "cheveu", "coeur",
|
|
"temps", "annee", "mois", "semaine", "jour", "heure", "minute", "horloge", "nombre", "mot",
|
|
"nom", "ami", "enfant", "animal", "oiseau", "poisson", "ville", "pays", "village", "jardin",
|
|
"champ", "travail", "force", "argent", "or", "banque", "marche", "prix", "carte", "point"
|
|
],
|
|
|
|
# Spanisch - 100 kurze, neutrale Begriffe (ohne Akzente oder Sonderzeichen)
|
|
"es": [
|
|
"bosque", "monte", "rio", "valle", "lago", "mar", "barco", "nave", "casa", "techo",
|
|
"puerta", "ventana", "vidrio", "madera", "piedra", "arena", "tierra", "via", "ruta", "calle",
|
|
"coche", "rueda", "bola", "juego", "mesa", "silla", "cama", "cojin", "lampara", "luz",
|
|
"dia", "noche", "sol", "luna", "estrella", "cielo", "nube", "lluvia", "nieve", "viento",
|
|
"arbol", "flor", "hierba", "hoja", "fruta", "manzana", "pan", "agua", "leche", "cafe",
|
|
"libro", "carta", "pluma", "musica", "cancion", "baile", "pelicula", "imagen", "color", "arte",
|
|
"mano", "pie", "cabeza", "ojo", "oreja", "nariz", "boca", "diente", "pelo", "corazon",
|
|
"tiempo", "ano", "mes", "semana", "dia", "hora", "minuto", "reloj", "numero", "palabra",
|
|
"nombre", "amigo", "nino", "animal", "ave", "pez", "ciudad", "pais", "pueblo", "jardin",
|
|
"campo", "trabajo", "fuerza", "dinero", "oro", "banco", "mercado", "precio", "carta", "punto"
|
|
],
|
|
|
|
# Japanisch - 100 kurze, neutrale Begriffe (in romanisierter Form)
|
|
"ja": [
|
|
"ki", "yama", "kawa", "tani", "mizu", "umi", "fune", "ie", "yane", "kado",
|
|
"mado", "garasu", "ki", "ishi", "suna", "tsuchi", "michi", "kuruma", "wa", "tama",
|
|
"asobi", "tsukue", "isu", "neru", "makura", "akari", "hikari", "hi", "yoru", "taiyou",
|
|
"tsuki", "hoshi", "sora", "kumo", "ame", "yuki", "kaze", "ki", "hana", "kusa",
|
|
"ha", "kudamono", "ringo", "pan", "mizu", "gyunyu", "kohi", "hon", "tegami", "pen",
|
|
"ongaku", "uta", "odori", "eiga", "e", "iro", "geijutsu", "te", "ashi", "atama",
|
|
"me", "mimi", "hana", "kuchi", "ha", "kami", "kokoro", "jikan", "toshi", "tsuki",
|
|
"shukan", "hi", "jikan", "fun", "tokei", "kazu", "kotoba", "namae", "tomodachi", "kodomo",
|
|
"doubutsu", "tori", "sakana", "machi", "kuni", "mura", "niwa", "hatake", "shigoto", "chikara",
|
|
"okane", "kin", "ginko", "ichiba", "nedan", "kado", "ten", "ai", "heiwa", "yume"
|
|
]
|
|
}
|
|
|
|
def get_platform_policy(self, platform: str) -> Dict[str, Any]:
|
|
"""
|
|
Gibt die Benutzernamen-Richtlinie für eine bestimmte Plattform zurück.
|
|
|
|
Args:
|
|
platform: Name der Plattform
|
|
|
|
Returns:
|
|
Dictionary mit der Benutzernamen-Richtlinie
|
|
"""
|
|
platform = platform.lower()
|
|
return self.platform_policies.get(platform, self.platform_policies["default"])
|
|
|
|
def set_platform_policy(self, platform: str, policy: Dict[str, Any]) -> None:
|
|
"""
|
|
Setzt oder aktualisiert die Benutzernamen-Richtlinie für eine Plattform.
|
|
|
|
Args:
|
|
platform: Name der Plattform
|
|
policy: Dictionary mit der Benutzernamen-Richtlinie
|
|
"""
|
|
platform = platform.lower()
|
|
self.platform_policies[platform] = policy
|
|
logger.info(f"Benutzernamen-Richtlinie für '{platform}' aktualisiert")
|
|
|
|
def generate_username(self, platform: str = "default", name: Optional[str] = None,
|
|
custom_policy: Optional[Dict[str, Any]] = None) -> str:
|
|
"""
|
|
Generiert einen Benutzernamen gemäß den Richtlinien.
|
|
|
|
Args:
|
|
platform: Name der Plattform
|
|
name: Optionaler vollständiger Name für die Generierung
|
|
custom_policy: Optionale benutzerdefinierte Richtlinie
|
|
|
|
Returns:
|
|
Generierter Benutzername
|
|
"""
|
|
# Richtlinie bestimmen
|
|
if custom_policy:
|
|
policy = custom_policy
|
|
else:
|
|
policy = self.get_platform_policy(platform)
|
|
|
|
# Wenn ein Name angegeben ist, versuche einen darauf basierenden Benutzernamen zu erstellen
|
|
if name:
|
|
return self.generate_from_name(name, policy)
|
|
else:
|
|
# Zufälligen Benutzernamen erstellen
|
|
return self.generate_random_username(policy)
|
|
|
|
def generate_from_name(self, name: str, policy: Dict[str, Any]) -> str:
|
|
"""
|
|
Generiert einen Benutzernamen aus einem vollständigen Namen im Format
|
|
Vorname_RandomBegriffAusDerAusgewähltenSprache_GeburtsjahrXX.
|
|
|
|
Args:
|
|
name: Vollständiger Name
|
|
policy: Benutzernamen-Richtlinie
|
|
|
|
Returns:
|
|
Generierter Benutzername
|
|
"""
|
|
# Name in Teile zerlegen
|
|
parts = name.lower().split()
|
|
|
|
# Sonderzeichen und Leerzeichen entfernen
|
|
parts = [re.sub(r'[^a-z0-9]', '', part) for part in parts]
|
|
parts = [part for part in parts if part]
|
|
|
|
if not parts:
|
|
# Falls keine gültigen Teile, zufälligen Benutzernamen generieren
|
|
return self.generate_random_username(policy)
|
|
|
|
# Vorname nehmen
|
|
firstname = parts[0]
|
|
|
|
# Zufällige Sprache auswählen
|
|
available_languages = list(self.international_words.keys())
|
|
chosen_language = random.choice(available_languages)
|
|
|
|
# Zufälliges Wort aus der gewählten Sprache wählen
|
|
random_word = random.choice(self.international_words[chosen_language])
|
|
|
|
# Geburtsjahr simulieren (zwischen 18 und 40 Jahre alt)
|
|
current_year = 2025 # Aktuelle Jahresangabe im Code
|
|
birth_year = current_year - random.randint(18, 40)
|
|
|
|
# Letzte zwei Ziffern vom Geburtsjahr plus eine Zufallszahl
|
|
year_suffix = str(birth_year)[-2:] + str(random.randint(0, 9))
|
|
|
|
# Benutzernamen im neuen Format zusammensetzen
|
|
username = f"{firstname}_{random_word}_{year_suffix}"
|
|
|
|
# Länge prüfen und anpassen
|
|
if len(username) > policy["max_length"]:
|
|
# Bei Überlänge, kürze den Mittelteil
|
|
max_word_length = policy["max_length"] - len(firstname) - len(year_suffix) - 2 # 2 für die Unterstriche
|
|
if max_word_length < 3: # Zu kurz für ein sinnvolles Wort
|
|
# Fallback: Nur Vorname + Jahreszahl
|
|
username = f"{firstname}_{year_suffix}"
|
|
else:
|
|
random_word = random_word[:max_word_length]
|
|
username = f"{firstname}_{random_word}_{year_suffix}"
|
|
|
|
# Überprüfen, ob die Richtlinien erfüllt sind
|
|
valid, error_msg = self.validate_username(username, policy=policy)
|
|
if not valid:
|
|
# Wenn nicht gültig, generiere einen alternativen Namen
|
|
logger.debug(f"Generierter Name '{username}' nicht gültig: {error_msg}")
|
|
|
|
# Einfachere Variante versuchen
|
|
username = f"{firstname}{year_suffix}"
|
|
|
|
valid, _ = self.validate_username(username, policy=policy)
|
|
if not valid:
|
|
# Wenn immer noch nicht gültig, Fallback auf Standard-Generator
|
|
return self.generate_random_username(policy)
|
|
|
|
logger.info(f"Aus Name generierter Benutzername: {username}")
|
|
return username
|
|
|
|
def generate_random_username(self, policy: Dict[str, Any]) -> str:
|
|
"""
|
|
Generiert einen zufälligen Benutzernamen.
|
|
|
|
Args:
|
|
policy: Benutzernamen-Richtlinie
|
|
|
|
Returns:
|
|
Generierter Benutzername
|
|
"""
|
|
# Verschiedene Muster für zufällige Benutzernamen
|
|
patterns = [
|
|
# Adjektiv + Substantiv
|
|
lambda: random.choice(self.adjectives) + random.choice(self.nouns),
|
|
|
|
# Substantiv + Zahlen
|
|
lambda: random.choice(self.nouns) + "".join(random.choices(string.digits, k=random.randint(1, 4))),
|
|
|
|
# Adjektiv + Substantiv + Zahlen
|
|
lambda: random.choice(self.adjectives) + random.choice(self.nouns) + "".join(random.choices(string.digits, k=random.randint(1, 3))),
|
|
|
|
# Substantiv + Unterstrich + Substantiv
|
|
lambda: random.choice(self.nouns) + ("_" if "_" in policy["allowed_chars"] else "") + random.choice(self.nouns),
|
|
|
|
# Benutzer + Zahlen
|
|
lambda: "user" + "".join(random.choices(string.digits, k=random.randint(3, 6)))
|
|
]
|
|
|
|
# Zufälliges Muster auswählen und Benutzernamen generieren
|
|
max_attempts = 10
|
|
for _ in range(max_attempts):
|
|
pattern_func = random.choice(patterns)
|
|
username = pattern_func()
|
|
|
|
# Zu lange Benutzernamen kürzen
|
|
if len(username) > policy["max_length"]:
|
|
username = username[:policy["max_length"]]
|
|
|
|
# Zu kurze Benutzernamen verlängern
|
|
if len(username) < policy["min_length"]:
|
|
username += "".join(random.choices(string.digits, k=policy["min_length"] - len(username)))
|
|
|
|
# Überprüfen, ob der Benutzername den Richtlinien entspricht
|
|
valid, _ = self.validate_username(username, policy=policy)
|
|
if valid:
|
|
logger.info(f"Zufälliger Benutzername generiert: {username}")
|
|
return username
|
|
|
|
# Fallback: Einfachen Benutzernamen mit Zufallsbuchstaben und Zahlen generieren
|
|
length = random.randint(policy["min_length"], min(policy["max_length"], policy["min_length"] + 5))
|
|
username = random.choice(string.ascii_lowercase) # Erster Buchstabe
|
|
|
|
allowed_chars = [c for c in policy["allowed_chars"] if c in (string.ascii_lowercase + string.digits)]
|
|
username += "".join(random.choice(allowed_chars) for _ in range(length - 1))
|
|
|
|
logger.info(f"Fallback-Benutzername generiert: {username}")
|
|
return username
|
|
|
|
def suggest_alternatives(self, username: str, platform: str = "default") -> List[str]:
|
|
"""
|
|
Schlägt alternative Benutzernamen vor, wenn der gewünschte bereits vergeben ist.
|
|
|
|
Args:
|
|
username: Gewünschter Benutzername
|
|
platform: Name der Plattform
|
|
|
|
Returns:
|
|
Liste mit alternativen Benutzernamen
|
|
"""
|
|
policy = self.get_platform_policy(platform)
|
|
|
|
# Wenn Auto-Suggestions deaktiviert sind, leere Liste zurückgeben
|
|
if not policy.get("auto_suggestions", True):
|
|
return []
|
|
|
|
alternatives = []
|
|
base_username = username
|
|
|
|
# Verschiedene Modifikationen ausprobieren
|
|
|
|
# Anhängen von Zahlen
|
|
for i in range(5):
|
|
suffix = str(random.randint(1, 999))
|
|
alt = base_username + suffix
|
|
if len(alt) <= policy["max_length"]:
|
|
alternatives.append(alt)
|
|
|
|
# Sonderzeichen einfügen
|
|
for special in ["_", ".", "-"]:
|
|
if special in policy["allowed_chars"]:
|
|
alt = base_username + special + str(random.randint(1, 99))
|
|
if len(alt) <= policy["max_length"]:
|
|
alternatives.append(alt)
|
|
|
|
# Adjektiv voranstellen
|
|
for _ in range(2):
|
|
prefix = random.choice(self.adjectives)
|
|
alt = prefix + base_username
|
|
if len(alt) <= policy["max_length"]:
|
|
alternatives.append(alt)
|
|
|
|
# Buchstaben ersetzen (z.B. 'o' durch '0')
|
|
if "0" in policy["allowed_chars"] and "o" in base_username.lower():
|
|
alt = base_username.lower().replace("o", "0")
|
|
if len(alt) <= policy["max_length"]:
|
|
alternatives.append(alt)
|
|
|
|
# Zufällige Buchstaben voranstellen
|
|
for _ in range(2):
|
|
prefix = "".join(random.choices(string.ascii_lowercase, k=random.randint(1, 3)))
|
|
alt = prefix + base_username
|
|
if len(alt) <= policy["max_length"]:
|
|
alternatives.append(alt)
|
|
|
|
# Validiere die alternativen Benutzernamen
|
|
valid_alternatives = []
|
|
for alt in alternatives:
|
|
valid, _ = self.validate_username(alt, policy=policy)
|
|
if valid:
|
|
valid_alternatives.append(alt)
|
|
|
|
# Zufällige Auswahl aus den gültigen Alternativen (maximal 5)
|
|
if len(valid_alternatives) > 5:
|
|
valid_alternatives = random.sample(valid_alternatives, 5)
|
|
|
|
logger.info(f"{len(valid_alternatives)} alternative Benutzernamen generiert für '{username}'")
|
|
|
|
return valid_alternatives
|
|
|
|
def validate_username(self, username: str, platform: str = "default",
|
|
policy: Optional[Dict[str, Any]] = None) -> Tuple[bool, str]:
|
|
"""
|
|
Überprüft, ob ein Benutzername den Richtlinien entspricht.
|
|
|
|
Args:
|
|
username: Zu überprüfender Benutzername
|
|
platform: Name der Plattform
|
|
policy: Optionale Richtlinie (sonst wird die der Plattform verwendet)
|
|
|
|
Returns:
|
|
(Gültigkeit, Fehlermeldung)
|
|
"""
|
|
# Richtlinie bestimmen
|
|
if not policy:
|
|
policy = self.get_platform_policy(platform)
|
|
|
|
# Länge prüfen
|
|
if len(username) < policy["min_length"]:
|
|
return False, f"Benutzername ist zu kurz (mindestens {policy['min_length']} Zeichen erforderlich)"
|
|
|
|
if len(username) > policy["max_length"]:
|
|
return False, f"Benutzername ist zu lang (maximal {policy['max_length']} Zeichen erlaubt)"
|
|
|
|
# Erlaubte Zeichen prüfen
|
|
for char in username:
|
|
if char not in policy["allowed_chars"]:
|
|
return False, f"Unerlaubtes Zeichen: '{char}'"
|
|
|
|
# Anfangszeichen prüfen
|
|
if username[0] not in policy["allowed_start_chars"]:
|
|
return False, f"Benutzername darf nicht mit '{username[0]}' beginnen"
|
|
|
|
# Endzeichen prüfen
|
|
if username[-1] not in policy["allowed_end_chars"]:
|
|
return False, f"Benutzername darf nicht mit '{username[-1]}' enden"
|
|
|
|
# Aufeinanderfolgende Sonderzeichen prüfen
|
|
if not policy["allowed_consecutive_special"]:
|
|
special_chars = set(policy["allowed_chars"]) - set(string.ascii_letters + string.digits)
|
|
for i in range(len(username) - 1):
|
|
if username[i] in special_chars and username[i+1] in special_chars:
|
|
return False, "Keine aufeinanderfolgenden Sonderzeichen erlaubt"
|
|
|
|
# Disallowed words
|
|
for word in policy["disallowed_words"]:
|
|
if word.lower() in username.lower():
|
|
return False, f"Der Benutzername darf '{word}' nicht enthalten"
|
|
|
|
return True, "Benutzername ist gültig" |