Initialisation du repository de Beta

This commit is contained in:
Mathis 2026-02-06 22:23:20 +01:00
commit 14985f6dbb
9469 changed files with 1903273 additions and 0 deletions

15
.env Normal file
View file

@ -0,0 +1,15 @@
DISCORD_TOKEN=MTM3MDcxNjU1MTQ4NTA2NzMwNA.GJHUjY.YrzGaD2yXj0-UwYZ0ut7RA_tSeI5ji1nD5iLTY
GUILD_ID=1370716551485067304
# --- CONFIGURATION BÊTA ---
# IDs des Administrateurs (Séparés par des virgules)
ADMIN_IDS=971446412690722826
# IDs du Staff (Assets Primaires), séparés par des virgules
PRIMARY_ASSETS_IDS=971446412690722826
# Channel d'Introspection (Log Stream de Bêta)
INTROSPECTION_CHANNEL_ID=1461107327381016629
# Modèles Ollama (Optimisation Radeon 8060S 64GB)
OLLAMA_MODEL_HEAVY=gpt-oss:120b
OLLAMA_MODEL_FAST=deepseek-r1:7b

0
To Normal file
View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

742
actions.log Normal file

File diff suppressed because one or more lines are too long

204
beta.py Normal file
View file

@ -0,0 +1,204 @@
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)

165
beta.py.save Normal file
View file

@ -0,0 +1,165 @@
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)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,91 @@
import os
import json
from typing import Dict, Any, Optional
class Config:
def __init__(self, config_file: str = 'config.json'):
self.config_file = config_file
self.data: Dict[str, Any] = {}
self.load_config()
def load_config(self):
"""Load configuration from file if it exists."""
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
self.data = json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
self.data = {}
else:
self.data = {}
def save_config(self):
"""Save configuration to file if dirty."""
if self.dirty:
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.data, f, indent=4, ensure_ascii=False)
self.dirty = False
except Exception as e:
print(f"Erreur lors de la sauvegarde de la configuration: {e}")
def get(self, key: str, default: Any = None) -> Any:
"""Get a configuration value."""
return self.data.get(key, default)
def set(self, key: str, value: Any):
"""Set a configuration value and save."""
self.data[key] = value
self.save_config()
def get_welcome_channel(self, guild_id: int) -> Optional[int]:
"""Get welcome channel ID for a guild."""
return self.get('welcome_channels', {}).get(str(guild_id))
def set_welcome_channel(self, guild_id: int, channel_id: int):
"""Set welcome channel for a guild."""
welcome_channels = self.get('welcome_channels', {})
welcome_channels[str(guild_id)] = channel_id
self.set('welcome_channels', welcome_channels)
def get_goodbye_channel(self, guild_id: int) -> Optional[int]:
"""Get goodbye channel ID for a guild."""
return self.get('goodbye_channels', {}).get(str(guild_id))
def set_goodbye_channel(self, guild_id: int, channel_id: int):
"""Set goodbye channel for a guild."""
goodbye_channels = self.get('goodbye_channels', {})
goodbye_channels[str(guild_id)] = channel_id
self.set('goodbye_channels', goodbye_channels)
def get_mute_role(self, guild_id: int) -> Optional[str]:
"""Get mute role name for a guild."""
return self.get('mute_roles', {}).get(str(guild_id))
def set_mute_role(self, guild_id: int, role_name: str):
"""Set mute role for a guild."""
mute_roles = self.get('mute_roles', {})
mute_roles[str(guild_id)] = role_name
self.set('mute_roles', mute_roles)
self.save_config() # Force save for critical config
def get_allowed_words(self) -> list:
"""Get list of allowed words."""
return self.get('allowed_words', [])
def set_allowed_words(self, words: list):
"""Set allowed words."""
self.set('allowed_words', words)
def get_mod_report_channel(self, guild_id: int) -> Optional[int]:
"""Get mod report channel ID for a guild."""
return self.get('mod_report_channels', {}).get(str(guild_id))
def set_mod_report_channel(self, guild_id: int, channel_id: int):
"""Set mod report channel for a guild."""
mod_channels = self.get('mod_report_channels', {})
mod_channels[str(guild_id)] = channel_id
self.set('mod_report_channels', mod_channels)
# Global config instance
config = Config()

65
commandes/autres/help.py Normal file
View file

@ -0,0 +1,65 @@
import discord
import os
HELP_TEXT = """
**Le Superviseur** - Bot de modération Discord
**Commandes générales:**
`@Le Superviseur {message}` - Parler avec l'IA (répondez à ses messages aussi)
`!help` - Afficher cette aide
`!status` - Afficher le statut du bot
**Modération:**
`!kick user [raison]` - Expulser un utilisateur
`!ban user [raison]` - Bannir un utilisateur
`!unban user` - Débannir un utilisateur
`!warn user [raison]` - Avertir un utilisateur
`!list_warnings` - Lister les avertissements
`!mute user duration` - Muter temporairement
`!timeout user duration` - Timeout temporaire
`!purge amount` - Supprimer des messages
**Gestion des salons:**
`!create_channel name1 [name2 name3 ...] [category type]` - Créer plusieurs salons (type: textuel/vocal, optionnel)
`!delete_channel channel1 [channel2 ...]` - Supprimer plusieurs salons
`!modify_channel channel new_name [new_category] [topic] [user_limit]` - Modifier un salon par session (pour multi, répétez la commande)
`!move_channel channel1 new_category [channel2 new_category ...]` - Déplacer plusieurs salons vers une nouvelle catégorie
`!rename_channel channel1 new_name [channel2 new_name ...]` - Renommer plusieurs salons
**Gestion des catégories:**
`!create_category name1 [name2 ...]` - Créer plusieurs catégories
`!delete_category category1 [category2 ...]` - Supprimer plusieurs catégories
`!modify_category category new_name` - Modifier une catégorie par commande (pour multi, répétez)
`!move_category category1 position [category2 position ...]` - Déplacer plusieurs catégories
`!rename_category category1 new_name [category2 new_name ...]` - Renommer plusieurs catégories
**Gestion des rôles:**
`!create_role name [color] [permissions]` - Créer un rôle par commande (pour multi, répétez)
`!delete_role role1 [role2 ...]` - Supprimer plusieurs rôles
`!modify_role role new_name [new_color] [new_permissions]` - Modifier un rôle par commande (pour multi, répétez)
`!add_role_to_user user role1 [role2 ...]` - Donner plusieurs rôles à un utilisateur
`!remove_role_from_user user role1 [role2 ...]` - Retirer plusieurs rôles d'un utilisateur
`!move_role role1 position [role2 position ...]` - Déplacer plusieurs rôles
`!rename_role role1 new_name [role2 new_name ...]` - Renommer plusieurs rôles
**Configuration:**
`!set_welcome_channel channel` - Canal de bienvenue
`!set_goodbye_channel channel` - Canal d'au revoir
`!set_mute_role role` - Rôle muet
**Outils:**
`!ping` - Vérifier latence
**Note:** Les commandes supportent maintenant plusieurs cibles à la fois quand applicable. Remplacez `user`, `channel`, etc. par des mentions ou IDs. Les [ ] indiquent paramètres optionnels. Pour créer/modifier multiples salons/rôles avec options différentes, utilisez des commandes séparées ou l'IA.
Le bot utilise l'IA Ollama pour des réponses intelligentes et peut gérer les salons, rôles et modération efficacement.
"""
async def execute(bot, params, message):
embed = discord.Embed(
title="Aide du Superviseur",
description=HELP_TEXT,
color=discord.Color.blue()
)
embed.set_footer(text="Utilisez les mentions pour référencer utilisateurs/salons/rôles")
await message.channel.send(embed=embed)

View file

@ -0,0 +1,54 @@
import logging
import json
from datetime import datetime
from .config import config
class ActionLogger:
def __init__(self, log_file: str = 'actions.log'):
self.log_file = log_file
self.logger = logging.getLogger('ActionLogger')
self.logger.setLevel(logging.INFO)
# File handler
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(logging.INFO)
# Formatter
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# Add handler if not already added
if not self.logger.handlers:
self.logger.addHandler(file_handler)
def log_action(self, action: str, user: str, guild: str, params: dict, success: bool, error: str = None):
"""Log an action performed by the bot."""
log_entry = {
'timestamp': datetime.now().isoformat(),
'action': action,
'user': user,
'guild': guild,
'params': params,
'success': success,
'error': error
}
message = f"ACTION: {json.dumps(log_entry, ensure_ascii=False)}"
if success:
self.logger.info(message)
else:
self.logger.error(message)
def log_message(self, level: str, message: str):
"""Log a general message."""
if level.casefold() == 'info':
self.logger.info(message)
elif level.casefold() == 'warning':
self.logger.warning(message)
elif level.casefold() == 'error':
self.logger.error(message)
elif level.casefold() == 'debug':
self.logger.debug(message)
# Global logger instance
action_logger = ActionLogger()

9
commandes/autres/ping.py Normal file
View file

@ -0,0 +1,9 @@
import discord
import time
async def execute(bot, params, message):
start = time.perf_counter()
temp_msg = await message.channel.send("Pong!")
end = time.perf_counter()
latency = round((end - start) * 1000) # ms
await temp_msg.edit(content=f"Pong! Latence : {latency}ms\nLatence API : {round(bot.latency * 1000)}ms")

View file

@ -0,0 +1,95 @@
import discord
async def execute(bot, params, message):
channel_name = params.get('channel_name')
dm_user_mention = params.get('dm_user')
content = params.get('message', '')
if not content.strip():
raise Exception("Le message ne peut pas être vide.")
guild = message.guild
sent_count = 0
if channel_name:
# Send to channel
# 1. Exact match
channel = discord.utils.get(guild.channels, name=channel_name)
# 2. Fuzzy match if not found (case insensitive, or partial match)
if not channel:
# Case insensitive
channel = discord.utils.find(lambda c: c.name.lower() == channel_name.lower(), guild.channels)
if not channel:
# Partial match (ignoring emojis/special chars often helps)
# Normalize: remove non-alphanumeric chars for comparison
def normalize(s):
return "".join(c for c in s if c.isalnum()).lower()
target_norm = normalize(channel_name)
if target_norm:
channel = discord.utils.find(lambda c: normalize(c.name) == target_norm, guild.channels)
if not channel:
# Last resort: contains
channel = discord.utils.find(lambda c: channel_name.lower() in c.name.lower(), guild.channels)
if not channel or not isinstance(channel, discord.TextChannel):
raise Exception(f"Salon '{channel_name}' introuvable.")
# Check permissions
if not channel.permissions_for(guild.me).send_messages:
raise Exception(f"Le bot n'a pas les permissions pour envoyer des messages dans #{channel.name}.")
await bot.messaging.send_message_with_limit(channel, content)
sent_count += 1
if dm_user_mention:
# Send DM to user
# Parse mention or ID or display name
user = None
if dm_user_mention.startswith('<@') and dm_user_mention.endswith('>'):
# Extract ID from mention
user_id_str = dm_user_mention.split('@')[-1].rstrip('>')
if user_id_str.isdigit():
user_id = int(user_id_str)
user = guild.get_member(user_id)
if not user:
try:
user = await guild.fetch_member(user_id)
except discord.NotFound:
raise Exception(f"Utilisateur introuvable via mention: {dm_user_mention}")
elif dm_user_mention.isdigit():
user_id = int(dm_user_mention)
user = guild.get_member(user_id)
if not user:
try:
user = await guild.fetch_member(user_id)
except discord.NotFound:
raise Exception(f"Utilisateur introuvable via ID: {dm_user_mention}")
else:
# Try to find by display name or username
user = discord.utils.find(lambda m: m.display_name == dm_user_mention or m.name == dm_user_mention, guild.members)
if not user:
# Try partial match (case insensitive)
dm_user_lower = dm_user_mention.lower()
user = discord.utils.find(lambda m: dm_user_lower in m.display_name.lower() or dm_user_lower in m.name.lower(), guild.members)
if not user:
raise Exception(f"Utilisateur introuvable via nom: {dm_user_mention}")
try:
await user.send(content)
sent_count += 1
except discord.Forbidden:
raise Exception(f"Impossible d'envoyer un DM à {dm_user_mention}. L'utilisateur a peut-être bloqué le bot.")
if sent_count == 0:
raise Exception("Au moins un destinataire (channel_name ou dm_user) doit être spécifié.")
# If both were specified and successful
if channel_name and dm_user_mention and sent_count == 2:
await message.channel.send(f"Message envoyé dans #{channel_name} et en DM à {dm_user_mention}.")
elif channel_name:
await message.channel.send(f"Message envoyé dans #{channel_name}.")
elif dm_user_mention:
await message.channel.send(f"Message envoyé en DM à {dm_user_mention}.")

View file

@ -0,0 +1,12 @@
import discord
from .config import config
async def execute(bot, params, message):
channel_name = params.get('channel_name')
guild = message.guild
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
return
config.set_goodbye_channel(guild.id, channel.id)
await message.channel.send(f"Salon d'au revoir défini sur {channel.mention}.")

View file

@ -0,0 +1,14 @@
import discord
from .config import config
async def execute(bot, params, message):
role_name = params.get('role_name')
if not role_name:
await message.channel.send("Veuillez spécifier un nom de rôle.")
return
role = discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), message.guild.roles)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
return
config.set_mute_role(message.guild.id, role.name)
await message.channel.send(f"Rôle mute défini sur {role.mention}.")

View file

@ -0,0 +1,12 @@
import discord
from .config import config
async def execute(bot, params, message):
channel_name = params.get('channel_name')
guild = message.guild
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
return
config.set_welcome_channel(guild.id, channel.id)
await message.channel.send(f"Salon de bienvenue défini sur {channel.mention}.")

View file

@ -0,0 +1,46 @@
import discord
from datetime import timedelta
import time
import psutil
import os
async def execute(bot, params, message):
embed = discord.Embed(
title="Statut du Superviseur",
color=discord.Color.green()
)
# Bot info
embed.add_field(
name="🤖 Bot",
value=f"Connecté en tant que {bot.user.mention}\n"
f"Serveur principal : {bot.guild_id}\n"
f"Latence : {round(bot.latency * 1000)}ms",
inline=True
)
# AI/Memory stats
metrics = bot.get_metrics()
embed.add_field(
name="🧠 IA & Mémoire",
value=f"Requêtes LLM : {metrics.get('total_llm_requests', 0)}\n"
f"Succès : {metrics.get('total_llm_success', 0)}\n"
f"Erreurs : {metrics.get('total_llm_errors', 0)}\n"
f"Temps moyen : {round(metrics.get('total_llm_time', 0) / max(metrics.get('total_llm_requests', 1), 1), 2)}s",
inline=True
)
# System stats
process = psutil.Process(os.getpid())
memory = process.memory_info().rss / 1024 / 1024 # MB
uptime = timedelta(seconds=time.time() - process.create_time())
embed.add_field(
name="💻 Système",
value=f"Mémoire : {round(memory, 2)} MB\n"
f"Disponible : {round(psutil.virtual_memory().available / 1024 / 1024, 0)} MB\n"
f"Disponible pour bot : {round(psutil.virtual_memory().available / 1024 / 1024 / 1024, 2)} GB",
inline=True
)
embed.set_footer(text=f"Uptime: {uptime}")
await message.channel.send(embed=embed)

34
commandes/autres/utils.py Normal file
View file

@ -0,0 +1,34 @@
"""
Utilities for command parsing and common operations
"""
import discord
async def parse_user_mention(bot, target_mention, message):
"""
Parse a user mention, username, or ID to get the user ID and member object.
Returns (user_id, member) or (None, None) if not found.
"""
if target_mention.startswith('<@') and target_mention.endswith('>'):
user_id = int(target_mention[2:-1]) if not target_mention.startswith('<@!') else int(target_mention[3:-1])
elif target_mention.startswith('@'):
# Handle @username
username = target_mention[1:].lower()
member = discord.utils.find(lambda m: username in m.name.lower() or username in m.display_name.lower(), message.guild.members)
if not member:
return None, None
user_id = member.id
else:
try:
user_id = int(target_mention)
except ValueError:
return None, None
member = message.guild.get_member(user_id)
if not member:
try:
user = await bot.fetch_user(user_id)
return user_id, None # User exists but not in guild
except discord.NotFound:
return None, None
return user_id, member

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,18 @@
import discord
async def execute(bot, params, message):
categories = params.get('categories', [])
if not categories:
# Fallback for single category
categories = [params]
guild = message.guild
created_count = 0
for category_params in categories:
category_name = category_params.get('category_name')
try:
category = await guild.create_category(category_name)
created_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour créer la catégorie '{category_name}'.")
if created_count > 0:
await message.channel.send(f"{created_count} catégorie(s) créée(s).")

View file

@ -0,0 +1,23 @@
import discord
async def execute(bot, params, message):
categories = params.get('categories', [])
if not categories:
# Fallback for single category
categories = [params]
guild = message.guild
moved_count = 0
for category_params in categories:
category_name = category_params.get('category_name')
position = category_params.get('position')
category = discord.utils.find(lambda cat: cat.name.casefold() == category_name.casefold(), guild.categories)
if not category:
await message.channel.send(f"Catégorie '{category_name}' introuvable.")
continue
try:
await category.edit(position=position)
moved_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour déplacer la catégorie '{category_name}'.")
if moved_count > 0:
await message.channel.send(f"{moved_count} catégorie(s) déplacée(s).")

View file

@ -0,0 +1,23 @@
import discord
async def execute(bot, params, message):
categories = params.get('categories', [])
if not categories:
# Fallback for single category
categories = [params]
guild = message.guild
renamed_count = 0
for category_params in categories:
category_name = category_params.get('category_name')
new_name = category_params.get('new_name')
category = discord.utils.find(lambda cat: cat.name.casefold() == category_name.casefold(), guild.categories)
if not category:
await message.channel.send(f"Catégorie '{category_name}' introuvable.")
continue
try:
await category.edit(name=new_name)
renamed_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour renommer la catégorie '{category_name}'.")
if renamed_count > 0:
await message.channel.send(f"{renamed_count} catégorie(s) renommée(s).")

View file

@ -0,0 +1,22 @@
import discord
async def execute(bot, params, message):
categories = params.get('categories', [])
if not categories:
# Fallback for single category
categories = [params]
guild = message.guild
deleted_count = 0
for category_params in categories:
category_name = category_params.get('category_name')
category = discord.utils.find(lambda cat: cat.name.casefold() == category_name.casefold(), guild.categories)
if not category:
await message.channel.send(f"Catégorie '{category_name}' introuvable.")
continue
try:
await category.delete(reason="Supprimée par le Superviseur")
deleted_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour supprimer la catégorie '{category_name}'.")
if deleted_count > 0:
await message.channel.send(f"{deleted_count} catégorie(s) supprimée(s).")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,62 @@
import discord
async def execute(bot, params, message):
role_assignments = params.get('role_assignments', [])
if not role_assignments:
# Fallback for single assignment
role_assignments = [params]
assigned_count = 0
total_members = 0
for assignment in role_assignments:
target_mention = assignment.get('target_user_mention')
role_name = assignment.get('role_name')
role = discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), message.guild.roles)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
continue
# Check for bulk assignment
if target_mention.lower() in ['@everyone', 'everyone', 'all', 'tous']:
total_members = 0
assigned_count = 0
await message.channel.send(f"Ajout du rôle '{role.name}' à tous les membres... (Cela peut prendre du temps)")
for member in message.guild.members:
if member.bot:
continue # Skip bots
try:
await member.add_roles(role, reason=f"Rôle ajouté par le Superviseur à tous les membres")
assigned_count += 1
total_members += 1
if assigned_count % 10 == 0: # Progress update every 10 assigns
await message.channel.send(f"Progression: {assigned_count} sur {total_members} membres...")
except discord.Forbidden:
await message.channel.send(f"Impossible d'ajouter le rôle à {member.mention} (permissions insuffisantes)")
continue
except Exception as e:
await message.channel.send(f"Erreur lors de l'ajout à {member.mention}: {str(e)}")
continue
await message.channel.send(f"Rôle '{role.name}' ajouté à {assigned_count} membres (bots exclus).")
continue
# Single user assignment
if target_mention.startswith('<@') and target_mention.endswith('>'):
user_id = int(target_mention[2:-1])
elif target_mention.startswith('<@!') and target_mention.endswith('>'):
user_id = int(target_mention[3:-1])
else:
try:
user_id = int(target_mention)
except ValueError:
await message.channel.send(f"Mention ou ID invalide: {target_mention}")
continue
member = message.guild.get_member(user_id)
if not member:
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
continue
try:
await member.add_roles(role, reason=f"Rôle ajouté par le Superviseur")
assigned_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour ajouter le rôle {role.name} à {member.mention}.")
if assigned_count > 0 and not total_members:
await message.channel.send(f"{assigned_count} rôle(s) ajouté(s).")

32
commandes/roles/creer.py Normal file
View file

@ -0,0 +1,32 @@
import discord
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
# Fallback for single role
roles = [params]
guild = message.guild
created_count = 0
for role_params in roles:
role_name = role_params.get('role_name')
color = role_params.get('color', '#ffffff')
permissions_str = role_params.get('permissions', '')
# Parse color
try:
color = discord.Color(int(color.lstrip('#'), 16))
except ValueError:
color = discord.Color.default()
# Parse permissions
permissions = discord.Permissions()
if permissions_str:
perm_list = [p.strip() for p in permissions_str.split(',')]
for perm in perm_list:
if hasattr(discord.Permissions, perm):
setattr(permissions, perm, True)
try:
role = await guild.create_role(name=role_name, color=color, permissions=permissions)
created_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour créer le rôle '{role_name}'.")
if created_count > 0:
await message.channel.send(f"{created_count} rôle(s) créé(s).")

View file

@ -0,0 +1,23 @@
import discord
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
# Fallback for single role
roles = [params]
guild = message.guild
moved_count = 0
for role_params in roles:
role_name = role_params.get('role_name')
position = role_params.get('position')
role = discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), guild.roles)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
continue
try:
await role.edit(position=position)
moved_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour déplacer le rôle '{role_name}'.")
if moved_count > 0:
await message.channel.send(f"{moved_count} rôle(s) déplacé(s).")

View file

@ -0,0 +1,23 @@
import discord
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
# Fallback for single role
roles = [params]
guild = message.guild
modified_count = 0
for role_params in roles:
role_name = role_params.get('role_name')
new_name = role_params.get('new_name')
role = discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), guild.roles)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
continue
try:
await role.edit(name=new_name)
modified_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour modifier le rôle '{role_name}'.")
if modified_count > 0:
await message.channel.send(f"{modified_count} rôle(s) modifié(s).")

View file

@ -0,0 +1,71 @@
import discord
async def execute(bot, params, message):
role_removals = params.get('role_removals', [])
if not role_removals:
# Fallback for single removal
role_removals = [params]
removed_count = 0
total_members = 0
for removal in role_removals:
target_mention = removal.get('target_user_mention')
role_name = removal.get('role_name')
role = discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), message.guild.roles)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
continue
# Check for bulk removal
if target_mention.lower() in ['@everyone', 'everyone', 'all', 'tous']:
total_members = 0
removed_count = 0
await message.channel.send(f"Retrait du rôle '{role.name}' de tous les membres... (Cela peut prendre du temps)")
for member in message.guild.members:
if member.bot:
continue # Skip bots
if role in member.roles:
try:
await member.remove_roles(role, reason=f"Rôle retiré par le Superviseur de tous les membres")
removed_count += 1
total_members += 1
if removed_count % 10 == 0: # Progress update every 10 removes
await message.channel.send(f"Progression: {removed_count} sur {total_members} membres...")
except discord.Forbidden:
await message.channel.send(f"Impossible de retirer le rôle de {member.mention} (permissions insuffisantes)")
continue
except Exception as e:
await message.channel.send(f"Erreur lors du retrait à {member.mention}: {str(e)}")
continue
await message.channel.send(f"Rôle '{role.name}' retiré à {removed_count} membres (bots exclus).")
continue
# Single user removal
if target_mention.startswith('<@') and target_mention.endswith('>'):
user_id = int(target_mention[2:-1])
elif target_mention.startswith('<@!') and target_mention.endswith('>'):
user_id = int(target_mention[3:-1])
elif target_mention.startswith('@'):
# Handle @username
username = target_mention[1:] # Remove @
member = discord.utils.find(lambda m: m.name == username or m.display_name == username, message.guild.members)
if not member:
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
continue
user_id = member.id
else:
try:
user_id = int(target_mention)
except ValueError:
await message.channel.send(f"Mention ou ID invalide: {target_mention}")
continue
member = message.guild.get_member(user_id)
if not member:
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
continue
try:
await member.remove_roles(role, reason=f"Rôle retiré par le Superviseur")
removed_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour retirer le rôle {role.name} de {member.mention}.")
if removed_count > 0 and not total_members:
await message.channel.send(f"{removed_count} rôle(s) retiré(s).")

View file

@ -0,0 +1,23 @@
import discord
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
# Fallback for single role
roles = [params]
guild = message.guild
renamed_count = 0
for role_params in roles:
role_name = role_params.get('role_name')
new_name = role_params.get('new_name')
role = discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), guild.roles)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
continue
try:
await role.edit(name=new_name)
renamed_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour renommer le rôle '{role_name}'.")
if renamed_count > 0:
await message.channel.send(f"{renamed_count} rôle(s) renommé(s).")

View file

@ -0,0 +1,22 @@
import discord
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
# Fallback for single role
roles = [params]
guild = message.guild
deleted_count = 0
for role_params in roles:
role_name = role_params.get('role_name')
role = discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), guild.roles)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
continue
try:
await role.delete(reason="Supprimé par le Superviseur")
deleted_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour supprimer le rôle '{role_name}'.")
if deleted_count > 0:
await message.channel.send(f"{deleted_count} rôle(s) supprimé(s).")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

42
commandes/salons/creer.py Normal file
View file

@ -0,0 +1,42 @@
import discord
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
# Fallback for single channel
channels = [params]
guild = message.guild
created_count = 0
for channel_params in channels:
channel_name = channel_params.get('channel_name')
category_name = channel_params.get('category_name')
channel_type = channel_params.get('channel_type', 'textuel')
if category_name:
# Recherche flexible : trouver une catégorie dont le nom contient le terme recherché
category = discord.utils.find(
lambda cat: category_name.casefold() in cat.name.casefold(),
guild.categories
)
if not category:
# Create the category if it doesn't exist
try:
category = await guild.create_category(category_name)
await message.channel.send(f"Catégorie '{category_name}' créée.")
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour créer la catégorie '{category_name}'.")
continue
else:
category = None
try:
if channel_type.casefold() == 'textuel':
channel = await guild.create_text_channel(channel_name, category=category)
elif channel_type.casefold() == 'vocal':
channel = await guild.create_voice_channel(channel_name, category=category)
else:
await message.channel.send(f"Type de salon invalide pour '{channel_name}'. Utilisez 'textuel' ou 'vocal'.")
continue
created_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour créer le salon '{channel_name}'.")
if created_count > 0:
await message.channel.send(f"{created_count} salon(s) créé(s).")

View file

@ -0,0 +1,27 @@
import discord
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
# Fallback for single channel
channels = [params]
guild = message.guild
moved_count = 0
for channel_params in channels:
channel_name = channel_params.get('channel_name')
new_category_name = channel_params.get('new_category_name')
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
continue
category = discord.utils.find(lambda cat: cat.name.casefold() == new_category_name.casefold(), guild.categories)
if not category:
await message.channel.send(f"Catégorie '{new_category_name}' introuvable.")
continue
try:
await channel.edit(category=category)
moved_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour déplacer le salon '{channel_name}'.")
if moved_count > 0:
await message.channel.send(f"{moved_count} salon(s) déplacé(s).")

View file

@ -0,0 +1,23 @@
import discord
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
# Fallback for single channel
channels = [params]
guild = message.guild
modified_count = 0
for channel_params in channels:
channel_name = channel_params.get('channel_name')
new_name = channel_params.get('new_name')
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
continue
try:
await channel.edit(name=new_name)
modified_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour modifier le salon '{channel_name}'.")
if modified_count > 0:
await message.channel.send(f"{modified_count} salon(s) modifié(s).")

27
commandes/salons/purge.py Normal file
View file

@ -0,0 +1,27 @@
import discord
async def execute(bot, params, message):
purges = params.get('purges', [])
if not purges:
await message.channel.send("Aucune purge spécifiée.")
return
for purge in purges:
channel_name = purge.get('channel_name')
amount = purge.get('amount', 0)
if not channel_name or amount <= 0 or amount > 1000:
await message.channel.send(f"Purge invalide: canal '{channel_name}', quantité {amount} (max 1000).")
continue
channel = discord.utils.find(lambda c: c.name == channel_name, message.guild.channels)
if not channel or not isinstance(channel, discord.TextChannel):
await message.channel.send(f"Canal '{channel_name}' introuvable ou n'est pas un canal texte.")
continue
try:
deleted = await channel.purge(limit=amount, reason=f"Purge par {message.author}")
await message.channel.send(f"Supprimé {len(deleted)} message(s) dans {channel.mention}.")
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour purger dans {channel.mention}.")
except Exception as e:
await message.channel.send(f"Erreur lors de la purge dans {channel.mention}: {str(e)}")

View file

@ -0,0 +1,23 @@
import discord
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
# Fallback for single channel
channels = [params]
guild = message.guild
renamed_count = 0
for channel_params in channels:
channel_name = channel_params.get('channel_name')
new_name = channel_params.get('new_name')
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
continue
try:
await channel.edit(name=new_name)
renamed_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour renommer le salon '{channel_name}'.")
if renamed_count > 0:
await message.channel.send(f"{renamed_count} salon(s) renommé(s).")

View file

@ -0,0 +1,22 @@
import discord
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
# Fallback for single channel
channels = [params]
guild = message.guild
deleted_count = 0
for channel_params in channels:
channel_name = channel_params.get('channel_name')
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
continue
try:
await channel.delete(reason="Supprimé par le Superviseur")
deleted_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour supprimer le salon '{channel_name}'.")
if deleted_count > 0:
await message.channel.send(f"{deleted_count} salon(s) supprimé(s).")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more