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

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