blob: 6682be01c2bb5d38d8055b2ef697003f58743bcc [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;
33 timeoutText: string | 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 }
pineafan62ce1922022-08-25 20:34:45 +010048 setEmoji(emoji: string, redEmoji?: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010049 this.emoji = emoji;
pineafan62ce1922022-08-25 20:34:45 +010050 if (redEmoji) this.redEmoji = redEmoji; // TODO: go through all confirmation messages and change them to use this, and not do their own edits
Skyler Grey75ea9172022-08-06 10:22:23 +010051 return this;
52 }
pineafan62ce1922022-08-25 20:34:45 +010053 setDescription(description: string, timedOut?: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010054 this.description = description;
pineafan62ce1922022-08-25 20:34:45 +010055 if (timedOut) this.timeoutText = timedOut; // TODO also this
Skyler Grey75ea9172022-08-06 10:22:23 +010056 return this;
57 }
58 setColor(color: "Danger" | "Warning" | "Success") {
59 this.color = color;
60 return this;
61 }
62 setInverted(inverted: boolean) {
63 this.inverted = inverted;
64 return this;
65 }
66 addCustomBoolean(
67 customId: string,
68 title: string,
69 disabled: boolean,
PineaFan100df682023-01-02 13:26:08 +000070 callback: (() => Promise<unknown>) | null = async () => null,
Skyler Grey75ea9172022-08-06 10:22:23 +010071 callbackClicked: string | null,
PineaFana34d04b2023-01-03 22:05:42 +000072 callbackNotClicked: string | null,
Skyler Grey75ea9172022-08-06 10:22:23 +010073 emoji?: string,
74 initial?: boolean
75 ) {
76 this.customButtons[customId] = {
77 title: title,
78 disabled: disabled,
79 value: callbackClicked,
PineaFana34d04b2023-01-03 22:05:42 +000080 notValue: callbackNotClicked,
Skyler Grey75ea9172022-08-06 10:22:23 +010081 emoji: emoji,
82 active: initial ?? false,
PineaFan100df682023-01-02 13:26:08 +000083 onClick: callback ?? (async () => null),
Skyler Grey75ea9172022-08-06 10:22:23 +010084 response: null
85 };
86 return this;
pineafan6fb3e072022-05-20 19:27:23 +010087 }
pineafan73a7c4a2022-07-24 10:38:04 +010088 addReasonButton(reason: string) {
89 this.reason = reason;
90 return this;
91 }
Skyler Grey11236ba2022-08-08 21:13:33 +010092 async send(editOnly?: boolean): Promise<{
93 success?: boolean;
94 cancelled?: boolean;
95 components?: Record<string, CustomBoolean<unknown>>;
96 newReason?: string;
97 }> {
Skyler Greya402d1c2022-08-13 23:18:16 +010098 let cancelled = false;
99 let success: boolean | undefined = undefined;
100 let returnComponents = false;
101 let newReason = undefined;
102
103 while (!cancelled && success === undefined && !returnComponents && !newReason) {
pineafan63fc5e22022-08-04 22:04:10 +0100104 const fullComponents = [
TheCodedProf21c08592022-09-13 14:14:43 -0400105 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100106 .setCustomId("yes")
107 .setLabel("Confirm")
TheCodedProf21c08592022-09-13 14:14:43 -0400108 .setStyle(this.inverted ? ButtonStyle.Success : ButtonStyle.Danger)
pineafan02ba0232022-07-24 22:16:15 +0100109 .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
TheCodedProf21c08592022-09-13 14:14:43 -0400110 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100111 .setCustomId("no")
112 .setLabel("Cancel")
TheCodedProf21c08592022-09-13 14:14:43 -0400113 .setStyle(ButtonStyle.Secondary)
pineafan02ba0232022-07-24 22:16:15 +0100114 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100115 ];
pineafan02ba0232022-07-24 22:16:15 +0100116 Object.entries(this.customButtons).forEach(([k, v]) => {
TheCodedProf21c08592022-09-13 14:14:43 -0400117 const button = new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100118 .setCustomId(k)
119 .setLabel(v.title)
TheCodedProf21c08592022-09-13 14:14:43 -0400120 .setStyle(v.active ? ButtonStyle.Success : ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100121 .setDisabled(v.disabled);
Skyler Grey11236ba2022-08-08 21:13:33 +0100122 if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
Skyler Grey75ea9172022-08-06 10:22:23 +0100123 fullComponents.push(button);
pineafan63fc5e22022-08-04 22:04:10 +0100124 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100125 if (this.reason !== null)
126 fullComponents.push(
TheCodedProf21c08592022-09-13 14:14:43 -0400127 new Discord.ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100128 .setCustomId("reason")
129 .setLabel("Edit Reason")
TheCodedProf21c08592022-09-13 14:14:43 -0400130 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100131 .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
132 .setDisabled(false)
133 );
pineafan63fc5e22022-08-04 22:04:10 +0100134 const components = [];
pineafan02ba0232022-07-24 22:16:15 +0100135 for (let i = 0; i < fullComponents.length; i += 5) {
PineaFan100df682023-01-02 13:26:08 +0000136 components.push(new ActionRowBuilder<
137 Discord.ButtonBuilder | Discord.StringSelectMenuBuilder |
138 Discord.RoleSelectMenuBuilder | Discord.UserSelectMenuBuilder
139 >().addComponents(fullComponents.slice(i, i + 5)));
pineafan02ba0232022-07-24 22:16:15 +0100140 }
pineafan63fc5e22022-08-04 22:04:10 +0100141 const object = {
pineafan377794f2022-04-18 19:01:01 +0100142 embeds: [
pineafan4edb7762022-06-26 19:21:04 +0100143 new EmojiEmbed()
pineafan377794f2022-04-18 19:01:01 +0100144 .setEmoji(this.emoji)
145 .setTitle(this.title)
Skyler Grey75ea9172022-08-06 10:22:23 +0100146 .setDescription(
147 this.description +
148 "\n\n" +
149 Object.values(this.customButtons)
150 .map((v) => {
PineaFana34d04b2023-01-03 22:05:42 +0000151 if (v.active) {
152 return v.value ? `*${v.value}*\n` : "";
153 } else {
154 return v.notValue ? `*${v.notValue}*\n` : "";
155 }
156 }).join("")
Skyler Grey75ea9172022-08-06 10:22:23 +0100157 )
pineafan377794f2022-04-18 19:01:01 +0100158 .setStatus(this.color)
pineafan377794f2022-04-18 19:01:01 +0100159 ],
pineafan02ba0232022-07-24 22:16:15 +0100160 components: components,
pineafan377794f2022-04-18 19:01:01 +0100161 ephemeral: true,
162 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +0100163 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100164 let m: Message;
pineafan02ba0232022-07-24 22:16:15 +0100165 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100166 if (editOnly) {
PineaFan100df682023-01-02 13:26:08 +0000167 m = (await this.interaction.editReply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100168 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100169 m = (await this.interaction.reply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100170 }
PineaFana34d04b2023-01-03 22:05:42 +0000171 } catch (e) {
172 console.log(e);
Skyler Greya402d1c2022-08-13 23:18:16 +0100173 cancelled = true;
174 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100175 }
pineafan377794f2022-04-18 19:01:01 +0100176 let component;
177 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100178 component = await m.awaitMessageComponent({
179 filter: (m) => m.user.id === this.interaction.user.id,
180 time: 300000
181 });
pineafan377794f2022-04-18 19:01:01 +0100182 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100183 success = false;
184 returnComponents = true;
185 continue;
pineafan377794f2022-04-18 19:01:01 +0100186 }
187 if (component.customId === "yes") {
188 component.deferUpdate();
pineafan63fc5e22022-08-04 22:04:10 +0100189 for (const v of Object.values(this.customButtons)) {
190 if (!v.active) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100191 try {
192 v.response = await v.onClick();
193 } catch (e) {
194 console.log(e);
195 }
pineafan63fc5e22022-08-04 22:04:10 +0100196 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100197 success = true;
198 returnComponents = true;
199 continue;
pineafan377794f2022-04-18 19:01:01 +0100200 } else if (component.customId === "no") {
201 component.deferUpdate();
Skyler Greya402d1c2022-08-13 23:18:16 +0100202 success = false;
203 returnComponents = true;
204 continue;
pineafan73a7c4a2022-07-24 10:38:04 +0100205 } else if (component.customId === "reason") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100206 await component.showModal(
PineaFan100df682023-01-02 13:26:08 +0000207 new Discord.ModalBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100208 .setCustomId("modal")
209 .setTitle("Editing reason")
210 .addComponents(
PineaFan100df682023-01-02 13:26:08 +0000211 new ActionRowBuilder<TextInputBuilder>().addComponents(
212 new TextInputBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100213 .setCustomId("reason")
214 .setLabel("Reason")
215 .setMaxLength(2000)
216 .setRequired(false)
PineaFan100df682023-01-02 13:26:08 +0000217 .setStyle(TextInputStyle.Paragraph)
Skyler Grey75ea9172022-08-06 10:22:23 +0100218 .setPlaceholder("Spammed in #general")
219 .setValue(this.reason ? this.reason : "")
220 )
221 )
222 );
pineafan73a7c4a2022-07-24 10:38:04 +0100223 await this.interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100224 embeds: [
225 new EmojiEmbed()
226 .setTitle(this.title)
Skyler Grey11236ba2022-08-08 21:13:33 +0100227 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100228 .setStatus(this.color)
229 .setEmoji(this.emoji)
230 ],
231 components: [
PineaFan100df682023-01-02 13:26:08 +0000232 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400233 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100234 .setLabel("Back")
235 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400236 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100237 .setCustomId("back")
PineaFan100df682023-01-02 13:26:08 +0000238 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 ]
pineafan73a7c4a2022-07-24 10:38:04 +0100240 });
241 let out;
242 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100243 out = await modalInteractionCollector(
244 m,
Skyler Grey11236ba2022-08-08 21:13:33 +0100245 (m: Interaction) =>
246 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId ===
247 this.interaction.channelId,
Skyler Grey75ea9172022-08-06 10:22:23 +0100248 (m) => m.customId === "reason"
249 );
250 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100251 cancelled = true;
252 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100254 if (out === null) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100255 cancelled = true;
256 continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100257 }
258 if (out instanceof ModalSubmitInteraction) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100259 newReason = out.fields.getTextInputValue("reason");
260 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100261 } else {
Skyler Greya402d1c2022-08-13 23:18:16 +0100262 returnComponents = true;
263 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100264 }
pineafan02ba0232022-07-24 22:16:15 +0100265 } else {
266 component.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100267 this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
Skyler Greya402d1c2022-08-13 23:18:16 +0100268 returnComponents = true;
269 continue;
pineafan377794f2022-04-18 19:01:01 +0100270 }
pineafan8b4b17f2022-02-27 20:42:52 +0000271 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100272 const returnValue: Awaited<ReturnType<typeof this.send>> = {};
273
pineafan62ce1922022-08-25 20:34:45 +0100274 if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
Skyler Greya402d1c2022-08-13 23:18:16 +0100275 if (success !== undefined) returnValue.success = success;
pineafan62ce1922022-08-25 20:34:45 +0100276 if (cancelled) {
277 await this.timeoutError()
278 returnValue.cancelled = true;
279 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100280 if (newReason) returnValue.newReason = newReason;
281
PineaFan100df682023-01-02 13:26:08 +0000282 const typedReturnValue = returnValue as {cancelled: true} |
283 { success: boolean, components: Record<string, CustomBoolean<unknown>>, newReason?: string} |
284 { newReason: string, components: Record<string, CustomBoolean<unknown>> } |
285 { components: Record<string, CustomBoolean<unknown>> };
286
287 return typedReturnValue;
pineafan4f164f32022-02-26 22:07:12 +0000288 }
pineafan62ce1922022-08-25 20:34:45 +0100289
290 async timeoutError(): Promise<void> {
291 await this.interaction.editReply({
292 embeds: [
293 new EmojiEmbed()
294 .setTitle(this.title)
295 .setDescription("We closed this message because it was not used for a while.")
296 .setStatus("Danger")
297 .setEmoji(this.redEmoji ?? this.emoji)
298 ],
299 components: []
300 })
301 }
pineafan4f164f32022-02-26 22:07:12 +0000302}
303
pineafan73a7c4a2022-07-24 10:38:04 +0100304export default confirmationMessage;