blob: f7cccaff5d33e119a5388fe8c5e185414a9f9534 [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 }
PineaFan1dee28f2023-01-16 22:09:07 +000065 setFailedMessage(text: string, failedStatus: "Success" | "Danger" | "Warning" | null, failedEmoji: string | null = null) {
66 this.failedMessage = text;
67 this.failedStatus = failedStatus;
68 this.failedEmoji = failedEmoji;
69 return this;
70 }
Skyler Grey75ea9172022-08-06 10:22:23 +010071 addCustomBoolean(
72 customId: string,
73 title: string,
74 disabled: boolean,
PineaFan100df682023-01-02 13:26:08 +000075 callback: (() => Promise<unknown>) | null = async () => null,
Skyler Grey75ea9172022-08-06 10:22:23 +010076 callbackClicked: string | null,
PineaFana34d04b2023-01-03 22:05:42 +000077 callbackNotClicked: string | null,
Skyler Grey75ea9172022-08-06 10:22:23 +010078 emoji?: string,
79 initial?: boolean
80 ) {
81 this.customButtons[customId] = {
82 title: title,
83 disabled: disabled,
84 value: callbackClicked,
PineaFana34d04b2023-01-03 22:05:42 +000085 notValue: callbackNotClicked,
Skyler Grey75ea9172022-08-06 10:22:23 +010086 emoji: emoji,
87 active: initial ?? false,
PineaFan100df682023-01-02 13:26:08 +000088 onClick: callback ?? (async () => null),
Skyler Grey75ea9172022-08-06 10:22:23 +010089 response: null
90 };
91 return this;
pineafan6fb3e072022-05-20 19:27:23 +010092 }
pineafan73a7c4a2022-07-24 10:38:04 +010093 addReasonButton(reason: string) {
94 this.reason = reason;
95 return this;
96 }
Skyler Grey11236ba2022-08-08 21:13:33 +010097 async send(editOnly?: boolean): Promise<{
98 success?: boolean;
99 cancelled?: boolean;
100 components?: Record<string, CustomBoolean<unknown>>;
101 newReason?: string;
102 }> {
Skyler Greya402d1c2022-08-13 23:18:16 +0100103 let cancelled = false;
104 let success: boolean | undefined = undefined;
105 let returnComponents = false;
106 let newReason = undefined;
107
108 while (!cancelled && success === undefined && !returnComponents && !newReason) {
pineafan63fc5e22022-08-04 22:04:10 +0100109 const fullComponents = [
TheCodedProf21c08592022-09-13 14:14:43 -0400110 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100111 .setCustomId("yes")
112 .setLabel("Confirm")
TheCodedProf21c08592022-09-13 14:14:43 -0400113 .setStyle(this.inverted ? ButtonStyle.Success : ButtonStyle.Danger)
pineafan02ba0232022-07-24 22:16:15 +0100114 .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
TheCodedProf21c08592022-09-13 14:14:43 -0400115 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100116 .setCustomId("no")
117 .setLabel("Cancel")
TheCodedProf21c08592022-09-13 14:14:43 -0400118 .setStyle(ButtonStyle.Secondary)
pineafan02ba0232022-07-24 22:16:15 +0100119 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100120 ];
pineafan02ba0232022-07-24 22:16:15 +0100121 Object.entries(this.customButtons).forEach(([k, v]) => {
TheCodedProf21c08592022-09-13 14:14:43 -0400122 const button = new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100123 .setCustomId(k)
124 .setLabel(v.title)
TheCodedProf21c08592022-09-13 14:14:43 -0400125 .setStyle(v.active ? ButtonStyle.Success : ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100126 .setDisabled(v.disabled);
Skyler Grey11236ba2022-08-08 21:13:33 +0100127 if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
Skyler Grey75ea9172022-08-06 10:22:23 +0100128 fullComponents.push(button);
pineafan63fc5e22022-08-04 22:04:10 +0100129 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100130 if (this.reason !== null)
131 fullComponents.push(
TheCodedProf21c08592022-09-13 14:14:43 -0400132 new Discord.ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100133 .setCustomId("reason")
134 .setLabel("Edit Reason")
TheCodedProf21c08592022-09-13 14:14:43 -0400135 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100136 .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
137 .setDisabled(false)
138 );
pineafan63fc5e22022-08-04 22:04:10 +0100139 const components = [];
pineafan02ba0232022-07-24 22:16:15 +0100140 for (let i = 0; i < fullComponents.length; i += 5) {
PineaFan100df682023-01-02 13:26:08 +0000141 components.push(new ActionRowBuilder<
142 Discord.ButtonBuilder | Discord.StringSelectMenuBuilder |
143 Discord.RoleSelectMenuBuilder | Discord.UserSelectMenuBuilder
144 >().addComponents(fullComponents.slice(i, i + 5)));
pineafan02ba0232022-07-24 22:16:15 +0100145 }
pineafan63fc5e22022-08-04 22:04:10 +0100146 const object = {
pineafan377794f2022-04-18 19:01:01 +0100147 embeds: [
pineafan4edb7762022-06-26 19:21:04 +0100148 new EmojiEmbed()
pineafan377794f2022-04-18 19:01:01 +0100149 .setEmoji(this.emoji)
150 .setTitle(this.title)
Skyler Grey75ea9172022-08-06 10:22:23 +0100151 .setDescription(
152 this.description +
153 "\n\n" +
154 Object.values(this.customButtons)
155 .map((v) => {
PineaFana34d04b2023-01-03 22:05:42 +0000156 if (v.active) {
157 return v.value ? `*${v.value}*\n` : "";
158 } else {
159 return v.notValue ? `*${v.notValue}*\n` : "";
160 }
161 }).join("")
Skyler Grey75ea9172022-08-06 10:22:23 +0100162 )
pineafan377794f2022-04-18 19:01:01 +0100163 .setStatus(this.color)
pineafan377794f2022-04-18 19:01:01 +0100164 ],
pineafan02ba0232022-07-24 22:16:15 +0100165 components: components,
pineafan377794f2022-04-18 19:01:01 +0100166 ephemeral: true,
167 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +0100168 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100169 let m: Message;
pineafan02ba0232022-07-24 22:16:15 +0100170 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100171 if (editOnly) {
PineaFan100df682023-01-02 13:26:08 +0000172 m = (await this.interaction.editReply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100173 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100174 m = (await this.interaction.reply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100175 }
PineaFana34d04b2023-01-03 22:05:42 +0000176 } catch (e) {
177 console.log(e);
Skyler Greya402d1c2022-08-13 23:18:16 +0100178 cancelled = true;
179 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100180 }
pineafan377794f2022-04-18 19:01:01 +0100181 let component;
182 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100183 component = await m.awaitMessageComponent({
TheCodedProffe0a7862023-01-27 20:36:35 -0500184 filter: (i) => i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id,
Skyler Grey75ea9172022-08-06 10:22:23 +0100185 time: 300000
186 });
pineafan377794f2022-04-18 19:01:01 +0100187 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100188 success = false;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500189 break;
pineafan377794f2022-04-18 19:01:01 +0100190 }
191 if (component.customId === "yes") {
192 component.deferUpdate();
pineafan63fc5e22022-08-04 22:04:10 +0100193 for (const v of Object.values(this.customButtons)) {
194 if (!v.active) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100195 try {
196 v.response = await v.onClick();
197 } catch (e) {
198 console.log(e);
199 }
pineafan63fc5e22022-08-04 22:04:10 +0100200 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100201 success = true;
202 returnComponents = true;
203 continue;
pineafan377794f2022-04-18 19:01:01 +0100204 } else if (component.customId === "no") {
205 component.deferUpdate();
Skyler Greya402d1c2022-08-13 23:18:16 +0100206 success = false;
207 returnComponents = true;
208 continue;
pineafan73a7c4a2022-07-24 10:38:04 +0100209 } else if (component.customId === "reason") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100210 await component.showModal(
PineaFan100df682023-01-02 13:26:08 +0000211 new Discord.ModalBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100212 .setCustomId("modal")
213 .setTitle("Editing reason")
214 .addComponents(
PineaFan100df682023-01-02 13:26:08 +0000215 new ActionRowBuilder<TextInputBuilder>().addComponents(
216 new TextInputBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100217 .setCustomId("reason")
218 .setLabel("Reason")
219 .setMaxLength(2000)
220 .setRequired(false)
PineaFan100df682023-01-02 13:26:08 +0000221 .setStyle(TextInputStyle.Paragraph)
Skyler Grey75ea9172022-08-06 10:22:23 +0100222 .setPlaceholder("Spammed in #general")
223 .setValue(this.reason ? this.reason : "")
224 )
225 )
226 );
pineafan73a7c4a2022-07-24 10:38:04 +0100227 await this.interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100228 embeds: [
229 new EmojiEmbed()
230 .setTitle(this.title)
Skyler Grey11236ba2022-08-08 21:13:33 +0100231 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100232 .setStatus(this.color)
233 .setEmoji(this.emoji)
234 ],
235 components: [
PineaFan100df682023-01-02 13:26:08 +0000236 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400237 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100238 .setLabel("Back")
239 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400240 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100241 .setCustomId("back")
PineaFan100df682023-01-02 13:26:08 +0000242 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100243 ]
pineafan73a7c4a2022-07-24 10:38:04 +0100244 });
245 let out;
246 try {
TheCodedProf01cba762023-02-18 15:55:05 -0500247 out = await modalInteractionCollector(m, this.interaction.user) as Discord.ModalSubmitInteraction | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100248 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100249 cancelled = true;
250 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100251 }
TheCodedProf4a6d5712023-01-19 15:54:40 -0500252 if (out === null || out.isButton()) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100253 cancelled = true;
254 continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100255 }
256 if (out instanceof ModalSubmitInteraction) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100257 newReason = out.fields.getTextInputValue("reason");
258 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100259 } else {
Skyler Greya402d1c2022-08-13 23:18:16 +0100260 returnComponents = true;
261 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100262 }
pineafan02ba0232022-07-24 22:16:15 +0100263 } else {
264 component.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100265 this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
Skyler Greya402d1c2022-08-13 23:18:16 +0100266 returnComponents = true;
267 continue;
pineafan377794f2022-04-18 19:01:01 +0100268 }
pineafan8b4b17f2022-02-27 20:42:52 +0000269 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100270 const returnValue: Awaited<ReturnType<typeof this.send>> = {};
271
pineafan62ce1922022-08-25 20:34:45 +0100272 if (cancelled) {
273 await this.timeoutError()
274 returnValue.cancelled = true;
275 }
TheCodedProff86ba092023-01-27 17:10:07 -0500276 if (success === false) {
PineaFan1dee28f2023-01-16 22:09:07 +0000277 await this.interaction.editReply({
278 embeds: [new EmojiEmbed()
279 .setTitle(this.title)
TheCodedProf267563a2023-01-21 17:00:57 -0500280 .setDescription(this.failedMessage ?? "*Message timed out*")
PineaFan1dee28f2023-01-16 22:09:07 +0000281 .setStatus(this.failedStatus ?? "Danger")
282 .setEmoji(this.failedEmoji ?? this.redEmoji ?? this.emoji)
283 ], components: []
284 });
285 return {success: false}
286 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500287 if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
288 if (success !== undefined) returnValue.success = success;
Skyler Greya402d1c2022-08-13 23:18:16 +0100289 if (newReason) returnValue.newReason = newReason;
290
PineaFan100df682023-01-02 13:26:08 +0000291 const typedReturnValue = returnValue as {cancelled: true} |
292 { success: boolean, components: Record<string, CustomBoolean<unknown>>, newReason?: string} |
293 { newReason: string, components: Record<string, CustomBoolean<unknown>> } |
294 { components: Record<string, CustomBoolean<unknown>> };
295
296 return typedReturnValue;
pineafan4f164f32022-02-26 22:07:12 +0000297 }
pineafan62ce1922022-08-25 20:34:45 +0100298
299 async timeoutError(): Promise<void> {
300 await this.interaction.editReply({
301 embeds: [
302 new EmojiEmbed()
303 .setTitle(this.title)
304 .setDescription("We closed this message because it was not used for a while.")
305 .setStatus("Danger")
PineaFan1dee28f2023-01-16 22:09:07 +0000306 .setEmoji("CONTROL.BLOCKCROSS")
pineafan62ce1922022-08-25 20:34:45 +0100307 ],
308 components: []
PineaFan1dee28f2023-01-16 22:09:07 +0000309 })
pineafan62ce1922022-08-25 20:34:45 +0100310 }
pineafan4f164f32022-02-26 22:07:12 +0000311}
312
pineafan73a7c4a2022-07-24 10:38:04 +0100313export default confirmationMessage;