Ajout de la possibilités de communiquer une information aux partenaires Omega Kube

This commit is contained in:
Mathis 2026-06-18 17:30:35 +02:00
parent 8bcc40f217
commit b0535af75c
3 changed files with 287 additions and 72 deletions

View file

@ -368,7 +368,7 @@ class ReportsFilterView(disnake.ui.View):
val = inter.values[0]
self.selected_status = None if val == "all" else val
self.update_components()
await inter.response.edit_message(components=self.get_components_v2())
await inter.response.edit_message(embed=self.get_filter_embed(), view=self)
status_select.callback = status_callback
self.add_item(status_select)
@ -392,7 +392,7 @@ class ReportsFilterView(disnake.ui.View):
val = inter.values[0]
self.selected_subject = None if val == "all" else val
self.update_components()
await inter.response.edit_message(components=self.get_components_v2())
await inter.response.edit_message(embed=self.get_filter_embed(), view=self)
subject_select.callback = subject_callback
self.add_item(subject_select)
@ -417,7 +417,7 @@ class ReportsFilterView(disnake.ui.View):
else:
self.selected_user = None
self.update_components()
await inter.response.edit_message(components=self.get_components_v2())
await inter.response.edit_message(embed=self.get_filter_embed(), view=self)
user_select.callback = user_callback
self.add_item(user_select)
current_row += 1
@ -439,7 +439,7 @@ class ReportsFilterView(disnake.ui.View):
else:
self.selected_role = None
self.update_components()
await inter.response.edit_message(components=self.get_components_v2())
await inter.response.edit_message(embed=self.get_filter_embed(), view=self)
role_select.callback = role_callback
self.add_item(role_select)
current_row += 1
@ -453,8 +453,6 @@ class ReportsFilterView(disnake.ui.View):
row=current_row
)
async def search_callback(inter: disnake.MessageInteraction):
await inter.response.defer(ephemeral=True)
author_filter = None
if not self.has_global_access:
author_filter = self.viewer_id
@ -474,11 +472,11 @@ class ReportsFilterView(disnake.ui.View):
)
if not reports:
await inter.followup.send("❌ Aucun rapport ne correspond à votre recherche.", ephemeral=True)
await inter.response.send_message("❌ Aucun rapport ne correspond à votre recherche.", ephemeral=True)
return
browser = ReportsBrowserView(self.cog, self.interaction, reports, filter_view=self)
await inter.edit_original_message(content=None, embed=browser.get_embed(), view=browser)
await inter.response.edit_message(embed=browser.get_embed(), view=browser)
search_btn.callback = search_callback
self.add_item(search_btn)
@ -496,49 +494,40 @@ class ReportsFilterView(disnake.ui.View):
self.selected_status = None
self.selected_subject = None
self.update_components()
await inter.response.edit_message(components=self.get_components_v2())
await inter.response.edit_message(embed=self.get_filter_embed(), view=self)
reset_btn.callback = reset_callback
self.add_item(reset_btn)
def get_components_v2(self):
sections = [
disnake.ui.Section(
"# 🔍 Recherche & Filtres de Rapports",
accessory=disnake.ui.Thumbnail(self.interaction.author.display_avatar.url)
),
disnake.ui.Separator(divider=True)
]
filters_desc = []
if self.selected_status:
filters_desc.append(f"🚦 **Statut** : {self.selected_status}")
else:
filters_desc.append("🚦 **Statut** : Tous")
def get_filter_embed(self) -> disnake.Embed:
"""Génère l'embed résumant les filtres actifs."""
author = self.interaction.author
lines = []
lines.append(f"🚦 **Statut** : {self.selected_status or 'Tous'}")
if self.has_global_access:
if self.selected_user:
mention = self.selected_user.mention if hasattr(self.selected_user, "mention") else f"<@{self.selected_user}>"
filters_desc.append(f"👤 **Auteur** : {mention}")
mention = self.selected_user.mention if hasattr(self.selected_user, 'mention') else f'<@{self.selected_user}>'
lines.append(f"👤 **Auteur** : {mention}")
else:
filters_desc.append("👤 **Auteur** : Tous")
lines.append("👤 **Auteur** : Tous")
if self.selected_role:
mention = self.selected_role.mention if hasattr(self.selected_role, "mention") else f"<@&{self.selected_role}>"
filters_desc.append(f"🛡️ **Rôle Auteur** : {mention}")
mention = self.selected_role.mention if hasattr(self.selected_role, 'mention') else f'<@&{self.selected_role}>'
lines.append(f"🛡️ **Rôle Auteur** : {mention}")
else:
filters_desc.append("🛡️ **Rôle Auteur** : Tous")
lines.append("🛡️ **Rôle Auteur** : Tous")
else:
filters_desc.append(f"👤 **Auteur** : {self.interaction.author.mention} *(Accès restreint à vos propres rapports)*")
lines.append(f"👤 **Auteur** : {author.mention} *(accès limité à vos rapports)*")
lines.append(f"📁 **Objet / Catégorie** : {self.selected_subject or 'Tous'}")
embed = disnake.Embed(
title="🔍 Recherche & Filtres de Rapports",
description="\n".join(lines),
color=disnake.Color.blurple()
)
embed.set_thumbnail(url=author.display_avatar.url)
return embed
if self.selected_subject:
filters_desc.append(f"📁 **Objet / Catégorie** : `{self.selected_subject}`")
else:
filters_desc.append("📁 **Objet / Catégorie** : Tous")
sections.append(disnake.ui.TextDisplay(content="\n".join(filters_desc)))
sections.append(disnake.ui.Separator(divider=True))
return [disnake.ui.Container(*sections)] + self.children
def get_components_v2(self):
"""Kept for backward compatibility — returns view children only."""
return self.children
async def interaction_check(self, interaction: disnake.MessageInteraction) -> bool:
if interaction.author.id != self.viewer_id:
@ -701,9 +690,14 @@ class ReportsBrowserView(disnake.ui.View):
if not self.reports:
if self.filter_view:
self.filter_view.update_components()
await inter.response.edit_message(content=None, embed=None, components=self.filter_view.get_components_v2())
await inter.response.edit_message(
embed=self.filter_view.get_filter_embed(), view=self.filter_view
)
else:
await inter.response.edit_message(content="✅ Le rapport a été supprimé. Plus aucun rapport dans cette recherche.", embed=None, view=None)
await inter.response.edit_message(
content="✅ Le rapport a été supprimé. Plus aucun rapport dans cette recherche.",
embed=None, view=None
)
return
if self.current_index >= len(self.reports):
@ -717,10 +711,14 @@ class ReportsBrowserView(disnake.ui.View):
async def back_callback(inter: disnake.MessageInteraction):
if self.filter_view:
self.filter_view.update_components()
await inter.response.edit_message(embed=None, components=self.filter_view.get_components_v2())
await inter.response.edit_message(
embed=self.filter_view.get_filter_embed(), view=self.filter_view
)
else:
new_filter_view = ReportsFilterView(self.cog, self.interaction)
await inter.response.edit_message(embed=None, components=new_filter_view.get_components_v2())
await inter.response.edit_message(
embed=new_filter_view.get_filter_embed(), view=new_filter_view
)
edit_btn.callback = edit_callback
delete_btn.callback = delete_callback
@ -732,9 +730,9 @@ class ReportsBrowserView(disnake.ui.View):
# Row 3: Menu Déroulant "Changer l'état" (Admin uniquement)
if self.is_admin:
status_options = [
disnake.SelectOption(label="En attente", emoji="🟡", value="En attente", default=(report['status'] == "En attente")),
disnake.SelectOption(label="Traité", emoji="🟢", value="Traité", default=(report['status'] == "Traité")),
disnake.SelectOption(label="Classé", emoji="🔴", value="Classé", default=(report['status'] == "Classé"))
disnake.SelectOption(label="En attente", emoji="🟡", value="En attente"),
disnake.SelectOption(label="Traité", emoji="🟢", value="Traité"),
disnake.SelectOption(label="Classé", emoji="🔴", value="Classé")
]
status_menu = disnake.ui.StringSelect(
placeholder="Changer le statut...",
@ -747,15 +745,80 @@ class ReportsBrowserView(disnake.ui.View):
if not inter.author.guild_permissions.administrator:
return await inter.response.send_message("❌ Seuls les administrateurs peuvent modifier le statut.", ephemeral=True)
old_status = report['status']
new_status = inter.values[0]
self.cog.db.update_report_status(report['id'], new_status)
if old_status == new_status:
return await inter.response.send_message(f" Le rapport est déjà en statut **{new_status}**.", ephemeral=True)
self.cog.db.update_report_status(report['id'], new_status)
updated_rep = self.cog.db.get_report(report['id'])
self.reports[self.current_index] = updated_rep
self.update_components()
await inter.response.edit_message(embed=self.get_embed(), view=self)
await inter.followup.send(f"✅ Statut du rapport #{report['id']} mis à jour vers : **{new_status}**.", ephemeral=True)
await inter.followup.send(
f"✅ Statut du rapport **#{report['id']}** mis à jour : **{old_status}** → **{new_status}**.",
ephemeral=True
)
# === Notification DM à l'auteur ===
status_colors = {
"En attente": disnake.Color.yellow(),
"Traité": disnake.Color.green(),
"Classé": disnake.Color.greyple(),
}
status_emojis = {"En attente": "🟡", "Traité": "🟢", "Classé": "🔴"}
dm_sent = False
try:
author_member = inter.guild.get_member(report['author_id'])
if not author_member:
author_member = await inter.guild.fetch_member(report['author_id'])
if author_member:
dm_embed = disnake.Embed(
title="📋 Mise à jour de votre rapport",
description=(
f"Le statut de votre rapport **#{report['id']}{report['title']}** "
f"vient d'être modifié."
),
color=status_colors.get(new_status, disnake.Color.blurple()),
timestamp=datetime.now(PARIS_TZ)
)
dm_embed.add_field(
name="Ancien statut",
value=f"{status_emojis.get(old_status, '')} {old_status}",
inline=True
)
dm_embed.add_field(
name="Nouveau statut",
value=f"{status_emojis.get(new_status, '')} {new_status}",
inline=True
)
dm_embed.add_field(name="Modifié par", value=inter.author.mention, inline=False)
dm_embed.add_field(name="Serveur", value=inter.guild.name, inline=False)
dm_embed.set_footer(text=f"Rapport #{report['id']}{inter.guild.name}")
if inter.guild.icon:
dm_embed.set_thumbnail(url=inter.guild.icon.url)
try:
await author_member.send(embed=dm_embed)
dm_sent = True
except disnake.Forbidden:
kuby_logger.info(
f"[Rapports] DM impossible pour {author_member} (DMs fermés) — rapport #{report['id']}"
)
except Exception as e:
kuby_logger.error(f"[Rapports] Erreur DM notification: {e}", exc_info=True)
except disnake.NotFound:
pass
except Exception as e:
kuby_logger.error(f"[Rapports] Erreur récupération membre pour DM: {e}", exc_info=True)
dm_note = " • 📬 Auteur notifié par DM." if dm_sent else " • ⚠️ DM non envoyé (DMs fermés ou membre introuvable)."
await inter.followup.send(
f"{dm_note}",
ephemeral=True
)
status_menu.callback = status_callback
self.add_item(status_menu)
@ -898,7 +961,20 @@ class Rapports(commands.Cog):
filter_view.selected_role = role
filter_view.update_components()
await interaction.response.send_message(components=filter_view.get_components_v2(), ephemeral=True)
await interaction.response.send_message(
embed=disnake.Embed(
title="🔍 Recherche & Filtres de Rapports",
description=(
f"🚦 **Statut** : {filter_view.selected_status or 'Tous'}\n"
f"📁 **Objet / Catégorie** : {filter_view.selected_subject or 'Tous'}\n"
+ (f"👤 **Auteur** : {filter_view.selected_user.mention if hasattr(filter_view.selected_user, 'mention') else 'Tous'}\n" if filter_view.has_global_access else f"👤 **Auteur** : {interaction.author.mention} *(accès limité à vos rapports)*\n")
+ (f"🛡️ **Rôle Auteur** : {filter_view.selected_role.mention if hasattr(filter_view.selected_role, 'mention') else 'Tous'}" if filter_view.has_global_access else "")
),
color=disnake.Color.blurple()
),
view=filter_view,
ephemeral=True
)
def setup(bot):
bot.add_cog(Rapports(bot))

View file

@ -1,24 +1,166 @@
import disnake
from disnake.ext import commands
OWNER_ID = 971446412690722826
def is_authorized(interaction: disnake.ApplicationCommandInteraction) -> bool:
return interaction.user.id == OWNER_ID
class AnnouncementModal(disnake.ui.Modal):
def __init__(self, cog: "SentBotMessages"):
self.cog = cog
components = [
disnake.ui.TextInput(
label="Titre principal de l'annonce",
placeholder="Ex: Mise à jour important / Maintenance / Nouveau système",
custom_id="announcement_title",
required=True,
max_length=100,
),
disnake.ui.TextInput(
label="Message de l'annonce (contenu réel)",
style=disnake.TextInputStyle.paragraph,
placeholder="Écris le vrai contenu de l'annonce ici.",
custom_id="announcement_message",
required=True,
max_length=4000,
),
disnake.ui.TextInput(
label="Message de prévention / contexte MP",
style=disnake.TextInputStyle.paragraph,
placeholder="Ex: Ce MP vous informe d'une annonce importante.",
custom_id="announcement_prevention",
required=True,
max_length=1000,
),
]
super().__init__(
title="Annonce aux propriétaires",
custom_id="announcement_modal",
components=components,
)
async def callback(self, interaction: disnake.ModalInteraction):
title = (interaction.text_values.get("announcement_title") or "").strip()
message = (interaction.text_values.get("announcement_message") or "").strip()
prevention = (interaction.text_values.get("announcement_prevention") or "").strip()
await interaction.response.defer(ephemeral=True)
preview_container = disnake.ui.Container(
disnake.ui.TextDisplay(content=f"# {title}"),
disnake.ui.Separator(divider=True),
disnake.ui.TextDisplay(content=prevention),
disnake.ui.Separator(divider=True),
disnake.ui.TextDisplay(content=message),
disnake.ui.Separator(divider=True),
disnake.ui.TextDisplay(
content=(
"📍 **Prévisualisation**\n"
"Le même format sera envoyé aux propriétaires."
)
)
)
successful = 0
failed_servers = []
for guild in self.cog.bot.guilds:
try:
owner = guild.owner
if owner is None:
owner = await guild.fetch_member(guild.owner_id)
container = disnake.ui.Container(
disnake.ui.TextDisplay(content=f"# {title}"),
disnake.ui.Separator(divider=True),
disnake.ui.TextDisplay(content=prevention),
disnake.ui.Separator(divider=True),
disnake.ui.TextDisplay(content=message),
disnake.ui.Separator(divider=True),
disnake.ui.TextDisplay(
content=(
f"📍 **Serveur** : {guild.name}\n"
f"🆔 **ID** : `{guild.id}`"
)
)
)
await owner.send(components=[container])
successful += 1
except Exception as e:
failed_servers.append(f"{guild.name} ({guild.id}) : {e}")
result_text = (
f"✅ Annonce envoyée à {successful} propriétaire(s)."
if not failed_servers
else (
f"✅ Annonce envoyée à {successful} propriétaire(s).\n"
f"❌ Échecs ({len(failed_servers)}):\n"
f"- {'\n- '.join(failed_servers[:10])}"
f"{'\n... et ' + str(len(failed_servers) - 10) + ' autres' if len(failed_servers) > 10 else ''}"
)
)
result_container = disnake.ui.Container(
disnake.ui.TextDisplay(content=result_text),
disnake.ui.Separator(divider=True),
disnake.ui.TextDisplay(content="🧩 Aperçu du format envoyé aux propriétaires :"),
disnake.ui.Separator(divider=True),
*preview_container.children
)
await interaction.edit_original_response(components=[result_container])
class SentBotMessages(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.slash_command(name="sentbotmessages", description="Le bot envoie un message dans un salon choisi.")
async def sentbotmessages(self, interaction: disnake.ApplicationCommandInteraction,
@commands.slash_command(
name="sentbotmessages",
description="Le bot envoie un message dans un salon choisi."
)
async def sentbotmessages(
self,
interaction: disnake.ApplicationCommandInteraction,
message: str = commands.Param(description="Le message à envoyer"),
salon: disnake.TextChannel = commands.Param(description="Le salon où envoyer le message")):
salon: disnake.TextChannel = commands.Param(description="Le salon où envoyer le message")
):
# Vérifie si l'utilisateur est admin
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("⛔ Tu n'as pas la permission d'utiliser cette commande.", ephemeral=True)
await interaction.response.send_message(
"⛔ Tu n'as pas la permission d'utiliser cette commande.",
ephemeral=True
)
return
try:
await salon.send(message)
await interaction.response.send_message(f"✅ Message envoyé dans {salon.mention}.", ephemeral=True)
await interaction.response.send_message(
f"✅ Message envoyé dans {salon.mention}.",
ephemeral=True
)
except Exception as e:
await interaction.response.send_message(f"❌ Une erreur est survenue : {e}", ephemeral=True)
await interaction.response.send_message(
f"❌ Une erreur est survenue : {e}",
ephemeral=True
)
@commands.slash_command(
name="annonce_proprietaires",
description="Ouvrir le formulaire d'annonce pour les propriétaires."
)
@commands.check(is_authorized)
async def annonce_proprietaires(
self,
interaction: disnake.ApplicationCommandInteraction,
):
await interaction.response.send_modal(AnnouncementModal(self))
def setup(bot):
bot.add_cog(SentBotMessages(bot))

View file

@ -3158,13 +3158,11 @@ class TicketCommands(commands.Cog):
if not custom_id:
return
# Accept both ticket_* / catv2_* / close_* custom_ids
# Accept only ticket_* / catv2_* / cat_* / close_* custom_ids
# reports_filter_* are intentionally excluded — handled by ReportsFilterView callbacks
if not (str(custom_id).startswith("ticket_")
or str(custom_id).startswith("catv2_")
or str(custom_id).startswith("cat_")
or str(custom_id).startswith("reports_filter_")
or str(custom_id) in {"close_confirm", "close_cancel", "delay_cancel"}):
return
@ -3222,6 +3220,7 @@ class TicketCommands(commands.Cog):
await inter.response.send_message(f"Erreur interne: {e}", ephemeral=True)
# backward compatibility (legacy cat_<id>_<suffix>)
elif custom_id.startswith("cat_"):
try:
parts = str(custom_id).split("_")
@ -3402,13 +3401,11 @@ class TicketCommands(commands.Cog):
await self._send_recruitment_decision(inter, ticket, category, accepted=False)
# === Fermeture via composants V2 (sans view=, donc custom_id) ===
elif custom_id == "close_cancel":
await inter.response.defer()
# Retire la réponse éphémère du user (si c'est bien une réponse d'interaction)
try:
await inter.delete_original_response()
except Exception:
pass
elif custom_id.startswith("reports_filter_"):
# No direct response; let the view's callback handle the interaction
return
elif custom_id == "close_confirm":
try: