Refactor some functions

This commit is contained in:
Jonny_Bro (Nikita) 2024-10-03 11:07:02 +05:00
parent 2469183919
commit 672b7b2001
Signed by: jonny_bro
GPG key ID: 3F1ECC04147E9BD8
21 changed files with 454 additions and 603 deletions

View file

@ -103,10 +103,10 @@ class JaBaClient extends Client {
mongoose
.connect(this.config.mongoDB)
.then(() => {
this.logger.log("Connected to the Mongodb database.");
this.logger.log("Connected to the MongoDB database.");
})
.catch(err => {
this.logger.error(`Unable to connect to the Mongodb database.\nError: ${err}`);
this.logger.error(`Unable to connect to the MongoDB database.\nError: ${err}`);
});
this.login(this.config.token);
@ -115,9 +115,8 @@ class JaBaClient extends Client {
/**
* Loads all the commands from the specified directory and registers them with the Discord API.
*
* This method is responsible for dynamically loading all the command files from the specified directory,
* creating instances of the corresponding command classes, and registering the commands with the Discord API.
* It also handles any additional setup or initialization required by the loaded commands.
* This method dynamically loads all command files from the specified directory,
* creates instances of the corresponding command classes, and registers them with the Discord API.
*
* @param {string} dir - The directory path where the command files are located.
* @returns {Promise<void>} A Promise that resolves when all the commands have been loaded and registered.
@ -125,43 +124,119 @@ class JaBaClient extends Client {
async loadCommands(dir) {
const rest = new REST().setToken(this.config.token),
filePath = path.join(__dirname, dir),
folders = (await fs.readdir(filePath)).map(file => path.join(filePath, file)).filter(async path => (await fs.lstat(path)).isDirectory());
folders = (await fs.readdir(filePath)).map(file => path.join(filePath, file));
const commands = [];
for (let index = 0; index < folders.length; index++) {
const folder = folders[index];
if (folder.endsWith("!DISABLED")) continue;
for (const folder of folders) {
const files = await fs.readdir(folder);
for (let index = 0; index < files.length; index++) {
const file = files[index];
for (const file of files) {
if (!file.endsWith(".js")) continue;
if (file.endsWith(".js")) {
const Command = require(path.join(folder, file));
const Command = require(path.join(folder, file));
if (Command.prototype instanceof BaseCommand) {
const command = new Command(this);
this.commands.set(command.command.name, command);
if (!(Command.prototype instanceof BaseCommand)) continue;
if (command.onLoad && typeof command.onLoad === "function") await command.onLoad(this);
const command = new Command(this);
this.commands.set(command.command.name, command);
commands.push(command.command instanceof SlashCommandBuilder || command.command instanceof ContextMenuCommandBuilder ? command.command.toJSON() : command.command);
if (typeof command.onLoad === "function") await command.onLoad(this);
this.logger.log(`Successfully loaded "${file}" command file. (Command: ${command.command.name})`);
}
}
commands.push(command.command instanceof SlashCommandBuilder || command.command instanceof ContextMenuCommandBuilder ? command.command.toJSON() : command.command);
this.logger.log(`Successfully loaded "${file}" command. (Command: ${command.command.name})`);
}
}
try {
if (this.config.production) await rest.put(Routes.applicationCommands(this.config.userId), { body: commands });
else await rest.put(Routes.applicationGuildCommands(this.config.userId, this.config.support.id), { body: commands });
const route = this.config.production ? Routes.applicationCommands(this.config.userId) : Routes.applicationGuildCommands(this.config.userId, this.config.support.id);
await rest.put(route, { body: commands });
this.logger.log("Successfully registered application commands.");
} catch (err) {
console.log(err);
this.logger.error("Error registering application commands:", err);
}
}
/**
* Loads a command from the specified directory and file.
* @param {string} dir - The directory containing the command file.
* @param {string} file - The name of the command file (without the .js extension).
* @returns {Promise<void>} This method does not return a value.
*/
async loadCommand(dir, file) {
try {
const Command = require(path.join(dir, `${file}.js`));
if (!(Command.prototype instanceof BaseCommand)) {
return this.logger.error(`Tried to load a non-command file: "${file}.js"`);
}
const command = new Command(this);
this.commands.set(command.command.name, command);
if (typeof command.onLoad === "function") await command.onLoad(this);
this.logger.log(`Successfully loaded "${file}" command file. (Command: ${command.command.name})`);
} catch (error) {
this.logger.error(`Error loading command "${file}":`, error);
}
}
/**
* Unloads a command from the specified directory and file.
* @param {string} dir - The directory containing the command file.
* @param {string} name - The name of the command file (without the .js extension).
* @returns {void} This method does not return a value.
*/
unloadCommand(dir, name) {
delete require.cache[require.resolve(`${dir}${path.sep}${name}.js`)];
return;
}
/**
* Loads all event files from the specified directory and its subdirectories.
* @param {string} dir - The directory containing the event files.
* @returns {Promise<void>} This method does not return a value.
*/
async loadEvents(dir) {
const filePath = path.join(__dirname, dir);
const files = await fs.readdir(filePath);
for (const file of files) {
const fullPath = path.join(filePath, file);
const stat = await fs.lstat(fullPath);
if (stat.isDirectory()) {
await this.loadEvents(path.join(dir, file));
continue;
}
if (file.endsWith(".js")) {
try {
const Event = require(fullPath);
if (!(Event.prototype instanceof BaseEvent)) {
this.logger.error(`"${file}" is not a valid event file.`);
continue;
}
const event = new Event();
if (!event.name || !event.name.length) {
this.logger.error(`Cannot load "${file}" event: Event name is missing!`);
continue;
}
event.once ? this.once(event.name, event.execute.bind(event, this)) : this.on(event.name, event.execute.bind(event, this));
this.logger.log(`Successfully loaded "${file}" event. (Event: ${event.name})`);
} catch (error) {
this.logger.error(`Error loading event "${file}":`, error);
}
}
}
}
@ -195,37 +270,24 @@ class JaBaClient extends Client {
* @param {Object[]} [data.fields] - An array of field objects for the embed.
* @param {string} [data.image] - The URL of the image for the embed.
* @param {string} [data.url] - The URL to be used as the embed's hyperlink.
* @param {string} [data.color] - The HEX color of the embed's border. If not provided, the default color from the client's configuration will be used.
* @param {string} [data.footer] - The text to be displayed as the embed's footer. If not provided, the default footer from the client's configuration will be used.
* @param {Date} [data.timestamp] - The timestamp to be displayed in the embed's footer. If not provided, the current timestamp will be used.
* @param {string|Object} [data.author] - The author information for the embed. Can be a string (name) or an object with `name` and/or `iconURL` properties.
* @param {string} [data.color] - The HEX color of the embed's border.
* @param {string|Object} [data.footer] - The text to be displayed as the embed's footer.
* @param {Date} [data.timestamp] - The timestamp to be displayed in the embed.
* @param {string|Object} [data.author] - The author information for the embed.
* @returns {EmbedBuilder} The generated EmbedBuilder instance.
*/
embed(data) {
const embed = new EmbedBuilder()
.setTitle(data.title || null)
.setDescription(data.description || null)
.setThumbnail(data.thumbnail || null)
.addFields(data.fields || [])
.setImage(data.image || null)
.setURL(data.url || null);
if (data.color) embed.setColor(data.color);
else if (data.color === null) embed.setColor(null);
else embed.setColor(this.config.embed.color);
if (data.footer) embed.setFooter(data.footer);
else if (data.footer === null) embed.setFooter(null);
else embed.setFooter(this.config.embed.footer);
if (data.timestamp) embed.setTimestamp(data.timestamp);
else if (data.timestamp === null) embed.setTimestamp(null);
else embed.setTimestamp();
if (!data.author || data.author === null) embed.setAuthor(null);
else if (typeof data.author === "string") embed.setAuthor({ name: data.author, iconURL: this.user.avatarURL() });
else if (typeof data.author === "object" && (data.author.iconURL !== null || data.author.iconURL !== undefined)) embed.setAuthor({ name: data.author.name, iconURL: data.author.iconURL });
else embed.setAuthor(data.author);
.setTitle(data.title ?? null)
.setDescription(data.description ?? null)
.setThumbnail(data.thumbnail ?? null)
.addFields(data.fields ?? [])
.setImage(data.image ?? null)
.setURL(data.url ?? null)
.setColor(data.color ?? this.config.embed.color)
.setFooter(typeof data.footer === "object" ? data.footer : data.footer ? { text: data.footer } : this.config.embed.footer)
.setTimestamp(data.timestamp ?? null)
.setAuthor(typeof data.author === "string" ? { name: data.author, iconURL: this.user.avatarURL() } : data.author ?? null);
return embed;
}
@ -243,65 +305,6 @@ class JaBaClient extends Client {
if (channel) return (await channel.createInvite()).url || "No channels found or missing permissions";
}
/**
* Loads a command from the specified directory and file.
* @param {string} dir - The directory containing the command file.
* @param {string} file - The name of the command file (without the .js extension).
* @returns {Promise<string>} A log message indicating the successful loading of the command.
*/
async loadCommand(dir, file) {
const Command = require(path.join(dir, `${file}.js`));
if (!(Command.prototype instanceof BaseCommand)) return this.logger.error("Tried to load a non-command file!");
const command = new Command(this);
this.commands.set(command.command.name, command);
if (command.onLoad && typeof command.onLoad === "function") await command.onLoad(this);
return this.logger.log(`Successfully loaded "${file}" command file. (Command: ${command.command.name})`);
}
/**
* Unloads a command from the specified directory and file.
* @param {string} dir - The directory containing the command file.
* @param {string} name - The name of the command file (without the .js extension).
* @returns {void} This method does not return a value.
*/
unloadCommand(dir, name) {
delete require.cache[require.resolve(`${dir}${path.sep}${name}.js`)];
return;
}
/**
* Loads all event files from the specified directory and its subdirectories.
* @param {string} dir - The directory containing the event files.
* @returns {void} This method does not return a value.
*/
async loadEvents(dir) {
const filePath = path.join(__dirname, dir);
const files = await fs.readdir(filePath);
for (let index = 0; index < files.length; index++) {
const file = files[index];
const stat = await fs.lstat(path.join(filePath, file));
if (stat.isDirectory()) this.loadEvents(path.join(dir, file));
if (file.endsWith(".js")) {
const Event = require(path.join(filePath, file));
if (Event.prototype instanceof BaseEvent) {
const event = new Event();
if (!event.name || !event.name.length) return console.error(`Cannot load "${file}" event file: Event name is not set!`);
if (event.once) this.once(event.name, event.execute.bind(event, this));
else this.on(event.name, event.execute.bind(event, this));
this.logger.log(`Successfully loaded "${file}" event file. (Event: ${event.name})`);
}
}
}
}
/**
* Returns a User data from the database.
* @param {string} userID - The ID of the user to find or create.
@ -310,19 +313,14 @@ class JaBaClient extends Client {
async getUserData(userID) {
let userData = await this.usersData.findOne({ id: userID });
if (userData) {
this.databaseCache.users.set(userID, userData);
return userData;
} else {
if (!userData) {
userData = new this.usersData({ id: userID });
await userData.save();
this.databaseCache.users.set(userID, userData);
return userData;
}
this.databaseCache.users.set(userID, userData);
return userData;
}
/**
@ -334,27 +332,19 @@ class JaBaClient extends Client {
async getMemberData(memberId, guildId) {
let memberData = await this.membersData.findOne({ guildID: guildId, id: memberId });
if (memberData) {
this.databaseCache.members.set(`${memberId}${guildId}`, memberData);
return memberData;
} else {
if (!memberData) {
memberData = new this.membersData({ id: memberId, guildID: guildId });
await memberData.save();
const guildData = await this.getGuildData(guildId);
if (guildData) {
guildData.members.push(memberData._id);
await guildData.save();
}
this.databaseCache.members.set(`${memberId}/${guildId}`, memberData);
return memberData;
}
this.databaseCache.members.set(`${memberId}/${guildId}`, memberData);
return memberData;
}
/**
@ -365,19 +355,14 @@ class JaBaClient extends Client {
async getGuildData(guildId) {
let guildData = await this.guildsData.findOne({ id: guildId }).populate("members");
if (guildData) {
this.databaseCache.guilds.set(guildId, guildData);
return guildData;
} else {
if (!guildData) {
guildData = new this.guildsData({ id: guildId });
await guildData.save();
this.databaseCache.guilds.set(guildId, guildData);
return guildData;
}
this.databaseCache.guilds.set(guildId, guildData);
return guildData;
}
}

View file

@ -1,7 +1,6 @@
const { SlashCommandBuilder, InteractionContextType, ApplicationIntegrationType } = require("discord.js");
const BaseCommand = require("../../base/BaseCommand"),
i18next = require("i18next");
// autoUpdateDocs = require("../../helpers/autoUpdateDocs");
class Reload extends BaseCommand {
/**
@ -52,7 +51,6 @@ class Reload extends BaseCommand {
await client.loadCommand(`../commands/${cmd.category}`, cmd.command.name);
i18next.reloadResources(["ru-RU", "uk-UA", "en-US"]);
// autoUpdateDocs.update(client);
interaction.success("owner/reload:SUCCESS", {
command: cmd.command.name,

View file

@ -108,12 +108,10 @@ module.exports.load = async client => {
const user = req.session?.user;
const username = (user?.discriminator === "0" ? user?.username : user?.tag) || "Guest";
const hiddenGuildMembersCount = client.guilds.cache.get("568120814776614924").memberCount;
let users = 0;
client.guilds.cache.forEach(g => {
users += g.memberCount;
});
users = users - hiddenGuildMembersCount;
const cards = [
{

View file

@ -10,16 +10,15 @@ class CommandHandler extends BaseEvent {
}
/**
*
* Handles command interaction events.
* @param {import("../base/Client")} client
* @param {import("discord.js").CommandInteraction} interaction
*/
async execute(client, interaction) {
const command = client.commands.get(interaction.commandName);
if (!command) return interaction.reply({ content: "Command not found!", ephemeral: true });
const data = [];
data.user = await client.getUserData(interaction.user.id);
const data = { user: await client.getUserData(interaction.user.id) };
if (interaction.inGuild()) {
data.guild = await client.getGuildData(interaction.guildId);
@ -30,44 +29,39 @@ class CommandHandler extends BaseEvent {
if (interaction.isButton() && interaction.customId === "quote_delete" && interaction.message.deletable) return interaction.message.delete();
if (interaction.isAutocomplete()) return await command.autocompleteRun(client, interaction);
if (interaction.type !== InteractionType.ApplicationCommand || !interaction.isCommand()) return;
if (interaction.type !== InteractionType.ApplicationCommand && !interaction.isCommand()) return;
// IAT Guild Command Check
if (command?.dirname.includes("IAT") && interaction.guildId !== "1039187019957555252")
return interaction.reply({ content: "IAT only", ephemeral: true });
if (command?.dirname.includes("IAT") && interaction.guildId !== "1039187019957555252") return interaction.reply({ content: "IAT only", ephemeral: true });
if (command.ownerOnly && interaction.user.id !== client.config.owner.id) return interaction.error("misc:OWNER_ONLY", null, { ephemeral: true });
// Owner-only command check
if (command.ownerOnly && interaction.user.id !== client.config.owner.id)
return interaction.error("misc:OWNER_ONLY", null, { ephemeral: true });
if (!interaction.data.user.achievements.firstCommand.achieved) {
const args = {
content: interaction.user.toString(),
files: [
{
name: "achievement_unlocked2.png",
attachment: "./assets/img/achievements/achievement_unlocked2.png",
},
],
};
interaction.data.user.achievements.firstCommand.progress.now = 1;
interaction.data.user.achievements.firstCommand.achieved = true;
// First command achievement check
const { firstCommand } = interaction.data.user.achievements;
if (!firstCommand.achieved) {
firstCommand.progress.now = 1;
firstCommand.achieved = true;
await interaction.data.user.save();
const achievementMessage = {
content: interaction.user.toString(),
files: [{ name: "achievement_unlocked2.png", attachment: "./assets/img/achievements/achievement_unlocked2.png" }],
};
try {
interaction.user.send(args);
} catch (e) { /**/ }
await interaction.user.send(achievementMessage);
} catch (e) {
client.logger.warn("Failed to send achievement message to user:", e);
}
}
client.logger.cmd(
`[${interaction.guild ? interaction.guild.name : "DM/Private Channel"}]: [${interaction.user.getUsername()}] => /${command.command.name}${
interaction.options.data.length > 0
? `, args: [${interaction.options.data
.map(arg => {
return `${arg.name}: ${arg.value}`;
})
.join(", ")}]`
: ""
}`,
);
// Command logging
const args = interaction.options.data.map(arg => `${arg.name}: ${arg.value}`).join(", ");
client.logger.cmd(`[${interaction.guild ? interaction.guild.name : "DM/Private Channel"}]: [${interaction.user.tag}] => /${command.command.name}${args ? `, args: [${args}]` : ""}`);
return command.execute(client, interaction);
}

View file

@ -23,7 +23,7 @@ class guildBanAdd extends BaseEvent {
});
try {
ban.user.send({
await ban.user.send({
embeds: [embed],
});
} catch (e) { /**/ }

View file

@ -23,23 +23,25 @@ class GuildCreate extends BaseEvent {
await userData.save();
}
const thanks = client.embed({
const embed = client.embed({
author: "Thanks for inviting me to your server!",
description: "Use </help:1029832476077596773> in your server to get list of all commands!.",
description: "Use </help:1029832476077596773> in your server to get a list of all commands!",
});
try {
const owner = await guild.fetchOwner();
owner.send({
await owner.send({
files: [
{
name: "unlocked.png",
attachment: "./assets/img/achievements/achievement_unlocked7.png",
},
],
embeds: [thanks],
embeds: [embed],
});
} catch (e) { /**/ }
} catch (e) {
client.logger.error(`Failed to send welcome message to guild owner: ${e.message}`);
}
if (client.config.support.logs) {
const users = guild.members.cache.filter(m => !m.user.bot).size;
@ -50,12 +52,20 @@ class GuildCreate extends BaseEvent {
name: guild.name,
iconURL: guild.iconURL() || client.user.avatarURL(),
},
description: `Joined a new guild **${guild.name}**. It has **${users}** ${client.functions.getNoun(users, client.translate("misc:NOUNS:USERS:1"), client.translate("misc:NOUNS:USERS:2"), client.translate("misc:NOUNS:USERS:5"))} and **${bots}** ${client.functions.getNoun(bots, client.translate("misc:NOUNS:BOTS:1"), client.translate("misc:NOUNS:BOTS:2"), client.translate("misc:NOUNS:BOTS:5"))}`,
description: `Joined a new guild **${guild.name}**. It has **${users}** ${client.functions.getNoun(
users,
client.translate("misc:NOUNS:USERS:1"),
client.translate("misc:NOUNS:USERS:2"),
client.translate("misc:NOUNS:USERS:5"),
)} and **${bots}** ${client.functions.getNoun(bots, client.translate("misc:NOUNS:BOTS:1"), client.translate("misc:NOUNS:BOTS:2"), client.translate("misc:NOUNS:BOTS:5"))}.`,
});
client.channels.cache.get(client.config.support.logs).send({
embeds: [embed],
});
const logChannel = client.channels.cache.get(client.config.support.logs);
if (logChannel)
await logChannel.send({
embeds: [embed],
});
}
}
}

View file

@ -23,9 +23,13 @@ class GuildDelete extends BaseEvent {
description: `Left from guild **${guild.name}**.`,
});
client.channels.cache.get(client.config.support.logs).send({
embeds: [embed],
});
const logChannel = client.channels.cache.get(client.config.support.logs);
if (logChannel)
await logChannel.send({
embeds: [embed],
});
else client.logger.warn(`Log channel not found for guild deletion: ${guild.name}`);
}
}
}

View file

@ -1,8 +1,3 @@
// const Canvas = require("@napi-rs/canvas"),
// BaseEvent = require("../../base/BaseEvent"),
// { AttachmentBuilder } = require("discord.js"),
// { applyText } = require("../../helpers/functions");
const BaseEvent = require("../../base/BaseEvent");
class GuildMemberAdd extends BaseEvent {
@ -19,13 +14,18 @@ class GuildMemberAdd extends BaseEvent {
* @param {import("discord.js").GuildMember} member
*/
async execute(client, member) {
if (member.guild && member.guildId === "568120814776614924") return;
await member.guild.members.fetch();
const guildData = await client.getGuildData(member.guild.id);
if (guildData.plugins.autorole.enabled) member.roles.add(guildData.plugins.autorole.role);
if (guildData.plugins.autorole.enabled) {
const role = guildData.plugins.autorole.role;
if (role) {
await member.roles.add(role).catch(err => {
client.logger.error(`Failed to add role to ${member.user.tag}: ${err}`);
});
}
}
if (guildData.plugins.welcome.enabled) {
const channel = member.guild.channels.cache.get(guildData.plugins.welcome.channel);
@ -36,103 +36,11 @@ class GuildMemberAdd extends BaseEvent {
.replace(/{server}/g, member.guild.name)
.replace(/{membercount}/g, member.guild.memberCount);
/*
if (guildData.plugins.welcome.withImage) {
const canvas = Canvas.createCanvas(1024, 450),
ctx = canvas.getContext("2d");
// Draw background
const background = await Canvas.loadImage("./assets/img/greetings_background.png");
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
// Draw layer
ctx.fillStyle = "#FFFFFF";
ctx.globalAlpha = "0.4";
ctx.fillRect(0, 0, 25, canvas.height);
ctx.fillRect(canvas.width - 25, 0, 25, canvas.height);
ctx.fillRect(25, 0, canvas.width - 50, 25);
ctx.fillRect(25, canvas.height - 25, canvas.width - 50, 25);
ctx.fillStyle = "#FFFFFF";
ctx.globalAlpha = "0.4";
ctx.fillRect(344, canvas.height - 296, 625, 65);
ctx.fillStyle = "#FFFFFF";
ctx.globalAlpha = "0.4";
ctx.fillRect(389, canvas.height - 225, 138, 65);
ctx.fillStyle = "#FFFFFF";
ctx.globalAlpha = "0.4";
ctx.fillRect(308, canvas.height - 110, 672, 65);
// Draw username
ctx.globalAlpha = 1;
ctx.fillStyle = "#FFFFFF";
ctx.font = applyText(canvas, member.user.username, 48, 600, "RubikMonoOne");
ctx.fillText(member.user.username, canvas.width - 670, canvas.height - 250);
// Draw server name
ctx.font = applyText(
canvas,
client.translate("administration/welcome:IMG_WELCOME", {
server: member.guild.name,
}, guildData.language),
53,
625,
"RubikMonoOne",
);
ctx.fillText(
client.translate("administration/welcome:IMG_WELCOME", {
server: member.guild.name,
}, guildData.language),
canvas.width - 700,
canvas.height - 70,
);
// Draw discriminator
ctx.font = "35px RubikMonoOne";
ctx.fillText(member.user.discriminator === "0" ? "" : member.user.discriminator, canvas.width - 623, canvas.height - 178);
// Draw membercount
ctx.font = "22px RubikMonoOne";
ctx.fillText(`${member.guild.memberCount}й ${client.translate("misc:NOUNS:MEMBERS:1", null, guildData.language)}`, 40, canvas.height - 35);
// Draw # for discriminator
ctx.fillStyle = "#FFFFFF";
ctx.font = "70px RubikMonoOne";
ctx.fillText(member.user.discriminator === "0" ? "" : "#", canvas.width - 690, canvas.height - 165);
// Draw title
ctx.font = "45px RubikMonoOne";
ctx.strokeStyle = "#000000";
ctx.lineWidth = 10;
ctx.strokeText(client.translate("administration/welcome:TITLE", null, guildData.language), canvas.width - 670, canvas.height - 330);
ctx.fillStyle = "#FFFFFF";
ctx.fillText(client.translate("administration/welcome:TITLE", null, guildData.language), canvas.width - 670, canvas.height - 330);
// Draw avatar circle
ctx.beginPath();
ctx.lineWidth = 10;
ctx.strokeStyle = "#FFFFFF";
ctx.arc(180, 225, 135, 0, Math.PI * 2, true);
ctx.stroke();
ctx.closePath();
ctx.clip();
const avatar = await Canvas.loadImage(
member.displayAvatarURL({
extension: "jpg",
}),
);
ctx.drawImage(avatar, 45, 90, 270, 270);
const attachment = new AttachmentBuilder((await canvas.encode("png")), { name: "welcome.png" });
channel.send({
content: message,
files: [attachment],
});
} else */
channel.send({ content: message });
await channel.send({ content: message }).catch(err => {
client.logger.error(`Failed to send welcome message in channel ${channel.id}: ${err}`);
});
} else {
client.logger.warn(`Welcome channel not found: ${guildData.plugins.welcome.channel}`);
}
}
}

View file

@ -1,8 +1,3 @@
// const Canvas = require("@napi-rs/canvas"),
// BaseEvent = require("../../base/BaseEvent"),
// { AttachmentBuilder } = require("discord.js"),
// { applyText } = require("../../helpers/functions");
const BaseEvent = require("../../base/BaseEvent");
class GuildMemberRemove extends BaseEvent {
@ -19,8 +14,6 @@ class GuildMemberRemove extends BaseEvent {
* @param {import("discord.js").GuildMember} member
*/
async execute(client, member) {
if (member.guild && member.guildId === "568120814776614924") return;
await member.guild.members.fetch();
const guildData = await client.getGuildData(member.guild.id);
@ -34,108 +27,11 @@ class GuildMemberRemove extends BaseEvent {
.replace(/{server}/g, member.guild.name)
.replace(/{membercount}/g, member.guild.memberCount);
/*
if (guildData.plugins.goodbye.withImage) {
const canvas = Canvas.createCanvas(1024, 450),
ctx = canvas.getContext("2d");
// Draw background
const background = await Canvas.loadImage("./assets/img/greetings_background.png");
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
// Draw layer
ctx.fillStyle = "#FFFFFF";
ctx.globalAlpha = "0.4";
ctx.fillRect(0, 0, 25, canvas.height);
ctx.fillRect(canvas.width - 25, 0, 25, canvas.height);
ctx.fillRect(25, 0, canvas.width - 50, 25);
ctx.fillRect(25, canvas.height - 25, canvas.width - 50, 25);
ctx.fillStyle = "#FFFFFF";
ctx.globalAlpha = "0.4";
ctx.fillRect(344, canvas.height - 296, 625, 65);
ctx.fillStyle = "#FFFFFF";
ctx.globalAlpha = "0.4";
ctx.fillRect(389, canvas.height - 225, 138, 65);
ctx.fillStyle = "#FFFFFF";
ctx.globalAlpha = "0.4";
ctx.fillRect(308, canvas.height - 110, 672, 65);
// Draw username
ctx.globalAlpha = 1;
ctx.fillStyle = "#FFFFFF";
ctx.font = applyText(canvas, member.user.username, 48, 600, "RubikMonoOne");
ctx.fillText(member.user.username, canvas.width - 670, canvas.height - 250);
// Draw server name
ctx.font = applyText(
canvas,
client.translate("administration/goodbye:IMG_GOODBYE", {
server: member.guild.name,
}, guildData.language),
53,
625,
"RubikMonoOne",
);
ctx.fillText(
client.translate("administration/goodbye:IMG_GOODBYE", {
server: member.guild.name,
}, guildData.language),
canvas.width - 700,
canvas.height - 70,
);
// Draw discriminator
ctx.font = "35px RubikMonoOne";
ctx.fillText(member.user.discriminator === "0" ? "" : member.user.discriminator, canvas.width - 623, canvas.height - 178);
// Draw membercount
ctx.font = "22px RubikMonoOne";
ctx.fillText(
`${member.guild.memberCount} ${client.functions.getNoun(member.guild.memberCount, client.translate("misc:NOUNS:MEMBERS:1", null, guildData.language), client.translate("misc:NOUNS:MEMBERS:2", null, guildData.language), client.translate("misc:NOUNS:MEMBERS:5", null, guildData.language))}`,
40,
canvas.height - 35,
);
// Draw # for discriminator
ctx.fillStyle = "#FFFFFF";
ctx.font = "70px RubikMonoOne";
ctx.fillText(member.user.discriminator === "0" ? "" : "#", canvas.width - 690, canvas.height - 165);
// Draw title
ctx.font = "45px RubikMonoOne";
ctx.strokeStyle = "#000000";
ctx.lineWidth = 10;
ctx.strokeText(client.translate("administration/goodbye:TITLE", null, guildData.language), canvas.width - 670, canvas.height - 330);
ctx.fillStyle = "#FFFFFF";
ctx.fillText(client.translate("administration/goodbye:TITLE", null, guildData.language), canvas.width - 670, canvas.height - 330);
// Draw avatar circle
ctx.beginPath();
ctx.lineWidth = 10;
ctx.strokeStyle = "#FFFFFF";
ctx.arc(180, 225, 135, 0, Math.PI * 2, true);
ctx.stroke();
ctx.closePath();
ctx.clip();
const avatar = await Canvas.loadImage(
member.displayAvatarURL({
extension: "png",
size: 512,
}),
);
ctx.drawImage(avatar, 45, 90, 270, 270);
const attachment = new AttachmentBuilder((await canvas.encode("png")), { name: "goodbye-image.png" });
channel.send({
content: message,
files: [attachment],
});
} else */
channel.send({ content: message });
await channel.send({ content: message }).catch(err => {
client.logger.error(`Failed to send goodbye message in channel ${channel.id}: ${err}`);
});
} else {
client.logger.warn(`Goodbye channel not found: ${guildData.plugins.goodbye.channel}`);
}
}
}

View file

@ -3,7 +3,7 @@ const BaseEvent = require("../../base/BaseEvent");
class GuildMemberUpdate extends BaseEvent {
constructor() {
super({
name: "guildMemberRemove",
name: "guildMemberUpdate",
once: false,
});
}
@ -15,7 +15,6 @@ class GuildMemberUpdate extends BaseEvent {
* @param {import("discord.js").GuildMember} newMember
*/
async execute(client, oldMember, newMember) {
if (oldMember.guild && oldMember.guildId === "568120814776614924") return;
if (oldMember.guild.id !== client.config.support.id) return;
if (oldMember.roles.cache.some(r => r.id === "940149470975365191")) return;
@ -25,16 +24,22 @@ class GuildMemberUpdate extends BaseEvent {
userData.achievements.tip.progress.now = 1;
userData.achievements.tip.achieved = true;
await userData.save();
newMember.send({
files: [
{
name: "achievement_unlocked5.png",
attachment: "./assets/img/achievements/achievement_unlocked5.png",
},
],
await userData.save().catch(err => {
client.logger.error(`Failed to save user data for ${newMember.id}: ${err}`);
});
try {
await newMember.send({
files: [
{
name: "achievement_unlocked5.png",
attachment: "./assets/img/achievements/achievement_unlocked5.png",
},
],
});
} catch (err) {
client.logger.error(`Failed to send achievement message to ${newMember.id}: ${err}`);
}
}
}
}

View file

@ -17,14 +17,20 @@ class MessageCreate extends BaseEvent {
* @param {import("discord.js").Message} message
*/
async execute(client, message) {
if (message.guild && message.guild.id === "568120814776614924") return;
const data = [];
if (message.author.bot) return;
if (message.content.match(new RegExp(`^<@!?${client.user.id}>( |)$`))) return message.replyT("misc:HELLO_SERVER", null, { mention: true });
data.user = await client.getUserData(message.author.id);
const data = await this.initializeMessageData(client, message);
message.data = data;
if (message.guild)
await this.handleGuildMessage(client, message);
return;
}
async initializeMessageData(client, message) {
const data = { user: await client.getUserData(message.author.id) };
if (message.guild) {
if (!message.member) await message.guild.members.fetch(message.author.id);
@ -33,86 +39,93 @@ class MessageCreate extends BaseEvent {
data.member = await client.getMemberData(message.author.id, message.guildId);
}
message.data = data;
return data;
}
if (message.guild) {
await updateXp(message);
async handleGuildMessage(client, message) {
await updateXp(message);
if (message.content.match(/(https|http):\/\/(ptb\.|canary\.)?(discord.com)\/(channels)\/\d+\/\d+\/\d+/g)) {
const link = message.content.match(/(https|http):\/\/(ptb\.|canary\.)?(discord.com)\/(channels)\/\d+\/\d+\/\d+/g)[0],
ids = link.match(/\d+/g),
channelId = ids[1],
messageId = ids[2];
if (this.isLinkQuote(message)) await this.handleLinkQuote(client, message);
if (message.data.guild.plugins.automod.enabled && !message.data.guild.plugins.automod.ignored.includes(message.channelId)) await this.checkAutomod(message);
const msg = await message.guild.channels.cache.get(channelId).messages.fetch(messageId);
const embed = client.embed({
author: {
name: message.translate("misc:QUOTE_TITLE", {
user: msg.author.getUsername(),
}),
iconURL: "https://wynem.com/assets/images/icons/quote.webp",
},
thumbnail: msg.author.displayAvatarURL(),
footer: {
text: message.translate("misc:QUOTE_FOOTER", { user: message.author.getUsername() }),
},
timestamp: msg.createdTimestamp,
});
await this.checkAfkStatus(client, message);
await this.checkMentionedUsersAfk(client, message);
}
if (msg.content !== "") embed.addFields([
{
name: message.translate("misc:QUOTE_CONTENT"),
value: msg.content,
},
]);
isLinkQuote(message) {
return /(https?:\/\/(ptb\.|canary\.)?(discord\.com)\/channels\/\d+\/\d+)/g.test(message.content);
}
if (msg.attachments.size > 0) {
if (msg.attachments.find(a => a.contentType.includes("image/"))) embed.setImage(msg.attachments.find(a => a.contentType.includes("image/")).url);
async handleLinkQuote(client, message) {
const link = message.content.match(/(https?:\/\/(ptb\.|canary\.)?(discord\.com)\/channels\/\d+\/\d+)/g)[0];
const ids = link.match(/\d+/g);
const channelId = ids[1];
const messageId = ids[2];
embed.addFields([
{
name: message.translate("misc:QUOTE_ATTACHED"),
value: msg.attachments.map(a => { return `[${a.name}](${a.url})`; }).join(", "),
},
]);
}
try {
const msg = await message.guild.channels.cache.get(channelId).messages.fetch(messageId);
const embed = this.createQuoteEmbed(client, msg, message);
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder().setLabel("Jump").setStyle(ButtonStyle.Link).setURL(msg.url),
new ButtonBuilder().setCustomId("quote_delete").setEmoji("1273665480451948544").setStyle(ButtonStyle.Danger),
);
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder().setLabel(message.translate("misc:QUOTE_JUMP")).setStyle(ButtonStyle.Link).setURL(msg.url),
new ButtonBuilder().setCustomId("quote_delete").setEmoji("1273665480451948544").setStyle(ButtonStyle.Danger),
);
await message.reply({ embeds: [embed], components: [row] });
} catch (error) {
client.logger.error("Failed to fetch quoted message:", error);
}
}
message.reply({
embeds: [embed],
components: [row],
});
}
createQuoteEmbed(client, msg, message) {
const embed = client.embed({
author: {
name: message.translate("misc:QUOTE_TITLE", { user: msg.author.getUsername() }),
iconURL: "https://wynem.com/assets/images/icons/quote.webp",
},
thumbnail: msg.author.displayAvatarURL(),
footer: message.translate("misc:QUOTE_FOOTER"),
timestamp: msg.createdTimestamp,
});
if (message.data.guild.plugins.automod.enabled && !message.data.guild.plugins.automod.ignored.includes(message.channelId))
if (/(discord\.(gg|io|me|li)\/.+|discordapp\.com\/invite\/.+)/i.test(message.content))
if (!message.channel.permissionsFor(message.member).has(PermissionsBitField.Flags.ManageMessages)) {
await message.error("administration/automod:DELETED", null, { mention: true });
message.delete();
}
if (msg.content) embed.addFields([{ name: message.translate("misc:QUOTE_CONTENT"), value: msg.content }]);
if (msg.attachments.size > 0) {
const images = msg.attachments.filter(a => a.contentType.includes("image/"));
if (images.size > 0) embed.setImage(images.first().url);
if (message.data.user.afk) {
message.data.user.afk = null;
await message.data.user.save();
message.replyT("general/afk:DELETED", {
user: message.author.username,
}, { mention: true });
}
message.mentions.users.forEach(async u => {
const userData = await client.getUserData(u.id);
if (userData.afk) message.replyT("general/afk:IS_AFK", { user: u.getUsername(), message: userData.afk }, { ephemeral: true });
});
embed.addFields([
{
name: message.translate("misc:QUOTE_ATTACHED"),
value: msg.attachments.map(a => `[${a.name}](${a.url})`).join(", "),
},
]);
}
return;
return embed;
}
async checkAutomod(message) {
const inviteRegex = /(discord\.(gg|io|me|li)\/.+|discordapp\.com\/invite\/.+)/i;
if (inviteRegex.test(message.content) && !message.channel.permissionsFor(message.member).has(PermissionsBitField.Flags.ManageMessages)) {
await message.error("administration/automod:DELETED", null, { mention: true });
await message.delete();
}
}
async checkAfkStatus(client, message) {
if (message.data.user.afk) {
message.data.user.afk = null;
await message.data.user.save();
message.replyT("general/afk:DELETED", { user: message.author.username }, { mention: true });
}
}
async checkMentionedUsersAfk(client, message) {
for (const user of message.mentions.users.values()) {
const userData = await client.getUserData(user.id);
if (userData.afk) message.replyT("general/afk:IS_AFK", { user: user.getUsername(), message: userData.afk }, { ephemeral: true });
}
}
}
@ -123,27 +136,28 @@ class MessageCreate extends BaseEvent {
* @returns
*/
async function updateXp(message) {
const memberData = message.data.member,
points = parseInt(memberData.exp),
level = parseInt(memberData.level),
isInCooldown = xpCooldown[message.author.id];
const memberData = message.data.member;
let level = parseInt(memberData.level),
exp = parseInt(memberData.exp);
const isInCooldown = xpCooldown[message.author.id];
if (isInCooldown) if (isInCooldown > Date.now()) return;
if (isInCooldown && isInCooldown > Date.now()) return;
const toWait = Date.now() + 60 * 1000; // 1 min
xpCooldown[message.author.id] = toWait;
const won = message.client.functions.randomNum(1, 2);
const newXp = parseInt(points + won, 10);
const neededXp = 5 * (level * level) + 80 * level + 100;
const newXp = exp + won;
const neededXp = 5 * level ** 2 + 80 * level + 100;
if (newXp > neededXp) {
memberData.level = parseInt(level + 1, 10);
memberData.exp = 0;
message.replyT("misc:LEVEL_UP", {
level: memberData.level,
}, { mention: false });
} else memberData.exp = parseInt(newXp, 10);
level += 1;
exp = 0;
message.replyT("misc:LEVEL_UP", { level: level }, { mention: false });
} else {
exp = newXp;
}
await memberData.save();
}

View file

@ -14,7 +14,6 @@ class messageDelete extends BaseEvent {
* @param {import("discord.js").Message} message The deleted message
*/
async execute(client, message) {
if (message.guild && message.guildId === "568120814776614924") return;
if (message.author.bot) return;
const guildData = message.data.guild;
@ -26,12 +25,17 @@ class messageDelete extends BaseEvent {
iconURL: message.author.displayAvatarURL(),
},
title: message.translate("misc:MONITORING:DELETE:TITLE", { user: message.author.getUsername() }),
description: message.translate("misc:MONITORING:DELETE:DESCRIPTION", { content: message.content, channel: message.channel.toString(), time: `<t:${Math.floor(message.createdTimestamp / 1000)}:f>` }),
description: message.translate("misc:MONITORING:DELETE:DESCRIPTION", {
content: message.content,
channel: message.channel.toString(),
time: `<t:${Math.floor(message.createdTimestamp / 1000)}:f>`,
}),
});
message.guild.channels.cache.get(guildData.plugins.monitoring.messageDelete).send({
embeds: [embed],
});
const monitoringChannelId = guildData.plugins.monitoring.messageDelete;
const monitoringChannel = message.guild.channels.cache.get(monitoringChannelId);
if (monitoringChannel) await monitoringChannel.send({ embeds: [embed] });
}
}
}

View file

@ -15,9 +15,7 @@ class messageUpdate extends BaseEvent {
* @param {import("discord.js").Message} newMessage The message after the update
*/
async execute(client, oldMessage, newMessage) {
if (oldMessage.guild && oldMessage.guildId === "568120814776614924") return;
if (oldMessage.author.bot) return;
if (oldMessage.content === newMessage.content) return;
const guildData = newMessage.data.guild;
@ -29,12 +27,17 @@ class messageUpdate extends BaseEvent {
iconURL: newMessage.author.displayAvatarURL(),
},
title: newMessage.translate("misc:MONITORING:UPDATE:TITLE", { user: newMessage.author.getUsername() }),
description: newMessage.translate("misc:MONITORING:UPDATE:DESCRIPTION", { oldContent: oldMessage.content, newContent: newMessage.content, url: newMessage.url }),
description: newMessage.translate("misc:MONITORING:UPDATE:DESCRIPTION", {
oldContent: oldMessage.content,
newContent: newMessage.content,
url: newMessage.url,
}),
});
newMessage.guild.channels.cache.get(guildData.plugins.monitoring.messageUpdate).send({
embeds: [embed],
});
const monitoringChannelId = guildData.plugins.monitoring.messageUpdate;
const monitoringChannel = newMessage.guild.channels.cache.get(monitoringChannelId);
if (monitoringChannel) await monitoringChannel.send({ embeds: [embed] });
}
}
}

View file

@ -8,15 +8,16 @@ class Ready extends BaseEvent {
once: false,
});
}
/**
*
* @param {import("../base/Client")} client
*/
async execute(client) {
const commands = [...new Map(client.commands.map(v => [v.constructor.name, v])).values()];
let servers = client.guilds.cache.size;
let users = 0;
client.guilds.cache.forEach(g => {
users += g.memberCount;
});
@ -31,7 +32,6 @@ class Ready extends BaseEvent {
client.logger.ready(`Loaded a total of ${commands.length} command(s).`);
client.logger.ready(`${client.user.getUsername()}, ready to serve ${users} members in ${servers} servers.`);
console.timeEnd("botReady");
const version = require("../package.json").version;
@ -44,9 +44,10 @@ class Ready extends BaseEvent {
];
let i = 0;
setInterval(() => {
servers = client.guilds.fetch().then(g => g.size);
setInterval(async () => {
servers = (await client.guilds.fetch()).size;
users = 0;
client.guilds.cache.forEach(g => {
users += g.memberCount;
});
@ -57,8 +58,7 @@ class Ready extends BaseEvent {
state: `${status[i]} | v${version}`,
});
if (status[i + 1]) i++;
else i = 0;
i = (i + 1) % status.length; // Wrap around to the start when reaching the end
}, 30 * 1000); // Every 30 seconds
}
}

View file

@ -5,30 +5,35 @@ const { CronJob } = require("cron");
* @param {import("../base/Client")} client
*/
module.exports.init = async client => {
const cronjob = new CronJob("0 5 * * *", async function () {
client.guilds.cache.forEach(async guild => {
try {
console.log(`Checking birthdays for "${guild.name}"`);
const cronjob = new CronJob("0 5 * * *",
async function () {
// Iterate over all guilds the bot is in
for (const guild of client.guilds.cache.values()) {
try {
console.log(`Checking birthdays for "${guild.name}"`);
const guildData = await client.getGuildData(guild.id);
const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null;
const guildData = await client.getGuildData(guild.id);
const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null;
if (channel) {
const date = new Date(),
currentDay = date.getDate(),
currentMonth = date.getMonth() + 1,
currentYear = date.getFullYear();
if (channel) {
const date = new Date();
const currentDay = date.getDate();
const currentMonth = date.getMonth() + 1;
const currentYear = date.getFullYear();
const users = await client.usersData.find({ birthdate: { $gt: 1 } });
client.usersData.find({ birthdate: { $gt: 1 } }).then(async users => {
for (const user of users) {
if (!guild.members.cache.find(m => m.id === user.id)) return;
// Check if the user is in the guild
if (!guild.members.cache.has(user.id)) continue;
const userDate = new Date(user.birthdate * 1000),
day = userDate.getDate(),
month = userDate.getMonth() + 1,
year = userDate.getFullYear(),
age = currentYear - year;
const userDate = new Date(user.birthdate * 1000);
const day = userDate.getDate();
const month = userDate.getMonth() + 1;
const year = userDate.getFullYear();
const age = currentYear - year;
// Check if it's the user's birthday
if (currentMonth === month && currentDay === day) {
const embed = client.embed({
author: client.user.getUsername(),
@ -48,21 +53,18 @@ module.exports.init = async client => {
],
});
channel.send({
embeds: [embed],
}).then(m => m.react("🎉"));
await channel.send({ embeds: [embed] }).then(m => m.react("🎉"));
}
}
});
}
} catch (err) {
if (err.code === 10003) console.log(`No channel found for ${guild.name}`);
else console.error(`Error processing guild "${guild.name}":`, err);
}
} catch (err) {
if (err.code === 10003) console.log(`No channel found for ${guild.name}`);
else throw err;
}
});
},
null,
true,
},
null,
true,
);
cronjob.start();
@ -73,51 +75,51 @@ module.exports.init = async client => {
* @param {import("../base/Client")} client
*/
module.exports.run = async client => {
client.guilds.cache.forEach(async guild => {
for (const guild of client.guilds.cache.values()) {
const guildData = await client.getGuildData(guild.id);
const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null;
if (channel) {
const date = new Date(),
currentDay = date.getDate(),
currentMonth = date.getMonth() + 1,
currentYear = date.getFullYear();
const date = new Date();
const currentDay = date.getDate();
const currentMonth = date.getMonth() + 1;
const currentYear = date.getFullYear();
client.usersData.find({ birthdate: { $gt: 1 } }).then(async users => {
for (const user of users) {
if (!guild.members.cache.find(m => m.id === user.id)) return;
const users = await client.usersData.find({ birthdate: { $gt: 1 } });
const userDate = new Date(user.birthdate * 1000),
day = userDate.getDate(),
month = userDate.getMonth() + 1,
year = userDate.getFullYear(),
age = currentYear - year;
for (const user of users) {
// Check if the user is in the guild
if (!guild.members.cache.has(user.id)) continue;
if (currentMonth === month && currentDay === day) {
const embed = client.embed({
author: client.user.getUsername(),
fields: [
{
name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", null, guildData.language),
value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", {
user: user.id,
age: `**${age}** ${client.functions.getNoun(
age,
client.translate("misc:NOUNS:AGE:1", null, guildData.language),
client.translate("misc:NOUNS:AGE:2", null, guildData.language),
client.translate("misc:NOUNS:AGE:5", null, guildData.language),
)}`,
}, guildData.language),
},
],
});
const userDate = new Date(user.birthdate * 1000);
const day = userDate.getDate();
const month = userDate.getMonth() + 1;
const year = userDate.getFullYear();
const age = currentYear - year;
channel.send({
embeds: [embed],
}).then(m => m.react("🎉"));
}
// Check if it's the user's birthday
if (currentMonth === month && currentDay === day) {
const embed = client.embed({
author: client.user.getUsername(),
fields: [
{
name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", null, guildData.language),
value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", {
user: user.id,
age: `**${age}** ${client.functions.getNoun(
age,
client.translate("misc:NOUNS:AGE:1", null, guildData.language),
client.translate("misc:NOUNS:AGE:2", null, guildData.language),
client.translate("misc:NOUNS:AGE:5", null, guildData.language),
)}`,
}, guildData.language),
},
],
});
await channel.send({ embeds: [embed] }).then(m => m.react("🎉"));
}
});
}
}
});
};
}
};

View file

@ -6,25 +6,31 @@ const table = require("markdown-table"),
* @param {import("../base/Client")} client
*/
module.exports.update = function (client) {
const commands = [...new Map(client.commands.map(v => [v.constructor.name, v])).values()],
categories = [];
const commands = [...new Map(client.commands.map(v => [v.constructor.name, v])).values()];
const categories = [];
// Collect unique command categories, ignoring the "Owner" category
commands.forEach(cmd => {
if (cmd.category === "Owner") return;
if (!categories.includes(cmd.category)) categories.push(cmd.category);
});
let text = `# JaBa has **${commands.length} ${client.functions.getNoun(commands.length, "command", "commands", "commands")}** in **${categories.length} ${client.functions.getNoun(categories.length, "category", "categories", "categories")}**! \n\n#### Table content \n**Name**: Command name \n**Description**: Command description \n**Usage**: How to use the command (*[]* - required, *()* - optional) \n**Accessible in**: Where you can use the command \n\n`;
// Build the initial text for the documentation
let text = `# JaBa has **${commands.length} ${client.functions.getNoun(commands.length, "command", "commands", "commands")}** in **${categories.length} ${client.functions.getNoun(categories.length, "category", "categories", "categories")}**! \n\n` +
"#### Table content \n" +
"**Name**: Command name \n" +
"**Description**: Command description \n" +
"**Usage**: How to use the command (*[]* - required, *()* - optional) \n" +
"**Accessible in**: Where you can use the command \n\n";
// Sort categories and generate command documentation for each category
categories.sort().forEach(cat => {
const categoriesArray = [["Name", "Description", "Usage", "Accessible in"]];
const cmds = [...new Map(commands.filter(cmd => cmd.category === cat).map(v => [v.constructor.name, v])).values()];
const cmds = commands.filter(cmd => cmd.category === cat);
text += `### ${cat} (${cmds.length} ${client.functions.getNoun(cmds.length, "command", "commands", "commands")})\n\n`;
cmds.sort(function (a, b) {
if (a.command.name < b.command.name) return -1;
else return 1;
}).forEach(cmd => {
// Sort commands alphabetically by name
cmds.sort((a, b) => a.command.name.localeCompare(b.command.name)).forEach(cmd => {
categoriesArray.push([
`**${cmd.command.name}**`,
client.translate(`${cmd.category.toLowerCase()}/${cmd.command.name}:DESCRIPTION`),
@ -32,11 +38,18 @@ module.exports.update = function (client) {
cmd.command.dm_permission ? "Anywhere" : "Servers only",
]);
});
// Append the generated table to the documentation text
text += `${table(categoriesArray)}\n\n`;
});
if (!fs.existsSync("./dashboard/public/docs")) fs.mkdirSync("./dashboard/public/docs");
fs.writeFileSync("./dashboard/public/docs/commands.md", text);
// Ensure the output directory exists
const outputDir = "./dashboard/public/docs";
if (!fs.existsSync(outputDir))
fs.mkdirSync(outputDir, { recursive: true });
// Write the generated documentation to a Markdown file
fs.writeFileSync(`${outputDir}/commands.md`, text);
client.logger.log("Dashboard docs updated!");
};

View file

@ -22,7 +22,7 @@
"QUOTE_CONTENT": "Content",
"QUOTE_ATTACHED": "Attached files",
"QUOTE_JUMP": "Jump to",
"QUOTE_FOOTER": "Quoted by {{user}}",
"QUOTE_FOOTER": "Sended",
"MONTHS": {
"JANUARY": "January",

View file

@ -22,7 +22,7 @@
"QUOTE_CONTENT": "Содержимое",
"QUOTE_ATTACHED": "Прикреплённые файлы",
"QUOTE_JUMP": "Перейти к",
"QUOTE_FOOTER": "Цитировал {{user}}",
"QUOTE_FOOTER": "Отправлено",
"MONTHS": {
"JANUARY": "Январь",

View file

@ -22,7 +22,7 @@
"QUOTE_CONTENT": "Вміст",
"QUOTE_ATTACHED": "Прикріплені файли",
"QUOTE_JUMP": "Перейти до",
"QUOTE_FOOTER": "Цитував {{user}}",
"QUOTE_FOOTER": "Надіслано",
"MONTHS": {
"JANUARY": "Січень",

View file

@ -23,6 +23,7 @@
"gamedig": "^4.1.0",
"i18next": "^21.10.0",
"i18next-fs-backend": "^1.2.0",
"markdown-table": "^2.0.0",
"md5": "^2.3.0",
"moment": "^2.29.4",
"mongoose": "^7.6.3",

View file

@ -53,6 +53,9 @@ importers:
i18next-fs-backend:
specifier: ^1.2.0
version: 1.2.0
markdown-table:
specifier: ^2.0.0
version: 2.0.0
md5:
specifier: ^2.3.0
version: 2.3.0
@ -845,6 +848,9 @@ packages:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
markdown-table@2.0.0:
resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==}
md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
@ -1077,6 +1083,10 @@ packages:
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
repeat-string@1.6.1:
resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
engines: {node: '>=0.10'}
resolve-alpn@1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
@ -2216,6 +2226,10 @@ snapshots:
dependencies:
semver: 6.3.1
markdown-table@2.0.0:
dependencies:
repeat-string: 1.6.1
md5@2.3.0:
dependencies:
charenc: 0.0.2
@ -2425,6 +2439,8 @@ snapshots:
regenerator-runtime@0.14.1: {}
repeat-string@1.6.1: {}
resolve-alpn@1.2.1: {}
resolve-from@4.0.0: {}