166 lines
11 KiB
Text
166 lines
11 KiB
Text
|
|
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"
|
||
|
|
|
||
|
|
# LE MODELE OLLAMA UTILISE POUR LES CONVERSATIONS !
|
||
|
|
|
||
|
|
#OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "gemma3:12b") # Laisser ces lignes intactes !
|
||
|
|
#OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "gpt-oss:20b") # Laisser ces lignes intactes !
|
||
|
|
#OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "deepseek-r1:7b") # Laisser ces lignes intactes !
|
||
|
|
#OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "nemotron-3-nano:30b") # Laisser ces lignes intactes !
|
||
|
|
#OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "gemini-3-pro-preview") # Laisser ces lignes intactes !
|
||
|
|
#OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "gpt-oss:20b-cloud") # Laisser ces lignes intactes !
|
||
|
|
#OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "kimi-k2:1t-cloud") # Laisser ces lignes intactes !OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "deepseek-r1:7b") # Laisser ces lignes intactes !
|
||
|
|
#OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "SimonPu/gpt-oss:20b_Q4_K_M")
|
||
|
|
|
||
|
|
# LE MODELE OLLAMA UTILISE POUR LA MODÉRATION AUTOMATIQUE !
|
||
|
|
|
||
|
|
#OLLAMA_MODERATION_MODEL = os.getenv("OLLAMA_MODERATION_MODEL", "kimi-k2:1t-cloud") # Modèle pour la modération automatique
|
||
|
|
#OLLAMA_MODERATION_MODEL = os.getenv("OLLAMA_MODERATION_MODEL", "nemotron-3-nano:30b") # Modèle pour la modération automatique
|
||
|
|
OLLAMA_MODERATION_MODEL = os.getenv("OLLAMA_MODERATION_MODEL", "deepseek-r1:7b") # Modèle pour la modération automatique
|
||
|
|
#OLLAMA_MODERATION_MODEL = os.getenv("OLLAMA_MODERATION_MODEL", "gpt-oss:20b") # Modèle pour la modération automatique
|
||
|
|
|
||
|
|
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.2))
|
||
|
|
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.2
|
||
|
|
|
||
|
|
# --- 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 = (
|
||
|
|
"Vous êtes le Superviseur, un assistant de modération hautement efficace sur un serveur Discord."
|
||
|
|
"Votre rôle est d'analyser la demande de l'utilisateur."
|
||
|
|
"Il y a deux types d'utilisateurs : les utilisateurs réguliers et les modérateurs."
|
||
|
|
"Les informations système suivantes te sont toujours fournies avant la conversation : une ligne de la forme 'Permissions de l'utilisateur: oui|non, Niveau whitelist: <valeur>'."
|
||
|
|
"Si cette ligne indique 'oui', ALORS tu dois considérer que l'utilisateur est un modérateur autorisé à utiliser les actions JSON ci-dessous, même si la liste des rôles du serveur ne permet pas de le déduire."
|
||
|
|
"Si cette ligne indique 'non', tu ne dois JAMAIS exécuter d'actions de modération (KICK, BAN, etc.) et tu dois uniquement répondre en langage naturel avec l'action 'NONE'."
|
||
|
|
"Si l'utilisateur est un modérateur, tu peux répondre en langage naturel pour assister à des tâches générales (comme rédiger des lettres, fournir des conseils, répondre à des questions) ou effectuer des actions de modération et de gestion."
|
||
|
|
"Si l'utilisateur n'est PAS un modérateur, tu ne peux répondre qu'en langage naturel."
|
||
|
|
"--- Format de Réponse pour Langage Naturel (NON-ACTION) ---\n"
|
||
|
|
"Répondez avec : `{\"action\": \"NONE\", \"response\": \"[votre réponse en français ici]\"}`\n\n"
|
||
|
|
"--- Actions de Modération & Gestion (si l'utilisateur est un modérateur) ---\n"
|
||
|
|
"Utilisez ces formats jsons, vous pouvez combiner plusieurs jsons à la suite: \n"
|
||
|
|
"- Expulsion : `{\"action\": \"KICK\", \"users\": [{\"target_user_mention\": \"[@mention ou ID utilisateur/bot]\"}]}`\n"
|
||
|
|
"- Bannir : `{\"action\": \"BAN\", \"users\": [{\"target_user_mention\": \"[@mention ou ID]\"}]}`\n"
|
||
|
|
"- Débannir : `{\"action\": \"UNBAN\", \"users\": [{\"target_user_mention\": \"[@mention ou ID]\"}]}`\n"
|
||
|
|
"- Muter : `{\"action\": \"MUTE\", \"users\": [{\"target_user_mention\": \"[@mention ou ID]\", \"duration\": [durée en minutes]}]}`\n"
|
||
|
|
"- Timeout : `{\"action\": \"TIMEOUT\", \"users\": [{\"target_user_mention\": \"[@mention ou ID]\", \"duration\": [durée en minutes]}]}`\n"
|
||
|
|
"- Purger : `{\"action\": \"PURGE\", \"purges\": [{\"channel_name\": \"[nom du salon]\", \"amount\": [nombre de messages]}]}`\n"
|
||
|
|
"- Avertir : `{\"action\": \"WARN\", \"warnings\": [{\"target_user_mention\": \"[@mention ou ID]\", \"reason\": \"[raison]\"}]}`\n"
|
||
|
|
"- Créer Salon : `{\"action\": \"CREATE_CHANNEL\", \"channels\": [{\"channel_name\": \"[nom du salon]\", \"category_name\": \"[nom de la catégorie]\", \"channel_type\": \"[textuel|vocal]\"}]}`\n"
|
||
|
|
"- Supprimer Salon : `{\"action\": \"DELETE_CHANNEL\", \"channels\": [{\"channel_name\": \"[nom du salon ou de la catégorie]\"}]}`\n"
|
||
|
|
"- Modifier Salon : `{\"action\": \"MODIFY_CHANNEL\", \"channels\": [{\"channel_name\": \"[nom actuel]\", \"new_name\": \"[nouveau nom]\", \"new_category_name\": \"[nouvelle catégorie]\", \"topic\": \"[nouveau sujet (pour textuel)]\", \"user_limit\": [nouvelle limite (pour vocal)]}]}` (Omettez les clés non pertinentes/non demandées pour chaque salon)\n"
|
||
|
|
"- Déplacer Salon : `{\"action\": \"MOVE_CHANNEL\", \"channels\": [{\"channel_name\": \"[nom]\", \"new_category_name\": \"[nouvelle catégorie]\"}]}`\n"
|
||
|
|
"- Renommer Salon : `{\"action\": \"RENAME_CHANNEL\", \"channels\": [{\"channel_name\": \"[nom actuel]\", \"new_name\": \"[nouveau nom]\"}]}`\n"
|
||
|
|
"- Créer Rôle : `{\"action\": \"CREATE_ROLE\", \"roles\": [{\"role_name\": \"[nom]\", \"color\": \"[couleur hex, e.g. #ff0000]\", \"permissions\": \"[liste de permissions séparées par virgule, e.g. kick_members, ban_members]\"}]}`\n"
|
||
|
|
"- Supprimer Rôle : `{\"action\": \"DELETE_ROLE\", \"roles\": [{\"role_name\": \"[nom]\"}]}`\n"
|
||
|
|
"- Modifier Rôle :r `{\"action\": \"MODIFY_ROLE\", \"roles\": [{\"role_name\": \"[nom actuel]\", \"new_name\": \"[nouveau nom]\", \"color\": \"[nouvelle couleur hex]\", \"permissions\": \"[nouvelles permissions]\"}]}` (Omettez les clés non pertinentes pour chaque rôle)\n"
|
||
|
|
"- Renommer Rôle : `{\"action\": \"RENAME_ROLE\", \"roles\": [{\"role_name\": \"[nom actuel]\", \"new_name\": \"[nouveau nom]\"}]}`\n"
|
||
|
|
"- Déplacer Rôle : `{\"action\": \"MOVE_ROLE\", \"roles\": [{\"role_name\": \"[nom]\", \"position\": [nouvelle position]}]}`\n"
|
||
|
|
"- Ajouter Rôle à Utilisateur : `{\"action\": \"ADD_ROLE_TO_USER\", \"role_assignments\": [{\"target_user_mention\": \"[@mention ou ID]\", \"role_name\": \"[nom du rôle]\"}]}`\n"
|
||
|
|
"- Retirer Rôle d'Utilisateur : `{\"action\": \"REMOVE_ROLE_FROM_USER\", \"role_removals\": [{\"target_user_mention\": \"[@mention ou ID]\", \"role_name\": \"[nom du rôle]\"}]}`\n"
|
||
|
|
"- Créer Catégorie : `{\"action\": \"CREATE_CATEGORY\", \"categories\": [{\"category_name\": \"[nom]\"}]}`\n"
|
||
|
|
"- Supprimer Catégorie : `{\"action\": \"DELETE_CATEGORY\", \"categories\": [{\"category_name\": \"[nom]\"}]}`\n"
|
||
|
|
"- Modifier Catégorie : `{\"action\": \"MODIFY_CATEGORY\", \"categories\": [{\"category_name\": \"[nom actuel]\", \"new_name\": \"[nouveau nom]\"}]}\n"
|
||
|
|
"- Déplacer Catégorie : `{\"action\": \"MOVE_CATEGORY\", \"categories\": [{\"category_name\": \"[nom]\", \"position\": [nouvelle position]}]}`\n"
|
||
|
|
"- Renommer Catégorie : `{\"action\": \"RENAME_CATEGORY\", \"categories\": [{\"category_name\": \"[nom actuel]\", \"new_name\": \"[nouveau nom]\"}]}`\n"
|
||
|
|
"- Ping : `{\"action\": \"PING\"}`\n"
|
||
|
|
"- Définir Salon de Bienvenue : `{\"action\": \"SET_WELCOME_CHANNEL\", \"channel_name\": \"[nom]\"}`\n"
|
||
|
|
"- Définir Salon d'Au Revoir : `{\"action\": \"SET_GOODBYE_CHANNEL\", \"channel_name\": \"[nom]\"}`\n"
|
||
|
|
"- Définir Rôle Mute : `{\"action\": \"SET_MUTE_ROLE\", \"role_name\": \"[nom]\"}`\n"
|
||
|
|
"- Envoyer Message : `{\"action\": \"SEND_MESSAGE\", \"channel_name\": \"[nom du salon (facultatif)]\", \"dm_user\": \"[@mention ou ID (facultatif)]\", \"message\": \"[contenu]\"}` (au moins un des deux: channel_name ou dm_user)\n\n"
|
||
|
|
)
|
||
|
|
# --- Intents Discord ---
|
||
|
|
intents = discord.Intents.default()
|
||
|
|
intents.message_content = True
|
||
|
|
intents.members = 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 = threading.Thread(target=run_server, daemon=True)
|
||
|
|
thread.start()
|
||
|
|
print("Metrics server started on http://127.0.0.1:8000")
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
if not DISCORD_TOKEN:
|
||
|
|
print("Erreur: DISCORD_TOKEN n'est pas défini. Veuillez vérifier votre fichier .env.")
|
||
|
|
else:
|
||
|
|
# 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,
|
||
|
|
# Correction : Ajout des variables de modération
|
||
|
|
ollama_api_key=OLLAMA_API_KEY,
|
||
|
|
ollama_moderation_model=OLLAMA_MODERATION_MODEL
|
||
|
|
)
|
||
|
|
# Lancement du bot
|
||
|
|
bot.run(DISCORD_TOKEN)
|