blob: 5bfbfdb671db2d6659c0ebd5625f1d472986f34f [file] [log] [blame]
pineafan6de4da52023-03-07 20:43:44 +00001import { ButtonInteraction, TextInputBuilder } from "discord.js";
Skyler Grey75ea9172022-08-06 10:22:23 +01002import Discord, {
3 CommandInteraction,
4 Message,
TheCodedProf21c08592022-09-13 14:14:43 -04005 ActionRowBuilder,
6 ButtonBuilder,
Skyler Grey11236ba2022-08-08 21:13:33 +01007 ModalSubmitInteraction,
PineaFan100df682023-01-02 13:26:08 +00008 ButtonStyle,
9 TextInputStyle
Skyler Grey75ea9172022-08-06 10:22:23 +010010} from "discord.js";
pineafan73a7c4a2022-07-24 10:38:04 +010011import { modalInteractionCollector } from "./dualCollector.js";
pineafan63fc5e22022-08-04 22:04:10 +010012import EmojiEmbed from "./generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +000013import getEmojiByName from "./getEmojiByName.js";
pineafan4f164f32022-02-26 22:07:12 +000014
pineafan02ba0232022-07-24 22:16:15 +010015interface CustomBoolean<T> {
16 title: string;
17 disabled: boolean;
18 value: string | null;
PineaFana34d04b2023-01-03 22:05:42 +000019 notValue: string | null;
pineafan63fc5e22022-08-04 22:04:10 +010020 emoji: string | undefined;
pineafan02ba0232022-07-24 22:16:15 +010021 active: boolean;
22 onClick: () => Promise<T>;
23 response: T | null;
24}
25
pineafan4f164f32022-02-26 22:07:12 +000026class confirmationMessage {
pineafan6de4da52023-03-07 20:43:44 +000027 interaction: CommandInteraction | ButtonInteraction;
pineafan63fc5e22022-08-04 22:04:10 +010028 title = "";
29 emoji = "";
pineafan62ce1922022-08-25 20:34:45 +010030 redEmoji: string | null = null;
PineaFan1dee28f2023-01-16 22:09:07 +000031 failedMessage: string | null = null;
32 failedEmoji: string | null = null;
33 failedStatus: "Success" | "Danger" | "Warning" | null = null;
pineafan63fc5e22022-08-04 22:04:10 +010034 description = "";
Skyler Grey75ea9172022-08-06 10:22:23 +010035 color: "Danger" | "Warning" | "Success" = "Success";
36 customButtons: Record<string, CustomBoolean<unknown>> = {};
pineafan63fc5e22022-08-04 22:04:10 +010037 inverted = false;
pineafan73a7c4a2022-07-24 10:38:04 +010038 reason: string | null = null;
pineafan4f164f32022-02-26 22:07:12 +000039
pineafan6de4da52023-03-07 20:43:44 +000040 modals: {buttonText: string, emoji: string, customId: string, modal: Discord.ModalBuilder, value: string | undefined}[] = [];
41
42 constructor(interaction: CommandInteraction | ButtonInteraction) {
pineafan4f164f32022-02-26 22:07:12 +000043 this.interaction = interaction;
pineafan4f164f32022-02-26 22:07:12 +000044 }
45
Skyler Grey75ea9172022-08-06 10:22:23 +010046 setTitle(title: string) {
47 this.title = title;
48 return this;
49 }
PineaFan1dee28f2023-01-16 22:09:07 +000050 setEmoji(emoji: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010051 this.emoji = emoji;
52 return this;
53 }
pineafan62ce1922022-08-25 20:34:45 +010054 setDescription(description: string, timedOut?: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010055 this.description = description;
PineaFan1dee28f2023-01-16 22:09:07 +000056 if (timedOut) this.failedMessage = timedOut;
Skyler Grey75ea9172022-08-06 10:22:23 +010057 return this;
58 }
59 setColor(color: "Danger" | "Warning" | "Success") {
60 this.color = color;
61 return this;
62 }
63 setInverted(inverted: boolean) {
64 this.inverted = inverted;
65 return this;
66 }
Skyler Greyda16adf2023-03-05 10:22:12 +000067 setFailedMessage(
68 text: string,
69 failedStatus: "Success" | "Danger" | "Warning" | null,
70 failedEmoji: string | null = null
71 ) {
PineaFan1dee28f2023-01-16 22:09:07 +000072 this.failedMessage = text;
73 this.failedStatus = failedStatus;
74 this.failedEmoji = failedEmoji;
75 return this;
76 }
Skyler Grey75ea9172022-08-06 10:22:23 +010077 addCustomBoolean(
78 customId: string,
79 title: string,
80 disabled: boolean,
PineaFan100df682023-01-02 13:26:08 +000081 callback: (() => Promise<unknown>) | null = async () => null,
Skyler Grey75ea9172022-08-06 10:22:23 +010082 callbackClicked: string | null,
PineaFana34d04b2023-01-03 22:05:42 +000083 callbackNotClicked: string | null,
Skyler Grey75ea9172022-08-06 10:22:23 +010084 emoji?: string,
85 initial?: boolean
86 ) {
87 this.customButtons[customId] = {
88 title: title,
89 disabled: disabled,
90 value: callbackClicked,
PineaFana34d04b2023-01-03 22:05:42 +000091 notValue: callbackNotClicked,
Skyler Grey75ea9172022-08-06 10:22:23 +010092 emoji: emoji,
93 active: initial ?? false,
PineaFan100df682023-01-02 13:26:08 +000094 onClick: callback ?? (async () => null),
Skyler Grey75ea9172022-08-06 10:22:23 +010095 response: null
96 };
97 return this;
pineafan6fb3e072022-05-20 19:27:23 +010098 }
pineafan73a7c4a2022-07-24 10:38:04 +010099 addReasonButton(reason: string) {
100 this.reason = reason;
101 return this;
102 }
pineafan6de4da52023-03-07 20:43:44 +0000103 addModal(buttonText: string, emoji: string, customId: string, current: string, modal: Discord.ModalBuilder) {
104 modal.setCustomId(customId);
105 this.modals.push({buttonText, emoji, customId, modal, value: current});
106 return this;
107 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100108 async send(editOnly?: boolean): Promise<{
109 success?: boolean;
110 cancelled?: boolean;
111 components?: Record<string, CustomBoolean<unknown>>;
112 newReason?: string;
pineafan6de4da52023-03-07 20:43:44 +0000113 modals?: {buttonText: string, emoji: string, customId: string, modal: Discord.ModalBuilder, value: string | undefined}[];
Skyler Grey11236ba2022-08-08 21:13:33 +0100114 }> {
Skyler Greya402d1c2022-08-13 23:18:16 +0100115 let cancelled = false;
116 let success: boolean | undefined = undefined;
117 let returnComponents = false;
118 let newReason = undefined;
119
120 while (!cancelled && success === undefined && !returnComponents && !newReason) {
pineafan63fc5e22022-08-04 22:04:10 +0100121 const fullComponents = [
TheCodedProf21c08592022-09-13 14:14:43 -0400122 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100123 .setCustomId("yes")
124 .setLabel("Confirm")
TheCodedProf21c08592022-09-13 14:14:43 -0400125 .setStyle(this.inverted ? ButtonStyle.Success : ButtonStyle.Danger)
pineafan02ba0232022-07-24 22:16:15 +0100126 .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
TheCodedProf21c08592022-09-13 14:14:43 -0400127 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100128 .setCustomId("no")
129 .setLabel("Cancel")
TheCodedProf21c08592022-09-13 14:14:43 -0400130 .setStyle(ButtonStyle.Secondary)
pineafan02ba0232022-07-24 22:16:15 +0100131 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100132 ];
pineafan02ba0232022-07-24 22:16:15 +0100133 Object.entries(this.customButtons).forEach(([k, v]) => {
TheCodedProf21c08592022-09-13 14:14:43 -0400134 const button = new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100135 .setCustomId(k)
136 .setLabel(v.title)
TheCodedProf21c08592022-09-13 14:14:43 -0400137 .setStyle(v.active ? ButtonStyle.Success : ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100138 .setDisabled(v.disabled);
Skyler Grey11236ba2022-08-08 21:13:33 +0100139 if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
Skyler Grey75ea9172022-08-06 10:22:23 +0100140 fullComponents.push(button);
pineafan63fc5e22022-08-04 22:04:10 +0100141 });
pineafan6de4da52023-03-07 20:43:44 +0000142 for (const modal of this.modals) {
143 fullComponents.push(
144 new Discord.ButtonBuilder()
145 .setCustomId(modal.customId)
146 .setLabel(modal.buttonText)
147 .setStyle(ButtonStyle.Primary)
148 .setEmoji(getEmojiByName(modal.emoji, "id"))
149 .setDisabled(false)
150 );
151 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100152 if (this.reason !== null)
153 fullComponents.push(
TheCodedProf21c08592022-09-13 14:14:43 -0400154 new Discord.ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100155 .setCustomId("reason")
156 .setLabel("Edit Reason")
TheCodedProf21c08592022-09-13 14:14:43 -0400157 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100158 .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
159 .setDisabled(false)
160 );
pineafan63fc5e22022-08-04 22:04:10 +0100161 const components = [];
pineafan02ba0232022-07-24 22:16:15 +0100162 for (let i = 0; i < fullComponents.length; i += 5) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000163 components.push(
164 new ActionRowBuilder<
165 | Discord.ButtonBuilder
166 | Discord.StringSelectMenuBuilder
167 | Discord.RoleSelectMenuBuilder
168 | Discord.UserSelectMenuBuilder
169 >().addComponents(fullComponents.slice(i, i + 5))
170 );
pineafan02ba0232022-07-24 22:16:15 +0100171 }
pineafan63fc5e22022-08-04 22:04:10 +0100172 const object = {
pineafan377794f2022-04-18 19:01:01 +0100173 embeds: [
pineafan4edb7762022-06-26 19:21:04 +0100174 new EmojiEmbed()
pineafan377794f2022-04-18 19:01:01 +0100175 .setEmoji(this.emoji)
176 .setTitle(this.title)
Skyler Grey75ea9172022-08-06 10:22:23 +0100177 .setDescription(
178 this.description +
179 "\n\n" +
180 Object.values(this.customButtons)
181 .map((v) => {
PineaFana34d04b2023-01-03 22:05:42 +0000182 if (v.active) {
183 return v.value ? `*${v.value}*\n` : "";
184 } else {
185 return v.notValue ? `*${v.notValue}*\n` : "";
186 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000187 })
188 .join("")
Skyler Grey75ea9172022-08-06 10:22:23 +0100189 )
pineafan377794f2022-04-18 19:01:01 +0100190 .setStatus(this.color)
pineafan377794f2022-04-18 19:01:01 +0100191 ],
pineafan02ba0232022-07-24 22:16:15 +0100192 components: components,
pineafan377794f2022-04-18 19:01:01 +0100193 ephemeral: true,
194 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +0100195 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100196 let m: Message;
pineafan02ba0232022-07-24 22:16:15 +0100197 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100198 if (editOnly) {
PineaFan100df682023-01-02 13:26:08 +0000199 m = (await this.interaction.editReply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100200 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100201 m = (await this.interaction.reply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100202 }
PineaFana34d04b2023-01-03 22:05:42 +0000203 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100204 cancelled = true;
205 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100206 }
pineafan377794f2022-04-18 19:01:01 +0100207 let component;
208 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100209 component = await m.awaitMessageComponent({
Skyler Greyda16adf2023-03-05 10:22:12 +0000210 filter: (i) =>
211 i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id,
Skyler Grey75ea9172022-08-06 10:22:23 +0100212 time: 300000
213 });
pineafan377794f2022-04-18 19:01:01 +0100214 } catch (e) {
pineafan6de4da52023-03-07 20:43:44 +0000215 success = false
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500216 break;
pineafan377794f2022-04-18 19:01:01 +0100217 }
218 if (component.customId === "yes") {
219 component.deferUpdate();
pineafan63fc5e22022-08-04 22:04:10 +0100220 for (const v of Object.values(this.customButtons)) {
221 if (!v.active) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100222 try {
223 v.response = await v.onClick();
224 } catch (e) {
225 console.log(e);
226 }
pineafan63fc5e22022-08-04 22:04:10 +0100227 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100228 success = true;
229 returnComponents = true;
230 continue;
pineafan377794f2022-04-18 19:01:01 +0100231 } else if (component.customId === "no") {
232 component.deferUpdate();
Skyler Greya402d1c2022-08-13 23:18:16 +0100233 success = false;
234 returnComponents = true;
235 continue;
pineafan73a7c4a2022-07-24 10:38:04 +0100236 } else if (component.customId === "reason") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100237 await component.showModal(
PineaFan100df682023-01-02 13:26:08 +0000238 new Discord.ModalBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 .setCustomId("modal")
240 .setTitle("Editing reason")
241 .addComponents(
PineaFan100df682023-01-02 13:26:08 +0000242 new ActionRowBuilder<TextInputBuilder>().addComponents(
243 new TextInputBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100244 .setCustomId("reason")
245 .setLabel("Reason")
246 .setMaxLength(2000)
247 .setRequired(false)
PineaFan100df682023-01-02 13:26:08 +0000248 .setStyle(TextInputStyle.Paragraph)
Skyler Grey75ea9172022-08-06 10:22:23 +0100249 .setPlaceholder("Spammed in #general")
250 .setValue(this.reason ? this.reason : "")
251 )
252 )
253 );
pineafan73a7c4a2022-07-24 10:38:04 +0100254 await this.interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100255 embeds: [
256 new EmojiEmbed()
257 .setTitle(this.title)
Skyler Grey11236ba2022-08-08 21:13:33 +0100258 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100259 .setStatus(this.color)
260 .setEmoji(this.emoji)
261 ],
262 components: [
PineaFan100df682023-01-02 13:26:08 +0000263 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400264 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100265 .setLabel("Back")
266 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400267 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100268 .setCustomId("back")
PineaFan100df682023-01-02 13:26:08 +0000269 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100270 ]
pineafan73a7c4a2022-07-24 10:38:04 +0100271 });
272 let out;
273 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000274 out = (await modalInteractionCollector(
275 m,
276 this.interaction.user
277 )) as Discord.ModalSubmitInteraction | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100278 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100279 cancelled = true;
280 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100281 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000282 if (out === null || out.isButton()) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100283 cancelled = true;
284 continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100285 }
286 if (out instanceof ModalSubmitInteraction) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100287 newReason = out.fields.getTextInputValue("reason");
288 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100289 } else {
Skyler Greya402d1c2022-08-13 23:18:16 +0100290 returnComponents = true;
291 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100292 }
pineafan6de4da52023-03-07 20:43:44 +0000293 } else if (this.modals.map((m) => m.customId).includes(component.customId)) {
294 const chosenModal = this.modals.find((component => m => m.customId === component.customId)(component));
295 await component.showModal(chosenModal!.modal);
296 await this.interaction.editReply({
297 embeds: [
298 new EmojiEmbed()
299 .setTitle(this.title)
300 .setDescription("Modal opened. If you can't see it, click back and try again.")
301 .setStatus(this.color)
302 .setEmoji(this.emoji)
303 ],
304 components: [
305 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
306 new ButtonBuilder()
307 .setLabel("Back")
308 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
309 .setStyle(ButtonStyle.Primary)
310 .setCustomId("back")
311 )
312 ]
313 });
314 let out;
315 try {
316 out = (await modalInteractionCollector(
317 m,
318 this.interaction.user
319 )) as Discord.ModalSubmitInteraction | null;
320 } catch (e) {
321 console.log(e);
322 cancelled = true;
323 continue;
324 }
325 if (out === null || out.isButton()) {
326 continue;
327 }
328 if (out instanceof ModalSubmitInteraction) {
329 chosenModal!.value = out.fields.getTextInputValue("default");
330 }
331 returnComponents = true;
332 continue;
pineafan02ba0232022-07-24 22:16:15 +0100333 } else {
334 component.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100335 this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
Skyler Greya402d1c2022-08-13 23:18:16 +0100336 returnComponents = true;
337 continue;
pineafan377794f2022-04-18 19:01:01 +0100338 }
pineafan8b4b17f2022-02-27 20:42:52 +0000339 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100340 const returnValue: Awaited<ReturnType<typeof this.send>> = {};
341
pineafan62ce1922022-08-25 20:34:45 +0100342 if (cancelled) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000343 await this.timeoutError();
pineafan62ce1922022-08-25 20:34:45 +0100344 returnValue.cancelled = true;
345 }
TheCodedProff86ba092023-01-27 17:10:07 -0500346 if (success === false) {
PineaFan1dee28f2023-01-16 22:09:07 +0000347 await this.interaction.editReply({
Skyler Greyda16adf2023-03-05 10:22:12 +0000348 embeds: [
349 new EmojiEmbed()
350 .setTitle(this.title)
351 .setDescription(this.failedMessage ?? "*Message timed out*")
352 .setStatus(this.failedStatus ?? "Danger")
353 .setEmoji(this.failedEmoji ?? this.redEmoji ?? this.emoji)
354 ],
355 components: []
PineaFan1dee28f2023-01-16 22:09:07 +0000356 });
pineafan6de4da52023-03-07 20:43:44 +0000357 return { success: false, cancelled: returnValue.cancelled ?? false };
PineaFan1dee28f2023-01-16 22:09:07 +0000358 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500359 if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
360 if (success !== undefined) returnValue.success = success;
Skyler Greya402d1c2022-08-13 23:18:16 +0100361 if (newReason) returnValue.newReason = newReason;
pineafan6de4da52023-03-07 20:43:44 +0000362 returnValue.modals = this.modals;
Skyler Greya402d1c2022-08-13 23:18:16 +0100363
pineafan6de4da52023-03-07 20:43:44 +0000364 const modals = this.modals;
Skyler Greyda16adf2023-03-05 10:22:12 +0000365 const typedReturnValue = returnValue as
366 | { cancelled: true }
pineafan6de4da52023-03-07 20:43:44 +0000367 | { success: boolean; components: Record<string, CustomBoolean<unknown>>; modals: typeof modals; newReason?: string }
368 | { newReason: string; components: Record<string, CustomBoolean<unknown>>; modals: typeof modals }
369 | { components: Record<string, CustomBoolean<unknown>>; modals: typeof modals };
PineaFan100df682023-01-02 13:26:08 +0000370
371 return typedReturnValue;
pineafan4f164f32022-02-26 22:07:12 +0000372 }
pineafan62ce1922022-08-25 20:34:45 +0100373
374 async timeoutError(): Promise<void> {
375 await this.interaction.editReply({
376 embeds: [
377 new EmojiEmbed()
378 .setTitle(this.title)
379 .setDescription("We closed this message because it was not used for a while.")
380 .setStatus("Danger")
PineaFan1dee28f2023-01-16 22:09:07 +0000381 .setEmoji("CONTROL.BLOCKCROSS")
pineafan62ce1922022-08-25 20:34:45 +0100382 ],
383 components: []
Skyler Greyda16adf2023-03-05 10:22:12 +0000384 });
pineafan62ce1922022-08-25 20:34:45 +0100385 }
pineafan4f164f32022-02-26 22:07:12 +0000386}
387
pineafan73a7c4a2022-07-24 10:38:04 +0100388export default confirmationMessage;