Beta/beta.py

205 lines
7.9 KiB
Python
Raw Permalink Normal View History

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()
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)