blob: b2d484c10638341199f91a2efc9b26d2d20f7bb6 [file] [log] [blame]
PineaFan0d06edc2023-01-17 22:10:31 +00001import { LoadingEmbed } from "../../utils/defaults.js";
Skyler Grey75ea9172022-08-06 10:22:23 +01002import Discord, {
Skyler Grey75ea9172022-08-06 10:22:23 +01003 CommandInteraction,
TheCodedProf01cba762023-02-18 15:55:05 -05004 AutocompleteInteraction,
TheCodedProf21c08592022-09-13 14:14:43 -04005 ActionRowBuilder,
6 ButtonBuilder,
PineaFan538d3752023-01-12 21:48:23 +00007 ButtonStyle,
TheCodedProf01cba762023-02-18 15:55:05 -05008 APIMessageComponentEmoji,
9 ChannelSelectMenuBuilder,
10 RoleSelectMenuBuilder,
11 RoleSelectMenuInteraction,
12 ChannelSelectMenuInteraction,
13 ButtonInteraction,
14 ModalBuilder,
15 TextInputBuilder,
16 TextInputStyle,
17 ModalSubmitInteraction,
Skyler Grey75ea9172022-08-06 10:22:23 +010018} from "discord.js";
TheCodedProff86ba092023-01-27 17:10:07 -050019import type { SlashCommandSubcommandBuilder } from "discord.js";
pineafan41d93562022-07-30 22:10:15 +010020import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafan63fc5e22022-08-04 22:04:10 +010021import client from "../../utils/client.js";
pineafan63fc5e22022-08-04 22:04:10 +010022import getEmojiByName from "../../utils/getEmojiByName.js";
TheCodedProf01cba762023-02-18 15:55:05 -050023import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js";
24import { modalInteractionCollector } from "../../utils/dualCollector.js";
pineafan41d93562022-07-30 22:10:15 +010025
26const command = (builder: SlashCommandSubcommandBuilder) =>
27 builder
pineafan63fc5e22022-08-04 22:04:10 +010028 .setName("welcome")
Skyler Grey11236ba2022-08-08 21:13:33 +010029 .setDescription("Messages and roles sent or given when someone joins the server")
pineafan41d93562022-07-30 22:10:15 +010030
TheCodedProf01cba762023-02-18 15:55:05 -050031const callback = async (interaction: CommandInteraction): Promise<void> => {
32 const { renderChannel } = client.logger;
33 const m = await interaction.reply({
Skyler Grey75ea9172022-08-06 10:22:23 +010034 embeds: LoadingEmbed,
35 fetchReply: true,
36 ephemeral: true
37 });
TheCodedProf01cba762023-02-18 15:55:05 -050038 let closed = false;
39 let config = await client.database.guilds.read(interaction.guild!.id);
40 let data = Object.assign({}, config.welcome);
Skyler Greyad002172022-08-16 18:48:26 +010041 do {
TheCodedProf01cba762023-02-18 15:55:05 -050042 const buttons = new ActionRowBuilder<ButtonBuilder>()
43 .addComponents(
44 new ButtonBuilder()
45 .setCustomId("switch")
46 .setLabel(data.enabled ? "Enabled" : "Disabled")
47 .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
48 .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji),
49 new ButtonBuilder()
50 .setCustomId("message")
51 .setLabel((data.message ? "Change" : "Set") + "Message")
52 .setStyle(ButtonStyle.Primary)
53 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
54 new ButtonBuilder()
55 .setCustomId("channelDM")
56 .setLabel("Send in DMs")
57 .setStyle(ButtonStyle.Primary)
58 .setDisabled(data.channel === "dm"),
59 new ButtonBuilder()
60 .setCustomId("role")
61 .setLabel("Clear Role")
62 .setStyle(ButtonStyle.Danger)
63 .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji),
64 new ButtonBuilder()
65 .setCustomId("save")
66 .setLabel("Save")
67 .setStyle(ButtonStyle.Success)
68 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
69 .setDisabled(
70 data.enabled === config.welcome.enabled &&
71 data.message === config.welcome.message &&
72 data.role === config.welcome.role &&
73 data.ping === config.welcome.ping &&
74 data.channel === config.welcome.channel
Skyler Grey75ea9172022-08-06 10:22:23 +010075 )
TheCodedProf01cba762023-02-18 15:55:05 -050076 );
77
78 const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
79 .addComponents(
80 new ChannelSelectMenuBuilder()
81 .setCustomId("channel")
82 .setPlaceholder("Select a channel to send welcome messages to")
83 );
84 const roleMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
85 .addComponents(
86 new RoleSelectMenuBuilder()
87 .setCustomId("roleToGive")
88 .setPlaceholder("Select a role to give to the member when they join the server")
89 );
90 const pingMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
91 .addComponents(
92 new RoleSelectMenuBuilder()
93 .setCustomId("roleToPing")
94 .setPlaceholder("Select a role to ping when a member joins the server")
95 );
96
97 const embed = new EmojiEmbed()
98 .setTitle("Welcome Settings")
99 .setStatus("Success")
100 .setDescription(
101 `${getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Welcome messages and roles are ${data.enabled ? "enabled" : "disabled"}\n` +
102 `**Welcome message:** ${data.message ?
103 `\n> ` +
104 await convertCurlyBracketString(
105 data.message,
106 interaction.user.id,
107 interaction.user.username,
108 interaction.guild!.name,
109 interaction.guild!.members
110 )
111 : "*None*"}\n` +
112 `**Send message in:** ` + (data.channel ? (data.channel == "dm" ? "DMs" : renderChannel(data.channel)) : `*None set*`) + `\n` +
113 `**Role to ping:** ` + (data.ping ? `<@&${data.ping}>` : `*None set*`) + `\n` +
114 `**Role given on join:** ` + (data.role ? `<@&${data.role}>` : `*None set*`)
115 )
116
117 await interaction.editReply({
118 embeds: [embed],
119 components: [buttons, channelMenu, roleMenu, pingMenu]
120 });
121
122 let i: RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction;
pineafan41d93562022-07-30 22:10:15 +0100123 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000124 i = await m.awaitMessageComponent({
TheCodedProf01cba762023-02-18 15:55:05 -0500125 filter: (interaction) => interaction.user.id === interaction.user.id,
126 time: 300000
127 }) as RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction;
pineafan41d93562022-07-30 22:10:15 +0100128 } catch (e) {
TheCodedProf01cba762023-02-18 15:55:05 -0500129 closed = true;
Skyler Greyad002172022-08-16 18:48:26 +0100130 continue;
pineafan41d93562022-07-30 22:10:15 +0100131 }
TheCodedProf01cba762023-02-18 15:55:05 -0500132
133 if(i.isButton()) {
134 switch(i.customId) {
135 case "switch": {
136 await i.deferUpdate();
137 data.enabled = !data.enabled;
138 break;
139 }
140 case "message": {
141 const modal = new ModalBuilder()
142 .setCustomId("modal")
143 .setTitle("Welcome Message")
144 .addComponents(
145 new ActionRowBuilder<TextInputBuilder>().addComponents(
146 new TextInputBuilder()
147 .setCustomId("ex1")
148 .setLabel("Server Info (1/3)")
149 .setPlaceholder(
150 `{serverName} - This server's name\n\n` +
151 `These placeholders will be replaced with the server's name, etc..`
152 )
153 .setMaxLength(1)
154 .setRequired(false)
155 .setStyle(TextInputStyle.Paragraph)
156 ),
157 new ActionRowBuilder<TextInputBuilder>().addComponents(
158 new TextInputBuilder()
159 .setCustomId("ex2")
160 .setLabel("Member Counts (2/3) - {MemberCount:...}")
161 .setPlaceholder(
162 `{:all} - Total member count\n` +
163 `{:humans} - Total non-bot users\n` +
164 `{:bots} - Number of bots\n`
165 )
166 .setMaxLength(1)
167 .setRequired(false)
168 .setStyle(TextInputStyle.Paragraph)
169 ),
170 new ActionRowBuilder<TextInputBuilder>().addComponents(
171 new TextInputBuilder()
172 .setCustomId("ex3")
173 .setLabel("Member who joined (3/3) - {member:...}")
174 .setPlaceholder(
175 `{:name} - The members name\n`
176 )
177 .setMaxLength(1)
178 .setRequired(false)
179 .setStyle(TextInputStyle.Paragraph)
180 ),
181 new ActionRowBuilder<TextInputBuilder>()
182 .addComponents(
183 new TextInputBuilder()
184 .setCustomId("message")
185 .setPlaceholder("Enter a message to send when someone joins the server")
186 .setValue(data.message ?? "")
187 .setLabel("Message")
188 .setStyle(TextInputStyle.Paragraph)
189 )
190 )
191 const button = new ActionRowBuilder<ButtonBuilder>()
192 .addComponents(
193 new ButtonBuilder()
194 .setCustomId("back")
195 .setLabel("Back")
196 .setStyle(ButtonStyle.Secondary)
197 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
198 )
199 await i.showModal(modal)
200 await i.editReply({
201 embeds: [
202 new EmojiEmbed()
203 .setTitle("Welcome Settings")
204 .setDescription("Modal opened. If you can't see it, click back and try again.")
205 .setStatus("Success")
206 ],
207 components: [button]
208 });
209
210 let out: ModalSubmitInteraction | null;
211 try {
212 out = await modalInteractionCollector(m, interaction.user) as ModalSubmitInteraction | null;
213 } catch (e) {
214 console.error(e);
215 out = null;
216 }
217 if(!out) break;
TheCodedProf1807fb32023-02-20 14:33:48 -0500218 data.message = out.fields.getTextInputValue("message");
TheCodedProf01cba762023-02-18 15:55:05 -0500219 break;
220 }
221 case "save": {
222 await i.deferUpdate();
223 await client.database.guilds.write(interaction.guild!.id, {"welcome": data});
224 config = await client.database.guilds.read(interaction.guild!.id);
225 data = Object.assign({}, config.welcome);
Skyler Grey16ecb172023-03-05 07:30:32 +0000226 await client.memory.forceUpdate(interaction.guild!.id)
TheCodedProf01cba762023-02-18 15:55:05 -0500227 break;
228 }
229 case "channelDM": {
230 await i.deferUpdate();
231 data.channel = "dm";
232 break;
233 }
234 case "role": {
235 await i.deferUpdate();
236 data.role = null;
237 break;
238 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100239 }
TheCodedProf01cba762023-02-18 15:55:05 -0500240 } else if (i.isRoleSelectMenu()) {
241 await i.deferUpdate();
242 switch(i.customId) {
243 case "roleToGive": {
244 data.role = i.values[0]!;
245 break
246 }
247 case "roleToPing": {
248 data.ping = i.values[0]!;
249 break
250 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100251 }
TheCodedProf01cba762023-02-18 15:55:05 -0500252 } else {
253 await i.deferUpdate();
254 data.channel = i.values[0]!;
pineafan41d93562022-07-30 22:10:15 +0100255 }
TheCodedProf01cba762023-02-18 15:55:05 -0500256
257 } while (!closed);
258 await interaction.deleteReply()
pineafan63fc5e22022-08-04 22:04:10 +0100259};
pineafan41d93562022-07-30 22:10:15 +0100260
TheCodedProff86ba092023-01-27 17:10:07 -0500261const check = (interaction: CommandInteraction, _partial: boolean = false) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100262 const member = interaction.member as Discord.GuildMember;
PineaFan538d3752023-01-12 21:48:23 +0000263 if (!member.permissions.has("ManageGuild"))
PineaFan0d06edc2023-01-17 22:10:31 +0000264 return "You must have the *Manage Server* permission to use this command";
pineafan41d93562022-07-30 22:10:15 +0100265 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100266};
pineafan41d93562022-07-30 22:10:15 +0100267
PineaFan538d3752023-01-12 21:48:23 +0000268const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
TheCodedProf4a6d5712023-01-19 15:54:40 -0500269 const validReplacements = ["serverName", "memberCount:all", "memberCount:bots", "memberCount:humans"]
PineaFan538d3752023-01-12 21:48:23 +0000270 if (!interaction.guild) return [];
271 const prompt = interaction.options.getString("message");
272 const autocompletions = [];
273 if ( prompt === null ) {
274 for (const replacement of validReplacements) {
275 autocompletions.push(`{${replacement}}`);
276 };
277 return autocompletions;
278 };
279 const beforeLastOpenBracket = prompt.match(/(.*){[^{}]{0,15}$/);
280 const afterLastOpenBracket = prompt.match(/{[^{}]{0,15}$/);
281 if (beforeLastOpenBracket !== null) {
282 if (afterLastOpenBracket !== null) {
283 for (const replacement of validReplacements) {
PineaFan5d98a4b2023-01-19 16:15:47 +0000284 if (replacement.startsWith(afterLastOpenBracket[0]!.slice(1))) {
PineaFan538d3752023-01-12 21:48:23 +0000285 autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
286 }
287 }
288 } else {
289 for (const replacement of validReplacements) {
290 autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
291 }
292 }
293 } else {
294 for (const replacement of validReplacements) {
295 autocompletions.push(`${prompt} {${replacement}}`);
296 }
297 }
298 return autocompletions;
299};
300
Skyler Grey16ecb172023-03-05 07:30:32 +0000301export { command, callback, check, autocomplete };