import { LoadingEmbed } from "../../utils/defaults.js"; import Discord, { CommandInteraction, AutocompleteInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, APIMessageComponentEmoji, ChannelSelectMenuBuilder, RoleSelectMenuBuilder, RoleSelectMenuInteraction, ChannelSelectMenuInteraction, ButtonInteraction, ModalBuilder, TextInputBuilder, TextInputStyle, ModalSubmitInteraction, } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("welcome") .setDescription("Messages and roles sent or given when someone joins the server") const callback = async (interaction: CommandInteraction): Promise => { const { renderChannel } = client.logger; const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true }); let closed = false; let config = await client.database.guilds.read(interaction.guild!.id); let data = Object.assign({}, config.welcome); do { const buttons = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId("switch") .setLabel(data.enabled ? "Enabled" : "Disabled") .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger) .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji), new ButtonBuilder() .setCustomId("message") .setLabel((data.message ? "Change" : "Set") + "Message") .setStyle(ButtonStyle.Primary) .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), new ButtonBuilder() .setCustomId("channelDM") .setLabel("Send in DMs") .setStyle(ButtonStyle.Primary) .setDisabled(data.channel === "dm"), new ButtonBuilder() .setCustomId("role") .setLabel("Clear Role") .setStyle(ButtonStyle.Danger) .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji), new ButtonBuilder() .setCustomId("save") .setLabel("Save") .setStyle(ButtonStyle.Success) .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) .setDisabled( data.enabled === config.welcome.enabled && data.message === config.welcome.message && data.role === config.welcome.role && data.ping === config.welcome.ping && data.channel === config.welcome.channel ) ); const channelMenu = new ActionRowBuilder() .addComponents( new ChannelSelectMenuBuilder() .setCustomId("channel") .setPlaceholder("Select a channel to send welcome messages to") ); const roleMenu = new ActionRowBuilder() .addComponents( new RoleSelectMenuBuilder() .setCustomId("roleToGive") .setPlaceholder("Select a role to give to the member when they join the server") ); const pingMenu = new ActionRowBuilder() .addComponents( new RoleSelectMenuBuilder() .setCustomId("roleToPing") .setPlaceholder("Select a role to ping when a member joins the server") ); const embed = new EmojiEmbed() .setTitle("Welcome Settings") .setStatus("Success") .setDescription( `${getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Welcome messages and roles are ${data.enabled ? "enabled" : "disabled"}\n` + `**Welcome message:** ${data.message ? `\n> ` + await convertCurlyBracketString( data.message, interaction.user.id, interaction.user.username, interaction.guild!.name, interaction.guild!.members ) : "*None*"}\n` + `**Send message in:** ` + (data.channel ? (data.channel == "dm" ? "DMs" : renderChannel(data.channel)) : `*None set*`) + `\n` + `**Role to ping:** ` + (data.ping ? `<@&${data.ping}>` : `*None set*`) + `\n` + `**Role given on join:** ` + (data.role ? `<@&${data.role}>` : `*None set*`) ) await interaction.editReply({ embeds: [embed], components: [buttons, channelMenu, roleMenu, pingMenu] }); let i: RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction; try { i = await m.awaitMessageComponent({ filter: (interaction) => interaction.user.id === interaction.user.id, time: 300000 }) as RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction; } catch (e) { closed = true; continue; } if(i.isButton()) { switch(i.customId) { case "switch": { await i.deferUpdate(); data.enabled = !data.enabled; break; } case "message": { const modal = new ModalBuilder() .setCustomId("modal") .setTitle("Welcome Message") .addComponents( new ActionRowBuilder().addComponents( new TextInputBuilder() .setCustomId("ex1") .setLabel("Server Info (1/3)") .setPlaceholder( `{serverName} - This server's name\n\n` + `These placeholders will be replaced with the server's name, etc..` ) .setMaxLength(1) .setRequired(false) .setStyle(TextInputStyle.Paragraph) ), new ActionRowBuilder().addComponents( new TextInputBuilder() .setCustomId("ex2") .setLabel("Member Counts (2/3) - {MemberCount:...}") .setPlaceholder( `{:all} - Total member count\n` + `{:humans} - Total non-bot users\n` + `{:bots} - Number of bots\n` ) .setMaxLength(1) .setRequired(false) .setStyle(TextInputStyle.Paragraph) ), new ActionRowBuilder().addComponents( new TextInputBuilder() .setCustomId("ex3") .setLabel("Member who joined (3/3) - {member:...}") .setPlaceholder( `{:name} - The members name\n` ) .setMaxLength(1) .setRequired(false) .setStyle(TextInputStyle.Paragraph) ), new ActionRowBuilder() .addComponents( new TextInputBuilder() .setCustomId("message") .setPlaceholder("Enter a message to send when someone joins the server") .setValue(data.message ?? "") .setLabel("Message") .setStyle(TextInputStyle.Paragraph) ) ) const button = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId("back") .setLabel("Back") .setStyle(ButtonStyle.Secondary) .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) ) await i.showModal(modal) await i.editReply({ embeds: [ new EmojiEmbed() .setTitle("Welcome Settings") .setDescription("Modal opened. If you can't see it, click back and try again.") .setStatus("Success") ], components: [button] }); let out: ModalSubmitInteraction | null; try { out = await modalInteractionCollector(m, interaction.user) as ModalSubmitInteraction | null; } catch (e) { console.error(e); out = null; } if(!out) break; data.message = out.fields.getTextInputValue("message"); break; } case "save": { await i.deferUpdate(); await client.database.guilds.write(interaction.guild!.id, {"welcome": data}); config = await client.database.guilds.read(interaction.guild!.id); data = Object.assign({}, config.welcome); await client.memory.forceUpdate(interaction.guild!.id) break; } case "channelDM": { await i.deferUpdate(); data.channel = "dm"; break; } case "role": { await i.deferUpdate(); data.role = null; break; } } } else if (i.isRoleSelectMenu()) { await i.deferUpdate(); switch(i.customId) { case "roleToGive": { data.role = i.values[0]!; break } case "roleToPing": { data.ping = i.values[0]!; break } } } else { await i.deferUpdate(); data.channel = i.values[0]!; } } while (!closed); await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { const member = interaction.member as Discord.GuildMember; if (!member.permissions.has("ManageGuild")) return "You must have the *Manage Server* permission to use this command"; return true; }; const autocomplete = async (interaction: AutocompleteInteraction): Promise => { const validReplacements = ["serverName", "memberCount:all", "memberCount:bots", "memberCount:humans"] if (!interaction.guild) return []; const prompt = interaction.options.getString("message"); const autocompletions = []; if ( prompt === null ) { for (const replacement of validReplacements) { autocompletions.push(`{${replacement}}`); }; return autocompletions; }; const beforeLastOpenBracket = prompt.match(/(.*){[^{}]{0,15}$/); const afterLastOpenBracket = prompt.match(/{[^{}]{0,15}$/); if (beforeLastOpenBracket !== null) { if (afterLastOpenBracket !== null) { for (const replacement of validReplacements) { if (replacement.startsWith(afterLastOpenBracket[0]!.slice(1))) { autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`); } } } else { for (const replacement of validReplacements) { autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`); } } } else { for (const replacement of validReplacements) { autocompletions.push(`${prompt} {${replacement}}`); } } return autocompletions; }; export { command, callback, check, autocomplete };