2026-02-06 22:23:20 +01:00
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 ( )
2026-03-22 14:43:53 +01:00
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 "
2026-02-06 22:23:20 +01:00
# 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 = (
2026-03-22 14:43:53 +01:00
" 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 "
2026-02-06 22:23:20 +01:00
2026-03-22 14:43:53 +01:00
" 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 "
2026-02-06 22:23:20 +01:00
2026-03-22 14:43:53 +01:00
" 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. "
2026-02-06 22:23:20 +01:00
)
# --- 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
2026-03-22 14:43:53 +01:00
if " /v1 " in url :
base_url = url . split ( " /v1 " ) [ 0 ] + " /v1/models "
else :
base_url = url . replace ( " /api/generate " , " " )
2026-02-06 22:23:20 +01:00
2026-03-22 14:43:53 +01:00
print ( f " 🔍 Vérification de la connexion de l ' IA sur { base_url } ... " )
2026-02-06 22:23:20 +01:00
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 )