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.

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

23
commandes/security/ban.py Normal file
View file

@ -0,0 +1,23 @@
import discord
from commandes.autres.utils import parse_user_mention
async def execute(bot, params, message):
users = params.get('users', [])
if not users:
users = [params]
banned_count = 0
for user_params in users:
target_mention = user_params.get('target_user_mention')
user_id, member = await parse_user_mention(bot, target_mention, message)
if user_id is None:
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
continue
try:
await message.guild.ban(discord.Object(id=user_id), reason="Bannissement par le Superviseur")
banned_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour bannir l'utilisateur avec ID {user_id}.")
except discord.HTTPException as e:
await message.channel.send(f"Erreur lors du bannissement de l'utilisateur avec ID {user_id}: {e}")
if banned_count > 0:
await message.channel.send(f"{banned_count} utilisateur(s) banni(s).")

View file

@ -0,0 +1,24 @@
import discord
from commandes.autres.utils import parse_user_mention
async def execute(bot, params, message):
users = params.get('users', [])
if not users:
users = [params]
kicked_count = 0
for user_params in users:
target_mention = user_params.get('target_user_mention')
user_id, member = await parse_user_mention(bot, target_mention, message)
if user_id is None:
await message.channel.send(f"Utilisateur {target_mention} introuvable.")
continue
if not member:
await message.channel.send(f"Utilisateur {target_mention} n'est pas dans ce serveur.")
continue
try:
await member.kick(reason="Expulsion par le Superviseur")
kicked_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour expulser {member.mention}.")
if kicked_count > 0:
await message.channel.send(f"{kicked_count} utilisateur(s) expulsé(s).")

View file

@ -0,0 +1,35 @@
import discord
import json
import os
WARNINGS_FILE = 'warnings.json'
def load_warnings():
if os.path.exists(WARNINGS_FILE):
with open(WARNINGS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
async def execute(bot, params, message):
all_warnings = load_warnings()
guild_id = str(message.guild.id)
if guild_id not in all_warnings:
await message.channel.send("Aucune avertissement enregistré pour ce serveur.")
return
embed = discord.Embed(
title="Avertissements du serveur",
description="Liste des utilisateurs ayant des avertissements :",
color=discord.Color.orange()
)
for user_id, warnings in all_warnings[guild_id].items():
member = message.guild.get_member(int(user_id))
name = member.display_name if member else f"Utilisateur {user_id}"
embed.add_field(
name=f"{name} ({len(warnings)} avertissements)",
value="Dernier : " + warnings[-1]['reason'][:100] + "..." if len(warnings[-1]['reason']) > 100 else warnings[-1]['reason'],
inline=False
)
await message.channel.send(embed=embed)

View file

@ -0,0 +1,78 @@
import discord
from discord.ext import commands
import asyncio
import re
from ..autres.config import config
async def execute(bot, params, message):
users = params.get('users', [])
if not users:
# Fallback for single user
users = [params]
# Get mute role from config or default to 'muted'
mute_role_name = config.get_mute_role(message.guild.id) or 'muted'
mute_role = discord.utils.get(message.guild.roles, name=mute_role_name)
if not mute_role:
await message.channel.send(f"Rôle '{mute_role_name}' introuvable. Veuillez le créer ou le configurer.")
return
muted_count = 0
failed_count = 0
for user_params in users:
target_mention = user_params.get('target_user_mention')
duration_str = user_params.get('duration', 10) # default 10 minutes
# Parse duration to extract numeric value
if isinstance(duration_str, str):
match = re.search(r'(\d+)', duration_str)
if match:
duration = int(match.group(1))
else:
duration = 10 # fallback to default
else:
duration = int(duration_str) if duration_str else 10
# Parse mention or ID
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.")
failed_count += 1
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}")
failed_count += 1
continue
member = message.guild.get_member(user_id)
if not member:
await message.channel.send(f"Utilisateur introuvable dans le serveur: {target_mention}")
failed_count += 1
continue
try:
await member.add_roles(mute_role, reason=f"Muté par le Superviseur pour {duration} minutes")
muted_count += 1
# Schedule unmute
asyncio.create_task(unmute_after_delay(member, mute_role, duration * 60)) # duration in minutes to seconds
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour muter {member.mention}.")
failed_count += 1
if muted_count > 0:
await message.channel.send(f"{muted_count} utilisateur(s) muté(s) pour {duration} {'minute' if duration == 1 else 'minutes'}.")
async def unmute_after_delay(member, mute_role, delay_seconds):
await asyncio.sleep(delay_seconds)
try:
await member.remove_roles(mute_role, reason="Fin du mute automatique")
except discord.Forbidden:
# Log or handle permission error, but don't send message as it's async
pass
except discord.HTTPException:
# Member might have left or role removed
pass

View file

@ -0,0 +1,32 @@
import discord
async def execute(bot, params, message):
purges = params.get('purges', [])
if not purges:
# Fallback for single purge
purges = [params]
total_deleted = 0
for purge_params in purges:
channel_name = purge_params.get('channel_name')
channel_id = purge_params.get('channel_id')
amount = purge_params.get('amount', 10)
guild = message.guild
channel = None
if channel_id:
try:
channel = guild.get_channel(int(channel_id))
except ValueError:
pass
elif channel_name:
channel = discord.utils.find(lambda c: c.name.casefold() == channel_name.casefold(), guild.channels)
if not channel:
identifier = channel_id or channel_name or 'inconnu'
await message.channel.send(f"Salon '{identifier}' introuvable.")
continue
try:
deleted = await channel.purge(limit=amount)
total_deleted += len(deleted)
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour purger {channel.mention}.")
if total_deleted > 0:
await message.channel.send(f"{total_deleted} messages supprimés au total.")

View file

@ -0,0 +1,42 @@
import discord
async def execute(bot, params, message):
users = params.get('users', [])
if not users:
# Fallback for single user
users = [params]
timed_out_count = 0
for user_params in users:
target_mention = user_params.get('target_user_mention')
duration = user_params.get('duration', 10) # default 10 minutes
# Parse mention or ID
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:
# Timeout for duration minutes
await member.timeout(discord.utils.utcnow() + discord.timedelta(minutes=duration), reason=f"Timeout par le Superviseur pour {duration} minutes")
timed_out_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour mettre en timeout {member.mention}.")
if timed_out_count > 0:
await message.channel.send(f"{timed_out_count} utilisateur(s) mis en timeout pour {duration} {'minute' if duration == 1 else 'minutes'}.")

View file

@ -0,0 +1,78 @@
import discord
async def execute(bot, params, message):
users = params.get('users', [])
if not users:
# Fallback for single user
users = [params]
unbanned_count = 0
for user_params in users:
target_mention = user_params.get('target_user_mention')
user_id = None
# 1. Try strict ID parsing first
try:
user_id = int(target_mention)
except ValueError:
pass
# 2. Try Mention parsing (specific to generic)
if user_id is None:
if target_mention.startswith('<@!') and target_mention.endswith('>'):
try:
user_id = int(target_mention[3:-1])
except ValueError:
pass
elif target_mention.startswith('<@') and target_mention.endswith('>'):
try:
user_id = int(target_mention[2:-1])
except ValueError:
pass
user = None
if user_id:
try:
user = bot.get_user(user_id) or await bot.fetch_user(user_id)
except discord.NotFound:
pass
except discord.HTTPException:
pass
# 3. If no ID match, search by name in ban list
if not user:
try:
# Fetch all bans to search by name
bans = [entry async for entry in message.guild.bans()]
target_lower = target_mention.lower()
target_clean = target_lower[1:] if target_lower.startswith('@') else target_lower
for ban_entry in bans:
u = ban_entry.user
# Check: name, global_name, name#discrim
u_global = (u.global_name or "").lower()
u_name = u.name.lower()
if (u_name == target_lower or
u_name == target_clean or
u_global == target_lower or
u_global == target_clean or
f"{u_name}#{u.discriminator}" == target_lower):
user = u
break
except Exception as e:
print(f"Error searching bans: {e}")
if not user:
await message.channel.send(f"Utilisateur {target_mention} introuvable (ni ID, ni Mention, ni Nom dans les bannis).")
continue
try:
await message.guild.unban(user, reason="Débannissement par le Superviseur")
unbanned_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour débannir {user.mention}.")
except discord.NotFound:
await message.channel.send(f"{user.mention} n'est pas banni.")
if unbanned_count > 0:
await message.channel.send(f"{unbanned_count} utilisateur(s) débanni(s).")

View file

@ -0,0 +1,79 @@
import discord
from discord.ext import commands
import asyncio
from ..autres.config import config
async def execute(bot, params, message):
users = params.get('users', [])
if not users:
# Fallback for single user
users = [params]
# Get mute role from config or default to 'muted'
mute_role_name = config.get_mute_role(message.guild.id) or 'muted'
mute_role = discord.utils.get(message.guild.roles, name=mute_role_name)
if not mute_role:
await message.channel.send(f"Rôle '{mute_role_name}' introuvable. Impossible de démuter.")
return
unmuted_count = 0
failed_count = 0
for user_params in users:
target_mention = user_params.get('target_user_mention')
user_id = None
# Parse mention or ID
if target_mention.startswith('<@') and target_mention.endswith('>'):
try:
# Handle <@123> and <@!123>
clean_id = target_mention[2:-1]
if clean_id.startswith('!'):
clean_id = clean_id[1:]
user_id = int(clean_id)
except ValueError:
pass
elif target_mention.startswith('@'):
# Handle @username
username = target_mention[1:]
member = discord.utils.find(lambda m: m.name == username or m.display_name == username, message.guild.members)
if member:
user_id = member.id
else:
try:
user_id = int(target_mention)
except ValueError:
pass
if not user_id:
# Try finding member by name if ID parsing failed
username = target_mention.lstrip('@')
member = discord.utils.find(lambda m: m.name == username or m.display_name == username, message.guild.members)
if member:
user_id = member.id
if not user_id:
await message.channel.send(f"Mention ou ID invalide: {target_mention}")
failed_count += 1
continue
member = message.guild.get_member(user_id)
if not member:
await message.channel.send(f"Utilisateur introuvable dans le serveur: {target_mention}")
failed_count += 1
continue
if mute_role not in member.roles:
await message.channel.send(f"{member.mention} n'est pas muté.")
continue
try:
await member.remove_roles(mute_role, reason="Démuté par le Superviseur")
unmuted_count += 1
except discord.Forbidden:
await message.channel.send(f"Je n'ai pas les permissions pour démuter {member.mention}.")
failed_count += 1
if unmuted_count > 0:
await message.channel.send(f"{unmuted_count} utilisateur(s) démuté(s).")

196
commandes/security/warn.py Normal file
View file

@ -0,0 +1,196 @@
import discord
import json
import os
import aiohttp
from datetime import datetime
WARNINGS_FILE = 'warnings.json'
def load_warnings():
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):
with open(WARNINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(warnings, f, indent=4, ensure_ascii=False)
async def rephrase_reason(original_reason, api_url, api_key, model, timeout, temperature):
try:
payload = {
"model": model,
"prompt": f"Reformule la raison suivante d'avertissement en français de manière polie et claire :\n{original_reason}\nRéformulation :",
"stream": False,
"options": {"temperature": temperature}
}
headers = {}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
async with aiohttp.ClientSession() as session:
async with session.post(api_url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=30)) as response:
response.raise_for_status()
try:
data = await response.json()
raw_response = data.get('response', '').strip()
# If the response is JSON (some models do that), try to parse it
try:
json_resp = json.loads(raw_response)
if isinstance(json_resp, dict) and 'response' in json_resp:
rephrased = json_resp['response'].strip()
else:
rephrased = raw_response
except json.JSONDecodeError:
rephrased = raw_response
except Exception:
# If not JSON, try to get text
content = await response.text()
try:
data = json.loads(content)
raw_response = data.get('response', '').strip()
try:
json_resp = json.loads(raw_response)
rephrased = json_resp.get('response', raw_response) if isinstance(json_resp, dict) else raw_response
except json.JSONDecodeError:
rephrased = raw_response
except json.JSONDecodeError:
rephrased = content.strip()
return rephrased if rephrased else None
except Exception as e:
print(f"Erreur lors de la reformulation : {e}")
return None
async def generate_dm_message(original_reason, warning_count, api_url, api_key, model, timeout, temperature):
try:
payload = {
"model": model,
"prompt": f"Écris seulement le message d'avertissement poli et concis pour un user en DM, sans ajouter de métadonnées ou de JSON. Le message doit commencer par 'Bonjour,' et finir par 'Cordialement, le Superviseur'. Raison originale : {original_reason}, nombre d'avertissements : {warning_count}.",
"stream": False,
"options": {"temperature": temperature}
}
headers = {}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
async with aiohttp.ClientSession() as session:
async with session.post(api_url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=30)) as response:
response.raise_for_status()
try:
data = await response.json()
raw_message = data.get('response', '').strip()
# If the response is JSON (some models do that), try to parse it
try:
json_resp = json.loads(raw_message)
if isinstance(json_resp, dict):
final_message = json_resp.get('generated_message') or json_resp.get('response', raw_message)
else:
final_message = raw_message
except json.JSONDecodeError:
final_message = raw_message
except Exception:
# If not JSON, try to get text
content = await response.text()
try:
data = json.loads(content)
raw_message = data.get('response', '').strip()
try:
json_resp = json.loads(raw_message)
final_message = json_resp.get('generated_message') or json_resp.get('response', raw_message) if isinstance(json_resp, dict) else raw_message
except json.JSONDecodeError:
final_message = raw_message
except json.JSONDecodeError:
final_message = content.strip()
# Ensure message is not too long (Discord DM limit is 2000 chars)
if len(final_message) > 1950:
final_message = final_message[:1950] + "..."
return final_message if final_message else None
except Exception as e:
print(f"Erreur lors de la génération du message DM : {e}")
return None
async def execute(bot, params, message):
warnings_list = params.get('warnings', []) # warnings is param name
if not warnings_list:
# Fallback for single warning
warnings_list = [params]
warned_count = 0
all_warnings = load_warnings()
guild_id = str(message.guild.id)
if guild_id not in all_warnings:
all_warnings[guild_id] = {}
for warn_params in warnings_list:
target_mention = warn_params.get('target_user_mention')
original_reason = warn_params.get('reason', 'Aucune raison spécifiée')
# Rephrase the reason using Ollama
rephrased_reason = await rephrase_reason(original_reason, bot.ollama_api_url, bot.ollama_api_key, bot.ollama_model, bot.ollama_timeout, bot.ollama_temperature)
reason = rephrased_reason if rephrased_reason else original_reason
# Parse mention or ID
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
# Add warning to record
user_id_str = str(user_id)
if user_id_str not in all_warnings[guild_id]:
all_warnings[guild_id][user_id_str] = []
all_warnings[guild_id][user_id_str].append({
'reason': reason,
'timestamp': datetime.now().isoformat(),
'issued_by': str(message.author.id)
})
save_warnings(all_warnings)
warning_count = len(all_warnings[guild_id][user_id_str])
# Send warning message in channel with count
embed = discord.Embed(
title=f"Avertissement #{warning_count}",
description=f"{member.mention}, {reason}\n"
f"Nombre total d'avertissements : {warning_count}",
color=discord.Color.orange()
)
await message.channel.send(embed=embed)
# Generate full DM message using AI with original reason for polite reformulation
dm_message = await generate_dm_message(original_reason, warning_count, bot.ollama_api_url, bot.ollama_api_key, bot.ollama_model, bot.ollama_timeout, bot.ollama_temperature)
dm_content = dm_message if dm_message else f"Avertissement : {reason}. Nombre total : {warning_count}."
# Send DM to user
try:
await member.send(dm_content)
warned_count += 1
except discord.Forbidden:
await message.channel.send(f"Impossible d'envoyer un DM à {member.mention}, avertissement public.")
warned_count += 1 # Still count as warned
# If 3+ warnings, auto-ban
if warning_count >= 3:
try:
await member.ban(reason=f"3 avertissements accumulés. Dernière raison: {reason}")
embed.add_field(name="Action automatique", value="Utilisateur banni pour accumulation d'avertissements.", inline=False)
await message.channel.send(embed=embed)
await message.channel.send(f"{member.mention} a été banni pour accumulation d'avertissements.")
continue # Skip further processing for this user
except discord.Forbidden:
embed.add_field(name="Action suggérée", value="Utilisateur a 3+ avertissements, mais bot n'a pas les permissions pour bannir.", inline=False)
await message.channel.send(embed=embed)
except discord.HTTPException as e:
embed.add_field(name="Erreur de bannissement", value=f"Impossible de bannir: {e}", inline=False)
await message.channel.send(embed=embed)
save_warnings(all_warnings) # Save at end
if warned_count > 0:
await message.channel.send(f"{warned_count} utilisateur(s) averti(s).")