Development (#11)
We need this NOW.
---------
Co-authored-by: PineaFan <ash@pinea.dev>
Co-authored-by: pineafan <pineapplefanyt@gmail.com>
Co-authored-by: PineappleFan <PineaFan@users.noreply.github.com>
Co-authored-by: Skyler <skyler3665@gmail.com>
diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts
index e7143fb..7584624 100644
--- a/src/commands/settings/welcome.ts
+++ b/src/commands/settings/welcome.ts
@@ -1,307 +1,263 @@
import { LoadingEmbed } from "../../utils/defaults.js";
import Discord, {
- Channel,
CommandInteraction,
- Message,
+ AutocompleteInteraction,
ActionRowBuilder,
ButtonBuilder,
- MessageComponentInteraction,
- Role,
ButtonStyle,
- AutocompleteInteraction,
- GuildChannel,
- EmbedBuilder
+ APIMessageComponentEmoji,
+ ChannelSelectMenuBuilder,
+ RoleSelectMenuBuilder,
+ RoleSelectMenuInteraction,
+ ChannelSelectMenuInteraction,
+ ButtonInteraction,
+ ModalBuilder,
+ TextInputBuilder,
+ TextInputStyle,
+ ModalSubmitInteraction,
} from "discord.js";
-import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import type { SlashCommandSubcommandBuilder } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import client from "../../utils/client.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import generateKeyValueList from "../../utils/generateKeyValueList.js";
-import { ChannelType } from "discord-api-types/v9";
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")
- .addStringOption((option) =>
- option
- .setName("message")
- .setDescription("The message to send when someone joins the server")
- .setAutocomplete(true)
- )
- .addRoleOption((option) =>
- option.setName("role").setDescription("The role given when someone joins the server")
- )
- .addRoleOption((option) =>
- option.setName("ping").setDescription("The role pinged when someone joins the server")
- )
- .addChannelOption((option) =>
- option
- .setName("channel")
- .setDescription("The channel the welcome message should be sent to")
- .addChannelTypes(ChannelType.GuildText)
- );
-const callback = async (interaction: CommandInteraction): Promise<unknown> => {
- const { renderRole, renderChannel, log, NucleusColors, entry, renderUser } = client.logger;
- await interaction.reply({
+const callback = async (interaction: CommandInteraction): Promise<void> => {
+ const { renderChannel } = client.logger;
+ const m = await interaction.reply({
embeds: LoadingEmbed,
fetchReply: true,
ephemeral: true
});
- let m: Message;
- if (
- interaction.options.get("role")?.role ||
- interaction.options.get("channel")?.channel ||
- interaction.options.get("message")?.value as string
- ) {
- let role: Role | null;
- let ping: Role | null;
- let channel: Channel | null;
- const message: string | null = interaction.options.get("message")?.value as string | null;
- try {
- role = interaction.options.get("role")?.role as Role | null;
- ping = interaction.options.get("ping")?.role as Role | null;
- } catch {
- return await interaction.editReply({
- embeds: [
- new EmojiEmbed()
- .setEmoji("GUILD.ROLES.DELETE")
- .setTitle("Welcome Events")
- .setDescription("The role you provided is not a valid role")
- .setStatus("Danger")
- ]
- });
- }
- try {
- channel = interaction.options.get("channel")?.channel as Channel | null;
- } catch {
- return await interaction.editReply({
- embeds: [
- new EmojiEmbed()
- .setEmoji("GUILD.ROLES.DELETE")
- .setTitle("Welcome Events")
- .setDescription("The channel you provided is not a valid channel")
- .setStatus("Danger")
- ]
- });
- }
- const options: {
- role?: string;
- ping?: string;
- channel?: string;
- message?: string;
- } = {};
-
- if (role) options.role = renderRole(role);
- if (ping) options.ping = renderRole(ping);
- if (channel) options.channel = renderChannel(channel as GuildChannel);
- if (message) options.message = "\n> " + message;
- const confirmation = await new confirmationMessage(interaction)
- .setEmoji("GUILD.ROLES.EDIT")
- .setTitle("Welcome Events")
- .setDescription(generateKeyValueList(options))
- .setColor("Warning")
- .setFailedMessage("Cancelled", "Warning", "GUILD.ROLES.DELETE") //TODO: Actual Message Needed
- .setInverted(true)
- .send(true);
- if (confirmation.cancelled) return;
- if (confirmation.success) {
- try {
- const toChange: {
- "welcome.role"?: string;
- "welcome.ping"?: string;
- "welcome.channel"?: string;
- "welcome.message"?: string;
- } = {};
- if (role) toChange["welcome.role"] = role.id;
- if (ping) toChange["welcome.ping"] = ping.id;
- if (channel) toChange["welcome.channel"] = channel.id;
- if (message) toChange["welcome.message"] = message;
- await client.database.guilds.write(interaction.guild!.id, toChange);
- const list: {
- memberId: ReturnType<typeof entry>;
- changedBy: ReturnType<typeof entry>;
- role?: ReturnType<typeof entry>;
- ping?: ReturnType<typeof entry>;
- channel?: ReturnType<typeof entry>;
- message?: ReturnType<typeof entry>;
- } = {
- memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
- changedBy: entry(interaction.user.id, renderUser(interaction.user))
- };
- if (role) list.role = entry(role.id, renderRole(role));
- if (ping) list.ping = entry(ping.id, renderRole(ping));
- if (channel) list.channel = entry(channel.id, renderChannel(channel as GuildChannel));
- if (message) list.message = entry(message, `\`${message}\``);
- const data = {
- meta: {
- type: "welcomeSettingsUpdated",
- displayName: "Welcome Settings Changed",
- calculateType: "nucleusSettingsUpdated",
- color: NucleusColors.green,
- emoji: "CONTROL.BLOCKTICK",
- timestamp: new Date().getTime()
- },
- list: list,
- hidden: {
- guild: interaction.guild!.id
- }
- };
- log(data);
- } catch (e) {
- console.log(e);
- return interaction.editReply({
- embeds: [
- new EmojiEmbed()
- .setTitle("Welcome Events")
- .setDescription("Something went wrong while updating welcome settings")
- .setStatus("Danger")
- .setEmoji("GUILD.ROLES.DELETE")
- ],
- components: []
- });
- }
- } else {
- return interaction.editReply({
- embeds: [
- new EmojiEmbed()
- .setTitle("Welcome Events")
- .setDescription("No changes were made")
- .setStatus("Success")
- .setEmoji("GUILD.ROLES.CREATE")
- ],
- components: []
- });
- }
- }
- let lastClicked = null;
- let timedOut = false;
+ let closed = false;
+ let config = await client.database.guilds.read(interaction.guild!.id);
+ let data = Object.assign({}, config.welcome);
do {
- const config = await client.database.guilds.read(interaction.guild!.id);
- m = (await interaction.editReply({
- embeds: [
- new EmojiEmbed()
- .setTitle("Welcome Events")
- .setDescription(
- `**Message:** ${config.welcome.message ? `\n> ${config.welcome.message}` : "*None set*"}\n` +
- `**Role:** ${
- config.welcome.role
- ? renderRole((await interaction.guild!.roles.fetch(config.welcome.role))!)
- : "*None set*"
- }\n` +
- `**Ping:** ${
- config.welcome.ping
- ? renderRole((await interaction.guild!.roles.fetch(config.welcome.ping))!)
- : "*None set*"
- }\n` +
- `**Channel:** ${
- config.welcome.channel
- ? config.welcome.channel == "dm"
- ? "DM"
- : renderChannel((await interaction.guild!.channels.fetch(config.welcome.channel))!)
- : "*None set*"
- }`
+ const buttons = new ActionRowBuilder<ButtonBuilder>()
+ .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
)
- .setStatus("Success")
- .setEmoji("CHANNEL.TEXT.CREATE")
- ],
- components: [
- new ActionRowBuilder<ButtonBuilder>().addComponents([
- new ButtonBuilder()
- .setLabel(lastClicked == "clear-message" ? "Click again to confirm" : "Clear Message")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- .setCustomId("clear-message")
- .setDisabled(!config.welcome.message)
- .setStyle(ButtonStyle.Danger),
- new ButtonBuilder()
- .setLabel(lastClicked == "clear-role" ? "Click again to confirm" : "Clear Role")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- .setCustomId("clear-role")
- .setDisabled(!config.welcome.role)
- .setStyle(ButtonStyle.Danger),
- new ButtonBuilder()
- .setLabel(lastClicked == "clear-ping" ? "Click again to confirm" : "Clear Ping")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- .setCustomId("clear-ping")
- .setDisabled(!config.welcome.ping)
- .setStyle(ButtonStyle.Danger),
- new ButtonBuilder()
- .setLabel(lastClicked == "clear-channel" ? "Click again to confirm" : "Clear Channel")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- .setCustomId("clear-channel")
- .setDisabled(!config.welcome.channel)
- .setStyle(ButtonStyle.Danger),
- new ButtonBuilder()
- .setLabel("Set Channel to DM")
- .setCustomId("set-channel-dm")
- .setDisabled(config.welcome.channel == "dm")
- .setStyle(ButtonStyle.Secondary)
- ])
- ]
- })) as Message;
- let i: MessageComponentInteraction;
+ );
+
+ const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
+ .addComponents(
+ new ChannelSelectMenuBuilder()
+ .setCustomId("channel")
+ .setPlaceholder("Select a channel to send welcome messages to")
+ );
+ const roleMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
+ .addComponents(
+ new RoleSelectMenuBuilder()
+ .setCustomId("roleToGive")
+ .setPlaceholder("Select a role to give to the member when they join the server")
+ );
+ const pingMenu = new ActionRowBuilder<RoleSelectMenuBuilder>()
+ .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({
- time: 300000,
- filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
- });
+ filter: (interaction) => interaction.user.id === interaction.user.id,
+ time: 300000
+ }) as RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction;
} catch (e) {
- timedOut = true;
+ closed = true;
continue;
}
- i.deferUpdate();
- if (i.customId == "clear-message") {
- if (lastClicked == "clear-message") {
- await client.database.guilds.write(interaction.guild!.id, {
- "welcome.message": null
- });
- lastClicked = null;
- } else {
- lastClicked = "clear-message";
+
+ 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<TextInputBuilder>().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<TextInputBuilder>().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<TextInputBuilder>().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<TextInputBuilder>()
+ .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<ButtonBuilder>()
+ .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);
+ break;
+ }
+ case "channelDM": {
+ await i.deferUpdate();
+ data.channel = "dm";
+ break;
+ }
+ case "role": {
+ await i.deferUpdate();
+ data.role = null;
+ break;
+ }
}
- } else if (i.customId == "clear-role") {
- if (lastClicked == "clear-role") {
- await client.database.guilds.write(interaction.guild!.id, {
- "welcome.role": null
- });
- lastClicked = null;
- } else {
- lastClicked = "clear-role";
+ } 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 if (i.customId == "clear-ping") {
- if (lastClicked == "clear-ping") {
- await client.database.guilds.write(interaction.guild!.id, {
- "welcome.ping": null
- });
- lastClicked = null;
- } else {
- lastClicked = "clear-ping";
- }
- } else if (i.customId == "clear-channel") {
- if (lastClicked == "clear-channel") {
- await client.database.guilds.write(interaction.guild!.id, {
- "welcome.channel": null
- });
- lastClicked = null;
- } else {
- lastClicked = "clear-channel";
- }
- } else if (i.customId == "set-channel-dm") {
- await client.database.guilds.write(interaction.guild!.id, {
- "welcome.channel": "dm"
- });
- lastClicked = null;
+ } else {
+ await i.deferUpdate();
+ data.channel = i.values[0]!;
}
- } while (!timedOut);
- await interaction.editReply({
- embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })],
- components: []
- });
+
+ } while (!closed);
+ await interaction.deleteReply()
};
-const check = (interaction: CommandInteraction) => {
+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";
@@ -309,7 +265,7 @@
};
const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
- const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
+ const validReplacements = ["serverName", "memberCount:all", "memberCount:bots", "memberCount:humans"]
if (!interaction.guild) return [];
const prompt = interaction.options.getString("message");
const autocompletions = [];
@@ -324,7 +280,7 @@
if (beforeLastOpenBracket !== null) {
if (afterLastOpenBracket !== null) {
for (const replacement of validReplacements) {
- if (replacement.startsWith(afterLastOpenBracket[0].slice(1))) {
+ if (replacement.startsWith(afterLastOpenBracket[0]!.slice(1))) {
autocompletions.push(`${beforeLastOpenBracket[1]}{${replacement}}`);
}
}
@@ -341,4 +297,4 @@
return autocompletions;
};
-export { command, callback, check, autocomplete };
+export { command, callback, check, autocomplete };
\ No newline at end of file