blob: 87724f356470fe23e074fdbe1ed6776d74d032f9 [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;
pineafan63fc5e22022-08-04 22:04:10 +010021 emoji: string | undefined;
pineafan02ba0232022-07-24 22:16:15 +010022 active: boolean;
23 onClick: () => Promise<T>;
24 response: T | null;
25}
26
pineafan4f164f32022-02-26 22:07:12 +000027class confirmationMessage {
28 interaction: CommandInteraction;
pineafan63fc5e22022-08-04 22:04:10 +010029 title = "";
30 emoji = "";
pineafan62ce1922022-08-25 20:34:45 +010031 redEmoji: string | null = null;
32 timeoutText: string | null = null;
pineafan63fc5e22022-08-04 22:04:10 +010033 description = "";
Skyler Grey75ea9172022-08-06 10:22:23 +010034 color: "Danger" | "Warning" | "Success" = "Success";
35 customButtons: Record<string, CustomBoolean<unknown>> = {};
pineafan63fc5e22022-08-04 22:04:10 +010036 inverted = false;
pineafan73a7c4a2022-07-24 10:38:04 +010037 reason: string | null = null;
pineafan4f164f32022-02-26 22:07:12 +000038
39 constructor(interaction: CommandInteraction) {
40 this.interaction = interaction;
pineafan4f164f32022-02-26 22:07:12 +000041 }
42
Skyler Grey75ea9172022-08-06 10:22:23 +010043 setTitle(title: string) {
44 this.title = title;
45 return this;
46 }
pineafan62ce1922022-08-25 20:34:45 +010047 setEmoji(emoji: string, redEmoji?: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010048 this.emoji = emoji;
pineafan62ce1922022-08-25 20:34:45 +010049 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 +010050 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;
pineafan62ce1922022-08-25 20:34:45 +010054 if (timedOut) this.timeoutText = timedOut; // TODO also this
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 }
65 addCustomBoolean(
66 customId: string,
67 title: string,
68 disabled: boolean,
PineaFan100df682023-01-02 13:26:08 +000069 callback: (() => Promise<unknown>) | null = async () => null,
Skyler Grey75ea9172022-08-06 10:22:23 +010070 callbackClicked: string | null,
71 emoji?: string,
72 initial?: boolean
73 ) {
74 this.customButtons[customId] = {
75 title: title,
76 disabled: disabled,
77 value: callbackClicked,
78 emoji: emoji,
79 active: initial ?? false,
PineaFan100df682023-01-02 13:26:08 +000080 onClick: callback ?? (async () => null),
Skyler Grey75ea9172022-08-06 10:22:23 +010081 response: null
82 };
83 return this;
pineafan6fb3e072022-05-20 19:27:23 +010084 }
pineafan73a7c4a2022-07-24 10:38:04 +010085 addReasonButton(reason: string) {
86 this.reason = reason;
87 return this;
88 }
Skyler Grey11236ba2022-08-08 21:13:33 +010089 async send(editOnly?: boolean): Promise<{
90 success?: boolean;
91 cancelled?: boolean;
92 components?: Record<string, CustomBoolean<unknown>>;
93 newReason?: string;
94 }> {
Skyler Greya402d1c2022-08-13 23:18:16 +010095 let cancelled = false;
96 let success: boolean | undefined = undefined;
97 let returnComponents = false;
98 let newReason = undefined;
99
100 while (!cancelled && success === undefined && !returnComponents && !newReason) {
pineafan63fc5e22022-08-04 22:04:10 +0100101 const fullComponents = [
TheCodedProf21c08592022-09-13 14:14:43 -0400102 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100103 .setCustomId("yes")
104 .setLabel("Confirm")
TheCodedProf21c08592022-09-13 14:14:43 -0400105 .setStyle(this.inverted ? ButtonStyle.Success : ButtonStyle.Danger)
pineafan02ba0232022-07-24 22:16:15 +0100106 .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
TheCodedProf21c08592022-09-13 14:14:43 -0400107 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100108 .setCustomId("no")
109 .setLabel("Cancel")
TheCodedProf21c08592022-09-13 14:14:43 -0400110 .setStyle(ButtonStyle.Secondary)
pineafan02ba0232022-07-24 22:16:15 +0100111 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100112 ];
pineafan02ba0232022-07-24 22:16:15 +0100113 Object.entries(this.customButtons).forEach(([k, v]) => {
TheCodedProf21c08592022-09-13 14:14:43 -0400114 const button = new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100115 .setCustomId(k)
116 .setLabel(v.title)
TheCodedProf21c08592022-09-13 14:14:43 -0400117 .setStyle(v.active ? ButtonStyle.Success : ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100118 .setDisabled(v.disabled);
Skyler Grey11236ba2022-08-08 21:13:33 +0100119 if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
Skyler Grey75ea9172022-08-06 10:22:23 +0100120 fullComponents.push(button);
pineafan63fc5e22022-08-04 22:04:10 +0100121 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100122 if (this.reason !== null)
123 fullComponents.push(
TheCodedProf21c08592022-09-13 14:14:43 -0400124 new Discord.ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100125 .setCustomId("reason")
126 .setLabel("Edit Reason")
TheCodedProf21c08592022-09-13 14:14:43 -0400127 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100128 .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
129 .setDisabled(false)
130 );
pineafan63fc5e22022-08-04 22:04:10 +0100131 const components = [];
pineafan02ba0232022-07-24 22:16:15 +0100132 for (let i = 0; i < fullComponents.length; i += 5) {
PineaFan100df682023-01-02 13:26:08 +0000133 components.push(new ActionRowBuilder<
134 Discord.ButtonBuilder | Discord.StringSelectMenuBuilder |
135 Discord.RoleSelectMenuBuilder | Discord.UserSelectMenuBuilder
136 >().addComponents(fullComponents.slice(i, i + 5)));
pineafan02ba0232022-07-24 22:16:15 +0100137 }
pineafan63fc5e22022-08-04 22:04:10 +0100138 const object = {
pineafan377794f2022-04-18 19:01:01 +0100139 embeds: [
pineafan4edb7762022-06-26 19:21:04 +0100140 new EmojiEmbed()
pineafan377794f2022-04-18 19:01:01 +0100141 .setEmoji(this.emoji)
142 .setTitle(this.title)
Skyler Grey75ea9172022-08-06 10:22:23 +0100143 .setDescription(
144 this.description +
145 "\n\n" +
146 Object.values(this.customButtons)
147 .map((v) => {
148 if (v.value === null) return "";
149 return v.active ? `*${v.value}*\n` : "";
150 })
151 .join("")
152 )
pineafan377794f2022-04-18 19:01:01 +0100153 .setStatus(this.color)
pineafan377794f2022-04-18 19:01:01 +0100154 ],
pineafan02ba0232022-07-24 22:16:15 +0100155 components: components,
pineafan377794f2022-04-18 19:01:01 +0100156 ephemeral: true,
157 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +0100158 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100159 let m: Message;
pineafan02ba0232022-07-24 22:16:15 +0100160 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100161 if (editOnly) {
PineaFan100df682023-01-02 13:26:08 +0000162 m = (await this.interaction.editReply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100163 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100164 m = (await this.interaction.reply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100165 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100166 } catch {
Skyler Greya402d1c2022-08-13 23:18:16 +0100167 cancelled = true;
168 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100169 }
pineafan377794f2022-04-18 19:01:01 +0100170 let component;
171 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100172 component = await m.awaitMessageComponent({
173 filter: (m) => m.user.id === this.interaction.user.id,
174 time: 300000
175 });
pineafan377794f2022-04-18 19:01:01 +0100176 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100177 success = false;
178 returnComponents = true;
179 continue;
pineafan377794f2022-04-18 19:01:01 +0100180 }
181 if (component.customId === "yes") {
182 component.deferUpdate();
pineafan63fc5e22022-08-04 22:04:10 +0100183 for (const v of Object.values(this.customButtons)) {
184 if (!v.active) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100185 try {
186 v.response = await v.onClick();
187 } catch (e) {
188 console.log(e);
189 }
pineafan63fc5e22022-08-04 22:04:10 +0100190 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100191 success = true;
192 returnComponents = true;
193 continue;
pineafan377794f2022-04-18 19:01:01 +0100194 } else if (component.customId === "no") {
195 component.deferUpdate();
Skyler Greya402d1c2022-08-13 23:18:16 +0100196 success = false;
197 returnComponents = true;
198 continue;
pineafan73a7c4a2022-07-24 10:38:04 +0100199 } else if (component.customId === "reason") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100200 await component.showModal(
PineaFan100df682023-01-02 13:26:08 +0000201 new Discord.ModalBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100202 .setCustomId("modal")
203 .setTitle("Editing reason")
204 .addComponents(
PineaFan100df682023-01-02 13:26:08 +0000205 new ActionRowBuilder<TextInputBuilder>().addComponents(
206 new TextInputBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100207 .setCustomId("reason")
208 .setLabel("Reason")
209 .setMaxLength(2000)
210 .setRequired(false)
PineaFan100df682023-01-02 13:26:08 +0000211 .setStyle(TextInputStyle.Paragraph)
Skyler Grey75ea9172022-08-06 10:22:23 +0100212 .setPlaceholder("Spammed in #general")
213 .setValue(this.reason ? this.reason : "")
214 )
215 )
216 );
pineafan73a7c4a2022-07-24 10:38:04 +0100217 await this.interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100218 embeds: [
219 new EmojiEmbed()
220 .setTitle(this.title)
Skyler Grey11236ba2022-08-08 21:13:33 +0100221 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100222 .setStatus(this.color)
223 .setEmoji(this.emoji)
224 ],
225 components: [
PineaFan100df682023-01-02 13:26:08 +0000226 new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400227 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100228 .setLabel("Back")
229 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400230 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100231 .setCustomId("back")
PineaFan100df682023-01-02 13:26:08 +0000232 )
Skyler Grey75ea9172022-08-06 10:22:23 +0100233 ]
pineafan73a7c4a2022-07-24 10:38:04 +0100234 });
235 let out;
236 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100237 out = await modalInteractionCollector(
238 m,
Skyler Grey11236ba2022-08-08 21:13:33 +0100239 (m: Interaction) =>
240 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId ===
241 this.interaction.channelId,
Skyler Grey75ea9172022-08-06 10:22:23 +0100242 (m) => m.customId === "reason"
243 );
244 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100245 cancelled = true;
246 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100247 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100248 if (out === null) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100249 cancelled = true;
250 continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100251 }
252 if (out instanceof ModalSubmitInteraction) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100253 newReason = out.fields.getTextInputValue("reason");
254 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100255 } else {
Skyler Greya402d1c2022-08-13 23:18:16 +0100256 returnComponents = true;
257 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100258 }
pineafan02ba0232022-07-24 22:16:15 +0100259 } else {
260 component.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100261 this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
Skyler Greya402d1c2022-08-13 23:18:16 +0100262 returnComponents = true;
263 continue;
pineafan377794f2022-04-18 19:01:01 +0100264 }
pineafan8b4b17f2022-02-27 20:42:52 +0000265 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100266 const returnValue: Awaited<ReturnType<typeof this.send>> = {};
267
pineafan62ce1922022-08-25 20:34:45 +0100268 if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
Skyler Greya402d1c2022-08-13 23:18:16 +0100269 if (success !== undefined) returnValue.success = success;
pineafan62ce1922022-08-25 20:34:45 +0100270 if (cancelled) {
271 await this.timeoutError()
272 returnValue.cancelled = true;
273 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100274 if (newReason) returnValue.newReason = newReason;
275
PineaFan100df682023-01-02 13:26:08 +0000276 const typedReturnValue = returnValue as {cancelled: true} |
277 { success: boolean, components: Record<string, CustomBoolean<unknown>>, newReason?: string} |
278 { newReason: string, components: Record<string, CustomBoolean<unknown>> } |
279 { components: Record<string, CustomBoolean<unknown>> };
280
281 return typedReturnValue;
pineafan4f164f32022-02-26 22:07:12 +0000282 }
pineafan62ce1922022-08-25 20:34:45 +0100283
284 async timeoutError(): Promise<void> {
285 await this.interaction.editReply({
286 embeds: [
287 new EmojiEmbed()
288 .setTitle(this.title)
289 .setDescription("We closed this message because it was not used for a while.")
290 .setStatus("Danger")
291 .setEmoji(this.redEmoji ?? this.emoji)
292 ],
293 components: []
294 })
295 }
pineafan4f164f32022-02-26 22:07:12 +0000296}
297
pineafan73a7c4a2022-07-24 10:38:04 +0100298export default confirmationMessage;