blob: ae55fc0c16fd90bd8c2667202d44e2f11eb7e491 [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;
218 data.message = out.fields.getTextInputValue("message") ?? null;
219 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);
226 break;
227 }
228 case "channelDM": {
229 await i.deferUpdate();
230 data.channel = "dm";
231 break;
232 }
233 case "role": {
234 await i.deferUpdate();
235 data.role = null;
236 break;
237 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100238 }
TheCodedProf01cba762023-02-18 15:55:05 -0500239 } else if (i.isRoleSelectMenu()) {
240 await i.deferUpdate();
241 switch(i.customId) {
242 case "roleToGive": {
243 data.role = i.values[0]!;
244 break
245 }
246 case "roleToPing": {
247 data.ping = i.values[0]!;
248 break
249 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100250 }
TheCodedProf01cba762023-02-18 15:55:05 -0500251 } else {
252 await i.deferUpdate();
253 data.channel = i.values[0]!;
pineafan41d93562022-07-30 22:10:15 +0100254 }
TheCodedProf01cba762023-02-18 15:55:05 -0500255
256 } while (!closed);
257 await interaction.deleteReply()
pineafan63fc5e22022-08-04 22:04:10 +0100258};
pineafan41d93562022-07-30 22:10:15 +0100259
TheCodedProff86ba092023-01-27 17:10:07 -0500260const check = (interaction: CommandInteraction, _partial: boolean = false) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100261 const member = interaction.member as Discord.GuildMember;
PineaFan538d3752023-01-12 21:48:23 +0000262 if (!member.permissions.has("ManageGuild"))
PineaFan0d06edc2023-01-17 22:10:31 +0000263 return "You must have the *Manage Server* permission to use this command";
pineafan41d93562022-07-30 22:10:15 +0100264 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100265};
pineafan41d93562022-07-30 22:10:15 +0100266
PineaFan538d3752023-01-12 21:48:23 +0000267const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
TheCodedProf4a6d5712023-01-19 15:54:40 -0500268 const validReplacements = ["serverName", "memberCount:all", "memberCount:bots", "memberCount:humans"]
PineaFan538d3752023-01-12 21:48:23 +0000269 if (!interaction.guild) return [];
270 const prompt = interaction.options.getString("message");
271 const autocompletions = [];
272 if ( prompt === null ) {
273 for (const replacement of validReplacements) {
274 autocompletions.push(`{${replacement}}`);
275 };
276 return autocompletions;
277 };
278 const beforeLastOpenBracket = prompt.match(/(.*){[^{}]{0,15}$/);
279 const afterLastOpenBracket = prompt.match(/{[^{}]{0,15}$/);
280 if (beforeLastOpenBracket !== null) {
281 if (afterLastOpenBracket !== null) {
282 for (const replacement of validReplacements) {
PineaFan5d98a4b2023-01-19 16:15:47 +0000283 if (replacement.startsWith(afterLastOpenBracket[0]!.slice(1))) {
PineaFan538d3752023-01-12 21:48:23 +0000284 autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
285 }
286 }
287 } else {
288 for (const replacement of validReplacements) {
289 autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
290 }
291 }
292 } else {
293 for (const replacement of validReplacements) {
294 autocompletions.push(`${prompt} {${replacement}}`);
295 }
296 }
297 return autocompletions;
298};
299
TheCodedProf01cba762023-02-18 15:55:05 -0500300export { command, callback, check, autocomplete };