blob: 613b48cd1c6c5df1e4fb7d82047e7c10afa9b8cd [file] [log] [blame]
PineaFan100df682023-01-02 13:26:08 +00001import { TextInputBuilder } from "@discordjs/builders";
Skyler Grey75ea9172022-08-06 10:22:23 +01002import Discord, {
3 CommandInteraction,
Skyler Grey11236ba2022-08-08 21:13:33 +01004 Interaction,
Skyler Grey75ea9172022-08-06 10:22:23 +01005 Message,
TheCodedProf21c08592022-09-13 14:14:43 -04006 ActionRowBuilder,
7 ButtonBuilder,
Skyler Grey11236ba2022-08-08 21:13:33 +01008 MessageComponentInteraction,
9 ModalSubmitInteraction,
PineaFan100df682023-01-02 13:26:08 +000010 ButtonStyle,
11 TextInputStyle
Skyler Grey75ea9172022-08-06 10:22:23 +010012} from "discord.js";
pineafan73a7c4a2022-07-24 10:38:04 +010013import { modalInteractionCollector } from "./dualCollector.js";
pineafan63fc5e22022-08-04 22:04:10 +010014import EmojiEmbed from "./generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +000015import getEmojiByName from "./getEmojiByName.js";
pineafan4f164f32022-02-26 22:07:12 +000016
pineafan02ba0232022-07-24 22:16:15 +010017interface CustomBoolean<T> {
18 title: string;
19 disabled: boolean;
20 value: string | null;
PineaFana34d04b2023-01-03 22:05:42 +000021 notValue: string | null;
pineafan63fc5e22022-08-04 22:04:10 +010022 emoji: string | undefined;
pineafan02ba0232022-07-24 22:16:15 +010023 active: boolean;
24 onClick: () => Promise<T>;
25 response: T | null;
26}
27
pineafan4f164f32022-02-26 22:07:12 +000028class confirmationMessage {
29 interaction: CommandInteraction;
pineafan63fc5e22022-08-04 22:04:10 +010030 title = "";
31 emoji = "";
pineafan62ce1922022-08-25 20:34:45 +010032 redEmoji: string | null = null;
PineaFan1dee28f2023-01-16 22:09:07 +000033 failedMessage: string | null = null;
34 failedEmoji: string | null = null;
35 failedStatus: "Success" | "Danger" | "Warning" | null = null;
pineafan63fc5e22022-08-04 22:04:10 +010036 description = "";
Skyler Grey75ea9172022-08-06 10:22:23 +010037 color: "Danger" | "Warning" | "Success" = "Success";
38 customButtons: Record<string, CustomBoolean<unknown>> = {};
pineafan63fc5e22022-08-04 22:04:10 +010039 inverted = false;
pineafan73a7c4a2022-07-24 10:38:04 +010040 reason: string | null = null;
pineafan4f164f32022-02-26 22:07:12 +000041
42 constructor(interaction: CommandInteraction) {
43 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 }
PineaFan1dee28f2023-01-16 22:09:07 +000067 setFailedMessage(text: string, failedStatus: "Success" | "Danger" | "Warning" | null, failedEmoji: string | null = null) {
68 this.failedMessage = text;
69 this.failedStatus = failedStatus;
70 this.failedEmoji = failedEmoji;
71 return this;
72 }
Skyler Grey75ea9172022-08-06 10:22:23 +010073 addCustomBoolean(
74 customId: string,
75 title: string,
76 disabled: boolean,
PineaFan100df682023-01-02 13:26:08 +000077 callback: (() => Promise<unknown>) | null = async () => null,
Skyler Grey75ea9172022-08-06 10:22:23 +010078 callbackClicked: string | null,
PineaFana34d04b2023-01-03 22:05:42 +000079 callbackNotClicked: string | null,
Skyler Grey75ea9172022-08-06 10:22:23 +010080 emoji?: string,
81 initial?: boolean
82 ) {
83 this.customButtons[customId] = {
84 title: title,
85 disabled: disabled,
86 value: callbackClicked,
PineaFana34d04b2023-01-03 22:05:42 +000087 notValue: callbackNotClicked,
Skyler Grey75ea9172022-08-06 10:22:23 +010088 emoji: emoji,
89 active: initial ?? false,
PineaFan100df682023-01-02 13:26:08 +000090 onClick: callback ?? (async () => null),
Skyler Grey75ea9172022-08-06 10:22:23 +010091 response: null
92 };
93 return this;
pineafan6fb3e072022-05-20 19:27:23 +010094 }
pineafan73a7c4a2022-07-24 10:38:04 +010095 addReasonButton(reason: string) {
96 this.reason = reason;
97 return this;
98 }
Skyler Grey11236ba2022-08-08 21:13:33 +010099 async send(editOnly?: boolean): Promise<{
100 success?: boolean;
101 cancelled?: boolean;
102 components?: Record<string, CustomBoolean<unknown>>;
103 newReason?: string;
104 }> {
Skyler Greya402d1c2022-08-13 23:18:16 +0100105 let cancelled = false;
106 let success: boolean | undefined = undefined;
107 let returnComponents = false;
108 let newReason = undefined;
109
110 while (!cancelled && success === undefined && !returnComponents && !newReason) {
pineafan63fc5e22022-08-04 22:04:10 +0100111 const fullComponents = [
TheCodedProf21c08592022-09-13 14:14:43 -0400112 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100113 .setCustomId("yes")
114 .setLabel("Confirm")
TheCodedProf21c08592022-09-13 14:14:43 -0400115 .setStyle(this.inverted ? ButtonStyle.Success : ButtonStyle.Danger)
pineafan02ba0232022-07-24 22:16:15 +0100116 .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
TheCodedProf21c08592022-09-13 14:14:43 -0400117 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100118 .setCustomId("no")
119 .setLabel("Cancel")
TheCodedProf21c08592022-09-13 14:14:43 -0400120 .setStyle(ButtonStyle.Secondary)
pineafan02ba0232022-07-24 22:16:15 +0100121 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100122 ];
pineafan02ba0232022-07-24 22:16:15 +0100123 Object.entries(this.customButtons).forEach(([k, v]) => {
TheCodedProf21c08592022-09-13 14:14:43 -0400124 const button = new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100125 .setCustomId(k)
126 .setLabel(v.title)
TheCodedProf21c08592022-09-13 14:14:43 -0400127 .setStyle(v.active ? ButtonStyle.Success : ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100128 .setDisabled(v.disabled);
Skyler Grey11236ba2022-08-08 21:13:33 +0100129 if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
Skyler Grey75ea9172022-08-06 10:22:23 +0100130 fullComponents.push(button);
pineafan63fc5e22022-08-04 22:04:10 +0100131 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100132 if (this.reason !== null)
133 fullComponents.push(
TheCodedProf21c08592022-09-13 14:14:43 -0400134 new Discord.ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100135 .setCustomId("reason")
136 .setLabel("Edit Reason")
TheCodedProf21c08592022-09-13 14:14:43 -0400137 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100138 .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
139 .setDisabled(false)
140 );
pineafan63fc5e22022-08-04 22:04:10 +0100141 const components = [];
pineafan02ba0232022-07-24 22:16:15 +0100142 for (let i = 0; i < fullComponents.length; i += 5) {
PineaFan100df682023-01-02 13:26:08 +0000143 components.push(new ActionRowBuilder<
144 Discord.ButtonBuilder | Discord.StringSelectMenuBuilder |
145 Discord.RoleSelectMenuBuilder | Discord.UserSelectMenuBuilder
146 >().addComponents(fullComponents.slice(i, i + 5)));
pineafan02ba0232022-07-24 22:16:15 +0100147 }
pineafan63fc5e22022-08-04 22:04:10 +0100148 const object = {
pineafan377794f2022-04-18 19:01:01 +0100149 embeds: [
pineafan4edb7762022-06-26 19:21:04 +0100150 new EmojiEmbed()
pineafan377794f2022-04-18 19:01:01 +0100151 .setEmoji(this.emoji)
152 .setTitle(this.title)
Skyler Grey75ea9172022-08-06 10:22:23 +0100153 .setDescription(
154 this.description +
155 "\n\n" +
156 Object.values(this.customButtons)
157 .map((v) => {
PineaFana34d04b2023-01-03 22:05:42 +0000158 if (v.active) {
159 return v.value ? `*${v.value}*\n` : "";
160 } else {
161 return v.notValue ? `*${v.notValue}*\n` : "";
162 }
163 }).join("")
Skyler Grey75ea9172022-08-06 10:22:23 +0100164 )
pineafan377794f2022-04-18 19:01:01 +0100165 .setStatus(this.color)
pineafan377794f2022-04-18 19:01:01 +0100166 ],
pineafan02ba0232022-07-24 22:16:15 +0100167 components: components,
pineafan377794f2022-04-18 19:01:01 +0100168 ephemeral: true,
169 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +0100170 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100171 let m: Message;
pineafan02ba0232022-07-24 22:16:15 +0100172 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100173 if (editOnly) {
PineaFan100df682023-01-02 13:26:08 +0000174 m = (await this.interaction.editReply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100175 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100176 m = (await this.interaction.reply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100177 }
PineaFana34d04b2023-01-03 22:05:42 +0000178 } catch (e) {
179 console.log(e);
Skyler Greya402d1c2022-08-13 23:18:16 +0100180 cancelled = true;
181 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100182 }
pineafan377794f2022-04-18 19:01:01 +0100183 let component;
184 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100185 component = await m.awaitMessageComponent({
PineaFan0d06edc2023-01-17 22:10:31 +0000186 filter: (m) => m.user.id === this.interaction.user.id && m.channel!.id === this.interaction.channel!.id,
Skyler Grey75ea9172022-08-06 10:22:23 +0100187 time: 300000
188 });
pineafan377794f2022-04-18 19:01:01 +0100189 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100190 success = false;
191 returnComponents = true;
192 continue;
pineafan377794f2022-04-18 19:01:01 +0100193 }
194 if (component.customId === "yes") {
195 component.deferUpdate();
pineafan63fc5e22022-08-04 22:04:10 +0100196 for (const v of Object.values(this.customButtons)) {
197 if (!v.active) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100198 try {
199 v.response = await v.onClick();
200 } catch (e) {
201 console.log(e);
202 }
pineafan63fc5e22022-08-04 22:04:10 +0100203 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100204 success = true;
205 returnComponents = true;
206 continue;
pineafan377794f2022-04-18 19:01:01 +0100207 } else if (component.customId === "no") {
208 component.deferUpdate();
Skyler Greya402d1c2022-08-13 23:18:16 +0100209 success = false;
210 returnComponents = true;
211 continue;
pineafan73a7c4a2022-07-24 10:38:04 +0100212 } else if (component.customId === "reason") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100213 await component.showModal(
PineaFan100df682023-01-02 13:26:08 +0000214 new Discord.ModalBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100215 .setCustomId("modal")
216 .setTitle("Editing reason")
217 .addComponents(
PineaFan100df682023-01-02 13:26:08 +0000218 new ActionRowBuilder<TextInputBuilder>().addComponents(
219 new TextInputBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100220 .setCustomId("reason")
221 .setLabel("Reason")
222 .setMaxLength(2000)
223 .setRequired(false)
PineaFan100df682023-01-02 13:26:08 +0000224 .setStyle(TextInputStyle.Paragraph)
Skyler Grey75ea9172022-08-06 10:22:23 +0100225 .setPlaceholder("Spammed in #general")
226 .setValue(this.reason ? this.reason : "")
227 )
228 )
229 );
pineafan73a7c4a2022-07-24 10:38:04 +0100230 await this.interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100231 embeds: [
232 new EmojiEmbed()
233 .setTitle(this.title)
Skyler Grey11236ba2022-08-08 21:13:33 +0100234 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100235 .setStatus(this.color)
236 .setEmoji(this.emoji)
237 ],
238 components: [
PineaFan100df682023-01-02 13:26:08 +0000239 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400240 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100241 .setLabel("Back")
242 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400243 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100244 .setCustomId("back")
PineaFan100df682023-01-02 13:26:08 +0000245 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100246 ]
pineafan73a7c4a2022-07-24 10:38:04 +0100247 });
248 let out;
249 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100250 out = await modalInteractionCollector(
251 m,
Skyler Grey11236ba2022-08-08 21:13:33 +0100252 (m: Interaction) =>
PineaFan1dee28f2023-01-16 22:09:07 +0000253 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === this.interaction.channelId,
Skyler Grey75ea9172022-08-06 10:22:23 +0100254 (m) => m.customId === "reason"
255 );
256 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100257 cancelled = true;
258 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100259 }
TheCodedProf4a6d5712023-01-19 15:54:40 -0500260 if (out === null || out.isButton()) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100261 cancelled = true;
262 continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100263 }
264 if (out instanceof ModalSubmitInteraction) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100265 newReason = out.fields.getTextInputValue("reason");
266 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100267 } else {
Skyler Greya402d1c2022-08-13 23:18:16 +0100268 returnComponents = true;
269 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100270 }
pineafan02ba0232022-07-24 22:16:15 +0100271 } else {
272 component.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100273 this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
Skyler Greya402d1c2022-08-13 23:18:16 +0100274 returnComponents = true;
275 continue;
pineafan377794f2022-04-18 19:01:01 +0100276 }
pineafan8b4b17f2022-02-27 20:42:52 +0000277 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100278 const returnValue: Awaited<ReturnType<typeof this.send>> = {};
279
pineafan62ce1922022-08-25 20:34:45 +0100280 if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
Skyler Greya402d1c2022-08-13 23:18:16 +0100281 if (success !== undefined) returnValue.success = success;
pineafan62ce1922022-08-25 20:34:45 +0100282 if (cancelled) {
283 await this.timeoutError()
284 returnValue.cancelled = true;
285 }
PineaFan1dee28f2023-01-16 22:09:07 +0000286 if (success == false) {
287 await this.interaction.editReply({
288 embeds: [new EmojiEmbed()
289 .setTitle(this.title)
290 .setDescription(this.failedMessage ?? "")
291 .setStatus(this.failedStatus ?? "Danger")
292 .setEmoji(this.failedEmoji ?? this.redEmoji ?? this.emoji)
293 ], components: []
294 });
295 return {success: false}
296 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100297 if (newReason) returnValue.newReason = newReason;
298
PineaFan100df682023-01-02 13:26:08 +0000299 const typedReturnValue = returnValue as {cancelled: true} |
300 { success: boolean, components: Record<string, CustomBoolean<unknown>>, newReason?: string} |
301 { newReason: string, components: Record<string, CustomBoolean<unknown>> } |
302 { components: Record<string, CustomBoolean<unknown>> };
303
304 return typedReturnValue;
pineafan4f164f32022-02-26 22:07:12 +0000305 }
pineafan62ce1922022-08-25 20:34:45 +0100306
307 async timeoutError(): Promise<void> {
308 await this.interaction.editReply({
309 embeds: [
310 new EmojiEmbed()
311 .setTitle(this.title)
312 .setDescription("We closed this message because it was not used for a while.")
313 .setStatus("Danger")
PineaFan1dee28f2023-01-16 22:09:07 +0000314 .setEmoji("CONTROL.BLOCKCROSS")
pineafan62ce1922022-08-25 20:34:45 +0100315 ],
316 components: []
PineaFan1dee28f2023-01-16 22:09:07 +0000317 })
pineafan62ce1922022-08-25 20:34:45 +0100318 }
pineafan4f164f32022-02-26 22:07:12 +0000319}
320
pineafan73a7c4a2022-07-24 10:38:04 +0100321export default confirmationMessage;