refactor some helpers

This commit is contained in:
Jonny_Bro (Nikita) 2024-11-18 19:19:24 +05:00
parent f65f09463c
commit 4aa0cfe9fa
Signed by: jonny_bro
GPG key ID: 3F1ECC04147E9BD8
10 changed files with 142 additions and 364 deletions

View file

@ -64,6 +64,7 @@ If you want to contribute, feel free to fork this repo and making a pull request
## TODO ## TODO
* [ ] Refactor [tictactoe](./helpers/tictactoe.js).
* [ ] Finish and release *dashboard-core* submodule. * [ ] Finish and release *dashboard-core* submodule.
## License ## License

View file

@ -105,8 +105,8 @@ class JaBaClient extends Client {
.then(() => { .then(() => {
this.logger.log("Connected to the MongoDB database."); this.logger.log("Connected to the MongoDB database.");
}) })
.catch(err => { .catch(e => {
this.logger.error(`Unable to connect to the MongoDB database.\nError: ${err}`); this.logger.error(`Unable to connect to the MongoDB database.\nError: ${e.message}\n${e.stack}`);
}); });
this.login(this.config.token); this.login(this.config.token);

View file

@ -41,8 +41,6 @@ class TicTacToe extends BaseCommand {
async execute(client, interaction) { async execute(client, interaction) {
const winner = await tictactoe(interaction, { const winner = await tictactoe(interaction, {
resultBtn: true, resultBtn: true,
embedColor: client.config.embed.color,
embedFoot: client.config.embed.footer,
}); });
const memberData = await client.getMemberData(winner.id, interaction.guildId); const memberData = await client.getMemberData(winner.id, interaction.guildId);

View file

@ -4,14 +4,9 @@ const { CronJob } = require("cron");
* *
* @param {import("../base/Client")} client * @param {import("../base/Client")} client
*/ */
module.exports.init = async client => { async function checkBirthdays(client) {
const cronjob = new CronJob("0 5 * * *",
async function () {
// Iterate over all guilds the bot is in
for (const guild of client.guilds.cache.values()) { for (const guild of client.guilds.cache.values()) {
try { try {
console.log(`Checking birthdays for "${guild.name}"`);
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;
@ -24,7 +19,6 @@ module.exports.init = async client => {
const users = await client.usersData.find({ birthdate: { $gt: 1 } }); const users = await client.usersData.find({ birthdate: { $gt: 1 } });
for (const user of users) { for (const user of users) {
// Check if the user is in the guild
if (!guild.members.cache.has(user.id)) continue; if (!guild.members.cache.has(user.id)) continue;
const userDate = user.birthdate > 9999999999 ? new Date(user.birthdate * 1000) : new Date(user.birthdate); const userDate = user.birthdate > 9999999999 ? new Date(user.birthdate * 1000) : new Date(user.birthdate);
@ -33,14 +27,15 @@ module.exports.init = async client => {
const year = userDate.getFullYear(); const year = userDate.getFullYear();
const 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(),
fields: [ fields: [
{ {
name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", null, guildData.language), name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", null, guildData.language),
value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", { value: client.translate(
"economy/birthdate:HAPPY_BIRTHDAY_MESSAGE",
{
user: user.id, user: user.id,
age: `**${age}** ${client.functions.getNoun( age: `**${age}** ${client.functions.getNoun(
age, age,
@ -48,7 +43,9 @@ module.exports.init = async client => {
client.translate("misc:NOUNS:AGE:2", null, guildData.language), client.translate("misc:NOUNS:AGE:2", null, guildData.language),
client.translate("misc:NOUNS:AGE:5", null, guildData.language), client.translate("misc:NOUNS:AGE:5", null, guildData.language),
)}`, )}`,
}, guildData.language), },
guildData.language,
),
}, },
], ],
}); });
@ -57,69 +54,12 @@ module.exports.init = async client => {
} }
} }
} }
} catch (err) { } catch (e) {
if (err.code === 10003) console.log(`No channel found for ${guild.name}`); if (e.code === 10003) console.log(`No channel found for ${guild.name}`);
else console.error(`Error processing guild "${guild.name}":`, err); else console.error(`Error processing birthdays for guild "${guild.name}":`, e);
}
}
},
null,
true,
);
cronjob.start();
};
/**
*
* @param {import("../base/Client")} client
*/
module.exports.run = async client => {
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();
const currentDay = date.getDate();
const currentMonth = date.getMonth() + 1;
const currentYear = date.getFullYear();
const users = await client.usersData.find({ birthdate: { $gt: 1 } });
for (const user of users) {
// Check if the user is in the guild
if (!guild.members.cache.has(user.id)) continue;
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(),
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("🎉"));
} }
} }
} }
}
}; module.exports.init = async client => new CronJob("0 5 * * *", checkBirthdays(client), null, true, "Europe/Moscow");
module.exports.run = async client => await checkBirthdays(client);

View file

@ -2,67 +2,7 @@
* *
* @param {import("../base/Client")} client * @param {import("../base/Client")} client
*/ */
module.exports.init = function (client) { async function checkReminds(client) {
client.usersData.find({ reminds: { $gt: [] } }).then(users => {
for (const user of users) {
if (!client.users.cache.has(user.id)) client.users.fetch(user.id);
client.databaseCache.usersReminds.set(user.id, user);
}
});
setInterval(async function () {
client.databaseCache.usersReminds.forEach(async user => {
const cachedUser = client.users.cache.get(user.id);
if (cachedUser) {
const dateNow = Math.floor(Date.now() / 1000),
reminds = user.reminds,
mustSent = reminds.filter(r => r.sendAt < dateNow);
if (mustSent.length > 0) {
mustSent.forEach(r => {
const embed = client.embed({
author: client.translate("general/remindme:EMBED_TITLE"),
fields: [
{
name: client.translate("general/remindme:EMBED_CREATED"),
value: `<t:${r.createdAt}:f>`,
inline: true,
},
{
name: client.translate("general/remindme:EMBED_TIME"),
value: `<t:${r.sendAt}:f>`,
inline: true,
},
{
name: client.translate("common:MESSAGE"),
value: r.message,
},
],
});
cachedUser.send({
embeds: [embed],
});
});
user.reminds = user.reminds.filter(r => r.sendAt >= dateNow);
await user.save();
if (user.reminds.length === 0) client.databaseCache.usersReminds.delete(user.id);
}
}
});
}, 1000);
};
/**
*
* @param {import("../base/Client")} client
*/
module.exports.run = function (client) {
client.usersData.find({ reminds: { $gt: [] } }).then(users => { client.usersData.find({ reminds: { $gt: [] } }).then(users => {
for (const user of users) { for (const user of users) {
if (!client.users.cache.has(user.id)) client.users.fetch(user.id); if (!client.users.cache.has(user.id)) client.users.fetch(user.id);
@ -114,4 +54,7 @@ module.exports.run = function (client) {
} }
} }
}); });
}; }
module.exports.init = async client => setInterval(async () => await checkReminds(client), 1000);
module.exports.run = async client => await checkReminds(client);

View file

@ -1,71 +1,106 @@
const { Message, BaseInteraction, User, GuildMember } = require("discord.js"); const { Message, BaseInteraction, User, GuildMember } = require("discord.js");
/**
*
* @param {Message|BaseInteraction} context
* @returns {string} Locale
*/
function getLocale(context) {
return context.data?.guild?.language;
}
/**
*
* @param {import("../base/Client")} client
* @param {string} key
* @param {any[]} args
* @param {string} locale
* @returns {string} Localized string
*/
function translate(client, key, args, locale) {
const language = client.translations.get(locale || "en-US");
if (!language) throw "Can't find given localization.";
return language(key, args);
}
/**
*
* @param {import("../base/Client")} client
* @param {string} prefixEmoji
* @param {string} message
* @returns {string} Localized message
*/
function formatReply(client, prefixEmoji, message) {
return prefixEmoji ? `${client.customEmojis[prefixEmoji]} | ${message}` : message;
}
/**
*
* @param {Message|BaseInteraction} context
* @param {string} key
* @param {any[]} args
* @param {string} options
* @returns
*/
async function replyTranslated(context, key, args, options = {}) {
const locale = options.locale || getLocale(context) || "en-US";
const translated = translate(context.client, key, args, locale);
const content = formatReply(context.client, options.prefixEmoji, translated);
if (options.edit) return context.editReply ? await context.editReply({ content, ephemeral: options.ephemeral || false }) : await context.edit({ content, allowedMentions: { repliedUser: options.mention || false } });
else return context.editReply ? await context.reply({ content, ephemeral: options.ephemeral || false }) : await context.reply({ content, allowedMentions: { repliedUser: options.mention || false } });
}
User.prototype.getUsername = function () { User.prototype.getUsername = function () {
return this.discriminator === "0" ? this.username : this.tag; return this.discriminator === "0" ? this.username : this.tag;
}; };
GuildMember.prototype.getUsername = function () { GuildMember.prototype.getUsername = function () {
return this.user.discriminator === "0" ? this.user.username : this.user.tag; return this.user.getUsername();
}; };
BaseInteraction.prototype.getLocale = function () { BaseInteraction.prototype.getLocale = function () {
return this.data?.guild?.language; return getLocale(this);
}; };
BaseInteraction.prototype.translate = function (key, args, locale) { BaseInteraction.prototype.translate = function (key, args, locale) {
const language = this.client.translations.get(this.getLocale() || locale || "en-US"); return translate(this.client, key, args, locale || this.getLocale());
if (!language) throw "Interaction: Invalid language set in data.";
return language(key, args);
}; };
BaseInteraction.prototype.replyT = async function (key, args, options = {}) { BaseInteraction.prototype.replyT = async function (key, args, options = {}) {
const translated = this.translate(key, args, this.getLocale() || options.locale || "en-US"); return await replyTranslated(this, key, args, options);
const string = options.prefixEmoji ? `${this.client.customEmojis[options.prefixEmoji]} | ${translated}` : translated;
if (options.edit) return this.editReply({ content: string, ephemeral: options.ephemeral || false });
else return this.reply({ content: string, ephemeral: options.ephemeral || false });
}; };
BaseInteraction.prototype.success = async function (key, args, options = {}) { BaseInteraction.prototype.success = async function (key, args, options = {}) {
options.prefixEmoji = "success"; options.prefixEmoji = "success";
return await this.replyT(key, args, options);
return this.replyT(key, args, options);
}; };
BaseInteraction.prototype.error = async function (key, args, options = {}) { BaseInteraction.prototype.error = async function (key, args, options = {}) {
options.prefixEmoji = "error"; options.prefixEmoji = "error";
return await this.replyT(key, args, options);
return this.replyT(key, args, options);
}; };
Message.prototype.getLocale = function () { Message.prototype.getLocale = function () {
return this.data?.guild?.language; return getLocale(this);
}; };
Message.prototype.translate = function (key, args, locale) { Message.prototype.translate = function (key, args, locale) {
const language = this.client.translations.get(this.getLocale() || locale || "en-US"); return translate(this.client, key, args, locale || this.getLocale());
if (!language) throw "Message: Invalid language set in data.";
return language(key, args);
}; };
Message.prototype.replyT = async function (key, args, options = {}) { Message.prototype.replyT = async function (key, args, options = {}) {
const translated = this.translate(key, args, this.getLocale() || options.locale || "en-US"); return await replyTranslated(this, key, args, options);
const string = options.prefixEmoji ? `${this.client.customEmojis[options.prefixEmoji]} | ${translated}` : translated;
if (options.edit) return this.edit({ content: string, allowedMentions: { repliedUser: options.mention ? true : false } });
else return this.reply({ content: string, allowedMentions: { repliedUser: options.mention ? true : false } });
}; };
Message.prototype.success = async function (key, args, options = {}) { Message.prototype.success = async function (key, args, options = {}) {
options.prefixEmoji = "success"; options.prefixEmoji = "success";
return await this.replyT(key, args, options);
return this.replyT(key, args, options);
}; };
Message.prototype.error = async function (key, args, options = {}) { Message.prototype.error = async function (key, args, options = {}) {
options.prefixEmoji = "error"; options.prefixEmoji = "error";
return await this.replyT(key, args, options);
return this.replyT(key, args, options);
}; };

View file

@ -1,9 +1,4 @@
const moment = require("moment"); const moment = require("moment");
// const { GlobalFonts } = require("@napi-rs/canvas"),
// const { resolve } = require("path");
// GlobalFonts.registerFromPath(resolve("./assets/fonts/RubikMonoOne-Regular.ttf"), "RubikMonoOne");
// GlobalFonts.registerFromPath(resolve("./assets/fonts/KeepCalm-Medium.ttf"), "KeepCalm");
module.exports = { module.exports = {
/** /**

View file

@ -27,38 +27,26 @@ function format(tDate) {
module.exports = class Logger { module.exports = class Logger {
static log(content) { static log(content) {
const date = `[${format(new Date(Date.now()))}]:`; return console.log(`[${format(new Date(Date.now()))}]: ${bgBlue("LOG")} ${content}`);
return console.log(`${date} ${bgBlue("LOG")} ${content}`);
} }
static warn(content) { static warn(content) {
const date = `[${format(new Date(Date.now()))}]:`; return console.log(`[${format(new Date(Date.now()))}]: ${black.bgYellow("WARN")} ${content}`);
return console.log(`${date} ${black.bgYellow("WARN")} ${content}`);
} }
static error(content) { static error(content) {
const date = `[${format(new Date(Date.now()))}]:`; return console.log(`[${format(new Date(Date.now()))}]: ${black.bgRed("ERROR")} ${content}`);
return console.log(`${date} ${black.bgRed("ERROR")} ${content}`);
} }
static debug(content) { static debug(content) {
const date = `[${format(new Date(Date.now()))}]:`; return console.log(`[${format(new Date(Date.now()))}]: ${green("DEBUG")} ${content}`);
return console.log(`${date} ${green("DEBUG")} ${content}`);
} }
static cmd(content) { static cmd(content) {
const date = `[${format(new Date(Date.now()))}]:`; return console.log(`[${format(new Date(Date.now()))}]: ${black.bgWhite("CMD")} ${content}`);
return console.log(`${date} ${black.bgWhite("CMD")} ${content}`);
} }
static ready(content) { static ready(content) {
const date = `[${format(new Date(Date.now()))}]:`; return console.log(`[${format(new Date(Date.now()))}]: ${black.bgGreen("READY")} ${content}`);
return console.log(`${date} ${black.bgGreen("READY")} ${content}`);
} }
}; };

View file

@ -1,112 +0,0 @@
/* eslint-disable no-constant-condition */
// Thanks to mee6-levels-api =)
const fetch = require("node-fetch");
const fetchMee6 = async args => {
const response = await fetch(`https://mee6.xyz/api/plugins/levels/leaderboard/${args}`).then(res => res.json());
if (response.statusCode) {
if (response.error && response.error.message) throw new Error(`${response.statusCode}: ${response.error.message}`);
else throw new Error(`${response.statusCode}: ${response.statusMessage}`);
}
return response;
};
class Mee6Api {
/**
* Resolves a guild or user to its ID.
* @param {{id:String}|String} guildOrUser Object to resolve to an ID.
* @returns {String} ID of the guild or user.
*/
static getId(guildOrUser) {
if (typeof guildOrUser === "string") return guildOrUser;
else if (typeof guildOrUser.id === "string") return guildOrUser.id;
else throw new Error("Invalid Id specified.");
}
/**
* Gets role rewards set for a guild.
* @param {{id:String}|String} guild Guild to get role rewards of.
* @returns {Promise<[Object]>} Role rewards set for this guild.
*/
static async getRoleRewards(guild) {
const guildId = this.getId(guild);
const { role_rewards } = await fetchMee6(`${guildId}?limit=1`);
return role_rewards.sort((a, b) => a.rank - b.rank).map(({ rank, ...rest }) => ({ ...rest, level: rank }));
}
/**
* Get a page of the leaderboard of a guild.
* @param {{id:String}|String} guild Guild to get the leaderboard of.
* @param {Number} limit Limit of users to fetch per page. Maximum 1000.
* @param {Number} page Number of pages to skip.
* @returns {Promise<[Object]>} Leaderboard page.
*/
static async getLeaderboardPage(guild, limit = 1000, page = 0) {
const guildId = this.getId(guild);
const { players } = await fetchMee6(`${guildId}?limit=${limit}&page=${page}`);
return players.map((user, index) => {
const { id, level, username, discriminator, avatar, message_count: messageCount } = user;
const avatarUrl = `https://cdn.discordapp.com/avatars/${id}/${avatar}`;
const [userXp, levelXp, totalXp] = user.detailed_xp;
return {
id,
level,
username,
discriminator,
avatarUrl,
messageCount,
tag: `${username}#${discriminator}`,
xp: { userXp, levelXp, totalXp },
rank: limit * page + index + 1,
};
});
}
/**
* Get the leaderboard of a guild.
* @param {{id:String}|String} guild Guild to get the leaderboard of.
* @returns {Promise<[Object]>} Leaderboard of the guild.
*/
static async getLeaderboard(guild) {
const leaderboard = [];
let pageNumber = 0,
page;
while (true) {
page = await this.getLeaderboardPage(guild, 1000, pageNumber);
leaderboard.push(...page);
if (page.length < 1000) break;
pageNumber += 1;
}
return leaderboard;
}
/**
* Get the XP of an individual user in a guild.
* @param {{id:String}|String} guild Guild the user is in.
* @param {{id:String}|String} user User to get the XP of.
* @returns {Promise<Object|undefined>} XP information of the user.
*/
static async getUserXp(guild, user) {
const userId = this.getId(user);
let pageNumber = 0,
page,
userInfo;
while (true) {
page = await this.getLeaderboardPage(guild, 1000, pageNumber);
userInfo = page.find(u => u.id === userId);
if (page.length < 1000 || userInfo) break;
pageNumber += 1;
}
return userInfo;
}
}
module.exports = Mee6Api;

View file

@ -1,32 +1,22 @@
// Thanks to simply-djs for this =) // Thanks to simply-djs for this =)
// TODO: Refactor this please...
const { ButtonBuilder, ActionRowBuilder, ButtonStyle, ComponentType } = require("discord.js"); const { ButtonBuilder, ActionRowBuilder, ButtonStyle, ComponentType } = require("discord.js");
/** /**
* @param {import("discord.js").ChatInputCommandInteraction} interaction * @param {import("discord.js").ChatInputCommandInteraction} interaction
* @param {Array} options * @param {any[]} options Array with options (everything is optional)
* slash => Boolean * @param {string} options.userSlash Name of the user option in the interaction
* * @param {string} options.embedFooter Game's embed footer
* userSlash => String * @param {string} options.embedColor Game's embed color
* * @param {string} options.timeoutEmbedColor Game's embed timeout color
* resultBtn => Boolean * @param {string} options.xEmoji Emoji for X
* * @param {string} options.oEmoji Emoji for O
* embedFoot => String * @param {string} options.idleEmoji Emoji for "nothing"
*
* embedColor => HexColor
*
* timeoutEmbedColor => HexColor
*
* xEmoji => (Emoji ID) String
*
* oEmoji => (Emoji ID) String
*
* idleEmoji => (Emoji ID) String
* @returns {Promise<import("discord.js").User>} * @returns {Promise<import("discord.js").User>}
*/ */
async function tictactoe(interaction, options = {}) { async function tictactoe(interaction, options = {}) {
// eslint-disable-next-line no-async-promise-executor return new Promise(resolve => {
return new Promise(async resolve => {
try { try {
const { client } = interaction; const { client } = interaction;
let opponent; let opponent;
@ -71,8 +61,8 @@ async function tictactoe(interaction, options = {}) {
}); });
} }
const footer = options.embedFoot ? options.embedFoot : { text: "GLHF" }, const footer = options.embedFooter || client.config.embed.footer,
color = options.embedColor || "#075FFF", color = options.embedColor || client.config.embed.color,
user = interaction.user ? interaction.user : interaction.author; user = interaction.user ? interaction.user : interaction.author;
const acceptEmbed = client.embed({ const acceptEmbed = client.embed({
@ -93,7 +83,7 @@ async function tictactoe(interaction, options = {}) {
let m; let m;
if (interaction.commandId) if (interaction.commandId)
m = await interaction.reply({ m = interaction.reply({
content: interaction.translate("fun/tictactoe:INVITE_USER", { content: interaction.translate("fun/tictactoe:INVITE_USER", {
opponent: opponent.id, opponent: opponent.id,
}), }),
@ -101,7 +91,7 @@ async function tictactoe(interaction, options = {}) {
components: [accep], components: [accep],
}); });
else if (!interaction.commandId) else if (!interaction.commandId)
m = await interaction.reply({ m = interaction.reply({
content: interaction.translate("fun/tictactoe:INVITE_USER", { content: interaction.translate("fun/tictactoe:INVITE_USER", {
opponent: opponent.id, opponent: opponent.id,
}), }),
@ -637,8 +627,8 @@ async function tictactoe(interaction, options = {}) {
}); });
} }
}); });
} catch (err) { } catch (e) {
console.log(`tictactoe | ERROR: ${err.stack}`); console.log("TicTacToe errored:", e);
} }
}); });
} }