refactor(modmail): centralize config into single /modmail config panel
- Remove 5 redundant commands (setup, addcategory, removecategory, enable, disable) - Single /modmail config opens interactive Components V2 panel - Channel select for ticket category, role select for staff, channel select for logs - Toggle enable/disable button - Categories sub-panel: add via modal, remove via select - Fix lint (unused imports/vars, contextlib.suppress) + ruff format
This commit is contained in:
parent
f505b6c552
commit
b736f4cc8b
6 changed files with 251 additions and 157 deletions
|
|
@ -1,7 +1,8 @@
|
|||
import contextlib
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
from discord.ext import commands
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from utils import modmail_config as cfg
|
||||
from utils.modmail_transcript import generate_transcript, save_transcript
|
||||
|
|
@ -45,9 +46,7 @@ class CategorySelect(discord.ui.Select):
|
|||
)
|
||||
return
|
||||
|
||||
await interaction.response.send_modal(
|
||||
TicketModal(category_key, cat.get("name", category_key))
|
||||
)
|
||||
await interaction.response.send_modal(TicketModal(category_key, cat.get("name", category_key)))
|
||||
|
||||
|
||||
class TicketModal(discord.ui.Modal, title="Nouveau Ticket"):
|
||||
|
|
@ -106,13 +105,11 @@ class TicketModal(discord.ui.Modal, title="Nouveau Ticket"):
|
|||
|
||||
await _send_ticket_panel(ticket_channel, interaction.user, self.category_name, self.reason.value)
|
||||
|
||||
try:
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
await interaction.followup.send(
|
||||
f"Ticket cree: {ticket_channel.mention}",
|
||||
ephemeral=True,
|
||||
)
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
log_ch = guild.get_channel(data.get("log_channel_id")) if data.get("log_channel_id") else None
|
||||
if log_ch:
|
||||
|
|
@ -181,6 +178,204 @@ class PersistentQuickOpen(discord.ui.View):
|
|||
await interaction.response.send_message("Erreur: reconfigurer le modmail.", ephemeral=True)
|
||||
|
||||
|
||||
class TicketCategorySelect(discord.ui.ChannelSelect):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
placeholder="Categorie des tickets",
|
||||
channel_types=[discord.ChannelType.category],
|
||||
custom_id="modmail_cfg_ticket_category",
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
cfg.set_category(interaction.guild_id, self.values[0].id)
|
||||
await interaction.response.edit_message(view=ModmailConfigView(interaction.guild))
|
||||
|
||||
|
||||
class StaffRoleSelect(discord.ui.RoleSelect):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
placeholder="Role du staff",
|
||||
custom_id="modmail_cfg_staff_role",
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
cfg.set_staff_role(interaction.guild_id, self.values[0].id)
|
||||
await interaction.response.edit_message(view=ModmailConfigView(interaction.guild))
|
||||
|
||||
|
||||
class LogChannelSelect(discord.ui.ChannelSelect):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
placeholder="Canal de logs",
|
||||
channel_types=[discord.ChannelType.text],
|
||||
custom_id="modmail_cfg_log_channel",
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
cfg.set_log_channel(interaction.guild_id, self.values[0].id)
|
||||
await interaction.response.edit_message(view=ModmailConfigView(interaction.guild))
|
||||
|
||||
|
||||
class ToggleButton(discord.ui.Button):
|
||||
def __init__(self, enabled: bool):
|
||||
super().__init__(
|
||||
label="Desactiver" if enabled else "Activer",
|
||||
style=discord.ButtonStyle.danger if enabled else discord.ButtonStyle.success,
|
||||
custom_id="modmail_cfg_toggle",
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
data = cfg.get_config()
|
||||
cfg.set_enabled(interaction.guild_id, not data.get("enabled", False))
|
||||
await interaction.response.edit_message(view=ModmailConfigView(interaction.guild))
|
||||
|
||||
|
||||
class ManageCategoriesButton(discord.ui.Button):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label="Gerer les categories",
|
||||
style=discord.ButtonStyle.primary,
|
||||
custom_id="modmail_cfg_manage_cat",
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
await interaction.response.edit_message(view=ModmailCategoriesView(interaction.guild))
|
||||
|
||||
|
||||
class CategoryRemoveSelect(discord.ui.Select):
|
||||
def __init__(self, categories: dict):
|
||||
options = [
|
||||
discord.SelectOption(
|
||||
label=v["name"],
|
||||
value=k,
|
||||
emoji=v.get("emoji") or None,
|
||||
description=v.get("description", "")[:100] or None,
|
||||
)
|
||||
for k, v in list(categories.items())[:25]
|
||||
]
|
||||
super().__init__(
|
||||
placeholder="Supprimer une categorie",
|
||||
options=options,
|
||||
custom_id="modmail_cfg_remove_cat",
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
cfg.remove_category(interaction.guild_id, self.values[0])
|
||||
await interaction.response.edit_message(view=ModmailCategoriesView(interaction.guild))
|
||||
|
||||
|
||||
class AddCategoryButton(discord.ui.Button):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label="Ajouter une categorie",
|
||||
style=discord.ButtonStyle.success,
|
||||
custom_id="modmail_cfg_add_cat",
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
await interaction.response.send_modal(AddCategoryModal())
|
||||
|
||||
|
||||
class BackButton(discord.ui.Button):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label="Retour",
|
||||
style=discord.ButtonStyle.secondary,
|
||||
custom_id="modmail_cfg_back",
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
await interaction.response.edit_message(view=ModmailConfigView(interaction.guild))
|
||||
|
||||
|
||||
class AddCategoryModal(discord.ui.Modal, title="Ajouter une categorie"):
|
||||
name = discord.ui.TextInput(label="Nom", max_length=50, required=True)
|
||||
emoji = discord.ui.TextInput(label="Emoji", max_length=50, required=False)
|
||||
description = discord.ui.TextInput(
|
||||
label="Description",
|
||||
max_length=100,
|
||||
required=False,
|
||||
style=discord.TextStyle.paragraph,
|
||||
)
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
cfg.add_category(
|
||||
interaction.guild_id,
|
||||
self.name.value,
|
||||
self.emoji.value or "🎫",
|
||||
self.description.value or "",
|
||||
)
|
||||
await interaction.response.edit_message(view=ModmailCategoriesView(interaction.guild))
|
||||
|
||||
|
||||
def _fmt_channel(cid) -> str:
|
||||
return f"<#{cid}>" if cid else "`non defini`"
|
||||
|
||||
|
||||
def _fmt_role(rid) -> str:
|
||||
return f"<@&{rid}>" if rid else "`non defini`"
|
||||
|
||||
|
||||
class ModmailConfigView(discord.ui.LayoutView):
|
||||
def __init__(self, guild: discord.Guild):
|
||||
super().__init__(timeout=None)
|
||||
data = cfg.get_config()
|
||||
categories = cfg.get_categories()
|
||||
enabled = data.get("enabled", False)
|
||||
|
||||
cat_list = (
|
||||
"\n".join(f"- {v.get('emoji', '')} **{v['name']}**" for v in categories.values()) or "*Aucune categorie*"
|
||||
)
|
||||
|
||||
container = discord.ui.Container(
|
||||
accent_colour=discord.Colour.green() if enabled else discord.Colour.red(),
|
||||
)
|
||||
container.add_item(
|
||||
discord.ui.TextDisplay(
|
||||
f"## Configuration Modmail\n"
|
||||
f"**Statut :** {'🟢 Actif' if enabled else '🔴 Inactif'}\n"
|
||||
f"**Categorie des tickets :** {_fmt_channel(data.get('category_id'))}\n"
|
||||
f"**Role staff :** {_fmt_role(data.get('staff_role_id'))}\n"
|
||||
f"**Canal de logs :** {_fmt_channel(data.get('log_channel_id'))}\n"
|
||||
f"### Categories ({len(categories)})\n{cat_list}"
|
||||
)
|
||||
)
|
||||
container.add_item(discord.ui.Separator())
|
||||
container.add_item(discord.ui.ActionRow(TicketCategorySelect()))
|
||||
container.add_item(discord.ui.ActionRow(StaffRoleSelect()))
|
||||
container.add_item(discord.ui.ActionRow(LogChannelSelect()))
|
||||
container.add_item(discord.ui.ActionRow(ToggleButton(enabled), ManageCategoriesButton()))
|
||||
self.add_item(container)
|
||||
|
||||
|
||||
class ModmailCategoriesView(discord.ui.LayoutView):
|
||||
def __init__(self, guild: discord.Guild):
|
||||
super().__init__(timeout=None)
|
||||
categories = cfg.get_categories()
|
||||
|
||||
cat_list = (
|
||||
"\n".join(
|
||||
f"- {v.get('emoji', '')} **{v['name']}**" + (f" — {v['description']}" if v.get("description") else "")
|
||||
for v in categories.values()
|
||||
)
|
||||
or "*Aucune categorie*"
|
||||
)
|
||||
|
||||
container = discord.ui.Container(accent_colour=discord.Colour.blurple())
|
||||
container.add_item(
|
||||
discord.ui.TextDisplay(
|
||||
f"## Gestion des categories\n"
|
||||
f"{cat_list}\n\n"
|
||||
"*Selectionne une categorie pour la supprimer, ou ajoute-en une nouvelle.*"
|
||||
)
|
||||
)
|
||||
container.add_item(discord.ui.Separator())
|
||||
if categories:
|
||||
container.add_item(discord.ui.ActionRow(CategoryRemoveSelect(categories)))
|
||||
container.add_item(discord.ui.ActionRow(AddCategoryButton(), BackButton()))
|
||||
self.add_item(container)
|
||||
|
||||
|
||||
class ModMail(commands.Cog):
|
||||
"""Systeme de modmail / tickets via DM."""
|
||||
|
||||
|
|
@ -221,7 +416,6 @@ class ModMail(commands.Cog):
|
|||
await self._send_ticket_creation_panel(message)
|
||||
|
||||
async def _send_ticket_creation_panel(self, message: discord.Message):
|
||||
data = cfg.get_config()
|
||||
categories = cfg.get_categories()
|
||||
if not categories:
|
||||
await message.author.send("Aucune categorie de ticket configuree.")
|
||||
|
|
@ -233,10 +427,7 @@ class ModMail(commands.Cog):
|
|||
|
||||
container = discord.ui.Container(accent_colour=discord.Colour.blurple())
|
||||
container.add_item(
|
||||
discord.ui.TextDisplay(
|
||||
f"## Ouvrir un ticket\n"
|
||||
f"Cliquez pour ouvrir un ticket **{cat['name']}**."
|
||||
)
|
||||
discord.ui.TextDisplay(f"## Ouvrir un ticket\nCliquez pour ouvrir un ticket **{cat['name']}**.")
|
||||
)
|
||||
open_button = discord.ui.Button(
|
||||
label=f"Ouvrir — {cat['name']}",
|
||||
|
|
@ -252,10 +443,7 @@ class ModMail(commands.Cog):
|
|||
|
||||
container = discord.ui.Container(accent_colour=discord.Colour.blurple())
|
||||
container.add_item(
|
||||
discord.ui.TextDisplay(
|
||||
"## Ouvrir un ticket\n"
|
||||
"Choisis une categorie pour ouvrir un ticket avec le staff."
|
||||
)
|
||||
discord.ui.TextDisplay("## Ouvrir un ticket\nChoisis une categorie pour ouvrir un ticket avec le staff.")
|
||||
)
|
||||
container.add_item(discord.ui.Separator())
|
||||
container.add_item(CategorySelect(categories))
|
||||
|
|
@ -266,7 +454,6 @@ class ModMail(commands.Cog):
|
|||
await message.author.send(view=view)
|
||||
|
||||
async def _relay_to_dm(self, message: discord.Message):
|
||||
data = cfg.get_config()
|
||||
ticket = cfg.get_ticket_by_channel(message.channel.id)
|
||||
if not ticket:
|
||||
return
|
||||
|
|
@ -279,15 +466,17 @@ class ModMail(commands.Cog):
|
|||
if not self._dm_messages.get(user_id):
|
||||
self._dm_messages[user_id] = []
|
||||
|
||||
self._dm_messages[user_id].append({
|
||||
"author": message.author,
|
||||
"content": message.content,
|
||||
"timestamp": message.created_at,
|
||||
"attachments": list(message.attachments),
|
||||
"is_staff": True,
|
||||
})
|
||||
self._dm_messages[user_id].append(
|
||||
{
|
||||
"author": message.author,
|
||||
"content": message.content,
|
||||
"timestamp": message.created_at,
|
||||
"attachments": list(message.attachments),
|
||||
"is_staff": True,
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
embed = discord.Embed(
|
||||
description=message.content or "*Pieces jointes*",
|
||||
color=discord.Color.green(),
|
||||
|
|
@ -299,26 +488,24 @@ class ModMail(commands.Cog):
|
|||
)
|
||||
embed.set_footer(text=f"Staff — {message.guild.name}" if message.guild else "Staff")
|
||||
await user.send(embed=embed)
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
for att in message.attachments:
|
||||
try:
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
await user.send(file=await att.to_file())
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
async def _send_to_ticket(self, channel, user, message):
|
||||
if not self._dm_messages.get(user.id):
|
||||
self._dm_messages[user.id] = []
|
||||
|
||||
self._dm_messages[user.id].append({
|
||||
"author": user,
|
||||
"content": message.content,
|
||||
"timestamp": message.created_at,
|
||||
"attachments": list(message.attachments),
|
||||
"is_staff": False,
|
||||
})
|
||||
self._dm_messages[user.id].append(
|
||||
{
|
||||
"author": user,
|
||||
"content": message.content,
|
||||
"timestamp": message.created_at,
|
||||
"attachments": list(message.attachments),
|
||||
"is_staff": False,
|
||||
}
|
||||
)
|
||||
|
||||
embed = discord.Embed(
|
||||
description=message.content or "*Pieces jointes*",
|
||||
|
|
@ -337,112 +524,17 @@ class ModMail(commands.Cog):
|
|||
|
||||
# --- COMMANDES ---
|
||||
|
||||
modmail_group = app_commands.Group(name="modmail", description="Configuration du modmail")
|
||||
modmail_group = app_commands.Group(name="modmail", description="Systeme de modmail")
|
||||
|
||||
@modmail_group.command(name="setup", description="Setup le systeme de modmail")
|
||||
@app_commands.describe(
|
||||
category="Category pour les tickets",
|
||||
staff_role="Role du staff",
|
||||
log_channel="Canal de logs",
|
||||
)
|
||||
@app_commands.checks.has_permissions(manage_guild=True)
|
||||
async def setup(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
category: discord.CategoryChannel,
|
||||
staff_role: discord.Role,
|
||||
log_channel: discord.TextChannel,
|
||||
):
|
||||
cfg.set_category(interaction.guild_id, category.id)
|
||||
cfg.set_staff_role(interaction.guild_id, staff_role.id)
|
||||
cfg.set_log_channel(interaction.guild_id, log_channel.id)
|
||||
cfg.set_enabled(interaction.guild_id, True)
|
||||
|
||||
data = cfg.get_config()
|
||||
data["guild_id"] = interaction.guild_id
|
||||
from utils.modmail_config import _save
|
||||
_save(data)
|
||||
|
||||
container = discord.ui.Container(accent_colour=discord.Colour.green())
|
||||
container.add_item(
|
||||
discord.ui.TextDisplay(
|
||||
"## Modmail Configure\n"
|
||||
f"**Category:** {category.mention}\n"
|
||||
f"**Staff:** {staff_role.mention}\n"
|
||||
f"**Logs:** {log_channel.mention}\n"
|
||||
f"**Status:** Active"
|
||||
)
|
||||
)
|
||||
view = discord.ui.LayoutView()
|
||||
view.add_item(container)
|
||||
|
||||
await interaction.response.send_message(view=view, ephemeral=True)
|
||||
|
||||
@modmail_group.command(name="config", description="Voir la configuration modmail")
|
||||
@modmail_group.command(name="config", description="Panneau de configuration du modmail")
|
||||
@app_commands.checks.has_permissions(manage_guild=True)
|
||||
async def config_cmd(self, interaction: discord.Interaction):
|
||||
data = cfg.get_config()
|
||||
categories = cfg.get_categories()
|
||||
|
||||
cat_list = "\n".join(
|
||||
f"- {v.get('emoji', '')} **{v['name']}**" for v in categories.values()
|
||||
) or "Aucune"
|
||||
|
||||
status = "Active" if data.get("enabled") else "Desactivee"
|
||||
color = discord.Colour.green() if data.get("enabled") else discord.Colour.red()
|
||||
|
||||
container = discord.ui.Container(accent_colour=color)
|
||||
container.add_item(
|
||||
discord.ui.TextDisplay(
|
||||
f"## Configuration Modmail\n"
|
||||
f"**Status:** {status}\n"
|
||||
f"**Category:** <#{data.get('category_id', 0)}>\n"
|
||||
f"**Staff:** <@&{data.get('staff_role_id', 0)}>\n"
|
||||
f"**Logs:** <#{data.get('log_channel_id', 0)}>\n\n"
|
||||
f"### Categories\n{cat_list}"
|
||||
)
|
||||
)
|
||||
view = discord.ui.LayoutView()
|
||||
view.add_item(container)
|
||||
|
||||
await interaction.response.send_message(view=view, ephemeral=True)
|
||||
|
||||
@modmail_group.command(name="addcategory", description="Ajouter une categorie de ticket")
|
||||
@app_commands.describe(name="Nom", emoji="Emoji", description="Description")
|
||||
@app_commands.checks.has_permissions(manage_guild=True)
|
||||
async def addcategory(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
name: str,
|
||||
emoji: str,
|
||||
description: str = "",
|
||||
):
|
||||
cfg.add_category(interaction.guild_id, name, emoji, description)
|
||||
cfg.set_guild_id(interaction.guild_id)
|
||||
await interaction.response.send_message(
|
||||
f"Categorie **{name}** ajoutee.", ephemeral=True
|
||||
view=ModmailConfigView(interaction.guild),
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
@modmail_group.command(name="removecategory", description="Supprimer une categorie")
|
||||
@app_commands.describe(name="Nom de la categorie")
|
||||
@app_commands.checks.has_permissions(manage_guild=True)
|
||||
async def removecategory(self, interaction: discord.Interaction, name: str):
|
||||
cfg.remove_category(interaction.guild_id, name)
|
||||
await interaction.response.send_message(
|
||||
f"Categorie **{name}** supprimee.", ephemeral=True
|
||||
)
|
||||
|
||||
@modmail_group.command(name="enable", description="Activer le modmail")
|
||||
@app_commands.checks.has_permissions(manage_guild=True)
|
||||
async def enable(self, interaction: discord.Interaction):
|
||||
cfg.set_enabled(interaction.guild_id, True)
|
||||
await interaction.response.send_message("Modmail active.", ephemeral=True)
|
||||
|
||||
@modmail_group.command(name="disable", description="Desactiver le modmail")
|
||||
@app_commands.checks.has_permissions(manage_guild=True)
|
||||
async def disable(self, interaction: discord.Interaction):
|
||||
cfg.set_enabled(interaction.guild_id, False)
|
||||
await interaction.response.send_message("Modmail desactive.", ephemeral=True)
|
||||
|
||||
# --- CLOSE ---
|
||||
|
||||
@commands.command(name="close")
|
||||
|
|
@ -475,13 +567,11 @@ class ModMail(commands.Cog):
|
|||
|
||||
dm_user = self.bot.get_user(user_id)
|
||||
if dm_user:
|
||||
try:
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
await dm_user.send(
|
||||
"Ton ticket a ete ferme. Voici le transcript:",
|
||||
file=discord.File(transcript_path, filename=f"ticket_{channel.id}.html"),
|
||||
)
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
data = cfg.get_config()
|
||||
log_ch = channel.guild.get_channel(data.get("log_channel_id")) if data.get("log_channel_id") else None
|
||||
|
|
@ -496,18 +586,14 @@ class ModMail(commands.Cog):
|
|||
|
||||
container = discord.ui.Container(accent_colour=discord.Colour.red())
|
||||
container.add_item(
|
||||
discord.ui.TextDisplay(
|
||||
f"## Ticket ferme\n**Ferme par:** {user.mention}\n**Categorie:** {category}"
|
||||
)
|
||||
discord.ui.TextDisplay(f"## Ticket ferme\n**Ferme par:** {user.mention}\n**Categorie:** {category}")
|
||||
)
|
||||
view = discord.ui.LayoutView()
|
||||
view.add_item(container)
|
||||
await channel.send(view=view)
|
||||
|
||||
try:
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
await channel.delete(reason=f"Ticket ferme par {user}")
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue