Skip to content
Closed
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
4 changes: 2 additions & 2 deletions src/bot/commands/checkin/handlers/checkin-audit.ts
Original file line number Diff line number Diff line change
@@ -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 }) {
Expand Down
4 changes: 2 additions & 2 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 { 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 Down
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
8 changes: 5 additions & 3 deletions src/bot/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ export async function registerCommands(client: Client) {
client.commands = new Collection<string, Command>()

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'
Expand Down
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
51 changes: 36 additions & 15 deletions src/bot/events/client-ready/jobs/validators/reset-grinder-roles.ts
Original file line number Diff line number Diff line change
@@ -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<ButtonBuilder> {
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<ButtonBuilder>().addComponents(noteButton)
}

static hasValidCheckin(checkin?: { created_at: Date, status: string }): boolean {
if (!checkin)
return false
Expand All @@ -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))
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand Down
4 changes: 4 additions & 0 deletions src/bot/events/client-ready/say-hello/validators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { SayHelloMessage } from '../messages'

export class SayHello extends SayHelloMessage {
}
4 changes: 0 additions & 4 deletions src/bot/events/client-ready/say-hello/validators/say-hello.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = [
Expand Down
18 changes: 10 additions & 8 deletions src/bot/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }) {
Expand All @@ -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()

Expand All @@ -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
})
Original file line number Diff line number Diff line change
@@ -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 }) {
Expand All @@ -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()

Expand Down Expand Up @@ -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
})
Original file line number Diff line number Diff line change
@@ -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 }) {
Expand All @@ -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()

Expand Down Expand Up @@ -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
})
Loading