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 mongoose
.connect(this.config.mongoDB) .connect(this.config.mongoDB)
.then(() => { .then(() => {
this.logger.log("Connected to the Mongodb database."); this.logger.log("Connected to the MongoDB database.");
}) })
.catch(err => { .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); 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. * 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, * This method dynamically loads all command files from the specified directory,
* creating instances of the corresponding command classes, and registering the commands with the Discord API. * creates instances of the corresponding command classes, and registers them with the Discord API.
* It also handles any additional setup or initialization required by the loaded commands.
* *
* @param {string} dir - The directory path where the command files are located. * @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. * @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) { async loadCommands(dir) {
const rest = new REST().setToken(this.config.token), const rest = new REST().setToken(this.config.token),
filePath = path.join(__dirname, dir), 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 = []; const commands = [];
for (let index = 0; index < folders.length; index++) { for (const folder of folders) {
const folder = folders[index];
if (folder.endsWith("!DISABLED")) continue;
const files = await fs.readdir(folder); const files = await fs.readdir(folder);
for (let index = 0; index < files.length; index++) { for (const file of files) {
const file = files[index]; 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) { if (!(Command.prototype instanceof BaseCommand)) continue;
const command = new Command(this); const command = new Command(this);
this.commands.set(command.command.name, command); this.commands.set(command.command.name, command);
if (command.onLoad && typeof command.onLoad === "function") await command.onLoad(this); if (typeof command.onLoad === "function") await command.onLoad(this);
commands.push(command.command instanceof SlashCommandBuilder || command.command instanceof ContextMenuCommandBuilder ? command.command.toJSON() : command.command); commands.push(command.command instanceof SlashCommandBuilder || command.command instanceof ContextMenuCommandBuilder ? command.command.toJSON() : command.command);
this.logger.log(`Successfully loaded "${file}" command file. (Command: ${command.command.name})`); this.logger.log(`Successfully loaded "${file}" command. (Command: ${command.command.name})`);
}
}
} }
} }
try { try {
if (this.config.production) await rest.put(Routes.applicationCommands(this.config.userId), { body: commands }); const route = this.config.production ? Routes.applicationCommands(this.config.userId) : Routes.applicationGuildCommands(this.config.userId, this.config.support.id);
else await rest.put(Routes.applicationGuildCommands(this.config.userId, this.config.support.id), { body: commands });
await rest.put(route, { body: commands });
this.logger.log("Successfully registered application commands."); this.logger.log("Successfully registered application commands.");
} catch (err) { } 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 {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.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.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.color] - The HEX color of the embed's border.
* @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 {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's footer. If not provided, the current timestamp will be used. * @param {Date} [data.timestamp] - The timestamp to be displayed in the embed.
* @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|Object} [data.author] - The author information for the embed.
* @returns {EmbedBuilder} The generated EmbedBuilder instance. * @returns {EmbedBuilder} The generated EmbedBuilder instance.
*/ */
embed(data) { embed(data) {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(data.title || null) .setTitle(data.title ?? null)
.setDescription(data.description || null) .setDescription(data.description ?? null)
.setThumbnail(data.thumbnail || null) .setThumbnail(data.thumbnail ?? null)
.addFields(data.fields || []) .addFields(data.fields ?? [])
.setImage(data.image || null) .setImage(data.image ?? null)
.setURL(data.url || null); .setURL(data.url ?? null)
.setColor(data.color ?? this.config.embed.color)
if (data.color) embed.setColor(data.color); .setFooter(typeof data.footer === "object" ? data.footer : data.footer ? { text: data.footer } : this.config.embed.footer)
else if (data.color === null) embed.setColor(null); .setTimestamp(data.timestamp ?? null)
else embed.setColor(this.config.embed.color); .setAuthor(typeof data.author === "string" ? { name: data.author, iconURL: this.user.avatarURL() } : data.author ?? null);
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);
return embed; return embed;
} }
@ -243,65 +305,6 @@ class JaBaClient extends Client {
if (channel) return (await channel.createInvite()).url || "No channels found or missing permissions"; 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. * Returns a User data from the database.
* @param {string} userID - The ID of the user to find or create. * @param {string} userID - The ID of the user to find or create.
@ -310,20 +313,15 @@ class JaBaClient extends Client {
async getUserData(userID) { async getUserData(userID) {
let userData = await this.usersData.findOne({ id: userID }); let userData = await this.usersData.findOne({ id: userID });
if (userData) { if (!userData) {
this.databaseCache.users.set(userID, userData);
return userData;
} else {
userData = new this.usersData({ id: userID }); userData = new this.usersData({ id: userID });
await userData.save(); await userData.save();
}
this.databaseCache.users.set(userID, userData); this.databaseCache.users.set(userID, userData);
return userData; return userData;
} }
}
/** /**
* Returns a Member data from the database. * Returns a Member data from the database.
@ -334,28 +332,20 @@ class JaBaClient extends Client {
async getMemberData(memberId, guildId) { async getMemberData(memberId, guildId) {
let memberData = await this.membersData.findOne({ guildID: guildId, id: memberId }); let memberData = await this.membersData.findOne({ guildID: guildId, id: memberId });
if (memberData) { if (!memberData) {
this.databaseCache.members.set(`${memberId}${guildId}`, memberData);
return memberData;
} else {
memberData = new this.membersData({ id: memberId, guildID: guildId }); memberData = new this.membersData({ id: memberId, guildID: guildId });
await memberData.save(); await memberData.save();
const guildData = await this.getGuildData(guildId); const guildData = await this.getGuildData(guildId);
if (guildData) { if (guildData) {
guildData.members.push(memberData._id); guildData.members.push(memberData._id);
await guildData.save(); await guildData.save();
} }
}
this.databaseCache.members.set(`${memberId}/${guildId}`, memberData); this.databaseCache.members.set(`${memberId}/${guildId}`, memberData);
return memberData; return memberData;
} }
}
/** /**
* Returns a Guild data from the database. * Returns a Guild data from the database.
@ -365,20 +355,15 @@ class JaBaClient extends Client {
async getGuildData(guildId) { async getGuildData(guildId) {
let guildData = await this.guildsData.findOne({ id: guildId }).populate("members"); let guildData = await this.guildsData.findOne({ id: guildId }).populate("members");
if (guildData) { if (!guildData) {
this.databaseCache.guilds.set(guildId, guildData);
return guildData;
} else {
guildData = new this.guildsData({ id: guildId }); guildData = new this.guildsData({ id: guildId });
await guildData.save(); await guildData.save();
}
this.databaseCache.guilds.set(guildId, guildData); this.databaseCache.guilds.set(guildId, guildData);
return guildData; return guildData;
} }
}
} }
module.exports = JaBaClient; module.exports = JaBaClient;

View file

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

View file

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

View file

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

View file

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

View file

@ -23,23 +23,25 @@ class GuildCreate extends BaseEvent {
await userData.save(); await userData.save();
} }
const thanks = client.embed({ const embed = client.embed({
author: "Thanks for inviting me to your server!", 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 { try {
const owner = await guild.fetchOwner(); const owner = await guild.fetchOwner();
owner.send({ await owner.send({
files: [ files: [
{ {
name: "unlocked.png", name: "unlocked.png",
attachment: "./assets/img/achievements/achievement_unlocked7.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) { if (client.config.support.logs) {
const users = guild.members.cache.filter(m => !m.user.bot).size; const users = guild.members.cache.filter(m => !m.user.bot).size;
@ -50,10 +52,18 @@ class GuildCreate extends BaseEvent {
name: guild.name, name: guild.name,
iconURL: guild.iconURL() || client.user.avatarURL(), 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({ const logChannel = client.channels.cache.get(client.config.support.logs);
if (logChannel)
await logChannel.send({
embeds: [embed], embeds: [embed],
}); });
} }

View file

@ -23,9 +23,13 @@ class GuildDelete extends BaseEvent {
description: `Left from guild **${guild.name}**.`, description: `Left from guild **${guild.name}**.`,
}); });
client.channels.cache.get(client.config.support.logs).send({ const logChannel = client.channels.cache.get(client.config.support.logs);
if (logChannel)
await logChannel.send({
embeds: [embed], 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"); const BaseEvent = require("../../base/BaseEvent");
class GuildMemberAdd extends BaseEvent { class GuildMemberAdd extends BaseEvent {
@ -19,13 +14,18 @@ class GuildMemberAdd extends BaseEvent {
* @param {import("discord.js").GuildMember} member * @param {import("discord.js").GuildMember} member
*/ */
async execute(client, member) { async execute(client, member) {
if (member.guild && member.guildId === "568120814776614924") return;
await member.guild.members.fetch(); await member.guild.members.fetch();
const guildData = await client.getGuildData(member.guild.id); 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) { if (guildData.plugins.welcome.enabled) {
const channel = member.guild.channels.cache.get(guildData.plugins.welcome.channel); 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(/{server}/g, member.guild.name)
.replace(/{membercount}/g, member.guild.memberCount); .replace(/{membercount}/g, member.guild.memberCount);
/* await channel.send({ content: message }).catch(err => {
if (guildData.plugins.welcome.withImage) { client.logger.error(`Failed to send welcome message in channel ${channel.id}: ${err}`);
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 */ } else {
client.logger.warn(`Welcome channel not found: ${guildData.plugins.welcome.channel}`);
channel.send({ content: message });
} }
} }
} }

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"); const BaseEvent = require("../../base/BaseEvent");
class GuildMemberRemove extends BaseEvent { class GuildMemberRemove extends BaseEvent {
@ -19,8 +14,6 @@ class GuildMemberRemove extends BaseEvent {
* @param {import("discord.js").GuildMember} member * @param {import("discord.js").GuildMember} member
*/ */
async execute(client, member) { async execute(client, member) {
if (member.guild && member.guildId === "568120814776614924") return;
await member.guild.members.fetch(); await member.guild.members.fetch();
const guildData = await client.getGuildData(member.guild.id); const guildData = await client.getGuildData(member.guild.id);
@ -34,108 +27,11 @@ class GuildMemberRemove extends BaseEvent {
.replace(/{server}/g, member.guild.name) .replace(/{server}/g, member.guild.name)
.replace(/{membercount}/g, member.guild.memberCount); .replace(/{membercount}/g, member.guild.memberCount);
/* await channel.send({ content: message }).catch(err => {
if (guildData.plugins.goodbye.withImage) { client.logger.error(`Failed to send goodbye message in channel ${channel.id}: ${err}`);
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 */ } else {
client.logger.warn(`Goodbye channel not found: ${guildData.plugins.goodbye.channel}`);
channel.send({ content: message });
} }
} }
} }

View file

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

View file

@ -14,7 +14,6 @@ class messageDelete extends BaseEvent {
* @param {import("discord.js").Message} message The deleted message * @param {import("discord.js").Message} message The deleted message
*/ */
async execute(client, message) { async execute(client, message) {
if (message.guild && message.guildId === "568120814776614924") return;
if (message.author.bot) return; if (message.author.bot) return;
const guildData = message.data.guild; const guildData = message.data.guild;
@ -26,12 +25,17 @@ class messageDelete extends BaseEvent {
iconURL: message.author.displayAvatarURL(), iconURL: message.author.displayAvatarURL(),
}, },
title: message.translate("misc:MONITORING:DELETE:TITLE", { user: message.author.getUsername() }), 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({ const monitoringChannelId = guildData.plugins.monitoring.messageDelete;
embeds: [embed], 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 * @param {import("discord.js").Message} newMessage The message after the update
*/ */
async execute(client, oldMessage, newMessage) { async execute(client, oldMessage, newMessage) {
if (oldMessage.guild && oldMessage.guildId === "568120814776614924") return;
if (oldMessage.author.bot) return; if (oldMessage.author.bot) return;
if (oldMessage.content === newMessage.content) return; if (oldMessage.content === newMessage.content) return;
const guildData = newMessage.data.guild; const guildData = newMessage.data.guild;
@ -29,12 +27,17 @@ class messageUpdate extends BaseEvent {
iconURL: newMessage.author.displayAvatarURL(), iconURL: newMessage.author.displayAvatarURL(),
}, },
title: newMessage.translate("misc:MONITORING:UPDATE:TITLE", { user: newMessage.author.getUsername() }), 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({ const monitoringChannelId = guildData.plugins.monitoring.messageUpdate;
embeds: [embed], 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, once: false,
}); });
} }
/** /**
* *
* @param {import("../base/Client")} client * @param {import("../base/Client")} client
*/ */
async execute(client) { async execute(client) {
const commands = [...new Map(client.commands.map(v => [v.constructor.name, v])).values()]; const commands = [...new Map(client.commands.map(v => [v.constructor.name, v])).values()];
let servers = client.guilds.cache.size; let servers = client.guilds.cache.size;
let users = 0; let users = 0;
client.guilds.cache.forEach(g => { client.guilds.cache.forEach(g => {
users += g.memberCount; 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(`Loaded a total of ${commands.length} command(s).`);
client.logger.ready(`${client.user.getUsername()}, ready to serve ${users} members in ${servers} servers.`); client.logger.ready(`${client.user.getUsername()}, ready to serve ${users} members in ${servers} servers.`);
console.timeEnd("botReady"); console.timeEnd("botReady");
const version = require("../package.json").version; const version = require("../package.json").version;
@ -44,9 +44,10 @@ class Ready extends BaseEvent {
]; ];
let i = 0; let i = 0;
setInterval(() => { setInterval(async () => {
servers = client.guilds.fetch().then(g => g.size); servers = (await client.guilds.fetch()).size;
users = 0; users = 0;
client.guilds.cache.forEach(g => { client.guilds.cache.forEach(g => {
users += g.memberCount; users += g.memberCount;
}); });
@ -57,8 +58,7 @@ class Ready extends BaseEvent {
state: `${status[i]} | v${version}`, state: `${status[i]} | v${version}`,
}); });
if (status[i + 1]) i++; i = (i + 1) % status.length; // Wrap around to the start when reaching the end
else i = 0;
}, 30 * 1000); // Every 30 seconds }, 30 * 1000); // Every 30 seconds
} }
} }

View file

@ -5,8 +5,10 @@ const { CronJob } = require("cron");
* @param {import("../base/Client")} client * @param {import("../base/Client")} client
*/ */
module.exports.init = async client => { module.exports.init = async client => {
const cronjob = new CronJob("0 5 * * *", async function () { const cronjob = new CronJob("0 5 * * *",
client.guilds.cache.forEach(async guild => { async function () {
// Iterate over all guilds the bot is in
for (const guild of client.guilds.cache.values()) {
try { try {
console.log(`Checking birthdays for "${guild.name}"`); console.log(`Checking birthdays for "${guild.name}"`);
@ -14,21 +16,24 @@ module.exports.init = async client => {
const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null; const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null;
if (channel) { if (channel) {
const date = new Date(), const date = new Date();
currentDay = date.getDate(), const currentDay = date.getDate();
currentMonth = date.getMonth() + 1, const currentMonth = date.getMonth() + 1;
currentYear = date.getFullYear(); 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) { 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), const userDate = new Date(user.birthdate * 1000);
day = userDate.getDate(), const day = userDate.getDate();
month = userDate.getMonth() + 1, const month = userDate.getMonth() + 1;
year = userDate.getFullYear(), const year = userDate.getFullYear();
age = currentYear - year; const age = currentYear - year;
// Check if it's the user's birthday
if (currentMonth === month && currentDay === day) { if (currentMonth === month && currentDay === day) {
const embed = client.embed({ const embed = client.embed({
author: client.user.getUsername(), author: client.user.getUsername(),
@ -48,18 +53,15 @@ module.exports.init = async client => {
], ],
}); });
channel.send({ await channel.send({ embeds: [embed] }).then(m => m.react("🎉"));
embeds: [embed],
}).then(m => m.react("🎉"));
} }
} }
});
} }
} catch (err) { } catch (err) {
if (err.code === 10003) console.log(`No channel found for ${guild.name}`); if (err.code === 10003) console.log(`No channel found for ${guild.name}`);
else throw err; else console.error(`Error processing guild "${guild.name}":`, err);
}
} }
});
}, },
null, null,
true, true,
@ -73,26 +75,29 @@ module.exports.init = async client => {
* @param {import("../base/Client")} client * @param {import("../base/Client")} client
*/ */
module.exports.run = async 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 guildData = await client.getGuildData(guild.id);
const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null; const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null;
if (channel) { if (channel) {
const date = new Date(), const date = new Date();
currentDay = date.getDate(), const currentDay = date.getDate();
currentMonth = date.getMonth() + 1, const currentMonth = date.getMonth() + 1;
currentYear = date.getFullYear(); 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) { 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), const userDate = new Date(user.birthdate * 1000);
day = userDate.getDate(), const day = userDate.getDate();
month = userDate.getMonth() + 1, const month = userDate.getMonth() + 1;
year = userDate.getFullYear(), const year = userDate.getFullYear();
age = currentYear - year; const age = currentYear - year;
// Check if it's the user's birthday
if (currentMonth === month && currentDay === day) { if (currentMonth === month && currentDay === day) {
const embed = client.embed({ const embed = client.embed({
author: client.user.getUsername(), author: client.user.getUsername(),
@ -112,12 +117,9 @@ module.exports.run = async client => {
], ],
}); });
channel.send({ await channel.send({ embeds: [embed] }).then(m => m.react("🎉"));
embeds: [embed], }
}).then(m => m.react("🎉"));
} }
} }
});
} }
});
}; };

View file

@ -6,25 +6,31 @@ const table = require("markdown-table"),
* @param {import("../base/Client")} client * @param {import("../base/Client")} client
*/ */
module.exports.update = function (client) { module.exports.update = function (client) {
const commands = [...new Map(client.commands.map(v => [v.constructor.name, v])).values()], const commands = [...new Map(client.commands.map(v => [v.constructor.name, v])).values()];
categories = []; const categories = [];
// Collect unique command categories, ignoring the "Owner" category
commands.forEach(cmd => { commands.forEach(cmd => {
if (cmd.category === "Owner") return;
if (!categories.includes(cmd.category)) categories.push(cmd.category); 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 => { categories.sort().forEach(cat => {
const categoriesArray = [["Name", "Description", "Usage", "Accessible in"]]; 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`; 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; // Sort commands alphabetically by name
else return 1; cmds.sort((a, b) => a.command.name.localeCompare(b.command.name)).forEach(cmd => {
}).forEach(cmd => {
categoriesArray.push([ categoriesArray.push([
`**${cmd.command.name}**`, `**${cmd.command.name}**`,
client.translate(`${cmd.category.toLowerCase()}/${cmd.command.name}:DESCRIPTION`), 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", cmd.command.dm_permission ? "Anywhere" : "Servers only",
]); ]);
}); });
// Append the generated table to the documentation text
text += `${table(categoriesArray)}\n\n`; text += `${table(categoriesArray)}\n\n`;
}); });
if (!fs.existsSync("./dashboard/public/docs")) fs.mkdirSync("./dashboard/public/docs"); // Ensure the output directory exists
fs.writeFileSync("./dashboard/public/docs/commands.md", text); 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!"); client.logger.log("Dashboard docs updated!");
}; };

View file

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

View file

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

View file

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

View file

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

View file

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