215 lines
8.9 KiB
Python
215 lines
8.9 KiB
Python
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)
|