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

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.

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,198 @@
"""
Module de modération automatique pour Superviseur Bot
Gère la détection et sanction des messages inappropriés automatiquement sur tous les messages
"""
import discord
import aiohttp
import json
import os
from datetime import datetime
WARNINGS_FILE = 'warnings.json'
def load_warnings():
"""Charge les avertissements depuis le fichier JSON"""
if os.path.exists(WARNINGS_FILE):
with open(WARNINGS_FILE, 'r', encoding='utf-8') as f:
content = f.read().strip()
if content:
return json.loads(content)
return {}
def save_warnings(warnings):
"""Sauvegarde les avertissements dans le fichier JSON"""
with open(WARNINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(warnings, f, indent=4, ensure_ascii=False)
async def check_message_content_global(bot, content):
"""
Vérifie le contenu d'un message pour tout contenu inapproprié
Utilise une liste locale de mots inappropriés pour performance optimale
"""
# Liste optimisée de mots inappropriés (mots-clés critiques seulement pour performance)
bad_words = {
# Mots critiques (haute sévérité)
'nigger', 'nigga', 'pédé', 'bougnoul', 'viol', 'pédophile', 'inceste', 'naziste', 'hitler',
'suicide', 'se tuer', 'se suicider', 'tuer', 'assassiner', 'meurtre',
# Gros mots courants
'fuck', 'shit', 'asshole', 'bitch', 'dick', 'pussy', 'cunt', 'motherfucker',
'merde', 'putain', 'salope', 'connard', 'enculé', 'bordel', 'emmerde',
'ta gueule', 'va te faire foutre', 'va te faire enculer', 'va chier', 'crève',
'branleur', 'sucer', 'pipe', 'pétasse', 'garce', 'traînée', 'salopard', 'ordure',
'fumier', 'pourri', 'dégueulasse', 'gueule', 'gueuler',
# Insultes
'con', 'abruti', 'débile', 'crétin', 'imbécile', 'loser', 'nullos', 'naze',
'ridicule', 'pathétique', 'minable', 'nul', 'inutile', 'incapable', 'bon à rien',
# NSFW/sexuel
'sexe', 'porn', 'porno', 'bite', 'seins', 'sodomie', 'agression sexuelle',
'harceleur', 'stalker',
# Discrimination
'raciste', 'antisémite', 'fasciste', 'gay', 'homo', 'féminin',
# Drogues
'drogue', 'cocaine', 'héroine', 'cannabis', 'ecstasy', 'lsd', 'crack', 'speed',
'amphétamine', 'steroide', 'anabolisant',
# Autres
'cancer', 'maladie grave', 'crever', 'mourir'
}
content_lower = content.lower()
detected_bad_words = []
# Recherche rapide avec set pour performance
for bad_word in bad_words:
bad_word_lower = bad_word.lower()
# Recherche insensible à la casse avec délimiteurs pour éviter faux positifs
if f" {bad_word_lower} " in f" {content_lower} " or content_lower == bad_word_lower:
detected_bad_words.append(bad_word)
# Continue pour lister tous les mots détectés, mais limite à 3 pour performance
if len(detected_bad_words) >= 3:
break
if detected_bad_words:
severity = "high" if any(word in ['nigger', 'nigga', 'pédé', 'bougnoul', 'viol', 'pédophile', 'inceste', 'naziste', 'hitler', 'suicide', 'se tuer', 'tuer', 'assassiner', 'meurtre'] for word in detected_bad_words) else "medium"
return {
"inappropriate": True,
"severity": severity,
"reason": f"Mots inappropriés détectés: {', '.join(detected_bad_words[:3])}",
"bad_words": detected_bad_words[:3]
}
return {"inappropriate": False}
async def warn_and_check_ban_global(message, reason, message_deleted=False):
"""
Émet un avertissement et vérifie si l'utilisateur doit être banni (3+ avertissements)
"""
guild_id = str(message.guild.id)
all_warnings = load_warnings()
if guild_id not in all_warnings:
all_warnings[guild_id] = {}
user_id_str = str(message.author.id)
if user_id_str not in all_warnings[guild_id]:
all_warnings[guild_id][user_id_str] = []
# Ajouter l'avertissement
all_warnings[guild_id][user_id_str].append({
'reason': reason,
'timestamp': datetime.now().isoformat(),
'issued_by': 'AUTOMATIC_MODERATION'
})
warning_count = len(all_warnings[guild_id][user_id_str])
save_warnings(all_warnings)
from commandes.autres.logger import action_logger
action_logger.log_action('AUTOMATIC_MODERATION', str(message.author), str(message.guild), {
'reason': reason,
'warning_count': warning_count,
'original_message': message.content
}, True)
# Vérifier si bannissement automatique nécessaire
if warning_count >= 3:
try:
await message.author.ban(reason=f"Bannissement automatique: 3 avertissements accumulés. Dernière raison: {reason}")
embed = discord.Embed(
title="🚫 Bannissement automatique",
description=f"{message.author.mention}, vous avez été banni pour accumulation d'avertissements (3+ avertissements).\n**Dernière raison:** {reason}",
color=discord.Color.red()
)
embed.set_footer(text=f"Total d'avertissements: {warning_count}")
await message.channel.send(embed=embed)
return True # Utilisateur banni
except discord.Forbidden:
embed = discord.Embed(
title="Ban requis",
description=f"❌ {message.author.mention} a 3+ avertissements, mais le bot n'a pas les permissions de bannir.\n**Agissez manuellement!**",
color=discord.Color.red()
)
embed.set_footer(text=f"Total d'avertissements: {warning_count}")
await message.channel.send(embed=embed)
return False
else:
# Envoyer message d'avertissement
deletion_text = "Votre message a été supprimé pour contenu inapproprié." if message_deleted else "Mots inappropriés détectés dans votre message."
warning_message = f"⚠️ **Avertissement automatique** {message.author.mention}\n{deletion_text}\n**Raison:** {reason}\n**Avertissements totaux:** {warning_count}\n**Avant bannissement:** {3 - warning_count} avertissement(s)\n*Modération automatique par IA*"
await message.channel.send(warning_message)
return False
async def moderate_message(bot, message):
"""
Fonction principale de modération automatique d'un message
Retourne True si le message a été modéré (et donc supprimé), False sinon
"""
# Préparer le contenu à analyser
content_to_check = message.content
if message.attachments:
for attachment in message.attachments:
if attachment.content_type and attachment.content_type.startswith('image/'):
if not content_to_check.strip():
content_to_check = f"[Image: {attachment.filename}]"
else:
content_to_check += f" [Image: {attachment.filename}]"
# Analyser le contenu
if content_to_check.strip():
print(f"[MODERATION] Analysing message from {message.author}: '{content_to_check}'")
moderation_result = await check_message_content_global(bot, content_to_check)
print(f"[MODERATION] Result: {moderation_result}")
bad_words = moderation_result.get('bad_words', [])
inappropriate = moderation_result.get('inappropriate', False)
print(f"[MODERATION] bad_words: {bad_words}, inappropriate: {inappropriate}")
# Toujours donner un avertissement si des mots inappropriés sont détectés
if bad_words:
reason = f"Mots inappropriés détectés: {', '.join(bad_words[:5])}"
print(f"[MODERATION] Processing bad words, reason: {reason}")
if inappropriate:
print(f"[MODERATION] Message inappropriate, deleting...")
try:
await message.delete()
print(f"[MODERATION] Message deleted successfully")
except discord.Forbidden:
print(f"[MODERATION] Forbidden to delete message")
pass # Impossible de supprimer
except Exception as e:
print(f"[MODERATION] Error deleting message: {e}")
await warn_and_check_ban_global(message, reason, message_deleted=True)
print(f"[MODERATION] Warning sent and ban checked, returning True")
return True # Message modéré
else:
print(f"[MODERATION] Not inappropriate, sending warning without deletion")
await warn_and_check_ban_global(message, reason, message_deleted=False)
return False # Message autorisé
# Test function pour vérifier la fonctionnalité
async def test_auto_moderation():
"""Fonction de test pour vérifier que les avertissements sont toujours donnés"""
print("Test auto-moderation: vérification que les avertissements sont donnés pour les mots inappropriés")
# Cette fonction peut être appelée pour tester manuellement si nécessaire

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