diff --git a/src/bot/commands/checkin/handlers/checkin-audit.ts b/src/bot/commands/checkin/handlers/checkin-audit.ts index 85bf8cf..10f1afc 100644 --- a/src/bot/commands/checkin/handlers/checkin-audit.ts +++ b/src/bot/commands/checkin/handlers/checkin-audit.ts @@ -1,13 +1,13 @@ import type { Command } from '@commands/command' import type { ChatInputCommandInteraction, Client } from 'discord.js' import { AUDIT_FLAME_CHANNEL, FLAMEWARDEN_ROLE } from '@config/discord' -import { CHECKIN_AUDIT_ID } from '@events/interaction-create/checkin/handlers/checkin-audit-modal' +import { CHECKIN_AUDIT_ID } from '@events/interaction-create/checkin/handlers/audit-modal' import { createCheckinReviewModal, encodeSnowflake, getCustomId } from '@utils/component' import { sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' import { log } from '@utils/logger' import { SlashCommandBuilder } from 'discord.js' -import { CheckinAudit } from '../../../events/interaction-create/checkin/validators/checkin-audit' +import { CheckinAudit } from '../../../events/interaction-create/checkin/validators/audit' export class CheckinAuditError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { diff --git a/src/bot/commands/checkin/handlers/checkin.ts b/src/bot/commands/checkin/handlers/checkin.ts index 432dc16..ab96010 100644 --- a/src/bot/commands/checkin/handlers/checkin.ts +++ b/src/bot/commands/checkin/handlers/checkin.ts @@ -1,8 +1,8 @@ import type { Command } from '@commands/command' import type { ChatInputCommandInteraction } from 'discord.js' import { CHECKIN_CHANNEL } from '@config/discord' -import { CHECKIN_ID } from '@events/interaction-create/checkin/handlers/checkin-modal' -import { Checkin } from '@events/interaction-create/checkin/validators/checkin' +import { CHECKIN_ID } from '@events/interaction-create/checkin/handlers/modal' +import { Checkin } from '@events/interaction-create/checkin/validators' import { encodeSnowflake, getCustomId } from '@utils/component' import { getAttachments, sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' diff --git a/src/bot/commands/checkin/validators/checkin-status.ts b/src/bot/commands/checkin/validators/checkin-status.ts index 102e750..f77da52 100644 --- a/src/bot/commands/checkin/validators/checkin-status.ts +++ b/src/bot/commands/checkin/validators/checkin-status.ts @@ -3,7 +3,7 @@ import type { Checkin as CheckinType } from '@type/checkin' import type { User } from '@type/user' import type { EmbedBuilder, Guild } from 'discord.js' import { FLAMEWARDEN_ROLE } from '@config/discord' -import { Checkin } from '@events/interaction-create/checkin/validators/checkin' +import { Checkin } from '@events/interaction-create/checkin/validators' import { createEmbed } from '@utils/component' import { DiscordAssert } from '@utils/discord' import { DUMMY } from '@utils/placeholder' diff --git a/src/bot/commands/index.ts b/src/bot/commands/index.ts index 8a897f7..06cf1c8 100644 --- a/src/bot/commands/index.ts +++ b/src/bot/commands/index.ts @@ -20,12 +20,14 @@ export async function registerCommands(client: Client) { client.commands = new Collection() for (const file of files) { + const { default: command } = await import(file) as { default: Command } const fileName = getModuleName(COMMAND_PATH, file) - log.info(`Registering command ${fileName}...`) try { - const { default: command } = await import(file) as { default: Command } - client.commands.set(command.data.name, command) + if (command) { + log.info(`Registering command ${fileName}...`) + client.commands.set(command.data.name, command) + } } catch (err: any) { const msg = err instanceof CommandError ? err.message : '❌ Something went wrong when importing the command' diff --git a/src/bot/events/client-ready/jobs/messages/reset-grinder-roles.ts b/src/bot/events/client-ready/jobs/messages/reset-grinder-roles.ts index a4119a9..a6a6e6a 100644 --- a/src/bot/events/client-ready/jobs/messages/reset-grinder-roles.ts +++ b/src/bot/events/client-ready/jobs/messages/reset-grinder-roles.ts @@ -1,5 +1,5 @@ import type { GuildMember } from 'discord.js' -import { FLAMEWARDEN_ROLE, IGNITE_PATH_CHANNEL } from '@config/discord' +import { AUDIT_FLAME_CHANNEL, FLAMEWARDEN_ROLE, IGNITE_PATH_CHANNEL } from '@config/discord' import { DiscordAssert } from '@utils/discord' export class ResetGrinderRolesMessage extends DiscordAssert { @@ -22,10 +22,11 @@ Api bukanlah padam karena kelemahan, melainkan karena ia tak disirami pada waktu Namun jangan berduka, jalan ini selalu terbuka bagi mereka yang bersedia memulai kembali. Apabila Tuan/Nona berkehendak menyalakan api kembali, silakan kembali ke <#${IGNITE_PATH_CHANNEL}> dan bangkitlah dari awal. *Aksaria menanti mereka yang konsisten.* - + `, + GoodByeNotes: ` > Apabila *check-in* Tuan/Nona masih berada dalam status menunggu peninjauan (*waiting*) dan belum memperoleh keputusan hingga mendekati pergantian hari, maka dengan ini disampaikan ketentuan berikut: > 1. Jangan terlebih dahulu memasuki ⁠<#${IGNITE_PATH_CHANNEL}>, demi menjaga ketertiban alur peninjauan. -> 2. Silakan menjalankan perintah **\`/checkin-status\`** untuk menampilkan status *check-in* terakhir Tuan/Nona. +> 2. Silakan menjalankan perintah **\`/checkin-status\`** pada <#${AUDIT_FLAME_CHANNEL}> untuk menampilkan status *check-in* terakhir Tuan/Nona. > 3. Setelah pesan status tersebut muncul, berikan reaksi “❓” pada pesan tersebut. > 4. Dari reaksi tersebut, sebuah *thread* akan tercipta secara otomatis sebagai ruang klarifikasi dan komunikasi dengan <@&${FLAMEWARDEN_ROLE}>. > ⏳ Batas waktu penantian atas status WAITING adalah maksimal 1×24 jam sejak *check-in* diajukan. diff --git a/src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts b/src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts index 287406c..75e1017 100644 --- a/src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts +++ b/src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts @@ -1,14 +1,42 @@ import type { PrismaClient } from '@generatedDB/client' import type { CheckinStreak } from '@type/checkin-streak' import type { User } from '@type/user' -import type { Guild, GuildMember, TextChannel } from 'discord.js' +import type { Guild, GuildMember, Interaction, TextChannel } from 'discord.js' import { getGrindRoles, GRINDER_ROLE } from '@config/discord' +import { GOODBYE_NOTE_BUTTON_ID, ResetGrinderRolesButtonError } from '@events/interaction-create/jobs/handlers/reset-grinder-roles-button' +import { decodeSnowflakes, encodeSnowflake, getCustomId } from '@utils/component' import { isDateToday, isDateYesterday } from '@utils/date' -import { sendAsBot } from '@utils/discord' +import { DiscordAssert, sendAsBot } from '@utils/discord' import { log } from '@utils/logger' +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js' import { ResetGrinderRolesMessage } from '../messages/reset-grinder-roles' export class ResetGrinderRoles extends ResetGrinderRolesMessage { + static override BASE_PERMS = [ + ...DiscordAssert.BASE_PERMS, + ] + + static getButtonId(interaction: Interaction, customId: string) { + const [prefix, guildId] = decodeSnowflakes(customId) + + if (!guildId) + throw new ResetGrinderRolesButtonError(this.ERR.GuildMissing) + if (interaction.guildId !== guildId) + throw new ResetGrinderRolesButtonError(this.ERR.NotGuild) + + return { prefix, guildId } + } + + static generateButton(guildId: string): ActionRowBuilder { + const noteButtonId = getCustomId([GOODBYE_NOTE_BUTTON_ID, encodeSnowflake(guildId)]) + const noteButton = new ButtonBuilder() + .setCustomId(noteButtonId) + .setLabel('📜 Ketentuan Peninjauan Api') + .setStyle(ButtonStyle.Primary) + + return new ActionRowBuilder().addComponents(noteButton) + } + static hasValidCheckin(checkin?: { created_at: Date, status: string }): boolean { if (!checkin) return false @@ -35,22 +63,23 @@ export class ResetGrinderRoles extends ResetGrinderRolesMessage { static async validateUsers(prisma: PrismaClient, guild: Guild, channel: TextChannel, users: User[]) { for (const user of users) { - const lastCheckin = user.checkins?.[0] - if (this.hasValidCheckin(lastCheckin)) - continue - const checkinStreak = user.checkin_streaks?.[0] if (!checkinStreak) continue + const lastCheckin = checkinStreak.checkins?.[0] + if (this.hasValidCheckin(lastCheckin)) + continue + const member = await guild.members.fetch(user.discord_id) await this.removeGrinderRoles(member) await this.breakCheckinStreakAt(prisma, checkinStreak) + const button = this.generateButton(guild.id) await sendAsBot( null, channel, - { content: ResetGrinderRoles.MSG.GoodBye(member), allowedMentions: { users: [member.id], roles: [] } }, + { content: ResetGrinderRoles.MSG.GoodBye(member), components: [button], allowedMentions: { users: [member.id], roles: [] } }, ) log.info(this.MSG.RemoveGrinderRoleFrom(member)) @@ -61,14 +90,6 @@ export class ResetGrinderRoles extends ResetGrinderRolesMessage { const users = await prisma.user.findMany({ select: { discord_id: true, - checkins: { - select: { - status: true, - created_at: true, - }, - orderBy: { created_at: 'desc' }, - take: 1, - }, checkin_streaks: { orderBy: { first_date: 'desc' }, take: 1, diff --git a/src/bot/events/client-ready/say-hello/handlers/say-hello.ts b/src/bot/events/client-ready/say-hello/handlers/index.ts similarity index 93% rename from src/bot/events/client-ready/say-hello/handlers/say-hello.ts rename to src/bot/events/client-ready/say-hello/handlers/index.ts index b7add49..90b6e74 100644 --- a/src/bot/events/client-ready/say-hello/handlers/say-hello.ts +++ b/src/bot/events/client-ready/say-hello/handlers/index.ts @@ -3,7 +3,7 @@ import type { Client } from 'discord.js' import { DiscordBaseError } from '@utils/discord/error' import { log } from '@utils/logger' import { Events } from 'discord.js' -import { SayHello } from '../validators/say-hello' +import { SayHello } from '../validators' export class SayHelloError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { diff --git a/src/bot/events/client-ready/say-hello/messages/say-hello.ts b/src/bot/events/client-ready/say-hello/messages/index.ts similarity index 100% rename from src/bot/events/client-ready/say-hello/messages/say-hello.ts rename to src/bot/events/client-ready/say-hello/messages/index.ts diff --git a/src/bot/events/client-ready/say-hello/validators/index.ts b/src/bot/events/client-ready/say-hello/validators/index.ts new file mode 100644 index 0000000..d4d8ce2 --- /dev/null +++ b/src/bot/events/client-ready/say-hello/validators/index.ts @@ -0,0 +1,4 @@ +import { SayHelloMessage } from '../messages' + +export class SayHello extends SayHelloMessage { +} diff --git a/src/bot/events/client-ready/say-hello/validators/say-hello.ts b/src/bot/events/client-ready/say-hello/validators/say-hello.ts deleted file mode 100644 index 68db5c8..0000000 --- a/src/bot/events/client-ready/say-hello/validators/say-hello.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { SayHelloMessage } from '../messages/say-hello' - -export class SayHello extends SayHelloMessage { -} diff --git a/src/bot/events/guild-member-update/grinder-role/handlers/grinder-role.ts b/src/bot/events/guild-member-update/grinder-role/handlers/index.ts similarity index 96% rename from src/bot/events/guild-member-update/grinder-role/handlers/grinder-role.ts rename to src/bot/events/guild-member-update/grinder-role/handlers/index.ts index 078f7cb..fc8bea1 100644 --- a/src/bot/events/guild-member-update/grinder-role/handlers/grinder-role.ts +++ b/src/bot/events/guild-member-update/grinder-role/handlers/index.ts @@ -5,7 +5,7 @@ import { getChannel, sendAsBot } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' import { log } from '@utils/logger' import { Events } from 'discord.js' -import { GrinderRole } from '../validators/grinder-role' +import { GrinderRole } from '../validators' export class GrinderRoleError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { diff --git a/src/bot/events/guild-member-update/grinder-role/messages/grinder-role.ts b/src/bot/events/guild-member-update/grinder-role/messages/index.ts similarity index 100% rename from src/bot/events/guild-member-update/grinder-role/messages/grinder-role.ts rename to src/bot/events/guild-member-update/grinder-role/messages/index.ts diff --git a/src/bot/events/guild-member-update/grinder-role/validators/grinder-role.ts b/src/bot/events/guild-member-update/grinder-role/validators/index.ts similarity index 74% rename from src/bot/events/guild-member-update/grinder-role/validators/grinder-role.ts rename to src/bot/events/guild-member-update/grinder-role/validators/index.ts index 61330ae..9fcde8b 100644 --- a/src/bot/events/guild-member-update/grinder-role/validators/grinder-role.ts +++ b/src/bot/events/guild-member-update/grinder-role/validators/index.ts @@ -1,5 +1,5 @@ import { DiscordAssert } from '@utils/discord' -import { GrinderRoleMessage } from '../messages/grinder-role' +import { GrinderRoleMessage } from '../messages' export class GrinderRole extends GrinderRoleMessage { static override BASE_PERMS = [ diff --git a/src/bot/events/index.ts b/src/bot/events/index.ts index 817cda5..b1de852 100644 --- a/src/bot/events/index.ts +++ b/src/bot/events/index.ts @@ -19,16 +19,18 @@ export async function registerEvents(client: Client) { for (const file of files) { const { default: event } = await import(file) as { default: Event } const fileName = getModuleName(EVENT_PATH, file) - log.info(`Registering event ${fileName}...`) try { - if (event.once) { - client.once(event.name, (...args) => event.exec(client, ...args)) - } - else { - client.on(event.name, (...args) => { - event.exec(client, ...args) - }) + if (event) { + log.info(`Registering event ${fileName}...`) + if (event.once) { + client.once(event.name, (...args) => event.exec(client, ...args)) + } + else { + client.on(event.name, (...args) => { + event.exec(client, ...args) + }) + } } } catch (err: any) { diff --git a/src/bot/events/interaction-create/checkin/handlers/checkin-approve-button.ts b/src/bot/events/interaction-create/checkin/handlers/approve-button.ts similarity index 69% rename from src/bot/events/interaction-create/checkin/handlers/checkin-approve-button.ts rename to src/bot/events/interaction-create/checkin/handlers/approve-button.ts index 29c0199..3a7522e 100644 --- a/src/bot/events/interaction-create/checkin/handlers/checkin-approve-button.ts +++ b/src/bot/events/interaction-create/checkin/handlers/approve-button.ts @@ -1,13 +1,11 @@ -import type { Event } from '@events/event' -import type { Client, Interaction, TextChannel } from 'discord.js' +import type { TextChannel } from 'discord.js' import { FLAMEWARDEN_ROLE } from '@config/discord' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { generateCustomId } from '@utils/component' import { sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { Events } from 'discord.js' -import { Checkin } from '../validators/checkin' +import { Checkin } from '../validators' export class CheckinApproveButtonError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { @@ -17,17 +15,14 @@ export class CheckinApproveButtonError extends DiscordBaseError { export const CHECKIN_APPROVE_BUTTON_ID = `${generateCustomId(EVENT_PATH, __filename)}` -export default { - name: Events.InteractionCreate, - desc: 'Handles check-in approve button interactions and approves user check-in.', - async exec(client: Client, interaction: Interaction) { +registerInteractionHandler({ + desc: 'Approves a user check-in from the approve button.', + id: CHECKIN_APPROVE_BUTTON_ID, + errorTag: () => `${CHECKIN_APPROVE_BUTTON_ID}: ${Checkin.ERR.UnexpectedButton}`, + async exec(client, interaction) { if (!interaction.isButton()) return - const isValidComponent = Checkin.assertComponentId(interaction.customId, CHECKIN_APPROVE_BUTTON_ID) - if (!isValidComponent) - return - try { await interaction.deferUpdate() @@ -54,7 +49,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${CHECKIN_APPROVE_BUTTON_ID}: ${Checkin.ERR.UnexpectedButton}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/checkin/handlers/checkin-audit-modal.ts b/src/bot/events/interaction-create/checkin/handlers/audit-modal.ts similarity index 77% rename from src/bot/events/interaction-create/checkin/handlers/checkin-audit-modal.ts rename to src/bot/events/interaction-create/checkin/handlers/audit-modal.ts index 657b2d2..80ccaee 100644 --- a/src/bot/events/interaction-create/checkin/handlers/checkin-audit-modal.ts +++ b/src/bot/events/interaction-create/checkin/handlers/audit-modal.ts @@ -1,15 +1,13 @@ -import type { Event } from '@events/event' import type { CheckinStatusType } from '@type/checkin' -import type { Client, Interaction, TextChannel } from 'discord.js' +import type { TextChannel } from 'discord.js' import { CHECKIN_CHANNEL, FLAMEWARDEN_ROLE } from '@config/discord' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { generateCustomId } from '@utils/component' import { sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { Events } from 'discord.js' -import { Checkin } from '../validators/checkin' -import { CheckinAudit } from '../validators/checkin-audit' +import { Checkin } from '../validators' +import { CheckinAudit } from '../validators/audit' export class CheckinAuditModalError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { @@ -19,17 +17,14 @@ export class CheckinAuditModalError extends DiscordBaseError { export const CHECKIN_AUDIT_ID = generateCustomId(EVENT_PATH, __filename) -export default { - name: Events.InteractionCreate, +registerInteractionHandler({ desc: 'Handles modal submissions for check-in audit modal forms.', - async exec(client: Client, interaction: Interaction) { + id: CHECKIN_AUDIT_ID, + errorTag: () => `${CHECKIN_AUDIT_ID}: ${CheckinAudit.ERR.UnexpectedModal}`, + async exec(client, interaction) { if (!interaction.isModalSubmit()) return - const isValidComponent = CheckinAudit.assertComponentId(interaction.customId, CHECKIN_AUDIT_ID) - if (!isValidComponent) - return - try { await interaction.deferUpdate() @@ -59,7 +54,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${CHECKIN_AUDIT_ID}: ${CheckinAudit.ERR.UnexpectedModal}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/checkin/handlers/checkin-custom-button-modal.ts b/src/bot/events/interaction-create/checkin/handlers/custom-button-modal.ts similarity index 76% rename from src/bot/events/interaction-create/checkin/handlers/checkin-custom-button-modal.ts rename to src/bot/events/interaction-create/checkin/handlers/custom-button-modal.ts index 46363cc..5dba028 100644 --- a/src/bot/events/interaction-create/checkin/handlers/checkin-custom-button-modal.ts +++ b/src/bot/events/interaction-create/checkin/handlers/custom-button-modal.ts @@ -1,14 +1,12 @@ -import type { Event } from '@events/event' import type { CheckinStatusType } from '@type/checkin' -import type { Client, Interaction, TextChannel } from 'discord.js' +import type { TextChannel } from 'discord.js' import { FLAMEWARDEN_ROLE } from '@config/discord' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { generateCustomId } from '@utils/component' import { sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { Events } from 'discord.js' -import { Checkin } from '../validators/checkin' +import { Checkin } from '../validators' export class CheckinCustomButtonModalError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { @@ -18,17 +16,14 @@ export class CheckinCustomButtonModalError extends DiscordBaseError { export const CHECKIN_CUSTOM_BUTTON_MODAL_ID = generateCustomId(EVENT_PATH, __filename) -export default { - name: Events.InteractionCreate, +registerInteractionHandler({ desc: 'Handles modal submissions for the custom check-in review modal.', - async exec(client: Client, interaction: Interaction) { + id: CHECKIN_CUSTOM_BUTTON_MODAL_ID, + errorTag: () => `${CHECKIN_CUSTOM_BUTTON_MODAL_ID}: ${Checkin.ERR.UnexpectedModal}`, + async exec(client, interaction) { if (!interaction.isModalSubmit()) return - const isValidComponent = Checkin.assertComponentId(interaction.customId, CHECKIN_CUSTOM_BUTTON_MODAL_ID) - if (!isValidComponent) - return - try { await interaction.deferUpdate() @@ -60,7 +55,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${CHECKIN_CUSTOM_BUTTON_MODAL_ID}: ${Checkin.ERR.UnexpectedModal}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/checkin/handlers/checkin-custom-button.ts b/src/bot/events/interaction-create/checkin/handlers/custom-button.ts similarity index 73% rename from src/bot/events/interaction-create/checkin/handlers/checkin-custom-button.ts rename to src/bot/events/interaction-create/checkin/handlers/custom-button.ts index f75b367..636ab94 100644 --- a/src/bot/events/interaction-create/checkin/handlers/checkin-custom-button.ts +++ b/src/bot/events/interaction-create/checkin/handlers/custom-button.ts @@ -1,14 +1,12 @@ -import type { Event } from '@events/event' -import type { Client, Interaction, TextChannel } from 'discord.js' +import type { TextChannel } from 'discord.js' import { FLAMEWARDEN_ROLE } from '@config/discord' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { createCheckinReviewModal, encodeSnowflake, generateCustomId, getCustomId } from '@utils/component' import { sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { Events } from 'discord.js' -import { Checkin } from '../validators/checkin' -import { CHECKIN_CUSTOM_BUTTON_MODAL_ID } from './checkin-custom-button-modal' +import { Checkin } from '../validators' +import { CHECKIN_CUSTOM_BUTTON_MODAL_ID } from './custom-button-modal' export class CheckinCustomButtonError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { @@ -18,17 +16,14 @@ export class CheckinCustomButtonError extends DiscordBaseError { export const CHECKIN_CUSTOM_BUTTON_ID = `${generateCustomId(EVENT_PATH, __filename)}` -export default { - name: Events.InteractionCreate, +registerInteractionHandler({ desc: 'Opens review modal for a check-in', - async exec(client: Client, interaction: Interaction) { + id: CHECKIN_CUSTOM_BUTTON_ID, + errorTag: () => `${CHECKIN_CUSTOM_BUTTON_ID}: ${Checkin.ERR.UnexpectedButton}`, + async exec(client, interaction) { if (!interaction.isButton()) return - const isValid = Checkin.assertComponentId(interaction.customId, CHECKIN_CUSTOM_BUTTON_ID) - if (!isValid) - return - try { if (!interaction.inCachedGuild()) throw new CheckinCustomButtonError(Checkin.ERR.NotGuild) @@ -54,7 +49,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${CHECKIN_CUSTOM_BUTTON_ID}: ${Checkin.ERR.UnexpectedButton}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/checkin/handlers/checkin-modal.ts b/src/bot/events/interaction-create/checkin/handlers/modal.ts similarity index 82% rename from src/bot/events/interaction-create/checkin/handlers/checkin-modal.ts rename to src/bot/events/interaction-create/checkin/handlers/modal.ts index 14db89d..1ca0a8a 100644 --- a/src/bot/events/interaction-create/checkin/handlers/checkin-modal.ts +++ b/src/bot/events/interaction-create/checkin/handlers/modal.ts @@ -1,13 +1,11 @@ -import type { Event } from '@events/event' -import type { Attachment, Client, GuildMember, Interaction, Message } from 'discord.js' +import type { Attachment, GuildMember, Message } from 'discord.js' import { FLAMEWARDEN_ROLE } from '@config/discord' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { generateCustomId, tempStore } from '@utils/component' import { sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { Events } from 'discord.js' -import { Checkin } from '../validators/checkin' +import { Checkin } from '../validators' export class CheckinModalError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { @@ -17,17 +15,14 @@ export class CheckinModalError extends DiscordBaseError { export const CHECKIN_ID = generateCustomId(EVENT_PATH, __filename) -export default { - name: Events.InteractionCreate, +registerInteractionHandler({ desc: 'Handles modal submissions for check-in modal forms.', - async exec(client: Client, interaction: Interaction) { + id: CHECKIN_ID, + errorTag: () => `${CHECKIN_ID}: ${Checkin.ERR.UnexpectedModal}`, + async exec(client, interaction) { if (!interaction.isModalSubmit()) return - const isValidComponent = Checkin.assertComponentId(interaction.customId, CHECKIN_ID) - if (!isValidComponent) - return - try { if (!interaction.inCachedGuild()) throw new CheckinModalError(Checkin.ERR.NotGuild) @@ -81,7 +76,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${CHECKIN_ID}: ${Checkin.ERR.UnexpectedModal}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/checkin/handlers/checkin-reject-button.ts b/src/bot/events/interaction-create/checkin/handlers/reject-button.ts similarity index 73% rename from src/bot/events/interaction-create/checkin/handlers/checkin-reject-button.ts rename to src/bot/events/interaction-create/checkin/handlers/reject-button.ts index 0a728da..10884e6 100644 --- a/src/bot/events/interaction-create/checkin/handlers/checkin-reject-button.ts +++ b/src/bot/events/interaction-create/checkin/handlers/reject-button.ts @@ -1,13 +1,11 @@ -import type { Event } from '@events/event' -import type { Client, Interaction, TextChannel } from 'discord.js' +import type { TextChannel } from 'discord.js' import { FLAMEWARDEN_ROLE } from '@config/discord' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { generateCustomId } from '@utils/component' import { sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { Events } from 'discord.js' -import { Checkin } from '../validators/checkin' +import { Checkin } from '../validators' export class CheckinRejectButtonError extends DiscordBaseError { constructor(message: string, options?: { cause?: unknown }) { @@ -17,17 +15,14 @@ export class CheckinRejectButtonError extends DiscordBaseError { export const CHECKIN_REJECT_BUTTON_ID = `${generateCustomId(EVENT_PATH, __filename)}` -export default { - name: Events.InteractionCreate, +registerInteractionHandler({ desc: 'Handles check-in reject button interactions and rejects user check-in.', - async exec(client: Client, interaction: Interaction) { + id: CHECKIN_REJECT_BUTTON_ID, + errorTag: () => `${CHECKIN_REJECT_BUTTON_ID}: ${Checkin.ERR.UnexpectedButton}`, + async exec(client, interaction) { if (!interaction.isButton()) return - const isValidComponent = Checkin.assertComponentId(interaction.customId, CHECKIN_REJECT_BUTTON_ID) - if (!isValidComponent) - return - try { await interaction.deferUpdate() @@ -54,7 +49,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${CHECKIN_REJECT_BUTTON_ID}: ${Checkin.ERR.UnexpectedButton}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/checkin/messages/checkin-audit.ts b/src/bot/events/interaction-create/checkin/messages/audit.ts similarity index 100% rename from src/bot/events/interaction-create/checkin/messages/checkin-audit.ts rename to src/bot/events/interaction-create/checkin/messages/audit.ts diff --git a/src/bot/events/interaction-create/checkin/messages/checkin.ts b/src/bot/events/interaction-create/checkin/messages/index.ts similarity index 100% rename from src/bot/events/interaction-create/checkin/messages/checkin.ts rename to src/bot/events/interaction-create/checkin/messages/index.ts diff --git a/src/bot/events/interaction-create/checkin/validators/checkin-audit.ts b/src/bot/events/interaction-create/checkin/validators/audit.ts similarity index 95% rename from src/bot/events/interaction-create/checkin/validators/checkin-audit.ts rename to src/bot/events/interaction-create/checkin/validators/audit.ts index efea943..6881cdd 100644 --- a/src/bot/events/interaction-create/checkin/validators/checkin-audit.ts +++ b/src/bot/events/interaction-create/checkin/validators/audit.ts @@ -7,9 +7,9 @@ import { decodeSnowflakes } from '@utils/component' import { isDateToday } from '@utils/date' import { DiscordAssert } from '@utils/discord' import { PermissionsBitField } from 'discord.js' -import { CheckinAuditModalError } from '../handlers/checkin-audit-modal' -import { CheckinAuditMessage } from '../messages/checkin-audit' -import { Checkin } from './checkin' +import { CheckinAuditModalError } from '../handlers/audit-modal' +import { CheckinAuditMessage } from '../messages/audit' +import { Checkin } from '.' export class CheckinAudit extends CheckinAuditMessage { static override BASE_PERMS = [ diff --git a/src/bot/events/interaction-create/checkin/validators/checkin.ts b/src/bot/events/interaction-create/checkin/validators/index.ts similarity index 97% rename from src/bot/events/interaction-create/checkin/validators/checkin.ts rename to src/bot/events/interaction-create/checkin/validators/index.ts index 3c4244e..b0c56ed 100644 --- a/src/bot/events/interaction-create/checkin/validators/checkin.ts +++ b/src/bot/events/interaction-create/checkin/validators/index.ts @@ -8,19 +8,19 @@ import type { Attachment, EmbedBuilder, Guild, GuildMember, Interaction, Message import crypto from 'node:crypto' import { CheckinError } from '@commands/checkin/handlers/checkin' import { AURA_FARMING_CHANNEL, CHECKIN_CHANNEL, GRINDER_ROLE } from '@config/discord' -import { SubmittedCheckinError } from '@events/message-reaction-add/checkin/handlers/submitted-checkin' +import { SubmittedCheckinError } from '@events/message-reaction-add/checkin/handlers/submitted' import { createEmbed, decodeSnowflakes, encodeSnowflake, getCustomId } from '@utils/component' import { isDateToday, isDateYesterday } from '@utils/date' import { DiscordAssert, getChannel, sendAsBot } from '@utils/discord' import { attachNewGrindRole, getGrindRoleByStreakCount } from '@utils/discord/roles' import { DUMMY } from '@utils/placeholder' import { ActionRowBuilder, ButtonBuilder, ButtonStyle, messageLink, PermissionsBitField } from 'discord.js' -import { CHECKIN_APPROVE_BUTTON_ID } from '../handlers/checkin-approve-button' -import { CHECKIN_CUSTOM_BUTTON_ID } from '../handlers/checkin-custom-button' -import { CheckinCustomButtonModalError } from '../handlers/checkin-custom-button-modal' -import { CheckinModalError } from '../handlers/checkin-modal' -import { CHECKIN_REJECT_BUTTON_ID } from '../handlers/checkin-reject-button' -import { CheckinMessage } from '../messages/checkin' +import { CHECKIN_APPROVE_BUTTON_ID } from '../handlers/approve-button' +import { CHECKIN_CUSTOM_BUTTON_ID } from '../handlers/custom-button' +import { CheckinCustomButtonModalError } from '../handlers/custom-button-modal' +import { CheckinModalError } from '../handlers/modal' +import { CHECKIN_REJECT_BUTTON_ID } from '../handlers/reject-button' +import { CheckinMessage } from '../messages' export class Checkin extends CheckinMessage { static override BASE_PERMS = [ @@ -228,7 +228,7 @@ export class Checkin extends CheckinMessage { if (!checkin) throw new SubmittedCheckinError(this.ERR.PlainMessage) - await Checkin.setAttachments(prisma, checkin) + await this.setAttachments(prisma, checkin) return checkin } diff --git a/src/bot/events/interaction-create/embed/handlers/role-grant-create-button.ts b/src/bot/events/interaction-create/embed/handlers/role-grant-create-button.ts index 3db3a53..269e762 100644 --- a/src/bot/events/interaction-create/embed/handlers/role-grant-create-button.ts +++ b/src/bot/events/interaction-create/embed/handlers/role-grant-create-button.ts @@ -1,11 +1,9 @@ -import type { Event } from '@events/event' -import type { GuildMember, Interaction, TextChannel } from 'discord.js' +import type { GuildMember, TextChannel } from 'discord.js' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { generateCustomId } from '@utils/component' import { getRole, sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { Events } from 'discord.js' import { RoleGrantCreate } from '../validators/role-grant-create' export class EmbedRoleGrantButtonError extends DiscordBaseError { @@ -16,17 +14,14 @@ export class EmbedRoleGrantButtonError extends DiscordBaseError { export const EMBED_ROLE_GRANT_CREATE_BUTTON_ID = generateCustomId(EVENT_PATH, __filename) -export default { - name: Events.InteractionCreate, +registerInteractionHandler({ desc: 'Handles role assignment button interactions and adds a role for users.', - async exec(_, interaction: Interaction) { + id: EMBED_ROLE_GRANT_CREATE_BUTTON_ID, + errorTag: () => `${EMBED_ROLE_GRANT_CREATE_BUTTON_ID}: ${RoleGrantCreate.ERR.UnexpectedButton}`, + async exec(_, interaction) { if (!interaction.isButton()) return - const isValidComponent = RoleGrantCreate.assertComponentId(interaction.customId, EMBED_ROLE_GRANT_CREATE_BUTTON_ID) - if (!isValidComponent) - return - try { if (!interaction.inCachedGuild()) throw new EmbedRoleGrantButtonError(RoleGrantCreate.ERR.NotGuild) @@ -49,7 +44,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${EMBED_ROLE_GRANT_CREATE_BUTTON_ID}: ${RoleGrantCreate.ERR.UnexpectedButton}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/embed/handlers/role-grant-create-modal.ts b/src/bot/events/interaction-create/embed/handlers/role-grant-create-modal.ts index 099683f..94ffed5 100644 --- a/src/bot/events/interaction-create/embed/handlers/role-grant-create-modal.ts +++ b/src/bot/events/interaction-create/embed/handlers/role-grant-create-modal.ts @@ -1,11 +1,9 @@ -import type { Event } from '@events/event' -import type { Interaction } from 'discord.js' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { createEmbed, encodeSnowflake, generateCustomId, getCustomId } from '@utils/component' import { getChannel, getRole, sendAsBot, sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Events } from 'discord.js' +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js' import { RoleGrantCreate } from '../validators/role-grant-create' import { EMBED_ROLE_GRANT_CREATE_BUTTON_ID } from './role-grant-create-button' @@ -17,17 +15,14 @@ export class EmbedRoleGrantModalError extends DiscordBaseError { export const EMBED_ROLE_GRANT_CREATE_MODAL_ID = generateCustomId(EVENT_PATH, __filename) -export default { - name: Events.InteractionCreate, +registerInteractionHandler({ desc: 'Handles modal submissions for creating an embed with a role-grant button.', - async exec(_, interaction: Interaction) { + id: EMBED_ROLE_GRANT_CREATE_MODAL_ID, + errorTag: () => `${EMBED_ROLE_GRANT_CREATE_MODAL_ID}: ${RoleGrantCreate.ERR.UnexpectedModal}`, + async exec(_, interaction) { if (!interaction.isModalSubmit()) return - const isValidComponent = RoleGrantCreate.assertComponentId(interaction.customId, EMBED_ROLE_GRANT_CREATE_MODAL_ID) - if (!isValidComponent) - return - try { if (!interaction.inCachedGuild()) throw new EmbedRoleGrantModalError(RoleGrantCreate.ERR.NotGuild) @@ -68,7 +63,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${EMBED_ROLE_GRANT_CREATE_MODAL_ID}: ${RoleGrantCreate.ERR.UnexpectedModal}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/entry.ts b/src/bot/events/interaction-create/entry.ts new file mode 100644 index 0000000..12b2530 --- /dev/null +++ b/src/bot/events/interaction-create/entry.ts @@ -0,0 +1,29 @@ +import type { Event } from '@events/event' +import type { Interaction } from 'discord.js' +import { ARCHFYRE_ROLE } from '@config/discord' +import { decodeSnowflakes } from '@utils/component' +import { sendReply } from '@utils/discord' +import { log } from '@utils/logger' +import { Events } from 'discord.js' +import { interactionHandlers } from './registry' + +export default { + name: Events.InteractionCreate, + desc: 'Handles Discord InteractionCreate events and delegates them to registered handlers.', + async exec(client, interaction: Interaction) { + if ('customId' in interaction && interaction.customId) { + const [prefix] = decodeSnowflakes(interaction.customId) + + const handler = interactionHandlers.get(prefix) + if (handler) { + try { + await handler.exec(client, interaction) + } + catch (err) { + await sendReply(interaction, `❓ Something weird happen... kindly contact <@&${ARCHFYRE_ROLE}> :)`) + log.error(`Interaction handler failed ${handler.errorTag()}: ${err}`) + } + } + } + }, +} as Event diff --git a/src/bot/events/interaction-create/execute-command.ts b/src/bot/events/interaction-create/execute-command.ts deleted file mode 100644 index 83484a7..0000000 --- a/src/bot/events/interaction-create/execute-command.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Command } from '@commands/command' -import type { Event } from '@events/event' -import type { Client, Interaction } from 'discord.js' -import { Events, MessageFlags } from 'discord.js' - -export default { - name: Events.InteractionCreate, - desc: 'Executing a command when an interaction is created.', - async exec(client: Client, interaction: Interaction) { - if (!interaction.isChatInputCommand()) - return - const command: Command | undefined = interaction.client.commands.get(interaction.commandName) - if (!command) { - console.error(`No command matching ${interaction.commandName} was found.`) - return - } - - try { - await command.execute(client, interaction) - } - catch (error) { - console.error(error) - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }) - } - else { - await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }) - } - } - }, -} as Event diff --git a/src/bot/events/interaction-create/execute/handlers/command.ts b/src/bot/events/interaction-create/execute/handlers/command.ts new file mode 100644 index 0000000..9052453 --- /dev/null +++ b/src/bot/events/interaction-create/execute/handlers/command.ts @@ -0,0 +1,35 @@ +import type { Command } from '@commands/command' +import { generateCustomId } from '@utils/component' +import { sendReply } from '@utils/discord' +import { DiscordBaseError } from '@utils/discord/error' +import { EVENT_PATH } from '../../..' +import { registerInteractionHandler } from '../../registry' +import { ExecuteCommand } from '../validators/command' + +export class ExecuteCommandError extends DiscordBaseError { + constructor(message: string, options?: { cause?: unknown }) { + super('ExecuteCommandError', message, options) + } +} + +export const EXECUTE_COMMAND_ID = `${generateCustomId(EVENT_PATH, __filename)}` + +registerInteractionHandler({ + desc: 'Executing a command when an interaction is created.', + id: EXECUTE_COMMAND_ID, + errorTag: () => `${EXECUTE_COMMAND_ID}`, + async exec(client, interaction) { + if (!interaction.isChatInputCommand()) + return + + try { + const command: Command = ExecuteCommand.getCommand(interaction) + await command.execute(client, interaction) + } + catch (err: any) { + if (err instanceof DiscordBaseError) + await sendReply(interaction, err.message) + else throw err + } + }, +}) diff --git a/src/bot/events/interaction-create/execute/messages/command.ts b/src/bot/events/interaction-create/execute/messages/command.ts new file mode 100644 index 0000000..4bc5cfe --- /dev/null +++ b/src/bot/events/interaction-create/execute/messages/command.ts @@ -0,0 +1,9 @@ +import { DiscordAssert } from '@utils/discord' + +export class ExecuteCommandMessage extends DiscordAssert { + static override readonly ERR = { + ...DiscordAssert.ERR, + NoMatchingCommand: (commandName: string) => `❌ No command matching ${commandName} was found`, + UnexpectedExecuteCommand: '❌ Something went wrong during execute command', + } +} diff --git a/src/bot/events/interaction-create/execute/validators/command.ts b/src/bot/events/interaction-create/execute/validators/command.ts new file mode 100644 index 0000000..c63a32d --- /dev/null +++ b/src/bot/events/interaction-create/execute/validators/command.ts @@ -0,0 +1,22 @@ +import type { Command } from '@commands/command' +import type { ChatInputCommandInteraction } from 'discord.js' +import { ExecuteCommandError } from '@events/interaction-create/execute/handlers/command' +import { DiscordAssert } from '@utils/discord' +import { PermissionsBitField } from 'discord.js' +import { ExecuteCommandMessage } from '../messages/command' + +export class ExecuteCommand extends ExecuteCommandMessage { + static override BASE_PERMS = [ + ...DiscordAssert.BASE_PERMS, + PermissionsBitField.Flags.UseApplicationCommands, + ] + + static getCommand(interaction: ChatInputCommandInteraction) { + const command: Command | undefined = interaction.client.commands.get(interaction.commandName) + if (!command) { + throw new ExecuteCommandError(this.ERR.NoMatchingCommand(interaction.commandName)) + } + + return command + } +} diff --git a/src/bot/events/interaction-create/jobs/handlers/reset-grinder-roles-button.ts b/src/bot/events/interaction-create/jobs/handlers/reset-grinder-roles-button.ts new file mode 100644 index 0000000..e9ff486 --- /dev/null +++ b/src/bot/events/interaction-create/jobs/handlers/reset-grinder-roles-button.ts @@ -0,0 +1,40 @@ +import type { TextChannel } from 'discord.js' +import { ResetGrinderRoles } from '@events/client-ready/jobs/validators/reset-grinder-roles' +import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' +import { generateCustomId } from '@utils/component' +import { sendReply } from '@utils/discord' +import { DiscordBaseError } from '@utils/discord/error' + +export class ResetGrinderRolesButtonError extends DiscordBaseError { + constructor(message: string, options?: { cause?: unknown }) { + super('ResetGrinderRolesButtonError', message, options) + } +} + +export const GOODBYE_NOTE_BUTTON_ID = `${generateCustomId(EVENT_PATH, __filename)}` + +registerInteractionHandler({ + desc: 'Opens goodbye note modal for users losing Grinder roles.', + id: GOODBYE_NOTE_BUTTON_ID, + errorTag: () => `${GOODBYE_NOTE_BUTTON_ID}: ${ResetGrinderRoles.ERR.UnexpectedButton}`, + async exec(_, interaction) { + if (!interaction.isButton()) + return + + try { + if (!interaction.inCachedGuild()) + throw new ResetGrinderRolesButtonError(ResetGrinderRoles.ERR.NotGuild) + + const channel = interaction.channel as TextChannel + ResetGrinderRoles.assertMissPerms(interaction.client.user, channel) + + await sendReply(interaction, ResetGrinderRoles.MSG.GoodByeNotes) + } + catch (err: any) { + if (err instanceof DiscordBaseError) + await sendReply(interaction, err.message) + else throw err + } + }, +}) diff --git a/src/bot/events/interaction-create/message/handlers/send-modal.ts b/src/bot/events/interaction-create/message/handlers/send-modal.ts index 48400c1..4af351e 100644 --- a/src/bot/events/interaction-create/message/handlers/send-modal.ts +++ b/src/bot/events/interaction-create/message/handlers/send-modal.ts @@ -1,11 +1,9 @@ -import type { Event } from '@events/event' -import type { Attachment, Interaction } from 'discord.js' +import type { Attachment } from 'discord.js' import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' import { generateCustomId, tempStore } from '@utils/component' import { getChannel, sendAsBot, sendReply } from '@utils/discord' import { DiscordBaseError } from '@utils/discord/error' -import { log } from '@utils/logger' -import { Events } from 'discord.js' import { Send } from '../validators/send' export class SendModalError extends DiscordBaseError { @@ -16,17 +14,14 @@ export class SendModalError extends DiscordBaseError { export const MESSAGE_SEND_ID = generateCustomId(EVENT_PATH, __filename) -export default { - name: Events.InteractionCreate, - desc: 'Handles modal submissions for creating an embed with a role-grant button.', - async exec(_, interaction: Interaction) { +registerInteractionHandler({ + desc: 'Handles message send modal submissions, posting messages (text/attachments) as the bot in the selected channel.', + id: MESSAGE_SEND_ID, + errorTag: () => `${MESSAGE_SEND_ID}: ${Send.ERR.UnexpectedModal}`, + async exec(_, interaction) { if (!interaction.isModalSubmit()) return - const isValidComponent = Send.assertComponentId(interaction.customId, MESSAGE_SEND_ID) - if (!isValidComponent) - return - try { if (!interaction.inCachedGuild()) throw new SendModalError(Send.ERR.NotGuild) @@ -51,7 +46,7 @@ export default { catch (err: any) { if (err instanceof DiscordBaseError) await sendReply(interaction, err.message) - else log.error(`Failed to handle ${MESSAGE_SEND_ID}: ${Send.ERR.UnexpectedModal}: ${err}`) + else throw err } }, -} as Event +}) diff --git a/src/bot/events/interaction-create/registry.ts b/src/bot/events/interaction-create/registry.ts new file mode 100644 index 0000000..41e27c0 --- /dev/null +++ b/src/bot/events/interaction-create/registry.ts @@ -0,0 +1,15 @@ +import type { Client, Interaction } from 'discord.js' + +export interface InteractionHandler { + desc: string + id: string + errorTag: () => string + match?: (interaction: Interaction) => boolean + exec: (client: Client, interaction: Interaction) => Promise | void +} + +export const interactionHandlers = new Map() + +export function registerInteractionHandler(handler: InteractionHandler) { + interactionHandlers.set(handler.id, handler) +} diff --git a/src/bot/events/message-create/im-fine/handlers/im-fine.ts b/src/bot/events/message-create/im-fine/handlers/index.ts similarity index 100% rename from src/bot/events/message-create/im-fine/handlers/im-fine.ts rename to src/bot/events/message-create/im-fine/handlers/index.ts diff --git a/src/bot/events/message-create/im-fine/messages/im-fine.ts b/src/bot/events/message-create/im-fine/messages/index.ts similarity index 100% rename from src/bot/events/message-create/im-fine/messages/im-fine.ts rename to src/bot/events/message-create/im-fine/messages/index.ts diff --git a/src/bot/events/message-create/im-fine/validators/im-fine.ts b/src/bot/events/message-create/im-fine/validators/im-fine.ts deleted file mode 100644 index 13d4941..0000000 --- a/src/bot/events/message-create/im-fine/validators/im-fine.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ImFineMessage } from '../messages/im-fine' - -export class ImFine extends ImFineMessage { -} diff --git a/src/bot/events/message-create/im-fine/validators/index.ts b/src/bot/events/message-create/im-fine/validators/index.ts new file mode 100644 index 0000000..66454d5 --- /dev/null +++ b/src/bot/events/message-create/im-fine/validators/index.ts @@ -0,0 +1,4 @@ +import { ImFineMessage } from '../messages' + +export class ImFine extends ImFineMessage { +} diff --git a/src/bot/events/message-reaction-add/checkin/handlers/submitted-checkin.ts b/src/bot/events/message-reaction-add/checkin/handlers/submitted.ts similarity index 99% rename from src/bot/events/message-reaction-add/checkin/handlers/submitted-checkin.ts rename to src/bot/events/message-reaction-add/checkin/handlers/submitted.ts index bbc422c..e3ed679 100644 --- a/src/bot/events/message-reaction-add/checkin/handlers/submitted-checkin.ts +++ b/src/bot/events/message-reaction-add/checkin/handlers/submitted.ts @@ -1,7 +1,7 @@ import type { Event } from '@events/event' import type { Client, MessageReaction, PartialMessageReaction, User } from 'discord.js' import { CHECKIN_CHANNEL, FLAMEWARDEN_ROLE } from '@config/discord' -import { Checkin } from '@events/interaction-create/checkin/validators/checkin' +import { Checkin } from '@events/interaction-create/checkin/validators' import { DiscordBaseError } from '@utils/discord/error' import { log } from '@utils/logger' import { Events } from 'discord.js' diff --git a/src/config/discord.ts b/src/config/discord.ts index 88ce3e9..8e031c1 100644 --- a/src/config/discord.ts +++ b/src/config/discord.ts @@ -13,8 +13,9 @@ export interface GrindRole { threshold: number } -export const GRINDER_ROLE = '1403320523756146768' +export const ARCHFYRE_ROLE = '1402625885684891658' export const FLAMEWARDEN_ROLE = '1403022712938561668' +export const GRINDER_ROLE = '1403320523756146768' const GRIND_ROLES: GrindRole[] = [ { diff --git a/src/utils/discord/assert.ts b/src/utils/discord/assert.ts index f1742ba..936d4b3 100644 --- a/src/utils/discord/assert.ts +++ b/src/utils/discord/assert.ts @@ -109,13 +109,6 @@ export class DiscordAssert extends DiscordMessage { } } - static assertComponentId(modalId: string, id: string): boolean { - if (!modalId.startsWith(id)) - return false - - return true - } - static isMemberHasRole(member: GuildMember, roleId: string): boolean { return member.roles.cache.has(roleId) } diff --git a/src/utils/io.ts b/src/utils/io.ts index a50a3db..62a0d26 100644 --- a/src/utils/io.ts +++ b/src/utils/io.ts @@ -1,7 +1,7 @@ import fs from 'node:fs' import { resolve } from 'node:path' -const EXCLUDED_FILES = new Set(['index.ts', 'messages.ts', 'validators.ts']) +const EXCLUDED_FILES = new Set(['registry.ts', 'messages.ts', 'validators.ts']) const EXCLUDED_FOLDERS = new Set(['validators', 'messages']) const EXCLUDED_PATTERNS: RegExp[] = [/\.d\.ts$/]