Add new features for giveaways, tickets, self-roles, tags, starboard, suggestions, and birthdays
- Integrated new command modules for managing giveaways, tickets, self-roles, tags, starboard, suggestions, and birthdays, enhancing the bot's capabilities. - Implemented job handling for giveaway endings, ticket management, and birthday role expirations, improving automation and user experience. - Updated command definitions and interaction handling to support new features, ensuring smooth user interactions. - Enhanced the job processing system with dedicated workers for managing various tasks, including auto-closing tickets and running birthday checks. - Documented the new features and their setup processes in PHASE-TRACKING.md for clarity on implementation steps.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { PermissionFlagsBits, type Guild, type GuildMember, type TextChannel } from 'discord.js';
|
||||
import { z } from 'zod';
|
||||
import { applyTagPlaceholders, tf } from '@nexumi/shared';
|
||||
import { applyTagPlaceholders } from '@nexumi/shared';
|
||||
import { ensureGuild } from '../../guild.js';
|
||||
import { logger } from '../../logger.js';
|
||||
import { birthdayQueue } from '../../queues.js';
|
||||
|
||||
@@ -15,9 +15,6 @@ export const selfrolesCommandData = applyCommandDescription(
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(o.setName('title').setRequired(true), 'selfroles.panel.create.options.title')
|
||||
)
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(o.setName('description'), 'selfroles.panel.create.options.description')
|
||||
)
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(o.setName('mode').setRequired(true), 'selfroles.panel.create.options.mode')
|
||||
.addChoices(
|
||||
@@ -45,6 +42,9 @@ export const selfrolesCommandData = applyCommandDescription(
|
||||
applyOptionDescription(o.setName('channel').setRequired(true), 'selfroles.panel.create.options.channel')
|
||||
.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement)
|
||||
)
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(o.setName('description'), 'selfroles.panel.create.options.description')
|
||||
)
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(o.setName('duration'), 'selfroles.panel.create.options.duration')
|
||||
)
|
||||
|
||||
@@ -5,41 +5,37 @@ import {
|
||||
} from '@nexumi/shared';
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
|
||||
// Discord forbids mixing SUB_COMMAND_GROUP and SUB_COMMAND siblings.
|
||||
// SPEC `/ticket panel create` is represented as subcommand `panel_create`.
|
||||
export const ticketCommandData = applyCommandDescription(
|
||||
new SlashCommandBuilder()
|
||||
.setName('ticket')
|
||||
.addSubcommandGroup((group) =>
|
||||
applyCommandDescription(group.setName('panel'), 'ticket.panel.description').addSubcommand(
|
||||
(s) =>
|
||||
applyCommandDescription(s.setName('create'), 'ticket.panel.create.description')
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(o.setName('name').setRequired(true), 'ticket.panel.create.options.name')
|
||||
)
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(
|
||||
o.setName('description'),
|
||||
'ticket.panel.create.options.description'
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
applyCommandDescription(s.setName('panel_create'), 'ticket.panel.create.description')
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(o.setName('name').setRequired(true), 'ticket.panel.create.options.name')
|
||||
)
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(
|
||||
o.setName('description'),
|
||||
'ticket.panel.create.options.description'
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommandGroup((group) =>
|
||||
applyCommandDescription(group.setName('category'), 'ticket.category.description').addSubcommand(
|
||||
(s) =>
|
||||
applyCommandDescription(s.setName('create'), 'ticket.category.create.description')
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(
|
||||
o.setName('name').setRequired(true),
|
||||
'ticket.category.create.options.name'
|
||||
)
|
||||
)
|
||||
.addRoleOption((o) =>
|
||||
applyOptionDescription(
|
||||
o.setName('support_role').setRequired(true),
|
||||
'ticket.category.create.options.support_role'
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
applyCommandDescription(s.setName('category_create'), 'ticket.category.create.description')
|
||||
.addStringOption((o) =>
|
||||
applyOptionDescription(
|
||||
o.setName('name').setRequired(true),
|
||||
'ticket.category.create.options.name'
|
||||
)
|
||||
)
|
||||
.addRoleOption((o) =>
|
||||
applyOptionDescription(
|
||||
o.setName('support_role').setRequired(true),
|
||||
'ticket.category.create.options.support_role'
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSubcommand((s) =>
|
||||
applyCommandDescription(s.setName('close'), 'ticket.close.description').addStringOption((o) =>
|
||||
|
||||
@@ -41,10 +41,9 @@ const ticketCommand: SlashCommand = {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = interaction.options.getSubcommandGroup(false);
|
||||
const sub = interaction.options.getSubcommand(false);
|
||||
const sub = interaction.options.getSubcommand(true);
|
||||
|
||||
if (group === 'panel' && sub === 'create') {
|
||||
if (sub === 'panel_create') {
|
||||
if (!(await ensureManageGuild(interaction, locale))) {
|
||||
return;
|
||||
}
|
||||
@@ -75,7 +74,7 @@ const ticketCommand: SlashCommand = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'category' && sub === 'create') {
|
||||
if (sub === 'category_create') {
|
||||
if (!(await ensureManageGuild(interaction, locale))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export async function handleTicketInteraction(
|
||||
const parsedRate = parseRateButton(interaction.customId);
|
||||
if (parsedRate) {
|
||||
try {
|
||||
await rateTicket(context, parsedRate.ticketId, interaction.user.id, parsedRate.rating, locale);
|
||||
await rateTicket(context, parsedRate.ticketId, interaction.user.id, parsedRate.rating);
|
||||
await interaction.update({
|
||||
content: tf(locale, 'ticket.rated', { rating: parsedRate.rating }),
|
||||
components: []
|
||||
|
||||
@@ -333,7 +333,7 @@ async function fetchTicketMessages(
|
||||
}
|
||||
}
|
||||
|
||||
function buildRatingComponents(ticketId: string, locale: 'de' | 'en'): ActionRowBuilder<ButtonBuilder> {
|
||||
function buildRatingComponents(ticketId: string): ActionRowBuilder<ButtonBuilder> {
|
||||
const row = new ActionRowBuilder<ButtonBuilder>();
|
||||
for (let rating = 1; rating <= 5; rating += 1) {
|
||||
row.addComponents(
|
||||
@@ -704,7 +704,7 @@ export async function closeTicket(
|
||||
try {
|
||||
await opener.send({
|
||||
content: t(locale, 'ticket.ratePrompt'),
|
||||
components: [buildRatingComponents(ticket.id, locale)]
|
||||
components: [buildRatingComponents(ticket.id)]
|
||||
});
|
||||
} catch {
|
||||
// User may have DMs disabled
|
||||
@@ -729,8 +729,7 @@ export async function rateTicket(
|
||||
context: BotContext,
|
||||
ticketId: string,
|
||||
userId: string,
|
||||
rating: number,
|
||||
locale: 'de' | 'en'
|
||||
rating: number
|
||||
): Promise<void> {
|
||||
const ticket = await context.prisma.ticket.findUnique({ where: { id: ticketId } });
|
||||
if (!ticket || ticket.openerId !== userId) {
|
||||
|
||||
Reference in New Issue
Block a user