Development (#11)
We need this NOW.
---------
Co-authored-by: PineaFan <ash@pinea.dev>
Co-authored-by: pineafan <pineapplefanyt@gmail.com>
Co-authored-by: PineappleFan <PineaFan@users.noreply.github.com>
Co-authored-by: Skyler <skyler3665@gmail.com>
diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts
index 0bd5a9f..fde1340 100644
--- a/src/utils/calculate.ts
+++ b/src/utils/calculate.ts
@@ -17,16 +17,14 @@
"webhookUpdate",
"guildMemberVerify",
"autoModeratorDeleted",
- "nucleusSettingsUpdated",
- "ticketUpdate"
+ "ticketUpdate",
+ // "nucleusSettingsUpdated"
];
const tickets = ["support", "report", "question", "issue", "suggestion", "other"];
const toHexInteger = (permissions: string[], array?: string[]): string => {
- if (!array) {
- array = logs;
- }
+ if (!array) { array = logs; }
let int = 0n;
for (const perm of permissions) {
diff --git a/src/utils/client.ts b/src/utils/client.ts
index 46d9f92..b1fa31f 100644
--- a/src/utils/client.ts
+++ b/src/utils/client.ts
@@ -1,11 +1,11 @@
-import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits } from 'discord.js';
+import Discord, { Client, Interaction, AutocompleteInteraction, Collection } from 'discord.js';
import { Logger } from "../utils/log.js";
import Memory from "../utils/memory.js";
import type { VerifySchema } from "../reflex/verify.js";
-import { Guilds, History, ModNotes, Premium, PerformanceTest } from "../utils/database.js";
+import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache, Transcript, } from "../utils/database.js";
import EventScheduler from "../utils/eventScheduler.js";
import type { RoleMenuSchema } from "../actions/roleMenu.js";
-import config from "../config/main.json" assert { type: "json" };
+import config from "../config/main.js";
class NucleusClient extends Client {
@@ -22,36 +22,33 @@
premium: Premium;
eventScheduler: EventScheduler;
performanceTest: PerformanceTest;
+ scanCache: ScanCache;
+ transcripts: Transcript
};
preloadPage: Record<string, {command: string, argument: string}> = {}; // e.g. { channelID: { command: privacy, page: 3}}
- commands: Record<string, {
+ commands: Record<string, [{
command: Discord.SlashCommandBuilder |
((builder: Discord.SlashCommandBuilder) => Discord.SlashCommandBuilder) |
Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder),
callback: (interaction: Interaction) => Promise<void>,
- check: (interaction: Interaction) => Promise<boolean> | boolean,
+ check: (interaction: Interaction, partial: boolean) => Promise<boolean> | boolean,
autocomplete: (interaction: AutocompleteInteraction) => Promise<string[]>
- }> = {};
-
+ } | undefined, {name: string, description: string}]> = {};
+ fetchedCommands = new Collection<string, Discord.ApplicationCommand>();
constructor(database: typeof NucleusClient.prototype.database) {
- super({ intents: [
- GatewayIntentBits.Guilds,
- GatewayIntentBits.GuildMessages,
- GatewayIntentBits.MessageContent,
- GatewayIntentBits.GuildPresences,
- GatewayIntentBits.GuildMembers
- ]});
+ super({ intents: 0b1100011011011111111111});
this.database = database;
}
}
-
const client = new NucleusClient({
- guilds: await new Guilds().setup(),
+ guilds: await new Guilds(),
history: new History(),
notes: new ModNotes(),
premium: new Premium(),
eventScheduler: new EventScheduler(),
- performanceTest: new PerformanceTest()
+ performanceTest: new PerformanceTest(),
+ scanCache: new ScanCache(),
+ transcripts: new Transcript()
});
export default client;
diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts
index 281be18..33c88b0 100644
--- a/src/utils/commandRegistration/register.ts
+++ b/src/utils/commandRegistration/register.ts
@@ -1,12 +1,12 @@
import type { CommandInteraction } from 'discord.js';
import Discord, { Interaction, SlashCommandBuilder, ApplicationCommandType } from 'discord.js';
-import config from "../../config/main.json" assert { type: "json" };
+import config from "../../config/main.js";
import client from "../client.js";
import fs from "fs";
import EmojiEmbed from '../generateEmojiEmbed.js';
import getEmojiByName from '../getEmojiByName.js';
-const colours = {
+const colors = {
red: "\x1b[31m",
green: "\x1b[32m",
yellow: "\x1b[33m",
@@ -26,23 +26,26 @@
for (const file of files) {
const last = i === files.length - 1 ? "└" : "├";
if (file.isDirectory()) {
- console.log(`${last}─ ${colours.yellow}Loading subcommands of ${file.name}${colours.none}`)
- const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)).command;
- commands.push(fetched);
+ console.log(`${last}─ ${colors.yellow}Loading subcommands of ${file.name}${colors.none}`)
+ const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`));
+ commands.push(fetched.command);
} else if (file.name.endsWith(".js")) {
- console.log(`${last}─ ${colours.yellow}Loading command ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading command ${file.name}${colors.none}`)
const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`));
fetched.command.setDMPermission(fetched.allowedInDMs ?? false)
fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {})
fetched.command.setDescriptionLocalizations(fetched.descriptionLocalizations ?? {})
- if (fetched.nameLocalizations || fetched.descriptionLocalizations) console.log("AAAAA")
+ // if (fetched.nameLocalizations || fetched.descriptionLocalizations)
commands.push(fetched.command);
- client.commands["commands/" + fetched.command.name] = fetched;
+ client.commands["commands/" + fetched.command.name] = [
+ fetched,
+ {name: fetched.name ?? fetched.command.name, description: fetched.description ?? fetched.command.description}
+ ];
}
i++;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${files.length}]${colors.none}`)
}
- console.log(`${colours.yellow}Loaded ${commands.length} commands, processing...`)
+ console.log(`${colors.yellow}Loaded ${commands.length} commands, processing...`)
const processed = []
for (const subcommand of commands) {
@@ -53,7 +56,7 @@
}
}
- console.log(`${colours.green}Processed ${processed.length} commands`)
+ console.log(`${colors.green}Processed ${processed.length} commands${colors.none}`)
return processed;
};
@@ -70,15 +73,15 @@
const last = i === files.length - 1 ? "└" : "├";
i++;
try {
- console.log(`${last}─ ${colours.yellow}Loading event ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading event ${file.name}${colors.none}`)
const event = (await import(`../../../${config.eventsFolder}/${file.name}`));
client.on(event.event, event.callback.bind(null, client));
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${files.length}]${colors.none}`)
} catch (e) {
errors++;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${files.length}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${files.length}]${colors.none}`)
}
}
console.log(`Loaded ${files.length - errors} events (${errors} failed)`)
@@ -101,36 +104,36 @@
const last = i === totalFiles - 1 ? "└" : "├";
i++;
try {
- console.log(`${last}─ ${colours.yellow}Loading message context menu ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading message context menu ${file.name}${colors.none}`)
const context = (await import(`../../../${config.messageContextFolder}/${file.name}`));
context.command.setType(ApplicationCommandType.Message);
context.command.setDMPermission(context.allowedInDMs ?? false)
context.command.setNameLocalizations(context.nameLocalizations ?? {})
commands.push(context.command);
- client.commands["contextCommands/message/" + context.command.name] = context;
+ client.commands["contextCommands/message/" + context.command.name] = [context, {name: context.name ?? context.command.name, description: context.description ?? context.command.description}];
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`)
} catch (e) {
errors++;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${totalFiles}] | ${e}${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${totalFiles}] | ${e}${colors.none}`)
}
}
for (const file of userFiles) {
const last = i === totalFiles - 1 ? "└" : "├";
i++;
try {
- console.log(`${last}─ ${colours.yellow}Loading user context menu ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading user context menu ${file.name}${colors.none}`)
const context = (await import(`../../../${config.userContextFolder}/${file.name}`));
context.command.setType(ApplicationCommandType.User);
commands.push(context.command);
client.commands["contextCommands/user/" + context.command.name] = context;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`)
} catch (e) {
errors++;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${totalFiles}]${colors.none}`)
}
}
@@ -142,11 +145,11 @@
client.on("interactionCreate", async (interaction: Interaction) => {
if (interaction.isUserContextMenuCommand()) {;
const commandName = "contextCommands/user/" + interaction.commandName;
- execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction)
+ execute(client.commands[commandName]![0]?.check, client.commands[commandName]![0]?.callback, interaction)
return;
} else if (interaction.isMessageContextMenuCommand()) {
const commandName = "contextCommands/message/" + interaction.commandName;
- execute(client.commands[commandName]?.check, client.commands[commandName]?.callback, interaction)
+ execute(client.commands[commandName]![0]?.check, client.commands[commandName]![0]?.callback, interaction)
return;
} else if (interaction.isAutocomplete()) {
const commandName = interaction.commandName;
@@ -155,7 +158,7 @@
const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : "");
- const choices = await client.commands[fullCommandName]?.autocomplete(interaction);
+ const choices = await client.commands[fullCommandName]![0]?.autocomplete(interaction);
const formatted = (choices ?? []).map(choice => {
return { name: choice, value: choice }
@@ -168,7 +171,8 @@
const fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : "");
- const command = client.commands[fullCommandName];
+ // console.log(fullCommandName, client.commands[fullCommandName])
+ const command = client.commands[fullCommandName]![0];
const callback = command?.callback;
const check = command?.check;
execute(check, callback, interaction);
@@ -177,6 +181,7 @@
}
async function execute(check: Function | undefined, callback: Function | undefined, data: CommandInteraction) {
+ // console.log(client.commands["contextCommands/user/User info"])
if (!callback) return;
if (check) {
let result;
@@ -189,12 +194,11 @@
if (typeof result === "string") {
const { NucleusColors } = client.logger
return data.reply({embeds: [new EmojiEmbed()
- .setTitle("")
.setDescription(result)
.setColor(NucleusColors.red)
.setEmoji(getEmojiByName("CONTROL.BLOCKCROSS"))
- ]});
- };
+ ], ephemeral: true});
+ }
}
callback(data);
}
@@ -207,17 +211,20 @@
if (process.argv.includes("--update-commands")) {
if (config.enableDevelopment) {
const guild = await client.guilds.fetch(config.developmentGuildID);
- console.log(`${colours.purple}Registering commands in ${guild!.name}${colours.none}`)
+ console.log(`${colors.purple}Registering commands in ${guild!.name}${colors.none}`)
await guild.commands.set(commandList);
} else {
- console.log(`${colours.blue}Registering commands in production mode${colours.none}`)
+ console.log(`${colors.blue}Registering commands in production mode${colors.none}`)
+ const guild = await client.guilds.fetch(config.developmentGuildID);
+ await guild.commands.set([]);
await client.application?.commands.set(commandList);
}
}
await registerCommandHandler();
await registerEvents();
- console.log(`${colours.green}Registered commands, events and context menus${colours.none}`)
+ console.log(`${colors.green}Registered commands, events and context menus${colors.none}`)
console.log(
- (config.enableDevelopment ? `${colours.purple}Bot started in Development mode` :
- `${colours.blue}Bot started in Production mode`) + colours.none)
+ (config.enableDevelopment ? `${colors.purple}Bot started in Development mode` :
+ `${colors.blue}Bot started in Production mode`) + colors.none
+ )
};
diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts
index b2927d6..66291b3 100644
--- a/src/utils/commandRegistration/slashCommandBuilder.ts
+++ b/src/utils/commandRegistration/slashCommandBuilder.ts
@@ -1,12 +1,12 @@
-import type { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
+import { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from "discord.js";
import type { SlashCommandBuilder } from "discord.js";
-import config from "../../config/main.json" assert { type: "json" };
+import config from "../../config/main.js";
import getSubcommandsInFolder from "./getFilesInFolder.js";
import client from "../client.js";
import Discord from "discord.js";
-const colours = {
+const colors = {
red: "\x1b[31m",
green: "\x1b[32m",
none: "\x1b[0m"
@@ -23,7 +23,7 @@
// If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
console.log(`│ ├─ Loading group ${name}`)
const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│ ")
- console.log(`│ │ └─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colours.none}`)
+ console.log(`│ │ └─ ${fetched.errors ? colors.red : colors.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colors.none}`)
return (subcommandGroup: SlashCommandSubcommandGroupBuilder) => {
subcommandGroup
.setName(name)
@@ -32,7 +32,9 @@
if (descriptionLocalizations) { subcommandGroup.setDescriptionLocalizations(descriptionLocalizations) }
for (const subcommand of fetched.subcommands) {
- subcommandGroup.addSubcommand(subcommand.command);
+ const processedCommand = subcommand.command(new SlashCommandSubcommandBuilder());
+ client.commands["commands/" + path + "/" + processedCommand.name] = [subcommand, { name: processedCommand.name, description: processedCommand.description }]
+ subcommandGroup.addSubcommand(processedCommand);
};
return subcommandGroup;
@@ -52,7 +54,9 @@
// If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
commandString = "commands/" + (commandString ?? path);
const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path);
- console.log(`│ ├─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colours.none}`)
+ console.log(`│ ├─ ${fetched.errors ? colors.red : colors.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colors.none}`)
+ // console.log({name: name, description: description})
+ client.commands[commandString!] = [undefined, { name: name, description: description }]
return (command: SlashCommandBuilder) => {
command.setName(name)
command.setDescription(description)
@@ -68,15 +72,17 @@
for (const subcommand of fetched.subcommands) {
let fetchedCommand;
if (subcommand.command instanceof Function) {
- fetchedCommand = subcommand.command(new Discord.SlashCommandSubcommandBuilder());
+ fetchedCommand = subcommand.command(new SlashCommandSubcommandBuilder());
} else {
fetchedCommand = subcommand.command;
}
- client.commands[commandString! + "/" + fetchedCommand.name] = subcommand
+ client.commands[commandString! + "/" + fetchedCommand.name] = [subcommand, { name: fetchedCommand.name, description: fetchedCommand.description }]
command.addSubcommand(fetchedCommand);
}
for (const group of fetched.subcommandGroups) {
- command.addSubcommandGroup(group.command);
+ const processedCommand = group.command(new SlashCommandSubcommandGroupBuilder());
+ client.commands[commandString! + "/" + processedCommand.name] = [undefined, { name: processedCommand.name, description: processedCommand.description }]
+ command.addSubcommandGroup(processedCommand);
};
return command;
};
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index 4d90676..f7cccaf 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -1,11 +1,9 @@
-import { TextInputBuilder } from "@discordjs/builders";
+import { TextInputBuilder } from "discord.js";
import Discord, {
CommandInteraction,
- Interaction,
Message,
ActionRowBuilder,
ButtonBuilder,
- MessageComponentInteraction,
ModalSubmitInteraction,
ButtonStyle,
TextInputStyle
@@ -183,13 +181,12 @@
let component;
try {
component = await m.awaitMessageComponent({
- filter: (m) => m.user.id === this.interaction.user.id && m.channel!.id === this.interaction.channel!.id,
+ filter: (i) => i.user.id === this.interaction.user.id && i.channel!.id === this.interaction.channel!.id,
time: 300000
});
} catch (e) {
success = false;
- returnComponents = true;
- continue;
+ break;
}
if (component.customId === "yes") {
component.deferUpdate();
@@ -247,17 +244,12 @@
});
let out;
try {
- out = await modalInteractionCollector(
- m,
- (m: Interaction) =>
- (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === this.interaction.channelId,
- (m) => m.customId === "reason"
- );
+ out = await modalInteractionCollector(m, this.interaction.user) as Discord.ModalSubmitInteraction | null;
} catch (e) {
cancelled = true;
continue;
}
- if (out === null) {
+ if (out === null || out.isButton()) {
cancelled = true;
continue;
}
@@ -277,23 +269,23 @@
}
const returnValue: Awaited<ReturnType<typeof this.send>> = {};
- if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
- if (success !== undefined) returnValue.success = success;
if (cancelled) {
await this.timeoutError()
returnValue.cancelled = true;
}
- if (success == false) {
+ if (success === false) {
await this.interaction.editReply({
embeds: [new EmojiEmbed()
.setTitle(this.title)
- .setDescription(this.failedMessage ?? "")
+ .setDescription(this.failedMessage ?? "*Message timed out*")
.setStatus(this.failedStatus ?? "Danger")
.setEmoji(this.failedEmoji ?? this.redEmoji ?? this.emoji)
], components: []
});
return {success: false}
}
+ if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
+ if (success !== undefined) returnValue.success = success;
if (newReason) returnValue.newReason = newReason;
const typedReturnValue = returnValue as {cancelled: true} |
diff --git a/src/utils/convertCurlyBracketString.ts b/src/utils/convertCurlyBracketString.ts
index 5d2c23d..058ba16 100644
--- a/src/utils/convertCurlyBracketString.ts
+++ b/src/utils/convertCurlyBracketString.ts
@@ -13,7 +13,7 @@
.replace("{member:mention}", memberID ? `<@${memberID}>` : "{member:mention}")
.replace("{member:name}", memberName ? `${memberName}` : "{member:name}")
.replace("{serverName}", serverName ? `${serverName}` : "{serverName}")
- .replace("{memberCount}", memberCount ? `${memberCount}` : "{memberCount}")
+ .replace("{memberCount:all}", memberCount ? `${memberCount}` : "{memberCount}")
.replace("{memberCount:bots}", bots ? `${bots}` : "{memberCount:bots}")
.replace("{memberCount:humans}", memberCount && bots ? `${memberCount - bots}` : "{memberCount:humans}");
diff --git a/src/utils/createPageIndicator.ts b/src/utils/createPageIndicator.ts
index ee123d6..6bc86a4 100644
--- a/src/utils/createPageIndicator.ts
+++ b/src/utils/createPageIndicator.ts
@@ -1,17 +1,17 @@
import getEmojiByName from "./getEmojiByName.js";
-function pageIndicator(amount: number, selected: number, showDetails?: boolean | true) {
+function pageIndicator(amount: number, selected: number, showDetails?: boolean, disabled?: boolean | string) {
let out = "";
-
+ disabled = disabled ? "GRAY." : ""
if (amount === 1) {
- out += getEmojiByName("TRACKS.SINGLE." + (selected === 0 ? "ACTIVE" : "INACTIVE"));
+ out += getEmojiByName("TRACKS.SINGLE." + (disabled) + (selected === 0 ? "ACTIVE" : "INACTIVE"));
} else {
for (let i = 0; i < amount; i++) {
out += getEmojiByName(
"TRACKS.HORIZONTAL." +
- (i === 0 ? "LEFT" : i === amount - 1 ? "RIGHT" : "MIDDLE") +
- "." +
- (i === selected ? "ACTIVE" : "INACTIVE")
+ (i === 0 ? "LEFT" : i === amount - 1 ? "RIGHT" : "MIDDLE") +
+ "." + (disabled) +
+ (i === selected ? "ACTIVE" : "INACTIVE")
);
}
}
@@ -21,4 +21,23 @@
return out;
}
+export const verticalTrackIndicator = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
+ active = active ? "ACTIVE" : "INACTIVE";
+ disabled = disabled ? "GRAY." : "";
+ if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
+ if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
+ if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
+ return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
+};
+
+export const createVerticalTrack = (items: string[], active: boolean[], disabled?: boolean[]) => {
+ let out = "";
+ if (!disabled) disabled = new Array(items.length).fill(false);
+ for (let i = 0; i < items.length; i++) {
+ out += getEmojiByName(verticalTrackIndicator(i, active[i] ?? false, items.length, disabled[i] ?? false));
+ out += items[i] + "\n";
+ }
+ return out;
+}
+
export default pageIndicator;
diff --git a/src/utils/createTemporaryStorage.ts b/src/utils/createTemporaryStorage.ts
index a684d9d..e8a8073 100644
--- a/src/utils/createTemporaryStorage.ts
+++ b/src/utils/createTemporaryStorage.ts
@@ -1,6 +1,6 @@
import client from "./client.js";
-function generalException(location: string) {
+export function generalException(location: string) {
client.noLog.push(location);
setTimeout(() => {
client.noLog = client.noLog.filter((i: string) => {
@@ -29,4 +29,4 @@
})
client.preloadPage = Object.fromEntries(object);
}, 60 * 5 * 1000);
-}
\ No newline at end of file
+}
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 1e8e990..2e64320 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -1,32 +1,45 @@
+import { ButtonStyle, CommandInteraction, ComponentType, GuildMember, Message, MessageComponentInteraction } from "discord.js";
import type Discord from "discord.js";
import { Collection, MongoClient } from "mongodb";
-import config from "../config/main.json" assert { type: "json" };
-
-const mongoClient = new MongoClient(config.mongoUrl);
+import config from "../config/main.js";
+import client from "../utils/client.js";
+import * as crypto from "crypto";
+import _ from "lodash";
+import defaultData from '../config/default.js';
+// config.mongoOptions.host, {
+// auth: {
+// username: config.mongoOptions.username,
+// password: config.mongoOptions.password
+// },
+// authSource: config.mongoOptions.authSource
+// }
+// mongodb://emails:SweetLife2023!!@127.0.0.1:28180/saveEmail?retryWrites=true&w=majority
+const username = encodeURIComponent(config.mongoOptions.username);
+const password = encodeURIComponent(config.mongoOptions.password);
+const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"});
await mongoClient.connect();
-const database = mongoClient.db("Nucleus");
+const database = mongoClient.db();
+
+const collectionOptions = { authdb: "admin" };
export class Guilds {
guilds: Collection<GuildConfig>;
- defaultData: GuildConfig | null;
+ defaultData: GuildConfig;
constructor() {
this.guilds = database.collection<GuildConfig>("guilds");
- this.defaultData = null;
- }
-
- async setup(): Promise<Guilds> {
- this.defaultData = (await import("../config/default.json", { assert: { type: "json" } }))
- .default as unknown as GuildConfig;
- return this;
+ this.defaultData = defaultData;
}
async read(guild: string): Promise<GuildConfig> {
+ // console.log("Guild read")
const entry = await this.guilds.findOne({ id: guild });
- return Object.assign({}, this.defaultData, entry);
+ const data = _.clone(this.defaultData!);
+ return _.merge(data, entry ?? {});
}
async write(guild: string, set: object | null, unset: string[] | string = []) {
+ // console.log("Guild write")
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const uo: Record<string, any> = {};
if (!Array.isArray(unset)) unset = [unset];
@@ -41,6 +54,7 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async append(guild: string, key: string, value: any) {
+ // console.log("Guild append")
if (Array.isArray(value)) {
await this.guilds.updateOne(
{ id: guild },
@@ -67,7 +81,7 @@
value: any,
innerKey?: string | null
) {
- console.log(Array.isArray(value));
+ // console.log("Guild remove")
if (innerKey) {
await this.guilds.updateOne(
{ id: guild },
@@ -96,10 +110,255 @@
}
async delete(guild: string) {
+ // console.log("Guild delete")
await this.guilds.deleteOne({ id: guild });
}
}
+interface TranscriptEmbed {
+ title?: string;
+ description?: string;
+ fields?: {
+ name: string;
+ value: string;
+ inline: boolean;
+ }[];
+ footer?: {
+ text: string;
+ iconURL?: string;
+ };
+ color?: number;
+ timestamp?: string;
+ author?: {
+ name: string;
+ iconURL?: string;
+ url?: string;
+ };
+}
+
+interface TranscriptComponent {
+ type: number;
+ style?: ButtonStyle;
+ label?: string;
+ description?: string;
+ placeholder?: string;
+ emojiURL?: string;
+}
+
+interface TranscriptAuthor {
+ username: string;
+ discriminator: number;
+ nickname?: string;
+ id: string;
+ iconURL?: string;
+ topRole: {
+ color: number;
+ badgeURL?: string;
+ };
+ bot: boolean;
+}
+
+interface TranscriptAttachment {
+ url: string;
+ filename: string;
+ size: number;
+ log?: string;
+}
+
+interface TranscriptMessage {
+ id: string;
+ author: TranscriptAuthor;
+ content?: string;
+ embeds?: TranscriptEmbed[];
+ components?: TranscriptComponent[][];
+ editedTimestamp?: number;
+ createdTimestamp: number;
+ flags?: string[];
+ attachments?: TranscriptAttachment[];
+ stickerURLs?: string[];
+ referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
+}
+
+interface TranscriptSchema {
+ code: string;
+ for: TranscriptAuthor;
+ type: "ticket" | "purge"
+ guild: string;
+ channel: string;
+ messages: TranscriptMessage[];
+ createdTimestamp: number;
+ createdBy: TranscriptAuthor;
+}
+
+export class Transcript {
+ transcripts: Collection<TranscriptSchema>;
+
+ constructor() {
+ this.transcripts = database.collection<TranscriptSchema>("transcripts");
+ }
+
+ async create(transcript: Omit<TranscriptSchema, "code">) {
+ // console.log("Transcript create")
+ let code;
+ do {
+ code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
+ } while (await this.transcripts.findOne({ code: code }));
+
+ const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
+ if(doc.acknowledged) return code;
+ else return null;
+ }
+
+ async read(code: string) {
+ // console.log("Transcript read")
+ return await this.transcripts.findOne({ code: code });
+ }
+
+ async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
+ const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
+ const newOut: Omit<TranscriptSchema, "code"> = {
+ type: "ticket",
+ for: {
+ username: member!.user.username,
+ discriminator: parseInt(member!.user.discriminator),
+ id: member!.user.id,
+ topRole: {
+ color: member!.roles.highest.color
+ },
+ iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
+ bot: member!.user.bot
+ },
+ guild: interaction.guild!.id,
+ channel: interaction.channel!.id,
+ messages: [],
+ createdTimestamp: Date.now(),
+ createdBy: {
+ username: interaction.user.username,
+ discriminator: parseInt(interaction.user.discriminator),
+ id: interaction.user.id,
+ topRole: {
+ color: interactionMember?.roles.highest.color ?? 0x000000
+ },
+ iconURL: interaction.user.displayAvatarURL({ forceStatic: true}),
+ bot: interaction.user.bot
+ }
+ }
+ if(member.nickname) newOut.for.nickname = member.nickname;
+ if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
+ messages.reverse().forEach((message) => {
+ const msg: TranscriptMessage = {
+ id: message.id,
+ author: {
+ username: message.author.username,
+ discriminator: parseInt(message.author.discriminator),
+ id: message.author.id,
+ topRole: {
+ color: message.member!.roles.highest.color
+ },
+ iconURL: message.member!.user.displayAvatarURL({ forceStatic: true}),
+ bot: message.author.bot
+ },
+ createdTimestamp: message.createdTimestamp
+ };
+ if(message.member?.nickname) msg.author.nickname = message.member.nickname;
+ if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
+ if (message.content) msg.content = message.content;
+ if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
+ const obj: TranscriptEmbed = {};
+ if (embed.title) obj.title = embed.title;
+ if (embed.description) obj.description = embed.description;
+ if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
+ return {
+ name: field.name,
+ value: field.value,
+ inline: field.inline ?? false
+ }
+ });
+ if (embed.color) obj.color = embed.color;
+ if (embed.timestamp) obj.timestamp = embed.timestamp
+ if (embed.footer) obj.footer = {
+ text: embed.footer.text,
+ };
+ if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
+ if (embed.author) obj.author = {
+ name: embed.author.name
+ };
+ if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
+ if (embed.author?.url) obj.author!.url = embed.author.url;
+ return obj;
+ });
+ if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
+ const obj: TranscriptComponent = {
+ type: child.type
+ }
+ if (child.type === ComponentType.Button) {
+ obj.style = child.style;
+ obj.label = child.label ?? "";
+ } else if (child.type > 2) {
+ obj.placeholder = child.placeholder ?? "";
+ }
+ return obj
+ }));
+ if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
+ msg.flags = message.flags.toArray();
+
+ if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
+ if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
+ newOut.messages.push(msg);
+ });
+ return newOut;
+ }
+
+ toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
+ let out = "";
+ for (const message of transcript.messages) {
+ if (message.referencedMessage) {
+ if (Array.isArray(message.referencedMessage)) {
+ out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
+ }
+ else out += `> [Reply To] ${message.referencedMessage}\n`;
+ }
+ out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id})`;
+ out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
+ if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
+ out += "\n";
+ if (message.content) out += `[Content]\n${message.content}\n\n`;
+ if (message.embeds) {
+ for (const embed of message.embeds) {
+ out += `[Embed]\n`;
+ if (embed.title) out += `| Title: ${embed.title}\n`;
+ if (embed.description) out += `| Description: ${embed.description}\n`;
+ if (embed.fields) {
+ for (const field of embed.fields) {
+ out += `| Field: ${field.name} - ${field.value}\n`;
+ }
+ }
+ if (embed.footer) {
+ out += `|Footer: ${embed.footer.text}\n`;
+ }
+ out += "\n";
+ }
+ }
+ if (message.components) {
+ for (const component of message.components) {
+ out += `[Component]\n`;
+ for (const button of component) {
+ out += `| Button: ${button.label ?? button.description}\n`;
+ }
+ out += "\n";
+ }
+ }
+ if (message.attachments) {
+ for (const attachment of message.attachments) {
+ out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
+ }
+ }
+ out += "\n\n"
+ }
+ return out
+ }
+}
+
export class History {
histories: Collection<HistorySchema>;
@@ -117,6 +376,7 @@
after?: string | null,
amount?: string | null
) {
+ // console.log("History create");
await this.histories.insertOne({
type: type,
guild: guild,
@@ -127,10 +387,11 @@
before: before ?? null,
after: after ?? null,
amount: amount ?? null
- });
+ }, collectionOptions);
}
async read(guild: string, user: string, year: number) {
+ // console.log("History read");
const entry = (await this.histories
.find({
guild: guild,
@@ -145,10 +406,41 @@
}
async delete(guild: string) {
+ // console.log("History delete");
await this.histories.deleteMany({ guild: guild });
}
}
+interface ScanCacheSchema {
+ addedAt: Date;
+ hash: string;
+ data: boolean;
+ tags: string[];
+}
+
+export class ScanCache {
+ scanCache: Collection<ScanCacheSchema>;
+
+ constructor() {
+ this.scanCache = database.collection<ScanCacheSchema>("scanCache");
+ }
+
+ async read(hash: string) {
+ // console.log("ScanCache read");
+ return await this.scanCache.findOne({ hash: hash });
+ }
+
+ async write(hash: string, data: boolean, tags?: string[]) {
+ // console.log("ScanCache write");
+ await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions);
+ }
+
+ async cleanup() {
+ // console.log("ScanCache cleanup");
+ await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
+ }
+}
+
export class PerformanceTest {
performanceData: Collection<PerformanceDataSchema>;
@@ -157,10 +449,12 @@
}
async record(data: PerformanceDataSchema) {
+ // console.log("PerformanceTest record");
data.timestamp = new Date();
- await this.performanceData.insertOne(data);
+ await this.performanceData.insertOne(data, collectionOptions);
}
async read() {
+ // console.log("PerformanceTest read");
return await this.performanceData.find({}).toArray();
}
}
@@ -184,27 +478,161 @@
}
async create(guild: string, user: string, note: string | null) {
+ // console.log("ModNotes create");
await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
}
async read(guild: string, user: string) {
+ // console.log("ModNotes read");
const entry = await this.modNotes.findOne({ guild: guild, user: user });
return entry?.note ?? null;
}
+
+ async delete(guild: string) {
+ // console.log("ModNotes delete");
+ await this.modNotes.deleteMany({ guild: guild });
+ }
}
export class Premium {
premium: Collection<PremiumSchema>;
+ cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
+ cacheTimeout = 1000 * 60 * 60; // 1 hour
constructor() {
this.premium = database.collection<PremiumSchema>("premium");
+ this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
}
- async hasPremium(guild: string) {
+ async updateUser(user: string, level: number) {
+ // console.log("Premium updateUser");
+ if(!(await this.userExists(user))) await this.createUser(user, level);
+ await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
+ }
+
+ async userExists(user: string): Promise<boolean> {
+ // console.log("Premium userExists");
+ const entry = await this.premium.findOne({ user: user });
+ return entry ? true : false;
+ }
+
+ async createUser(user: string, level: number) {
+ // console.log("Premium createUser");
+ await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
+ }
+
+ async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
+ // console.log("Premium hasPremium");
+ // [Has premium, user giving premium, level, is mod: if given automatically]
+ const cached = this.cache.get(guild);
+ if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
+ const entries = await this.premium.find({}).toArray();
+ const members = (await client.guilds.fetch(guild)).members.cache
+ for(const {user} of entries) {
+ const member = members.get(user);
+ if(member) { //TODO: Notify user if they've given premium to a server that has since gotten premium via a mod.
+ const modPerms = //TODO: Create list in config for perms
+ member.permissions.has("Administrator") ||
+ member.permissions.has("ManageChannels") ||
+ member.permissions.has("ManageRoles") ||
+ member.permissions.has("ManageEmojisAndStickers") ||
+ member.permissions.has("ManageWebhooks") ||
+ member.permissions.has("ManageGuild") ||
+ member.permissions.has("KickMembers") ||
+ member.permissions.has("BanMembers") ||
+ member.permissions.has("ManageEvents") ||
+ member.permissions.has("ManageMessages") ||
+ member.permissions.has("ManageThreads")
+ const entry = entries.find(e => e.user === member.id);
+ if(entry && (entry.level === 3) && modPerms) {
+ this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]);
+ return [true, member.id, entry.level, true];
+ }
+ }
+ }
const entry = await this.premium.findOne({
- appliesTo: { $in: [guild] }
+ appliesTo: {
+ $elemMatch: {
+ $eq: guild
+ }
+ }
});
- return entry !== null;
+ this.cache.set(guild, [entry ? true : false, entry?.user ?? "", entry?.level ?? 0, false, new Date(Date.now() + this.cacheTimeout)]);
+ return entry ? [true, entry.user, entry.level, false] : null;
+ }
+
+ async fetchUser(user: string): Promise<PremiumSchema | null> {
+ // console.log("Premium fetchUser");
+ const entry = await this.premium.findOne({ user: user });
+ if (!entry) return null;
+ return entry;
+ }
+
+ async checkAllPremium(member?: GuildMember) {
+ // console.log("Premium checkAllPremium");
+ const entries = await this.premium.find({}).toArray();
+ if(member) {
+ const entry = entries.find(e => e.user === member.id);
+ if(entry) {
+ const expiresAt = entry.expiresAt;
+ if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null;
+ }
+ const roles = member.roles;
+ let level = 0;
+ if (roles.cache.has("1066468879309750313")) {
+ level = 99;
+ } else if (roles.cache.has("1066465491713003520")) {
+ level = 1;
+ } else if (roles.cache.has("1066439526496604194")) {
+ level = 2;
+ } else if (roles.cache.has("1066464134322978912")) {
+ level = 3;
+ }
+ await this.updateUser(member.id, level);
+ if (level > 0) {
+ await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}})
+ } else {
+ await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
+ }
+ } else {
+ const members = await (await client.guilds.fetch('684492926528651336')).members.fetch();
+ for(const {roles, id} of members.values()) {
+ const entry = entries.find(e => e.user === id);
+ if(entry) {
+ const expiresAt = entry.expiresAt;
+ if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null;
+ }
+ let level: number = 0;
+ if (roles.cache.has("1066468879309750313")) {
+ level = 99;
+ } else if (roles.cache.has("1066465491713003520")) {
+ level = 1;
+ } else if (roles.cache.has("1066439526496604194")) {
+ level = 2;
+ } else if (roles.cache.has("1066464134322978912")) {
+ level = 3;
+ }
+ await this.updateUser(id, level);
+ if (level > 0) {
+ await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}})
+ } else {
+ await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
+ }
+ }
+ }
+ }
+
+ async addPremium(user: string, guild: string) {
+ // console.log("Premium addPremium");
+ const { level } = (await this.fetchUser(user))!;
+ this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
+ return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
+ }
+
+ removePremium(user: string, guild: string) {
+ // console.log("Premium removePremium");
+ this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
+ return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
}
}
@@ -249,7 +677,18 @@
channels: string[];
};
};
+ clean: {
+ channels: string[];
+ allowed: {
+ users: string[];
+ roles: string[];
+ }
+ }
};
+ autoPublish: {
+ enabled: boolean;
+ channels: string[];
+ }
welcome: {
enabled: boolean;
role: string | null;
@@ -364,6 +803,6 @@
export interface PremiumSchema {
user: string;
level: number;
- expires: Date;
appliesTo: string[];
+ expiresAt?: number;
}
diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts
index 714a2d9..0b05779 100644
--- a/src/utils/dualCollector.ts
+++ b/src/utils/dualCollector.ts
@@ -1,4 +1,4 @@
-import Discord, { Client, Interaction, Message, MessageComponentInteraction } from "discord.js";
+import { ButtonInteraction, Client, User, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js";
import client from "./client.js";
export default async function (
@@ -10,15 +10,14 @@
try {
out = await new Promise((resolve, _reject) => {
const mes = m
- .createMessageComponentCollector({
- filter: (m) => interactionFilter(m),
- time: 300000
- })
- .on("collect", (m) => {
+ .createMessageComponentCollector({
+ filter: (m) => interactionFilter(m),
+ time: 300000
+ })
+ .on("collect", (m) => {
resolve(m);
});
- const int = m.channel
- .createMessageCollector({
+ const int = m.channel.createMessageCollector({
filter: (m) => messageFilter(m),
time: 300000
})
@@ -45,35 +44,41 @@
return out;
}
+function defaultInteractionFilter(i: MessageComponentInteraction, user: User, m: Message) {
+ return i.channel!.id === m.channel!.id && i.user.id === user.id
+}
+function defaultModalFilter(i: ModalSubmitInteraction, user: User, m: Message) {
+ return i.channel!.id === m.channel!.id && i.user.id === user.id
+}
+
+
export async function modalInteractionCollector(
- m: Message,
- modalFilter: (i: Interaction) => boolean | Promise<boolean>,
- interactionFilter: (i: MessageComponentInteraction) => boolean | Promise<boolean>
-): Promise<null | Interaction> {
- let out: Interaction;
+ m: Message, user: User,
+ modalFilter?: (i: Interaction) => boolean | Promise<boolean>,
+ interactionFilter?: (i: MessageComponentInteraction) => boolean | Promise<boolean>
+): Promise<null | ButtonInteraction | ModalSubmitInteraction> {
+ let out: ButtonInteraction | ModalSubmitInteraction;
try {
out = await new Promise((resolve, _reject) => {
const int = m
.createMessageComponentCollector({
- filter: (i: MessageComponentInteraction) => interactionFilter(i),
+ filter: (i: MessageComponentInteraction) => (interactionFilter ? interactionFilter(i) : true) && defaultInteractionFilter(i, user, m),
time: 300000
})
- .on("collect", (i: Interaction) => {
+ .on("collect", async (i: ButtonInteraction) => {
+ mod.stop();
+ int.stop();
+ await i.deferUpdate();
resolve(i);
});
- const mod = new Discord.InteractionCollector(client as Client, {
- filter: (i: Interaction) => modalFilter(i),
+ const mod = new InteractionCollector(client as Client, {
+ filter: (i: Interaction) => (modalFilter ? modalFilter(i) : true) && i.isModalSubmit() && defaultModalFilter(i, user, m),
time: 300000
- }).on("collect", async (i: Interaction) => {
+ }).on("collect", async (i: ModalSubmitInteraction) => {
int.stop();
- (i as Discord.ModalSubmitInteraction).deferUpdate();
- resolve(i as Discord.ModalSubmitInteraction);
- });
- int.on("end", () => {
mod.stop();
- });
- mod.on("end", () => {
- int.stop();
+ await i.deferUpdate();
+ resolve(i);
});
});
} catch (e) {
diff --git a/src/utils/ellipsis.ts b/src/utils/ellipsis.ts
new file mode 100644
index 0000000..6ec5888
--- /dev/null
+++ b/src/utils/ellipsis.ts
@@ -0,0 +1,4 @@
+export default (str: string, max: number): string => {
+ if (str.length <= max) return str;
+ return str.slice(0, max - 3) + "...";
+}
\ No newline at end of file
diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts
index 3c9d6ca..a79a260 100644
--- a/src/utils/eventScheduler.ts
+++ b/src/utils/eventScheduler.ts
@@ -2,7 +2,7 @@
import client from "./client.js";
import * as fs from "fs";
import * as path from "path";
-import config from "../config/main.json" assert { type: "json" };
+import config from "../config/main.js";
class EventScheduler {
private agenda: Agenda;
@@ -10,11 +10,10 @@
constructor() {
this.agenda = new Agenda({
db: {
- address: config.mongoUrl + "Nucleus",
+ address: config.mongoOptions.host,
collection: "eventScheduler"
}
});
-
this.agenda.define("unmuteRole", async (job) => {
const guild = await client.guilds.fetch(job.attrs.data.guild);
const user = await guild.members.fetch(job.attrs.data.user);
@@ -43,12 +42,12 @@
calculateType: "guildMemberPunish",
color: NucleusColors.green,
emoji: "PUNISH.MUTE.GREEN",
- timestamp: new Date().getTime()
+ timestamp: Date.now()
},
list: {
memberId: entry(user.user.id, `\`${user.user.id}\``),
name: entry(user.user.id, renderUser(user.user)),
- unmuted: entry(new Date().getTime().toString(), renderDelta(new Date().getTime())),
+ unmuted: entry(Date.now().toString(), renderDelta(Date.now())),
unmutedBy: entry(null, "*Time out ended*")
},
hidden: {
diff --git a/src/utils/generateEmojiEmbed.ts b/src/utils/generateEmojiEmbed.ts
index c0f17ae..a326fc5 100644
--- a/src/utils/generateEmojiEmbed.ts
+++ b/src/utils/generateEmojiEmbed.ts
@@ -1,4 +1,4 @@
-import { EmbedBuilder } from "@discordjs/builders";
+import { EmbedBuilder } from "discord.js";
import getEmojiByName from "./getEmojiByName.js";
const colors = {
@@ -13,13 +13,16 @@
description = "";
_generateTitle() {
+ if (this._emoji && !this._title) return getEmojiByName(this._emoji)
if (this._emoji) { return `${getEmojiByName(this._emoji)} ${this._title}`; }
- return this._title;
+ if (this._title) { return this._title };
+ return "";
}
override setTitle(title: string) {
this._title = title;
- super.setTitle(this._generateTitle());
+ const proposedTitle = this._generateTitle();
+ if (proposedTitle) super.setTitle(proposedTitle);
return this;
}
override setDescription(description: string) {
@@ -29,7 +32,8 @@
}
setEmoji(emoji: string) {
this._emoji = emoji;
- super.setTitle(this._generateTitle());
+ const proposedTitle = this._generateTitle();
+ if (proposedTitle) super.setTitle(proposedTitle);
return this;
}
setStatus(color: "Danger" | "Warning" | "Success") {
diff --git a/src/utils/getCommandDataByName.ts b/src/utils/getCommandDataByName.ts
new file mode 100644
index 0000000..da3e54b
--- /dev/null
+++ b/src/utils/getCommandDataByName.ts
@@ -0,0 +1,28 @@
+import type Discord from "discord.js";
+import client from "./client.js";
+
+
+export const getCommandMentionByName = (name: string): string => {
+ const split = name.replaceAll("/", " ").split(" ")
+ const commandName: string = split[0]!;
+
+ const filterCommand = (command: Discord.ApplicationCommand) => command.name === commandName;
+
+ const command = client.fetchedCommands.filter(c => filterCommand(c))
+ if (command.size === 0) return `\`/${name.replaceAll("/", " ")}\``;
+ const commandID = command.first()!.id;
+ return `</${split.join(" ")}:${commandID}>`;
+}
+
+export const getCommandByName = (name: string): {name: string, description: string, mention: string} => {
+
+ const split = name.replaceAll(" ", "/")
+ const command = client.commands["commands/" + split]!;
+ // console.log(command)
+ const mention = getCommandMentionByName(name);
+ return {
+ name: command[1].name,
+ description: command[1].description,
+ mention: mention
+ }
+}
\ No newline at end of file
diff --git a/src/utils/getCommandMentionByName.ts b/src/utils/getCommandMentionByName.ts
deleted file mode 100644
index b2b9937..0000000
--- a/src/utils/getCommandMentionByName.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type Discord from "discord.js";
-import client from "./client.js";
-import config from "../config/main.json" assert { type: "json"};
-
-
-export const getCommandMentionByName = async (name: string): Promise<string> => {
- const split = name.replaceAll("/", " ").split(" ")
- const commandName: string = split[0]!;
- let commandID: string;
-
- const filterCommand = (command: Discord.ApplicationCommand) => command.name === commandName;
-
- if (config.enableDevelopment) {
- const developmentGuild = client.guilds.cache.get(config.developmentGuildID)!;
- await developmentGuild.commands.fetch();
- commandID = developmentGuild.commands.cache.filter(c => filterCommand(c)).first()!.id;
- } else {
- await client.application?.commands.fetch();
- commandID = client.application?.commands.cache.filter(c => filterCommand(c)).first()!.id!;
- }
- return `</${split.join(" ")}:${commandID}>`;
-}
\ No newline at end of file
diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts
index 3fa2b53..9df17a4 100644
--- a/src/utils/getEmojiByName.ts
+++ b/src/utils/getEmojiByName.ts
@@ -1,5 +1,7 @@
import emojis from "../config/emojis.json" assert { type: "json" };
+import lodash from 'lodash';
+const isArray = lodash.isArray;
interface EmojisIndex {
[key: string]: string | EmojisIndex | EmojisIndex[];
}
@@ -12,7 +14,7 @@
if (typeof id === "string" || id === undefined) {
throw new Error(`Emoji ${name} not found`);
}
- if (Array.isArray(id)) {
+ if (isArray(id)) {
id = id[parseInt(part)];
} else {
id = id[part];
@@ -21,6 +23,10 @@
if (typeof id !== "string" && id !== undefined) {
throw new Error(`Emoji ${name} not found`);
}
+ return getEmojiFromId(id, format);
+}
+
+function getEmojiFromId(id: string | undefined, format?: string): string {
if (format === "id") {
if (id === undefined) return "0";
return id.toString();
diff --git a/src/utils/listToAndMore.ts b/src/utils/listToAndMore.ts
new file mode 100644
index 0000000..791ce40
--- /dev/null
+++ b/src/utils/listToAndMore.ts
@@ -0,0 +1,7 @@
+export default (list: string[], max: number) => {
+ // PineappleFan, Coded, Mini (and 10 more)
+ if(list.length > max) {
+ return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`;
+ }
+ return list.join(", ");
+}
\ No newline at end of file
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 54f656a..c6416a1 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -7,10 +7,37 @@
const wait = promisify(setTimeout);
+export interface LoggerOptions {
+ meta: {
+ type: string;
+ displayName: string;
+ calculateType: string;
+ color: number;
+ emoji: string;
+ timestamp: number;
+ };
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ list: any;
+ hidden: {
+ guild: string;
+ },
+ separate?: {
+ start?: string;
+ end?: string;
+ }
+}
+
+async function isLogging(guild: string, type: string): Promise<boolean> {
+ const config = await client.database.guilds.read(guild);
+ if (!config.logging.logs.enabled) return false;
+ if (!config.logging.logs.channel) return false;
+ if (!toHexArray(config.logging.logs.toLog).includes(type)) { return false; }
+ return true;
+}
export const Logger = {
renderUser(user: Discord.User | string) {
- if (typeof user === "string") return `${user} [<@${user}>]`;
+ if (typeof user === "string") user = client.users.cache.get(user)!;
return `${user.username} [<@${user.id}>]`;
},
renderTime(t: number) {
@@ -29,10 +56,12 @@
if (typeof value === "number") value = value.toString();
return { value: value, displayValue: displayValue };
},
- renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel) {
+ renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | string) {
+ if (typeof channel === "string") channel = client.channels.cache.get(channel) as Discord.GuildChannel | Discord.ThreadChannel;
return `${channel.name} [<#${channel.id}>]`;
},
- renderRole(role: Discord.Role) {
+ renderRole(role: Discord.Role | string, guild?: Discord.Guild | string) {
+ if (typeof role === "string") role = (typeof guild === "string" ? client.guilds.cache.get(guild) : guild)!.roles.cache.get(role)!;
return `${role.name} [<@&${role.id}>]`;
},
renderEmoji(emoji: Discord.GuildEmoji) {
@@ -43,19 +72,14 @@
yellow: 0xf2d478,
green: 0x68d49e
},
- async getAuditLog(guild: Discord.Guild, event: Discord.GuildAuditLogsResolvable): Promise<Discord.GuildAuditLogsEntry[]> {
- await wait(250);
+ async getAuditLog(guild: Discord.Guild, event: Discord.GuildAuditLogsResolvable, delay?: number): Promise<Discord.GuildAuditLogsEntry[]> {
+ await wait(delay ?? 250);
const auditLog = (await guild.fetchAuditLogs({ type: event })).entries.map(m => m)
return auditLog as Discord.GuildAuditLogsEntry[];
},
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- async log(log: any): Promise<void> {
+ async log(log: LoggerOptions): Promise<void> {
+ if (!await isLogging(log.hidden.guild, log.meta.calculateType)) return;
const config = await client.database.guilds.read(log.hidden.guild);
- if (!config.logging.logs.enabled) return;
- if (!toHexArray(config.logging.logs.toLog).includes(log.meta.calculateType)) {
- console.log("Not logging this type of event");
- return;
- }
if (config.logging.logs.channel) {
const channel = (await client.channels.fetch(config.logging.logs.channel)) as Discord.TextChannel | null;
const description: Record<string, string> = {};
@@ -70,7 +94,7 @@
}
});
if (channel) {
- log.separate = log.separate || {};
+ log.separate = log.separate ?? {};
const embed = new Discord.EmbedBuilder()
.setTitle(`${getEmojiByName(log.meta.emoji)} ${log.meta.displayName}`)
.setDescription(
@@ -83,8 +107,8 @@
channel.send({ embeds: [embed] });
}
}
- }
+ },
+ isLogging
};
-
export default {};
diff --git a/src/utils/logTranscripts.ts b/src/utils/logTranscripts.ts
deleted file mode 100644
index 0950664..0000000
--- a/src/utils/logTranscripts.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import type Discord from 'discord.js';
-
-export interface JSONTranscriptSchema {
- messages: {
- content: string | null;
- attachments: {
- url: string;
- name: string;
- size: number;
- }[];
- authorID: string;
- authorUsername: string;
- authorUsernameColor: string;
- timestamp: string;
- id: string;
- edited: boolean;
- }[];
- channel: string;
- guild: string;
- timestamp: string;
-}
-
-
-export const JSONTranscriptFromMessageArray = (messages: Discord.Message[]): JSONTranscriptSchema | null => {
- if (messages.length === 0) return null;
- return {
- guild: messages[0]!.guild!.id,
- channel: messages[0]!.channel.id,
- timestamp: Date.now().toString(),
- messages: messages.map((message: Discord.Message) => {
- return {
- content: message.content,
- attachments: message.attachments.map((attachment: Discord.Attachment) => {
- return {
- url: attachment.url,
- name: attachment.name!,
- size: attachment.size,
- };
- }),
- authorID: message.author.id,
- authorUsername: message.author.username + "#" + message.author.discriminator,
- authorUsernameColor: message.member!.displayHexColor.toString(),
- timestamp: message.createdTimestamp.toString(),
- id: message.id,
- edited: message.editedTimestamp ? true : false,
- };
- })
- };
-}
-
-export const JSONTranscriptToHumanReadable = (data: JSONTranscriptSchema): string => {
- let out = "";
-
- for (const message of data.messages) {
- const date = new Date(parseInt(message.timestamp));
- out += `${message.authorUsername} (${message.authorID}) [${date}]`;
- if (message.edited) out += " (edited)";
- if (message.content) out += "\nContent:\n" + message.content.split("\n").map((line: string) => `\n> ${line}`).join("");
- if (message.attachments.length > 0) out += "\nAttachments:\n" + message.attachments.map((attachment: { url: string; name: string; size: number; }) => `\n> [${attachment.name}](${attachment.url}) (${attachment.size} bytes)`).join("\n");
-
- out += "\n\n";
- }
- return out;
-}
\ No newline at end of file
diff --git a/src/utils/memory.ts b/src/utils/memory.ts
index 870ffaf..60a6535 100644
--- a/src/utils/memory.ts
+++ b/src/utils/memory.ts
@@ -7,6 +7,7 @@
logging: GuildConfig["logging"];
tickets: GuildConfig["tickets"];
tags: GuildConfig["tags"];
+ autoPublish: GuildConfig["autoPublish"];
}
class Memory {
@@ -31,7 +32,8 @@
filters: guildData.filters,
logging: guildData.logging,
tickets: guildData.tickets,
- tags: guildData.tags
+ tags: guildData.tags,
+ autoPublish: guildData.autoPublish
});
}
return this.memory.get(guild)!;
diff --git a/src/utils/performanceTesting/record.ts b/src/utils/performanceTesting/record.ts
index 95761e9..71883c5 100644
--- a/src/utils/performanceTesting/record.ts
+++ b/src/utils/performanceTesting/record.ts
@@ -2,7 +2,7 @@
import * as CP from 'child_process';
import * as process from 'process';
import systeminformation from "systeminformation";
-import config from "../../config/main.json" assert { type: "json" };
+import config from "../../config/main.js";
import singleNotify from "../singleNotify.js";
@@ -39,7 +39,7 @@
singleNotify(
"performanceTest",
config.developmentGuildID,
- `Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${results.resources.memory}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``,
+ `Discord ping time: \`${results.discord}ms\`\nDatabase read time: \`${results.databaseRead}ms\`\nCPU usage: \`${results.resources.cpu}%\`\nMemory usage: \`${Math.round(results.resources.memory)}MB\`\nCPU temperature: \`${results.resources.temperature}°C\``,
"Critical",
config.owners
)
diff --git a/src/utils/singleNotify.ts b/src/utils/singleNotify.ts
index 8e3aa60..6bf63e1 100644
--- a/src/utils/singleNotify.ts
+++ b/src/utils/singleNotify.ts
@@ -1,6 +1,7 @@
import client from "./client.js";
import EmojiEmbed from "./generateEmojiEmbed.js";
import { Record as ImmutableRecord } from "immutable";
+import type { TextChannel, ThreadChannel, NewsChannel } from "discord.js";
const severitiesType = ImmutableRecord({
Critical: "Danger",
@@ -31,20 +32,20 @@
const channel = await client.channels.fetch(data.logging.staff.channel);
if (!channel) return;
if (!channel.isTextBased()) return;
+ const textChannel = channel as TextChannel | ThreadChannel | NewsChannel;
+ let messageData = {embeds: [
+ new EmojiEmbed()
+ .setTitle(`${severity} notification`)
+ .setDescription(message)
+ .setStatus(severities.get(severity))
+ .setEmoji("CONTROL.BLOCKCROSS")
+ ]}
if (pings) {
- await channel.send({
+ messageData = Object.assign(messageData, {
content: pings.map((ping) => `<@${ping}>`).join(" ")
});
}
- await channel.send({
- embeds: [
- new EmojiEmbed()
- .setTitle(`${severity} notification`)
- .setDescription(message)
- .setStatus(severities.get(severity))
- .setEmoji("CONTROL.BLOCKCROSS")
- ]
- });
+ await textChannel.send(messageData);
} catch (err) {
console.error(err);
}
diff --git a/src/utils/temp/generateFileName.ts b/src/utils/temp/generateFileName.ts
index 3aab64c..109478d 100644
--- a/src/utils/temp/generateFileName.ts
+++ b/src/utils/temp/generateFileName.ts
@@ -12,7 +12,7 @@
if (fs.existsSync(`./${fileName}`)) {
fileName = generateFileName(ending);
}
- client.database.eventScheduler.schedule("deleteFile", (new Date().getTime() + 60 * 1000).toString(), {
+ client.database.eventScheduler.schedule("deleteFile", (Date.now() + 60 * 1000).toString(), {
fileName: `${fileName}.${ending}`
});
return path.join(__dirname, fileName + "." + ending);