blob: 05c549445a9d471e2b7176ce49c47adfd1851906 [file] [log] [blame]
TheCodedProff86ba092023-01-27 17:10:07 -05001import { 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 {
27 interaction: CommandInteraction;
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
40 constructor(interaction: CommandInteraction) {
41 this.interaction = interaction;
pineafan4f164f32022-02-26 22:07:12 +000042 }
43
Skyler Grey75ea9172022-08-06 10:22:23 +010044 setTitle(title: string) {
45 this.title = title;
46 return this;
47 }
PineaFan1dee28f2023-01-16 22:09:07 +000048 setEmoji(emoji: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010049 this.emoji = emoji;
50 return this;
51 }
pineafan62ce1922022-08-25 20:34:45 +010052 setDescription(description: string, timedOut?: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010053 this.description = description;
PineaFan1dee28f2023-01-16 22:09:07 +000054 if (timedOut) this.failedMessage = timedOut;
Skyler Grey75ea9172022-08-06 10:22:23 +010055 return this;
56 }
57 setColor(color: "Danger" | "Warning" | "Success") {
58 this.color = color;
59 return this;
60 }
61 setInverted(inverted: boolean) {
62 this.inverted = inverted;
63 return this;
64 }
Skyler Greyda16adf2023-03-05 10:22:12 +000065 setFailedMessage(
66 text: string,
67 failedStatus: "Success" | "Danger" | "Warning" | null,
68 failedEmoji: string | null = null
69 ) {
PineaFan1dee28f2023-01-16 22:09:07 +000070 this.failedMessage = text;
71 this.failedStatus = failedStatus;
72 this.failedEmoji = failedEmoji;
73 return this;
74 }
Skyler Grey75ea9172022-08-06 10:22:23 +010075 addCustomBoolean(
76 customId: string,
77 title: string,
78 disabled: boolean,
PineaFan100df682023-01-02 13:26:08 +000079 callback: (() => Promise<unknown>) | null = async () => null,
Skyler Grey75ea9172022-08-06 10:22:23 +010080 callbackClicked: string | null,
PineaFana34d04b2023-01-03 22:05:42 +000081 callbackNotClicked: string | null,
Skyler Grey75ea9172022-08-06 10:22:23 +010082 emoji?: string,
83 initial?: boolean
84 ) {
85 this.customButtons[customId] = {
86 title: title,
87 disabled: disabled,
88 value: callbackClicked,
PineaFana34d04b2023-01-03 22:05:42 +000089 notValue: callbackNotClicked,
Skyler Grey75ea9172022-08-06 10:22:23 +010090 emoji: emoji,
91 active: initial ?? false,
PineaFan100df682023-01-02 13:26:08 +000092 onClick: callback ?? (async () => null),
Skyler Grey75ea9172022-08-06 10:22:23 +010093 response: null
94 };
95 return this;
pineafan6fb3e072022-05-20 19:27:23 +010096 }
pineafan73a7c4a2022-07-24 10:38:04 +010097 addReasonButton(reason: string) {
98 this.reason = reason;
99 return this;
100 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100101 async send(editOnly?: boolean): Promise<{
102 success?: boolean;
103 cancelled?: boolean;
104 components?: Record<string, CustomBoolean<unknown>>;
105 newReason?: string;
106 }> {
Skyler Greya402d1c2022-08-13 23:18:16 +0100107 let cancelled = false;
108 let success: boolean | undefined = undefined;
109 let returnComponents = false;
110 let newReason = undefined;
111
112 while (!cancelled && success === undefined && !returnComponents && !newReason) {
pineafan63fc5e22022-08-04 22:04:10 +0100113 const fullComponents = [
TheCodedProf21c08592022-09-13 14:14:43 -0400114 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100115 .setCustomId("yes")
116 .setLabel("Confirm")
TheCodedProf21c08592022-09-13 14:14:43 -0400117 .setStyle(this.inverted ? ButtonStyle.Success : ButtonStyle.Danger)
pineafan02ba0232022-07-24 22:16:15 +0100118 .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
TheCodedProf21c08592022-09-13 14:14:43 -0400119 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100120 .setCustomId("no")
121 .setLabel("Cancel")
TheCodedProf21c08592022-09-13 14:14:43 -0400122 .setStyle(ButtonStyle.Secondary)
pineafan02ba0232022-07-24 22:16:15 +0100123 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100124 ];
pineafan02ba0232022-07-24 22:16:15 +0100125 Object.entries(this.customButtons).forEach(([k, v]) => {
TheCodedProf21c08592022-09-13 14:14:43 -0400126 const button = new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100127 .setCustomId(k)
128 .setLabel(v.title)
TheCodedProf21c08592022-09-13 14:14:43 -0400129 .setStyle(v.active ? ButtonStyle.Success : ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100130 .setDisabled(v.disabled);
Skyler Grey11236ba2022-08-08 21:13:33 +0100131 if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
Skyler Grey75ea9172022-08-06 10:22:23 +0100132 fullComponents.push(button);
pineafan63fc5e22022-08-04 22:04:10 +0100133 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100134 if (this.reason !== null)
135 fullComponents.push(
TheCodedProf21c08592022-09-13 14:14:43 -0400136 new Discord.ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100137 .setCustomId("reason")
138 .setLabel("Edit Reason")
TheCodedProf21c08592022-09-13 14:14:43 -0400139 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100140 .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
141 .setDisabled(false)
142 );
pineafan63fc5e22022-08-04 22:04:10 +0100143 const components = [];
pineafan02ba0232022-07-24 22:16:15 +0100144 for (let i = 0; i < fullComponents.length; i += 5) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000145 components.push(
146 new ActionRowBuilder<
147 | Discord.ButtonBuilder
148 | Discord.StringSelectMenuBuilder
149 | Discord.RoleSelectMenuBuilder
150 | Discord.UserSelectMenuBuilder
151 >().addComponents(fullComponents.slice(i, i + 5))
152 );
pineafan02ba0232022-07-24 22:16:15 +0100153 }
pineafan63fc5e22022-08-04 22:04:10 +0100154 const object = {
pineafan377794f2022-04-18 19:01:01 +0100155 embeds: [
pineafan4edb7762022-06-26 19:21:04 +0100156 new EmojiEmbed()
pineafan377794f2022-04-18 19:01:01 +0100157 .setEmoji(this.emoji)
158 .setTitle(this.title)
Skyler Grey75ea9172022-08-06 10:22:23 +0100159 .setDescription(
160 this.description +
161 "\n\n" +
162 Object.values(this.customButtons)
163 .map((v) => {
PineaFana34d04b2023-01-03 22:05:42 +0000164 if (v.active) {
165 return v.value ? `*${v.value}*\n` : "";
166 } else {
167 return v.notValue ? `*${v.notValue}*\n` : "";
168 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000169 })
170 .join("")
Skyler Grey75ea9172022-08-06 10:22:23 +0100171 )
pineafan377794f2022-04-18 19:01:01 +0100172 .setStatus(this.color)
pineafan377794f2022-04-18 19:01:01 +0100173 ],
pineafan02ba0232022-07-24 22:16:15 +0100174 components: components,
pineafan377794f2022-04-18 19:01:01 +0100175 ephemeral: true,
176 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +0100177 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100178 let m: Message;
pineafan02ba0232022-07-24 22:16:15 +0100179 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100180 if (editOnly) {
PineaFan100df682023-01-02 13:26:08 +0000181 m = (await this.interaction.editReply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100182 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100183 m = (await this.interaction.reply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100184 }
PineaFana34d04b2023-01-03 22:05:42 +0000185 } catch (e) {
186 console.log(e);
Skyler Greya402d1c2022-08-13 23:18:16 +0100187 cancelled = true;
188 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100189 }
pineafan377794f2022-04-18 19:01:01 +0100190 let component;
191 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100192 component = await m.awaitMessageComponent({
Skyler Greyda16adf2023-03-05 10:22:12 +0000193 filter: (i) =>
194 i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id,
Skyler Grey75ea9172022-08-06 10:22:23 +0100195 time: 300000
196 });
pineafan377794f2022-04-18 19:01:01 +0100197 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100198 success = false;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500199 break;
pineafan377794f2022-04-18 19:01:01 +0100200 }
201 if (component.customId === "yes") {
202 component.deferUpdate();
pineafan63fc5e22022-08-04 22:04:10 +0100203 for (const v of Object.values(this.customButtons)) {
204 if (!v.active) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100205 try {
206 v.response = await v.onClick();
207 } catch (e) {
208 console.log(e);
209 }
pineafan63fc5e22022-08-04 22:04:10 +0100210 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100211 success = true;
212 returnComponents = true;
213 continue;
pineafan377794f2022-04-18 19:01:01 +0100214 } else if (component.customId === "no") {
215 component.deferUpdate();
Skyler Greya402d1c2022-08-13 23:18:16 +0100216 success = false;
217 returnComponents = true;
218 continue;
pineafan73a7c4a2022-07-24 10:38:04 +0100219 } else if (component.customId === "reason") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100220 await component.showModal(
PineaFan100df682023-01-02 13:26:08 +0000221 new Discord.ModalBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100222 .setCustomId("modal")
223 .setTitle("Editing reason")
224 .addComponents(
PineaFan100df682023-01-02 13:26:08 +0000225 new ActionRowBuilder<TextInputBuilder>().addComponents(
226 new TextInputBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100227 .setCustomId("reason")
228 .setLabel("Reason")
229 .setMaxLength(2000)
230 .setRequired(false)
PineaFan100df682023-01-02 13:26:08 +0000231 .setStyle(TextInputStyle.Paragraph)
Skyler Grey75ea9172022-08-06 10:22:23 +0100232 .setPlaceholder("Spammed in #general")
233 .setValue(this.reason ? this.reason : "")
234 )
235 )
236 );
pineafan73a7c4a2022-07-24 10:38:04 +0100237 await this.interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100238 embeds: [
239 new EmojiEmbed()
240 .setTitle(this.title)
Skyler Grey11236ba2022-08-08 21:13:33 +0100241 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100242 .setStatus(this.color)
243 .setEmoji(this.emoji)
244 ],
245 components: [
PineaFan100df682023-01-02 13:26:08 +0000246 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400247 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100248 .setLabel("Back")
249 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400250 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100251 .setCustomId("back")
PineaFan100df682023-01-02 13:26:08 +0000252 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 ]
pineafan73a7c4a2022-07-24 10:38:04 +0100254 });
255 let out;
256 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000257 out = (await modalInteractionCollector(
258 m,
259 this.interaction.user
260 )) as Discord.ModalSubmitInteraction | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100261 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100262 cancelled = true;
263 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100264 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000265 if (out === null || out.isButton()) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100266 cancelled = true;
267 continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100268 }
269 if (out instanceof ModalSubmitInteraction) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100270 newReason = out.fields.getTextInputValue("reason");
271 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100272 } else {
Skyler Greya402d1c2022-08-13 23:18:16 +0100273 returnComponents = true;
274 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100275 }
pineafan02ba0232022-07-24 22:16:15 +0100276 } else {
277 component.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100278 this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
Skyler Greya402d1c2022-08-13 23:18:16 +0100279 returnComponents = true;
280 continue;
pineafan377794f2022-04-18 19:01:01 +0100281 }
pineafan8b4b17f2022-02-27 20:42:52 +0000282 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100283 const returnValue: Awaited<ReturnType<typeof this.send>> = {};
284
pineafan62ce1922022-08-25 20:34:45 +0100285 if (cancelled) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000286 await this.timeoutError();
pineafan62ce1922022-08-25 20:34:45 +0100287 returnValue.cancelled = true;
288 }
TheCodedProff86ba092023-01-27 17:10:07 -0500289 if (success === false) {
PineaFan1dee28f2023-01-16 22:09:07 +0000290 await this.interaction.editReply({
Skyler Greyda16adf2023-03-05 10:22:12 +0000291 embeds: [
292 new EmojiEmbed()
293 .setTitle(this.title)
294 .setDescription(this.failedMessage ?? "*Message timed out*")
295 .setStatus(this.failedStatus ?? "Danger")
296 .setEmoji(this.failedEmoji ?? this.redEmoji ?? this.emoji)
297 ],
298 components: []
PineaFan1dee28f2023-01-16 22:09:07 +0000299 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000300 return { success: false };
PineaFan1dee28f2023-01-16 22:09:07 +0000301 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500302 if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
303 if (success !== undefined) returnValue.success = success;
Skyler Greya402d1c2022-08-13 23:18:16 +0100304 if (newReason) returnValue.newReason = newReason;
305
Skyler Greyda16adf2023-03-05 10:22:12 +0000306 const typedReturnValue = returnValue as
307 | { cancelled: true }
308 | { success: boolean; components: Record<string, CustomBoolean<unknown>>; newReason?: string }
309 | { newReason: string; components: Record<string, CustomBoolean<unknown>> }
310 | { components: Record<string, CustomBoolean<unknown>> };
PineaFan100df682023-01-02 13:26:08 +0000311
312 return typedReturnValue;
pineafan4f164f32022-02-26 22:07:12 +0000313 }
pineafan62ce1922022-08-25 20:34:45 +0100314
315 async timeoutError(): Promise<void> {
316 await this.interaction.editReply({
317 embeds: [
318 new EmojiEmbed()
319 .setTitle(this.title)
320 .setDescription("We closed this message because it was not used for a while.")
321 .setStatus("Danger")
PineaFan1dee28f2023-01-16 22:09:07 +0000322 .setEmoji("CONTROL.BLOCKCROSS")
pineafan62ce1922022-08-25 20:34:45 +0100323 ],
324 components: []
Skyler Greyda16adf2023-03-05 10:22:12 +0000325 });
pineafan62ce1922022-08-25 20:34:45 +0100326 }
pineafan4f164f32022-02-26 22:07:12 +0000327}
328
pineafan73a7c4a2022-07-24 10:38:04 +0100329export default confirmationMessage;