# 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"