Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

Binary file not shown.

Binary file not shown.

View file

@ -41,13 +41,16 @@ class ActionLogger:
def log_message(self, level: str, message: str):
"""Log a general message."""
if level.casefold() == 'info':
if not level or not message:
return
level_lower = level.casefold()
if level_lower == 'info':
self.logger.info(message)
elif level.casefold() == 'warning':
elif level_lower == 'warning':
self.logger.warning(message)
elif level.casefold() == 'error':
elif level_lower == 'error':
self.logger.error(message)
elif level.casefold() == 'debug':
elif level_lower == 'debug':
self.logger.debug(message)
# Global logger instance

View file

@ -3,6 +3,9 @@ from .config import config
async def execute(bot, params, message):
channel_name = params.get('channel_name')
if not channel_name:
await message.channel.send("Veuillez spécifier un nom de salon.")
return
guild = message.guild
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:

View file

@ -3,6 +3,9 @@ from .config import config
async def execute(bot, params, message):
channel_name = params.get('channel_name')
if not channel_name:
await message.channel.send("Veuillez spécifier un nom de salon.")
return
guild = message.guild
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:

View file

@ -9,6 +9,9 @@ async def execute(bot, params, message):
created_count = 0
for category_params in categories:
category_name = category_params.get('category_name')
if not category_name:
await message.channel.send("Nom de catégorie requis pour la création.")
continue
try:
category = await guild.create_category(category_name)
created_count += 1

View file

@ -1,5 +1,22 @@
import discord
async def _find_category(guild, category_params):
"""Find a category by ID first, then by name (case-insensitive)."""
category_id = category_params.get('category_id')
if category_id:
try:
cat = guild.get_channel(int(category_id))
if cat and isinstance(cat, discord.CategoryChannel):
return cat
except (ValueError, TypeError):
pass
category_name = category_params.get('category_name')
if not category_name:
return None
return discord.utils.find(lambda cat: cat.name.casefold() == category_name.casefold(), guild.categories)
async def execute(bot, params, message):
categories = params.get('categories', [])
if not categories:
@ -10,14 +27,17 @@ async def execute(bot, params, message):
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)
category = await _find_category(guild, category_params)
if not category:
await message.channel.send(f"Catégorie '{category_name}' introuvable.")
label = category_name or category_params.get('category_id', 'inconnue')
await message.channel.send(f"Catégorie '{label}' 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}'.")
label = category_name or category_params.get('category_id', 'inconnue')
await message.channel.send(f"Je n'ai pas les permissions pour déplacer la catégorie '{label}'.")
if moved_count > 0:
await message.channel.send(f"{moved_count} catégorie(s) déplacée(s).")
await message.channel.send(f"{moved_count} catégorie(s) déplacée(s).")

View file

@ -1,5 +1,22 @@
import discord
async def _find_category(guild, category_params):
"""Find a category by ID first, then by name (case-insensitive)."""
category_id = category_params.get('category_id')
if category_id:
try:
cat = guild.get_channel(int(category_id))
if cat and isinstance(cat, discord.CategoryChannel):
return cat
except (ValueError, TypeError):
pass
category_name = category_params.get('category_name')
if not category_name:
return None
return discord.utils.find(lambda cat: cat.name.casefold() == category_name.casefold(), guild.categories)
async def execute(bot, params, message):
categories = params.get('categories', [])
if not categories:
@ -10,14 +27,17 @@ async def execute(bot, params, message):
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)
category = await _find_category(guild, category_params)
if not category:
await message.channel.send(f"Catégorie '{category_name}' introuvable.")
label = category_name or category_params.get('category_id', 'inconnue')
await message.channel.send(f"Catégorie '{label}' 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}'.")
label = category_name or category_params.get('category_id', 'inconnue')
await message.channel.send(f"Je n'ai pas les permissions pour renommer la catégorie '{label}'.")
if renamed_count > 0:
await message.channel.send(f"{renamed_count} catégorie(s) renommée(s).")
await message.channel.send(f"{renamed_count} catégorie(s) renommée(s).")

View file

@ -10,9 +10,22 @@ async def execute(bot, params, message):
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)
# Try by role_id first if available
role = None
role_id = assignment.get('role_id')
if role_id:
try:
role = message.guild.get_role(int(role_id))
except (ValueError, TypeError):
pass
if not role and 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.")
label = role_name or assignment.get('role_id', 'inconnu')
await message.channel.send(f"Rôle '{label}' introuvable.")
continue
# Check for bulk assignment

View file

@ -9,6 +9,9 @@ async def execute(bot, params, message):
created_count = 0
for role_params in roles:
role_name = role_params.get('role_name')
if not role_name:
await message.channel.send("Nom de rôle requis pour la création.")
continue
color = role_params.get('color', '#ffffff')
permissions_str = role_params.get('permissions', '')
# Parse color

View file

@ -1,5 +1,22 @@
import discord
async def _find_role(guild, role_params):
"""Find a role by ID first, then by name (case-insensitive)."""
role_id = role_params.get('role_id')
if role_id:
try:
r = guild.get_role(int(role_id))
if r:
return r
except (ValueError, TypeError):
pass
role_name = role_params.get('role_name')
if not role_name:
return None
return discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), guild.roles)
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
@ -10,14 +27,17 @@ async def execute(bot, params, message):
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)
role = await _find_role(guild, role_params)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
label = role_name or role_params.get('role_id', 'inconnu')
await message.channel.send(f"Rôle '{label}' 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}'.")
label = role_name or role_params.get('role_id', 'inconnu')
await message.channel.send(f"Je n'ai pas les permissions pour déplacer le rôle '{label}'.")
if moved_count > 0:
await message.channel.send(f"{moved_count} rôle(s) déplacé(s).")
await message.channel.send(f"{moved_count} rôle(s) déplacé(s).")

View file

@ -1,5 +1,22 @@
import discord
async def _find_role(guild, role_params):
"""Find a role by ID first, then by name (case-insensitive)."""
role_id = role_params.get('role_id')
if role_id:
try:
r = guild.get_role(int(role_id))
if r:
return r
except (ValueError, TypeError):
pass
role_name = role_params.get('role_name')
if not role_name:
return None
return discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), guild.roles)
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
@ -10,14 +27,17 @@ async def execute(bot, params, message):
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)
role = await _find_role(guild, role_params)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
label = role_name or role_params.get('role_id', 'inconnu')
await message.channel.send(f"Rôle '{label}' 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}'.")
label = role_name or role_params.get('role_id', 'inconnu')
await message.channel.send(f"Je n'ai pas les permissions pour modifier le rôle '{label}'.")
if modified_count > 0:
await message.channel.send(f"{modified_count} rôle(s) modifié(s).")
await message.channel.send(f"{modified_count} rôle(s) modifié(s).")

View file

@ -10,13 +10,26 @@ async def execute(bot, params, message):
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)
# Try by role_id first if available
role = None
role_id = removal.get('role_id')
if role_id:
try:
role = message.guild.get_role(int(role_id))
except (ValueError, TypeError):
pass
if not role and 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.")
label = role_name or removal.get('role_id', 'inconnu')
await message.channel.send(f"Rôle '{label}' introuvable.")
continue
# Check for bulk removal
if target_mention.lower() in ['@everyone', 'everyone', 'all', 'tous']:
if target_mention and 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)")
@ -39,6 +52,10 @@ async def execute(bot, params, message):
await message.channel.send(f"Rôle '{role.name}' retiré à {removed_count} membres (bots exclus).")
continue
if not target_mention:
await message.channel.send("Mention utilisateur requise.")
continue
# Single user removal
if target_mention.startswith('<@') and target_mention.endswith('>'):
user_id = int(target_mention[2:-1])
@ -68,4 +85,4 @@ async def execute(bot, params, message):
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).")
await message.channel.send(f"{removed_count} rôle(s) retiré(s).")

View file

@ -1,5 +1,22 @@
import discord
async def _find_role(guild, role_params):
"""Find a role by ID first, then by name (case-insensitive)."""
role_id = role_params.get('role_id')
if role_id:
try:
r = guild.get_role(int(role_id))
if r:
return r
except (ValueError, TypeError):
pass
role_name = role_params.get('role_name')
if not role_name:
return None
return discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), guild.roles)
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
@ -10,14 +27,17 @@ async def execute(bot, params, message):
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)
role = await _find_role(guild, role_params)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
label = role_name or role_params.get('role_id', 'inconnu')
await message.channel.send(f"Rôle '{label}' 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}'.")
label = role_name or role_params.get('role_id', 'inconnu')
await message.channel.send(f"Je n'ai pas les permissions pour renommer le rôle '{label}'.")
if renamed_count > 0:
await message.channel.send(f"{renamed_count} rôle(s) renommé(s).")
await message.channel.send(f"{renamed_count} rôle(s) renommé(s).")

View file

@ -1,5 +1,22 @@
import discord
async def _find_role(guild, role_params):
"""Find a role by ID first, then by name (case-insensitive)."""
role_id = role_params.get('role_id')
if role_id:
try:
r = guild.get_role(int(role_id))
if r:
return r
except (ValueError, TypeError):
pass
role_name = role_params.get('role_name')
if not role_name:
return None
return discord.utils.find(lambda r: r.name.casefold() == role_name.casefold(), guild.roles)
async def execute(bot, params, message):
roles = params.get('roles', [])
if not roles:
@ -9,14 +26,17 @@ async def execute(bot, params, message):
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)
role = await _find_role(guild, role_params)
if not role:
await message.channel.send(f"Rôle '{role_name}' introuvable.")
label = role_name or role_params.get('role_id', 'inconnu')
await message.channel.send(f"Rôle '{label}' 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}'.")
label = role_name or role_params.get('role_id', 'inconnu')
await message.channel.send(f"Je n'ai pas les permissions pour supprimer le rôle '{label}'.")
if deleted_count > 0:
await message.channel.send(f"{deleted_count} rôle(s) supprimé(s).")
await message.channel.send(f"{deleted_count} rôle(s) supprimé(s).")

View file

@ -9,8 +9,14 @@ async def execute(bot, params, message):
created_count = 0
for channel_params in channels:
channel_name = channel_params.get('channel_name')
if not channel_name:
await message.channel.send("Nom de salon requis pour la création.")
continue
category_name = channel_params.get('category_name')
channel_type = channel_params.get('channel_type', 'textuel')
channel_type = (channel_params.get('channel_type') or 'textuel').casefold()
category = None
if category_name:
# Recherche flexible : trouver une catégorie dont le nom contient le terme recherché
category = discord.utils.find(
@ -25,12 +31,11 @@ async def execute(bot, params, message):
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':
if channel_type == 'textuel':
channel = await guild.create_text_channel(channel_name, category=category)
elif channel_type.casefold() == 'vocal':
elif channel_type == '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'.")
@ -39,4 +44,4 @@ async def execute(bot, params, message):
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).")
await message.channel.send(f"{created_count} salon(s) créé(s).")

View file

@ -1,5 +1,40 @@
import discord
async def _find_channel(guild, channel_params):
"""Find a channel by ID first, then by name (case-insensitive)."""
channel_id = channel_params.get('channel_id')
if channel_id:
try:
ch = guild.get_channel(int(channel_id))
if ch:
return ch
except (ValueError, TypeError):
pass
channel_name = channel_params.get('channel_name')
if not channel_name:
return None
return discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
async def _find_category(guild, category_params):
"""Find a category by ID first, then by name (case-insensitive)."""
# Support both 'category_id' and 'new_category_id'
category_id = category_params.get('new_category_id') or category_params.get('category_id')
if category_id:
try:
cat = guild.get_channel(int(category_id))
if cat and isinstance(cat, discord.CategoryChannel):
return cat
except (ValueError, TypeError):
pass
category_name = category_params.get('new_category_name') or category_params.get('category_name')
if not category_name:
return None
return discord.utils.find(lambda cat: cat.name.casefold() == category_name.casefold(), guild.categories)
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
@ -10,18 +45,26 @@ async def execute(bot, params, message):
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)
channel = await _find_channel(guild, channel_params)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
label = channel_name or channel_params.get('channel_id', 'inconnu')
await message.channel.send(f"Salon '{label}' introuvable.")
continue
category = discord.utils.find(lambda cat: cat.name.casefold() == new_category_name.casefold(), guild.categories)
category = await _find_category(guild, channel_params)
if not category:
await message.channel.send(f"Catégorie '{new_category_name}' introuvable.")
continue
has_category_param = any(k in channel_params for k in ['new_category_id', 'new_category_name', 'category_id', 'category_name'])
if has_category_param:
label = new_category_name or channel_params.get('new_category_id') or channel_params.get('category_id', 'inconnue')
await message.channel.send(f"Catégorie '{label}' 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}'.")
label = channel_name or channel_params.get('channel_id', 'inconnu')
await message.channel.send(f"Je n'ai pas les permissions pour déplacer le salon '{label}'.")
if moved_count > 0:
await message.channel.send(f"{moved_count} salon(s) déplacé(s).")
await message.channel.send(f"{moved_count} salon(s) déplacé(s).")

View file

@ -1,5 +1,39 @@
import discord
async def _find_channel(guild, channel_params):
"""Find a channel by ID first, then by name (case-insensitive)."""
channel_id = channel_params.get('channel_id')
if channel_id:
try:
ch = guild.get_channel(int(channel_id))
if ch:
return ch
except (ValueError, TypeError):
pass
channel_name = channel_params.get('channel_name')
if not channel_name:
return None
return discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
async def _find_category(guild, category_params):
"""Find a category by ID first, then by name (case-insensitive)."""
category_id = category_params.get('new_category_id') or category_params.get('category_id')
if category_id:
try:
cat = guild.get_channel(int(category_id))
if cat and isinstance(cat, discord.CategoryChannel):
return cat
except (ValueError, TypeError):
pass
category_name = category_params.get('new_category_name') or category_params.get('category_name')
if not category_name:
return None
return discord.utils.find(lambda cat: cat.name.casefold() == category_name.casefold(), guild.categories)
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
@ -9,15 +43,43 @@ async def execute(bot, params, message):
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)
# Support both 'new_name' and 'new_channel_name' variants
new_name = channel_params.get('new_name') or channel_params.get('new_channel_name')
new_category_id = channel_params.get('new_category_id')
new_category_name = channel_params.get('new_category_name')
channel = await _find_channel(guild, channel_params)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
label = channel_name or channel_params.get('channel_id', 'inconnu')
await message.channel.send(f"Salon '{label}' introuvable.")
continue
# Build edit kwargs based on what params are provided
edit_kwargs = {}
if new_name:
edit_kwargs['name'] = new_name
if new_category_id or new_category_name:
category = await _find_category(guild, channel_params)
if category:
edit_kwargs['category'] = category
else:
cat_label = new_category_name or new_category_id or 'inconnue'
await message.channel.send(f"Catégorie '{cat_label}' introuvable.")
continue
if not edit_kwargs:
await message.channel.send("Aucune modification à appliquer (ni nom, ni catégorie spécifiés).")
continue
try:
await channel.edit(name=new_name)
await channel.edit(**edit_kwargs)
modified_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour modifier le salon '{channel_name}'.")
label = channel_name or channel_params.get('channel_id', 'inconnu')
await message.channel.send(f"Je n'ai pas les permissions pour modifier le salon '{label}'.")
except Exception as e:
label = channel_name or channel_params.get('channel_id', 'inconnu')
await message.channel.send(f"Erreur lors de la modification du salon '{label}': {e}")
if modified_count > 0:
await message.channel.send(f"{modified_count} salon(s) modifié(s).")
await message.channel.send(f"{modified_count} salon(s) modifié(s).")

View file

@ -1,5 +1,22 @@
import discord
async def _find_channel(guild, channel_params):
"""Find a channel by ID first, then by name (case-insensitive)."""
channel_id = channel_params.get('channel_id')
if channel_id:
try:
ch = guild.get_channel(int(channel_id))
if ch:
return ch
except (ValueError, TypeError):
pass
channel_name = channel_params.get('channel_name')
if not channel_name:
return None
return discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
async def execute(bot, params, message):
channels = params.get('channels', [])
if not channels:
@ -9,15 +26,27 @@ async def execute(bot, params, message):
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)
# Support both 'new_name' and 'new_channel_name' variants
new_name = channel_params.get('new_name') or channel_params.get('new_channel_name')
channel = await _find_channel(guild, channel_params)
if not channel:
await message.channel.send(f"Salon '{channel_name}' introuvable.")
label = channel_name or channel_params.get('channel_id', 'inconnu')
await message.channel.send(f"Salon '{label}' introuvable.")
continue
if not new_name:
await message.channel.send("Aucun nouveau nom spécifié pour le renommage.")
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}'.")
label = channel_name or channel_params.get('channel_id', 'inconnu')
await message.channel.send(f"Je n'ai pas les permissions pour renommer le salon '{label}'.")
except Exception as e:
label = channel_name or channel_params.get('channel_id', 'inconnu')
await message.channel.send(f"Erreur lors du renommage du salon '{label}': {e}")
if renamed_count > 0:
await message.channel.send(f"{renamed_count} salon(s) renommé(s).")
await message.channel.send(f"{renamed_count} salon(s) renommé(s).")

View file

@ -1,198 +0,0 @@
"""
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 = 'data/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