Skip to content

Commit 8abfaea

Browse files
committed
Use a custom modal submit interaction class
1 parent 108f3ae commit 8abfaea

5 files changed

Lines changed: 92 additions & 17 deletions

File tree

src/command-abstractions/modal.ts

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,87 @@ export type ModalField = {
2121
value?: string;
2222
};
2323

24+
export class BotModalSubmitInteraction {
25+
public readonly guild: Discord.Guild | null;
26+
public readonly guild_id: string | null;
27+
public readonly channel: Discord.TextBasedChannel | null;
28+
public readonly channel_id: string | null;
29+
public readonly member: Discord.GuildMember | Discord.APIInteractionGuildMember | null;
30+
public readonly user: Discord.User;
31+
public readonly customId: string;
32+
public readonly replied: boolean;
33+
public readonly deferred: boolean;
34+
35+
constructor(
36+
private readonly interaction: Discord.ModalSubmitInteraction,
37+
private readonly field_configs: ModalField[],
38+
) {
39+
this.guild = interaction.guild;
40+
this.guild_id = interaction.guildId;
41+
this.channel = interaction.channel;
42+
this.channel_id = interaction.channelId;
43+
this.member = interaction.member;
44+
this.user = interaction.user;
45+
this.customId = interaction.customId;
46+
this.replied = interaction.replied;
47+
this.deferred = interaction.deferred;
48+
}
49+
50+
get_field_value(field_custom_id: string): string {
51+
return this.interaction.fields.getTextInputValue(field_custom_id);
52+
}
53+
54+
get_all_field_values(): Record<string, string> {
55+
const values: Record<string, string> = {};
56+
for (const field of this.field_configs) {
57+
values[field.custom_id] = this.get_field_value(field.custom_id);
58+
}
59+
return values;
60+
}
61+
62+
async reply(options: Discord.InteractionReplyOptions): Promise<Discord.InteractionResponse> {
63+
return await this.interaction.reply(options);
64+
}
65+
66+
async deferReply(options?: Discord.InteractionDeferReplyOptions): Promise<Discord.InteractionResponse> {
67+
return await this.interaction.deferReply(options);
68+
}
69+
70+
async editReply(options: Discord.InteractionEditReplyOptions): Promise<Discord.Message> {
71+
return await this.interaction.editReply(options);
72+
}
73+
74+
async followUp(options: Discord.InteractionReplyOptions): Promise<Discord.Message> {
75+
return await this.interaction.followUp(options);
76+
}
77+
78+
async deferUpdate(options?: Discord.InteractionDeferUpdateOptions): Promise<Discord.InteractionResponse> {
79+
return await this.interaction.deferUpdate(options);
80+
}
81+
82+
async update(options: Discord.InteractionUpdateOptions): Promise<Discord.InteractionResponse> {
83+
if (!this.interaction.isFromMessage()) {
84+
throw new Error("update() can only be called on modal interactions from messages");
85+
}
86+
return await this.interaction.update(options);
87+
}
88+
89+
isFromMessage(): this is BotModalSubmitInteraction & { message: Discord.Message } {
90+
return this.interaction.isFromMessage();
91+
}
92+
93+
get raw_interaction(): Discord.ModalSubmitInteraction {
94+
return this.interaction;
95+
}
96+
}
97+
2498
export class BotModalHandler<Args extends unknown[] = []> {
2599
private readonly metadata_fields: ModalParameterType[] = [];
26100
private readonly field_configs: ModalField[] = [];
27101

28102
constructor(
29103
public readonly base_custom_id: string,
30-
public readonly handler: (interaction: Discord.ModalSubmitInteraction, ...args: Args) => Promise<void>,
104+
public readonly handler: (interaction: BotModalSubmitInteraction, ...args: Args) => Promise<void>,
31105
public readonly permissions?: bigint,
32106
metadata_fields: ModalParameterType[] = [],
33107
field_configs: ModalField[] = [],
@@ -79,7 +153,8 @@ export class BotModalHandler<Args extends unknown[] = []> {
79153

80154
try {
81155
const parsed_args = this.parse_arguments(raw_args);
82-
await this.handler(interaction, ...parsed_args);
156+
const bot_interaction = new BotModalSubmitInteraction(interaction, this.field_configs);
157+
await this.handler(bot_interaction, ...parsed_args);
83158
} catch (error) {
84159
M.error(`Error handling modal ${this.base_custom_id}:`, error);
85160

@@ -209,7 +284,7 @@ export class BotModal<Args extends unknown[] = []> {
209284
export class ModalInteractionBuilder<
210285
Args extends unknown[] = [],
211286
HasHandler extends boolean = false,
212-
> extends BaseBuilder<HasHandler, [Discord.ModalSubmitInteraction, ...Args]> {
287+
> extends BaseBuilder<HasHandler, [BotModalSubmitInteraction, ...Args]> {
213288
private readonly metadata_fields: ModalParameterType[] = [];
214289
private permissions?: bigint;
215290
private title = "Modal";
@@ -276,7 +351,7 @@ export class ModalInteractionBuilder<
276351
}
277352

278353
set_handler(
279-
handler: (interaction: Discord.ModalSubmitInteraction, ...args: Args) => Promise<void>,
354+
handler: (interaction: BotModalSubmitInteraction, ...args: Args) => Promise<void>,
280355
): ModalInteractionBuilder<Args, true> {
281356
this.handler = handler;
282357
return this as unknown as ModalInteractionBuilder<Args, true>;

src/components/echo.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Wheatley } from "../wheatley.js";
1010
import { EarlyReplyMode, TextBasedCommandBuilder } from "../command-abstractions/text-based-command-builder.js";
1111
import { TextBasedCommand } from "../command-abstractions/text-based-command.js";
1212
import { unwrap } from "../utils/misc.js";
13-
import { ModalInteractionBuilder, BotModal } from "../command-abstractions/modal.js";
13+
import { ModalInteractionBuilder, BotModal, BotModalSubmitInteraction } from "../command-abstractions/modal.js";
1414

1515
export default class Echo extends BotComponent {
1616
static override get is_freestanding() {
@@ -65,8 +65,8 @@ export default class Echo extends BotComponent {
6565
await command.get_interaction().showModal(modal);
6666
}
6767

68-
async say_modal_submit(interaction: Discord.ModalSubmitInteraction) {
69-
const message = this.say_modal.get_field_value(interaction, "say_modal_message");
68+
async say_modal_submit(interaction: BotModalSubmitInteraction) {
69+
const message = interaction.get_field_value("say_modal_message");
7070
await interaction.deferReply({ ephemeral: true });
7171
const channel = unwrap(interaction.channel);
7272
assert(channel.isTextBased() && !(channel instanceof Discord.PartialGroupDMChannel));

src/components/moderation/modmail.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { EarlyReplyMode, TextBasedCommandBuilder } from "../../command-abstracti
1515
import { TextBasedCommand } from "../../command-abstractions/text-based-command.js";
1616
import { MessageContextMenuInteractionBuilder } from "../../command-abstractions/context-menu.js";
1717
import { ButtonInteractionBuilder, BotButton } from "../../command-abstractions/button.js";
18-
import { ModalInteractionBuilder, BotModal } from "../../command-abstractions/modal.js";
18+
import { ModalInteractionBuilder, BotModal, BotModalSubmitInteraction } from "../../command-abstractions/modal.js";
1919

2020
/*
2121
* Flow:
@@ -335,11 +335,11 @@ export default class Modmail extends BotComponent {
335335
await this.log_action(interaction.member, "Modmail continue");
336336
}
337337

338-
async modmail_modal_submit(interaction: Discord.ModalSubmitInteraction) {
339-
const codeword = this.confirm_modal.get_field_value(interaction, "modmail_create_confirm_codeword");
338+
async modmail_modal_submit(interaction: BotModalSubmitInteraction) {
339+
const codeword = interaction.get_field_value("modmail_create_confirm_codeword");
340340
if (codeword.toLowerCase().replace(/\s/g, "").includes("raboof")) {
341341
await interaction.deferUpdate();
342-
await this.create_modmail_thread(interaction);
342+
await this.create_modmail_thread(interaction.raw_interaction);
343343
await interaction.editReply({
344344
content:
345345
"Your modmail request has been processed. A thread has been created and the staff " +

src/components/report.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { BotComponent } from "../bot-component.js";
99
import { CommandSetBuilder } from "../command-abstractions/command-set-builder.js";
1010
import { Wheatley } from "../wheatley.js";
1111
import { MessageContextMenuInteractionBuilder } from "../command-abstractions/context-menu.js";
12-
import { ModalInteractionBuilder, BotModal } from "../command-abstractions/modal.js";
12+
import { ModalInteractionBuilder, BotModal, BotModalSubmitInteraction } from "../command-abstractions/modal.js";
1313
import { BotButton, ButtonInteractionBuilder } from "../command-abstractions/button.js";
1414

1515
export default class Report extends BotComponent {
@@ -69,14 +69,14 @@ export default class Report extends BotComponent {
6969
await interaction.showModal(modal);
7070
}
7171

72-
async modal_handler(interaction: Discord.ModalSubmitInteraction, id: string) {
72+
async modal_handler(interaction: BotModalSubmitInteraction, id: string) {
7373
M.log("Received report modal submit", id);
7474
await interaction.reply({
7575
ephemeral: true,
7676
content: "Processing...",
7777
});
7878
try {
79-
const message = this.report_modal.get_field_value(interaction, "report-modal-message").trim();
79+
const message = interaction.get_field_value("report-modal-message").trim();
8080
const reporter =
8181
interaction.member instanceof Discord.GuildMember
8282
? interaction.member

src/modules/tccpp/components/skill-role-suggestion.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
UserContextMenuInteractionBuilder,
1515
MessageContextMenuInteractionBuilder,
1616
} from "../../../command-abstractions/context-menu.js";
17-
import { ModalInteractionBuilder, BotModal } from "../../../command-abstractions/modal.js";
17+
import { ModalInteractionBuilder, BotModal, BotModalSubmitInteraction } from "../../../command-abstractions/modal.js";
1818
import { build_description, capitalize } from "../../../utils/strings.js";
1919
import { EarlyReplyMode, TextBasedCommandBuilder } from "../../../command-abstractions/text-based-command-builder.js";
2020
import { TextBasedCommand } from "../../../command-abstractions/text-based-command.js";
@@ -287,7 +287,7 @@ export default class SkillRoleSuggestion extends BotComponent {
287287
await interaction.showModal(modal);
288288
}
289289

290-
async handle_modal_submit(interaction: Discord.ModalSubmitInteraction) {
290+
async handle_modal_submit(interaction: BotModalSubmitInteraction) {
291291
await interaction.reply({
292292
content: "Working...",
293293
ephemeral: true,
@@ -297,7 +297,7 @@ export default class SkillRoleSuggestion extends BotComponent {
297297
? interaction.member
298298
: await this.wheatley.guild.members.fetch(interaction.user.id);
299299
const { member, role, context } = unwrap(this.target_map.get(interaction.user.id));
300-
const comments = this.suggestion_modal.get_field_value(interaction, "skill-role-suggestion-modal-comments");
300+
const comments = interaction.get_field_value("skill-role-suggestion-modal-comments");
301301
// TODO: Why does role need to be unwrapped?
302302
await this.handle_suggestion(member, suggester, unwrap(role), comments, context);
303303
await interaction.editReply({

0 commit comments

Comments
 (0)