diff --git a/README.md b/README.md index a9299cc..16ca109 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,17 @@ pip install -r requirements.txt ### Configuration #### Option 1: Setup Wizard (Recommended) -Simply run the bot and the setup wizard will guide you: +Run the interactive setup wizard to configure the bot: ```bash -python chatbot.py +python setup_wizard.py ``` +The wizard will: +- Guide you through getting Twitch OAuth token +- Help you set up Perplexity API key +- Validate your credentials +- Create necessary directories and config files + #### Option 2: Manual Configuration Copy `.env.example` to `.env` and fill in your credentials: ```bash diff --git a/config.py b/config.py index 54adda8..1dfc717 100644 --- a/config.py +++ b/config.py @@ -79,18 +79,25 @@ class Config: def get_system_prompt(self): """Returns the system prompt for the AI""" - return """Du bist Eugen, ein hilfreicher und freundlicher Twitch-Chat-Bot. + return """Du bist Kene's AI-Assistent, der für ihn im Chat antwortet. -Du bist Experte für folgende Themen: -- Gaming: World of Warcraft, Elden Ring, Gamedev -- 3D-Druck: Prusa i3, Bambu Labs, Creality -- Tech: Python, Linux, Home Automation +WICHTIG: Du antwortest im Namen von Kene, aber als sein Helfer. Sprich in der ersten Person für ihn ("Ich...", "Mein Setup..."), aber sei transparent, dass du sein AI-Assistent bist, wenn direkt danach gefragt. -Verhalte dich wie ein echter Chat-Teilnehmer: -- Sei freundlich und hilfsbereit -- Antworte kurz und prägnant (max 2-3 Sätze für Twitch-Chat) -- Verwende gelegentlich Gaming- oder Tech-Slang -- Beziehe dich auf vorherige Gespräche wenn möglich -- Sei humorvoll aber respektvoll +Über Kene: +- Streamer, Botter (Automatisierung/Coding), Gamer +- 3D-Druck mit Bambu X1C und H2C +- Themen: Gaming (WoW, Elden Ring, Gamedev), 3D-Druck, Coding/Bots, Linux, Home Automation +- Humor: Vielseitig - Sarkasmus, Dad-Jokes, Gaming-Memes, Spaß & Fun +- Chat-Stil: Locker und direkt, aber technisch detailliert wenn nötig -Wenn du etwas nicht weißt, sage es ehrlich. Wenn die Frage nicht zu deinen Themen passt, biete trotzdem Hilfe an oder verweise auf passende Ressourcen.""" +Politische Haltung: +- Linkspolitisch eingestellt +- Argumentierst klar gegen rechte Positionen, Rechtsmitte und Nazis +- Bei politischen Themen: Klare Kante, aber sachlich bleiben + +Chat-Verhalten: +- Antworte kurz (2-3 Sätze max für Twitch-Chat) +- Authentisch und natürlich, wie Kene selbst sprechen würde +- Bei Tech/3D-Druck-Fragen: Gerne detaillierter mit konkreten Tipps +- Beziehe dich auf frühere Chat-Nachrichten wenn möglich +- Ehrlich sagen wenn du etwas nicht weißt""" diff --git a/requirements.txt b/requirements.txt index 7e5c8ec..0f81a20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ irc==20.1.0 python-dotenv==1.0.0 -PySimpleGUI==4.60.0 +PySimpleGUI==5.0.8.3 requests==2.31.0 httpx==0.25.0 diff --git a/setup_wizard.py b/setup_wizard.py new file mode 100644 index 0000000..f779cea --- /dev/null +++ b/setup_wizard.py @@ -0,0 +1,381 @@ +""" +Setup Wizard for Eugen Twitch Bot +Interactive configuration tool for first-time setup +""" +import os +import sys +import asyncio +import socket +import getpass +from pathlib import Path +from dotenv import set_key, load_dotenv +import httpx + + +class Colors: + """ANSI color codes for terminal output""" + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + + +def print_header(text): + """Print colored header""" + print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}{text.center(60)}{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}\n") + + +def print_success(text): + """Print success message""" + print(f"{Colors.OKGREEN}✓ {text}{Colors.ENDC}") + + +def print_error(text): + """Print error message""" + print(f"{Colors.FAIL}✗ {text}{Colors.ENDC}") + + +def print_info(text): + """Print info message""" + print(f"{Colors.OKCYAN}ℹ {text}{Colors.ENDC}") + + +def print_warning(text): + """Print warning message""" + print(f"{Colors.WARNING}⚠ {text}{Colors.ENDC}") + + +async def validate_twitch_token(token, bot_nickname): + """ + Validate Twitch OAuth token by attempting IRC connection + + Args: + token (str): OAuth token + bot_nickname (str): Bot nickname for authentication + + Returns: + bool: True if valid + """ + if not token.startswith("oauth:"): + print_error("Token muss mit 'oauth:' beginnen!") + return False + + try: + print_info("Validiere Twitch-Verbindung...") + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.settimeout(5) + sock.connect(('irc.chat.twitch.tv', 6667)) + + # Send authentication + sock.send(f"PASS {token}\r\n".encode()) + sock.send(f"NICK {bot_nickname}\r\n".encode()) + + response = sock.recv(1024).decode() + + if "Login authentication failed" in response: + print_error("Twitch OAuth Token ist ungültig!") + return False + + print_success("Twitch-Verbindung erfolgreich!") + return True + + except Exception as e: + print_warning(f"Konnte Twitch-Verbindung nicht testen: {e}") + print_info("Fahre trotzdem fort - bitte später manuell prüfen!") + return True + + +async def validate_perplexity_key(api_key, model="sonar-pro"): + """ + Validate Perplexity API key with test request + + Note: Validation uses the specified model (default: sonar-pro). + If your API key doesn't have access to this model, validation may fail + even with a valid key for other models. + + Args: + api_key (str): Perplexity API key + model (str): Model to test with (defaults to sonar-pro) + + Returns: + tuple: (bool success, bool should_retry) - success indicates if validation passed, + should_retry indicates if user should be prompted to retry + """ + try: + print_info(f"Validiere Perplexity API-Key mit Modell '{model}'...") + async with httpx.AsyncClient(timeout=10.0) as client: + response = await client.post( + "https://api.perplexity.ai/chat/completions", + json={ + "model": model, + "messages": [{"role": "user", "content": "test"}], + "max_tokens": 10 + }, + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + ) + + if response.status_code == 200: + print_success("Perplexity API-Key ist gültig!") + return (True, False) + elif response.status_code == 401: + print_error("Perplexity API-Key ist ungültig!") + return (False, True) + else: + print_warning(f"Unerwartete Antwort: {response.status_code}") + print_info("Fahre trotzdem fort - bitte später manuell prüfen!") + return (True, False) + + except Exception as e: + print_warning(f"Konnte Perplexity API nicht testen: {e}") + print_info("Fahre trotzdem fort - bitte später manuell prüfen!") + return (True, False) + + +def create_env_file(config): + """ + Create or update .env file with configuration + + Args: + config (dict): Configuration dictionary + """ + env_file = ".env" + + # Create .env if it doesn't exist + if not os.path.exists(env_file): + Path(env_file).touch() + + # Write all config values + for key, value in config.items(): + set_key(env_file, key, value) + + print_success(f".env Datei erstellt/aktualisiert!") + + +def create_directories(): + """Create necessary directories for bot operation""" + directories = [ + "data", + "data/conversations", + "logs" + ] + + for directory in directories: + Path(directory).mkdir(parents=True, exist_ok=True) + + print_success("Verzeichnisse erstellt!") + + +def get_input(prompt, default=None, required=True, secret=False): + """ + Get user input with validation + + Args: + prompt (str): Input prompt + default (str): Default value + required (bool): Whether input is required + secret (bool): Whether to hide input (for passwords) + + Returns: + str: User input + """ + if default: + prompt_text = f"{prompt} [{default}]: " + else: + prompt_text = f"{prompt}: " + + while True: + if secret: + value = getpass.getpass(prompt_text) + else: + value = input(prompt_text) + + # Use default if provided and input is empty + if not value and default: + return default + + # Check if required + if required and not value: + print_error("Dieses Feld ist erforderlich!") + continue + + return value + + +async def run_wizard(): + """Main wizard flow""" + + # Welcome + print_header("🤖 EUGEN BOT SETUP WIZARD 🤖") + print("Willkommen beim Setup-Assistenten für deinen Twitch-Bot!") + print("Dieser Wizard führt dich durch die Erstkonfiguration.\n") + + print_info("Du benötigst:") + print(" 1. Einen Twitch OAuth Token") + print(" 2. Deinen Twitch Channel-Namen") + print(" 3. Einen Perplexity API Key") + print() + + input("Drücke ENTER um zu starten...") + + # Configuration dictionary + config = {} + + # Step 1: Twitch Configuration + print_header("SCHRITT 1: TWITCH KONFIGURATION") + + print_info("Twitch OAuth Token generieren:") + print(" → Gehe zu: https://twitchtokengenerator.com") + print(" → Wähle 'Bot Chat Token'") + print(" → Authorisiere und kopiere das 'oauth:...' Token") + print() + + while True: + token = get_input("Twitch OAuth Token", secret=True, required=True) + if not token.startswith("oauth:"): + print_warning("Token sollte mit 'oauth:' beginnen. Füge ich hinzu...") + token = f"oauth:{token}" + + config['TWITCH_OAUTH_TOKEN'] = token + break + + config['TWITCH_CHANNEL'] = get_input( + "Twitch Channel Name (ohne #)", + default="keneraosmd", + required=True + ) + + # Add # if not present + if not config['TWITCH_CHANNEL'].startswith('#'): + config['TWITCH_CHANNEL'] = f"#{config['TWITCH_CHANNEL']}" + + config['TWITCH_BOT_NICKNAME'] = get_input( + "Bot Nickname", + default="Eugen", + required=True + ) + + # Validate Twitch + if not await validate_twitch_token(config['TWITCH_OAUTH_TOKEN'], config['TWITCH_BOT_NICKNAME']): + retry = input("\nTrotzdem fortfahren? (j/n): ") + if retry.lower() != 'j': + print_error("Setup abgebrochen!") + return False + + # Step 2: Perplexity Configuration + print_header("SCHRITT 2: PERPLEXITY API") + + print_info("Perplexity API Key erhalten:") + print(" → Gehe zu: https://www.perplexity.ai/settings/api") + print(" → Erstelle einen neuen API Key") + print(" → Kopiere den Key (beginnt mit 'pplx-...')") + print() + + while True: + api_key = get_input("Perplexity API Key", secret=True, required=True) + config['PERPLEXITY_API_KEY'] = api_key + + success, should_retry = await validate_perplexity_key(api_key) + if success: + break + + if should_retry: + retry = input("\nErneut versuchen? (j/n): ") + if retry.lower() != 'j': + print_error("Setup abgebrochen!") + return False + + # Step 3: Advanced Settings + print_header("SCHRITT 3: ERWEITERTE EINSTELLUNGEN") + + print("Möchtest du erweiterte Einstellungen konfigurieren?") + advanced = input("(j/n, default: n): ").lower() == 'j' + + if advanced: + config['PERPLEXITY_MODEL'] = get_input( + "Perplexity Modell", + default="sonar-pro" + ) + config['MAX_TOKENS'] = get_input( + "Max Tokens pro Antwort", + default="450" + ) + config['DEBUG_MODE'] = get_input( + "Debug Mode aktivieren?", + default="false" + ) + config['CONTEXT_RETENTION_HOURS'] = get_input( + "Context Retention (Stunden)", + default="1" + ) + else: + config['PERPLEXITY_MODEL'] = "sonar-pro" + config['MAX_TOKENS'] = "450" + config['DEBUG_MODE'] = "false" + config['CONTEXT_RETENTION_HOURS'] = "1" + + # Step 4: Create files and directories + print_header("SCHRITT 4: SETUP ABSCHLIESSEN") + + print_info("Erstelle Konfigurationsdateien...") + create_env_file(config) + create_directories() + + # Final summary + print_header("✅ SETUP ERFOLGREICH!") + + print(f"{Colors.OKGREEN}Deine Konfiguration:{Colors.ENDC}") + print(f" Channel: {config['TWITCH_CHANNEL']}") + print(f" Bot Name: {config['TWITCH_BOT_NICKNAME']}") + print(f" Modell: {config['PERPLEXITY_MODEL']}") + print(f" Max Tokens: {config['MAX_TOKENS']}") + print() + + print_info("Nächste Schritte:") + print(" 1. Starte den Bot mit: python chatbot.py") + print(" 2. Der Bot verbindet sich mit Twitch") + print(" 3. Erwähne den Bot im Chat mit '@Eugen' oder 'Eugen:'") + print() + + print_success("Viel Spaß mit deinem Bot! 🚀") + return True + + +def main(): + """Main entry point""" + try: + # Check if already configured + if os.path.exists('.env'): + load_dotenv() + if os.getenv('TWITCH_OAUTH_TOKEN') and os.getenv('PERPLEXITY_API_KEY'): + print_warning(".env Datei existiert bereits!") + reconfigure = input("Möchtest du die Konfiguration überschreiben? (j/n): ") + if reconfigure.lower() != 'j': + print_info("Setup abgebrochen. Nutze die bestehende Konfiguration.") + return + + # Run async wizard + success = asyncio.run(run_wizard()) + + if not success: + sys.exit(1) + + except KeyboardInterrupt: + print() + print_warning("Setup abgebrochen!") + sys.exit(1) + except Exception as e: + print_error(f"Fehler während des Setups: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main()