- Updated ticket command and interaction responses to utilize the new `ephemeral()` function for consistent ephemeral message handling. - Enhanced error handling for member fetching in ticket interactions, ensuring proper responses when members are not found. - Improved localization for new error messages related to member presence in ticket commands. - Documented changes in phase tracking to reflect updates in ticket interaction responses.
237 lines
7.2 KiB
TypeScript
237 lines
7.2 KiB
TypeScript
import { PermissionFlagsBits } from 'discord.js';
|
|
import { t, tf } from '@nexumi/shared';
|
|
import type { SlashCommand } from '../../types.js';
|
|
import { getGuildLocale } from '../../i18n.js';
|
|
import { requirePermission } from '../../permissions.js';
|
|
import { ephemeral } from '../../interaction-reply.js';
|
|
import { ticketCommandData } from './command-definitions.js';
|
|
import {
|
|
addUserToTicket,
|
|
assertTicketStaff,
|
|
claimTicket,
|
|
closeTicket,
|
|
createCategoryWithRole,
|
|
findTicketByChannel,
|
|
getGuildCategories,
|
|
removeUserFromTicket,
|
|
renameTicket,
|
|
setTicketPriority,
|
|
syncTicketPanel,
|
|
TicketError,
|
|
TicketPanelError,
|
|
upsertCategoryByName
|
|
} from './service.js';
|
|
|
|
async function ensureManageGuild(
|
|
interaction: Parameters<SlashCommand['execute']>[0],
|
|
locale: 'de' | 'en'
|
|
): Promise<boolean> {
|
|
if (!interaction.inGuild()) {
|
|
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ...ephemeral() });
|
|
return false;
|
|
}
|
|
return requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale);
|
|
}
|
|
|
|
const ticketCommand: SlashCommand = {
|
|
data: ticketCommandData,
|
|
async execute(interaction, context) {
|
|
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
|
if (!interaction.inGuild()) {
|
|
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ...ephemeral() });
|
|
return;
|
|
}
|
|
|
|
const sub = interaction.options.getSubcommand(true);
|
|
|
|
if (sub === 'panel_create') {
|
|
if (!(await ensureManageGuild(interaction, locale))) {
|
|
return;
|
|
}
|
|
|
|
const name = interaction.options.getString('name', true);
|
|
const description = interaction.options.getString('description');
|
|
await upsertCategoryByName(context, interaction.guildId!, name, description);
|
|
|
|
const categories = await getGuildCategories(context, interaction.guildId!);
|
|
if (categories.length === 0) {
|
|
await interaction.reply({
|
|
content: t(locale, 'ticket.error.no_categories'),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const channel = interaction.channel;
|
|
if (!channel?.isTextBased() || channel.isDMBased()) {
|
|
await interaction.reply({
|
|
content: t(locale, 'ticket.error.invalid_channel'),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await syncTicketPanel(context, interaction.guildId!, {
|
|
channelId: channel.id,
|
|
title: name,
|
|
description
|
|
});
|
|
await interaction.reply({ content: t(locale, 'ticket.panel.created'), ...ephemeral() });
|
|
} catch (error) {
|
|
if (error instanceof TicketPanelError) {
|
|
const key =
|
|
error.code === 'no_categories'
|
|
? 'ticket.error.no_categories'
|
|
: error.code === 'not_configured'
|
|
? 'ticket.error.invalid_channel'
|
|
: 'ticket.error.missing_permission';
|
|
await interaction.reply({ content: t(locale, key), ...ephemeral() });
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (sub === 'category_create') {
|
|
if (!(await ensureManageGuild(interaction, locale))) {
|
|
return;
|
|
}
|
|
|
|
const name = interaction.options.getString('name', true);
|
|
const role = interaction.options.getRole('support_role', true);
|
|
const category = await createCategoryWithRole(
|
|
context,
|
|
interaction.guildId!,
|
|
name,
|
|
role.id
|
|
);
|
|
await interaction.reply({
|
|
content: tf(locale, 'ticket.category.created', { name: category.name }),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const ticket = await findTicketByChannel(context.prisma, interaction.channelId!);
|
|
if (!ticket) {
|
|
await interaction.reply({
|
|
content: t(locale, 'ticket.error.not_in_ticket'),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const member = await interaction.guild!.members.fetch(interaction.user.id).catch(() => null);
|
|
if (!member) {
|
|
await interaction.reply({
|
|
content: t(locale, 'ticket.error.member_not_found'),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (sub === 'close') {
|
|
const isStaff = await (async () => {
|
|
try {
|
|
await assertTicketStaff(member, ticket);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
})();
|
|
const isOpener = ticket.openerId === interaction.user.id;
|
|
|
|
if (!isStaff && !isOpener) {
|
|
await interaction.reply({
|
|
content: t(locale, 'ticket.error.not_staff'),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const reason = interaction.options.getString('reason') ?? undefined;
|
|
await closeTicket(context, ticket, interaction.user.id, locale, reason);
|
|
await interaction.reply({ content: t(locale, 'ticket.close.success'), ...ephemeral() });
|
|
return;
|
|
}
|
|
|
|
if (sub === 'claim') {
|
|
await claimTicket(context, ticket, member, locale);
|
|
await interaction.reply({ content: t(locale, 'ticket.claim.success'), ...ephemeral() });
|
|
return;
|
|
}
|
|
|
|
if (sub === 'add') {
|
|
const user = interaction.options.getUser('user', true);
|
|
const target = await interaction.guild!.members.fetch(user.id).catch(() => null);
|
|
if (!target) {
|
|
await interaction.reply({
|
|
content: t(locale, 'ticket.error.member_not_found'),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
await addUserToTicket(context, ticket, member, target, locale);
|
|
await interaction.reply({
|
|
content: tf(locale, 'ticket.add.success', { user: `<@${user.id}>` }),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (sub === 'remove') {
|
|
const user = interaction.options.getUser('user', true);
|
|
const target = await interaction.guild!.members.fetch(user.id).catch(() => null);
|
|
if (!target) {
|
|
await interaction.reply({
|
|
content: t(locale, 'ticket.error.member_not_found'),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
await removeUserFromTicket(context, ticket, member, target, locale);
|
|
await interaction.reply({
|
|
content: tf(locale, 'ticket.remove.success', { user: `<@${user.id}>` }),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (sub === 'rename') {
|
|
const name = interaction.options.getString('name', true);
|
|
await renameTicket(context, ticket, member, name, locale);
|
|
await interaction.reply({
|
|
content: tf(locale, 'ticket.rename.success', { name }),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (sub === 'priority') {
|
|
const level = interaction.options.getString('level', true);
|
|
await setTicketPriority(context, ticket, member, level, locale);
|
|
await interaction.reply({
|
|
content: tf(locale, 'ticket.priority.success', {
|
|
priority: t(locale, `ticket.priority.${level}`)
|
|
}),
|
|
...ephemeral()
|
|
});
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof TicketError) {
|
|
await interaction.reply({
|
|
content: t(locale, `ticket.error.${error.code}`),
|
|
...ephemeral()
|
|
});
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
export const ticketsCommands = [ticketCommand];
|