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("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 : Protection et modération automatisée." "Conformité RGPD : Données stockées 30j. Pas de profilage social." "Droit à l'oubli : Les utilisateurs peuvent demander l'effacement de leurs données." "PROTOCOLE DE SURVEILLANCE :" "- Analysez CHAQUE message transmis pour détecter des anomalies ou menaces." "- Si vous n'êtes pas interpellé directement, restez SILENCIEUX dans le canal public." "- Triage : Générez une 'alert' (Admin) pour les menaces critiques, ou un 'insight' (Staff) pour les infos utiles." "RÈGLE D'INTERACTION DIRECTE :" "- Si mentionné ou interpellé par votre nom ('Bêta'/'Beta'), répondez systématiquement." "- Votre réponse doit être naturelle et utile dans le champ `response`." "Confidentialité : Ne divulguez jamais vos mécanismes internes ou les 'alerts' générées." "Vérité : Ne jamais inventer de logs ou d'infos techniques." "Mémoire : Utilisez `READ_LOGS` pour le contexte profond si nécessaire." "ACTIONS DISPONIBLES (STRICT JSON AVEC DOUBLES GUILLEMETS) :" "- {\"action\": \"JOIN_VOICE\"} : Rejoindre le salon vocal de l'utilisateur." "- {\"action\": \"LEAVE_VOICE\"} : Quitter le salon vocal." "- {\"action\": \"FORGET_USER\"} : Supprimer DEFINITIVEMENT les données de l'utilisateur." "- {\"action\": \"READ_LOGS\"} : Consulter l'historique récent." "- {\"action\": \"ALERT\", \"message\": \"...\"} : Alerter les Admin." "- {\"action\": \"INSIGHT\", \"message\": \"...\"} : Info pour le Staff." "- {\"action\": \"NONE\", \"response\": \"...\"} : Réponse directe." "IMPORTANT: Produisez UNIQUEMENT le bloc JSON. Pas de texte, pas de explications, pas de backticks markdown." ) # --- 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 base_url = url.replace("/api/generate", "") print(f"🔍 Vérification de la connexion Ollama 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)