mirror of
https://github.com/JonnyBro/JaBa.git
synced 2024-11-21 12:44:58 +05:00
Refactor some functions
This commit is contained in:
parent
2469183919
commit
672b7b2001
21 changed files with 454 additions and 603 deletions
273
base/Client.js
273
base/Client.js
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = [
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class guildBanAdd extends BaseEvent {
|
|||
});
|
||||
|
||||
try {
|
||||
ban.user.send({
|
||||
await ban.user.send({
|
||||
embeds: [embed],
|
||||
});
|
||||
} catch (e) { /**/ }
|
||||
|
|
|
@ -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],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("🎉"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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!");
|
||||
};
|
|
@ -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",
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"QUOTE_CONTENT": "Содержимое",
|
||||
"QUOTE_ATTACHED": "Прикреплённые файлы",
|
||||
"QUOTE_JUMP": "Перейти к",
|
||||
"QUOTE_FOOTER": "Цитировал {{user}}",
|
||||
"QUOTE_FOOTER": "Отправлено",
|
||||
|
||||
"MONTHS": {
|
||||
"JANUARY": "Январь",
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"QUOTE_CONTENT": "Вміст",
|
||||
"QUOTE_ATTACHED": "Прикріплені файли",
|
||||
"QUOTE_JUMP": "Перейти до",
|
||||
"QUOTE_FOOTER": "Цитував {{user}}",
|
||||
"QUOTE_FOOTER": "Надіслано",
|
||||
|
||||
"MONTHS": {
|
||||
"JANUARY": "Січень",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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: {}
|
||||
|
|
Loading…
Reference in a new issue