Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/bot/commands/checkin/handlers/checkin-audit.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import type { Command } from '@commands/command'
import type { ChatInputCommandInteraction, Client } from 'discord.js'
import { registerCommand } from '@commands/registry'
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 }) {
super('CheckinAuditError', message, options)
}
}

export default {
registerCommand({
data: new SlashCommandBuilder()
.setName('checkin-audit')
.setDescription('Review an old check-in using its public ID.')
Expand Down Expand Up @@ -57,4 +57,4 @@ export default {
else log.error(`Failed to handle: ${CheckinAudit.ERR.UnexpectedCheckinAudit}: ${err}`)
}
},
} as Command
})
6 changes: 3 additions & 3 deletions src/bot/commands/checkin/handlers/checkin-status.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Command } from '@commands/command'
import type { ChatInputCommandInteraction, Client, GuildMember } from 'discord.js'
import { registerCommand } from '@commands/registry'
import { AUDIT_FLAME_CHANNEL, FLAMEWARDEN_ROLE, GRINDER_ROLE } from '@config/discord'
import { sendReply } from '@utils/discord'
import { DiscordBaseError } from '@utils/discord/error'
Expand All @@ -13,7 +13,7 @@ export class CheckinStatusError extends DiscordBaseError {
}
}

export default {
registerCommand({
data: new SlashCommandBuilder()
.setName('checkin-status')
.setDescription('Check your current daily check-in and streak status.'),
Expand Down Expand Up @@ -47,4 +47,4 @@ export default {
else log.error(`Failed to handle: ${CheckinStatus.ERR.UnexpectedCheckinStatus}: ${err}`)
}
},
} as Command
})
10 changes: 5 additions & 5 deletions src/bot/commands/checkin/handlers/checkin.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Command } from '@commands/command'
import type { ChatInputCommandInteraction } from 'discord.js'
import { registerCommand } from '@commands/registry'
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'
Expand All @@ -16,7 +16,7 @@ export class CheckinError extends DiscordBaseError {
}
}

export default {
registerCommand({
data: new SlashCommandBuilder()
.setName('checkin')
.setDescription('Daily grind check-in.')
Expand Down Expand Up @@ -67,4 +67,4 @@ export default {
else log.error(`Failed to handle: ${Checkin.ERR.UnexpectedCheckin}: ${err}`)
}
},
} as Command
})
2 changes: 1 addition & 1 deletion src/bot/commands/checkin/validators/checkin-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 2 additions & 2 deletions src/bot/commands/command.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ChatInputCommandInteraction, Client } from 'discord.js'
import type { ChatInputCommandInteraction, Client, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder } from 'discord.js'

export interface Command {
data: SlashCommandBuilder
data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder
execute: (client: Client, interaction: ChatInputCommandInteraction) => Promise<void>
}
6 changes: 3 additions & 3 deletions src/bot/commands/embed/handlers/role-grant-create.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Command } from '@commands/command'
import type { ChatInputCommandInteraction, TextChannel } from 'discord.js'
import { registerCommand } from '@commands/registry'
import { LabelBuilder, ModalBuilder, TextInputBuilder } from '@discordjs/builders'
import { EMBED_ROLE_GRANT_CREATE_MODAL_ID } from '@events/interaction-create/embed/handlers/role-grant-create-modal'
import { RoleGrantCreate } from '@events/interaction-create/embed/validators/role-grant-create'
Expand All @@ -16,7 +16,7 @@ export class EmbedRoleGrantError extends DiscordBaseError {
}
}

export default {
registerCommand({
data: new SlashCommandBuilder()
.setName('create-embed-role-grant')
.setDescription('Create an embed in a channel w/ a role-grant button.')
Expand Down Expand Up @@ -103,4 +103,4 @@ export default {
else log.error(`Failed to handle ${EMBED_ROLE_GRANT_CREATE_MODAL_ID}: ${RoleGrantCreate.ERR.UnexpectedRoleGrantCreate}: ${err}`)
}
},
} as Command
})
28 changes: 9 additions & 19 deletions src/bot/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Command } from '@commands/command'
import type { Client } from 'discord.js'
import path from 'node:path'
import { getModuleName, readFiles } from '@utils/io'
import { log } from '@utils/logger'
import { Collection } from 'discord.js'
import { commandRegistry, loadCommands } from './registry'

export class CommandError extends Error {
constructor(message: string, options?: { cause?: unknown }) {
Expand All @@ -13,23 +11,15 @@ export class CommandError extends Error {
}
}

export const COMMAND_PATH = path.basename(__dirname)
const files = readFiles(__dirname)
export const COMMAND_PATH = path.join(__dirname)

export async function registerCommands(client: Client) {
client.commands = new Collection<string, Command>()

for (const file of files) {
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)
}
catch (err: any) {
const msg = err instanceof CommandError ? err.message : '❌ Something went wrong when importing the command'
log.error(`Failed to register a command: ${msg}: ${err.message}`)
}
try {
await loadCommands()
client.commands = commandRegistry
}
catch (err: any) {
const msg = err instanceof CommandError ? err.message : '❌ Something went wrong when importing the command'
log.error(`Failed to register an command: ${msg}: ${err.message}`)
}
}
6 changes: 3 additions & 3 deletions src/bot/commands/message/handlers/send.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Command } from '@commands/command'
import type { ChatInputCommandInteraction, TextChannel } from 'discord.js'
import { registerCommand } from '@commands/registry'
import { MESSAGE_SEND_ID } from '@events/interaction-create/message/handlers/send-modal'
import { Send } from '@events/interaction-create/message/validators/send'
import { encodeSnowflake, getCustomId } from '@utils/component'
Expand Down Expand Up @@ -29,7 +29,7 @@ for (let i = 1; i <= Send.ATTACHMENT_COUNT; i++) {
)
}

export default {
registerCommand({
data,
async execute(_, interaction: ChatInputCommandInteraction) {
try {
Expand Down Expand Up @@ -73,4 +73,4 @@ export default {
else log.error(`Failed to handle: ${Send.ERR.UnexpectedSend}: ${err}`)
}
},
} as Command
})
34 changes: 34 additions & 0 deletions src/bot/commands/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Command } from '@commands/command'
import path from 'node:path'
import { readFiles } from '@utils/io'
import { log } from '@utils/logger'
import { Collection } from 'discord.js'
import { COMMAND_PATH } from '.'

export const commandRegistry = new Collection<string, Command>()

export function registerCommand(command: Command) {
if (commandRegistry.has(command.data.name)) {
throw new Error(`Duplicate command name: ${command.data.name}`)
}

commandRegistry.set(command.data.name, command)
}

export async function loadCommands() {
const files = readFiles(COMMAND_PATH).filter(file => !file.endsWith('/index.ts'))

await Promise.all(
files.map(async (file) => {
const fileName = path.basename(file, path.extname(file))

try {
await import(file)
log.info(`Loaded command file '${fileName}'`)
}
catch (err) {
log.error(`Failed to load command file ${file}: ${err}`)
}
}),
)
}
6 changes: 3 additions & 3 deletions src/bot/commands/utility/handlers/ping.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Command } from '@commands/command'
import type { ChatInputCommandInteraction, TextChannel } from 'discord.js'
import { registerCommand } from '@commands/registry'
import { sendReply } from '@utils/discord'
import { DiscordBaseError } from '@utils/discord/error'
import { log } from '@utils/logger'
Expand All @@ -12,7 +12,7 @@ export class PingError extends DiscordBaseError {
}
}

export default {
registerCommand({
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with pong!'),
Expand All @@ -33,4 +33,4 @@ export default {
else log.error(`Failed to handle: ${Ping.ERR.UnexpectedPing}: ${err}`)
}
},
} as Command
})
21 changes: 21 additions & 0 deletions src/bot/events/client-ready/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Event } from '@events/event'
import type { Client } from 'discord.js'
import { log } from '@utils/logger'
import { Events } from 'discord.js'
import { clientReadyHandlers } from './registry'

export default {
name: Events.ClientReady,
once: true,
desc: 'Runs all registered ClientReady handlers.',
async exec(client: Client) {
for (const handler of clientReadyHandlers) {
try {
await handler.exec(client)
}
catch (err) {
log.error(`ClientReady handler failed ${handler.errorTag()}: ${err}`)
}
}
},
} as Event
18 changes: 10 additions & 8 deletions src/bot/events/client-ready/jobs/handlers/reset-grinder-roles.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { Event } from '@events/event'
import type { Client } from 'discord.js'
import process from 'node:process'
import { GRIND_ASHES_CHANNEL } from '@config/discord'
import { registerClientReadyHandler } from '@events/client-ready/registry'
import { EVENT_PATH } from '@events/index'
import { getChannel } from '@utils/discord'
import { DiscordBaseError } from '@utils/discord/error'
import { getModuleName } from '@utils/io'
import { log } from '@utils/logger'
import { Events } from 'discord.js'
import cron from 'node-cron'
import { ResetGrinderRoles } from '../validators/reset-grinder-roles'

Expand All @@ -15,10 +16,11 @@ export class ResetGrinderRolesError extends DiscordBaseError {
}
}

export default {
name: Events.ClientReady,
const moduleName = getModuleName(EVENT_PATH, __filename)

registerClientReadyHandler({
desc: `Reset Grinder roles for users that didn't do a check-in yesterday or the check-in didn't approved.`,
once: true,
errorTag: () => `${moduleName}: ${ResetGrinderRoles.ERR.UnexpectedResetGrinderRoles}`,
exec(client: Client) {
try {
cron.schedule('0 0 * * *', async () => {
Expand All @@ -34,9 +36,9 @@ export default {
log.success(ResetGrinderRoles.MSG.JobSuccess)
})
}
catch (err: any) {
catch (err) {
if (!(err instanceof DiscordBaseError))
log.error(`Failed to handle ${ResetGrinderRoles.ERR.UnexpectedResetGrinderRoles}: ${err}`)
throw err
}
},
} as Event
})
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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.
Expand Down
Loading