Merge branch 'dev' into 'main'
Fix de la base de données See merge request Omega_Kube/kuby!26
BIN
banners/banner_1370362952938688573.png
Normal file
|
After Width: | Height: | Size: 345 KiB |
BIN
banners/banner_1402491843899555840.png
Normal file
|
After Width: | Height: | Size: 328 KiB |
BIN
banners/banner_goodbye_1015968420203855883.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -149,6 +149,7 @@ class Goodbye(commands.Cog):
|
||||||
async def on_member_remove(self, member):
|
async def on_member_remove(self, member):
|
||||||
kuby_logger.info(f"[Goodbye] Départ détecté: {member.name} (ID: {member.id}) du serveur {member.guild.name}")
|
kuby_logger.info(f"[Goodbye] Départ détecté: {member.name} (ID: {member.id}) du serveur {member.guild.name}")
|
||||||
|
|
||||||
|
self._init_db()
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self.db_path)
|
conn = sqlite3.connect(self.db_path)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ class Moderation(commands.Cog):
|
||||||
if chan: await chan.send(components=components)
|
if chan: await chan.send(components=components)
|
||||||
|
|
||||||
async def send_modpanel_v2(self, interaction: disnake.Interaction, member: disnake.Member, edit=False):
|
async def send_modpanel_v2(self, interaction: disnake.Interaction, member: disnake.Member, edit=False):
|
||||||
|
self._init_db()
|
||||||
conn = sqlite3.connect(self.db_path); cursor = conn.cursor()
|
conn = sqlite3.connect(self.db_path); cursor = conn.cursor()
|
||||||
cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, member.id))
|
cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, member.id))
|
||||||
warn_count = cursor.fetchone()[0]
|
warn_count = cursor.fetchone()[0]
|
||||||
|
|
|
||||||
|
|
@ -3162,6 +3162,7 @@ class TicketCommands(commands.Cog):
|
||||||
if not (
|
if not (
|
||||||
str(custom_id).startswith("ticket_")
|
str(custom_id).startswith("ticket_")
|
||||||
or str(custom_id).startswith("catv2_")
|
or str(custom_id).startswith("catv2_")
|
||||||
|
or str(custom_id).startswith("cat_")
|
||||||
or str(custom_id) in {"close_confirm", "close_cancel", "delay_cancel"}
|
or str(custom_id) in {"close_confirm", "close_cancel", "delay_cancel"}
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
@ -3208,6 +3209,9 @@ class TicketCommands(commands.Cog):
|
||||||
else:
|
else:
|
||||||
modal = TicketPriorityModal(self.bot, config, category)
|
modal = TicketPriorityModal(self.bot, config, category)
|
||||||
|
|
||||||
|
# Sécurité anti-crash : Limite stricte de Discord à 45 caractères pour le titre
|
||||||
|
if hasattr(modal, "title") and modal.title:
|
||||||
|
modal.title = str(modal.title)[:45]
|
||||||
await inter.response.send_modal(modal)
|
await inter.response.send_modal(modal)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
kuby_logger.error(f"[TicketSystem] cat| routing error: {e}", exc_info=True)
|
kuby_logger.error(f"[TicketSystem] cat| routing error: {e}", exc_info=True)
|
||||||
|
|
@ -3230,12 +3234,70 @@ class TicketCommands(commands.Cog):
|
||||||
else:
|
else:
|
||||||
modal = TicketPriorityModal(self.bot, config, category)
|
modal = TicketPriorityModal(self.bot, config, category)
|
||||||
|
|
||||||
|
# Sécurité anti-crash : Limite stricte de Discord à 45 caractères pour le titre
|
||||||
|
if hasattr(modal, "title") and modal.title:
|
||||||
|
modal.title = str(modal.title)[:45]
|
||||||
await inter.response.send_modal(modal)
|
await inter.response.send_modal(modal)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
kuby_logger.error(f"[TicketSystem] catv2_ routing error: {e}", exc_info=True)
|
kuby_logger.error(f"[TicketSystem] catv2_ routing error: {e}", exc_info=True)
|
||||||
if not inter.response.is_done():
|
if not inter.response.is_done():
|
||||||
await inter.response.send_message(f"Erreur interne: {e}", ephemeral=True)
|
await inter.response.send_message(f"Erreur interne: {e}", ephemeral=True)
|
||||||
|
|
||||||
|
elif custom_id == "ticket_create":
|
||||||
|
# Construction du layout de sélection Premium V2
|
||||||
|
category_items = [
|
||||||
|
disnake.ui.TextDisplay(
|
||||||
|
content="# 📂 Sélectionner une Catégorie - Omega Kube\n\n"
|
||||||
|
"Veuillez choisir la catégorie qui correspond au motif de votre demande pour ouvrir un ticket."
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Génération dynamique des sections avec Emojis et Descriptions configurées
|
||||||
|
for cat in config.categories[:5]:
|
||||||
|
cat_emoji = getattr(cat, "emoji", "") or ""
|
||||||
|
cat_desc = getattr(cat, "description", "") or getattr(cat, "desc", "") or ""
|
||||||
|
|
||||||
|
category_items.append(disnake.ui.Separator(divider=True))
|
||||||
|
category_items.append(
|
||||||
|
disnake.ui.Section(
|
||||||
|
f"### {cat_emoji} {cat.name}\n{cat_desc}" if cat_desc else f"### {cat_emoji} {cat.name}",
|
||||||
|
accessory=disnake.ui.Button(
|
||||||
|
label="Choisir",
|
||||||
|
style=disnake.ButtonStyle.blurple,
|
||||||
|
custom_id=f"cat_{cat.id}_select"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await inter.response.send_message(
|
||||||
|
components=[disnake.ui.Container(*category_items)],
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
elif custom_id == "ticket_my_tickets":
|
||||||
|
# Afficher les tickets de l'utilisateur
|
||||||
|
user_tickets = self.storage.get_user_tickets(inter.guild.id, inter.user.id)
|
||||||
|
if not user_tickets:
|
||||||
|
await inter.response.send_message("Vous n'avez aucun ticket ouvert.", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Créer un message avec la liste des tickets
|
||||||
|
ticket_list = []
|
||||||
|
for ticket in user_tickets:
|
||||||
|
channel = self.bot.get_channel(ticket.channel_id)
|
||||||
|
if channel:
|
||||||
|
ticket_list.append(f"• {channel.mention} - {ticket.status.value}")
|
||||||
|
else:
|
||||||
|
ticket_list.append(f"• #{ticket.channel_id} - {ticket.status.value}")
|
||||||
|
|
||||||
|
embed = disnake.Embed(
|
||||||
|
title="📂 Vos Tickets",
|
||||||
|
description="\n".join(ticket_list) if ticket_list else "Aucun ticket trouvé.",
|
||||||
|
color=0x9B59B6
|
||||||
|
)
|
||||||
|
await inter.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
elif custom_id == "ticket_analytics":
|
||||||
|
panel_view = TicketPanelView(self.bot, config, self.storage)
|
||||||
|
await panel_view._analytics_callback(inter)
|
||||||
elif custom_id == "ticket_claim":
|
elif custom_id == "ticket_claim":
|
||||||
ticket = self.storage.load_ticket(inter.channel.id)
|
ticket = self.storage.load_ticket(inter.channel.id)
|
||||||
if ticket:
|
if ticket:
|
||||||
|
|
@ -3747,7 +3809,7 @@ class TicketCommands(commands.Cog):
|
||||||
"""Ouvre le panel des tickets"""
|
"""Ouvre le panel des tickets"""
|
||||||
config = self.storage.load_config(inter.guild.id)
|
config = self.storage.load_config(inter.guild.id)
|
||||||
view = TicketPanelView(self.bot, config, self.storage)
|
view = TicketPanelView(self.bot, config, self.storage)
|
||||||
await inter.response.send_message(components=view.get_components_v2(), view=view, ephemeral=True)
|
await inter.response.send_message(components=view.get_components_v2(), ephemeral=True)
|
||||||
|
|
||||||
@commands.slash_command(name="ticketpanel", description="Envoie le panel de création de tickets dans un canal")
|
@commands.slash_command(name="ticketpanel", description="Envoie le panel de création de tickets dans un canal")
|
||||||
@commands.has_permissions(administrator=True)
|
@commands.has_permissions(administrator=True)
|
||||||
|
|
@ -3818,4 +3880,3 @@ class TicketCommands(commands.Cog):
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
"""Setup function"""
|
"""Setup function"""
|
||||||
bot.add_cog(TicketCommands(bot))
|
bot.add_cog(TicketCommands(bot))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,8 @@ class Welcome(commands.Cog):
|
||||||
async def on_member_join(self, member):
|
async def on_member_join(self, member):
|
||||||
kuby_logger.info(f"📥 [Welcome] Événement on_member_join reçu pour {member.name} (ID: {member.id}) sur {member.guild.name}")
|
kuby_logger.info(f"📥 [Welcome] Événement on_member_join reçu pour {member.name} (ID: {member.id}) sur {member.guild.name}")
|
||||||
|
|
||||||
|
welcome_channel = None
|
||||||
|
self._init_db()
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self.db_path)
|
conn = sqlite3.connect(self.db_path)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
@ -216,83 +218,83 @@ class Welcome(commands.Cog):
|
||||||
kuby_logger.error(f"❌ [Welcome] Erreur lors de l'envoi du DM à {member.name}: {e}", exc_info=True)
|
kuby_logger.error(f"❌ [Welcome] Erreur lors de l'envoi du DM à {member.name}: {e}", exc_info=True)
|
||||||
else:
|
else:
|
||||||
kuby_logger.info(f"ℹ️ [Welcome] Aucun message de DM configuré pour le serveur {member.guild.id}")
|
kuby_logger.info(f"ℹ️ [Welcome] Aucun message de DM configuré pour le serveur {member.guild.id}")
|
||||||
except Exception as e:
|
|
||||||
kuby_logger.error(f"❌ [Welcome] Erreur critique dans on_member_join: {e}", exc_info=True)
|
|
||||||
|
|
||||||
# 2. Gestion du salon de bienvenue
|
# 2. Gestion du salon de bienvenue
|
||||||
if welcome_channel:
|
if welcome_channel:
|
||||||
channel = member.guild.get_channel(welcome_channel)
|
channel = member.guild.get_channel(welcome_channel)
|
||||||
if channel:
|
if channel:
|
||||||
try:
|
try:
|
||||||
# Créer l'image de bienvenue
|
# 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:
|
|
||||||
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 = ""
|
|
||||||
|
|
||||||
# Envoyer le message avec le format exact requis
|
|
||||||
if img:
|
|
||||||
server_display_name = server_name if server_name else member.guild.name
|
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}
|
img = await self.create_welcome_image(
|
||||||
# (utile quand welcome_server_name n'est pas configuré)
|
member.name,
|
||||||
# (server_name est utilisé par create_welcome_image)
|
member.avatar.url if member.avatar else member.default_avatar.url,
|
||||||
embed = disnake.Embed(
|
server_name=server_display_name,
|
||||||
description=f"Bienvenue sur {server_display_name} {member.name} ! Grâce à toi, nous avons atteint les {member.guild.member_count} membres !{inviter_text}",
|
banner_background=banner_background,
|
||||||
color=0x9B59B6
|
custom_message=welcome_channel_message
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.set_image(url="attachment://welcome.png")
|
# Attendre un peu que le Cog d'invitations traite le join
|
||||||
await channel.send(embed=embed, file=disnake.File(img, filename="welcome.png"))
|
await asyncio.sleep(1)
|
||||||
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:
|
# Récupérer l'inviteur
|
||||||
kuby_logger.error(f"[Welcome] Erreur lors de l'envoi du message dans le salon: {e}", exc_info=True)
|
invites_cog = self.bot.get_cog("Invites")
|
||||||
# Fallback avec un message minimal
|
inviter_text = ""
|
||||||
try:
|
if invites_cog:
|
||||||
await channel.send(f"{member.mention} Bienvenue dans le serveur !")
|
try:
|
||||||
kuby_logger.info(f"[Welcome] Message de fallback envoyé pour {member.name}")
|
inviter_id = invites_cog.get_inviter_info(member.guild.id, member.id)
|
||||||
except Exception as e2:
|
if inviter_id:
|
||||||
kuby_logger.error(f"[Welcome] Échec du message de fallback: {e2}", exc_info=True)
|
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 = ""
|
||||||
|
|
||||||
|
# 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
|
||||||
|
)
|
||||||
|
|
||||||
|
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:
|
else:
|
||||||
kuby_logger.warning(f"[Welcome] Salon de bienvenue introuvable (ID: {welcome_channel}) pour {member.guild.name}")
|
kuby_logger.debug(f"[Welcome] Aucun salon de bienvenue configuré pour {member.guild.name}")
|
||||||
else:
|
except Exception as e:
|
||||||
kuby_logger.debug(f"[Welcome] Aucun salon de bienvenue configuré pour {member.guild.name}")
|
kuby_logger.error(f"❌ [Welcome] Erreur critique dans on_member_join: {e}", exc_info=True)
|
||||||
|
|
||||||
async def save_banner_attachment(self, attachment: disnake.Attachment, guild_id: int) -> str:
|
async def save_banner_attachment(self, attachment: disnake.Attachment, guild_id: int) -> str:
|
||||||
"""Sauvegarde l'attachment et retourne le chemin du fichier"""
|
"""Sauvegarde l'attachment et retourne le chemin du fichier"""
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,9 @@
|
||||||
"1369669999345537145": {
|
"1369669999345537145": {
|
||||||
"absence_channel_id": 1369669999677145103,
|
"absence_channel_id": 1369669999677145103,
|
||||||
"absence_role_id": 1369669999345537154
|
"absence_role_id": 1369669999345537154
|
||||||
|
},
|
||||||
|
"1402491843899555840": {
|
||||||
|
"absence_channel_id": 1494492360732835972,
|
||||||
|
"absence_role_id": 1501619311918190703
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"1369669999345537145": {}
|
"1369669999345537145": {},
|
||||||
|
"1402491843899555840": {}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
{
|
{
|
||||||
"4": 971446412690722826,
|
"4": 971446412690722826,
|
||||||
"5": 971446412690722826,
|
"5": 971446412690722826,
|
||||||
"15": 971446412690722826
|
"15": 971446412690722826,
|
||||||
|
"153": 534003230435442690,
|
||||||
|
"160": 971446412690722826,
|
||||||
|
"161": 971446412690722826,
|
||||||
|
"162": 971446412690722826,
|
||||||
|
"164": 971446412690722826
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 195 KiB |