loads of new commands, updates and bug fixes
diff --git a/src/api/index.ts b/src/api/index.ts
index 570ca5b..07131ff 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -2,6 +2,8 @@
import express from 'express';
import bodyParser from 'body-parser';
import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
+import structuredClone from '@ungap/structured-clone';
+
const jsonParser = bodyParser.json();
const app = express();
@@ -9,7 +11,7 @@
const runServer = (client: HaikuClient) => {
app.get('/', (req, res) => {
- res.send(client.ws.ping);
+ res.status(200).send(client.ws.ping);
});
app.post('/verify/:code', jsonParser, async function (req, res) {
@@ -32,9 +34,9 @@
.setEmoji("MEMBER.JOIN")
], components: []});
}
- res.status(200).send();
+ res.sendStatus(200);
} else {
- res.status(403).send();
+ res.sendStatus(403);
}
});
@@ -51,17 +53,17 @@
]});
}
} catch {}
- res.status(200).send();
+ res.sendStatus(200);
})
app.get('/verify/:code', jsonParser, function (req, res) {
const code = req.params.code;
if (client.verify[code]) {
- // let data = structuredClone(client.verify[code])
- // delete data.interaction;
- // return res.status(200).send(data);
+ let data = structuredClone(client.verify[code])
+ delete data.interaction;
+ return res.status(200).send(data);
}
- return res.status(404).send();
+ return res.sendStatus(404);
})
app.listen(port);
diff --git a/src/automations/createModActionTicket.ts b/src/automations/createModActionTicket.ts
index ef317b7..a9a5a27 100644
--- a/src/automations/createModActionTicket.ts
+++ b/src/automations/createModActionTicket.ts
@@ -1,10 +1,10 @@
import Discord, { MessageActionRow, MessageButton } from 'discord.js';
-import readConfig from '../utils/readConfig.js'
import generateEmojiEmbed from '../utils/generateEmojiEmbed.js';
import getEmojiByName from "../utils/getEmojiByName.js";
+import client from "../utils/client.js";
-export async function create(guild: Discord.Guild, member: Discord.User, createdBy: Discord.User, client) {
- let config = await readConfig(guild.id);
+export async function create(guild: Discord.Guild, member: Discord.User, createdBy: Discord.User, reason: string) {
+ let config = await client.database.read(guild.id);
// @ts-ignore
const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger
let overwrites = [{
@@ -12,6 +12,11 @@
allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"],
type: "member"
}] as Discord.OverwriteResolvable[];
+ overwrites.push({
+ id: guild.roles.everyone,
+ deny: ["VIEW_CHANNEL"],
+ type: "role"
+ })
if (config.tickets.supportRole != null) {
overwrites.push({
id: guild.roles.cache.get(config.tickets.supportRole),
@@ -47,7 +52,7 @@
.setTitle("New Ticket")
.setDescription(
`Ticket created by a Moderator\n` +
- `**Support type:** Appeal submission\n` +
+ `**Support type:** Appeal submission\n` + (reason != null ? `**Reason:**\n> ${reason}\n` : "") +
`**Ticket ID:** \`${c.id}\`\n` +
`Type \`/ticket close\` to archive this ticket.`,
)
@@ -84,6 +89,6 @@
}
export async function areTicketsEnabled(guild: string) {
- let config = await readConfig(guild);
+ let config = await client.database.read(guild);
return config.tickets.enabled;
}
\ No newline at end of file
diff --git a/src/automations/guide.ts b/src/automations/guide.ts
index 6a9b0b5..d443a42 100644
--- a/src/automations/guide.ts
+++ b/src/automations/guide.ts
@@ -96,7 +96,20 @@
)
.setEmoji("NUCLEUS.LOGO")
.setStatus("Danger")
- ).setTitle("Tickets").setDescription("Ticket system").setPageId(5)
+ ).setTitle("Tickets").setDescription("Ticket system").setPageId(5),
+ new Embed()
+ .setEmbed(new generateEmojiEmbed()
+ .setTitle("Tags")
+ .setDescription(
+ "Add a tag system to your server with the `/tag` and `/tags` commands.\n" +
+ "To create a tag, type `/tags create <tag name> <tag content>`.\n" +
+ "Tag names and content can be edited with `/tags edit`.\n" +
+ "To delete a tag, type `/tags delete <tag name>`.\n" +
+ "To view all tags, type `/tags list`.\n"
+ )
+ .setEmoji("NUCLEUS.LOGO")
+ .setStatus("Danger")
+ ).setTitle("Tags").setDescription("Tag system").setPageId(6)
]
let m;
if (interaction) {
@@ -144,30 +157,25 @@
.setPlaceholder("Choose a page...")
])]
}
+ let 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")
+ ])])
if (interaction) {
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")
- ])]),
- fetchReply: true
+ components: components
});
} else {
let em = new Discord.MessageEmbed(pages[page].embed)
em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page));
await m.edit({
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")
- ])]),
+ components: components,
fetchReply: true
});
}
diff --git a/src/automations/roleMenu.ts b/src/automations/roleMenu.ts
index c7073c5..cac15a5 100644
--- a/src/automations/roleMenu.ts
+++ b/src/automations/roleMenu.ts
@@ -1,18 +1,18 @@
import { Message, MessageButton } from "discord.js";
-import readConfig from '../utils/readConfig.js'
import generateEmojiEmbed from '../utils/generateEmojiEmbed.js'
import { MessageActionRow, MessageSelectMenu } from 'discord.js';
import getEmojiByName from "../utils/getEmojiByName.js";
+import client from "../utils/client.js";
export async function callback(interaction) {
- let config = await readConfig(interaction.guild.id);
- if (!config.roleMenu.enabled) await interaction.reply({embeds: [new generateEmojiEmbed()
+ let config = await client.database.read(interaction.guild.id);
+ if (!config.roleMenu.enabled) return await interaction.reply({embeds: [new generateEmojiEmbed()
.setTitle("Roles")
.setDescription("Self roles are currently disabled. Please contact a staff member or try again later.")
.setStatus("Danger")
.setEmoji("CONTROL.BLOCKCROSS")
], ephemeral: true})
- if (config.roleMenu.options.length === 0) await interaction.reply({embeds: [new generateEmojiEmbed()
+ if (config.roleMenu.options.length === 0) return await interaction.reply({embeds: [new generateEmojiEmbed()
.setTitle("Roles")
.setDescription("There are no roles available. Please contact a staff member or try again later.")
.setStatus("Danger")
@@ -136,7 +136,7 @@
.setDescription("Something went wrong and your roles were not added. Please contact a staff member or try again later.")
.setStatus("Danger")
.setEmoji("GUILD.RED")
- ]})
+ ], components: []})
}
await interaction.editReply({embeds: [new generateEmojiEmbed()
.setTitle("Roles")
diff --git a/src/automations/statsChannelAdd.ts b/src/automations/statsChannelAdd.ts
index 42ec580..01dfef1 100644
--- a/src/automations/statsChannelAdd.ts
+++ b/src/automations/statsChannelAdd.ts
@@ -1,19 +1,28 @@
-import log from '../utils/log.js'
-import readConfig from '../utils/readConfig.js'
import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
+import singleNotify from '../utils/singleNotify.js';
+import client from '../utils/client.js';
export async function callback(_, member) {
- let config = await readConfig(member.guild.id);
+ let config = await client.database.read(member.guild.id);
config.stats.forEach(async element => {
if (element.enabled) {
let string = element.text
if (!string) return
string = await convertCurlyBracketString(string, member.id, member.displayName, member.guild.name, member.guild.members)
-
- let channel = await member.client.channels.fetch(element.channel)
+ let channel;
+ try {
+ channel = await member.client.channels.fetch(element.channel)
+ } catch (error) { channel = null }
+ if (!channel) {
+ return singleNotify(
+ "statsChannelDeleted",
+ member.guild.id,
+ "One or more of your stats channels have been deleted. Please open the settings menu to change this.",
+ "Critical"
+ )
+ }
if (channel.guild.id !== member.guild.id) return
- if (!channel) return // TODO: Notify mods
try {
await channel.edit({ name: string })
} catch (err) {
@@ -21,4 +30,4 @@
}
}
});
-}
\ No newline at end of file
+}
diff --git a/src/automations/statsChannelRemove.ts b/src/automations/statsChannelRemove.ts
index 4b24768..fee0d2d 100644
--- a/src/automations/statsChannelRemove.ts
+++ b/src/automations/statsChannelRemove.ts
@@ -1,10 +1,9 @@
-import log from '../utils/log.js'
-import readConfig from '../utils/readConfig.js'
+import client from '../utils/client.js';
import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
import singleNotify from '../utils/singleNotify.js';
-export async function callback(interaction, member) {
- let config = await readConfig(member.guild.id);
+export async function callback(_, member) {
+ let config = await client.database.read(member.guild.id);
config.stats.forEach(async element => {
if (element.enabled) {
@@ -14,10 +13,10 @@
let channel = await member.client.channels.fetch(element.channel)
if (channel.guild.id !== member.guild.id) return
- if (!channel) return await singleNotify(interaction.client,
+ if (!channel) return singleNotify(
"statsChannelDeleted",
member.guild.id,
- "The stats channel has been deleted. Please set a new channel to use this feature.",
+ "One or more of your stats channels have been deleted. Please open the settings menu to change this.",
"Critical"
)
try {
diff --git a/src/automations/tickets/create.ts b/src/automations/tickets/create.ts
index fd0ae57..9642089 100644
--- a/src/automations/tickets/create.ts
+++ b/src/automations/tickets/create.ts
@@ -1,6 +1,6 @@
import Discord, { MessageActionRow, MessageButton } from "discord.js";
import { tickets, toHexArray } from "../../utils/calculate.js";
-import readConfig from "../../utils/readConfig.js";
+import client from "../../utils/client.js";
import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
@@ -13,7 +13,7 @@
// @ts-ignore
const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = interaction.client.logger
- let config = await readConfig(interaction.guild.id);
+ let config = await client.database.read(interaction.guild.id);
if (!config.tickets.enabled || !config.tickets.category) {
return await interaction.reply({embeds: [new generateEmojiEmbed()
.setTitle("Tickets are disabled")
@@ -63,8 +63,8 @@
.setEmoji(getEmojiByName(("TICKETS." + type.toString().toUpperCase()), "id"));
}
});
- for (let i = 0; i < formattedTicketTypes.length; i += 4) {
- splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 4)));
+ for (let i = 0; i < formattedTicketTypes.length; i += 5) {
+ splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 5)));
}
let m = await interaction.reply({embeds: [new generateEmojiEmbed()
.setTitle("Create Ticket")
@@ -97,8 +97,8 @@
.setDisabled(true)
}
});
- for (let i = 0; i < formattedTicketTypes.length; i += 4) {
- splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 4)));
+ for (let i = 0; i < formattedTicketTypes.length; i += 5) {
+ splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 5)));
}
component.update({embeds: [new generateEmojiEmbed()
.setTitle("Create Ticket")
diff --git a/src/automations/tickets/delete.ts b/src/automations/tickets/delete.ts
index 17d889a..1d577a4 100644
--- a/src/automations/tickets/delete.ts
+++ b/src/automations/tickets/delete.ts
@@ -1,18 +1,20 @@
import Discord, { MessageButton, MessageActionRow } from "discord.js";
+import client from "../../utils/client.js";
import generateEmojiEmbed from "../../utils/generateEmojiEmbed.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
-import readConfig from "../../utils/readConfig.js";
export default async function (interaction) {
// @ts-ignore
const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = interaction.client.logger
- let config = await readConfig(interaction.guild.id);
+ let config = await client.database.read(interaction.guild.id);
+ let thread = false; let threadChannel
+ if (interaction.channel instanceof Discord.ThreadChannel) thread = true; threadChannel = interaction.channel as Discord.ThreadChannel
let channel = (interaction.channel as Discord.TextChannel)
- if (!channel.parent || config.tickets.category != channel.parent.id) {
+ if (!channel.parent || config.tickets.category != channel.parent.id || (thread ? (threadChannel.parent.parent.id != config.tickets.category) : false)) {
return interaction.reply({embeds: [new generateEmojiEmbed()
.setTitle("Deleting Ticket...")
- .setDescription("This ticket is not in your tickets category, so cannot be deleted. You cannot run close in a thread.") // TODO bridge to cross later!
+ .setDescription("This ticket is not in your tickets category, so cannot be deleted. You cannot run close in a thread.")
.setStatus("Danger")
.setEmoji("CONTROL.BLOCKCROSS")
], ephemeral: true});
@@ -110,7 +112,7 @@
}
async function purgeByUser(member, guild) {
- let config = await readConfig(guild.id);
+ let config = await client.database.read(guild.id);
if (!config.tickets.category) return;
let tickets = guild.channels.cache.get(config.tickets.category);
if (!tickets) return;
diff --git a/src/automations/verify.ts b/src/automations/verify.ts
index 3d4e658..bf26505 100644
--- a/src/automations/verify.ts
+++ b/src/automations/verify.ts
@@ -1,9 +1,9 @@
-import Discord, { CommandInteraction, GuildMember } from "discord.js";
+import Discord, { GuildMember } from "discord.js";
import generateEmojiEmbed from "../utils/generateEmojiEmbed.js";
-import readConfig from "../utils/readConfig.js";
import fetch from "node-fetch";
import { TestString, NSFWCheck } from "../automations/unscan.js";
import createPageIndicator from "../utils/createPageIndicator.js";
+import client from "../utils/client.js";
function step(i) {
return "\n\n" + createPageIndicator(5, i);
@@ -18,7 +18,13 @@
.setStatus("Danger")
.setEmoji("NUCLEUS.LOADING")
], ephemeral: true, fetchReply: true});
- let config = await readConfig(interaction.guild.id);
+ let config = await client.database.read(interaction.guild.id);
+ if ((!config.verify.enabled ) || (!config.verify.role)) return interaction.editReply({embeds: [new generateEmojiEmbed()
+ .setTitle("Verify")
+ .setDescription(`Verify is not enabled on this server`)
+ .setStatus("Danger")
+ .setEmoji("CONTROL.BLOCKCROSS")
+ ], ephemeral: true, fetchReply: true});
if ((interaction.member as GuildMember).roles.cache.has(config.verify.role)) {
return await interaction.editReply({embeds: [new generateEmojiEmbed()
.setTitle("Verify")
@@ -34,7 +40,7 @@
.setEmoji("NUCLEUS.LOADING")
]});
try {
- let status = await fetch(`https://clicks.codes/`).then(res => res.status);
+ let status = await fetch(client.config.baseUrl).then(res => res.status);
if (status != 200) {
return await interaction.editReply({embeds: [new generateEmojiEmbed()
.setTitle("Verify")
@@ -53,7 +59,7 @@
new Discord.MessageButton()
.setLabel("Check webpage")
.setStyle("LINK")
- .setURL("https://clicks.codes/"),
+ .setURL(client.config.baseUrl),
new Discord.MessageButton()
.setLabel("Support")
.setStyle("LINK")
@@ -132,7 +138,6 @@
], components: [new Discord.MessageActionRow().addComponents([new Discord.MessageButton()
.setLabel("Verify")
.setStyle("LINK")
- // .setURL(`https://clicks.codes/nucleus/verify?code=${code}`)
- .setURL(`https://insulation-coin-hoping-nevertheless.trycloudflare.com/nucleus/verify?code=${code}`)
+ .setURL(`${client.config.baseUrl}/nucleus/verify?code=${code}`)
])]});
}
diff --git a/src/automations/welcome.ts b/src/automations/welcome.ts
index 84a87ec..11c4844 100644
--- a/src/automations/welcome.ts
+++ b/src/automations/welcome.ts
@@ -1,10 +1,10 @@
import log from '../utils/log.js'
-import readConfig from '../utils/readConfig.js'
import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
+import client from '../utils/client.js';
export async function callback(_, member) {
if (member.bot) return
- let config = await readConfig(member.guild.id);
+ let config = await client.database.read(member.guild.id);
if (!config.welcome.enabled) return
if (!config.welcome.verificationRequired.role) {
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 };
diff --git a/src/config/default.json b/src/config/default.json
index 129bfe9..3b0882f 100644
--- a/src/config/default.json
+++ b/src/config/default.json
@@ -57,14 +57,18 @@
"logs": {
"enabled": true,
"channel": null,
- "toLog": "3fffff"
+ "toLog": "3fffff",
+ "ignore": {
+ "users": [],
+ "roles": [],
+ "channels": []
+ }
},
"staff": {
"channel": null
}
},
"verify": {
- "enabled": false,
"role": null
},
"tickets": {
@@ -72,6 +76,7 @@
"category": null,
"types": "3f",
"customTypes": null,
+ "useCustom": false,
"supportRole": null,
"maxTickets": 5
},
diff --git a/src/events/channelCreate.ts b/src/events/channelCreate.ts
index 883efd9..ec788f7 100644
--- a/src/events/channelCreate.ts
+++ b/src/events/channelCreate.ts
@@ -1,5 +1,3 @@
-import { Interaction } from "discord.js";
-
export const event = 'channelCreate'
export async function callback(client, channel) {
diff --git a/src/index.ts b/src/index.ts
index 2616f40..ca14cdb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -18,5 +18,4 @@
client.memory = new Memory()
client.database = await new Database(config.mongoUrl).connect()
-
await client.login();
\ No newline at end of file
diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts
index 26c231c..8a297b9 100644
--- a/src/utils/calculate.ts
+++ b/src/utils/calculate.ts
@@ -1,13 +1,13 @@
const logs = [
"channelUpdate",
- "channelPinsUpdate",
+ "channelPinsUpdate", // TODO
"emojiUpdate",
- "stickerUpdate",
+ "stickerUpdate", // TODO
"guildUpdate",
"guildMemberUpdate",
"guildMemberPunish",
- "guildEventUpdate",
- "guildEventMemberUpdate",
+ "guildEventUpdate", // TODO
+ "guildEventMemberUpdate", // TODO
"guildRoleUpdate",
"guildInviteUpdate",
"messageUpdate",
@@ -16,11 +16,11 @@
"messageReactionUpdate",
"messagePing",
"messageMassPing",
- "messageAnnounce",
+ "messageAnnounce", // TODO
"stageUpdate",
"threadUpdate",
- "voiceStateUpdate",
- "webhookUpdate"
+ "voiceStateUpdate", // TODO
+ "webhookUpdate" // TODO
]
const tickets = [
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 53ccb97..4e37652 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -1,8 +1,10 @@
import { Collection, Db, MongoClient } from 'mongodb';
+import structuredClone from '@ungap/structured-clone';
export const Entry = data => {
data = data ?? {};
+ data.getKey = key => data[key]
return {
get(target, prop, receiver) {
let dataToReturn = data[prop]
@@ -38,11 +40,42 @@
async read(guild: string) {
let entry = await this.guilds.findOne({ id: guild });
- return new Proxy(this.defaultData, Entry(entry)) as unknown as GuildConfig
+ return new Proxy(structuredClone(this.defaultData), Entry(entry)) as unknown as GuildConfig
}
- async write(guild: string, config: GuildConfig) {
- await this.guilds.updateOne({ id: guild }, { $set: config }, { upsert: true });
+ async write(guild: string, set: object = {}, unset: string[] = []) {
+ let uo = {}
+ for (let key of unset) {
+ uo[key] = "";
+ }
+ await this.guilds.updateOne({ id: guild }, {
+ $unset: uo,
+ $set: set
+ }, { upsert: true });
+ }
+
+ async append(guild: string, key: string, value: any) {
+ if (Array.isArray(value)) {
+ await this.guilds.updateOne({ id: guild }, {
+ $addToSet: { [key]: { $each: value } }
+ }, { upsert: true });
+ } else {
+ await this.guilds.updateOne({ id: guild }, {
+ $addToSet: { [key]: value }
+ }, { upsert: true });
+ }
+ }
+
+ async remove(guild: string, key: string, value: any) {
+ if (Array.isArray(value)) {
+ await this.guilds.updateOne({ id: guild }, {
+ $pullAll: { [key]: value }
+ }, { upsert: true });
+ } else {
+ await this.guilds.updateOne({ id: guild }, {
+ $pullAll: { [key]: [value] }
+ }, { upsert: true });
+ }
}
}
@@ -94,64 +127,65 @@
enabled: boolean,
verificationRequired: {
message: boolean,
- role: string
+ role: string | null
},
- welcomeRole: string,
- channel: string,
- message: string
+ welcomeRole: string | null,
+ channel: string | null,
+ message: string | null,
}
stats: {
enabled: boolean,
- channel: string,
- text: string
+ channel: string | null,
+ text: string | null,
}[]
logging: {
logs: {
enabled: boolean,
- channel: string,
- toLog: string
+ channel: string | null,
+ toLog: string | null,
},
staff: {
- channel: string
+ channel: string | null,
}
}
verify: {
enabled: boolean,
- role: string
+ role: string | null,
}
tickets: {
enabled: boolean,
- category: string,
- types: string,
+ category: string | null,
+ types: string | null,
customTypes: string[],
- supportRole: string,
+ useCustom: boolean,
+ supportRole: string | null,
maxTickets: number
}
moderation: {
mute: {
timeout: boolean,
- role: string,
- text: string,
- link: string
+ role: string | null,
+ text: string | null,
+ link: string | null
},
kick: {
- text: string,
- link: string
+ text: string | null,
+ link: string | null
},
ban: {
- text: string,
- link: string
+ text: string | null,
+ link: string | null
},
softban: {
- text: string,
- link: string
+ text: string | null,
+ link: string | null
},
warn: {
- text: string,
- link: string
+ text: string | null,
+ link: string | null
},
role: {
- role: string
+ role: string | null,
}
}
tracks: {
diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts
new file mode 100644
index 0000000..ae63757
--- /dev/null
+++ b/src/utils/dualCollector.ts
@@ -0,0 +1,49 @@
+import Discord from 'discord.js';
+import client from './client.js';
+import generateEmojiEmbed from "./generateEmojiEmbed.js";
+
+export default async function (m, interactionFilter, messageFilter) {
+ let out;
+ try {
+ out = await new Promise((resolve, reject) => {
+ let mes, int;
+ mes = m.createMessageComponentCollector({filter: (m) => interactionFilter(m), time: 600000})
+ .on("collect", (m) => { resolve(m); })
+ int = m.channel.createMessageCollector({filter: (m) => messageFilter(m), time: 600000})
+ .then("collect", (m) => { try {m.delete();} catch {}; resolve(m); })
+ mes.on("end", () => { int.stop(); })
+ int.on("end", () => { mes.stop(); })
+ })
+ } catch(e) {
+ console.log(e)
+ return null;
+ }
+
+ return out;
+}
+
+export async function modalInteractionCollector(m, modalFilter, interactionFilter) {
+ let out;
+ try {
+ out = await new Promise((resolve, reject) => {
+ let mod, int;
+ int = m.createMessageComponentCollector({filter: (m) => interactionFilter(m), time: 600000})
+ .on("collect", (m) => { resolve(m); })
+ mod = new Discord.InteractionCollector(
+ client, {
+ filter: (m) => modalFilter(m),
+ time: 600000
+ })
+ .on("collect", async (m) => {
+ int.stop();
+ (m as Discord.ModalSubmitInteraction).deferUpdate()
+ resolve((m as Discord.ModalSubmitInteraction)); })
+ int.on("end", () => { mod.stop(); })
+ mod.on("end", () => { int.stop(); })
+ })
+ } catch(e) {
+ console.log(e)
+ return null;
+ }
+ return out;
+}
\ No newline at end of file
diff --git a/src/utils/generateConfig.ts b/src/utils/generateConfig.ts
deleted file mode 100644
index 39b28b0..0000000
--- a/src/utils/generateConfig.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as fs from 'fs';
-
-function writeLogConfig(guild, logs) {
- if( !fs.existsSync(`./data/guilds/${guild.id}/config.json`) ) {
- fs.rmSync(`./data/guilds/${guild.id}/config.json`);
- }
- if( !fs.existsSync(`./data/guilds/${guild.id}/pins.json`) ) {
- let pins = guild.channels.cache.filter(c => c.type === "GUILD_TEXT").map(
- c => c.messages.fetchPinned().then(m => m.map(m => m.id))
- );
- fs.writeFileSync(`./data/guilds/${guild.id}/pins.json`, JSON.stringify(pins));
- }
- if( !fs.existsSync(`./data/guilds/${guild.id}/logs.json`) ) {
- fs.writeFileSync(`./data/guilds/${guild.id}/logs.json`, JSON.stringify([]));
- } else if( logs ) {
- fs.rmSync(`./data/guilds/${guild.id}/logs.json`);
- fs.writeFileSync(`./data/guilds/${guild.id}/logs.json`, JSON.stringify([]));
- }
- fs.writeFileSync(`./data/guilds/${guild.id}/config.json`, JSON.stringify({
- metadata: {
- premium: false
- },
- logs: {
- enabled: true,
- logChannel: guild.systemChannelId,
- toLog: "8be71",
- toIgnore: {
- bots: false,
- channels: [],
- members: [],
- roles: []
- }
- },
- userVerification: {
- enabled: false,
- roleID: null,
- customMessage: null
- },
- modmail: {
- enabled: false,
- categoryId: null,
- namingScheme: "rsm-{user}-{discriminator}",
- },
- welcome: {
- enabled: false,
- channelId: null,
- message: null,
- messageType: "embed",
- },
- filters: {
- images: {
- NSFW: true,
- size: true
- },
- malware: true,
- wordFilter: {
- enabled: true,
- words: {
- strict: [],
- loose: []
- },
- allowed: {
- users: [],
- roles: [],
- channels: []
- }
- },
- invite: {
- enabled: true,
- allowed: {
- users: [],
- channels: [],
- roles: []
- }
- },
- pings: {
- mass: 5,
- everyone: true,
- roles: true,
- allowed: {
- roles: [],
- rolesToMention: [],
- users: [],
- channels: []
- }
- }
- },
- tags: {}
- }));
-}
-
-export default writeLogConfig;
\ No newline at end of file
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 238b6f4..19eb2b6 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -1,10 +1,10 @@
import * as fs from 'fs';
import * as Discord from 'discord.js';
import getEmojiByName from './getEmojiByName.js';
-import readConfig from './readConfig.js';
import { toHexArray } from './calculate.js';
import { promisify } from 'util';
import generateKeyValueList from './generateKeyValueList.js';
+import client from './client.js';
const wait = promisify(setTimeout);
@@ -52,8 +52,8 @@
return auditLog;
}
- async log(log: any, client): Promise<void> {
- let config = await readConfig(log.hidden.guild);
+ async log(log: any): Promise<void> {
+ let config = await client.database.read(log.hidden.guild);
if (!config.logging.logs.enabled) return;
if (!(log.meta.calculateType == true)) {
if(!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) return console.log('Not logging this type of event');
diff --git a/src/utils/memory.ts b/src/utils/memory.ts
index 0cbf955..7e21fa9 100644
--- a/src/utils/memory.ts
+++ b/src/utils/memory.ts
@@ -1,22 +1,31 @@
-import readConfig from "./readConfig.js";
+import client from "./client.js";
class Memory {
memory: {};
constructor() {
this.memory = {};
+
+ setInterval(() => {
+ for (let guild in this.memory) {
+ if (this.memory[guild].updated + 15 * 60 * 1000 < Date.now()) {
+ delete this.memory[guild];
+ }
+ }
+ }, 1000 * 60 * 30)
}
async readGuildInfo(guild: string): Promise<object> {
if (!this.memory[guild]) {
- let guildData = await readConfig(guild);
+ let guildData = await client.database.read(guild);
this.memory[guild] = {
+ lastUpdated: Date.now(),
filters: guildData.filters,
logging: guildData.logging,
tickets: guildData.tickets,
- }; // TODO: REMOVE GUILD FROM MEMORY WHEN THESE UPDATE
- } // TODO: Add a "lastAccessed" prop, delete after 15 minutes
+ };
+ };
return this.memory[guild];
}
}
-export default Memory;
\ No newline at end of file
+export default Memory;
diff --git a/src/utils/readConfig.ts b/src/utils/readConfig.ts
deleted file mode 100644
index b363fc0..0000000
--- a/src/utils/readConfig.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import client from './client.js';
-
-export default async function readConfig(guild: string): Promise<any> {
- return await client.database.read(guild);
-}
diff --git a/src/utils/singleNotify.ts b/src/utils/singleNotify.ts
index 4e9e6fe..a983478 100644
--- a/src/utils/singleNotify.ts
+++ b/src/utils/singleNotify.ts
@@ -1,4 +1,4 @@
-import readConfig from "./readConfig.js";
+import client from './client.js';
import generateEmojiEmbed from "./generateEmojiEmbed.js";
let severities = {
@@ -7,17 +7,18 @@
"Info": "Success"
}
-export default async function(client, type: string, guild: string, message: string, severity: string) {
- let config = await readConfig(guild);
- if (config.singleEventNotifications[type]) return;
- // TODO: Set config.singleEventNotifications[type] to true
- let channel = await client.channels.fetch(config.logging.staff);
- if (!channel) return;
+export default async function(type: string, guild: string, message: string, severity: string) {
+ let data = await client.database.read(guild);
+ if (data.singleEventNotifications[type]) return;
+ data.singleEventNotifications[type] = true;
+ client.database.write(guild, data);
try {
+ let channel = await client.channels.fetch(data.logging.staff.channel);
+ if (!channel) return;
await channel.send({embeds: [new generateEmojiEmbed()
.setTitle(`${severity} notification`)
.setDescription(message)
- .setColor(severities[severity])
+ .setStatus(severities[severity])
.setEmoji("CONTROL.BLOCKCROSS")
]})
} catch (err) {