diff --git a/src/bot/commands/checkin/messages/checkin-status.ts b/src/bot/commands/checkin/messages/checkin-status.ts index f79df51..790245d 100644 --- a/src/bot/commands/checkin/messages/checkin-status.ts +++ b/src/bot/commands/checkin/messages/checkin-status.ts @@ -19,7 +19,7 @@ Nyala api Tuan/Nona belum dinyalakan hari ini. πŸ—“ **Date**: ${getParsedNow()} πŸ”₯ **Current Streak**: ${checkinStreak?.streak ?? 0} day(s) πŸ”Ž **Status**: Belum melakukan *check-in* -> *β€œPercikan hari ini belum ditorehkan. Lakukan *check-in* sebelum 23:59 WIB, agar api Tuan/Nona tak meredup.”* +> *"Percikan hari ini belum ditorehkan. Lakukan check-in sebelum 23:59 WIB, agar api Tuan/Nona tak meredup."* `, WaitingCheckin: (userDiscordId: string, checkin: Checkin) => ` πŸ†” **Check-In ID**: @@ -31,7 +31,7 @@ ${checkin.public_id} πŸ—“ **Submitted At**: ${getParsedNow(getNow(checkin.created_at))} πŸ”₯ **Current Streak**: ${checkin.checkin_streak!.streak} day(s) πŸ”Ž **Status**: Menunggu peninjauan <@&${FLAMEWARDEN_ROLE}> -> *β€œPercikan telah Tuan/Nona <@${userDiscordId}> titipkan. Mohon menanti sesaat, <@&${FLAMEWARDEN_ROLE}> tengah menakar apakah [nyala tersebut](${checkin.link}) layak menjadi bagian dari perjalanan Tuan/Nona.”* +> *"Percikan telah Tuan/Nona <@${userDiscordId}> titipkan. Mohon menanti sesaat, <@&${FLAMEWARDEN_ROLE}> tengah menakar apakah [nyala tersebut](${checkin.link}) layak menjadi bagian dari perjalanan Tuan/Nona."* `, ApprovedCheckin: (userDiscordId: string, flamewarden: GuildMember, checkin: Checkin) => ` πŸ†” **Check-In ID**: @@ -45,7 +45,7 @@ ${checkin.public_id} πŸ—“ **Approved At**: ${getParsedNow(getNow(checkin.updated_at!))} πŸ‘€ **Approved By**: ${flamewarden.displayName} (@${flamewarden.user.username}) ✍🏻 **${flamewarden.displayName}'(s) Comment**: ${checkin.comment ?? '-'} -> *β€œ[Nyala hari ini](${checkin.link}) diterima. Teruslah menenun aksara disiplin, satu hari demi satu hari.”* +> *"[Nyala hari ini](${checkin.link}) diterima. Teruslah menenun aksara disiplin, satu hari demi satu hari."* `, RejectedCheckin: (userDiscordId: string, flamewarden: GuildMember, checkin: Checkin) => ` πŸ†” **Check-In ID**: 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 a6a6e6a..4ec6821 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 @@ -27,9 +27,9 @@ Namun jangan berduka, jalan ini selalu terbuka bagi mereka yang bersedia memulai > 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\`** pada <#${AUDIT_FLAME_CHANNEL}> untuk menampilkan status *check-in* terakhir Tuan/Nona. -> 3. Setelah pesan status tersebut muncul, berikan reaksi β€œβ“β€ pada pesan tersebut. +> 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. +> ⏳ Batas waktu penantian atas status *WAITING* adalah maksimal 1Γ—24 jam sejak *check-in* diajukan. `, } } diff --git a/src/bot/events/guild-member-update/grinder-role/handlers/index.ts b/src/bot/events/guild-member-update/grinder-role/handlers/index.ts index 888d012..15e6bef 100644 --- a/src/bot/events/guild-member-update/grinder-role/handlers/index.ts +++ b/src/bot/events/guild-member-update/grinder-role/handlers/index.ts @@ -28,11 +28,12 @@ registerGuildMemberUpdateHandler({ if (newHasGrinderRole && !oldHasGrinderRole) { const channel = await getChannel(newMember.guild, GRIND_ASHES_CHANNEL) GrinderRole.assertChannel(channel) + const button = GrinderRole.generateButton(newMember.guild.id) await sendAsBot( null, channel, - { content: GrinderRole.MSG.Greetings(newMember), allowedMentions: { users: [newMember.id], roles: [] } }, + { content: GrinderRole.MSG.Greetings(newMember), components: [button], allowedMentions: { users: [newMember.id], roles: [] } }, ) } } diff --git a/src/bot/events/guild-member-update/grinder-role/messages/index.ts b/src/bot/events/guild-member-update/grinder-role/messages/index.ts index 8e99385..7febbc9 100644 --- a/src/bot/events/guild-member-update/grinder-role/messages/index.ts +++ b/src/bot/events/guild-member-update/grinder-role/messages/index.ts @@ -19,7 +19,8 @@ Sebagai langkah permulaan, perkenankan kami menuntun Tuan/Nona: β… . Kunjungilah ⁠<#${CHECKIN_CHANNEL}> untuk menorehkan grind harian pertama kamu. β…‘. Tuliskan apa yang tengah Tuan/Nona tempuh hari ini, entah itu reading, coding, crafting, designing, exercise, ataupun belajar hal baru. β…’. Nantikan peninjauan dari seorang <@&${FLAMEWARDEN_ROLE}>, yang akan menilai dan mengesahkan *check-in* Tuan/Nona. - + `, + WelcomeNotes: ` > Harap diingat dengan saksama: > Streak Tuan/Nona hanya bermula setelah *check-in* pertama disahkan. > Apabila hingga pukul 23:59 WIB Tuan/Nona lalai menorehkan *check-in*, maka nyala api akan meredup, dan perjalanan harus dimulai kembali dari awal. diff --git a/src/bot/events/guild-member-update/grinder-role/validators/index.ts b/src/bot/events/guild-member-update/grinder-role/validators/index.ts index 9fcde8b..a7241ad 100644 --- a/src/bot/events/guild-member-update/grinder-role/validators/index.ts +++ b/src/bot/events/guild-member-update/grinder-role/validators/index.ts @@ -1,8 +1,21 @@ +import { WELCOME_NOTE_BUTTON_ID } from '@events/interaction-create/grinder-role/handlers/button' +import { encodeSnowflake, getCustomId } from '@utils/component' import { DiscordAssert } from '@utils/discord' +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js' import { GrinderRoleMessage } from '../messages' export class GrinderRole extends GrinderRoleMessage { static override BASE_PERMS = [ ...DiscordAssert.BASE_PERMS, ] + + static generateButton(guildId: string): ActionRowBuilder { + const noteButtonId = getCustomId([WELCOME_NOTE_BUTTON_ID, encodeSnowflake(guildId)]) + const noteButton = new ButtonBuilder() + .setCustomId(noteButtonId) + .setLabel('πŸ“œ Titah Perjalanan') + .setStyle(ButtonStyle.Primary) + + return new ActionRowBuilder().addComponents(noteButton) + } } diff --git a/src/bot/events/interaction-create/grinder-role/handlers/button.ts b/src/bot/events/interaction-create/grinder-role/handlers/button.ts new file mode 100644 index 0000000..98b6293 --- /dev/null +++ b/src/bot/events/interaction-create/grinder-role/handlers/button.ts @@ -0,0 +1,42 @@ +import type { TextChannel } from 'discord.js' +import { GrinderRole } from '@events/guild-member-update/grinder-role/validators' +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' + +export class GrinderRoleButtonError extends DiscordBaseError { + constructor(message: string, options?: { cause?: unknown }) { + super('GrinderRoleButtonError', message, options) + } +} + +const moduleName = getModuleName(EVENT_PATH, __filename) +export const WELCOME_NOTE_BUTTON_ID = `${generateCustomId(EVENT_PATH, __filename)}` + +registerInteractionHandler({ + desc: 'Opens welcome note modal for users receiving Grinder roles.', + id: WELCOME_NOTE_BUTTON_ID, + errorTag: () => `${moduleName}: ${GrinderRole.ERR.UnexpectedButton}`, + async exec(_, interaction) { + if (!interaction.isButton()) + return + + try { + if (!interaction.inCachedGuild()) + throw new GrinderRoleButtonError(GrinderRole.ERR.NotGuild) + + const channel = interaction.channel as TextChannel + GrinderRole.assertMissPerms(interaction.client.user, channel) + + await sendReply(interaction, GrinderRole.MSG.WelcomeNotes) + } + catch (err: any) { + if (err instanceof DiscordBaseError) + await sendReply(interaction, err.message) + else throw err + } + }, +})