import os from dotenv import load_dotenv import discord import importlib from superviseur import Superviseur from commandes.autres.config import config import threading import asyncio # --- Configuration et Chargement des Variables d'Environnement --- load_dotenv() DISCORD_TOKEN = os.getenv('DISCORD_TOKEN') try: GUILD_ID = int(os.getenv('GUILD_ID')) except (ValueError, TypeError): print("Erreur: GUILD_ID n'est pas défini ou n'est pas un nombre entier valide.") exit() OLLAMA_API_URL = os.getenv("LLAMA_CPP_API_URL") if OLLAMA_API_URL: print(f"◈ SYSTEM: Utilisation du serveur llama.cpp direct sur {OLLAMA_API_URL}") if not OLLAMA_API_URL.endswith("/v1/chat/completions"): OLLAMA_API_URL = OLLAMA_API_URL.rstrip("/") + "/v1/chat/completions" else: OLLAMA_API_URL = os.getenv("OLLAMA_API_BASE_URL", "http://localhost:11434") + "/api/generate" # LES MODÈLES OLLAMA UTILISÉS # Modèle "Lourd" pour les requêtes complexes et sécurité (Relevant) OLLAMA_MODEL_HEAVY = os.getenv("OLLAMA_MODEL_HEAVY", "gpt-oss:120b") # Modèle "Fast" pour le monitoring et les insights (Irrelevant) OLLAMA_MODEL_FAST = os.getenv("OLLAMA_MODEL_FAST", "deepseek-r1:7b") # LE MODELE PAR DEFAUT (Maintenu pour compatibilité si nécessaire) OLLAMA_MODEL = OLLAMA_MODEL_HEAVY # LE MODELE OLLAMA UTILISE POUR LA MODÉRATION AUTOMATIQUE ! OLLAMA_MODERATION_MODEL = os.getenv("OLLAMA_MODERATION_MODEL", "deepseek-r1:7b") OLLAMA_API_KEY = os.getenv("OLLAMA_API_KEY") try: OLLAMA_TIMEOUT = int(os.getenv("OLLAMA_TIMEOUT", 600)) except (ValueError, TypeError): print("Avertissement: OLLAMA_TIMEOUT n'est pas un entier valide. Utilisation de la valeur par défaut (600s).") OLLAMA_TIMEOUT = 600 try: OLLAMA_TEMPERATURE = float(os.getenv("OLLAMA_TEMPERATURE", 0.4)) except (ValueError, TypeError): print("Avertissement: OLLAMA_TEMPERATURE n'est pas un nombre valide. Utilisation de la valeur par défaut (0.7).") OLLAMA_TEMPERATURE = 0.4 # --- PROMPT SYSTÈME CENTRALISÉ --- # Ceci est l'instruction donnée au modèle LLM pour définir son rôle et les formats de réponse. # Les actions disponibles dans le prompt DOIVENT correspondre aux fonctions implémentées # dans superviseur_bot.py. SYSTEM_PROMPT = ( "Mission : Vous êtes Beta, l'assistant conversationnel polyvalent et amical du serveur.\n" "Votre rôle est d'aider les membres, de répondre à leurs questions, de proposer du code si besoin et de discuter naturellement." "Ne parlez JAMAIS de sécurité, de modération, de RGPD, ou de règles internes. Agissez comme un membre normal et utile.\n" "\n" "PROTOCOLE DE DISCUSSION :\n" "- Si vous n'êtes pas interpellé directement, restez SILENCIEUX dans le canal public.\n" "RÈGLE D'INTERACTION DIRECTE :\n" "- Si mentionné ou interpellé, répondez systématiquement de manière FLUIDE, HUMAINE et ADAPTÉE à la discussion.\n" "- Ne vous comportez PAS comme un robot qui répète sa mission en boucle. Si l'utilisateur plaisante ou discute, faites de même !\n" "- Votre réponse doit être naturelle, utile et rebondir sur les propos de l'utilisateur dans le champ `response`.\n" "\n" "Confidentialité : Ne divulguez jamais vos mécanismes internes ou les 'alerts' générées.\n" "Vérité : Ne jamais inventer de logs ou d'infos techniques.\n" "Mémoire : Utilisez `READ_LOGS` pour le contexte profond si nécessaire. Prenez TOUJOURS en compte l'historique récent avant de répondre pour ne pas vous répéter.\n" "ACTIONS DISPONIBLES (STRICT JSON AVEC DOUBLES GUILLEMETS) :\n" "- {\"action\": \"JOIN_VOICE\"} : Rejoindre le salon vocal de l'utilisateur.\n" "- {\"action\": \"LEAVE_VOICE\"} : Quitter le salon vocal.\n" "- {\"action\": \"FORGET_USER\"} : Supprimer DEFINITIVEMENT les données.\n" "- {\"action\": \"READ_LOGS\"} : Consulter l'historique récent.\n" "- {\"action\": \"ALERT\", \"message\": \"(votre alerte complète ici)\"} : Alerter les Admin.\n" "- {\"action\": \"INSIGHT\", \"message\": \"(votre note complète ici)\"} : Info pour Staff.\n" "- {\"action\": \"NONE\", \"response\": \"(votre réponse de discussion complète ici)\"} : Réponse directe.\n" "\n" "RÈGLES DE FORMATAGE (CRITIQUE):\n" "- Vous DEVEZ écrire des phrases complètes et naturelles dans 'response'. Les réponses incomplètes ou vides sont interdites.\n" "- Produisez UNIQUEMENT le bloc JSON final. Pas de texte autour, pas de balise markdown.\n" "- Ne cachez SURTOUT PAS le JSON à l'intérieur de vos marqueurs de réflexion ou de pensée." ) # --- Intents Discord --- intents = discord.Intents.default() intents.message_content = True intents.members = True intents.voice_states = True # Optional: faster event loop with uvloop in production (toggle with env USE_UVLOOP=1) if os.environ.get("USE_UVLOOP", "0") in ("1", "true", "True"): try: uvloop = importlib.import_module("uvloop") import asyncio asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) print("uvloop enabled — using high-performance event loop") except Exception: print("uvloop requested but not available; continuing with default event loop") async def send_hello(bot): """Send 'hello' message to the first text channel of the guild.""" guild = bot.get_guild(bot.guild_id) if guild: channel = next((ch for ch in guild.text_channels if ch.permissions_for(guild.me).send_messages), None) if channel: await channel.send("hello") else: print("No text channel found where I can send messages.") else: print("Guild not found.") def maybe_start_metrics(bot): """Start metrics server if METRICS_ENABLED is set.""" if os.getenv("METRICS_ENABLED", "0") in ("1", "true", "True"): app = FastAPI() prometheus_client.start_http_server(port=8000, addr="127.0.0.1") @app.get("/metrics") def get_metrics(): return bot.get_metrics() config = Config(app=app, host="127.0.0.1", port=8000, log_level="info") server = Server(config) def run_server(): asyncio.run(server.serve()) thread.start() print("Metrics server started on http://127.0.0.1:8000") def check_ollama_connection(url: str) -> bool: """Check if Ollama is accessible.""" import urllib.request import urllib.error # Ensure we check the base URL or version endpoint, not the generate endpoint which needs POST if "/v1" in url: base_url = url.split("/v1")[0] + "/v1/models" else: base_url = url.replace("/api/generate", "") print(f"🔍 Vérification de la connexion de l'IA sur {base_url}...") try: # Try both the API URL and base URL to be safe urllib.request.urlopen(base_url, timeout=2) print(f"✅ Connexion Ollama établie avec succès !") return True except Exception as e: print(f"⚠️ Attention: Impossible de joindre Ollama ({e}).") print("Le bot va démarrer mais l'IA risque de ne pas répondre.") return False def load_libopus(): """Load the Opus library for voice support on Linux.""" if not discord.opus.is_loaded(): import ctypes.util # 1. Search via system utility found_path = ctypes.util.find_library('opus') # 2. Hardcoded common Linux paths opus_libs = [ 'libopus.so.0', 'libopus.so', '/usr/lib/x86_64-linux-gnu/libopus.so.0', '/usr/lib/libopus.so.0', '/usr/local/lib/libopus.so' ] if found_path: opus_libs.insert(0, found_path) for lib in opus_libs: try: discord.opus.load_opus(lib) print(f"✅ Bibliothèque Opus chargée : {lib}") return True except Exception: continue print("⚠️ Attention: Impossible de localiser libopus sur votre système.") return False return True if __name__ == "__main__": if not DISCORD_TOKEN: print("Erreur: DISCORD_TOKEN n'est pas défini. Veuillez vérifier votre fichier .env.") else: # Vérification de la connexion Ollama check_ollama_connection(OLLAMA_API_URL) # Chargement de libopus pour la voix load_libopus() # Initialisation du bot avec les configurations bot = Superviseur( guild_id=GUILD_ID, ollama_api_url=OLLAMA_API_URL, ollama_model=OLLAMA_MODEL, ollama_timeout=OLLAMA_TIMEOUT, system_prompt=SYSTEM_PROMPT, command_prefix="!", intents=intents, ollama_temperature=OLLAMA_TEMPERATURE, # Configuration multi-modèles pour Bêta ollama_api_key=OLLAMA_API_KEY, ollama_moderation_model=OLLAMA_MODERATION_MODEL, ollama_model_fast=OLLAMA_MODEL_FAST ) # Lancement du bot bot.run(DISCORD_TOKEN)