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
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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 { sendReply } from '@utils/discord'
import { DiscordBaseError } from '@utils/discord/error'
import { getModuleName } from '@utils/io'
import { Checkin } from '../validators'

export class CheckinDetailButtonError extends DiscordBaseError {
constructor(message: string, options?: { cause?: unknown }) {
super('CheckinDetailButtonError', message, options)
}
}

const moduleName = getModuleName(EVENT_PATH, __filename)
export const CHECKIN_DETAIL_BUTTON_ID = `${generateCustomId(EVENT_PATH, __filename)}`

registerInteractionHandler({
desc: 'Handles displaying more details for a user check-in when the detail button is pressed.',
id: CHECKIN_DETAIL_BUTTON_ID,
errorTag: () => `${moduleName}: ${Checkin.ERR.UnexpectedButton}`,
async exec(client, interaction) {
if (!interaction.isButton())
return

try {
if (!interaction.inCachedGuild())
throw new CheckinDetailButtonError(Checkin.ERR.NotGuild)

const { checkinId } = Checkin.getButtonId(interaction, interaction.customId)

const channel = interaction.channel as TextChannel
const member = interaction.member as GuildMember
Checkin.assertMissPerms(interaction.client.user, channel)
Checkin.assertMember(member)
Checkin.assertMemberGrindRoles(member)

const checkin = await Checkin.getCheckin(client.prisma, checkinId)
const prevCheckin = await Checkin.getPrevCheckin(client.prisma, checkin.user!.id, checkin.checkin_streak!, checkin)

await sendReply(interaction, Checkin.MSG.GrinderDetails(member, checkin, checkin.checkin_streak!.streak, prevCheckin))
}
catch (err: any) {
if (err instanceof DiscordBaseError)
await sendReply(interaction, err.message)
else throw err
}
},
})
15 changes: 2 additions & 13 deletions src/bot/events/interaction-create/checkin/handlers/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,12 @@ registerInteractionHandler({
Checkin.assertMemberGrindRoles(member)
Checkin.assertCheckinToday(user)

const {
checkinStreak,
checkin,
prevCheckin,
} = await Checkin.validateCheckinStreak(client.prisma, user.id, user.checkin_streaks?.[0], todo)

const { checkin } = await Checkin.validateCheckinStreak(client.prisma, user.id, user.checkin_streaks?.[0], todo)
const buttons = Checkin.generateButtons(interaction.guildId, checkin.id.toString())

const msg = await sendReply(
interaction,
Checkin.MSG.CheckinSuccess(
member,
attachments,
checkinStreak.streak,
todo,
prevCheckin,
),
Checkin.MSG.CheckinSuccess(todo),
false,
{
files: attachments.length ? attachments : undefined,
Expand Down
19 changes: 11 additions & 8 deletions src/bot/events/interaction-create/checkin/messages/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Checkin } from '@type/checkin'
import type { Attachment, GuildMember } from 'discord.js'
import type { GuildMember } from 'discord.js'
import { FLAMEWARDEN_ROLE } from '@config/discord'
import { getNow, getParsedNow } from '@utils/date'
import { DiscordAssert } from '@utils/discord'
Expand All @@ -17,21 +17,24 @@ export class CheckinMessage extends DiscordAssert {

static override readonly MSG = {
...DiscordAssert.MSG,
CheckinSuccess: (member: GuildMember, checkinAttachments: Attachment[], streakCount: number, todo: string, lastCheckin?: Checkin) => `
CheckinSuccess: (todo: string) => `
# ✅ Check-In Baru Terdeteksi!
*Kindly take a look and do a review for this one, <@&${FLAMEWARDEN_ROLE}>*

✨─────✨/✨━━━━✨
🌟 **Grinder:** <@${member.id}>
📁 **Attachment:** ${checkinAttachments.length > 0 ? '✅' : '❌'}
🕓 **Date:** ${getParsedNow()}
🔥 **Current Streak:** ${streakCount} day(s)
🗓 **Last Check-In:** ${lastCheckin ? getParsedNow(getNow(lastCheckin.created_at)) : '-'}
⋆。˚ ☁︎ ˚。⋆。˚☽˚。⋆
${todo}

> ${DUMMY.FOOTER}`,

GrinderDetails: (member: GuildMember, checkin: Checkin, streakCount: number, lastCheckin?: Checkin) => `
✨─────✨/✨━━━━✨
🌟 **Grinder:** <@${member.id}>
📁 **Attachment:** ${checkin.attachments && checkin.attachments.length > 0 ? '✅' : '❌'}
🕓 **Date:** ${getParsedNow()}
🔥 **Current Streak:** ${streakCount} day(s)
🗓 **Last Check-In:** ${lastCheckin ? `[${getParsedNow(getNow(lastCheckin.created_at))}](${lastCheckin.link})` : '-'}
`,

CheckinSuccessToMember: (checkin: Checkin) => `
Sebuah [check-in](${checkin.link}) baru telah Tuan/Nona serahkan dan kini menunggu pemeriksaan dari Flamewarden.
🆔 **Check-In ID**:
Expand Down
54 changes: 31 additions & 23 deletions src/bot/events/interaction-create/checkin/validators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, messageLink, PermissionsB
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 { CHECKIN_DETAIL_BUTTON_ID } from '../handlers/detail-button'
import { CheckinModalError } from '../handlers/modal'
import { CHECKIN_REJECT_BUTTON_ID } from '../handlers/reject-button'
import { CheckinMessage } from '../messages'
Expand Down Expand Up @@ -100,6 +101,12 @@ export class Checkin extends CheckinMessage {
}

static generateButtons(guildId: string, checkinId: string): ActionRowBuilder<ButtonBuilder> {
const detailButtonId = getCustomId([CHECKIN_DETAIL_BUTTON_ID, encodeSnowflake(guildId), encodeSnowflake(checkinId)])
const detailButton = new ButtonBuilder()
.setCustomId(detailButtonId)
.setLabel('🔍 Detail')
.setStyle(ButtonStyle.Primary)

const approveButtonId = getCustomId([CHECKIN_APPROVE_BUTTON_ID, encodeSnowflake(guildId), encodeSnowflake(checkinId)])
const approveButton = new ButtonBuilder()
.setCustomId(approveButtonId)
Expand All @@ -116,9 +123,9 @@ export class Checkin extends CheckinMessage {
const customButton = new ButtonBuilder()
.setCustomId(customButtonId)
.setLabel('⚙️ Review')
.setStyle(ButtonStyle.Primary)
.setStyle(ButtonStyle.Secondary)

return new ActionRowBuilder<ButtonBuilder>().addComponents(approveButton, rejectButton, customButton)
return new ActionRowBuilder<ButtonBuilder>().addComponents(detailButton, approveButton, rejectButton, customButton)
}

static getNewGrindRole(guild: Guild, streakCount: number) {
Expand Down Expand Up @@ -323,6 +330,26 @@ export class Checkin extends CheckinMessage {
}) as CheckinType
}

static async getCheckin(
prisma: PrismaClient,
checkinId: number,
): Promise<CheckinType> {
const checkin = await prisma.checkin.findUnique({
where: { id: checkinId },
include: {
checkin_streak: true,
user: true,
},
}) as CheckinType

if (!checkin)
throw new SubmittedCheckinError(this.ERR.PlainMessage)

await this.setAttachments(prisma, checkin)

return checkin
}

static async createAttachments(
prisma: PrismaClient,
checkin: CheckinType,
Expand Down Expand Up @@ -368,13 +395,8 @@ export class Checkin extends CheckinMessage {
return prisma.$transaction(async (tx) => {
const checkinStreak = await this.upsertStreak(tx, userId, lastCheckinStreak, decision)
const checkin = await this.createCheckin(tx, userId, checkinStreak, description)
const prevCheckin = await this.getPrevCheckin(tx, userId, checkinStreak, checkin)

return {
checkinStreak,
checkin,
prevCheckin,
}
return { checkinStreak, checkin }
})
}

Expand All @@ -392,7 +414,7 @@ export class Checkin extends CheckinMessage {
const updatedCheckin = await this.updateCheckinStatus(prisma, flamewarden, checkin, checkinStatus, comment) as CheckinType

await this.validateCheckinHandleToUser(guild, flamewarden, checkin.user!.discord_id, updatedCheckin)
await this.validateCheckinHandleSubmittedMsg(message, updatedCheckin, checkinStatus)
await message.react(this.REVERSED_EMOJI_STATUS[checkinStatus])
}

static async validateCheckinHandleToUser(guild: Guild, flamewarden: GuildMember, userDiscordId: string, updatedCheckin: CheckinType) {
Expand All @@ -403,11 +425,6 @@ export class Checkin extends CheckinMessage {
await this.sendCheckinStatusToMember(flamewarden, member, updatedCheckin)
}

static async validateCheckinHandleSubmittedMsg(message: Message, updatedCheckin: CheckinType, checkinStatus: CheckinStatusType) {
await this.updateSubmittedCheckin(message, updatedCheckin.checkin_streak!.streak)
await message.react(this.REVERSED_EMOJI_STATUS[checkinStatus])
}

static async updateCheckinMsgLink(interaction: Interaction, prisma: PrismaClient, checkin: CheckinType, msg: Message): Promise<CheckinType> {
const msgLink = messageLink(interaction.channelId!, msg.id, interaction.guildId!)

Expand Down Expand Up @@ -491,13 +508,4 @@ export class Checkin extends CheckinMessage {

await member.send({ embeds: [embed] })
}

static async updateSubmittedCheckin(message: Message, newStreak: number) {
await message.edit(
message.content.replace(
/🔥\s*\*\*Current Streak:\*\*\s*\d+\s*day\(s\)/i,
`🔥 **Current Streak:** ${newStreak} day(s)`,
),
)
}
}
2 changes: 1 addition & 1 deletion src/utils/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ ${checkin.public_id}
🔥 **Current Streak**: ${checkin.checkin_streak!.streak} day(s)
## Notulen Grinder
${checkin.description}
✰⋆。:゚・*☽:゚・⋆。✰⋆。:゚`))
⋆。˚ ☁︎ ˚。⋆。˚☽˚。⋆`))
.addTextDisplayComponents(textDisplay => textDisplay.setContent(DUMMY.MARKDOWN))

return modal
Expand Down