loads of new commands, updates and bug fixes
diff --git a/src/commands/categorisationTest.ts b/src/commands/categorisationTest.ts
index 2852a2b..33f99e9 100644
--- a/src/commands/categorisationTest.ts
+++ b/src/commands/categorisationTest.ts
@@ -60,7 +60,6 @@
.setCustomId("category")
.setMinValues(0)
.setMaxValues(1)
- // .setMaxValues(Object.keys(types).length)
.setOptions(Object.keys(types).map(type => {return {label: toCapitals(type), value: type}}))
])]});
}
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index 1e17e53..8b17db3 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -4,8 +4,8 @@
import confirmationMessage from "../../utils/confirmationMessage.js";
import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js';
import addPlurals from "../../utils/plurals.js";
+import client from "../../utils/client.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
@@ -31,13 +31,11 @@
+ `${addPlurals(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
+ `Are you sure you want to ban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
.setColor("Danger")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send()
if (confirmation.success) {
let dmd = false
let dm;
- let config = await readConfig(interaction.guild.id);
+ let config = await client.database.read(interaction.guild.id);
try {
if (interaction.options.getString("notify") != "no") {
dm = await (interaction.options.getMember("user") as GuildMember).send({
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index 9dc447b..20fbc01 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -5,7 +5,7 @@
import confirmationMessage from "../../utils/confirmationMessage.js";
import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js'
+import client from "../../utils/client.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
@@ -29,13 +29,11 @@
+ `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
+ `Are you sure you want to kick <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
.setColor("Danger")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send()
if (confirmation.success) {
let dmd = false
let dm;
- let config = await readConfig(interaction.guild.id);
+ let config = await client.database.read(interaction.guild.id);
try {
if (interaction.options.getString("notify") != "no") {
dm = await (interaction.options.getMember("user") as GuildMember).send({
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index b2cc94e..854f38f 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -6,7 +6,7 @@
import confirmationMessage from "../../utils/confirmationMessage.js";
import keyValueList from "../../utils/generateKeyValueList.js";
import humanizeDuration from "humanize-duration";
-import readConfig from "../../utils/readConfig.js";
+import client from "../../utils/client.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
@@ -21,7 +21,7 @@
.addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are muted | Default yes").setRequired(false)
.addChoices([["Yes", "yes"], ["No", "no"]]))
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
// @ts-ignore
const { log, NucleusColors, renderUser, entry } = interaction.client.logger
const user = interaction.options.getMember("user") as GuildMember
@@ -32,7 +32,7 @@
minutes: interaction.options.getInteger("minutes") || 0,
seconds: interaction.options.getInteger("seconds") || 0
}
- let config = await readConfig(interaction.guild.id)
+ let config = await client.database.read(interaction.guild.id)
let serverSettingsDescription = (config.moderation.mute.timeout ? "given a timeout" : "")
if (config.moderation.mute.role) serverSettingsDescription += (serverSettingsDescription ? " and " : "") + `given the <@&${config.moderation.mute.role}> role`
@@ -132,13 +132,11 @@
+ `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
+ `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
.setColor("Danger")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send(true)
if (confirmation.success) {
let dmd = false
let dm;
- let config = await readConfig(interaction.guild.id);
+ let config = await client.database.read(interaction.guild.id);
try {
if (interaction.options.getString("notify") != "no") {
dm = await (interaction.options.getMember("user") as GuildMember).send({
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index 6a0410a..96738c6 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -4,7 +4,6 @@
import confirmationMessage from "../../utils/confirmationMessage.js";
import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js';
import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -31,10 +30,8 @@
.setColor("Danger")
.addCustomBoolean(
"Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
- async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.client),
+ async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.options.getString("reason")),
"An appeal ticket will be created when Confirm is clicked")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send()
if (confirmation.success) {
let dmd = false
diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts
index 924e507..8e6762e 100644
--- a/src/commands/mod/purge.ts
+++ b/src/commands/mod/purge.ts
@@ -19,7 +19,7 @@
.addUserOption(option => option.setName("user").setDescription("The user to purge messages from").setRequired(false))
.addStringOption(option => option.setName("reason").setDescription("The reason for the purge").setRequired(false))
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
let user = interaction.options.getMember("user") as GuildMember ?? null
let channel = (interaction.channel as GuildChannel)
if (!(["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(channel.type.toString()))) {
@@ -205,8 +205,6 @@
"reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
}))
.setColor("Danger")
- // pluralize("day", interaction.options.getInteger("amount"))
- // const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send()
if (confirmation.success) {
let messages;
diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts
index b91f065..2498746 100644
--- a/src/commands/mod/slowmode.ts
+++ b/src/commands/mod/slowmode.ts
@@ -29,8 +29,6 @@
})
+ `Are you sure you want to set the slowmode in this channel?`)
.setColor("Danger")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send()
if (confirmation.success) {
try {
@@ -39,7 +37,7 @@
await interaction.editReply({embeds: [new generateEmojiEmbed()
.setEmoji("CHANNEL.SLOWMODE.RED")
.setTitle(`Slowmode`)
- .setDescription("An error occurred while setting the slowmode")
+ .setDescription("Something went wrong while setting the slowmode")
.setStatus("Danger")
], components: []})
}
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index 29d3bef..5a01287 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -4,8 +4,8 @@
import confirmationMessage from "../../utils/confirmationMessage.js";
import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
import keyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from '../../utils/readConfig.js';
-import addPlurals from '../../utils/plurals.js';
+import client from "../../utils/client.js";
+import addPlural from "../../utils/plurals.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
@@ -28,15 +28,13 @@
"reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
})
+ `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n`
- + `${addPlurals(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
+ + `${addPlural(interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0, "day")} of messages will be deleted\n\n`
+ `Are you sure you want to softban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
.setColor("Danger")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send()
if (confirmation.success) {
let dmd = false;
- let config = await readConfig(interaction.guild.id);
+ let config = await client.database.read(interaction.guild.id);
try {
if (interaction.options.getString("notify") != "no") {
await (interaction.options.getMember("user") as GuildMember).send({
diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts
index 8231752..f201142 100644
--- a/src/commands/mod/unban.ts
+++ b/src/commands/mod/unban.ts
@@ -11,7 +11,7 @@
.setDescription("Unbans a user")
.addStringOption(option => option.setName("user").setDescription("The user to unban (Username or ID)").setRequired(true))
-const callback = async (interaction: CommandInteraction) => { // TODO: User search
+const callback = async (interaction: CommandInteraction) => { // TODO: User search UI
let bans = await interaction.guild.bans.fetch()
let user = interaction.options.getString("user")
let resolved = bans.find(ban => ban.user.id == user)
diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts
index b2f8234..2a98c54 100644
--- a/src/commands/mod/unmute.ts
+++ b/src/commands/mod/unmute.ts
@@ -27,8 +27,6 @@
+ `The user **will${interaction.options.getString("notify") === "yes" ? '' : ' not'}** be notified\n\n`
+ `Are you sure you want to unmute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
.setColor("Danger")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send()
if (confirmation.success) {
let dmd = false
diff --git a/src/commands/mod/unnamed.ts b/src/commands/mod/unnamed.ts
deleted file mode 100644
index ca0bcef..0000000
--- a/src/commands/mod/unnamed.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-import Discord, { CommandInteraction, GuildMember, MessageActionRow } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import getEmojiByName from "../../utils/getEmojiByName.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
-import keyValueList from "../../utils/generateKeyValueList.js";
-import humanizeDuration from "humanize-duration";
-import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
-import readConfig from '../../utils/readConfig.js'
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
- .setName("unnamed")
- .setDescription("Gives a user a role")
- .addUserOption(option => option.setName("user").setDescription("The user to UNNAMED").setRequired(true)) // TODO
- .addIntegerOption(option => option.setName("days").setDescription("The number of days to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(27).setRequired(false))
- .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(23).setRequired(false))
- .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
- .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to UNNAMED the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
- .addStringOption(option => option.setName("reason").setDescription("The reason for the UNNAMED").setRequired(false))
- .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are UNNAMED | Default yes").setRequired(false)
- .addChoices([["Yes", "yes"], ["No", "no"]]))
-
-const callback = async (interaction: CommandInteraction) => {
- // @ts-ignore
- const { log, NucleusColors, renderUser, entry } = interaction.client.logger
- let config = await readConfig(interaction.guild.id);
- const user = interaction.options.getMember("user") as GuildMember
- const reason = interaction.options.getString("reason")
- const time = {
- days: interaction.options.getInteger("days") || 0,
- hours: interaction.options.getInteger("hours") || 0,
- minutes: interaction.options.getInteger("minutes") || 0,
- seconds: interaction.options.getInteger("seconds") || 0
- }
- let muteTime = (time.days * 24 * 60 * 60) + (time.hours * 60 * 60) + (time.minutes * 60) + time.seconds
- if (muteTime == 0) {
- let m = await interaction.reply({embeds: [
- new generateEmojiEmbed()
- .setEmoji("PUNISH.MUTE.GREEN") // TODO
- .setTitle("UNNAMED")
- .setDescription("How long should the user be UNNAMED")
- .setStatus("Success")
- ], components: [
- new MessageActionRow().addComponents([
- new Discord.MessageButton()
- .setCustomId("1m")
- .setLabel("1 Minute")
- .setStyle("SECONDARY"),
- new Discord.MessageButton()
- .setCustomId("10m")
- .setLabel("10 Minutes")
- .setStyle("SECONDARY"),
- new Discord.MessageButton()
- .setCustomId("30m")
- .setLabel("30 Minutes")
- .setStyle("SECONDARY"),
- new Discord.MessageButton()
- .setCustomId("1h")
- .setLabel("1 Hour")
- .setStyle("SECONDARY")
- ]),
- new MessageActionRow().addComponents([
- new Discord.MessageButton()
- .setCustomId("6h")
- .setLabel("6 Hours")
- .setStyle("SECONDARY"),
- new Discord.MessageButton()
- .setCustomId("12h")
- .setLabel("12 Hours")
- .setStyle("SECONDARY"),
- new Discord.MessageButton()
- .setCustomId("1d")
- .setLabel("1 Day")
- .setStyle("SECONDARY"),
- new Discord.MessageButton()
- .setCustomId("1w")
- .setLabel("1 Week")
- .setStyle("SECONDARY")
- ]),
- new MessageActionRow().addComponents([
- new Discord.MessageButton()
- .setCustomId("cancel")
- .setLabel("Cancel")
- .setStyle("DANGER")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- ])
- ], ephemeral: true, fetchReply: true})
- let component;
- try {
- component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000});
- } catch { return }
- component.deferUpdate();
- if (component.customId == "cancel") return interaction.editReply({embeds: [new generateEmojiEmbed()
- .setEmoji("PUNISH.MUTE.RED") // TODO
- .setTitle("UNNAMED")
- .setDescription("UNNAMED cancelled")
- .setStatus("Danger")
- ]})
- switch (component.customId) {
- case "1m": { muteTime = 60; break; }
- case "10m": { muteTime = 60 * 10; break; }
- case "30m": { muteTime = 60 * 30; break; }
- case "1h": { muteTime = 60 * 60; break; }
- case "6h": { muteTime = 60 * 60 * 6; break; }
- case "12h": { muteTime = 60 * 60 * 12; break; }
- case "1d": { muteTime = 60 * 60 * 24; break; }
- case "1w": { muteTime = 60 * 60 * 24 * 7; break; }
- }
- } else {
- await interaction.reply({embeds: [
- new generateEmojiEmbed()
- .setEmoji("PUNISH.MUTE.GREEN") // TODO
- .setTitle("UNNAMED")
- .setDescription("Loading...")
- .setStatus("Success")
- ], ephemeral: true, fetchReply: true})
- }
- // TODO:[Modals] Replace this with a modal
- let confirmation = await new confirmationMessage(interaction)
- .setEmoji("PUNISH.MUTE.RED") // TODO
- .setTitle("UNNAMED")
- .setDescription(keyValueList({
- "user": `<@!${user.id}> (${user.user.username})`,
- "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
- "reason": `\n> ${reason ? reason : "*No reason provided*"}`
- })
- + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
- + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`) // TODO
- .setColor("Danger")
- .addCustomBoolean(
- "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
- async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.client),
- "An appeal ticket will be created when Confirm is clicked")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
- .send()
- if (confirmation.success) {
- let dmd = false
- let dm;
- try {
- if (interaction.options.getString("notify") != "no") {
- dm = await (interaction.options.getMember("user") as GuildMember).send({
- embeds: [new generateEmojiEmbed()
- .setEmoji("PUNISH.MUTE.RED") // TODO
- .setTitle("UNNAMED")
- .setDescription(`You have been muted in ${interaction.guild.name}` + // TODO
- (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".\n\n" +
- `You will be unmuted at: <t:${Math.round((new Date).getTime() / 1000) + muteTime}:D> at <t:${Math.round((new Date).getTime() / 1000) + muteTime}:T> (<t:${Math.round((new Date).getTime() / 1000) + muteTime}:R>)`)) // TODO
- .setStatus("Danger")
- ]
- })
- dmd = true
- }
- } catch {}
- try {
- await ((interaction.options.getMember("user") as GuildMember).roles.add(interaction.guild.roles.cache.find((r) => r.id === config.moderation.role.role)))
- // TODO: Store when to remove the role
- } catch {
- await interaction.editReply({embeds: [new generateEmojiEmbed()
- .setEmoji("PUNISH.MUTE.RED")
- .setTitle(`Mute`)
- .setDescription("Something went wrong and the user was not UNNAMED")
- .setStatus("Danger")
- ], components: []})
- if (dmd) await dm.delete()
- return
- }
- let failed = (dmd == false && interaction.options.getString("notify") != "no")
- await interaction.editReply({embeds: [new generateEmojiEmbed()
- .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`) // TODO
- .setTitle(`Mute`) // TODO
- .setDescription("The member was muted" + (failed ? ", but could not be notified" : "")) // TODO
- .setStatus(failed ? "Warning" : "Success")
- ], components: []})
- let data = {
- meta:{
- type: 'memberMute', // TODO
- displayName: 'Member Muted', // TODO
- calculateType: 'guildMemberPunish',
- color: NucleusColors.yellow,
- emoji: 'PUNISH.WARN.YELLOW', // TODO
- timestamp: new Date().getTime()
- },
- list: {
- user: entry((interaction.options.getMember("user") as GuildMember).user.id, renderUser((interaction.options.getMember("user") as GuildMember).user)),
- mutedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)), // TODO
- time: entry(muteTime, `${humanizeDuration(muteTime * 1000, {round: true})}`),
- reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
- },
- hidden: {
- guild: interaction.guild.id
- }
- }
- log(data, interaction.client);
- } else {
- await interaction.editReply({embeds: [new generateEmojiEmbed()
- .setEmoji("PUNISH.MUTE.GREEN") // TODO
- .setTitle(`Mute`) // TODO
- .setDescription("No changes were made")
- .setStatus("Success")
- ], components: []})
- }
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
- let member = (interaction.member as GuildMember)
- let me = (interaction.guild.me as GuildMember)
- let apply = (interaction.options.getMember("user") as GuildMember)
- if (member == null || me == null || apply == null) throw "That member is not in the server"
- let memberPos = member.roles ? member.roles.highest.position : 0
- let mePos = me.roles ? me.roles.highest.position : 0
- let applyPos = apply.roles ? apply.roles.highest.position : 0
- // Check if Nucleus can UNNAMED the member
- if (! (mePos > applyPos)) throw "I do not have a role higher than that member"
- // Check if Nucleus has permission to UNNAMED
- if (! me.permissions.has("MANAGE_ROLES")) throw "I do not have the `manage_roles` permission";
- // Do not allow the user to have admin or be the owner
- if (apply.permissions.has("ADMINISTRATOR") || apply.id == interaction.guild.ownerId) throw "You cannot mute an admin or the owner"
- // Do not allow muting Nucleus
- if (member.id == me.id) throw "I cannot UNNAMED myself"
- // Allow the owner to UNNAMED anyone
- if (member.id == interaction.guild.ownerId) return true
- // Check if the user has moderate_members permission
- if (! member.permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission";
- // Check if the user is below on the role list
- if (! (memberPos > applyPos)) throw "You do not have a role higher than that member"
- // Allow UNNAMED
- return true
-}
-
-export { command, callback, check };
\ No newline at end of file
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 715777e..46e2871 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -16,7 +16,7 @@
.addChoices([["Yes", "yes"], ["No", "no"]])
)
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
// @ts-ignore
const { log, NucleusColors, renderUser, entry } = interaction.client.logger
// TODO:[Modals] Replace this with a modal
@@ -32,10 +32,8 @@
.setColor("Danger")
.addCustomBoolean(
"Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
- async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.client),
+ async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.options.getString("reason")),
"An appeal ticket will be created when Confirm is clicked")
-// pluralize("day", interaction.options.getInteger("delete"))
-// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" }
.send()
if (confirmation.success) {
let dmd = false
diff --git a/src/commands/settings/automation.ts b/src/commands/settings/automation.ts
deleted file mode 100644
index 0053f76..0000000
--- a/src/commands/settings/automation.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
- .setName("automation")
- .setDescription("Shows all automation options")
-
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/automation]");
-}
-
-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/settings/log/channel.ts b/src/commands/settings/log/channel.ts
deleted file mode 100644
index 5843438..0000000
--- a/src/commands/settings/log/channel.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
- .setName("channel")
- .setDescription("Sets the log channel")
-
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/log/channel]");
-}
-
-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/settings/log/ignore.ts b/src/commands/settings/log/ignore.ts
deleted file mode 100644
index 1b4d245..0000000
--- a/src/commands/settings/log/ignore.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
- .setName("ignore")
- .setDescription("Sets which users, channels and roles should be ignored")
-
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/log/ignore]");
-}
-
-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/settings/log/ignored.ts b/src/commands/settings/log/ignored.ts
deleted file mode 100644
index bf4a30c..0000000
--- a/src/commands/settings/log/ignored.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
- .setName("ignored")
- .setDescription("Gets the ignored users, channels and roles")
-
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/log/ignored]");
-}
-
-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/settings/log/_meta.ts b/src/commands/settings/logs/_meta.ts
similarity index 100%
rename from src/commands/settings/log/_meta.ts
rename to src/commands/settings/logs/_meta.ts
diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts
new file mode 100644
index 0000000..18ed8d9
--- /dev/null
+++ b/src/commands/settings/logs/channel.ts
@@ -0,0 +1,129 @@
+import { ChannelType } from 'discord-api-types';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import generateEmojiEmbed 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";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+ builder
+ .setName("channel")
+ .setDescription("Sets or shows the log channel")
+ .addChannelOption(option => option.setName("channel").setDescription("The channel to set the log channel to").addChannelTypes([
+ ChannelType.GuildNews, ChannelType.GuildText
+ ]))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ let m;
+ m = await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Loading")
+ .setStatus("Danger")
+ .setEmoji("NUCLEUS.LOADING")
+ ], ephemeral: true, fetchReply: true});
+ if (interaction.options.getChannel("channel")) {
+ let channel
+ try {
+ channel = interaction.options.getChannel("channel")
+ } catch {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ .setTitle("Log Channel")
+ .setDescription("The channel you provided is not a valid channel")
+ .setStatus("Danger")
+ ]})
+ }
+ channel = channel as Discord.TextChannel
+ if (channel.guild.id != interaction.guild.id) {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Log Channel")
+ .setDescription(`You must choose a channel in this server`)
+ .setStatus("Danger")
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ ]});
+ }
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("CHANNEL.TEXT.EDIT")
+ .setTitle("Log Channel")
+ .setDescription(`Are you sure you want to set the log channel to <#${channel.id}>?`)
+ .setColor("Warning")
+ .setInverted(true)
+ .send(true)
+ if (confirmation.success) {
+ try {
+ await client.database.write(interaction.guild.id, {"logging.logs.channel": channel.id})
+ } catch (e) {
+ console.log(e)
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Log Channel")
+ .setDescription(`Something went wrong and the log channel could not be set`)
+ .setStatus("Danger")
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ ], components: []});
+ }
+ } else {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Log Channel")
+ .setDescription(`No changes were made`)
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ ], components: []});
+ }
+ }
+ let clicks = 0;
+ let data = await client.database.read(interaction.guild.id);
+ let channel = data.logging.logs.channel;
+ while (true) {
+ await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Log channel")
+ .setDescription(channel ? `Your log channel is currently set to <#${channel}>` : "This server does not have a log channel")
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setCustomId("clear")
+ .setLabel(clicks ? "Click again to confirm" : "Reset channel")
+ .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
+ .setStyle("DANGER")
+ .setDisabled(!channel)
+ ])]});
+ let i;
+ try {
+ i = await m.awaitMessageComponent({time: 600000});
+ } catch(e) { break }
+ i.deferUpdate()
+ if (i.component.customId == "clear") {
+ clicks += 1;
+ if (clicks == 2) {
+ clicks = 0;
+ await client.database.write(interaction.guild.id, {}, ["logging.logs.channel"])
+ channel = undefined;
+ }
+ } else {
+ break
+ }
+ }
+ await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Log channel")
+ .setDescription(channel ? `Your log channel is currently set to <#${channel}>` : "This server does not have a log channel")
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ .setFooter({text: "Message closed"})
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setCustomId("clear")
+ .setLabel("Clear")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setStyle("SECONDARY")
+ .setDisabled(true)
+ ])]});
+}
+
+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 };
diff --git a/src/commands/settings/log/events.ts b/src/commands/settings/logs/events.ts
similarity index 100%
rename from src/commands/settings/log/events.ts
rename to src/commands/settings/logs/events.ts
diff --git a/src/commands/settings/logs/ignore.ts b/src/commands/settings/logs/ignore.ts
new file mode 100644
index 0000000..4b66307
--- /dev/null
+++ b/src/commands/settings/logs/ignore.ts
@@ -0,0 +1,119 @@
+import { ChannelType } from 'discord-api-types';
+import Discord, { CommandInteraction } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import generateEmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import { WrappedCheck } from "jshaiku";
+import confirmationMessage from '../../../utils/confirmationMessage.js';
+import keyValueList from '../../../utils/generateKeyValueList.js';
+import client from '../../../utils/client.js';
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+ builder
+ .setName("ignore")
+ .setDescription("Sets which users, channels and roles should be ignored")
+ .addStringOption(o => o.setName("action").setDescription("Add or remove from the list").addChoices([
+ ["Add", "add"], ["Remove", "remove"]
+ ]).setRequired(true))
+ .addChannelOption(o => o.setName("addchannel").setDescription("Add a channel that should be ignored").addChannelTypes([
+ ChannelType.GuildText, ChannelType.GuildVoice, ChannelType.GuildNews, ChannelType.GuildPublicThread, ChannelType.GuildPrivateThread, ChannelType.GuildNewsThread
+ ]))
+ .addUserOption(o => o.setName("adduser").setDescription("Add a user that should be ignored"))
+ .addRoleOption(o => o.setName("addrole").setDescription("Add a role that should be ignored"))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ let channel = interaction.options.getChannel("addchannel")
+ let user = interaction.options.getUser("adduser")
+ let role = interaction.options.getRole("addrole")
+ await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Loading")
+ .setStatus("Danger")
+ .setEmoji("NUCLEUS.LOADING")
+ ], ephemeral: true, fetchReply: true});
+ if (channel || user || role) {
+ if (channel) {
+ try {
+ channel = interaction.guild.channels.cache.get(channel.id)
+ } catch {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ .setTitle("Logs > Ignore")
+ .setDescription("The channel you provided is not a valid channel")
+ .setStatus("Danger")
+ ]})
+ }
+ channel = channel as Discord.TextChannel
+ if (channel.guild.id != interaction.guild.id) {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Logs > Ignore")
+ .setDescription(`You must choose a channel in this server`)
+ .setStatus("Danger")
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ ]});
+ }
+ }
+ if (user) {
+ try {
+ user = interaction.guild.members.cache.get(user.id).user
+ } catch {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setEmoji("USER.DELETE")
+ .setTitle("Logs > Ignore")
+ .setDescription("The user you provided is not a valid user")
+ .setStatus("Danger")
+ ]})
+ }
+ user = user as Discord.User
+ }
+ if (role) {
+ try {
+ role = interaction.guild.roles.cache.get(role.id)
+ } catch {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setEmoji("ROLE.DELETE")
+ .setTitle("Logs > Ignore")
+ .setDescription("The role you provided is not a valid role")
+ .setStatus("Danger")
+ ]})
+ }
+ role = role as Discord.Role
+ if (role.guild.id != interaction.guild.id) {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Logs > Ignore")
+ .setDescription(`You must choose a role in this server`)
+ .setStatus("Danger")
+ .setEmoji("ROLE.DELETE")
+ ]});
+ }
+ }
+ let changes = {}
+ if (channel) changes["channel"] = channel.id
+ if (user) changes["user"] = user.id
+ if (role) changes["role"] = role.id
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("NUCLEUS.COMMANDS.IGNORE")
+ .setTitle("Logs > Ignore")
+ .setDescription(keyValueList(changes)
+ + `Are you sure you want to **${interaction.options.getString("action") == "add" ? "add" : "remove"}** these to the ignore list?`)
+ .setColor("Warning")
+ .send(true)
+ if (confirmation.success) {
+ let data = client.database.read(interaction.guild.id)
+ if (channel) data.logging.logs.ignore.channels.concat([channel.id])
+ if (user) data.logging.logs.ignore.users.concat([user.id])
+ if (role) data.logging.logs.ignore.roles.concat([role.id])
+ if (interaction.options.getString("action") == "add") {
+ await client.database.append(interaction.guild.id, data)
+ }
+ }
+ }
+}
+
+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/menu.ts b/src/commands/settings/menu.ts
deleted file mode 100644
index 9950fe4..0000000
--- a/src/commands/settings/menu.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { CommandInteraction, MessageEmbed, MessageSelectMenu } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-import readConfig from "../../utils/readConfig.js";
-import { toHexArray, toHexInteger, logs } from "../../utils/calculate.js"
-import { capitalize } from "../../utils/generateKeyValueList.js";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
- .setName("menu")
- .setDescription("Shows a full UI of all settings")
-
-const callback = async (interaction: CommandInteraction) => {
- return
- let config = await readConfig(interaction.guild.id);
-
- let currentValues = toHexArray(config.logging.log.toLog);
-
- let toLogDropdownOptions = []
-
- for(let i of logs) {
- if(currentValues.includes(i)) {
- toLogDropdownOptions.push({
- name: capitalize(i),
- value: i,
- emoji: "TICK"
- })
- } else {
- toLogDropdownOptions.push({
- label: capitalize(i),
- value: i,
- emoji: "CROSS"
- })
- }
- }
-
- let toLogDropdown = new MessageSelectMenu()
- .setCustomId("log")
- .setMaxValues(22)
- .addOptions()
-
- let embed = new MessageEmbed()
-
- interaction.reply("This command is not yet finished [settings/all]");
-}
-
-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/settings/mod/channel.ts b/src/commands/settings/mod/channel.ts
deleted file mode 100644
index 88c8396..0000000
--- a/src/commands/settings/mod/channel.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
- .setName("channel")
- .setDescription("Sets the channel for staff messages to go to")
-
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/mod/channel]");
-}
-
-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/settings/mod/events.ts b/src/commands/settings/mod/events.ts
deleted file mode 100644
index be5de57..0000000
--- a/src/commands/settings/mod/events.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
- .setName("events")
- .setDescription("Sets which events mods should be notified about")
-
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/mod/events]");
-}
-
-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/settings/mod/_meta.ts b/src/commands/settings/staff/_meta.ts
similarity index 100%
rename from src/commands/settings/mod/_meta.ts
rename to src/commands/settings/staff/_meta.ts
diff --git a/src/commands/settings/staff/channel.ts b/src/commands/settings/staff/channel.ts
new file mode 100644
index 0000000..69ace92
--- /dev/null
+++ b/src/commands/settings/staff/channel.ts
@@ -0,0 +1,131 @@
+import { ChannelType } from 'discord-api-types';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import generateEmojiEmbed 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";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+ builder
+ .setName("channel")
+ .setDescription("Sets or shows the staff notifications channel")
+ .addChannelOption(option => option.setName("channel").setDescription("The channel to set the staff notifications channel to").addChannelTypes([
+ ChannelType.GuildNews, ChannelType.GuildText
+ ]))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ let m;
+ m = await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Loading")
+ .setStatus("Danger")
+ .setEmoji("NUCLEUS.LOADING")
+ ], ephemeral: true, fetchReply: true});
+ if (interaction.options.getChannel("channel")) {
+ let channel
+ try {
+ channel = interaction.options.getChannel("channel")
+ } catch {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ .setTitle("Staff Notifications Channel")
+ .setDescription("The channel you provided is not a valid channel")
+ .setStatus("Danger")
+ ]})
+ }
+ channel = channel as Discord.TextChannel
+ if (channel.guild.id != interaction.guild.id) {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Staff Notifications Channel")
+ .setDescription(`You must choose a channel in this server`)
+ .setStatus("Danger")
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ ]});
+ }
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("CHANNEL.TEXT.EDIT")
+ .setTitle("Staff Notifications Channel")
+ .setDescription(
+ `This will be the channel all notifications, updates, user reports etc. will be sent to.\n\n` +
+ `Are you sure you want to set the staff notifications channel to <#${channel.id}>?`
+ )
+ .setColor("Warning")
+ .setInverted(true)
+ .send(true)
+ if (confirmation.success) {
+ try {
+ await client.database.write(interaction.guild.id, {"logging.staff.channel": channel.id})
+ } catch (e) {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Staff Notifications Channel")
+ .setDescription(`Something went wrong and the staff notifications channel could not be set`)
+ .setStatus("Danger")
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ ], components: []});
+ }
+ } else {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Staff Notifications Channel")
+ .setDescription(`No changes were made`)
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ ], components: []});
+ }
+ }
+ let clicks = 0;
+ let data = await client.database.read(interaction.guild.id);
+ let channel = data.logging.staff.channel;
+ while (true) {
+ await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Staff Notifications channel")
+ .setDescription(channel ? `Your staff notifications channel is currently set to <#${channel}>` : "This server does not have a staff notifications channel")
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setCustomId("clear")
+ .setLabel(clicks ? "Click again to confirm" : "Reset channel")
+ .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
+ .setStyle("DANGER")
+ .setDisabled(!channel)
+ ])]});
+ let i;
+ try {
+ i = await m.awaitMessageComponent({time: 600000});
+ } catch(e) { break }
+ i.deferUpdate()
+ if (i.component.customId == "clear") {
+ clicks += 1;
+ if (clicks == 2) {
+ clicks = 0;
+ await client.database.write(interaction.guild.id, {}, ["logging.staff.channel"])
+ channel = undefined;
+ }
+ } else {
+ break
+ }
+ }
+ await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Staff Notifications channel")
+ .setDescription(channel ? `Your staff notifications channel is currently set to <#${channel}>` : "This server does not have a staff notifications channel")
+ .setStatus("Success")
+ .setEmoji("CHANNEL.TEXT.CREATE")
+ .setFooter({text: "Message closed"})
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setCustomId("clear")
+ .setLabel("Clear")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setStyle("SECONDARY")
+ .setDisabled(true)
+ ])]});
+}
+
+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 };
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index 64dc980..aac271e 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -1,24 +1,410 @@
-import { CommandInteraction } from "discord.js";
-import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import Discord, { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu, TextInputComponent } from "discord.js";
+import { SelectMenuOption, SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
import { ChannelType } from 'discord-api-types';
+import client from "../../utils/client.js";
+import { toHexInteger, toHexArray, tickets as ticketTypes } from "../../utils/calculate.js";
+import { capitalize } from '../../utils/generateKeyValueList.js';
+import { modalInteractionCollector } from "../../utils/dualCollector.js";
-const command = (builder: SlashCommandSubcommandBuilder) =>
- builder
+const command = (builder: SlashCommandSubcommandBuilder) => builder
.setName("tickets")
- .setDescription("Shows settings for tickets")
- .addStringOption(option => option.setName("enabled").setDescription("If users should be able to create tickets | Default yes").setRequired(false)
+ .setDescription("Shows settings for tickets | Use no arguments to manage custom types")
+ .addStringOption(option => option.setName("enabled").setDescription("If users should be able to create tickets").setRequired(false)
.addChoices([["Yes", "yes"], ["No", "no"]]))
.addChannelOption(option => option.setName("category").setDescription("The category where tickets are created").addChannelType(ChannelType.GuildCategory).setRequired(false))
- .addNumberOption(option => option.setName("maxtickets").setDescription("The maximum amount of tickets a user can create | Default 5").setRequired(false).setMinValue(1))
- .addRoleOption(option => option.setName("supportping").setDescription("The role pinged when a ticket is created").setRequired(false))
+ .addNumberOption(option => option.setName("maxticketsperuser").setDescription("The maximum amount of tickets a user can create | Default 5").setRequired(false).setMinValue(1))
+ .addRoleOption(option => option.setName("supportrole").setDescription("This role will have view access to all tickets and will be pinged when a ticket is created").setRequired(false))
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/tickets]");
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ let m;
+ m = await interaction.reply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Loading")
+ .setStatus("Danger")
+ .setEmoji("NUCLEUS.LOADING")
+ ], ephemeral: true, fetchReply: true
+ });
+ let options = {
+ enabled: interaction.options.getString("enabled") as string | boolean,
+ category: interaction.options.getChannel("category"),
+ maxtickets: interaction.options.getNumber("maxticketsperuser"),
+ supportping: interaction.options.getRole("supportrole")
+ }
+ if (options.enabled !== null || options.category || options.maxtickets || options.supportping) {
+ options.enabled = options.enabled === "yes" ? true : false;
+ if (options.category) {
+ let channel
+ try {
+ channel = interaction.guild.channels.cache.get(options.category.id)
+ } catch {
+ return await interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ .setTitle("Tickets > Category")
+ .setDescription("The channel you provided is not a valid category")
+ .setStatus("Danger")
+ ]
+ })
+ }
+ channel = channel as Discord.CategoryChannel
+ if (channel.guild.id != interaction.guild.id) return interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Tickets > Category")
+ .setDescription(`You must choose a category in this server`)
+ .setStatus("Danger")
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ ]
+ });
+ }
+ if (options.maxtickets) {
+ if (options.maxtickets < 1) return interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Tickets > Max Tickets")
+ .setDescription(`You must choose a number greater than 0`)
+ .setStatus("Danger")
+ .setEmoji("CHANNEL.TEXT.DELETE")
+ ]
+ });
+ }
+ let role
+ if (options.supportping) {
+ try {
+ role = interaction.guild.roles.cache.get(options.supportping.id)
+ } catch {
+ return await interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setEmoji("GUILD.ROLE.DELETE")
+ .setTitle("Tickets > Support Ping")
+ .setDescription("The role you provided is not a valid role")
+ .setStatus("Danger")
+ ]
+ })
+ }
+ role = role as Discord.Role
+ if (role.guild.id != interaction.guild.id) return interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Tickets > Support Ping")
+ .setDescription(`You must choose a role in this server`)
+ .setStatus("Danger")
+ .setEmoji("GUILD.ROLE.DELETE")
+ ]
+ });
+ }
+
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("GUILD.TICKET.ARCHIVED")
+ .setTitle("Tickets")
+ .setDescription(
+ (options.category ? `**Category:** ${options.category.name}\n` : "") +
+ (options.maxtickets ? `**Max Tickets:** ${options.maxtickets}\n` : "") +
+ (options.supportping ? `**Support Ping:** ${options.supportping.name}\n` : "") +
+ (options.enabled !== null ? `**Enabled:** ${options.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`
+ }\n` : "") +
+ `\nAre you sure you want to apply these settings?`
+ )
+ .setColor("Warning")
+ .setInverted(true)
+ .send(true)
+ if (confirmation.success) {
+ let toUpdate = {}
+ if (options.enabled !== null) toUpdate["tickets.enabled"] = options.enabled
+ if (options.category) toUpdate["tickets.category"] = options.category.id
+ if (options.maxtickets) toUpdate["tickets.maxTickets"] = options.maxtickets
+ if (options.supportping) toUpdate["tickets.supportRole"] = options.supportping.id
+ try {
+ await client.database.write(interaction.guild.id, toUpdate)
+ } catch (e) {
+ return interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Tickets")
+ .setDescription(`Something went wrong and the staff notifications channel could not be set`)
+ .setStatus("Danger")
+ .setEmoji("GUILD.TICKET.DELETE")
+ ], components: []
+ });
+ }
+ } else {
+ return interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Tickets")
+ .setDescription(`No changes were made`)
+ .setStatus("Success")
+ .setEmoji("GUILD.TICKET.OPEN")
+ ], components: []
+ });
+ }
+ }
+ let data = await client.database.read(interaction.guild.id);
+ data.tickets.customTypes = data.tickets.customTypes.filter((v, i, a) => a.indexOf(v) === i)
+ let lastClicked = "";
+ let embed;
+ data = {
+ enabled: data.tickets.enabled,
+ category: data.tickets.category,
+ maxTickets: data.tickets.maxTickets,
+ supportRole: data.tickets.supportRole,
+ useCustom: data.tickets.useCustom,
+ types: data.tickets.types,
+ customTypes: data.tickets.customTypes
+ }
+ while (true) {
+ embed = new generateEmojiEmbed()
+ .setTitle("Tickets")
+ .setDescription(
+ `${data.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${data.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No`}\n` +
+ `${data.category ? "" : getEmojiByName("TICKETS.REPORT")} **Category:** ${data.category ? `<#${data.category}>` : "*None set*"}\n` +
+ `**Max Tickets:** ${data.maxTickets ? data.maxTickets : "*No limit*"}\n` +
+ `**Support Ping:** ${data.supportRole ? `<@&${data.supportRole}>` : "*None set*"}\n\n` +
+ ((data.useCustom && data.customTypes === null) ? `${getEmojiByName("TICKETS.REPORT")} ` : "") +
+ `${data.useCustom ? "Custom" : "Default"} types in use` + "\n\n" +
+ `${getEmojiByName("TICKETS.REPORT")} *Indicates a setting stopping tickets from being used*`
+ )
+ .setStatus("Success")
+ .setEmoji("GUILD.TICKET.OPEN")
+ m = await interaction.editReply({
+ embeds: [embed], components: [new MessageActionRow().addComponents([
+ new MessageButton()
+ .setLabel("Tickets " + (data.enabled ? "enabled" : "disabled"))
+ .setEmoji(getEmojiByName("CONTROL." + (data.enabled ? "TICK" : "CROSS"), "id"))
+ .setStyle(data.enabled ? "SUCCESS" : "DANGER")
+ .setCustomId("enabled"),
+ new MessageButton()
+ .setLabel(lastClicked == "cat" ? "Click again to confirm" : "Clear category")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setStyle("DANGER")
+ .setCustomId("clearCategory")
+ .setDisabled(data.category == null),
+ new MessageButton()
+ .setLabel(lastClicked == "max" ? "Click again to confirm" : "Reset max tickets")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setStyle("DANGER")
+ .setCustomId("clearMaxTickets")
+ .setDisabled(data.maxTickets == 5),
+ new MessageButton()
+ .setLabel(lastClicked == "sup" ? "Click again to confirm" : "Clear support ping")
+ .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+ .setStyle("DANGER")
+ .setCustomId("clearSupportPing")
+ .setDisabled(data.supportRole == null),
+ ]), new MessageActionRow().addComponents([
+ new MessageButton()
+ .setLabel("Manage types")
+ .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
+ .setStyle("SECONDARY")
+ .setCustomId("manageTypes"),
+ ])]
+ });
+ let i;
+ try {
+ i = await m.awaitMessageComponent({ time: 600000 });
+ } catch (e) { break }
+ i.deferUpdate()
+ if (i.component.customId == "clearCategory") {
+ if (lastClicked == "cat") {
+ lastClicked = "";
+ await client.database.write(interaction.guild.id, {}, ["tickets.category"])
+ data.category = undefined;
+ } else lastClicked = "cat";
+ } else if (i.component.customId == "clearMaxTickets") {
+ if (lastClicked == "max") {
+ lastClicked = "";
+ await client.database.write(interaction.guild.id, {}, ["tickets.maxTickets"])
+ data.maxTickets = 5;
+ } else lastClicked = "max";
+ } else if (i.component.customId == "clearSupportPing") {
+ if (lastClicked == "sup") {
+ lastClicked = "";
+ await client.database.write(interaction.guild.id, {}, ["tickets.supportRole"])
+ data.supportRole = undefined;
+ } else lastClicked = "sup";
+ } else if (i.component.customId == "enabled") {
+ await client.database.write(interaction.guild.id, { "tickets.enabled": !data.enabled })
+ data.enabled = !data.enabled;
+ } else if (i.component.customId == "manageTypes") {
+ data = await manageTypes(interaction, data, m);
+ } else {
+ break
+ }
+ }
+ await interaction.editReply({ embeds: [embed.setFooter({ text: "Message closed" })], components: [] });
}
+async function manageTypes(interaction, data, m) {
+ while (true) {
+ if (data.useCustom) {
+ let customTypes = data.customTypes;
+ interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Tickets > Types")
+ .setDescription(
+ "**Custom types enabled**\n\n" +
+ "**Types in use:**\n" + ((customTypes !== null) ?
+ (customTypes.map((t) => `> ${t}`).join("\n")) :
+ "*None set*"
+ ) + "\n\n" + (customTypes === null ?
+ `${getEmojiByName("TICKETS.REPORT")} Having no types will disable tickets. Please add at least 1 type or use default types` : ""
+ )
+ )
+ .setStatus("Success")
+ .setEmoji("GUILD.TICKET.OPEN")
+ ], components: (customTypes ? [
+ new MessageActionRow().addComponents([new Discord.MessageSelectMenu()
+ .setCustomId("removeTypes")
+ .setPlaceholder("Select types to remove")
+ .setMaxValues(customTypes.length)
+ .setMinValues(1)
+ .addOptions(customTypes.map((t) => new SelectMenuOption().setLabel(t).setValue(t)))
+ ])
+ ] : []).concat([
+ new MessageActionRow().addComponents([
+ new MessageButton()
+ .setLabel("Back")
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+ .setStyle("PRIMARY")
+ .setCustomId("back"),
+ new MessageButton()
+ .setLabel("Add new type")
+ .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+ .setStyle("PRIMARY")
+ .setCustomId("addType")
+ .setDisabled(customTypes !== null && customTypes.length >= 25),
+ new MessageButton()
+ .setLabel("Switch to default types")
+ .setStyle("SECONDARY")
+ .setCustomId("switchToDefault"),
+ ])
+ ])
+ });
+ } else {
+ let inUse = toHexArray(data.types, ticketTypes)
+ let options = [];
+ ticketTypes.forEach(type => {
+ options.push(new SelectMenuOption({
+ label: capitalize(type),
+ value: type,
+ emoji: interaction.client.emojis.cache.get(getEmojiByName(`TICKETS.${type.toUpperCase()}`, "id")),
+ default: inUse.includes(type)
+ }))
+ })
+ let selectPane = new MessageActionRow().addComponents([
+ new Discord.MessageSelectMenu()
+ .addOptions(options)
+ .setCustomId("types")
+ .setMaxValues(ticketTypes.length)
+ .setMinValues(1)
+ .setPlaceholder("Select types to use")
+ ])
+ interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Tickets > Types")
+ .setDescription(
+ "**Default types enabled**\n\n" +
+ "**Types in use:**\n" +
+ (inUse.map((t) => `> ${getEmojiByName("TICKETS." + t.toUpperCase())} ${capitalize(t)}`).join("\n"))
+ )
+ .setStatus("Success")
+ .setEmoji("GUILD.TICKET.OPEN")
+ ], components: [
+ selectPane,
+ new MessageActionRow().addComponents([
+ new MessageButton()
+ .setLabel("Back")
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+ .setStyle("PRIMARY")
+ .setCustomId("back"),
+ new MessageButton()
+ .setLabel("Switch to custom types")
+ .setStyle("SECONDARY")
+ .setCustomId("switchToCustom"),
+ ])
+ ]
+ });
+ }
+ let i;
+ try {
+ i = await m.awaitMessageComponent({ time: 600000 });
+ } catch (e) { break }
+ if (i.component.customId == "types") {
+ i.deferUpdate()
+ let types = toHexInteger(i.values, ticketTypes);
+ await client.database.write(interaction.guild.id, { "tickets.types": types })
+ data.types = types;
+ } else if (i.component.customId == "removeTypes") {
+ i.deferUpdate()
+ let types = i.values
+ let customTypes = data.customTypes;
+ if (customTypes) {
+ customTypes = customTypes.filter((t) => !types.includes(t));
+ customTypes = customTypes.length > 0 ? customTypes : null;
+ await client.database.write(interaction.guild.id, { "tickets.customTypes": customTypes })
+ data.customTypes = customTypes;
+ }
+ } else if (i.component.customId == "addType") {
+ await i.showModal(new Discord.Modal().setCustomId("modal").setTitle("Enter a name for the new type").addComponents(
+ // @ts-ignore
+ new MessageActionRow().addComponents(new TextInputComponent()
+ .setCustomId("type")
+ .setLabel("Name")
+ .setMaxLength(100)
+ .setMinLength(1)
+ .setPlaceholder("E.g. \"Server Idea\"")
+ .setRequired(true)
+ .setStyle("SHORT")
+ )
+ ))
+ await interaction.editReply({
+ embeds: [new generateEmojiEmbed()
+ .setTitle("Tickets > Types")
+ .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 == "addType")
+ } catch (e) { continue }
+ if (out.fields) {
+ let toAdd = out.fields.getTextInputValue("type");
+ if (!toAdd) { continue }
+ try {
+ await client.database.append(interaction.guild.id, "tickets.customTypes", toAdd)
+ } catch { continue }
+ data.customTypes = data.customTypes || [];
+ if (!data.customTypes.includes(toAdd)) {
+ data.customTypes.push(toAdd);
+ }
+ } else { continue }
+ } else if (i.component.customId == "switchToDefault") {
+ i.deferUpdate()
+ await client.database.write(interaction.guild.id, { "tickets.useCustom": false }, [])
+ data.useCustom = false;
+ } else if (i.component.customId == "switchToCustom") {
+ i.deferUpdate()
+ await client.database.write(interaction.guild.id, { "tickets.useCustom": true }, [])
+ data.useCustom = true;
+ } else {
+ i.deferUpdate()
+ break
+ }
+ }
+ return data
+}
+
+
const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
- return interaction.memberPermissions.has("MANAGE_GUILD");
+ 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 };
diff --git a/src/commands/settings/verify/role.ts b/src/commands/settings/verify/role.ts
index c4de7af..dc1d4d9 100644
--- a/src/commands/settings/verify/role.ts
+++ b/src/commands/settings/verify/role.ts
@@ -1,20 +1,126 @@
-import { CommandInteraction } from "discord.js";
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import generateEmojiEmbed 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";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
.setName("role")
- .setDescription("Sets the role given after verifying")
+ .setDescription("Sets or shows the role given to users after using /verify")
+ .addRoleOption(option => option.setName("role").setDescription("The role to give after verifying"))
-const callback = (interaction: CommandInteraction) => {
- interaction.reply("This command is not yet finished [settings/verify/role]");
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ let m;
+ m = await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Loading")
+ .setStatus("Danger")
+ .setEmoji("NUCLEUS.LOADING")
+ ], ephemeral: true, fetchReply: true});
+ if (interaction.options.getRole("role")) {
+ let role
+ try {
+ role = interaction.options.getRole("role")
+ } catch {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setEmoji("GUILD.ROLES.DELETE")
+ .setTitle("Verify Role")
+ .setDescription("The role you provided is not a valid role")
+ .setStatus("Danger")
+ ]})
+ }
+ role = role as Discord.Role
+ if (role.guild.id != interaction.guild.id) {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Verify Role")
+ .setDescription(`You must choose a role in this server`)
+ .setStatus("Danger")
+ .setEmoji("GUILD.ROLES.DELETE")
+ ]});
+ }
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("GUILD.ROLES.EDIT")
+ .setTitle("Verify Role")
+ .setDescription(`Are you sure you want to set the verify role to <@&${role.id}>?`)
+ .setColor("Warning")
+ .setInverted(true)
+ .send(true)
+ if (confirmation.success) {
+ try {
+ await client.database.write(interaction.guild.id, {"verify.role": role.id, "verify.enabled": true});
+ } catch (e) {
+ console.log(e)
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Verify Role")
+ .setDescription(`Something went wrong while setting the verify role`)
+ .setStatus("Danger")
+ .setEmoji("GUILD.ROLES.DELETE")
+ ], components: []});
+ }
+ } else {
+ return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Verify Role")
+ .setDescription(`No changes were made`)
+ .setStatus("Success")
+ .setEmoji("GUILD.ROLES.CREATE")
+ ], components: []});
+ }
+ }
+ let clicks = 0;
+ let data = await client.database.read(interaction.guild.id);
+ let role = data.verify.role;
+ while (true) {
+ await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Verify Role")
+ .setDescription(role ? `Your verify role is currently set to <@&${role}>` : `You have not set a verify role`)
+ .setStatus("Success")
+ .setEmoji("GUILD.ROLES.CREATE")
+ ], components: [new MessageActionRow().addComponents([new MessageButton()
+ .setCustomId("clear")
+ .setLabel(clicks ? "Click again to confirm" : "Reset role")
+ .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
+ .setStyle("DANGER")
+ .setDisabled(!role)
+ ])]});
+ let i;
+ try {
+ i = await m.awaitMessageComponent({time: 600000});
+ } catch(e) { break }
+ i.deferUpdate()
+ if (i.component.customId == "clear") {
+ clicks += 1;
+ if (clicks == 2) {
+ clicks = 0;
+ await client.database.write(interaction.guild.id, {}, ["verify.role", "verify.enabled"])
+ role = undefined;
+ }
+ } else {
+ break
+ }
+ }
+ await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .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)
+ ])]});
}
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
+export { check };
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index a5fcfc1..8d6d8c9 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -1,12 +1,14 @@
import { CommandInteraction } from "discord.js";
import { SlashCommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
+import { callback as statsChannelAdd } from '../automations/statsChannelAdd.js';
const command = new SlashCommandBuilder()
.setName("tag")
.setDescription("Get and manage the servers tags")
const callback = (interaction: CommandInteraction) => {
+ try { statsChannelAdd(interaction.client, interaction.member); } catch {} // TODO: REMOVE THIS FOR PRODUCTION
interaction.reply("This command is not yet finished [tag]");
}
diff --git a/src/commands/tags/_meta.ts b/src/commands/tags/_meta.ts
new file mode 100644
index 0000000..8c07682
--- /dev/null
+++ b/src/commands/tags/_meta.ts
@@ -0,0 +1,4 @@
+const name = "tags";
+const description = "manage server tags";
+
+export { name, description };
\ No newline at end of file
diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts
new file mode 100644
index 0000000..e53f94f
--- /dev/null
+++ b/src/commands/tags/create.ts
@@ -0,0 +1,87 @@
+import Discord, { CommandInteraction } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import client from "../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+ builder
+ .setName("create")
+ .setDescription("Creates a tag")
+ .addStringOption(o => o.setName("name").setRequired(true).setDescription("The name of the tag"))
+ .addStringOption(o => o.setName("value").setRequired(true).setDescription("The value of the tag, shown after running /tag name"))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ let name = interaction.options.getString("name");
+ let value = interaction.options.getString("value");
+ if (name.length > 100) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Create")
+ .setDescription("Tag names cannot be longer than 100 characters")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ if (value.length > 1000) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Create")
+ .setDescription("Tag values cannot be longer than 1000 characters")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ let data = await client.database.read(interaction.guild.id);
+ if (data.tags.length >= 100) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Create")
+ .setDescription("You cannot have more than 100 tags")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ if (data.tags[name]) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Create")
+ .setDescription("That tag already exists")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.NICKNAME.YELLOW")
+ .setTitle("Tag create")
+ .setDescription(keyValueList({
+ "name": `${name}`,
+ "value": `\n> ${value}`
+ })
+ + `\nAre you sure you want to create this tag?`)
+ .setColor("Warning")
+ .setInverted(true)
+ .send()
+ if (!confirmation) return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Create")
+ .setDescription("No changes were made")
+ .setStatus("Success")
+ .setEmoji("PUNISH.NICKNAME.GREEN")
+ ]});
+ try {
+ await client.database.write(interaction.guild.id, {[`tags.${name}`]: value});
+ } catch (e) {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Create")
+ .setDescription("Something went wrong and the tag was not created")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], components: []});
+ }
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Create")
+ .setDescription("Tag created")
+ .setStatus("Success")
+ .setEmoji("PUNISH.NICKNAME.GREEN")
+ ], components: []});
+}
+
+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_messages` permission to use this command"
+ return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/tags/delete.ts b/src/commands/tags/delete.ts
new file mode 100644
index 0000000..ff54ee5
--- /dev/null
+++ b/src/commands/tags/delete.ts
@@ -0,0 +1,69 @@
+import Discord, { CommandInteraction } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import client from "../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+ builder
+ .setName("delete")
+ .setDescription("Deletes a tag")
+ .addStringOption(o => o.setName("name").setRequired(true).setDescription("The name of the tag"))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ let name = interaction.options.getString("name");
+ let data = await client.database.read(interaction.guild.id);
+ if (!data.tags[name]) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tags")
+ .setDescription("That tag does not exist")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.NICKNAME.YELLOW")
+ .setTitle("Tag Delete")
+ .setDescription(keyValueList({
+ "name": `${name}`,
+ "value": `\n> ${data.tags[name]}`
+ })
+ + `\nAre you sure you want to delete this tag?`)
+ .setColor("Warning")
+ .setInverted(true)
+ .send()
+ if (!confirmation) return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Delete")
+ .setDescription("No changes were made")
+ .setStatus("Success")
+ .setEmoji("PUNISH.NICKNAME.GREEN")
+ ]});
+ try {
+ data = await client.database.read(interaction.guild.id);
+ delete data.tags[name];
+ await client.database.write(interaction.guild.id, {tags: data});
+ } catch (e) {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Delete")
+ .setDescription("Something went wrong and the tag was not deleted")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], components: []});
+ }
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Delete")
+ .setDescription("Tag deleted")
+ .setStatus("Success")
+ .setEmoji("PUNISH.NICKNAME.GREEN")
+ ], components: []});
+}
+
+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_messages` permission to use this command"
+ return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/tags/edit.ts b/src/commands/tags/edit.ts
new file mode 100644
index 0000000..3cf73e5
--- /dev/null
+++ b/src/commands/tags/edit.ts
@@ -0,0 +1,102 @@
+import Discord, { CommandInteraction } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import confirmationMessage from "../../utils/confirmationMessage.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import client from "../../utils/client.js";
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+ builder
+ .setName("edit")
+ .setDescription("Edits or renames a tag")
+ .addStringOption(o => o.setName("name").setRequired(true).setDescription("The tag to edit"))
+ .addStringOption(o => o.setName("value").setRequired(false).setDescription("The new value of the tag / Rename"))
+ .addStringOption(o => o.setName("newname").setRequired(false).setDescription("The new name of the tag / Edit"))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+ let name = interaction.options.getString("name");
+ let value = interaction.options.getString("value") || "";
+ let newname = interaction.options.getString("newname") || "";
+ if (!newname && !value) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Edit")
+ .setDescription("You must specify a value or a new name")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ if (newname.length > 100) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Edit")
+ .setDescription("Tag names cannot be longer than 100 characters")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ if (value.length > 2000) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Edit")
+ .setDescription("Tag values cannot be longer than 2000 characters")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ let data = await client.database.read(interaction.guild.id);
+ if (!data.tags[name]) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Edit")
+ .setDescription("That tag does not exist")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ if (newname && newname !== name && data.tags[newname]) return await interaction.reply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Edit")
+ .setDescription("A tag with that name already exists")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], ephemeral: true});
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.NICKNAME.YELLOW")
+ .setTitle("Tag Edit")
+ .setDescription(keyValueList({
+ "name": `${name}` + (newname ? ` -> ${newname}` : ""),
+ "value": `\n> ${value ? value : data.tags[name]}`
+ })
+ + `\nAre you sure you want to edit this tag?`)
+ .setColor("Warning")
+ .setInverted(true)
+ .send()
+ if (!confirmation) return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Edit")
+ .setDescription("No changes were made")
+ .setStatus("Success")
+ .setEmoji("PUNISH.NICKNAME.GREEN")
+ ]});
+ try {
+ let toSet = {};
+ let toUnset = []
+ if (value) toSet[`tags.${name}`] = value;
+ if (newname) {
+ toUnset.push(`tags.${name}`);
+ toSet[`tags.${newname}`] = data.tags[name];
+ }
+ await client.database.write(interaction.guild.id, toSet, toUnset);
+ } catch (e) {
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tag Edit")
+ .setDescription("Something went wrong and the tag was not edited")
+ .setStatus("Danger")
+ .setEmoji("PUNISH.NICKNAME.RED")
+ ], components: []});
+ }
+ return await interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Tags")
+ .setDescription("Tag edited successfully")
+ .setStatus("Success")
+ .setEmoji("PUNISH.NICKNAME.GREEN")
+ ], components: []});
+}
+
+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_messages` permission to use this command"
+ return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/tags/list.ts b/src/commands/tags/list.ts
new file mode 100644
index 0000000..bf234d5
--- /dev/null
+++ b/src/commands/tags/list.ts
@@ -0,0 +1,150 @@
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import keyValueList from "../../utils/generateKeyValueList.js";
+import client from "../../utils/client.js";
+import { SelectMenuOption } from '@discordjs/builders';
+import getEmojiByName from "../../utils/getEmojiByName.js";
+import createPageIndicator from "../../utils/createPageIndicator.js";
+
+
+class Embed {
+ embed: Discord.MessageEmbed;
+ title: string;
+ description: string = "";
+ pageId: number = 0;
+ setEmbed(embed: Discord.MessageEmbed) { this.embed = embed; return this; }
+ setTitle(title: string) { this.title = title; return this; }
+ setDescription(description: string) { this.description = description; return this; }
+ setPageId(pageId: number) { this.pageId = pageId; return this; }
+}
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+ builder
+ .setName("list")
+ .setDescription("Lists all tags in the server")
+
+const callback = async (interaction: CommandInteraction) => {
+ let data = await client.database.read(interaction.guild.id);
+ let tags = data.getKey("tags");
+ console.log(tags)
+ let strings = []
+ if (data === {}) strings = ["*No tags exist*"]
+ else {
+ let string = ""
+ for (let tag in tags) {
+ let proposed = `**${tag}:** ${tags[tag]}\n`
+ if (string.length + proposed.length > 2000) {
+ strings.push(string.slice(0, -1))
+ string = ""
+ }
+ console.log(string)
+ string += proposed
+ }
+ strings.push(string.slice(0, -1))
+ }
+
+ let pages = []
+ for (let string of strings) {
+ pages.push(new Embed()
+ .setEmbed(new generateEmojiEmbed()
+ .setTitle("Tags")
+ .setDescription(string)
+ .setEmoji("PUNISH.NICKNAME.GREEN")
+ .setStatus("Success")
+ ).setTitle(`Page ${pages.length + 1}`).setPageId(pages.length))
+ }
+ let m;
+ m = await interaction.reply({
+ embeds: [
+ new generateEmojiEmbed()
+ .setTitle("Welcome")
+ .setDescription(`One moment...`)
+ .setStatus("Danger")
+ .setEmoji("NUCLEUS.LOADING")
+ ], fetchReply: true, ephemeral: true
+ });
+ let page = 0;
+ let selectPaneOpen = false;
+ while (true) {
+ let selectPane = []
+
+ if (selectPaneOpen) {
+ let options = [];
+ pages.forEach(embed => {
+ options.push(new SelectMenuOption({
+ label: embed.title,
+ value: embed.pageId.toString(),
+ description: embed.description || "",
+ }))
+ })
+ selectPane = [new MessageActionRow().addComponents([
+ new Discord.MessageSelectMenu()
+ .addOptions(options)
+ .setCustomId("page")
+ .setMaxValues(1)
+ .setPlaceholder("Choose a page...")
+ ])]
+ }
+ let em = new Discord.MessageEmbed(pages[page].embed)
+ em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
+ await interaction.editReply({
+ embeds: [em],
+ components: selectPane.concat([new MessageActionRow().addComponents([
+ new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(page === 0),
+ new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle(selectPaneOpen ? "PRIMARY" : "SECONDARY").setDisabled(false),
+ new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(page === pages.length - 1),
+ new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER")
+ ])])
+ });
+ let i
+ try {
+ i = await m.awaitMessageComponent({time: 600000 });
+ } catch (e) { break }
+ i.deferUpdate()
+ if (i.component.customId == "left") {
+ if (page > 0) page--;
+ selectPaneOpen = false;
+ } else if (i.component.customId == "right") {
+ if (page < pages.length - 1) page++;
+ selectPaneOpen = false;
+ } else if (i.component.customId == "select") {
+ selectPaneOpen = !selectPaneOpen;
+ } else if (i.component.customId == "page") {
+ page = parseInt(i.values[0]);
+ selectPaneOpen = false;
+ } else {
+ let em = new Discord.MessageEmbed(pages[page].embed)
+ em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message closed");
+ await interaction.editReply({
+ embeds: [em], components: [new MessageActionRow().addComponents([
+ new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(true),
+ new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle(selectPaneOpen ? "PRIMARY" : "SECONDARY").setDisabled(true),
+ new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(true),
+ new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER").setDisabled(true)
+ ])]
+ })
+ return;
+ }
+ }
+ let em = new Discord.MessageEmbed(pages[page])
+ em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message timed out");
+ await interaction.editReply({
+ embeds: [em],
+ components: [new MessageActionRow().addComponents([
+ new MessageButton().setCustomId("left").setEmoji(getEmojiByName("CONTROL.LEFT", "id")).setStyle("SECONDARY").setDisabled(true),
+ new MessageButton().setCustomId("select").setEmoji(getEmojiByName("CONTROL.MENU", "id")).setStyle("SECONDARY").setDisabled(true),
+ new MessageButton().setCustomId("right").setEmoji(getEmojiByName("CONTROL.RIGHT", "id")).setStyle("SECONDARY").setDisabled(true),
+ new MessageButton().setCustomId("close").setEmoji(getEmojiByName("CONTROL.CROSS", "id")).setStyle("DANGER").setDisabled(true)
+ ])]
+ });
+}
+
+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/user/track.ts b/src/commands/user/track.ts
index be32f41..f96f718 100644
--- a/src/commands/user/track.ts
+++ b/src/commands/user/track.ts
@@ -3,9 +3,8 @@
import { WrappedCheck } from "jshaiku";
import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
-import generateKeyValueList from "../../utils/generateKeyValueList.js";
-import readConfig from "../../utils/readConfig.js";
import addPlural from "../../utils/plurals.js";
+import client from "../../utils/client.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
@@ -24,10 +23,10 @@
const callback = async (interaction: CommandInteraction) => {
// @ts-ignore
- const { renderUser } = interaction.client.logger
+ const { renderUser } = interaction.client.logger;
const member = interaction.options.getMember("user") as GuildMember;
const guild = interaction.guild;
- let config = await readConfig(guild.id);
+ let config = await client.database.read(guild.id);
await interaction.reply({embeds: [new generateEmojiEmbed()
.setEmoji("NUCLEUS.LOADING")
.setTitle("Loading")
@@ -161,14 +160,19 @@
}
}
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+const check = async (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
let member = (interaction.member as GuildMember)
// Allow the owner to promote anyone
if (member.id == interaction.guild.ownerId) return true
+ // Check if the user can manage any of the tracks
+ // @ts-ignore
+ let tracks = (await interaction.client.database.get(interaction.guild.id)).tracks
+ let managed = false
+ tracks.forEach(element => { if (element.track.manageableBy.some(role => member.roles.cache.has(role))) managed = true });
// Check if the user has manage_roles permission
- if (! member.permissions.has("MANAGE_ROLES")) throw "You do not have the `manage_roles` permission";
+ if (!managed && ! member.permissions.has("MANAGE_ROLES")) throw "You do not have the `manage_roles` permission";
// Allow track
- return true // TODO: allow if the member has manage perms
+ return true;
}
export { command };