Merge branch 'development' of github.com:clicksminuteper/nucleus into development
diff --git a/.gitignore b/.gitignore
index 72a0e85..bfedc85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,11 @@
dist/
.history/
node_modules/
-src/config/
+src/config/*
+!src/config/format.ts
+!src/config/default.json
+!src/config/emojis.json
+src/config/main.json
.vscode/
yarn-error.log
yarn.lock
diff --git a/ClicksMigratingProblems/index.js b/ClicksMigratingProblems/index.js
index ca6e63d..c6f77bf 100644
--- a/ClicksMigratingProblems/index.js
+++ b/ClicksMigratingProblems/index.js
@@ -62,7 +62,7 @@
"message": null,
"role": null,
},
- "welcomeRole": data.welcome ? (data.welcome.role !== null ? data.welcome.role.toString() : null) : null,
+ "role": data.welcome ? (data.welcome.role !== null ? data.welcome.role.toString() : null) : null,
"channel": data.welcome ? (data.welcome.message.text !== null ? data.welcome.message.channel.toString() : null) : null,
"message": data.welcome ? (data.welcome.message.text) : null
},
diff --git a/TODO b/TODO
index cdcd42a..579b574 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,6 @@
Role all
Server rules
-! A way to add buttons to verify / role menu
+verificationRequired on welcome
ROLE MENU SETTINGS
diff --git a/TODO.json b/TODO.json
index 7a13d1e..802aee6 100644
--- a/TODO.json
+++ b/TODO.json
@@ -1,11 +1,5 @@
{
- "logging": {
- "logs": {
- "enabled": true,
- "toLog": "3fffff"
- }
- },
"filters": {
"images": {
"NSFW": false,
@@ -30,11 +24,5 @@
}
},
"roleMenu": [],
- "tracks": [],
- "welcome": {
- "enabled": false,
- "welcomeRole": null,
- "channel": null,
- "message": null
- }
+ "tracks": []
}
\ No newline at end of file
diff --git a/src/Unfinished/all.ts b/src/Unfinished/all.ts
index 5c482f1..d49633c 100644
--- a/src/Unfinished/all.ts
+++ b/src/Unfinished/all.ts
@@ -78,7 +78,7 @@
}
}
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true})
let filters: Filter[] = [
filterList.member.has.role("959901346000154674"),
diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts
index 722eb3f..28eb435 100644
--- a/src/actions/tickets/create.ts
+++ b/src/actions/tickets/create.ts
@@ -17,6 +17,7 @@
return await interaction.reply({embeds: [new EmojiEmbed()
.setTitle("Tickets are disabled")
.setDescription("Please enable tickets in the configuration to use this command.")
+ .setFooter({text: interaction.member.permissions.has("MANAGE_GUILD") ? "You can enable it by running /settings tickets" : ""})
.setStatus("Danger")
.setEmoji("CONTROL.BLOCKCROSS")
], ephemeral: true});
diff --git a/src/commands/createTestButton.ts b/src/commands/createTestButton.ts
deleted file mode 100644
index c04e13d..0000000
--- a/src/commands/createTestButton.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = new SlashCommandBuilder()
- .setName("createtestbutton")
- .setDescription("creates a test button") // TODO: remove for release
-
-const callback = (interaction: CommandInteraction) => {
- interaction.reply({components: [new MessageActionRow().addComponents([
- new MessageButton()
- .setCustomId("createticket")
- .setLabel("Create Ticket")
- .setStyle("PRIMARY")
- .setDisabled(false),
- new MessageButton()
- .setCustomId("verifybutton")
- .setLabel("Verify")
- .setStyle("PRIMARY")
- .setDisabled(false),
- new MessageButton()
- .setCustomId("rolemenu")
- .setLabel("Get roles")
- .setStyle("PRIMARY")
- .setDisabled(false)
- ])]});
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
- return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/help.ts b/src/commands/help.ts
index 568a90c..c3b015c 100644
--- a/src/commands/help.ts
+++ b/src/commands/help.ts
@@ -6,7 +6,7 @@
.setName("help")
.setDescription("Shows help for commands")
-const callback = (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
interaction.reply("hel p"); // TODO: FINISH THIS FOR RELEASE
}
diff --git a/src/commands/nucleus/invite.ts b/src/commands/nucleus/invite.ts
index 7f7d4b8..96e1449 100644
--- a/src/commands/nucleus/invite.ts
+++ b/src/commands/nucleus/invite.ts
@@ -9,7 +9,7 @@
.setName("invite")
.setDescription("Invites Nucleus to your server")
-const callback = (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
interaction.reply({embeds: [new EmojiEmbed()
.setTitle("Invite")
.setDescription("You can invite Nucleus to your server by clicking the button below")
diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts
index 740ab7b..9d273b9 100644
--- a/src/commands/nucleus/premium.ts
+++ b/src/commands/nucleus/premium.ts
@@ -8,7 +8,7 @@
.setName("premium")
.setDescription("Information about Nucleus Premium")
-const callback = (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
interaction.reply({embeds: [new EmojiEmbed()
.setTitle("Premium")
.setDescription(
diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts
index cb10e7a..beea94b 100644
--- a/src/commands/nucleus/stats.ts
+++ b/src/commands/nucleus/stats.ts
@@ -9,7 +9,7 @@
.setName("stats")
.setDescription("Gets the bot's stats")
-const callback = (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
interaction.reply({
embeds: [new EmojiEmbed()
.setTitle("Stats")
diff --git a/src/commands/settings/filters.ts b/src/commands/settings/filters.ts
new file mode 100644
index 0000000..183b91c
--- /dev/null
+++ b/src/commands/settings/filters.ts
@@ -0,0 +1,29 @@
+import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+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';
+import getEmojiByName from '../../utils/getEmojiByName.js';
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+ builder
+ .setName("filter")
+ .setDescription("Setting for message filters")
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+ let member = (interaction.member as Discord.GuildMember)
+ if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the *Manage Server* permission to use this command"
+ return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/settings/logs/_meta.ts b/src/commands/settings/logs/_meta.ts
index 15a6fd4..f46987f 100644
--- a/src/commands/settings/logs/_meta.ts
+++ b/src/commands/settings/logs/_meta.ts
@@ -1,4 +1,4 @@
-const name = "log";
+const name = "logs";
const description = "Settings for logging";
export { name, description };
\ No newline at end of file
diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts
index dac200c..ef303cb 100644
--- a/src/commands/settings/logs/events.ts
+++ b/src/commands/settings/logs/events.ts
@@ -1,17 +1,106 @@
-import { CommandInteraction } from "discord.js";
+import { LoadingEmbed } from './../../../utils/defaultEmbeds.js';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
+import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import client from '../../../utils/client.js';
+import { toHexArray, toHexInteger } from "../../../utils/calculate.js";
+
+
+const logs = {
+ "channelUpdate": "Channels created, deleted or modified",
+ "emojiUpdate": "Server emojis modified",
+ "stickerUpdate": "Server stickers modified",
+ "guildUpdate": "Server settings updated",
+ "guildMemberUpdate": "Member updated (i.e. nickname)",
+ "guildMemberPunish": "Members punished (i.e. muted, banned, kicked)",
+ "guildRoleUpdate": "Role settings changed",
+ "guildInviteUpdate": "Server invite created or deleted",
+ "messageUpdate": "Message edited",
+ "messageDelete": "Message deleted",
+ "messageDeleteBulk": "Messages purged",
+ "messageReactionUpdate": "Message reactions cleared",
+ "messageMassPing": "Message pings multiple members at once",
+ "messageAnnounce": "Message published in announcement channel",
+ "threadUpdate": "Thread created or deleted",
+ "webhookUpdate": "Webhooks created or deleted",
+ "guildMemberVerify": "Member runs verify",
+ "autoModeratorDeleted": "Messages auto deleted by Nucleus",
+ "nucleusSettingsUpdated": "Nucleus' settings updated by a moderator",
+ "ticketUpdate": "Tickets created or deleted",
+}
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
.setName("events")
.setDescription("Sets what events should be logged")
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/log/events]");
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
+ let m;
+ while (true) {
+ let config = await client.database.guilds.read(interaction.guild.id)
+ let converted = toHexArray(config.logging.logs.toLog)
+ m = await interaction.editReply({embeds: [new EmojiEmbed()
+ .setTitle("Logging Events")
+ .setDescription("Below are the events being logged in the server. You can toggle them on and off in the dropdown")
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ ], components: [
+ new MessageActionRow().addComponents([new MessageSelectMenu()
+ .setPlaceholder("Set events to log")
+ .setMaxValues(Object.keys(logs).length)
+ .setCustomId("logs")
+ .setMinValues(0)
+ .setOptions(Object.keys(logs).map((e, i) => ({
+ label: logs[e],
+ value: i.toString(),
+ default: converted.includes(e)
+ })))
+ ]),
+ new MessageActionRow().addComponents([
+ new MessageButton()
+ .setLabel("Select all")
+ .setStyle("PRIMARY")
+ .setCustomId("all"),
+ new MessageButton()
+ .setLabel("Select none")
+ .setStyle("DANGER")
+ .setCustomId("none")
+ ])
+ ]})
+ let i;
+ try {
+ i = await m.awaitMessageComponent({ time: 300000 });
+ } catch (e) {
+ break
+ }
+ i.deferUpdate()
+ if (i.customId === "logs") {
+ let selected = i.values;
+ let newLogs = toHexInteger(selected.map(e => Object.keys(logs)[parseInt(e)]))
+ await client.database.guilds.write(interaction.guild.id, {"logging.logs.toLog": newLogs})
+ } else if (i.customId === "all") {
+ let newLogs = toHexInteger(Object.keys(logs).map(e => e))
+ await client.database.guilds.write(interaction.guild.id, {"logging.logs.toLog": newLogs})
+ } else if (i.customId === "none") {
+ await client.database.guilds.write(interaction.guild.id, {"logging.logs.toLog": 0})
+ } else {
+ break
+ }
+ }
+ m = await interaction.editReply({embeds: [new EmojiEmbed()
+ .setTitle("Logging Events")
+ .setDescription("Below are the events being logged in the server. You can toggle them on and off in the dropdown")
+ .setFooter({text: "Message timed out"})
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ ]})
}
const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+ let member = (interaction.member as Discord.GuildMember)
+ if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the *Manage Server* permission to use this command"
return true;
}
diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts
index d972674..be15869 100644
--- a/src/commands/settings/stats.ts
+++ b/src/commands/settings/stats.ts
@@ -144,12 +144,7 @@
await client.database.guilds.write(interaction.guild.id, null, toRemove.map(k => `stats.${k}`));
}
}
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Stats Channel")
- .setDescription("The following channels update when someone joins or leaves the server. You can select a channel to remove it from the list.")
- .setStatus("Danger")
- .setEmoji("CHANNEL.TEXT.DELETE")
- ], components: []})
+ await interaction.editReply({embeds: [m.embeds[0].setFooter({text: "Message closed"})], components: []});
}
const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index f199ac3..44f974e 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -192,6 +192,11 @@
.setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
.setStyle("SECONDARY")
.setCustomId("manageTypes"),
+ new MessageButton()
+ .setLabel("Add create ticket button")
+ .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+ .setStyle("PRIMARY")
+ .setCustomId("send"),
])]
});
let i;
@@ -217,6 +222,122 @@
await client.database.guilds.write(interaction.guild.id, null, ["tickets.supportRole"])
data.supportRole = undefined;
} else lastClicked = "sup";
+ } else if (i.component.customId === "send") {
+ const ticketMessages = [
+ {label: "Create ticket", description: "Click the button below to create a ticket"},
+ {label: "Issues, questions or feedback?", description: "Click below to open a ticket and get help from our staff team"},
+ {label: "Contact Us", description: "Click the button below to speak to us privately"},
+ ]
+ while (true) {
+ let enabled = data.enabled && data.category !== null;
+ await interaction.editReply({embeds: [new EmojiEmbed()
+ .setTitle("Ticket Button")
+ .setDescription("Select a message template to send in this channel")
+ .setFooter({text: enabled ? "" : "Tickets are not set up correctly so the button may not work for users. Check the main menu to find which options must be set."})
+ .setStatus(enabled ? "Success" : "Warning")
+ .setEmoji("GUILD.ROLES.CREATE")
+ ], components: [
+ new MessageActionRow().addComponents([
+ new MessageSelectMenu().setOptions(ticketMessages.map((t: {label: string, description: string, value?: string}, index) => {
+ t.value = index.toString(); return t as {value: string, label: string, description: string}
+ })).setCustomId("template").setMaxValues(1).setMinValues(1).setPlaceholder("Select a message template"),
+ ]),
+ new MessageActionRow().addComponents([
+ new MessageButton()
+ .setCustomId("back")
+ .setLabel("Back")
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+ .setStyle("DANGER"),
+ new MessageButton()
+ .setCustomId("blank")
+ .setLabel("Empty")
+ .setStyle("SECONDARY"),
+ new MessageButton()
+ .setCustomId("custom")
+ .setLabel("Custom")
+ .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
+ .setStyle("PRIMARY")
+ ])
+ ]});
+ let i;
+ try {
+ i = await m.awaitMessageComponent({time: 300000});
+ } catch(e) { break }
+ if (i.component.customId === "template") {
+ i.deferUpdate()
+ await interaction.channel.send({embeds: [new EmojiEmbed()
+ .setTitle(ticketMessages[parseInt(i.values[0])].label)
+ .setDescription(ticketMessages[parseInt(i.values[0])].description)
+ .setStatus("Success")
+ .setEmoji("GUILD.TICKET.OPEN")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Create Ticket")
+ .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
+ .setStyle("SUCCESS")
+ .setCustomId("createticket")
+ ])]});
+ break
+ } else if (i.component.customId === "blank") {
+ i.deferUpdate()
+ await interaction.channel.send({components: [new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Create Ticket")
+ .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+ .setStyle("SUCCESS")
+ .setCustomId("createticket")
+ ])]});
+ break
+ } else if (i.component.customId === "custom") {
+ await i.showModal(new Discord.Modal().setCustomId("modal").setTitle(`Enter embed details`).addComponents(
+ new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
+ .setCustomId("title")
+ .setLabel("Title")
+ .setMaxLength(256)
+ .setRequired(true)
+ .setStyle("SHORT")
+ ),
+ new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
+ .setCustomId("description")
+ .setLabel("Description")
+ .setMaxLength(4000)
+ .setRequired(true)
+ .setStyle("PARAGRAPH")
+ )
+ ))
+ await interaction.editReply({
+ embeds: [new EmojiEmbed()
+ .setTitle("Ticket Button")
+ .setDescription("Modal opened. If you can't see it, click back and try again.")
+ .setStatus("Success")
+ .setEmoji("GUILD.TICKET.OPEN")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Back")
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+ .setStyle("PRIMARY")
+ .setCustomId("back")
+ ])]
+ });
+ let out;
+ try {
+ out = await modalInteractionCollector(m, (m) => m.channel.id === interaction.channel.id, (m) => m.customId === "modify")
+ } catch (e) { break }
+ if (out.fields) {
+ let title = out.fields.getTextInputValue("title");
+ let description = out.fields.getTextInputValue("description");
+ await interaction.channel.send({embeds: [new EmojiEmbed()
+ .setTitle(title)
+ .setDescription(description)
+ .setStatus("Success")
+ .setEmoji("GUILD.TICKET.OPEN")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Create Ticket")
+ .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+ .setStyle("SUCCESS")
+ .setCustomId("createticket")
+ ])]});
+ break
+ } else { continue }
+ }
+ }
} else if (i.component.customId === "enabled") {
await client.database.guilds.write(interaction.guild.id, { "tickets.enabled": !data.enabled })
data.enabled = !data.enabled;
diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts
index 14dfe8d..a77f2f4 100644
--- a/src/commands/settings/verify.ts
+++ b/src/commands/settings/verify.ts
@@ -1,11 +1,12 @@
import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
-import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import Discord, { CommandInteraction, Emoji, MessageActionRow, MessageButton, MessageSelectMenu, TextInputComponent } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import confirmationMessage from "../../utils/confirmationMessage.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
import client from "../../utils/client.js";
+import { modalInteractionCollector } from '../../utils/dualCollector.js';
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
@@ -53,7 +54,7 @@
let data = {
meta:{
type: 'verifyRoleChanged',
- displayName: 'Ignored Groups Changed',
+ displayName: 'Verify Role Changed',
calculateType: 'nucleusSettingsUpdated',
color: NucleusColors.green,
emoji: "CONTROL.BLOCKTICK",
@@ -103,7 +104,12 @@
.setLabel(clicks ? "Click again to confirm" : "Reset role")
.setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
.setStyle("DANGER")
- .setDisabled(!role)
+ .setDisabled(!role),
+ new MessageButton()
+ .setCustomId("send")
+ .setLabel("Add verify button")
+ .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+ .setStyle("PRIMARY")
])]});
let i;
try {
@@ -117,23 +123,127 @@
await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"])
role = undefined;
}
+ } else if (i.component.customId === "send") {
+ const verifyMessages = [
+ {label: "Verify", description: "Click the button below to get verified"},
+ {label: "Get verified", description: "To get access to the rest of the server, click the button below"},
+ {label: "Ready to verify?", description: "Click the button below to verify yourself"},
+ ]
+ while (true) {
+ await interaction.editReply({embeds: [new EmojiEmbed()
+ .setTitle("Verify Button")
+ .setDescription("Select a message template to send in this channel")
+ .setFooter({text: role ? "" : "You do no have a verify role set so the button will not work."})
+ .setStatus(role ? "Success" : "Warning")
+ .setEmoji("GUILD.ROLES.CREATE")
+ ], components: [
+ new MessageActionRow().addComponents([
+ new MessageSelectMenu().setOptions(verifyMessages.map((t: {label: string, description: string, value?: string}, index) => {
+ t.value = index.toString(); return t as {value: string, label: string, description: string}
+ })).setCustomId("template").setMaxValues(1).setMinValues(1).setPlaceholder("Select a message template"),
+ ]),
+ new MessageActionRow().addComponents([
+ new MessageButton()
+ .setCustomId("back")
+ .setLabel("Back")
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+ .setStyle("DANGER"),
+ new MessageButton()
+ .setCustomId("blank")
+ .setLabel("Empty")
+ .setStyle("SECONDARY"),
+ new MessageButton()
+ .setCustomId("custom")
+ .setLabel("Custom")
+ .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
+ .setStyle("PRIMARY")
+ ])
+ ]});
+ let i;
+ try {
+ i = await m.awaitMessageComponent({time: 300000});
+ } catch(e) { break }
+ if (i.component.customId === "template") {
+ i.deferUpdate()
+ await interaction.channel.send({embeds: [new EmojiEmbed()
+ .setTitle(verifyMessages[parseInt(i.values[0])].label)
+ .setDescription(verifyMessages[parseInt(i.values[0])].description)
+ .setStatus("Success")
+ .setEmoji("CONTROL.BLOCKTICK")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Verify")
+ .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
+ .setStyle("SUCCESS")
+ .setCustomId("verifybutton")
+ ])]});
+ break
+ } else if (i.component.customId === "blank") {
+ i.deferUpdate()
+ await interaction.channel.send({components: [new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Verify")
+ .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
+ .setStyle("SUCCESS")
+ .setCustomId("verifybutton")
+ ])]});
+ break
+ } else if (i.component.customId === "custom") {
+ await i.showModal(new Discord.Modal().setCustomId("modal").setTitle(`Enter embed details`).addComponents(
+ new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
+ .setCustomId("title")
+ .setLabel("Title")
+ .setMaxLength(256)
+ .setRequired(true)
+ .setStyle("SHORT")
+ ),
+ new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
+ .setCustomId("description")
+ .setLabel("Description")
+ .setMaxLength(4000)
+ .setRequired(true)
+ .setStyle("PARAGRAPH")
+ )
+ ))
+ await interaction.editReply({
+ embeds: [new EmojiEmbed()
+ .setTitle("Verify Button")
+ .setDescription("Modal opened. If you can't see it, click back and try again.")
+ .setStatus("Success")
+ .setEmoji("GUILD.TICKET.OPEN")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Back")
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+ .setStyle("PRIMARY")
+ .setCustomId("back")
+ ])]
+ });
+ let out;
+ try {
+ out = await modalInteractionCollector(m, (m) => m.channel.id === interaction.channel.id, (m) => m.customId === "modify")
+ } catch (e) { break }
+ if (out.fields) {
+ let title = out.fields.getTextInputValue("title");
+ let description = out.fields.getTextInputValue("description");
+ await interaction.channel.send({embeds: [new EmojiEmbed()
+ .setTitle(title)
+ .setDescription(description)
+ .setStatus("Success")
+ .setEmoji("CONTROL.BLOCKTICK")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Verify")
+ .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
+ .setStyle("SUCCESS")
+ .setCustomId("verifybutton")
+ ])]});
+ break
+ } else { continue }
+ }
+ }
} else {
- break
+ i.deferUpdate()
+ break;
}
}
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify Role")
- .setDescription(role ? `Your verify role is currently set to <@&${role}}>` : `You have not set a verify role`)
- .setStatus("Success")
- .setEmoji("GUILD.ROLE.CREATE")
- .setFooter({text: "Message closed"})
- ], components: [new MessageActionRow().addComponents([new MessageButton()
- .setCustomId("clear")
- .setLabel("Clear")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- .setStyle("SECONDARY")
- .setDisabled(true),
- ])]});
+ await interaction.editReply({embeds: [m.embeds[0].setFooter({text: "Message closed"})], components: []});
}
const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts
new file mode 100644
index 0000000..1a107ed
--- /dev/null
+++ b/src/commands/settings/welcome.ts
@@ -0,0 +1,209 @@
+import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+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';
+import getEmojiByName from '../../utils/getEmojiByName.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, ChannelType.GuildNews
+ ]))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ const { renderRole, renderChannel, log, NucleusColors, entry, renderUser } = client.logger
+ await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
+ let m;
+ if (interaction.options.getRole("role") || interaction.options.getChannel("channel") || interaction.options.getString("message")) {
+ let role;
+ let ping;
+ let message = interaction.options.getString("message");
+ try {
+ role = interaction.options.getRole("role")
+ ping = interaction.options.getRole("ping")
+ } 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")
+ ]})
+ }
+ let channel;
+ try {
+ channel = interaction.options.getChannel("channel")
+ } 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")
+ ]})
+ }
+ role = role as Discord.Role
+ ping = ping as Discord.Role
+ channel = channel as Discord.TextChannel
+ let options = {}
+ if (role) options["role"] = renderRole(role)
+ if (ping) options["ping"] = renderRole(ping)
+ if (channel) options["channel"] = renderChannel(channel)
+ if (message) options["message"] = "\n> " + message
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("GUILD.ROLES.EDIT")
+ .setTitle("Welcome Events")
+ .setDescription(generateKeyValueList(options))
+ .setColor("Warning")
+ .setInverted(true)
+ .send(true)
+ if (confirmation.cancelled) return
+ if (confirmation.success) {
+ try {
+ let toChange = {}
+ 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);
+ let list = {
+ 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.id))
+ if (message) list["message"] = entry(message, `\`${message}\``)
+ try {
+ let 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 {}
+ } 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
+ while (true) {
+ let 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*"}`
+ )
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ ], components: [
+ new MessageActionRow().addComponents([
+ new MessageButton()
+ .setLabel(lastClicked == "clear-message" ? "Click again to confirm" : "Clear Message")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setCustomId("clear-message")
+ .setDisabled(!config.welcome.message)
+ .setStyle("DANGER"),
+ new MessageButton()
+ .setLabel(lastClicked == "clear-role" ? "Click again to confirm" : "Clear Role")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setCustomId("clear-role")
+ .setDisabled(!config.welcome.role)
+ .setStyle("DANGER"),
+ new MessageButton()
+ .setLabel(lastClicked == "clear-ping" ? "Click again to confirm" : "Clear Ping")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setCustomId("clear-ping")
+ .setDisabled(!config.welcome.ping)
+ .setStyle("DANGER"),
+ new MessageButton()
+ .setLabel(lastClicked == "clear-channel" ? "Click again to confirm" : "Clear Channel")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setCustomId("clear-channel")
+ .setDisabled(!config.welcome.channel)
+ .setStyle("DANGER"),
+ new MessageButton()
+ .setLabel("Set Channel to DM")
+ .setCustomId("set-channel-dm")
+ .setDisabled(config.welcome.channel == "dm")
+ .setStyle("SECONDARY")
+ ])
+ ]})
+ let i;
+ try {
+ i = await m.awaitMessageComponent({ time: 300000 });
+ } catch (e) {
+ break
+ }
+ 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" }
+ } 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.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
+ }
+ }
+ await interaction.editReply({embeds: [m.embeds[0].setFooter({text: "Message closed"})], components: []});
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+ let member = (interaction.member as Discord.GuildMember)
+ if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the *Manage Server* permission to use this command"
+ return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index 03b13c6..70d5a75 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -9,7 +9,7 @@
.setDescription("Get and manage the servers tags")
.addStringOption(o => o.setName("tag").setDescription("The tag to get").setAutocomplete(true).setRequired(true))
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
const config = await client.database.guilds.read(interaction.guild.id)
const tags = config.getKey("tags")
const tag = tags[interaction.options.getString("tag")]
diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts
index b0a278d..4615def 100644
--- a/src/commands/tags/create.ts
+++ b/src/commands/tags/create.ts
@@ -79,7 +79,7 @@
const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
let member = (interaction.member as Discord.GuildMember)
- if (!member.permissions.has("MANAGE_MESSAGES")) throw "You must have the *Manage Server* permission to use this command"
+ if (!member.permissions.has("MANAGE_MESSAGES")) throw "You must have the *Manage Messages* permission to use this command"
return true;
}
diff --git a/src/config/default.json b/src/config/default.json
index 84a4de0..3976812 100644
--- a/src/config/default.json
+++ b/src/config/default.json
@@ -29,7 +29,8 @@
},
"welcome": {
"enabled": false,
- "welcomeRole": null,
+ "role": null,
+ "ping": null,
"channel": null,
"message": null
},
diff --git a/src/config/format.js b/src/config/format.js
index 16519b5..bdd3fdb 100644
--- a/src/config/format.js
+++ b/src/config/format.js
@@ -61,7 +61,7 @@
if (walkthrough) {
switch (key) {
case "enableDevelopment": {
- json[key] = (await getInput("\x1b[36mEnable development mode? \x1b[0m(\x1b[32mY\x1b[0m/\x1b[31mn\x1b[0m) > ") || "Y").toLowerCase() === "y"; break;
+ json[key] = (await getInput("\x1b[36mEnable development mode? This redisters commands in a single server making it easier to test\x1b[0m(\x1b[32mY\x1b[0m/\x1b[31mn\x1b[0m) > ") || "Y").toLowerCase() === "y"; break;
} case "owners": {
let chosen = "!";
let toWrite = []
@@ -81,7 +81,12 @@
if (walkthrough && !json.mongoUrl) json.mongoUrl = "mongodb://127.0.0.1:27017"
if (!json.mongoUrl.endsWith("/")) json.mongoUrl += "/";
if (!json.baseUrl.endsWith("/")) json.baseUrl += "/";
- let hosts = fs.readFileSync('/etc/hosts', 'utf8').toString().split("\n");
+ let hosts;
+ try {
+ hosts = fs.readFileSync('/etc/hosts', 'utf8').toString().split("\n");
+ } catch (e) {
+ return console.log("\x1b[31m⚠ No /etc/hosts found. Please ensure the file exists and is readable. (Windows is not supported, Mac and Linux users should not experience this error)")
+ }
let localhost = hosts.find(line => line.split(" ")[1] === "localhost");
if (localhost) { localhost = localhost.split(" ")[0]; }
else { localhost = "127.0.0.1" }
diff --git a/src/events/commandError.ts b/src/events/commandError.ts
index 6d3672e..8edb480 100644
--- a/src/events/commandError.ts
+++ b/src/events/commandError.ts
@@ -5,7 +5,7 @@
export async function callback(client, interaction, error) {
if (interaction.replied || interaction.deferred) {
await interaction.followUp({embeds: [new EmojiEmbed()
- .setTitle("Something went")
+ .setTitle("Something went wrong")
.setDescription(error.message ?? error.toString())
.setStatus("Danger")
.setEmoji("CONTROL.BLOCKCROSS")
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
index eaf4fea..a4bb8c3 100644
--- a/src/events/interactionCreate.ts
+++ b/src/events/interactionCreate.ts
@@ -16,8 +16,16 @@
return fuse.slice(0, 25).map(option => ({name: option.item, value: option.item}))
}
-const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
function generateStatsChannelAutocomplete(typed) {
+ const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
+ let autocompletions = []
+ const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/)
+ if (beforeLastOpenBracket !== null) { for (let replacement of validReplacements) { autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`) } }
+ else { for (let replacement of validReplacements) { autocompletions.push(`${typed} {${replacement}}`) } }
+ return getAutocomplete(typed, autocompletions)
+}
+function generateWelcomeMessageAutocomplete(typed) {
+ const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans", "member:mention", "member:name"]
let autocompletions = []
const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/)
if (beforeLastOpenBracket !== null) { for (let replacement of validReplacements) { autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`) } }
@@ -39,6 +47,7 @@
switch (`${interaction.commandName} ${interaction.options.getSubcommandGroup(false)} ${interaction.options.getSubcommand(false)}`) {
case `tag null null`: { return interaction.respond(getAutocomplete(interaction.options.getString("tag"), (await tagAutocomplete(interaction)))) }
case `settings null stats`: { return interaction.respond(generateStatsChannelAutocomplete(interaction.options.getString("name"))) }
+ case `settings null welcome`: { return interaction.respond(generateWelcomeMessageAutocomplete(interaction.options.getString("message"))) }
}
}
}
diff --git a/src/reflex/verify.ts b/src/reflex/verify.ts
index b95d5db..bc7aa4f 100644
--- a/src/reflex/verify.ts
+++ b/src/reflex/verify.ts
@@ -17,6 +17,7 @@
if ((!config.verify.enabled ) || (!config.verify.role)) return interaction.editReply({embeds: [new EmojiEmbed()
.setTitle("Verify")
.setDescription(`Verify is not enabled on this server`)
+ .setFooter({text: interaction.member.permissions.has("MANAGE_GUILD") ? "You can enable it by running /settings verify" : ""})
.setStatus("Danger")
.setEmoji("CONTROL.BLOCKCROSS")
], ephemeral: true, fetchReply: true});
diff --git a/src/reflex/welcome.ts b/src/reflex/welcome.ts
index 5b80fbd..9be71d5 100644
--- a/src/reflex/welcome.ts
+++ b/src/reflex/welcome.ts
@@ -1,41 +1,39 @@
-import log from '../utils/log.js'
import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
import client from '../utils/client.js';
+import EmojiEmbed from '../utils/generateEmojiEmbed.js';
export async function callback(_, member) {
if (member.bot) return
let config = await client.database.guilds.read(member.guild.id);
if (!config.welcome.enabled) return
- if (!config.welcome.verificationRequired.role) {
- if (config.welcome.welcomeRole) {
- try {
- await member.roles.add(config.welcome.welcomeRole)
- } catch (err) {
- console.error(err)
- }
- }
- }
-
- if (!config.welcome.verificationRequired.message && config.welcome.channel) {
+ if (config.welcome.channel) {
let string = config.welcome.message
if (string) {
string = await convertCurlyBracketString(string, member.id, member.displayName, member.guild.name, member.guild.members)
-
if (config.welcome.channel === 'dm') {
try {
- await member.send(string)
- } catch (err) {
- console.error(err)
- }
+ await member.send({
+ embeds: [new EmojiEmbed()
+ .setDescription(string)
+ .setStatus('Success')
+ ]
+ })
+ } catch {}
} else {
- let channel = await member.client.channels.fetch(config.welcome.channel)
+ let channel = await member.guild.channels.fetch(config.welcome.channel)
if (channel.guild.id !== member.guild.id) return
if (!channel) return
try {
- await channel.send(string)
+ await channel.send({
+ embeds: [new EmojiEmbed()
+ .setDescription(string)
+ .setStatus('Success')
+ ],
+ content: (config.welcome.ping ? `<@${config.welcome.ping}>` : '') + `<@${member.id}>`
+ })
} catch (err) {
- console.error(err)
+ console.error(err) // SEN
}
}
}
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 2f04198..8a5ca6f 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -49,7 +49,6 @@
let out = {$set: {}, $unset: {}}
if (set) out["$set"] = set;
if (unset.length) out["$unset"] = uo;
- console.log(out)
await this.guilds.updateOne({ id: guild }, out, { upsert: true });
}
@@ -211,7 +210,8 @@
message: boolean,
role: string | null
},
- welcomeRole: string | null,
+ role: string | null,
+ ping: string | null,
channel: string | null,
message: string | null,
}
diff --git a/src/utils/defaultEmbeds.ts b/src/utils/defaultEmbeds.ts
index 0f226da..e799307 100644
--- a/src/utils/defaultEmbeds.ts
+++ b/src/utils/defaultEmbeds.ts
@@ -4,5 +4,6 @@
.setTitle("Loading")
.setDescription("One moment...")
.setStatus("Danger")
- .setEmoji("NUCLEUS.LOADING")]
+ .setEmoji("NUCLEUS.LOADING")
+]
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 22da837..0c6aa89 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -29,7 +29,7 @@
entry(value, displayValue) {
return { value: value, displayValue: displayValue }
}
- renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | Discord.NewsChannel) {
+ renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel) {
return `${channel.name} [<#${channel.id}>]`;
}
renderRole(role: Discord.Role) {
diff --git a/tsconfig.json b/tsconfig.json
index e38a3bc..0b5e28f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,6 +11,6 @@
"moduleResolution": "node",
"skipLibCheck": true
},
- "include": ["src/**/*", "isntaller.js"],
+ "include": ["src/**/*", "installer.js"],
"exclude": []
}
\ No newline at end of file