Fix système arriver départ V1

This commit is contained in:
Mathis 2026-05-25 19:08:45 +02:00
parent 01dd4979d8
commit d9afa32828
18 changed files with 1367 additions and 291 deletions

View file

@ -13,8 +13,9 @@ kuby_logger = logging.getLogger("KubyBot")
class Welcome(commands.Cog):
def __init__(self, bot):
self.bot = bot
# Base de données à la racine
self.db_path = os.path.join(os.path.dirname(__file__), "..", "config.db")
# Base de données à la racine - Chemin absolu
self.base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
self.db_path = os.path.join(self.base_dir, "config.db")
self._init_db()
def _init_db(self):
@ -56,16 +57,18 @@ class Welcome(commands.Cog):
conn.commit()
conn.close()
async def create_welcome_image(self, username, avatar_url, server_name=None, banner_background=None):
current_dir = os.path.dirname(__file__)
base_dir = os.path.abspath(os.path.join(current_dir, ".."))
async def create_welcome_image(self, username, avatar_url, server_name=None, banner_background=None, custom_message=None):
# Utiliser base_dir déjà calculé dans __init__
try:
# Télécharger l'avatar
async with aiohttp.ClientSession() as session:
async with session.get(avatar_url) as resp:
if resp.status != 200:
kuby_logger.error(f"[Welcome] Impossible de télécharger l'avatar: {resp.status}")
raise ValueError(f"Impossible de télécharger l'avatar (code: {resp.status})")
avatar_bytes = await resp.read()
# Utiliser le fond personnalisé ou celui par défaut
# Vérifier et utiliser le fond personnalisé ou celui par défaut
bg_path = None
if banner_background:
# Si c'est un chemin absolu (pour compatibilité temporaire)
@ -73,40 +76,97 @@ class Welcome(commands.Cog):
bg_path = banner_background
else:
# Sinon on cherche dans le dossier banners/
potential_path = os.path.join(base_dir, "banners", banner_background)
potential_path = os.path.join(self.base_dir, "banners", banner_background)
if os.path.exists(potential_path):
bg_path = potential_path
kuby_logger.debug(f"[Welcome] Fond personnalisé trouvé: {bg_path}")
if bg_path:
background = Image.open(bg_path)
else:
background = Image.open(os.path.join(base_dir, 'background_template.png'))
if not bg_path:
default_bg = os.path.join(self.base_dir, 'background_template.png')
if not os.path.exists(default_bg):
kuby_logger.error(f"[Welcome] Fond par défaut introuvable: {default_bg}")
raise FileNotFoundError(f"Fond par défaut introuvable: {default_bg}")
bg_path = default_bg
kuby_logger.debug(f"[Welcome] Utilisation du fond par défaut: {bg_path}")
# Charger le fond
background = Image.open(bg_path)
draw = ImageDraw.Draw(background)
font_large = ImageFont.truetype(os.path.join(base_dir, 'custom_font_large.otf'), 70)
font_small = ImageFont.truetype(os.path.join(base_dir, 'custom_font_small.otf'), 50)
# Charger la police avec support des accents
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Bold.ttf"
if not os.path.exists(font_path):
kuby_logger.warning(f"[Welcome] Police DejaVu introuvable, fallback sur police custom")
font_path = os.path.join(self.base_dir, 'custom_font_large.otf')
font = ImageFont.truetype(font_path, 55)
# Construire le message à afficher sur la bannière
display_message = custom_message if custom_message else "Bienvenue {membre} chez {server}"
# Remplacer les placeholders
try:
display_message = display_message.format(
membre=username,
member=username,
server=server_name or "",
serveur=server_name or "",
username=username,
servername=server_name or "",
)
except Exception:
pass # Si placeholders inconnus, garder tel quel
# Zone de texte disponible (à droite de l'avatar)
text_area_left = 280
text_area_right = background.width - 20
text_area_width = text_area_right - text_area_left
# Découper le texte en lignes qui tiennent dans la zone
words = display_message.split()
lines = []
current_line = ""
for word in words:
test_line = f"{current_line} {word}".strip()
bbox = draw.textbbox((0, 0), test_line, font=font)
if bbox[2] - bbox[0] <= text_area_width:
current_line = test_line
else:
if current_line:
lines.append(current_line)
current_line = word
if current_line:
lines.append(current_line)
wrapped_text = "\n".join(lines)
# Mesurer le bloc de texte complet
bbox = draw.textbbox((0, 0), wrapped_text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# Centrer le bloc dans la zone disponible
text_x = text_area_left + (text_area_width - text_width) // 2
text_y = (background.height - text_height) // 2
draw.text((text_x, text_y), wrapped_text, font=font, fill=(255, 255, 255), align="center")
draw.text((325, 70), "Bienvenue", font=font_large, fill=(255,255,255))
draw.text((325, 135), f"{username}", font=font_small, fill=(255,255,255))
# Afficher le nom du serveur uniquement si configuré
if server_name:
font_server = ImageFont.truetype(os.path.join(base_dir, 'custom_font_large.otf'), 55)
draw.text((325, 195), server_name, font=font_server, fill=(255,255,255))
# Ajouter l'avatar
avatar = Image.open(io.BytesIO(avatar_bytes)).convert("RGBA").resize((224, 224), Image.LANCZOS)
mask = Image.new('L', avatar.size, 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.ellipse((12, 12, 212, 212), fill=255)
background.paste(avatar, (40, 40), mask)
# Retourner l'image
img_byte_arr = io.BytesIO()
background.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)
return img_byte_arr
except Exception as e:
logging.error(f"Erreur image: {e}")
kuby_logger.error(f"[Welcome] Erreur lors de la création de l'image: {e}", exc_info=True)
return None
@commands.Cog.listener()
@ -121,7 +181,7 @@ class Welcome(commands.Cog):
conn.close()
if not result:
kuby_logger.warning(f"⚠️ [Welcome] Aucune configuration trouvée dans la base de données pour le serveur {member.guild.id}")
kuby_logger.debug(f"⚠️ [Welcome] Aucune configuration trouvée dans la base de données pour le serveur {member.guild.id}")
return
welcome_channel = result[0]
@ -170,40 +230,76 @@ class Welcome(commands.Cog):
if welcome_channel:
channel = member.guild.get_channel(welcome_channel)
if channel:
img = await self.create_welcome_image(
member.name,
member.avatar.url if member.avatar else member.default_avatar.url,
server_name=server_name,
banner_background=banner_background
)
if img:
try:
# Créer l'image de bienvenue
server_display_name = server_name if server_name else member.guild.name
img = await self.create_welcome_image(
member.name,
member.avatar.url if member.avatar else member.default_avatar.url,
server_name=server_display_name,
banner_background=banner_background,
custom_message=welcome_channel_message
)
# Attendre un peu que le Cog d'invitations traite le join
await asyncio.sleep(1)
# Récupérer l'inviteur
invites_cog = self.bot.get_cog("Invites")
inviter_text = ""
if invites_cog:
inviter_id = invites_cog.get_inviter_info(member.guild.id, member.id)
if inviter_id:
inviter = member.guild.get_member(inviter_id) or await self.bot.fetch_user(inviter_id)
total, current = invites_cog.get_invite_stats(member.guild.id, inviter_id)
inviter_text = f"\n\n**Invité par :** {inviter.mention} ({current} invitations)"
try:
inviter_id = invites_cog.get_inviter_info(member.guild.id, member.id)
if inviter_id:
inviter = member.guild.get_member(inviter_id)
if not inviter:
try:
inviter = await self.bot.fetch_user(inviter_id)
except (disnake.NotFound, disnake.HTTPException) as e:
kuby_logger.warning(f"[Welcome] Impossible de récupérer l'inviteur {inviter_id}: {e}")
inviter = None
if inviter:
total, current = invites_cog.get_invite_stats(member.guild.id, inviter_id)
inviter_text = f"\n\n**Invité par :** {inviter.mention} ({current} invitations)"
else:
inviter_text = "\n\n**Invité par :** [Utilisateur introuvable]"
except Exception as e:
kuby_logger.error(f"[Welcome] Erreur lors de la récupération de l'inviteur: {e}", exc_info=True)
inviter_text = ""
# Utiliser le message personnalisé ou un message par défaut
channel_description = (welcome_channel_message.format(
username=member.name,
servername=member.guild.name,
member_count=member.guild.member_count
) if welcome_channel_message else "Bienvenue dans le serveur !\nSi vous cherchez des RolePlay de qualité,\nVous êtes au bon endroit. <a:sip:1316891821858619452>") + inviter_text
components = [
disnake.ui.Container(
disnake.ui.TextDisplay(f"Merci d'accueillir {member} 🤝"),
disnake.ui.Separator(divider=True),
disnake.ui.TextDisplay(channel_description),
disnake.ui.MediaGallery(disnake.MediaGalleryItem(url="attachment://welcome.png"))
# Envoyer le message avec le format exact requis
if img:
server_display_name = server_name if server_name else member.guild.name
# IMPORTANT: transmettre aussi le nom réel du serveur à Pillow pour les placeholders {server}
# (utile quand welcome_server_name n'est pas configuré)
# (server_name est utilisé par create_welcome_image)
embed = disnake.Embed(
description=f"Bienvenue sur {server_display_name} {member.name} ! Grâce à toi, nous avons atteint les {member.guild.member_count} membres !{inviter_text}",
color=0x9B59B6
)
]
await channel.send(content=member.mention, components=components, file=disnake.File(img, filename="welcome.png"))
embed.set_image(url="attachment://welcome.png")
await channel.send(embed=embed, file=disnake.File(img, filename="welcome.png"))
kuby_logger.info(f"[Welcome] Message de bienvenue avec image envoyé pour {member.name}")
else:
# Fallback : image non générée, envoyer un message texte
kuby_logger.warning(f"[Welcome] Image non générée pour {member.name}, envoi d'un message texte")
server_display_name = server_name if server_name else member.guild.name
await channel.send(f"Bienvenue sur {server_display_name} {member.mention} ! Grâce à toi, nous avons atteint les {member.guild.member_count} membres !{inviter_text}")
except Exception as e:
kuby_logger.error(f"[Welcome] Erreur lors de l'envoi du message dans le salon: {e}", exc_info=True)
# Fallback avec un message minimal
try:
await channel.send(f"{member.mention} Bienvenue dans le serveur !")
kuby_logger.info(f"[Welcome] Message de fallback envoyé pour {member.name}")
except Exception as e2:
kuby_logger.error(f"[Welcome] Échec du message de fallback: {e2}", exc_info=True)
else:
kuby_logger.warning(f"[Welcome] Salon de bienvenue introuvable (ID: {welcome_channel}) pour {member.guild.name}")
else:
kuby_logger.debug(f"[Welcome] Aucun salon de bienvenue configuré pour {member.guild.name}")
async def save_banner_attachment(self, attachment: disnake.Attachment, guild_id: int) -> str:
"""Sauvegarde l'attachment et retourne le chemin du fichier"""
@ -240,31 +336,42 @@ class Welcome(commands.Cog):
@commands.slash_command(name="setwelcome", description="Définit le salon, les messages de bienvenue et le fond de la bannière")
@commands.has_permissions(administrator=True)
async def set_welcome(self, interaction: disnake.ApplicationCommandInteraction, channel: disnake.TextChannel, message_dm: str, message_salon: str, server_name: str, banner_file: disnake.Attachment = None):
# ✅ DÉFÉRER LA RÉPONSE (le traitement peut prendre > 3s avec les images)
await interaction.response.defer(ephemeral=True)
banner_path = None
error_message = None
# Si un fichier est uploadé, le sauvegarder
if banner_file:
try:
banner_path = await self.save_banner_attachment(banner_file, interaction.guild_id)
except Exception as e:
await interaction.response.send_message(f"❌ Erreur avec le fichier: {e}", ephemeral=True)
return
error_message = f"❌ Erreur avec le fichier: {e}"
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''INSERT INTO server_configs (server_id, welcome_channel, welcome_dm_message, welcome_server_name, welcome_banner_background, welcome_channel_message)
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(server_id)
DO UPDATE SET welcome_channel=excluded.welcome_channel,
welcome_dm_message=excluded.welcome_dm_message,
welcome_server_name=excluded.welcome_server_name,
welcome_banner_background=COALESCE(excluded.welcome_banner_background, welcome_banner_background),
welcome_channel_message=excluded.welcome_channel_message''',
(interaction.guild_id, channel.id, message_dm, server_name, banner_path, message_salon))
conn.commit()
conn.close()
# Si pas d'erreur, sauvegarder en base de données
if not error_message:
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''INSERT INTO server_configs (server_id, welcome_channel, welcome_dm_message, welcome_server_name, welcome_banner_background, welcome_channel_message)
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(server_id)
DO UPDATE SET welcome_channel=excluded.welcome_channel,
welcome_dm_message=excluded.welcome_dm_message,
welcome_server_name=excluded.welcome_server_name,
welcome_banner_background=COALESCE(excluded.welcome_banner_background, welcome_banner_background),
welcome_channel_message=excluded.welcome_channel_message''',
(interaction.guild_id, channel.id, message_dm, server_name, banner_path, message_salon))
conn.commit()
conn.close()
bg_used = "background_template.png" if not banner_path else (banner_path.split('/')[-1])
error_message = f"✅ Configuration de bienvenue enregistrée !\nFond utilisé: {bg_used}"
except Exception as e:
error_message = f"❌ Erreur lors de la sauvegarde en base de données: {e}"
bg_used = "background_template.png" if not banner_path else (banner_path.split('/')[-1])
await interaction.response.send_message(f"✅ Configuration de bienvenida enregistrée !\nFond utilisé: {bg_used}", ephemeral=True)
# Envoyer la réponse finale (déferrée)
await interaction.followup.send(error_message, ephemeral=True)
@commands.slash_command(name="invitations", description="Affiche vos statistiques d'invitations")
async def invitations_stats(self, interaction: disnake.ApplicationCommandInteraction, user: disnake.Member = None):