loads of bug fixes
diff --git a/COPYING.md b/COPYING.md
index ceffdf6..dfe4882 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -1,5 +1,13 @@
# Nucleus by Clicks
+
+## Contents:
+
+- [In short](in-short)
+- [How to](how-to)
+- [The legal bit](the-legal-bit)
+
+
## In Short:
You **may**:
@@ -31,6 +39,7 @@
"verifySecret": "if making a verify command, this is the value that checks if requests are from the website",
"mongoUrl": "mongodb://your-mongo-ip-and-port",
"baseUrl": "your website url, e.g. https://clicks.codes",
+ "pastebinApiKey": "your-pastebin-api-key"
}
```
diff --git a/package.json b/package.json
index 9a9338b..0320d64 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,7 @@
"dependencies": {
"@discordjs/builders": "^0.12.0",
"@ungap/structured-clone": "^1.0.1",
+ "agenda": "^4.3.0",
"body-parser": "^1.20.0",
"discord.js": "13.8.1",
"express": "^4.18.1",
@@ -11,9 +12,10 @@
"json-diff": "^0.7.1",
"mongodb": "^4.7.0",
"node-tesseract": "^0.2.7",
+ "node-tesseract-ocr": "^2.2.1",
"opencv.js": "^1.2.1",
+ "pastebin-api": "^5.1.1",
"structured-clone": "^0.2.2",
- "tesseract.js": "^2.1.5",
"typescript": "^4.5.5",
"unscan": "^1.1.2",
"uuid": "^8.3.2"
diff --git a/src/api/index.ts b/src/api/index.ts
index b2edc66..c429452 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -90,7 +90,7 @@
const data = req.body.data;
if (secret === client.config.verifySecret) {
console.table(data)
- let guild = await client.guilds.fetch(client.roleMenu[code].guild); // TODO: do checks here to like max roles because people are fucking annoying and will edit the source :)
+ let guild = await client.guilds.fetch(client.roleMenu[code].guild);
if (!guild) { return res.status(404) }
let member = await guild.members.fetch(client.roleMenu[code].user);
if (!member) { return res.status(404) }
diff --git a/src/automations/createModActionTicket.ts b/src/automations/createModActionTicket.ts
deleted file mode 100644
index 2ab9a46..0000000
--- a/src/automations/createModActionTicket.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import Discord, { MessageActionRow, MessageButton } from 'discord.js';
-import EmojiEmbed 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, reason: string) {
- let config = await client.database.guilds.read(guild.id);
- // @ts-ignore
- const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger
- let overwrites = [{
- id: member,
- 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),
- allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"],
- type: "role"
- })
- }
-
- let c;
- try {
- c = await guild.channels.create(member.username, {
- type: "GUILD_TEXT",
- topic: `${member.id} Active`,
- parent: config.tickets.category,
- nsfw: false,
- permissionOverwrites: (overwrites as Discord.OverwriteResolvable[]),
- reason: "Creating ticket"
- })
- } catch (e) {
- return null
- }
- try {
- await c.send(
- {
- content: (`<@${member.id}>` + (config.tickets.supportRole != null ? ` • <@&${config.tickets.supportRole}>` : "")),
- allowedMentions: {
- users: [member.id],
- roles: (config.tickets.supportRole != null ? [config.tickets.supportRole] : [])
- }
- }
- )
- await c.send({ embeds: [new EmojiEmbed()
- .setTitle("New Ticket")
- .setDescription(
- `Ticket created by a Moderator\n` +
- `**Support type:** Appeal submission\n` + (reason != null ? `**Reason:**\n> ${reason}\n` : "") +
- `**Ticket ID:** \`${c.id}\`\n` +
- `Type \`/ticket close\` to close this ticket.`,
- )
- .setStatus("Success")
- .setEmoji("GUILD.TICKET.OPEN")
- ], components: [new MessageActionRow().addComponents([new MessageButton()
- .setLabel("Close")
- .setStyle("DANGER")
- .setCustomId("closeticket")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- ])]})
- let data = {
- meta:{
- type: 'ticketCreate',
- displayName: 'Ticket Created',
- calculateType: true,
- color: NucleusColors.green,
- emoji: 'GUILD.TICKET.OPEN',
- timestamp: new Date().getTime()
- },
- list: {
- ticketFor: entry(member.id, renderUser(member)),
- createdBy: entry(createdBy.id, renderUser(createdBy)),
- created: entry(new Date().getTime(), renderDelta(new Date().getTime())),
- ticketChannel: entry(c.id, renderChannel(c)),
- },
- hidden: {
- guild: guild.id
- }
- }
- log(data);
- } catch (e) { console.log(e); return null }
- return c.id
-}
-
-export async function areTicketsEnabled(guild: string) {
- let config = await client.database.guilds.read(guild);
- return config.tickets.enabled;
-}
\ No newline at end of file
diff --git a/src/automations/guide.ts b/src/automations/guide.ts
deleted file mode 100644
index 655d781..0000000
--- a/src/automations/guide.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-import { SelectMenuOption } from '@discordjs/builders';
-import Discord, { MessageActionRow, MessageButton } from "discord.js";
-import EmojiEmbed from "../utils/generateEmojiEmbed.js";
-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; }
-}
-
-export default async (guild, interaction?) => {
- let c = guild.publicUpdatesChannel ? guild.publicUpdatesChannel : guild.systemChannel;
- c = c ? c : guild.channels.cache.find(ch => ch.type === "GUILD_TEXT" && ch.permissionsFor(guild.roles.everyone).has("SEND_MESSAGES") && ch.permissionsFor(guild.me).has("EMBED_LINKS"));
- let pages = [
- new Embed()
- .setEmbed(new EmojiEmbed()
- .setTitle("Welcome to Nucleus")
- .setDescription(
- "Thanks for adding Nucleus to your server\n\n" +
- "On the next few pages you can find instructions on getting started, and commands you may want to set up\n\n" +
- "If you need support, have questions or want features, you can let us know in [Clicks](https://discord.gg/bPaNnxe)"
- )
- .setEmoji("NUCLEUS.LOGO")
- .setStatus("Danger")
- ).setTitle("Welcome").setDescription("About Nucleus").setPageId(0),
- new Embed()
- .setEmbed(new EmojiEmbed()
- .setTitle("Logging")
- .setDescription(
- "Nucleus can log server events and keep you informed with what content is being posted to your server.\n" +
- "We have 2 different types of logs, which each can be configured to send to a channel of your choice:\n" +
- "**General Logs:** These are events like kicks and channel changes etc.\n" +
- "**Warning Logs:** Warnings like NSFW avatars and spam etc. that may require action by a server staff member. " +
- "These go to to a separate staff notifications channel.\n\n" +
- "A general log channel can be set with `/settings log`\n" +
- "A warning log channel can be set with `/settings warnings channel`"
- )
- .setEmoji("NUCLEUS.LOGO")
- .setStatus("Danger")
- ).setTitle("Logging").setDescription("Logging, staff warning logs etc.").setPageId(1),
- new Embed()
- .setEmbed(new EmojiEmbed()
- .setTitle("Moderation")
- .setDescription(
- "Nucleus has a number of commands that can be used to moderate your server.\n" +
- "These commands are all found under `/mod`, and they include:\n" +
- `**${getEmojiByName("PUNISH.WARN.YELLOW")} Warn:** The user is warned (via DM) that they violated server rules.\n` +
- `**${getEmojiByName("PUNISH.CLEARHISTORY")} Clear:** Some messages from a user are deleted in a channel.\n` +
- `**${getEmojiByName("PUNISH.MUTE.YELLOW")} Mute:** The user is unable to send messages or join voice chats.\n` +
- `**${getEmojiByName("PUNISH.MUTE.GREEN")} Unmute:** The user is able to send messages in the server.\n` +
- `**${getEmojiByName("PUNISH.KICK.RED")} Kick:** The user is removed from the server.\n` +
- `**${getEmojiByName("PUNISH.SOFTBAN")} Softban:** Kicks the user, deleting their messages from every channel.\n` +
- `**${getEmojiByName("PUNISH.BAN.RED")} Ban:** The user is removed from the server, and they are unable to rejoin.\n` +
- `**${getEmojiByName("PUNISH.BAN.GREEN")} Unban:** The user is able to rejoin the server.`
- )
- .setEmoji("NUCLEUS.LOGO")
- .setStatus("Danger")
- ).setTitle("Moderation").setDescription("Basic moderation commands").setPageId(2),
- new Embed()
- .setEmbed(new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(
- "Nucleus has a verification system that allows users to prove they aren't bots.\n" +
- "This is done by running `/verify` which sends a message only the user can see, giving them a link to a CAPTCHA to verify.\n" +
- "After the user complete's the CAPTCHA, they are given a role and can use the permissions accordingly.\n" +
- "You can set the role given with `/settings verify`"
- )
- .setEmoji("NUCLEUS.LOGO")
- .setStatus("Danger")
- ).setTitle("Verify").setDescription("Captcha verification system").setPageId(3),
- new Embed()
- .setEmbed(new EmojiEmbed()
- .setTitle("Content Scanning")
- .setDescription(
- "Nucleus has a content scanning system that automatically scans links and images sent by users.\n" +
- "Nucleus can detect, delete, and punish users for sending NSFW content, or links to scam or adult sites.\n" +
- "You can set the threshold for this in `/settings automation`" // TODO
- )
- .setEmoji("NUCLEUS.LOGO")
- .setStatus("Danger")
- ).setTitle("Content Scanning").setDescription("Content (NSFW, malware, scams) scanning").setPageId(4),
- new Embed()
- .setEmbed(new EmojiEmbed()
- .setTitle("Tickets")
- .setDescription(
- "Nucleus has a ticket system that allows users to create tickets and have a support team respond to them.\n" +
- "Tickets can be created with `/ticket create` and a channel is created, pinging the user and support role.\n" +
- "When the ticket is resolved, anyone can run `/ticket close` (or click the button) to close it.\n" +
- "Running `/ticket close` again will delete the ticket."
- )
- .setEmoji("NUCLEUS.LOGO")
- .setStatus("Danger")
- ).setTitle("Tickets").setDescription("Ticket system").setPageId(5),
- new Embed()
- .setEmbed(new EmojiEmbed()
- .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) {
- m = await interaction.reply({embeds: [
- new EmojiEmbed()
- .setTitle("Welcome")
- .setDescription(`One moment...`)
- .setStatus("Danger")
- .setEmoji("NUCLEUS.LOADING")
- ], fetchReply: true, ephemeral: true});
- } else {
- m = await c.send({embeds: [
- new EmojiEmbed()
- .setTitle("Welcome")
- .setDescription(`One moment...`)
- .setStatus("Danger")
- .setEmoji("NUCLEUS.LOADING")
- ], fetchReply: true });
- }
- let page = 0;
-
- let f = async (component) => {
- return (component.member as Discord.GuildMember).permissions.has("MANAGE_GUILD");
- }
-
- 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 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: 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: components,
- fetchReply: true
- });
- }
- let i
- try {
- i = await m.awaitMessageComponent({filter: interaction ? () => { return true } : f, time: 300000});
- } 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 {
- if (interaction) {
- let em = new Discord.MessageEmbed(pages[page].embed)
- em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message closed");
- 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)
- ])], fetchReply: true});
- } else {
- m.delete();
- }
- return;
- }
- }
- if (interaction) {
- 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)
- ])]
- });
- } else {
- let em = new Discord.MessageEmbed(pages[page])
- em.setDescription(em.description + "\n\n" + createPageIndicator(pages.length, page) + " | Message timed out");
- await m.edit({
- 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)
- ])]
- });
- }
-}
diff --git a/src/automations/roleMenu.ts b/src/automations/roleMenu.ts
deleted file mode 100644
index 318a0dd..0000000
--- a/src/automations/roleMenu.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import { Message, MessageButton } from "discord.js";
-import EmojiEmbed 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 client.database.guilds.read(interaction.guild.id);
- if (!config.roleMenu.enabled) return await interaction.reply({embeds: [new EmojiEmbed()
- .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) return await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Roles")
- .setDescription("There are no roles available. Please contact a staff member or try again later.")
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ], ephemeral: true})
- await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Roles")
- .setDescription("Loading...")
- .setStatus("Success")
- .setEmoji("NUCLEUS.LOADING")
- ], ephemeral: true})
- let m;
- if (config.roleMenu.allowWebUI) {
- let code = ""
- let length = 5
- let itt = 0
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- while (true) {
- itt += 1
- code = ""
- for (let i = 0; i < length; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)); }
- if (code in client.roleMenu) continue;
- if (itt > 1000) {
- itt = 0
- length += 1
- continue;
- }
- break;
- }
- client.roleMenu[code] = {
- guild: interaction.guild.id,
- guildName: interaction.guild.name,
- guildIcon: interaction.guild.iconURL({format: "png"}),
- user: interaction.member.user.id,
- username: interaction.member.user.username,
- data: config.roleMenu.options,
- interaction: interaction
- };
- m = await interaction.editReply({
- embeds: [new EmojiEmbed()
- .setTitle("Roles")
- .setDescription("Select how to choose your roles")
- .setStatus("Success")
- .setEmoji("GUILD.GREEN")
- ], components: [new MessageActionRow().addComponents([
- new MessageButton()
- .setLabel("Online")
- .setStyle("LINK")
- .setDisabled(false) // TODO check if the server is up
- .setURL(`${client.config.baseUrl}/nucleus/rolemenu?code=${code}`),
- new MessageButton()
- .setLabel("Manual")
- .setStyle("PRIMARY")
- .setCustomId("manual")
- ])]
- })
- }
- let component;
- try { component = await (m as Message).awaitMessageComponent({time: 300000});
- } catch (e) { return }
- component.deferUpdate()
- let rolesToAdd = []
- for (let i = 0; i < config.roleMenu.options.length; i++) {
- let object = config.roleMenu.options[i];
- let m = await interaction.editReply({
- embeds: [
- new EmojiEmbed()
- .setTitle("Roles")
- .setEmoji("GUILD.GREEN")
- .setDescription(`**${object.name}**` + (object.description ? `\n${object.description}` : ``) +
- `\n\nSelect ${object.min}` + (object.min != object.max ? ` to ${object.max}` : ``) + ` role${object.max == 1 ? '' : 's'} to add.`)
- .setStatus("Success")
- .setFooter({text: `Step ${i + 1}/${config.roleMenu.options.length}`})
- ],
- components: [
- new MessageActionRow().addComponents([
- new MessageButton()
- .setLabel("Cancel")
- .setStyle("DANGER")
- .setCustomId("cancel")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- ].concat(object.min == 0 ? [
- new MessageButton()
- .setLabel("Skip")
- .setStyle("SECONDARY")
- .setCustomId("skip")
- .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
- ] : []))
- ].concat([new MessageActionRow().addComponents([new MessageSelectMenu()
- .setPlaceholder(`${object.name}`)
- .setCustomId("rolemenu")
- .setMinValues(object.min)
- .setMaxValues(object.max)
- .setOptions(object.options.map(o => { return {label: o.name, description: o.description, value: o.role} }))
- ])])
- });
- let component;
- try {
- component = await (m as Message).awaitMessageComponent({time: 300000});
- } catch (e) {
- return
- }
- component.deferUpdate()
- if (component.customId == "rolemenu") {
- rolesToAdd = rolesToAdd.concat(component.values)
- } else if (component.customId == "cancel") {
- return await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Roles")
- .setDescription("Cancelled. No changes were made.")
- .setStatus("Danger")
- .setEmoji("GUILD.RED")
- ], components: []})
- }
- }
- let rolesToRemove = config.roleMenu.options.map(o => o.options.map(o => o.role)).flat()
- let memberRoles = interaction.member.roles.cache.map(r => r.id)
- rolesToRemove = rolesToRemove.filter(r => memberRoles.includes(r)).filter(r => !rolesToAdd.includes(r))
- rolesToAdd = rolesToAdd.filter(r => !memberRoles.includes(r))
- try {
- await interaction.member.roles.remove(rolesToRemove)
- await interaction.member.roles.add(rolesToAdd)
- } catch (e) {
- return await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Roles")
- .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 EmojiEmbed()
- .setTitle("Roles")
- .setDescription("Roles have been added. You may close this message.")
- .setStatus("Success")
- .setEmoji("GUILD.GREEN")
- ], components: []})
- return
-}
diff --git a/src/automations/statsChannelAdd.ts b/src/automations/statsChannelAdd.ts
deleted file mode 100644
index 32de0ff..0000000
--- a/src/automations/statsChannelAdd.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-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 client.database.guilds.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;
- 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
- try {
- await channel.edit({ name: string })
- } catch (err) {
- console.error(err)
- }
- }
- });
-}
diff --git a/src/automations/statsChannelRemove.ts b/src/automations/statsChannelRemove.ts
deleted file mode 100644
index c6d4e65..0000000
--- a/src/automations/statsChannelRemove.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import client from '../utils/client.js';
-import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
-import singleNotify from '../utils/singleNotify.js';
-
-export async function callback(_, member) {
- let config = await client.database.guilds.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;
- try {
- channel = await member.client.channels.fetch(element.channel)
- } catch { 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"
- )
- try {
- await channel.edit({ name: string })
- } catch (err) {
- console.error(err)
- }
- }
- });
-}
\ No newline at end of file
diff --git a/src/automations/tickets/create.ts b/src/automations/tickets/create.ts
deleted file mode 100644
index 8d58493..0000000
--- a/src/automations/tickets/create.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-import Discord, { MessageActionRow, MessageButton } from "discord.js";
-import { tickets, toHexArray } from "../../utils/calculate.js";
-import client from "../../utils/client.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import getEmojiByName from "../../utils/getEmojiByName.js";
-
-function capitalize(s: string) {
- s = s.replace(/([A-Z])/g, ' $1');
- return s.length < 3 ? s.toUpperCase() : s[0].toUpperCase() + s.slice(1).toLowerCase();
-}
-
-export default async function (interaction) {
- const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger
-
- let config = await client.database.guilds.read(interaction.guild.id);
- if (!config.tickets.enabled || !config.tickets.category) {
- return await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Tickets are disabled")
- .setDescription("Please enable tickets in the configuration to use this command.")
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ], ephemeral: true});
- }
- let category = interaction.guild.channels.cache.get(config.tickets.category) as Discord.CategoryChannel;
- let count = 0;
- category.children.forEach(element => {
- if (!(element.type == "GUILD_TEXT")) return;
- if ((element as Discord.TextChannel).topic.includes(`${interaction.member.user.id}`)) {
- if ((element as Discord.TextChannel).topic.endsWith("Active")) {
- count++;
- }
- }
- });
- if (count >= config.tickets.maxTickets) {
- return await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Create Ticket")
- .setDescription(`You have reached the maximum amount of tickets (${config.tickets.maxTickets}). Please close one of your active tickets before creating a new one.`)
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ], ephemeral: true});
- }
- let ticketTypes;
- let custom = false
- if (config.tickets.customTypes && config.tickets.useCustom) { ticketTypes = config.tickets.customTypes; custom = true }
- else if (config.tickets.types) ticketTypes = toHexArray(config.tickets.types, tickets);
- else ticketTypes = [];
- let chosenType;
- let splitFormattedTicketTypes = [];
- if (ticketTypes.length > 0) {
- let formattedTicketTypes = [];
- formattedTicketTypes = ticketTypes.map(type => {
- if (custom) {
- return new MessageButton()
- .setLabel(type)
- .setStyle("PRIMARY")
- .setCustomId(type)
- } else {
- return new MessageButton()
- .setLabel(capitalize(type))
- .setStyle("PRIMARY")
- .setCustomId(type)
- .setEmoji(getEmojiByName(("TICKETS." + type.toString().toUpperCase()), "id"));
- }
- });
- 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 EmojiEmbed()
- .setTitle("Create Ticket")
- .setDescription("Select a ticket type")
- .setStatus("Success")
- .setEmoji("GUILD.TICKET.OPEN")
- ], ephemeral: true, fetchReply: true, components: splitFormattedTicketTypes});
- let component;
- try {
- component = await (m as Discord.Message).awaitMessageComponent({time: 300000});
- } catch (e) {
- return;
- }
- chosenType = component.customId;
- splitFormattedTicketTypes = [];
- formattedTicketTypes = [];
- formattedTicketTypes = ticketTypes.map(type => {
- if (custom) {
- return new MessageButton()
- .setLabel(type)
- .setStyle(chosenType == type ? "SUCCESS" : "SECONDARY")
- .setCustomId(type)
- .setDisabled(true)
- } else {
- return new MessageButton()
- .setLabel(capitalize(type))
- .setStyle(chosenType == type ? "SUCCESS" : "SECONDARY")
- .setCustomId(type)
- .setEmoji(getEmojiByName(("TICKETS." + type.toString().toUpperCase()), "id"))
- .setDisabled(true)
- }
- });
- for (let i = 0; i < formattedTicketTypes.length; i += 5) {
- splitFormattedTicketTypes.push(new MessageActionRow().addComponents(formattedTicketTypes.slice(i, i + 5)));
- }
- component.update({embeds: [new EmojiEmbed()
- .setTitle("Create Ticket")
- .setDescription("Select a ticket type")
- .setStatus("Success")
- .setEmoji("GUILD.TICKET.OPEN")
- ], components: splitFormattedTicketTypes});
- } else {
- chosenType = null
- await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Create Ticket")
- .setEmoji("GUILD.TICKET.OPEN")
- ], ephemeral: true, components: splitFormattedTicketTypes})
- }
- let overwrites = [{
- id: interaction.member,
- allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"],
- type: "member"
- }] as Discord.OverwriteResolvable[];
- overwrites.push({
- id: interaction.guild.roles.everyone,
- deny: ["VIEW_CHANNEL"],
- type: "role"
- })
- if (config.tickets.supportRole != null) {
- overwrites.push({
- id: interaction.guild.roles.cache.get(config.tickets.supportRole),
- allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"],
- type: "role"
- })
- }
-
- let c;
- try {
- c = await interaction.guild.channels.create(interaction.member.user.username, {
- type: "GUILD_TEXT",
- topic: `${interaction.member.user.id} Active`,
- parent: config.tickets.category,
- nsfw: false,
- permissionOverwrites: (overwrites as Discord.OverwriteResolvable[]),
- reason: "Creating ticket"
- })
- } catch (e) {
- return await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Create Ticket")
- .setDescription("Failed to create ticket")
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ]});
- }
- try {
- await c.send(
- {
- content: (`<@${interaction.member.user.id}>` + (config.tickets.supportRole != null ? ` • <@&${config.tickets.supportRole}>` : "")),
- allowedMentions: {
- users: [(interaction.member as Discord.GuildMember).id],
- roles: (config.tickets.supportRole != null ? [config.tickets.supportRole] : [])
- }
- }
- )
- let content = interaction.options ? interaction.options.getString("message") || "" : "";
- if (content) content = `**Message:**\n> ${content}\n`;
- let emoji = custom ? "" : getEmojiByName("TICKETS." + chosenType.toUpperCase());
- await c.send({ embeds: [new EmojiEmbed()
- .setTitle("New Ticket")
- .setDescription(
- `Ticket created by <@${interaction.member.user.id}>\n` +
- `**Support type:** ${chosenType != null ? (emoji) + " " + capitalize(chosenType) : "General"}\n` +
- `**Ticket ID:** \`${c.id}\`\n${content}\n` +
- `Type \`/ticket close\` to close this ticket.`,
- )
- .setStatus("Success")
- .setEmoji("GUILD.TICKET.OPEN")
- ], components: [new MessageActionRow().addComponents([new MessageButton()
- .setLabel("Close")
- .setStyle("DANGER")
- .setCustomId("closeticket")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- ])]})
- let data = {
- meta:{
- type: 'ticketCreate',
- displayName: 'Ticket Created',
- calculateType: true,
- color: NucleusColors.green,
- emoji: 'GUILD.TICKET.OPEN',
- timestamp: new Date().getTime()
- },
- list: {
- ticketFor: entry(interaction.member.user.id, renderUser(interaction.member.user)),
- created: entry(new Date().getTime(), renderDelta(new Date().getTime())),
- ticketChannel: entry(c.id, renderChannel(c)),
- },
- hidden: {
- guild: interaction.guild.id
- }
- }
- log(data);
- } catch (e) { console.log(e)}
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Create Ticket")
- .setDescription(`Ticket created. You can view it here: <#${c.id}>`)
- .setStatus("Success")
- .setEmoji("GUILD.TICKET.OPEN")
- ], components: splitFormattedTicketTypes});
-}
\ No newline at end of file
diff --git a/src/automations/tickets/delete.ts b/src/automations/tickets/delete.ts
deleted file mode 100644
index 3df284b..0000000
--- a/src/automations/tickets/delete.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import Discord, { MessageButton, MessageActionRow } from "discord.js";
-import client from "../../utils/client.js";
-import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import getEmojiByName from "../../utils/getEmojiByName.js";
-
-export default async function (interaction) {
- // @ts-ignore
- const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger
-
- let config = await client.database.guilds.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 || (thread ? (threadChannel.parent.parent.id != config.tickets.category) : false)) {
- return interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Deleting Ticket...")
- .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});
- }
- let status = channel.topic.split(" ")[1];
- if (status == "Archived") {
- await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Delete Ticket")
- .setDescription("Your ticket is being deleted...")
- .setStatus("Danger")
- .setEmoji("GUILD.TICKET.CLOSE")
- ]});
- let data = {
- meta:{
- type: 'ticketDeleted',
- displayName: 'Ticket Deleted',
- calculateType: true,
- color: NucleusColors.red,
- emoji: 'GUILD.TICKET.CLOSE',
- timestamp: new Date().getTime()
- },
- list: {
- ticketFor: entry(channel.topic.split(" ")[0], renderUser((await interaction.guild.members.fetch(channel.topic.split(" ")[0])).user)),
- deletedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
- closed: entry(new Date().getTime(), renderDelta(new Date().getTime()))
- },
- hidden: {
- guild: interaction.guild.id
- }
- }
- log(data);
- interaction.channel.delete();
- return;
- } else if (status == "Active") {
- await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Close Ticket")
- .setDescription("Your ticket is being closed...")
- .setStatus("Warning")
- .setEmoji("GUILD.TICKET.ARCHIVED")
- ]});
- let overwrites = [
- {
- id: channel.topic.split(" ")[0],
- deny: ["VIEW_CHANNEL"],
- type: "member"
- },
- {
- id: interaction.guild.id,
- deny: ["VIEW_CHANNEL"],
- type: "role"
- }
- ] as Discord.OverwriteResolvable[];
- if (config.tickets.supportRole != null) {
- overwrites.push({
- id: interaction.guild.roles.cache.get(config.tickets.supportRole),
- allow: ["VIEW_CHANNEL", "SEND_MESSAGES", "ATTACH_FILES", "ADD_REACTIONS", "READ_MESSAGE_HISTORY"],
- type: "role"
- })
- }
- channel.edit({permissionOverwrites: overwrites})
- channel.setTopic(`${channel.topic.split(" ")[0]} Archived`);
- let data = {
- meta:{
- type: 'ticketClosed',
- displayName: 'Ticket Closed',
- calculateType: true,
- color: NucleusColors.yellow,
- emoji: 'GUILD.TICKET.ARCHIVED',
- timestamp: new Date().getTime()
- },
- list: {
- ticketFor: entry(channel.topic.split(" ")[0], renderUser((await interaction.guild.members.fetch(channel.topic.split(" ")[0])).user)),
- closedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
- closed: entry(new Date().getTime(), renderDelta(new Date().getTime())),
- ticketChannel: entry(channel.id, renderChannel(channel)),
- },
- hidden: {
- guild: interaction.guild.id
- }
- }
- log(data);
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Close Ticket")
- .setDescription("This ticket has been closed.\nType `/ticket close` again to delete it.")
- .setStatus("Warning")
- .setEmoji("GUILD.TICKET.ARCHIVED") // TODO:[Premium] Add a transcript option ||\----/|| <- the bridge we will cross when we come to it
- ], components: [new MessageActionRow().addComponents([new MessageButton()
- .setLabel("Delete")
- .setStyle("DANGER")
- .setCustomId("closeticket")
- .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
- ])]});
- return;
- }
-}
-
-async function purgeByUser(member, guild) {
- let config = await client.database.guilds.read(guild.id);
- if (!config.tickets.category) return;
- let tickets = guild.channels.cache.get(config.tickets.category);
- if (!tickets) return;
- let ticketChannels = tickets.children;
- let deleted = 0
- ticketChannels.forEach(element => {
- if (element.type != "GUILD_TEXT") return;
- if (element.topic.split(" ")[0] == member) {
- element.delete();
- deleted++
- }
- });
- if (deleted) {
- try {
- const { log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
- let data = {
- meta:{
- type: 'ticketPurge',
- displayName: 'Tickets Purged',
- calculateType: true,
- color: NucleusColors.red,
- emoji: 'GUILD.TICKET.DELETE',
- timestamp: new Date().getTime()
- },
- list: {
- ticketFor: entry(member, renderUser(member)),
- deletedBy: entry(null, "Member left server"),
- deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
- ticketsDeleted: deleted,
- },
- hidden: {
- guild: guild.id
- }
- }
- log(data);
- } catch {}
- }
-}
-
-export { purgeByUser }
\ No newline at end of file
diff --git a/src/automations/unscan.ts b/src/automations/unscan.ts
deleted file mode 100644
index d1fa7d4..0000000
--- a/src/automations/unscan.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import * as scan from '../utils/scanners.js'
-
-export async function LinkCheck(message): Promise<boolean> {
- let links = message.content.match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi) ?? []
- let detections = []
- const promises = links.map(async element => {
- try {
- if (element.match(/https?:\/\/[a-zA-Z]+\.?discord(app)?\.(com|net)\/?/)) return // Also matches discord.net, not enough of a bug
- console.log(1.1)
- element = await scan.testLink(element)
- console.log(1.2)
- } catch {}
- detections.push({tags: element.tags || [], safe: element.safe})
- });
- await Promise.all(promises);
- let types = [
- "PHISHING", "DATING", "TRACKERS", "ADVERTISEMENTS", "FACEBOOK",
- "AMP", "FACEBOOK TRACKERS", "IP GRABBERS", "PORN",
- "GAMBLING", "MALWARE", "PIRACY", "RANSOMWARE",
- "REDIRECTS", "SCAMS", "TORRENT", "HATE", "JUNK"
- ]
- let detectionsTypes = detections.map(element => {
- let type = types.find(type => element.tags.includes(type))
- if (type) return type
- // if (!element.safe) return "UNSAFE"
- return undefined
- }).filter(element => element !== undefined)
- return detectionsTypes.length > 0
-}
-
-export async function NSFWCheck(element): Promise<boolean> {
- try {
- let test = (await scan.testNSFW(element))
- //@ts-ignore
- return test.nsfw
- } catch {
- return false
- }
-}
-
-export async function SizeCheck(element): Promise<boolean> {
- if (element.height == undefined || element.width == undefined) return true
- if (element.height < 20 || element.width < 20) return false
- return true
-}
-
-export async function MalwareCheck(element): Promise<boolean> {
- try {
- //@ts-ignore
- return (await scan.testMalware(element)).safe
- } catch {
- return true
- }
-}
-
-export function TestString(string, soft, strict): object | null {
- for(let word of strict || []) {
- if (string.toLowerCase().includes(word)) {
- return {word: word, type: "strict"}
- }
- }
- for(let word of soft) {
- for(let word2 of string.match(/[a-z]+/gi) || []) {
- if (word2 == word) {
- return {word: word, type: "strict"}
- }
- }
- }
- return null
-}
-
-export async function TestImage(element): Promise<string | null> {
- return null;
-}
diff --git a/src/automations/verify.ts b/src/automations/verify.ts
deleted file mode 100644
index 710439a..0000000
--- a/src/automations/verify.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import Discord, { GuildMember } from "discord.js";
-import EmojiEmbed from "../utils/generateEmojiEmbed.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);
-}
-
-export default async function(interaction) {
- let verify = client.verify
- await interaction.reply({embeds: [new EmojiEmbed()
- .setTitle("Loading")
- .setDescription(step(-1))
- .setStatus("Danger")
- .setEmoji("NUCLEUS.LOADING")
- ], ephemeral: true, fetchReply: true});
- let config = await client.database.guilds.read(interaction.guild.id);
- if ((!config.verify.enabled ) || (!config.verify.role)) return interaction.editReply({embeds: [new EmojiEmbed()
- .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 EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`You already have the <@&${config.verify.role}> role` + step(0))
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ]});
- }
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`Checking our servers are up` + step(0))
- .setStatus("Warning")
- .setEmoji("NUCLEUS.LOADING")
- ]});
- try {
- let status = await fetch(client.config.baseUrl).then(res => res.status);
- if (status != 200) {
- return await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`Our servers appear to be down, please try again later` + step(0))
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ]});
- }
- } catch {
- return await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`Our servers appear to be down, please try again later` + step(0))
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ], components: [new Discord.MessageActionRow().addComponents([
- new Discord.MessageButton()
- .setLabel("Check webpage")
- .setStyle("LINK")
- .setURL(client.config.baseUrl),
- new Discord.MessageButton()
- .setLabel("Support")
- .setStyle("LINK")
- .setURL("https://discord.gg/bPaNnxe")
- ])]});
- }
- if (config.filters.images.NSFW) {
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`Checking your avatar is safe for work` + step(1))
- .setStatus("Warning")
- .setEmoji("NUCLEUS.LOADING")
- ]});
- if (await NSFWCheck((interaction.member as GuildMember).user.avatarURL({format: "png"}))) {
- return await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`Your avatar was detected as NSFW, which we do not allow in this server.\nPlease contact one of our staff members if you believe this is a mistake` + step(1))
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ]});
- }
- }
- if (config.filters.wordFilter) {
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`Checking your name is allowed` + step(2))
- .setStatus("Warning")
- .setEmoji("NUCLEUS.LOADING")
- ]});
- if (TestString((interaction.member as Discord.GuildMember).displayName, config.filters.wordFilter.words.loose, config.filters.wordFilter.words.strict) !== null) {
- return await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`Your name contained a word we do not allow in this server.\nPlease contact one of our staff members if you believe this is a mistake` + step(2))
- .setStatus("Danger")
- .setEmoji("CONTROL.BLOCKCROSS")
- ]});
- }
- }
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`One moment...` + step(3))
- .setStatus("Warning")
- .setEmoji("NUCLEUS.LOADING")
- ]});
- let code = ""
- let length = 5
- let itt = 0
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- while (true) {
- itt += 1
- code = ""
- for (let i = 0; i < length; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)); }
- if (code in verify) continue;
- if (itt > 1000) {
- itt = 0
- length += 1
- continue
- }
- break;
- }
- verify[code] = {
- uID: interaction.member.user.id,
- gID: interaction.guild.id,
- rID: config.verify.role,
- rName: (await interaction.guild.roles.fetch(config.verify.role)).name,
- uName: interaction.member.user.username,
- gName: interaction.guild.name,
- gIcon: interaction.guild.iconURL({format: "png"}),
- interaction: interaction
- }
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Verify")
- .setDescription(`Looking good!\nClick the button below to get verified` + step(4))
- .setStatus("Success")
- .setEmoji("MEMBER.JOIN")
- ], components: [new Discord.MessageActionRow().addComponents([new Discord.MessageButton()
- .setLabel("Verify")
- .setStyle("LINK")
- .setURL(`${client.config.baseUrl}/nucleus/verify?code=${code}`)
- ])]});
-}
diff --git a/src/automations/welcome.ts b/src/automations/welcome.ts
deleted file mode 100644
index 5b80fbd..0000000
--- a/src/automations/welcome.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import log from '../utils/log.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 client.database.guilds.read(member.guild.id);
- if (!config.welcome.enabled) return
-
- if (!config.welcome.verificationRequired.role) {
- if (config.welcome.welcomeRole) {
- try {
- await member.roles.add(config.welcome.welcomeRole)
- } catch (err) {
- console.error(err)
- }
- }
- }
-
- if (!config.welcome.verificationRequired.message && config.welcome.channel) {
- let string = config.welcome.message
- if (string) {
- string = await convertCurlyBracketString(string, member.id, member.displayName, member.guild.name, member.guild.members)
-
- if (config.welcome.channel === 'dm') {
- try {
- await member.send(string)
- } catch (err) {
- console.error(err)
- }
- } else {
- let channel = await member.client.channels.fetch(config.welcome.channel)
- if (channel.guild.id !== member.guild.id) return
- if (!channel) return
- try {
- await channel.send(string)
- } catch (err) {
- console.error(err)
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/commands/categorisationTest.ts b/src/commands/categorisationTest.ts
index a6c7479..f89a899 100644
--- a/src/commands/categorisationTest.ts
+++ b/src/commands/categorisationTest.ts
@@ -1,68 +1,97 @@
-import { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
+import { CommandInteraction, GuildChannel, MessageActionRow, MessageButton, MessageSelectMenu } from "discord.js";
+import { SelectMenuOption, SlashCommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
import EmojiEmbed from "../utils/generateEmojiEmbed.js";
-import generateKeyValueList, { toCapitals } from "../utils/generateKeyValueList.js";
-import getEmojiByName from "../utils/getEmojiByName.js";
import client from "../utils/client.js"
+import addPlural from "../utils/plurals.js";
+import getEmojiByName from "../utils/getEmojiByName.js";
const command = new SlashCommandBuilder()
.setName("categorise")
.setDescription("Categorises your servers channels")
const callback = async (interaction: CommandInteraction): Promise<any> => {
- const { renderChannel } = client.logger
-
let channels = interaction.guild.channels.cache.filter(c => c.type !== "GUILD_CATEGORY");
let categorised = {}
-
await interaction.reply({embeds: [new EmojiEmbed()
.setTitle("Loading...")
.setEmoji("NUCLEUS.LOADING")
.setStatus("Success")
], ephemeral: true});
+ let predicted = {}
+ let types = {
+ general: ["general", "muted", "main", "topic", "discuss"],
+ commands: ["bot", "command", "music"],
+ images: ["pic", "selfies", "image"],
+ nsfw: ["porn", "nsfw", "sex"],
+ links: ["links"],
+ advertising: ["ads", "advert", "server", "partner"],
+ staff: ["staff", "mod", "admin"],
+ spam: ["spam"],
+ other: ["random"]
+ }
for (let c of channels.values()) {
- let predicted = []
- let types = {
- general: ["general"],
- commands: ["bot", "command", "music"],
- images: ["pic", "selfies", "image"],
- nsfw: ["porn", "nsfw", "sex"],
- links: ["links"],
- advertising: ["ads", "advert", "server", "partner"],
- staff: ["staff", "mod", "admin"]
- }
-
for (let type in types) {
for (let word of types[type]) {
if (c.name.toLowerCase().includes(word)) {
- predicted.push(type)
+ predicted[c.id] = predicted[c.id] ?? []
+ predicted[c.id].push(type)
}
}
}
-
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Categorise")
- .setDescription(generateKeyValueList({
- channel: renderChannel(c),
- category: c.parent ? c.parent.name : "Uncategorised"
- }) + "\n\n" + `Suggested tags: ${predicted.join(", ")}`)
- .setEmoji("CHANNEL.TEXT.CREATE")
- .setStatus("Success")
- ], components: [ new MessageActionRow().addComponents([
- new MessageButton()
- .setLabel("Use suggested")
- .setStyle("PRIMARY")
- .setCustomId("accept")
- .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
- ]), new MessageActionRow().addComponents([new MessageSelectMenu()
- .setPlaceholder("Select a category")
- .setCustomId("category")
- .setMinValues(0)
- .setMaxValues(1)
- .setOptions(Object.keys(types).map(type => {return {label: toCapitals(type), value: type}}))
- ])]});
}
+ let m;
+ for (let c of channels) {
+ // convert channel to a channel if its a string
+ let channel: any
+ console.log(c)
+ if (typeof c === "string") channel = interaction.guild.channels.cache.get(channel).id
+ // @ts-ignore
+ else channel = c[0].id
+ console.log(channel)
+ if (!predicted[channel]) predicted[channel] = []
+ m = await interaction.editReply({embeds: [new EmojiEmbed()
+ .setTitle("Categorise")
+ .setDescription(`Select all types that apply to <#${channel}>.\n\n` +
+ `${addPlural(predicted[channel].length, "Suggestion")}: ${predicted[channel].join(", ")}`)
+ .setEmoji("CHANNEL.CATEGORY.CREATE")
+ .setStatus("Success")
+ ], components: [
+ new MessageActionRow().addComponents([new MessageSelectMenu()
+ .setCustomId("selected")
+ .setMaxValues(Object.keys(types).length)
+ .setMinValues(1)
+ .setPlaceholder("Select all types that apply to this channel")
+ .setOptions(Object.keys(types).map(type => ({label: type, value: type})))
+ ]),
+ new MessageActionRow().addComponents([
+ new MessageButton().setLabel("Accept Suggestion").setCustomId("accept").setStyle("SUCCESS").setDisabled(predicted[channel].length === 0)
+ .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.TICK", "id"))),
+ new MessageButton().setLabel("Use \"Other\"").setCustomId("reject").setStyle("SECONDARY")
+ .setEmoji(client.emojis.cache.get(getEmojiByName("ICONS.CROSS", "id")))
+ ])
+ ]})
+ let i;
+ try {
+ i = await m.awaitMessageComponent({ time: 300000 });
+ } catch (e) {
+ return await interaction.editReply({embeds: [new EmojiEmbed()
+ .setTitle("Categorise")
+ .setEmoji("CHANNEL.CATEGORY.DELETE")
+ .setStatus("Danger")
+ .setDescription(`Select all types that apply to <#${channel}>.\n\n` +
+ `${addPlural(predicted[channel].length, "Suggestion")}: ${predicted[channel].join(", ")}`)
+ .setFooter({text: "Message timed out"})
+ ]})
+ }
+ i.deferUpdate()
+ let selected;
+ if (i.customId === "select") { selected = i.values; }
+ if (i.customId === "accept") { selected = predicted[channel]; }
+ if (i.customId === "reject") { selected = ["other"]; }
+ categorised[channel] = selected
+ }
+ console.log(categorised)
}
const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts
index 11c6c79..0239951 100644
--- a/src/commands/mod/ban.ts
+++ b/src/commands/mod/ban.ts
@@ -12,27 +12,34 @@
.setName("ban")
.setDescription("Bans a user from the server")
.addUserOption(option => option.setName("user").setDescription("The user to ban").setRequired(true))
- .addStringOption(option => option.setName("reason").setDescription("The reason for the ban").setRequired(false))
- .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are banned | Default yes").setRequired(false)
+ .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are banned | Default: Yes").setRequired(false)
.addChoices([["Yes", "yes"], ["No", "no"]])
)
- .addIntegerOption(option => option.setName("delete").setDescription("The days of messages to delete | Default 0").setMinValue(0).setMaxValue(7).setRequired(false))
+ .addIntegerOption(option => option.setName("delete").setDescription("The days of messages to delete | Default: 0").setMinValue(0).setMaxValue(7).setRequired(false))
const callback = async (interaction: CommandInteraction): Promise<any> => {
const { renderUser } = client.logger
// TODO:[Modals] Replace this with a modal
- let confirmation = await new confirmationMessage(interaction)
- .setEmoji("PUNISH.BAN.RED")
- .setTitle("Ban")
- .setDescription(keyValueList({
- "user": renderUser(interaction.options.getUser("user")),
- "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`
- + `Are you sure you want to ban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
- .setColor("Danger")
- .send()
+ let reason = null
+ let confirmation
+ while (true) {
+ confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.BAN.RED")
+ .setTitle("Ban")
+ .setDescription(keyValueList({
+ "user": renderUser(interaction.options.getUser("user")),
+ "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*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`
+ + `Are you sure you want to ban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
+ .setColor("Danger")
+ .addReasonButton(reason ?? "")
+ .send(reason !== null)
+ reason = reason ?? ""
+ if (confirmation.newReason === undefined) break
+ reason = confirmation.newReason
+ }
if (confirmation.success) {
let dmd = false
let dm;
@@ -44,7 +51,7 @@
.setEmoji("PUNISH.BAN.RED")
.setTitle("Banned")
.setDescription(`You have been banned in ${interaction.guild.name}` +
- (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : "."))
+ (reason ? ` for:\n> ${reason}` : "."))
.setStatus("Danger")
],
components: [new MessageActionRow().addComponents(config.moderation.ban.text ? [new MessageButton()
@@ -58,7 +65,6 @@
} catch {}
try {
let member = (interaction.options.getMember("user") as GuildMember)
- let reason = interaction.options.getString("reason")
member.ban({
days: Number(interaction.options.getInteger("delete") ?? 0),
reason: reason ?? "No reason provided"
diff --git a/src/commands/mod/info.ts b/src/commands/mod/info.ts
index c3a4388..b7f4b74 100644
--- a/src/commands/mod/info.ts
+++ b/src/commands/mod/info.ts
@@ -1,6 +1,6 @@
import { HistorySchema } from '../../utils/database';
import Discord, { CommandInteraction, GuildMember, MessageActionRow, MessageButton, TextInputComponent } from "discord.js";
-import { SlashCommandSubcommandBuilder, SelectMenuOption } from "@discordjs/builders";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
@@ -18,6 +18,7 @@
const types = {
"warn": {emoji: "PUNISH.WARN.YELLOW", text: "Warned"},
"mute": {emoji: "PUNISH.MUTE.YELLOW", text: "Muted"},
+ "unmute": {emoji: "PUNISH.MUTE.GREEN", text: "Unmuted"},
"join": {emoji: "MEMBER.JOIN", text: "Joined"},
"leave": {emoji: "MEMBER.LEAVE", text: "Left"},
"kick": {emoji: "MEMBER.KICK", text: "Kicked"},
diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts
index b1464ee..793a630 100644
--- a/src/commands/mod/kick.ts
+++ b/src/commands/mod/kick.ts
@@ -12,25 +12,32 @@
.setName("kick")
.setDescription("Kicks a user from the server")
.addUserOption(option => option.setName("user").setDescription("The user to kick").setRequired(true))
- .addStringOption(option => option.setName("reason").setDescription("The reason for the kick").setRequired(false))
- .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are kicked | Default yes").setRequired(false)
+ .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are kicked | Default: Yes").setRequired(false)
.addChoices([["Yes", "yes"], ["No", "no"]])
)
const callback = async (interaction: CommandInteraction): Promise<any> => {
const { renderUser } = client.logger
// TODO:[Modals] Replace this with a modal
- let confirmation = await new confirmationMessage(interaction)
- .setEmoji("PUNISH.KICK.RED")
- .setTitle("Kick")
- .setDescription(keyValueList({
- "user": renderUser(interaction.options.getUser("user")),
- "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\n`
- + `Are you sure you want to kick <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
- .setColor("Danger")
- .send()
+ let reason = null;
+ let confirmation
+ while (true) {
+ confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.KICK.RED")
+ .setTitle("Kick")
+ .setDescription(keyValueList({
+ "user": renderUser(interaction.options.getUser("user")),
+ "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
+ })
+ + `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")
+ .addReasonButton(reason ?? "")
+ .send(reason !== null)
+ reason = reason ?? ""
+ if (confirmation.newReason === undefined) break
+ reason = confirmation.newReason
+ }
if (confirmation.success) {
let dmd = false
let dm;
@@ -42,7 +49,7 @@
.setEmoji("PUNISH.KICK.RED")
.setTitle("Kicked")
.setDescription(`You have been kicked in ${interaction.guild.name}` +
- (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : "."))
+ (reason ? ` for:\n> ${reason}` : "."))
.setStatus("Danger")
],
components: [new MessageActionRow().addComponents(config.moderation.kick.text ? [new MessageButton()
@@ -55,9 +62,8 @@
}
} catch {}
try {
- (interaction.options.getMember("user") as GuildMember).kick(interaction.options.getString("reason") ?? "No reason provided.")
+ (interaction.options.getMember("user") as GuildMember).kick(reason ?? "No reason provided.")
let member = (interaction.options.getMember("user") as GuildMember)
- let reason = interaction.options.getString("reason") ?? null
try { await client.database.history.create("kick", interaction.guild.id, member.user, interaction.user, reason) } catch {}
// @ts-ignore
const { log, NucleusColors, entry, renderUser, renderDelta } = member.client.logger
diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts
index 4c326e7..5e1a18b 100644
--- a/src/commands/mod/mute.ts
+++ b/src/commands/mod/mute.ts
@@ -7,24 +7,23 @@
import keyValueList from "../../utils/generateKeyValueList.js";
import humanizeDuration from "humanize-duration";
import client from "../../utils/client.js";
+import { areTicketsEnabled, create } from "../../actions/createModActionTicket.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
.setName("mute")
- .setDescription("Mutes a member using Discord's \"Timeout\" feature")
+ .setDescription("Mutes a member, stopping them from talking in the server")
.addUserOption(option => option.setName("user").setDescription("The user to mute").setRequired(true))
- .addIntegerOption(option => option.setName("days").setDescription("The number of days to mute the user for | Default 0").setMinValue(0).setMaxValue(27).setRequired(false))
- .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to mute the user for | Default 0").setMinValue(0).setMaxValue(23).setRequired(false))
- .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
- .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false))
- .addStringOption(option => option.setName("reason").setDescription("The reason for the mute").setRequired(false))
- .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are muted | Default yes").setRequired(false)
+ .addIntegerOption(option => option.setName("days").setDescription("The number of days to mute the user for | Default: 0").setMinValue(0).setMaxValue(27).setRequired(false))
+ .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to mute the user for | Default: 0").setMinValue(0).setMaxValue(23).setRequired(false))
+ .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default: 0").setMinValue(0).setMaxValue(59).setRequired(false))
+ .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default: 0").setMinValue(0).setMaxValue(59).setRequired(false))
+ .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): Promise<any> => {
- const { log, NucleusColors, renderUser, entry } = client.logger
+ const { log, NucleusColors, renderUser, entry, renderDelta } = client.logger
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,
@@ -119,31 +118,43 @@
], ephemeral: true, fetchReply: true})
}
// TODO:[Modals] Replace this with a modal
- let confirmation = await new confirmationMessage(interaction)
- .setEmoji("PUNISH.MUTE.RED")
- .setTitle("Mute")
- .setDescription(keyValueList({
- "user": renderUser(user),
- "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
- "reason": `\n> ${reason ? reason : "*No reason provided*"}`
- })
- + `The user will be ` + serverSettingsDescription + "\n"
- + `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")
- .send(true)
+ let reason = null;
+ let confirmation;
+ while (true) {
+ confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.MUTE.RED")
+ .setTitle("Mute")
+ .setDescription(keyValueList({
+ "user": renderUser(user.user),
+ "time": `${humanizeDuration(muteTime * 1000, {round: true})}`,
+ "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
+ })
+ + `The user will be ` + serverSettingsDescription + "\n"
+ + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
+ + `Are you sure you want to mute <@!${user.id}>?`)
+ .setColor("Danger")
+ .addCustomBoolean(
+ "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
+ async () => await create(interaction.guild, user.user, interaction.user, reason),
+ "An appeal ticket will be created when Confirm is clicked")
+ .addReasonButton(reason ?? "")
+ .send(true)
+ reason = reason ?? ""
+ if (confirmation.newReason === undefined) break
+ reason = confirmation.newReason
+ }
if (confirmation.success) {
let dmd = false
let dm;
let config = await client.database.guilds.read(interaction.guild.id);
try {
if (interaction.options.getString("notify") != "no") {
- dm = await (interaction.options.getMember("user") as GuildMember).send({
+ dm = await user.send({
embeds: [new EmojiEmbed()
.setEmoji("PUNISH.MUTE.RED")
.setTitle("Muted")
.setDescription(`You have been muted in ${interaction.guild.name}` +
- (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".\n\n" +
+ (reason ? ` for:\n> ${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>)`))
.setStatus("Danger")
],
@@ -156,19 +167,36 @@
dmd = true
}
} catch {}
- let member = (interaction.options.getMember("user") as GuildMember)
+ let member = user
+ let errors = 0
try {
if (config.moderation.mute.timeout) {
- member.timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided")
+ await member.timeout(muteTime * 1000, reason || "No reason provided")
+ if (config.moderation.mute.role !== null) {
+ await member.roles.add(config.moderation.mute.role)
+ await client.database.eventScheduler.schedule("naturalUnmute", new Date().getTime() + muteTime * 1000, {
+ guild: interaction.guild.id,
+ user: user.id,
+ expires: new Date().getTime() + muteTime * 1000
+ })
+ }
}
- if (config.moderation.mute.role) {
- member.roles.add(config.moderation.mute.role)
- } // make sure this gets removed
- } catch {
+ } catch { errors++ }
+ try {
+ if (config.moderation.mute.role !== null) {
+ await member.roles.add(config.moderation.mute.role)
+ await client.database.eventScheduler.schedule("unmuteRole", new Date().getTime() + muteTime * 1000, {
+ guild: interaction.guild.id,
+ user: user.id,
+ role: config.moderation.mute.role
+ })
+ }
+ } catch (e){ console.log(e); errors++ }
+ if (errors == 2) {
await interaction.editReply({embeds: [new EmojiEmbed()
.setEmoji("PUNISH.MUTE.RED")
.setTitle(`Mute`)
- .setDescription("Something went wrong and the user was not mute")
+ .setDescription("Something went wrong and the user was not muted")
.setStatus("Danger")
], components: []})
if (dmd) await dm.delete()
@@ -192,10 +220,12 @@
timestamp: new Date().getTime()
},
list: {
- user: entry(member.user.id, renderUser(member.user)),
+ memberId: entry(member.user.id, `\`${member.user.id}\``),
+ name: entry(member.user.id, renderUser(member.user)),
+ mutedUntil: entry(new Date().getTime() + muteTime * 1000, renderDelta(new Date().getTime() + muteTime * 1000)),
+ muted: entry(new Date().getTime(), renderDelta(new Date().getTime() - 1000)),
mutedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
- time: entry(muteTime, `${humanizeDuration(muteTime * 1000, {round: true})}`),
- reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
+ reason: entry(reason, reason ? reason : '*No reason provided*')
},
hidden: {
guild: interaction.guild.id
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index 8dcbf62..f842d76 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -4,7 +4,7 @@
import confirmationMessage from "../../utils/confirmationMessage.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import keyValueList from "../../utils/generateKeyValueList.js";
-import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
+import { create, areTicketsEnabled } from "../../actions/createModActionTicket.js";
import client from "../../utils/client.js"
const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -13,7 +13,7 @@
.setDescription("Changes a users nickname")
.addUserOption(option => option.setName("user").setDescription("The user to change").setRequired(true))
.addStringOption(option => option.setName("name").setDescription("The name to set | Leave blank to clear").setRequired(false))
- .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when their nickname is changed | Default no").setRequired(false)
+ .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when their nickname is changed | Default: No").setRequired(false)
.addChoices([["Yes", "yes"], ["No", "no"]])
)
@@ -32,7 +32,7 @@
.setColor("Danger")
.addCustomBoolean(
"Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
- async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, interaction.options.getString("reason")),
+ async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, null),
"An appeal ticket will be created when Confirm is clicked")
.send()
if (confirmation.success) {
diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts
index ea4a447..a368e7a 100644
--- a/src/commands/mod/softban.ts
+++ b/src/commands/mod/softban.ts
@@ -12,27 +12,34 @@
.setName("softban")
.setDescription("Kicks a user and deletes their messages")
.addUserOption(option => option.setName("user").setDescription("The user to softban").setRequired(true))
- .addStringOption(option => option.setName("reason").setDescription("The reason for the softban").setRequired(false))
- .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are softbanned | Default yes").setRequired(false)
+ .addIntegerOption(option => option.setName("delete").setDescription("The days of messages to delete | Default: 0").setMinValue(0).setMaxValue(7).setRequired(false))
+ .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are softbanned | Default: Yes").setRequired(false)
.addChoices([["Yes", "yes"], ["No", "no"]])
)
- .addIntegerOption(option => option.setName("delete").setDescription("The days of messages to delete | Default 0").setMinValue(0).setMaxValue(7).setRequired(false))
const callback = async (interaction: CommandInteraction): Promise<any> => {
const { renderUser } = client.logger
// TODO:[Modals] Replace this with a modal
- let confirmation = await new confirmationMessage(interaction)
- .setEmoji("PUNISH.BAN.RED")
- .setTitle("Softban")
- .setDescription(keyValueList({
- "user": renderUser(interaction.options.getUser("user")),
- "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`
- + `${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")
- .send()
+ let reason = null;
+ let confirmation;
+ while (true) {
+ let confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.BAN.RED")
+ .setTitle("Softban")
+ .setDescription(keyValueList({
+ "user": renderUser(interaction.options.getUser("user")),
+ "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
+ })
+ + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\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")
+ .addReasonButton(reason ?? "")
+ .send(reason !== null)
+ reason = reason ?? ""
+ if (confirmation.newReason === undefined) break
+ reason = confirmation.newReason
+ }
if (confirmation.success) {
let dmd = false;
let config = await client.database.guilds.read(interaction.guild.id);
@@ -43,7 +50,7 @@
.setEmoji("PUNISH.BAN.RED")
.setTitle("Softbanned")
.setDescription(`You have been softbanned from ${interaction.guild.name}` +
- (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : "."))
+ (reason ? ` for:\n> ${reason}` : "."))
.setStatus("Danger")
],
components: [new MessageActionRow().addComponents(config.moderation.ban.text ? [new MessageButton()
@@ -59,7 +66,7 @@
try {
await member.ban({
days: Number(interaction.options.getInteger("delete") ?? 0),
- reason: interaction.options.getString("reason")
+ reason: reason
});
await interaction.guild.members.unban(member, "Softban");
} catch {
@@ -70,7 +77,7 @@
.setStatus("Danger")
], components: []})
}
- try { await client.database.history.create("softban", interaction.guild.id, member.user, interaction.options.getString("reason")) } catch {}
+ try { await client.database.history.create("softban", interaction.guild.id, member.user, reason) } catch {}
let failed = (dmd == false && interaction.options.getString("notify") != "no")
await interaction.editReply({embeds: [new EmojiEmbed()
.setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`)
diff --git a/src/commands/mod/unmute.ts b/src/commands/mod/unmute.ts
index 5a1b67c..d5f4205 100644
--- a/src/commands/mod/unmute.ts
+++ b/src/commands/mod/unmute.ts
@@ -11,25 +11,32 @@
.setName("unmute")
.setDescription("Unmutes a user")
.addUserOption(option => option.setName("user").setDescription("The user to unmute").setRequired(true))
- .addStringOption(option => option.setName("reason").setDescription("The reason for the unmute").setRequired(false))
- .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are unmuted | Default no").setRequired(false)
+ .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are unmuted | Default: No").setRequired(false)
.addChoices([["Yes", "yes"], ["No", "no"]])
)
const callback = async (interaction: CommandInteraction): Promise<any> => {
- const { renderUser } = client.logger
+ const { log, NucleusColors, renderUser, entry, renderDelta } = client.logger
// TODO:[Modals] Replace this with a modal
- let confirmation = await new confirmationMessage(interaction)
- .setEmoji("PUNISH.MUTE.RED")
- .setTitle("Unmute")
- .setDescription(keyValueList({
- "user": renderUser(interaction.options.getUser("user")),
- "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}`
- })
- + `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")
- .send()
+ let reason = null;
+ let confirmation;
+ while (true) {
+ confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.MUTE.RED")
+ .setTitle("Unmute")
+ .setDescription(keyValueList({
+ "user": renderUser(interaction.options.getUser("user")),
+ "reason": `\n> ${reason ? reason : "*No reason provided*"}`
+ })
+ + `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")
+ .addReasonButton(reason ?? "")
+ .send(reason !== null)
+ reason = reason ?? ""
+ if (confirmation.newReason === undefined) break
+ reason = confirmation.newReason
+ }
if (confirmation.success) {
let dmd = false
let dm;
@@ -40,15 +47,16 @@
.setEmoji("PUNISH.MUTE.GREEN")
.setTitle("Unmuted")
.setDescription(`You have been unmuted in ${interaction.guild.name}` +
- (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided."))
+ (reason ? ` for:\n> ${reason}` : " with no reason provided."))
.setStatus("Success")
]
})
dmd = true
}
} catch {}
+ let member = (interaction.options.getMember("user") as GuildMember)
try {
- (interaction.options.getMember("user") as GuildMember).timeout(0, interaction.options.getString("reason") || "No reason provided")
+ member.timeout(0, reason || "No reason provided")
} catch {
await interaction.editReply({embeds: [new EmojiEmbed()
.setEmoji("PUNISH.MUTE.RED")
@@ -59,7 +67,27 @@
if (dmd) await dm.delete()
return
}
- try { await client.database.history.create("unmute", interaction.guild.id, (interaction.options.getMember("user") as GuildMember).user, interaction.user, interaction.options.getString("reason")) } catch {}
+ try { await client.database.history.create("unmute", interaction.guild.id, (interaction.options.getMember("user") as GuildMember).user, interaction.user, reason) } catch {}
+ let data = {
+ meta: {
+ type: 'memberUnmute',
+ displayName: 'Unmuted',
+ calculateType: 'guildMemberPunish',
+ color: NucleusColors.green,
+ emoji: "PUNISH.MUTE.GREEN",
+ timestamp: new Date().getTime()
+ },
+ list: {
+ memberId: entry(member.user.id, `\`${member.user.id}\``),
+ name: entry(member.user.id, renderUser(member.user)),
+ unmuted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+ unmutedBy: entry(interaction.user.id, renderUser(interaction.user)),
+ },
+ hidden: {
+ guild: interaction.guild.id
+ }
+ }
+ log(data);
let failed = (dmd == false && interaction.options.getString("notify") != "no")
await interaction.editReply({embeds: [new EmojiEmbed()
.setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`)
diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts
index 370f347..379d49c 100644
--- a/src/commands/mod/warn.ts
+++ b/src/commands/mod/warn.ts
@@ -4,7 +4,7 @@
import confirmationMessage from "../../utils/confirmationMessage.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import keyValueList from "../../utils/generateKeyValueList.js";
-import { create, areTicketsEnabled } from "../../automations/createModActionTicket.js";
+import { create, areTicketsEnabled } from "../../actions/createModActionTicket.js";
import client from "../../utils/client.js"
const command = (builder: SlashCommandSubcommandBuilder) =>
@@ -12,29 +12,37 @@
.setName("warn")
.setDescription("Warns a user")
.addUserOption(option => option.setName("user").setDescription("The user to warn").setRequired(true))
- .addStringOption(option => option.setName("reason").setDescription("The reason for the warn").setRequired(false))
- .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are warned | Default yes").setRequired(false)
+ .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are warned | Default: Yes").setRequired(false)
.addChoices([["Yes", "yes"], ["No", "no"]])
)
const callback = async (interaction: CommandInteraction): Promise<any> => {
const { log, NucleusColors, renderUser, entry } = client.logger
// TODO:[Modals] Replace this with a modal
- let confirmation = await new confirmationMessage(interaction)
- .setEmoji("PUNISH.WARN.RED")
- .setTitle("Warn")
- .setDescription(keyValueList({
- "user": renderUser(interaction.options.getUser("user")),
- "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\n`
- + `Are you sure you want to warn <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
- .setColor("Danger")
- .addCustomBoolean(
- "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
- 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")
- .send()
+ let reason = null;
+ let confirmation;
+ while (true) {
+ confirmation = await new confirmationMessage(interaction)
+ .setEmoji("PUNISH.WARN.RED")
+ .setTitle("Warn")
+ .setDescription(keyValueList({
+ "user": renderUser(interaction.options.getUser("user")),
+ "reason": reason ? ("\n> " + ((reason ?? "").replaceAll("\n", "\n> "))) : "*No reason provided*"
+ })
+ + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`
+ + `Are you sure you want to warn <@!${(interaction.options.getMember("user") as GuildMember).id}>?`)
+ .setColor("Danger")
+ .addCustomBoolean(
+ "Create appeal ticket", !(await areTicketsEnabled(interaction.guild.id)),
+ async () => await create(interaction.guild, interaction.options.getUser("user"), interaction.user, reason),
+ "An appeal ticket will be created when Confirm is clicked")
+ .addReasonButton(reason)
+ .addReasonButton(reason ?? "")
+ .send(reason !== null)
+ reason = reason ?? ""
+ if (confirmation.newReason === undefined) break
+ reason = confirmation.newReason
+ }
if (confirmation.success) {
let dmd = false
try {
@@ -44,7 +52,7 @@
.setEmoji("PUNISH.WARN.RED")
.setTitle("Warned")
.setDescription(`You have been warned in ${interaction.guild.name}` +
- (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : ".") + "\n\n" +
+ (reason ? ` for:\n> ${reason}` : ".") + "\n\n" +
(confirmation.buttonClicked ? `You can appeal this here ticket: <#${confirmation.response}>` : ``))
.setStatus("Danger")
]
@@ -71,7 +79,7 @@
list: {
user: entry((interaction.options.getMember("user") as GuildMember).user.id, renderUser((interaction.options.getMember("user") as GuildMember).user)),
warnedBy: entry(interaction.member.user.id, renderUser(interaction.member.user)),
- reason: (interaction.options.getString("reason") ? `\n> ${interaction.options.getString("reason")}` : "No reason provided")
+ reason: reason ? `\n> ${reason}` : "No reason provided"
},
hidden: {
guild: interaction.guild.id
@@ -80,7 +88,7 @@
try { await client.database.history.create(
"warn", interaction.guild.id,
(interaction.options.getMember("user") as GuildMember).user,
- interaction.user, interaction.options.getString("reason")
+ interaction.user, reason
)} catch {}
log(data);
let failed = (dmd == false && interaction.options.getString("notify") != "no")
@@ -129,7 +137,7 @@
.setEmoji(`PUNISH.WARN.RED`)
.setTitle(`Warn`)
.setDescription(`You have been warned` +
- (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : "."))
+ (reason ? ` for:\n> ${reason}` : "."))
.setStatus("Danger")
],
content: `<@!${(interaction.options.getMember("user") as GuildMember).id}>`,
diff --git a/src/commands/nucleus/guide.ts b/src/commands/nucleus/guide.ts
index eb94de4..5f2cde2 100644
--- a/src/commands/nucleus/guide.ts
+++ b/src/commands/nucleus/guide.ts
@@ -3,7 +3,7 @@
import { WrappedCheck } from "jshaiku";
import getEmojiByName from "../../utils/getEmojiByName.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import guide from "../../automations/guide.js";
+import guide from "../../reflex/guide.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts
index cc6c554..6d62745 100644
--- a/src/commands/privacy.ts
+++ b/src/commands/privacy.ts
@@ -1,15 +1,35 @@
-import Discord, { CommandInteraction } from "discord.js";
+import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
import { SlashCommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
-import { testLink, testMalware, testNSFW } from '../utils/scanners.js';
+import { testLink, testMalware, testNSFW } from "../reflex/scanners.js";
+import EmojiEmbed from "../utils/generateEmojiEmbed.js";
const command = new SlashCommandBuilder()
.setName("privacy")
- .setDescription("we changed the fucking charger again!")
- .addStringOption(option => option.setName("link").setDescription("fuck you").setRequired(false))
+ .setDescription("Information and options for you and your server's settings")
const callback = async (interaction: CommandInteraction): Promise<any> => {
- console.log(await testLink(interaction.options.getString("link")))
+ let components = [];
+ if (interaction.inCachedGuild() && interaction.member.permissions.has("MANAGE_GUILD")) {
+ components.push(new MessageActionRow().addComponents([new MessageButton()
+ .setLabel("Clear all data")
+ .setEmoji("CONTROL.CROSS")
+ .setCustomId("clear")
+ .setStyle("DANGER")
+ ]));
+ }
+ await interaction.reply({embeds: [new EmojiEmbed()
+ .setTitle("Privacy")
+ .setDescription(
+ "**Link Scanning Types**\n" +
+ "> Facebook - Facebook trackers include data such as your date of birth, and guess your age if not entered, your preferences, who you interact with and more.\n" +
+ "> AMP - AMP is a technology that allows websites to be served by Google. This means Google can store and track data, and are pushing this to as many pages as possible.\n\n" +
+ "**Transcripts**\n" +
+ "> Transcripts allow you to store all messages sent in a channel. This could be an issue in some cases, as they are hosted on [Pastebin](https://pastebin.com), so a leaked link could show all messages sent in the channel.\n"
+ )
+ .setStatus("Success")
+ .setEmoji("NUCLEUS.COMMANDS.LOCK")
+ ], components: components});
}
const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/role/all.ts b/src/commands/role/all.ts
index b2b8c52..7ea7cb6 100644
--- a/src/commands/role/all.ts
+++ b/src/commands/role/all.ts
@@ -114,7 +114,7 @@
count ++;
return (count == 1 ? getEmojiByName("ICONS.FILTER") : (all ? "**and** " : "**or** ")) +
(f.inverted ? "**not** " : "") + `${f.name}`
- }).join("\n") + "\n\n" + `This will affect ${addPlural(affected.length.toString(), "member")}`)
+ }).join("\n") + "\n\n" + `This will affect ${addPlural(affected.length, "member")}`)
.setEmoji("GUILD.ROLES.CREATE")
.setStatus("Success")
], components: [
diff --git a/src/commands/rolemenu.ts b/src/commands/rolemenu.ts
index f53e10a..39e18f7 100644
--- a/src/commands/rolemenu.ts
+++ b/src/commands/rolemenu.ts
@@ -1,7 +1,7 @@
import { CommandInteraction } from "discord.js";
import { SlashCommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
-import { callback as roleMenu } from "../automations/roleMenu.js"
+import { callback as roleMenu } from "../actions/roleMenu.js"
const command = new SlashCommandBuilder()
.setName("rolemenu")
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index e664bd0..ba16751 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -16,7 +16,7 @@
.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("maxticketsperuser").setDescription("The maximum amount of tickets a user can create | Default 5").setRequired(false).setMinValue(1))
+ .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 = async (interaction: CommandInteraction): Promise<any> => {
@@ -139,7 +139,7 @@
}
}
let data = await client.database.guilds.read(interaction.guild.id);
- data.tickets.customTypes = data.tickets.customTypes.filter((v, i, a) => a.indexOf(v) === i)
+ data.tickets.customTypes = (data.tickets.customTypes || []).filter((v, i, a) => a.indexOf(v) === i)
let lastClicked = "";
let embed;
data = {
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index a9f0075..d032598 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -1,7 +1,7 @@
import { CommandInteraction } from "discord.js";
import { SlashCommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
-import { callback as statsChannelAdd } from '../automations/statsChannelAdd.js';
+import { callback as statsChannelAdd } from '../reflex/statsChannelAdd.js';
import client from "../utils/client.js"
const command = new SlashCommandBuilder()
diff --git a/src/commands/ticket/close.ts b/src/commands/ticket/close.ts
index 15abb0a..5a3371e 100644
--- a/src/commands/ticket/close.ts
+++ b/src/commands/ticket/close.ts
@@ -1,7 +1,7 @@
import { CommandInteraction } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
-import close from "../../automations/tickets/delete.js";
+import close from "../../actions/tickets/delete.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
diff --git a/src/commands/ticket/create.ts b/src/commands/ticket/create.ts
index c44e576..3d796aa 100644
--- a/src/commands/ticket/create.ts
+++ b/src/commands/ticket/create.ts
@@ -1,7 +1,7 @@
import { CommandInteraction } from "discord.js";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
-import create from "../../automations/tickets/create.js";
+import create from "../../actions/tickets/create.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
diff --git a/src/commands/user/track.ts b/src/commands/user/track.ts
index da332da..54af9d7 100644
--- a/src/commands/user/track.ts
+++ b/src/commands/user/track.ts
@@ -163,7 +163,7 @@
const check = async (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
let tracks = (await client.database.guilds.read(interaction.guild.id)).tracks
- if (!tracks) throw "This server does not have any tracks"
+ if (tracks.length === 0) throw "This server does not have any tracks"
let member = (interaction.member as GuildMember)
// Allow the owner to promote anyone
if (member.id == interaction.guild.ownerId) return true
diff --git a/src/commands/verify.ts b/src/commands/verify.ts
index 4ccc807..bd71fe4 100644
--- a/src/commands/verify.ts
+++ b/src/commands/verify.ts
@@ -1,7 +1,7 @@
import { CommandInteraction } from "discord.js";
import { SlashCommandBuilder } from "@discordjs/builders";
import { WrappedCheck } from "jshaiku";
-import verify from "../automations/verify.js";
+import verify from "../reflex/verify.js";
const command = new SlashCommandBuilder()
.setName("verify")
diff --git a/src/config/default.json b/src/config/default.json
index 3b0882f..81c7880 100644
--- a/src/config/default.json
+++ b/src/config/default.json
@@ -66,6 +66,10 @@
},
"staff": {
"channel": null
+ },
+ "attachments": {
+ "channel": null,
+ "saved": {}
}
},
"verify": {
diff --git a/src/config/emojis.json b/src/config/emojis.json
index e9fb578..2f32df1 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -20,6 +20,8 @@
"EDIT": "989911820267577366",
"HISTORY": "989911933069168690",
"FILTER": "990242059451514902",
+ "ATTACHMENT": "997570687193587812",
+ "LOGGING": "999613304446144562",
"OPP": {
"ADD": "837355918831124500",
"REMOVE": "837355918420869162"
@@ -40,6 +42,7 @@
},
"CONTROL": {
"TICK": "947441964234702849",
+ "REDTICK": "999612396727439370",
"CROSS": "947441948543815720",
"BLOCKCROSS": "952261738349330493",
"BLOCKTICK": "991805475777695855",
diff --git a/src/events/channelDelete.ts b/src/events/channelDelete.ts
index 5c7139a..ffe3eae 100644
--- a/src/events/channelDelete.ts
+++ b/src/events/channelDelete.ts
@@ -39,7 +39,7 @@
}
}
let list = {
- channelIid: entry(channel.id, `\`${channel.id}\``),
+ channelId: entry(channel.id, `\`${channel.id}\``),
name: entry(channel.id, `${channel.name}`),
topic: null,
type: entry(channel.type, readableType),
diff --git a/src/events/commandError.ts b/src/events/commandError.ts
index 8edb480..6d3672e 100644
--- a/src/events/commandError.ts
+++ b/src/events/commandError.ts
@@ -5,7 +5,7 @@
export async function callback(client, interaction, error) {
if (interaction.replied || interaction.deferred) {
await interaction.followUp({embeds: [new EmojiEmbed()
- .setTitle("Something went wrong")
+ .setTitle("Something went")
.setDescription(error.message ?? error.toString())
.setStatus("Danger")
.setEmoji("CONTROL.BLOCKCROSS")
diff --git a/src/events/guildBanAdd.ts b/src/events/guildBanAdd.ts
index 05b98ed..64a1604 100644
--- a/src/events/guildBanAdd.ts
+++ b/src/events/guildBanAdd.ts
@@ -1,5 +1,5 @@
-import { purgeByUser } from '../automations/tickets/delete.js';
-import { callback as statsChannelRemove } from '../automations/statsChannelRemove.js';
+import { purgeByUser } from '../actions/tickets/delete.js';
+import { callback as statsChannelRemove } from '../reflex/statsChannelRemove.js';
export const event = 'guildBanAdd';
diff --git a/src/events/guildBanRemove.ts b/src/events/guildBanRemove.ts
index 85dc2af..a6d1c14 100644
--- a/src/events/guildBanRemove.ts
+++ b/src/events/guildBanRemove.ts
@@ -1,6 +1,6 @@
import humanizeDuration from 'humanize-duration';
-import { purgeByUser } from '../automations/tickets/delete.js';
-import { callback as statsChannelRemove } from '../automations/statsChannelRemove.js';
+import { purgeByUser } from '../actions/tickets/delete.js';
+import { callback as statsChannelRemove } from '../reflex/statsChannelRemove.js';
export const event = 'guildBanRemove';
diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts
index 10986a5..413e5fb 100644
--- a/src/events/guildCreate.ts
+++ b/src/events/guildCreate.ts
@@ -1,7 +1,7 @@
import { MessageActionRow, MessageButton } from "discord.js";
import EmojiEmbed from "../utils/generateEmojiEmbed.js";
import getEmojiByName from "../utils/getEmojiByName.js";
-import guide from "../automations/guide.js";
+import guide from "../reflex/guide.js";
export const event = 'guildCreate';
diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts
index dab89fa..e68aee4 100644
--- a/src/events/guildMemberUpdate.ts
+++ b/src/events/guildMemberUpdate.ts
@@ -1,15 +1,12 @@
-import { callback as statsChannelAdd } from '../automations/statsChannelAdd.js';
-import { callback as welcome } from '../automations/welcome.js';
-import log from '../utils/log.js';
export const event = 'guildMemberUpdate'
export async function callback(client, before, after) {
try {
const { log, NucleusColors, entry, renderUser, renderDelta, getAuditLog } = after.client.logger
+ let auditLog = await getAuditLog(after.guild, 'MEMBER_UPDATE');
+ let audit = auditLog.entries.filter(entry => entry.target.id == after.id).first();
+ if (audit.executor.id == client.user.id) return;
if (before.nickname != after.nickname) {
- let auditLog = await getAuditLog(after.guild, 'MEMBER_UPDATE');
- let audit = auditLog.entries.filter(entry => entry.target.id == after.id).first();
- if (audit.executor.id == client.user.id) return;
try { await client.database.history.create(
"nickname", after.guild.id, after.user, audit.executor,
null, before.nickname || before.user.username, after.nickname || after.user.username) } catch {}
@@ -24,16 +21,74 @@
},
list: {
memberId: entry(after.id, `\`${after.id}\``),
+ name: entry(after.user.id, renderUser(after.user)),
before: entry(before.nickname, before.nickname ? before.nickname : '*None*'),
after: entry(after.nickname, after.nickname ? after.nickname : '*None*'),
- updated: entry(new Date().getTime(), renderDelta(new Date().getTime())),
- updatedBy: entry(audit.executor.id, renderUser(audit.executor))
+ changed: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+ changedBy: entry(audit.executor.id, renderUser(audit.executor))
},
hidden: {
guild: after.guild.id
}
}
log(data);
+ } else if (before.communicationDisabledUntilTimestamp < new Date().getTime() && after.communicationDisabledUntil > new Date().getTime()) {
+ try { await client.database.history.create(
+ "mute", after.guild.id, after.user, audit.executor, audit.reason, null, null, null
+ )} catch {}
+ let data = {
+ meta: {
+ type: 'memberMute',
+ displayName: 'Muted',
+ calculateType: 'guildMemberPunish',
+ color: NucleusColors.yellow,
+ emoji: "PUNISH.MUTE.YELLOW",
+ timestamp: new Date().getTime()
+ },
+ list: {
+ memberId: entry(after.id, `\`${after.id}\``),
+ name: entry(after.user.id, renderUser(after.user)),
+ mutedUntil: entry(after.communicationDisabledUntilTimestamp, renderDelta(after.communicationDisabledUntilTimestamp)),
+ muted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+ mutedBy: entry(audit.executor.id, renderUser(audit.executor)),
+ reason: entry(audit.reason, audit.reason ? audit.reason : '\n> *No reason provided*')
+ },
+ hidden: {
+ guild: after.guild.id
+ }
+ }
+ log(data);
+ client.database.eventScheduler.schedule("naturalUnmute", after.communicationDisabledUntil,
+ {guild: after.guild.id, user: after.id, expires: after.communicationDisabledUntilTimestamp}
+ );
+ } else if (
+ after.communicationDisabledUntil === null && before.communicationDisabledUntilTimestamp !== null &&
+ new Date().getTime() >= audit.createdTimestamp
+ ) {
+ try { await client.database.history.create(
+ "unmute", after.guild.id, after.user, audit.executor, null, null, null, null
+ )} catch {}
+ let data = {
+ meta: {
+ type: 'memberUnmute',
+ displayName: 'Unmuted',
+ calculateType: 'guildMemberPunish',
+ color: NucleusColors.green,
+ emoji: "PUNISH.MUTE.GREEN",
+ timestamp: new Date().getTime()
+ },
+ list: {
+ memberId: entry(after.id, `\`${after.id}\``),
+ name: entry(after.user.id, renderUser(after.user)),
+ unmuted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+ unmutedBy: entry(audit.executor.id, renderUser(audit.executor))
+ },
+ hidden: {
+ guild: after.guild.id
+ }
+ }
+ log(data);
+ client.database.eventScheduler.cancel("naturalUnmute", {guild: after.guild.id, user: after.id, expires: before.communicationDisabledUntilTimestamp});
}
} catch (e) { console.log(e) }
}
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
index 3470450..1fd1e1f 100644
--- a/src/events/interactionCreate.ts
+++ b/src/events/interactionCreate.ts
@@ -1,7 +1,8 @@
-import { callback as roleMenu } from "../automations/roleMenu.js"
-import verify from "../automations/verify.js";
-import create from "../automations/tickets/create.js";
-import close from "../automations/tickets/delete.js";
+import { callback as roleMenu } from "../actions/roleMenu.js"
+import verify from "../reflex/verify.js";
+import create from "../actions/tickets/create.js";
+import close from "../actions/tickets/delete.js";
+import createTranscript from "../premium/createTranscript.js";
export const event = 'interactionCreate';
@@ -11,6 +12,7 @@
if (interaction.customId === "verifybutton") return verify(interaction)
if (interaction.customId === "createticket") return create(interaction)
if (interaction.customId === "closeticket") return close(interaction)
+ if (interaction.customId === "createtranscript") return createTranscript(interaction)
} else if (interaction.componentType === "MESSAGE_COMPONENT") {
console.table(interaction)
}
diff --git a/src/events/memberJoin.ts b/src/events/memberJoin.ts
index 2031b12..b1c2700 100644
--- a/src/events/memberJoin.ts
+++ b/src/events/memberJoin.ts
@@ -1,5 +1,5 @@
-import { callback as statsChannelAdd } from '../automations/statsChannelAdd.js';
-import { callback as welcome } from '../automations/welcome.js';
+import { callback as statsChannelAdd } from '../reflex/statsChannelAdd.js';
+import { callback as welcome } from '../reflex/welcome.js';
import log from '../utils/log.js';
import client from '../utils/client.js';
diff --git a/src/events/memberLeave.ts b/src/events/memberLeave.ts
index b9d6e84..592a630 100644
--- a/src/events/memberLeave.ts
+++ b/src/events/memberLeave.ts
@@ -1,6 +1,6 @@
import humanizeDuration from 'humanize-duration';
-import { purgeByUser } from '../automations/tickets/delete.js';
-import { callback as statsChannelRemove } from '../automations/statsChannelRemove.js';
+import { purgeByUser } from '../actions/tickets/delete.js';
+import { callback as statsChannelRemove } from '../reflex/statsChannelRemove.js';
export const event = 'guildMemberRemove'
diff --git a/src/events/messageChecks.ts b/src/events/messageChecks.ts
deleted file mode 100644
index 2755ff1..0000000
--- a/src/events/messageChecks.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { LinkCheck, MalwareCheck, NSFWCheck, SizeCheck, TestString, TestImage } from '../automations/unscan.js'
-import { Message } from 'discord.js'
-import client from '../utils/client.js'
-
-export const event = 'messageCreate'
-
-export async function callback(client, message) {
- const { log, NucleusColors, entry, renderUser } = client.logger
- if (message.author.bot) return
- if (message.channel.type === 'dm') return
-
- let content = message.content.toLowerCase() || ''
- let config = await client.memory.readGuildInfo(message.guild.id);
-
- if (config.filters.invite.enabled) {
- if (!config.filters.invite.allowed.users.includes(message.author.id) ||
- !config.filters.invite.allowed.channels.includes(message.channel.id) ||
- !message.author.roles.cache.some(role => config.filters.invite.allowed.roles.includes(role.id))
- ) {
- if ((/(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/.test(content))) {
- return message.delete();
- }
- }
- }
-
- let attachments = message.attachments.map(element => element)
- attachments = [...attachments, ...content.match(
- /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi
- ) ?? []].filter(element => (element.url ? element.url : element))
- if (attachments.length > 0) {
- attachments.forEach(async element => {
- if(!message) return;
- let url = element.url ? element.url : element
- if (url != undefined) {
- if(/\.+(webp|png|jpg|jpeg|bmp)/.test(url)) {
- if (config.filters.images.NSFW && !message.channel.nsfw) {
- if (await NSFWCheck(url)) {
- return await message.delete()
- }
- }
- if (config.filters.images.size) {
- if(!url.match(/\.+(webp|png|jpg)$/gi)) return
- if(!await SizeCheck(element)) {
- return await message.delete()
- }
- }
- }
- if (config.filters.malware) {
- if (!MalwareCheck(url)) {
- return await message.delete()
- }
- }
- }
- });
- }
- if(!message) return;
-
- if (await LinkCheck(message)) {
- return await message.delete()
- }
-
- if (config.filters.wordFilter.enabled) {
- let check = TestString(content, config.filters.wordFilter.words.loose, config.filters.wordFilter.words.strict)
- if(check !== null) {
- return await message.delete()
- }
- }
-
- if (!config.filters.pings.allowed.users.includes(message.author.id) ||
- !config.filters.pings.allowed.channels.includes(message.channel.id) ||
- !message.author.roles.cache.some(role => config.filters.pings.allowed.roles.includes(role.id))
- ) {
- if (config.filters.pings.everyone && message.mentions.everyone) {
- return message.delete();
- }
- if (config.filters.pings.roles) {
- for(let role of message.mentions.roles) {
- if(!message) return;
- if (!config.filters.pings.allowed.roles.includes(role.id)) {
- return message.delete();
- }
- }
- }
- if(!message) return;
- if (message.mentions.users.size >= config.filters.pings.mass && config.filters.pings.mass) {
- return message.delete();
- }
- }
-}
diff --git a/src/events/messageDelete.ts b/src/events/messageDelete.ts
index f0fbb57..2263d51 100644
--- a/src/events/messageDelete.ts
+++ b/src/events/messageDelete.ts
@@ -3,6 +3,7 @@
export async function callback(client, message) {
try {
if (message.author.id == client.user.id) return;
+ if (client.noLog.includes(`${message.guild.id}/${message.channel.id}/${message.id}`)) return;
const { getAuditLog, log, NucleusColors, entry, renderUser, renderDelta, renderChannel } = message.channel.client.logger
let auditLog = await getAuditLog(message.guild, 'MEMBER_BAN_ADD')
let audit = auditLog.entries.filter(entry => entry.target.id == message.author.id).first();
@@ -11,7 +12,11 @@
}
message.reference = message.reference || {}
let content = message.cleanContent
+ content.replace(`\``, `\\\``)
if (content.length > 256) content = content.substring(0, 253) + '...'
+ let attachmentJump = ""
+ let config = (await client.database.guilds.read(message.guild.id)).logging.attachments.saved[message.channel.id + message.id];
+ if (config) { attachmentJump = ` [[View attachments]](${config})` }
let data = {
meta: {
type: 'messageDelete',
@@ -30,7 +35,7 @@
sentIn: entry(message.channel.id, renderChannel(message.channel)),
deleted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
mentions: message.mentions.users.size,
- attachments: message.attachments.size,
+ attachments: entry(message.attachments.size, message.attachments.size + attachmentJump),
repliedTo: entry(
message.reference.messageId || null,
message.reference.messageId ? `[[Jump to message]](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.reference.messageId})` : "None"
@@ -41,5 +46,5 @@
}
}
log(data);
- } catch {}
+ } catch(e) { console.log(e) }
}
diff --git a/src/events/messageEdit.ts b/src/events/messageEdit.ts
index 3502963..5e63797 100644
--- a/src/events/messageEdit.ts
+++ b/src/events/messageEdit.ts
@@ -7,6 +7,9 @@
newMessage.reference = newMessage.reference || {}
let newContent = newMessage.cleanContent.replaceAll("`", "‘")
let oldContent = oldMessage.cleanContent.replaceAll("`", "‘")
+ let attachmentJump = "";
+ let config = (await client.database.guilds.read(newMessage.guild.id)).logging.attachments.saved[newMessage.channel.id + newMessage.id];
+ if (config) { attachmentJump = ` [[View attachments]](${config})` }
if (newContent == oldContent) {
if (!oldMessage.flags.has("CROSSPOSTED") && newMessage.flags.has("CROSSPOSTED")) {
let data = {
@@ -28,16 +31,19 @@
sent: entry(new Date(newMessage.createdTimestamp), renderDelta(new Date(newMessage.createdTimestamp))),
published: entry(new Date(newMessage.editedTimestamp), renderDelta(new Date(newMessage.editedTimestamp))),
mentions: renderNumberDelta(oldMessage.mentions.users.size, newMessage.mentions.users.size),
- attachments: renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size)
+ attachments: entry(
+ renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size),
+ renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size) + attachmentJump
+ )
},
hidden: {
guild: newMessage.channel.guild.id
}
}
- log(data);
+ return log(data);
}
- return
};
+ if (!newMessage.editedTimestamp) { return }
if (newContent.length > 256) newContent = newContent.substring(0, 253) + '...'
if (oldContent.length > 256) oldContent = oldContent.substring(0, 253) + '...'
let data = {
@@ -61,7 +67,10 @@
sent: entry(new Date(newMessage.createdTimestamp), renderDelta(new Date(newMessage.createdTimestamp))),
edited: entry(new Date(newMessage.editedTimestamp), renderDelta(new Date(newMessage.editedTimestamp))),
mentions: renderNumberDelta(oldMessage.mentions.users.size, newMessage.mentions.users.size),
- attachments: renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size),
+ attachments: entry(
+ renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size),
+ renderNumberDelta(oldMessage.attachments.size, newMessage.attachments.size) + attachmentJump
+ ),
repliedTo: entry(
newMessage.reference.messageId || null,
newMessage.reference.messageId ? `[[Jump to message]](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${newMessage.reference.messageId})` : "None"
diff --git a/src/events/roleUpdate.ts b/src/events/roleUpdate.ts
index baf4399..7c6ca75 100644
--- a/src/events/roleUpdate.ts
+++ b/src/events/roleUpdate.ts
@@ -13,7 +13,7 @@
let changes = {
roleId: entry(nr.id, `\`${nr.id}\``),
role: entry(nr.id, renderRole(nr)),
- edited: entry(nr.createdTimestamp, renderDelta(nr.createdTimestamp)),
+ edited: entry(new Date().getTime(), renderDelta(new Date().getTime())),
editedBy: entry(audit.executor.id, renderUser((await nr.guild.members.fetch(audit.executor.id)).user)),
}
let mentionable = ["", ""]
@@ -28,6 +28,8 @@
if (or.mentionable != nr.mentionable) changes["mentionable"] = entry([or.mentionable, nr.mentionable], `${mentionable[0]} -> ${mentionable[1]}`);
if (or.hexColor != nr.hexColor) changes["color"] = entry([or.hexColor, nr.hexColor], `\`${or.hexColor}\` -> \`${nr.hexColor}\``);
+ if (Object.keys(changes).length == 4) return
+
let data = {
meta:{
type: 'roleUpdate',
@@ -41,7 +43,7 @@
hidden: {
guild: nr.guild.id
}
- } // TODO: show perms changed
+ } // TODO: show perms changed (webpage)
log(data);
} catch {}
}
\ No newline at end of file
diff --git a/src/events/webhookUpdate.ts b/src/events/webhookUpdate.ts
index a20608c..66b1cd0 100644
--- a/src/events/webhookUpdate.ts
+++ b/src/events/webhookUpdate.ts
@@ -72,7 +72,7 @@
hidden: {
guild: channel.guild.id
}
- } // TODO
+ }
log(data);
} catch(e) { console.log(e) }
}
diff --git a/src/index.ts b/src/index.ts
index 35afc14..0ae917c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,7 +2,7 @@
import { Logger } from './utils/log.js';
import runServer from './api/index.js';
import Memory from './utils/memory.js';
-import { Guilds, History, ModNotes, EventSchedulerDatabase } from './utils/database.js';
+import { Guilds, History, ModNotes, Premium } from './utils/database.js';
import client from './utils/client.js';
import EventScheduler from './utils/eventScheduler.js';
@@ -16,11 +16,13 @@
client.verify = {}
client.roleMenu = {}
client.memory = new Memory()
+client.noLog = []
client.database = {
guilds: await new Guilds().setup(),
history: await new History().setup(),
notes: await new ModNotes().setup(),
- eventScheduler: new EventSchedulerDatabase()
+ premium: await new Premium().setup(),
+ eventScheduler: await new EventScheduler().start()
}
await client.login();
\ No newline at end of file
diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts
index 7f3b16d..6b8c058 100644
--- a/src/utils/calculate.ts
+++ b/src/utils/calculate.ts
@@ -11,14 +11,14 @@
"messageDelete",
"messageDeleteBulk",
"messageReactionUpdate",
- "messagePing",
"messageMassPing",
"messageAnnounce",
"threadUpdate",
"webhookUpdate",
"guildMemberVerify",
- "autoModeratorDeleted", // TODO: Not implemented
- "nucleusSettingsUpdated"
+ "autoModeratorDeleted",
+ "nucleusSettingsUpdated",
+ "ticketUpdate"
]
const tickets = [
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index 7eaa369..da10cfb 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -1,33 +1,27 @@
-import Discord, { CommandInteraction, MessageActionRow, Message } from "discord.js";
+import Discord, { CommandInteraction, MessageActionRow, Message, MessageButton, TextInputComponent } from "discord.js";
+import { modalInteractionCollector } from "./dualCollector.js";
import EmojiEmbed from "./generateEmojiEmbed.js"
import getEmojiByName from "./getEmojiByName.js";
class confirmationMessage {
interaction: CommandInteraction;
- title: string;
- emoji: string;
- description: string;
- color: string;
- customCallback: () => any;
+ title: string = "";
+ emoji: string = "";
+ description: string = "";
+ color: string = "";
+ customCallback: () => any = () => {};
customButtonTitle: string;
customButtonDisabled: boolean;
customCallbackString: string = "";
customCallbackClicked: boolean = false;
customCallbackResponse: any = null;
- customBoolean: () => any;
+ customBoolean: () => any = () => {}; // allow multiple booleans
customBooleanClicked: boolean = null;
- inverted: boolean;
+ inverted: boolean = false;
+ reason: string | null = null;
constructor(interaction: CommandInteraction) {
this.interaction = interaction;
-
- this.title = "";
- this.emoji = "";
- this.description = "";
- this.color = "";
- this.inverted = false;
- this.customCallback = () => {};
- this.customBoolean = () => {};
}
setTitle(title: string) { this.title = title; return this }
@@ -52,8 +46,10 @@
this.customBooleanClicked = false;
return this;
}
-
-
+ addReasonButton(reason: string) {
+ this.reason = reason;
+ return this;
+ }
async send(editOnly?: boolean) {
while (true) {
let object = {
@@ -86,6 +82,12 @@
)
.setDisabled(this.customButtonDisabled)
.setEmoji(getEmojiByName("CONTROL.TICKET", "id"))
+ ] : [])
+ .concat(this.reason !== null ? [new Discord.MessageButton()
+ .setCustomId("reason")
+ .setLabel(`Edit Reason`)
+ .setStyle("PRIMARY")
+ .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
] : []))
],
ephemeral: true,
@@ -132,9 +134,42 @@
this.customButtonDisabled = true;
}
editOnly = true;
+ } else if (component.customId === "reason") {
+ await component.showModal(new Discord.Modal().setCustomId("modal").setTitle(`Editing reason`).addComponents(
+ // @ts-ignore
+ new MessageActionRow().addComponents(new TextInputComponent()
+ .setCustomId("reason")
+ .setLabel("Reason")
+ .setMaxLength(2000)
+ .setRequired(false)
+ .setStyle("PARAGRAPH")
+ .setPlaceholder("Spammed in #general")
+ .setValue(this.reason ? this.reason : "")
+ )
+ ))
+ await this.interaction.editReply({
+ embeds: [new EmojiEmbed()
+ .setTitle(this.title)
+ .setDescription("Modal opened. If you can't see it, click back and try again.")
+ .setStatus(this.color)
+ .setEmoji(this.emoji)
+ ], 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 == this.interaction.channel.id, (m) => m.customId == "reason")
+ } catch (e) { continue }
+ if (out.fields) {
+ return {newReason: out.fields.getTextInputValue("reason") ?? ""};
+ } else { return { newReason: this.reason } }
}
}
}
}
-export default confirmationMessage;
\ No newline at end of file
+export default confirmationMessage;
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 66d7e67..c0ae9be 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -132,26 +132,17 @@
}
}
-export class EventSchedulerDatabase {
- events: Collection<EventSchedulerSchema>;
- defaultData: GuildConfig;
+export class Premium {
+ premium: Collection<PremiumSchema>;
async setup() {
- this.events = database.collection<EventSchedulerSchema>("eventScheduler");
+ this.premium = database.collection<PremiumSchema>("premium");
return this;
}
- async create(timestamp: Date, data: object) {
- await this.events.insertOne({ timestamp: timestamp, data: data});
- }
-
- async getNext() {
- let entry = await this.events.findOne({ timestamp: { $lte: new Date() }});
- return entry;
- }
-
- async remove(timestamp: Date, data: object) {
- await this.events.deleteOne({ timestamp: timestamp, data: data});
+ async hasPremium(guild: string) {
+ let entry = await this.premium.findOne({ appliesTo: { $in: [guild] } });
+ return entry != null;
}
}
@@ -222,6 +213,10 @@
},
staff: {
channel: string | null,
+ },
+ attachments: {
+ channel: string | null,
+ saved: {} // {channelID+messageID: log url (string)}
}
}
verify: {
@@ -307,7 +302,9 @@
note: string
}
-export interface EventSchedulerSchema {
- timestamp: Date,
- data: object
+export interface PremiumSchema {
+ user: string,
+ level: number,
+ expires: Date,
+ appliesTo: string[]
}
\ No newline at end of file
diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts
index 9210883..396b202 100644
--- a/src/utils/eventScheduler.ts
+++ b/src/utils/eventScheduler.ts
@@ -1,37 +1,70 @@
-import { EventSchedulerDatabase } from './database';
+import { Agenda } from "agenda/es.js";
import client from './client.js';
+import * as fs from 'fs';
+import * as path from 'path';
+import config from '../config/main.json' assert {type: 'json'};
class EventScheduler {
- database: any;
- next: {timestamp: Date, data: object, responded: boolean} | {};
+ private agenda: Agenda;
constructor() {
- this.database = EventSchedulerDatabase;
- this.next = {};
+ this.agenda = new Agenda({db: {address: config.mongoUrl + "Nucleus", collection: 'eventScheduler'}})
+
+ this.agenda.define("unmuteRole", async (job: Agenda.job) => {
+ let guild = await client.guilds.fetch(job.attrs.data.guild);
+ let user = await guild.members.fetch(job.attrs.data.user);
+ let role = await guild.roles.fetch(job.attrs.data.role);
+ await user.roles.remove(role);
+ await job.remove();
+ })
+ this.agenda.define("deleteFile", async (job: Agenda.job) => {
+ fs.rm(path.resolve("dist/utils/temp", job.attrs.data.fileName), (err) => {})
+ await job.remove();
+ })
+ this.agenda.define("naturalUnmute", async (job: Agenda.job) => {
+ const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger
+ let guild = await client.guilds.fetch(job.attrs.data.guild);
+ let user = await guild.members.fetch(job.attrs.data.user);
+ if (user.communicationDisabledUntil === null) return
+ try { await client.database.history.create(
+ "unmute", user.guild.id, user.user, null, null, null, null
+ )} catch {}
+ let data = {
+ meta: {
+ type: 'memberUnmute',
+ displayName: 'Unmuted',
+ calculateType: 'guildMemberPunish',
+ color: NucleusColors.green,
+ emoji: "PUNISH.MUTE.GREEN",
+ timestamp: new Date().getTime()
+ },
+ list: {
+ memberId: entry(user.user.id, `\`${user.user.id}\``),
+ name: entry(user.user.id, renderUser(user.user)),
+ unmuted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+ unmutedBy: entry(null, "*Time out ended*")
+ },
+ hidden: {
+ guild: guild.id
+ }
+ }
+ log(data);
+ })
}
- async create(timestamp: Date, data: object) {
- await this.database.create(timestamp, data);
- if (this.next === {}) {
- this.next = this.next = await this.getNext();
- return
- }
- if (timestamp.getTime() < (this.next as {timestamp: Date}).timestamp.getTime()) {
- this.next = {timestamp: timestamp, data: data, responded: false};
- }
+ async start() {
+ await new Promise(resolve => this.agenda.once('ready', resolve));
+ this.agenda.start()
+ return this
}
- async getNext() {
- let entry = await this.database.getNext();
- if (entry) {
- this.next = entry;
- }
- return this.next;
+ async schedule(name: string, time: string, data: any) {
+ await this.agenda.schedule(time, name, data)
}
- async delete(timestamp: Date, data: object) {
- await this.database.delete(timestamp, data);
- } // TODO: add a loop
+ cancel(name: string, data: Object) {
+ this.agenda.cancel({name, data})
+ }
}
export default EventScheduler;
\ No newline at end of file
diff --git a/src/utils/generateKeyValueList.ts b/src/utils/generateKeyValueList.ts
index c9eb0c8..1a76fa7 100644
--- a/src/utils/generateKeyValueList.ts
+++ b/src/utils/generateKeyValueList.ts
@@ -1,14 +1,18 @@
const forceCaps = [
"ID",
- "NSFW"
+ "NSFW",
+ "URL"
]
export function capitalize(s: string) {
s = s.replace(/([A-Z])/g, ' $1');
- return forceCaps.includes(s.toUpperCase()) ? s.toUpperCase() : s[0]
- .toUpperCase() + s.slice(1)
- .toLowerCase()
- .replace("discord", "Discord");
+ s = s.split(" ").map(word => {
+ return forceCaps.includes(word.toUpperCase()) ? word.toUpperCase() : word[0]
+ .toUpperCase() + word.slice(1)
+ .toLowerCase()
+ .replace("discord", "Discord")
+ }).join(" ");
+ return s
}
export function toCapitals(s: string) {
diff --git a/src/utils/plurals.ts b/src/utils/plurals.ts
index f956057..48b889c 100644
--- a/src/utils/plurals.ts
+++ b/src/utils/plurals.ts
@@ -1,4 +1,5 @@
function addPlural(amount: any, unit: string) {
+ amount = amount.toString();
if (amount === '1') return `${amount} ${unit}`
return `${amount} ${unit}s`
}
diff --git a/src/utils/scanners.ts b/src/utils/scanners.ts
deleted file mode 100644
index 5abd726..0000000
--- a/src/utils/scanners.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as us from 'unscan'
-import fetch from 'node-fetch'
-import { writeFileSync } from 'fs'
-import generateFileName from './temp/generateFileName.js'
-import * as path from 'path'
-import {fileURLToPath} from 'url';
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-export async function testNSFW(link: string): Promise<JSON> {
- const image = (await (await fetch(link)).buffer()).toString('base64')
- let fileName = generateFileName(link.split('/').pop().split('.').pop())
- let p = path.join(__dirname, '/temp', fileName)
- writeFileSync(p, image, 'base64')
- let result = await us.nsfw.file(p)
- return result
-}
-
-export async function testMalware(link: string): Promise<JSON> {
- const file = (await (await fetch(link)).buffer()).toString('base64')
- let fileName = generateFileName(link.split('/').pop().split('.').pop())
- let p = path.join(__dirname, '/temp', fileName)
- writeFileSync(p, file, 'base64')
- let result = await us.malware.file(p)
- return result
-}
-
-export async function testLink(link: string): Promise<JSON> {
- return await us.link.scan(link)
-}
diff --git a/src/utils/temp/generateFileName.ts b/src/utils/temp/generateFileName.ts
index 98240a1..f9662ad 100644
--- a/src/utils/temp/generateFileName.ts
+++ b/src/utils/temp/generateFileName.ts
@@ -1,5 +1,10 @@
import * as fs from 'fs';
import * as crypto from 'crypto';
+import client from '../client.js';
+import * as path from 'path'
+import {fileURLToPath} from 'url';
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
export default function generateFileName(ending: string): string {
let fileName = crypto.randomBytes(35).toString('hex');
@@ -7,5 +12,6 @@
if (fs.existsSync(`./${fileName}`)) {
fileName = generateFileName(ending);
}
- return fileName + '.' + ending;
-}
\ No newline at end of file
+ client.database.eventScheduler.schedule("deleteFile", new Date().getTime() + (60 * 1000), {fileName: `${fileName}.${ending}`});
+ return path.join(__dirname, fileName + '.' + ending);
+}