blob: 18e6ea07456c32fab9841f86b2f1e4c698524bf1 [file] [log] [blame]
Skyler Grey75ea9172022-08-06 10:22:23 +01001import Discord, {
2 CommandInteraction,
Skyler Grey11236ba2022-08-08 21:13:33 +01003 Interaction,
Skyler Grey75ea9172022-08-06 10:22:23 +01004 Message,
TheCodedProf21c08592022-09-13 14:14:43 -04005 ActionRowBuilder,
6 ButtonBuilder,
Skyler Grey11236ba2022-08-08 21:13:33 +01007 MessageComponentInteraction,
8 ModalSubmitInteraction,
TheCodedProf21c08592022-09-13 14:14:43 -04009 TextInputComponent,
10 ButtonStyle
Skyler Grey75ea9172022-08-06 10:22:23 +010011} from "discord.js";
pineafan73a7c4a2022-07-24 10:38:04 +010012import { modalInteractionCollector } from "./dualCollector.js";
pineafan63fc5e22022-08-04 22:04:10 +010013import EmojiEmbed from "./generateEmojiEmbed.js";
pineafan8b4b17f2022-02-27 20:42:52 +000014import getEmojiByName from "./getEmojiByName.js";
pineafan4f164f32022-02-26 22:07:12 +000015
pineafan02ba0232022-07-24 22:16:15 +010016interface CustomBoolean<T> {
17 title: string;
18 disabled: boolean;
19 value: 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;
31 timeoutText: string | null = null;
pineafan63fc5e22022-08-04 22:04:10 +010032 description = "";
Skyler Grey75ea9172022-08-06 10:22:23 +010033 color: "Danger" | "Warning" | "Success" = "Success";
34 customButtons: Record<string, CustomBoolean<unknown>> = {};
pineafan63fc5e22022-08-04 22:04:10 +010035 inverted = false;
pineafan73a7c4a2022-07-24 10:38:04 +010036 reason: string | null = null;
pineafan4f164f32022-02-26 22:07:12 +000037
38 constructor(interaction: CommandInteraction) {
39 this.interaction = interaction;
pineafan4f164f32022-02-26 22:07:12 +000040 }
41
Skyler Grey75ea9172022-08-06 10:22:23 +010042 setTitle(title: string) {
43 this.title = title;
44 return this;
45 }
pineafan62ce1922022-08-25 20:34:45 +010046 setEmoji(emoji: string, redEmoji?: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010047 this.emoji = emoji;
pineafan62ce1922022-08-25 20:34:45 +010048 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 +010049 return this;
50 }
pineafan62ce1922022-08-25 20:34:45 +010051 setDescription(description: string, timedOut?: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +010052 this.description = description;
pineafan62ce1922022-08-25 20:34:45 +010053 if (timedOut) this.timeoutText = timedOut; // TODO also this
Skyler Grey75ea9172022-08-06 10:22:23 +010054 return this;
55 }
56 setColor(color: "Danger" | "Warning" | "Success") {
57 this.color = color;
58 return this;
59 }
60 setInverted(inverted: boolean) {
61 this.inverted = inverted;
62 return this;
63 }
64 addCustomBoolean(
65 customId: string,
66 title: string,
67 disabled: boolean,
68 callback: () => Promise<unknown> = async () => null,
69 callbackClicked: string | null,
70 emoji?: string,
71 initial?: boolean
72 ) {
73 this.customButtons[customId] = {
74 title: title,
75 disabled: disabled,
76 value: callbackClicked,
77 emoji: emoji,
78 active: initial ?? false,
79 onClick: callback,
80 response: null
81 };
82 return this;
pineafan6fb3e072022-05-20 19:27:23 +010083 }
pineafan73a7c4a2022-07-24 10:38:04 +010084 addReasonButton(reason: string) {
85 this.reason = reason;
86 return this;
87 }
Skyler Grey11236ba2022-08-08 21:13:33 +010088 async send(editOnly?: boolean): Promise<{
89 success?: boolean;
90 cancelled?: boolean;
91 components?: Record<string, CustomBoolean<unknown>>;
92 newReason?: string;
93 }> {
Skyler Greya402d1c2022-08-13 23:18:16 +010094 let cancelled = false;
95 let success: boolean | undefined = undefined;
96 let returnComponents = false;
97 let newReason = undefined;
98
99 while (!cancelled && success === undefined && !returnComponents && !newReason) {
pineafan63fc5e22022-08-04 22:04:10 +0100100 const fullComponents = [
TheCodedProf21c08592022-09-13 14:14:43 -0400101 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100102 .setCustomId("yes")
103 .setLabel("Confirm")
TheCodedProf21c08592022-09-13 14:14:43 -0400104 .setStyle(this.inverted ? ButtonStyle.Success : ButtonStyle.Danger)
pineafan02ba0232022-07-24 22:16:15 +0100105 .setEmoji(getEmojiByName("CONTROL.TICK", "id")),
TheCodedProf21c08592022-09-13 14:14:43 -0400106 new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100107 .setCustomId("no")
108 .setLabel("Cancel")
TheCodedProf21c08592022-09-13 14:14:43 -0400109 .setStyle(ButtonStyle.Secondary)
pineafan02ba0232022-07-24 22:16:15 +0100110 .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
pineafan63fc5e22022-08-04 22:04:10 +0100111 ];
pineafan02ba0232022-07-24 22:16:15 +0100112 Object.entries(this.customButtons).forEach(([k, v]) => {
TheCodedProf21c08592022-09-13 14:14:43 -0400113 const button = new Discord.ButtonBuilder()
pineafan02ba0232022-07-24 22:16:15 +0100114 .setCustomId(k)
115 .setLabel(v.title)
TheCodedProf21c08592022-09-13 14:14:43 -0400116 .setStyle(v.active ? ButtonStyle.Success : ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100117 .setDisabled(v.disabled);
Skyler Grey11236ba2022-08-08 21:13:33 +0100118 if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
Skyler Grey75ea9172022-08-06 10:22:23 +0100119 fullComponents.push(button);
pineafan63fc5e22022-08-04 22:04:10 +0100120 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100121 if (this.reason !== null)
122 fullComponents.push(
TheCodedProf21c08592022-09-13 14:14:43 -0400123 new Discord.ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100124 .setCustomId("reason")
125 .setLabel("Edit Reason")
TheCodedProf21c08592022-09-13 14:14:43 -0400126 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100127 .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
128 .setDisabled(false)
129 );
pineafan63fc5e22022-08-04 22:04:10 +0100130 const components = [];
pineafan02ba0232022-07-24 22:16:15 +0100131 for (let i = 0; i < fullComponents.length; i += 5) {
TheCodedProf21c08592022-09-13 14:14:43 -0400132 components.push(new ActionRowBuilder().addComponents(fullComponents.slice(i, i + 5)));
pineafan02ba0232022-07-24 22:16:15 +0100133 }
pineafan63fc5e22022-08-04 22:04:10 +0100134 const object = {
pineafan377794f2022-04-18 19:01:01 +0100135 embeds: [
pineafan4edb7762022-06-26 19:21:04 +0100136 new EmojiEmbed()
pineafan377794f2022-04-18 19:01:01 +0100137 .setEmoji(this.emoji)
138 .setTitle(this.title)
Skyler Grey75ea9172022-08-06 10:22:23 +0100139 .setDescription(
140 this.description +
141 "\n\n" +
142 Object.values(this.customButtons)
143 .map((v) => {
144 if (v.value === null) return "";
145 return v.active ? `*${v.value}*\n` : "";
146 })
147 .join("")
148 )
pineafan377794f2022-04-18 19:01:01 +0100149 .setStatus(this.color)
pineafan377794f2022-04-18 19:01:01 +0100150 ],
pineafan02ba0232022-07-24 22:16:15 +0100151 components: components,
pineafan377794f2022-04-18 19:01:01 +0100152 ephemeral: true,
153 fetchReply: true
pineafan63fc5e22022-08-04 22:04:10 +0100154 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100155 let m: Message;
pineafan02ba0232022-07-24 22:16:15 +0100156 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100157 if (editOnly) {
158 m = (await this.interaction.editReply(object)) as Message;
pineafan02ba0232022-07-24 22:16:15 +0100159 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100160 m = (await this.interaction.reply(object)) as unknown as Message;
pineafan02ba0232022-07-24 22:16:15 +0100161 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100162 } catch {
Skyler Greya402d1c2022-08-13 23:18:16 +0100163 cancelled = true;
164 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100165 }
pineafan377794f2022-04-18 19:01:01 +0100166 let component;
167 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100168 component = await m.awaitMessageComponent({
169 filter: (m) => m.user.id === this.interaction.user.id,
170 time: 300000
171 });
pineafan377794f2022-04-18 19:01:01 +0100172 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100173 success = false;
174 returnComponents = true;
175 continue;
pineafan377794f2022-04-18 19:01:01 +0100176 }
177 if (component.customId === "yes") {
178 component.deferUpdate();
pineafan63fc5e22022-08-04 22:04:10 +0100179 for (const v of Object.values(this.customButtons)) {
180 if (!v.active) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100181 try {
182 v.response = await v.onClick();
183 } catch (e) {
184 console.log(e);
185 }
pineafan63fc5e22022-08-04 22:04:10 +0100186 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100187 success = true;
188 returnComponents = true;
189 continue;
pineafan377794f2022-04-18 19:01:01 +0100190 } else if (component.customId === "no") {
191 component.deferUpdate();
Skyler Greya402d1c2022-08-13 23:18:16 +0100192 success = false;
193 returnComponents = true;
194 continue;
pineafan73a7c4a2022-07-24 10:38:04 +0100195 } else if (component.customId === "reason") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100196 await component.showModal(
197 new Discord.Modal()
198 .setCustomId("modal")
199 .setTitle("Editing reason")
200 .addComponents(
TheCodedProf21c08592022-09-13 14:14:43 -0400201 new ActionRowBuilder<TextInputComponent>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100202 new TextInputComponent()
203 .setCustomId("reason")
204 .setLabel("Reason")
205 .setMaxLength(2000)
206 .setRequired(false)
207 .setStyle("PARAGRAPH")
208 .setPlaceholder("Spammed in #general")
209 .setValue(this.reason ? this.reason : "")
210 )
211 )
212 );
pineafan73a7c4a2022-07-24 10:38:04 +0100213 await this.interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100214 embeds: [
215 new EmojiEmbed()
216 .setTitle(this.title)
Skyler Grey11236ba2022-08-08 21:13:33 +0100217 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100218 .setStatus(this.color)
219 .setEmoji(this.emoji)
220 ],
221 components: [
TheCodedProf21c08592022-09-13 14:14:43 -0400222 new ActionRowBuilder().addComponents([
223 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100224 .setLabel("Back")
225 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
TheCodedProf21c08592022-09-13 14:14:43 -0400226 .setStyle(ButtonStyle.Primary)
Skyler Grey75ea9172022-08-06 10:22:23 +0100227 .setCustomId("back")
228 ])
229 ]
pineafan73a7c4a2022-07-24 10:38:04 +0100230 });
231 let out;
232 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100233 out = await modalInteractionCollector(
234 m,
Skyler Grey11236ba2022-08-08 21:13:33 +0100235 (m: Interaction) =>
236 (m as MessageComponentInteraction | ModalSubmitInteraction).channelId ===
237 this.interaction.channelId,
Skyler Grey75ea9172022-08-06 10:22:23 +0100238 (m) => m.customId === "reason"
239 );
240 } catch (e) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100241 cancelled = true;
242 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100243 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100244 if (out === null) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100245 cancelled = true;
246 continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100247 }
248 if (out instanceof ModalSubmitInteraction) {
Skyler Greya402d1c2022-08-13 23:18:16 +0100249 newReason = out.fields.getTextInputValue("reason");
250 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100251 } else {
Skyler Greya402d1c2022-08-13 23:18:16 +0100252 returnComponents = true;
253 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100254 }
pineafan02ba0232022-07-24 22:16:15 +0100255 } else {
256 component.deferUpdate();
Skyler Grey11236ba2022-08-08 21:13:33 +0100257 this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
Skyler Greya402d1c2022-08-13 23:18:16 +0100258 returnComponents = true;
259 continue;
pineafan377794f2022-04-18 19:01:01 +0100260 }
pineafan8b4b17f2022-02-27 20:42:52 +0000261 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100262 const returnValue: Awaited<ReturnType<typeof this.send>> = {};
263
pineafan62ce1922022-08-25 20:34:45 +0100264 if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
Skyler Greya402d1c2022-08-13 23:18:16 +0100265 if (success !== undefined) returnValue.success = success;
pineafan62ce1922022-08-25 20:34:45 +0100266 if (cancelled) {
267 await this.timeoutError()
268 returnValue.cancelled = true;
269 }
Skyler Greya402d1c2022-08-13 23:18:16 +0100270 if (newReason) returnValue.newReason = newReason;
271
272 return returnValue;
pineafan4f164f32022-02-26 22:07:12 +0000273 }
pineafan62ce1922022-08-25 20:34:45 +0100274
275 async timeoutError(): Promise<void> {
276 await this.interaction.editReply({
277 embeds: [
278 new EmojiEmbed()
279 .setTitle(this.title)
280 .setDescription("We closed this message because it was not used for a while.")
281 .setStatus("Danger")
282 .setEmoji(this.redEmoji ?? this.emoji)
283 ],
284 components: []
285 })
286 }
pineafan4f164f32022-02-26 22:07:12 +0000287}
288
pineafan73a7c4a2022-07-24 10:38:04 +0100289export default confirmationMessage;