diff --git a/.gitignore b/.gitignore index 2b86767f..afb877a2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ Thumbs.db # Bot Configuration -/config.js +/config.json # DB /giveaways.json @@ -13,3 +13,6 @@ Thumbs.db # Node node_modules +dist + +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 219e598f..a48d543d 100644 --- a/.prettierrc +++ b/.prettierrc @@ -6,6 +6,5 @@ "semi": true, "tabWidth": 4, "trailingComma": "all", - "useTabs": true, - "parser": "babel" + "useTabs": true } \ No newline at end of file diff --git a/README.md b/README.md index 7c6f0eb0..d1e4303d 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ If you want to contribute, feel free to fork this repo and making a pull request ## TODO * [ ] Refactor [tictactoe](./helpers/tictactoe.js). +* [ ] Finish and release *dashboard-core* submodule. ## License diff --git a/base/Member.js b/base/Member.js deleted file mode 100644 index d494f72c..00000000 --- a/base/Member.js +++ /dev/null @@ -1,33 +0,0 @@ -const mongoose = require("mongoose"); - -module.exports = mongoose.model("Member", new mongoose.Schema({ - id: { type: String }, - guildID: { type: String }, - - money: { type: Number, default: 0 }, - workStreak: { type: Number, default: 0 }, - bankSold: { type: Number, default: 0 }, - exp: { type: Number, default: 0 }, - level: { type: Number, default: 0 }, - transactions: { type: Array, default: [] }, - - registeredAt: { type: Number, default: Date.now() }, - - cooldowns: { - type: Object, - default: { - work: 0, - rob: 0, - }, - }, - - sanctions: { type: Array, default: [] }, - mute: { - type: Object, - default: { - muted: false, - case: null, - endDate: null, - }, - }, -})); diff --git a/base/User.js b/base/User.js deleted file mode 100644 index c455a4c4..00000000 --- a/base/User.js +++ /dev/null @@ -1,113 +0,0 @@ -const mongoose = require("mongoose"), - Canvas = require("@napi-rs/canvas"); - -const genToken = () => { - let token = ""; - const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzy0123456789.-_"; - for (let i = 0; i < 32; i++) token += characters.charAt(Math.floor(Math.random() * characters.length)); - - return token; -}; - -const userSchema = new mongoose.Schema({ - id: { type: String }, - - rep: { type: Number, default: 0 }, - bio: { type: String }, - birthdate: { type: Number }, - lover: { type: String }, - - registeredAt: { type: Number, default: Date.now() }, - - achievements: { - type: Object, - default: { - married: { - achieved: false, - progress: { - now: 0, - total: 1, - }, - }, - work: { - achieved: false, - progress: { - now: 0, - total: 10, - }, - }, - firstCommand: { - achieved: false, - progress: { - now: 0, - total: 1, - }, - }, - slots: { - achieved: false, - progress: { - now: 0, - total: 3, - }, - }, - tip: { - achieved: false, - progress: { - now: 0, - total: 1, - }, - }, - rep: { - achieved: false, - progress: { - now: 0, - total: 20, - }, - }, - invite: { - achieved: false, - progress: { - now: 0, - total: 1, - }, - }, - }, - }, - - cooldowns: { - type: Object, - default: { - rep: 0, - }, - }, - - afk: { type: String, default: null }, - reminds: { type: Array, default: [] }, - logged: { type: Boolean, default: false }, - apiToken: { type: String, default: genToken() }, -}); - -userSchema.method("getAchievements", async function () { - const canvas = Canvas.createCanvas(1800, 250), - ctx = canvas.getContext("2d"); - - const images = [ - await Canvas.loadImage(`./assets/img/achievements/achievement${this.achievements.work.achieved ? "_colored" : ""}1.png`), - await Canvas.loadImage(`./assets/img/achievements/achievement${this.achievements.firstCommand.achieved ? "_colored" : ""}2.png`), - await Canvas.loadImage(`./assets/img/achievements/achievement${this.achievements.married.achieved ? "_colored" : ""}3.png`), - await Canvas.loadImage(`./assets/img/achievements/achievement${this.achievements.slots.achieved ? "_colored" : ""}4.png`), - await Canvas.loadImage(`./assets/img/achievements/achievement${this.achievements.tip.achieved ? "_colored" : ""}5.png`), - await Canvas.loadImage(`./assets/img/achievements/achievement${this.achievements.rep.achieved ? "_colored" : ""}6.png`), - await Canvas.loadImage(`./assets/img/achievements/achievement${this.achievements.invite.achieved ? "_colored" : ""}7.png`), - ]; - let dim = 0; - - for (let i = 0; i < images.length; i++) { - ctx.drawImage(images[i], dim, 10, 350, 200); - dim += 200; - } - - return (await canvas.encode("png")); -}); - -module.exports = mongoose.model("User", userSchema); diff --git a/config.sample.js b/config.sample.js deleted file mode 100644 index 4102afe6..00000000 --- a/config.sample.js +++ /dev/null @@ -1,39 +0,0 @@ -module.exports = { - /* The token of your Discord Bot */ - token: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", - /* UserID of your Discord Bot */ - userId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", - /* The URL of the MongoDB database */ - mongoDB: "mongodb://127.0.0.1:27017/discordbot", - /* Set to true for production */ - /* If set to false, commands only will be registered on the support.id server */ - production: true, - /* Spotify */ - spotify: { - clientId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", - clientSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", - }, - /* YouTube Cookie */ - youtubeCookie: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", - /* Support server */ - support: { - id: "123456789098765432", // The ID of the support server - logs: "123456789098765432", // The channel's ID for logs on the support server (when bot joins or leaves a guild) - invite: "https://discord.gg/discord", // Invite link to the support server - }, - /* Embeds defaults */ - embed: { - color: "#00FF00", // Color - footer: { - text: "My Discord Bot | v" + require("./package.json").version, // Footer text - }, - }, - /* Bot's owner informations */ - owner: { - id: "123456789098765432", // The ID of the bot's owner - }, - /* Add your own API keys here */ - apiKeys: { - shlink: "12345678-1234-1234-1234-123456789098" /* Shlink.io REST API key */, - }, -}; diff --git a/config.sample.json b/config.sample.json new file mode 100644 index 00000000..dda773d4 --- /dev/null +++ b/config.sample.json @@ -0,0 +1,28 @@ +{ + "token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "mongoDB": "mongodb://127.0.0.1:27017/discordbot", + "production": true, + "spotify": { + "clientId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "clientSecret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX" + }, + "youtubeCookie": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "devGuildsIds": [], + "embed": { + "color": "#00FF00", + "footer": { + "text": "My Discord Bot | v1.0.0" + } + }, + "owner": { + "id": "123456789098765432" + }, + "apiKeys": {}, + "paths": { + "commands": "./src/commands", + "events": "./src/events", + "locales": "./src/services/languages/locales", + "tasks": "./src/helpers/tasks" + }, + "defaultLang": "en-US" +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..5911a187 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,57 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tsParser from "@typescript-eslint/parser"; +import stylisticJs from "@stylistic/eslint-plugin-js"; +import tsPlugin from "@typescript-eslint/eslint-plugin"; + +/** @type {import("eslint").Linter.Config[]} */ +export default [ + pluginJs.configs.recommended, + { + files: ["**/*.ts"], + ignores: ["**/*.d.ts", "dist"], + languageOptions: { + globals: globals.node, + ecmaVersion: "latest", + sourceType: "module", + parser: tsParser, + }, + plugins: { + "@typescript-eslint": tsPlugin, + "@stylistic/js": stylisticJs, + }, + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" }], + "arrow-body-style": ["error", "as-needed"], + camelcase: "error", + curly: ["error", "multi-line"], + eqeqeq: ["error", "always"], + "no-console": "off", + "no-var": "error", + "prefer-const": "error", + yoda: "error", + "@stylistic/js/arrow-spacing": ["error", { before: true, after: true }], + "@stylistic/js/comma-dangle": ["error", "always-multiline"], + "@stylistic/js/comma-spacing": ["error", { before: false, after: true }], + "@stylistic/js/comma-style": ["error", "last"], + "@stylistic/js/dot-location": ["error", "property"], + "@stylistic/js/keyword-spacing": ["error", { before: true, after: true }], + "@stylistic/js/no-multi-spaces": "error", + "@stylistic/js/no-multiple-empty-lines": [ + "error", + { + max: 2, + maxEOF: 1, + maxBOF: 0, + }, + ], + "@stylistic/js/no-trailing-spaces": ["error"], + "@stylistic/js/object-curly-spacing": ["error", "always"], + "@stylistic/js/quotes": ["error", "double"], + "@stylistic/js/indent": ["error", "tab"], + "@stylistic/js/semi": ["error", "always"], + "@stylistic/js/space-infix-ops": "error", + }, + }, +]; diff --git a/helpers/birthdays.js b/helpers/birthdays.js deleted file mode 100644 index 2a8a57d3..00000000 --- a/helpers/birthdays.js +++ /dev/null @@ -1,65 +0,0 @@ -const { CronJob } = require("cron"); - -/** - * - * @param {import("../base/Client")} client - */ -async function checkBirthdays(client) { - for (const guild of client.guilds.cache.values()) { - try { - 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) { - if (!guild.members.cache.has(user.id)) continue; - - const userDate = new Date(user.birthdate).getFullYear() <= 1970 ? new Date(user.birthdate * 1000) : new Date(user.birthdate); - const day = userDate.getDate(); - const month = userDate.getMonth() + 1; - const year = userDate.getFullYear(); - const age = currentYear - year; - - 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("🎉")); - } - } - } - } catch (e) { - if (e.code === 10003) console.log(`No channel found for ${guild.name}`); - else console.error(`Error processing birthdays for guild "${guild.name}":`, e); - } - } -} - -module.exports.init = async client => new CronJob("0 5 * * *", checkBirthdays(client), null, true, "Europe/Moscow"); -module.exports.run = async client => await checkBirthdays(client); diff --git a/helpers/checkReminds.js b/helpers/checkReminds.js deleted file mode 100644 index 7f0c123b..00000000 --- a/helpers/checkReminds.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * - * @param {import("../base/Client")} 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); - } - }); - - 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: ``, - inline: true, - }, - { - name: client.translate("general/remindme:EMBED_TIME"), - value: ``, - 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); - } - } - }); -} - -module.exports.init = async client => setInterval(async () => await checkReminds(client), 1000); -module.exports.run = async client => await checkReminds(client); diff --git a/helpers/cleanup.js b/helpers/cleanup.js deleted file mode 100644 index 159bd4e1..00000000 --- a/helpers/cleanup.js +++ /dev/null @@ -1,69 +0,0 @@ -// Thanks Stackoverflow <3 - -function setDaysTimeout(callback, days) { - // 86400 seconds in a day - const msInDay = 86400 * 1000; - - let dayCount = 0; - const timer = setInterval(function () { - dayCount++; // a day has passed - - if (dayCount === days) { - clearInterval(timer); - callback.apply(this, []); - } - }, msInDay); -} - -/** - * - * @param {import("../base/Client")} client - */ -module.exports.init = async function (client) { - setDaysTimeout(async () => { - const timestamp = Date.now() + 29 * 24 * 60 * 60 * 1000; // 29 days - const members = client.membersData.find({ transactions: { $gt: [] } }); - - for (const member of members) { - const transactions = member.transactions; - for await (const transaction of transactions) { - if (transaction.date < timestamp) { - const index = transactions.indexOf(transaction); - transactions.splice(index, 1); - - await member.save(); - } - } - } - }, 14); - - setDaysTimeout(async () => { - client.usersData.find({}, function (err, res) { - for (const user of res) { - client.users.fetch(user.id).then(async u => { - if (u.username.match(/.*Deleted User.* [A-z0-9]+/g)) { - await client.databaseCache.users.delete(u.id); - await client.usersData.deleteOne({ id: u.id }); - client.logger.log(`Removed from database deleted user - ID: ${u.id} Username: ${u.username}`); - - await client.usersData.save(); - } - }); - } - }); - - client.membersData.find({}, function (err, res) { - for (const user of res) { - client.users.fetch(user.id).then(async u => { - if (u.username.match(/.*Deleted User.* [A-z0-9]+/g)) { - await client.databaseCache.members.delete(u.id); - await client.membersData.deleteOne({ id: u.id }); - client.logger.log(`Removed from database deleted user - ID: ${u.id} Username: ${u.username}`); - - await client.membersData.save(); - } - }); - } - }); - }, 30); -}; diff --git a/helpers/extenders.js b/helpers/extenders.js deleted file mode 100644 index 54856c47..00000000 --- a/helpers/extenders.js +++ /dev/null @@ -1,105 +0,0 @@ -const { Message, BaseInteraction, User, GuildMember } = require("discord.js"); - -/** - * - * @param {Message|BaseInteraction} context - * @returns {string} Guild's language - */ -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} Formatted 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 () { - return this.discriminator === "0" ? this.username : this.tag; -}; - -GuildMember.prototype.getUsername = function () { - return this.user.getUsername(); -}; - -BaseInteraction.prototype.getLocale = function () { - return getLocale(this); -}; - -BaseInteraction.prototype.translate = function (key, args, locale) { - return translate(this.client, key, args, locale || this.getLocale()); -}; - -BaseInteraction.prototype.replyT = async function (key, args, options = {}) { - return await replyTranslated(this, key, args, options); -}; - -BaseInteraction.prototype.success = async function (key, args, options = {}) { - options.prefixEmoji = "success"; - return await this.replyT(key, args, options); -}; - -BaseInteraction.prototype.error = async function (key, args, options = {}) { - options.prefixEmoji = "error"; - return await this.replyT(key, args, options); -}; - -Message.prototype.getLocale = function () { - return getLocale(this); -}; - -Message.prototype.translate = function (key, args, locale) { - return translate(this.client, key, args, locale || this.getLocale()); -}; - -Message.prototype.replyT = async function (key, args, options = {}) { - return await replyTranslated(this, key, args, options); -}; - -Message.prototype.success = async function (key, args, options = {}) { - options.prefixEmoji = "success"; - return await this.replyT(key, args, options); -}; - -Message.prototype.error = async function (key, args, options = {}) { - options.prefixEmoji = "error"; - return await this.replyT(key, args, options); -}; diff --git a/helpers/functions.js b/helpers/functions.js deleted file mode 100644 index 923885cc..00000000 --- a/helpers/functions.js +++ /dev/null @@ -1,147 +0,0 @@ -const moment = require("moment"); - -module.exports = { - /** - * Asynchronously iterates over a collection and executes a callback function for each item. - * - * @param {any[]} collection - The collection to iterate over. - * @param {(item: any) => Promise} callback - The async callback function to execute for each item in the collection. - * @returns {Promise} A promise that resolves when all items in the collection have been processed. - */ - async asyncForEach(collection, callback) { - const allPromises = collection.map(async key => { - await callback(key); - }); - - return await Promise.all(allPromises); - }, - - /** - * Sorts an array by the specified key in ascending order. - * - * @param {any[]} array - The array to sort. - * @param {string} key - The key to sort the array by. - * @returns {any[]} The sorted array. - */ - sortByKey(array, key) { - return array.sort(function (a, b) { - const x = a[key]; - const y = b[key]; - return x < y ? 1 : x > y ? -1 : 0; - }); - }, - - /** - * Shuffles the elements of the provided array in-place. - * - * @param {any[]} pArray - The array to shuffle. - * @returns {any[]} The shuffled array. - */ - shuffle(pArray) { - const array = []; - - pArray.forEach(element => array.push(element)); - - let currentIndex = array.length, - temporaryValue, - randomIndex; - - while (currentIndex !== 0) { - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - - return array; - }, - - /** - * Generates a random integer between the specified minimum and maximum values (inclusive). - * - * @param {number} [min=0] - The minimum value (inclusive). - * @param {number} [max=100] - The maximum value (inclusive). - * @returns {number} A random integer between min and max. - */ - randomNum(min = 0, max = 100) { - min = Math.floor(min); - max = Math.floor(max); - - return Math.floor(Math.random() * (max - min + 1) + min); - }, - - /** - * Formats a date for the specified client and locale. - * - * @param {Object} client - The client object containing language data. - * @param {string} date - The date to format. - * @param {string} [format=null] - The date format to use. If not provided, the default format for the client's language will be used. - * @param {string} [locale=client.defaultLanguage.name] - The locale to use for formatting the date. - * @returns {string} The formatted date. - */ - printDate(client, date, format = null, locale = client.defaultLanguage.name) { - const languageData = client.languages.find(language => language.name === locale); - if (format === "" || format === null) format = languageData.defaultMomentFormat; - - return moment(new Date(date)).locale(languageData.moment).format(format); - }, - - /** - * Formats a time value relative to the current time. - * - * @param {Object} client - The client object containing language data. - * @param {string|number|Date} time - The time value to format. - * @param {boolean} [type=false] - If true, formats the time as "X time ago", otherwise formats it as "in X time". - * @param {boolean} [prefix=true] - If true, includes a prefix like "in" or "ago" in the formatted time. - * @param {string} [locale=client.defaultLanguage.name] - The locale to use for formatting the time. - * @returns {string} The formatted time value. - */ - convertTime(client, time, type = false, prefix = true, locale = client.defaultLanguage.name) { - const languageData = client.languages.find(language => language.name === locale); - const m = moment(time).locale(languageData.moment); - - return type ? m.toNow(!prefix) : m.fromNow(!prefix); - }, - - /** - * Generates the appropriate noun form based on the given number and noun forms. - * - * @param {number} number - The number to use for determining the noun form. - * @param {string} one - The noun form for the singular case. - * @param {string} two - The noun form for the dual case. - * @param {string} five - The noun form for the plural case. - * @returns {string} The appropriate noun form based on the given number. - */ - getNoun(number, one, two, five) { - let n = Math.abs(number); - n %= 100; - if (n >= 5 && n <= 20) return five; - n %= 10; - - if (n === 1) return one; - if (n >= 2 && n <= 4) return two; - - return five; - }, - - /** - * Function to apply text on a canvas with dynamic font size based on the width constraint. - * - * @param {import("@napi-rs/canvas").Canvas} canvas - The canvas object where the text will be applied. - * @param {string} text - The string of text that needs to be applied on the canvas. - * @param {number} defaultFontSize - The initial font size for the text. It is expected to decrease with each iteration. - * @param {number} width - The maximum width that the text can occupy before it has to shrink down. - * @param {string} font - The name of the font used for drawing the text on the canvas. - * - * @return {string} - The final calculated font size in a format 'px '. - */ - applyText(canvas, text, defaultFontSize, width, font) { - const ctx = canvas.getContext("2d"); - do ctx.font = `${(defaultFontSize -= 1)}px ${font}`; - while (ctx.measureText(text).width > width); - - return ctx.font; - }, -}; diff --git a/helpers/languages.js b/helpers/languages.js deleted file mode 100644 index 78ff2445..00000000 --- a/helpers/languages.js +++ /dev/null @@ -1,51 +0,0 @@ -const i18next = require("i18next"), - Backend = require("i18next-fs-backend"), - path = require("path"), - fs = require("fs").promises; - -async function walkDirectory(dir, namespaces = [], folderName = "") { - const files = await fs.readdir(dir); - - const languages = []; - for (const file of files) { - const stat = await fs.stat(path.join(dir, file)); - if (stat.isDirectory()) { - const isLanguage = file.includes("-"); - if (isLanguage) languages.push(file); - - const folder = await walkDirectory(path.join(dir, file), namespaces, isLanguage ? "" : `${file}/`); - - namespaces = folder.namespaces; - } else { - namespaces.push(`${folderName}${file.substr(0, file.length - 5)}`); - } - } - - return { - namespaces: [...new Set(namespaces)], - languages, - }; -} - -module.exports = async () => { - const options = { - loadPath: path.resolve(__dirname, "../languages/{{lng}}/{{ns}}.json"), - }; - - const { namespaces, languages } = await walkDirectory(path.resolve(__dirname, "../languages/")); - - i18next.use(Backend); - - await i18next.init({ - backend: options, - debug: false, - fallbackLng: "en-US", - initImmediate: false, - interpolation: { escapeValue: false }, - load: "all", - ns: namespaces, - preload: languages, - }); - - return new Map(languages.map(item => [item, i18next.getFixedT(item)])); -}; diff --git a/helpers/logger.js b/helpers/logger.js deleted file mode 100644 index 86f02925..00000000 --- a/helpers/logger.js +++ /dev/null @@ -1,52 +0,0 @@ -const { bgBlue, black, green } = require("chalk"); - -function dateTimePad(value, digits) { - let number = value; - while (number.toString().length < digits) number = "0" + number; - - return number; -} - -function format(tDate) { - return ( - dateTimePad(tDate.getDate(), 2) + - "-" + - dateTimePad(tDate.getMonth() + 1, 2) + - "-" + - dateTimePad(tDate.getFullYear(), 2) + - " " + - dateTimePad(tDate.getHours(), 2) + - ":" + - dateTimePad(tDate.getMinutes(), 2) + - ":" + - dateTimePad(tDate.getSeconds(), 2) + - "." + - dateTimePad(tDate.getMilliseconds(), 3) - ); -} - -module.exports = class Logger { - static log(content) { - return console.log(`[${format(new Date(Date.now()))}]: ${bgBlue("LOG")} ${content}`); - } - - static warn(content) { - return console.log(`[${format(new Date(Date.now()))}]: ${black.bgYellow("WARN")} ${content}`); - } - - static error(content) { - return console.log(`[${format(new Date(Date.now()))}]: ${black.bgRed("ERROR")} ${content}`); - } - - static debug(content) { - return console.log(`[${format(new Date(Date.now()))}]: ${green("DEBUG")} ${content}`); - } - - static cmd(content) { - return console.log(`[${format(new Date(Date.now()))}]: ${black.bgWhite("CMD")} ${content}`); - } - - static ready(content) { - return console.log(`[${format(new Date(Date.now()))}]: ${black.bgGreen("READY")} ${content}`); - } -}; diff --git a/helpers/tictactoe.js b/helpers/tictactoe.js deleted file mode 100644 index a7c44a33..00000000 --- a/helpers/tictactoe.js +++ /dev/null @@ -1,599 +0,0 @@ -// Thanks to simply-djs for this =) -// TODO: Refactor this please... - -const { ButtonBuilder, ActionRowBuilder, ButtonStyle, ComponentType } = require("discord.js"); - -/** - * @param {import("discord.js").ChatInputCommandInteraction} interaction - * @param {any[]} options Array with options (everything is optional) - * @param {string} options.userSlash Name of the user option in the interaction - * @param {string} options.embedFooter Game's embed footer - * @param {string} options.embedColor Game's embed color - * @param {string} options.timeoutEmbedColor Game's embed timeout color - * @param {string} options.xEmoji Emoji for X - * @param {string} options.oEmoji Emoji for O - * @param {string} options.idleEmoji Emoji for "nothing" - * @returns {Promise} - */ -async function tictactoe(interaction, options = {}) { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async resolve => { - try { - const { client } = interaction; - let opponent; - - if (interaction.commandId) { - opponent = interaction.options.getUser(options.userSlash || "user"); - - if (!opponent) - return interaction.reply({ - content: interaction.translate("fun/tictactoe:NO_USER"), - ephemeral: true, - }); - - if (opponent.bot) - return interaction.reply({ - content: interaction.translate("fun/tictactoe:BOT_USER"), - ephemeral: true, - }); - - if (opponent.id == (interaction.user ? interaction.user : interaction.author).id) - return interaction.reply({ - content: interaction.translate("misc:CANT_YOURSELF"), - ephemeral: true, - }); - } else if (!interaction.commandId) { - opponent = interaction.mentions.members.first()?.user; - - if (!opponent) - return interaction.reply({ - content: interaction.translate("fun/tictactoe:NO_USER"), - }); - - if (opponent.bot) - return interaction.reply({ - content: interaction.translate("fun/tictactoe:BOT_USER"), - ephemeral: true, - }); - - if (opponent.id === interaction.member.id) - return interaction.reply({ - content: interaction.translate("misc:CANT_YOURSELF"), - }); - } - - const footer = options.embedFooter || client.config.embed.footer, - color = options.embedColor || client.config.embed.color, - user = interaction.user ? interaction.user : interaction.author; - - const acceptEmbed = client.embed({ - author: { - name: user.getUsername(), - iconURL: user.displayAvatarURL(), - }, - title: interaction.translate("fun/tictactoe:REQUEST_WAIT", { - user: opponent.getUsername(), - }), - color, - footer, - }); - - const accept = new ButtonBuilder().setLabel(interaction.translate("common:ACCEPT")).setStyle(ButtonStyle.Success).setCustomId("acceptttt"); - const decline = new ButtonBuilder().setLabel(interaction.translate("common:DECLINE")).setStyle(ButtonStyle.Danger).setCustomId("declinettt"); - const accep = new ActionRowBuilder().addComponents([accept, decline]); - - const m = await interaction.reply({ - content: interaction.translate("fun/tictactoe:INVITE_USER", { - opponent: opponent.id, - }), - embeds: [acceptEmbed], - components: [accep], - fetchReply: true, - }); - - const collector = m.createMessageComponentCollector({ - componentType: ComponentType.Button, - time: 30 * 1000, - }); - - collector.on("collect", async button => { - if (button.user.id !== opponent.id) - return button.reply({ - content: interaction.translate("fun/tictactoe:REQUEST_SEND", { - opponent: opponent.id, - }), - ephemeral: true, - }); - - if (button.customId == "declinettt") { - button.deferUpdate(); - return collector.stop("decline"); - } else if (button.customId == "acceptttt") { - button.deferUpdate(); - collector.stop(); - - const fighters = [(interaction.user ? interaction.user : interaction.author).id, opponent.id].sort(() => (Math.random() > 0.5 ? 1 : -1)); - - const x_emoji = options.xEmoji || "❌"; - const o_emoji = options.oEmoji || "⭕"; - - const dashmoji = options.idleEmoji || "➖"; - - const Args = { - user: 0, - a1: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - a2: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - a3: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - b1: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - b2: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - b3: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - c1: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - c2: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - c3: { - style: ButtonStyle.Secondary, - emoji: dashmoji, - disabled: false, - }, - }; - - const epm = client.embed({ - title: interaction.translate("fun/tictactoe:DESCRIPTION"), - color, - footer, - }); - - let msg; - if (interaction.commandId) - msg = await interaction.editReply({ - embeds: [ - epm.setDescription( - interaction.translate("fun/tictactoe:WAITING", { - user: Args.userid, - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - ), - ], - }); - else if (!interaction.commandId) - msg = await button.message.edit({ - embeds: [ - epm.setDescription( - interaction.translate("fun/tictactoe:WAITING", { - user: Args.userid, - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - ), - ], - }); - - await ttt(msg); - - // eslint-disable-next-line no-inner-declarations - async function ttt(m) { - Args.userid = fighters[Args.user]; - const won = { - "<:O_:863314110560993340>": false, - "<:X_:863314044781723668>": false, - }; - - const a1 = new ButtonBuilder().setStyle(Args.a1.style).setEmoji(Args.a1.emoji).setCustomId("a1").setDisabled(Args.a1.disabled); - const a2 = new ButtonBuilder().setStyle(Args.a2.style).setEmoji(Args.a2.emoji).setCustomId("a2").setDisabled(Args.a2.disabled); - const a3 = new ButtonBuilder().setStyle(Args.a3.style).setEmoji(Args.a3.emoji).setCustomId("a3").setDisabled(Args.a3.disabled); - const b1 = new ButtonBuilder().setStyle(Args.b1.style).setEmoji(Args.b1.emoji).setCustomId("b1").setDisabled(Args.b1.disabled); - const b2 = new ButtonBuilder().setStyle(Args.b2.style).setEmoji(Args.b2.emoji).setCustomId("b2").setDisabled(Args.b2.disabled); - const b3 = new ButtonBuilder().setStyle(Args.b3.style).setEmoji(Args.b3.emoji).setCustomId("b3").setDisabled(Args.b3.disabled); - const c1 = new ButtonBuilder().setStyle(Args.c1.style).setEmoji(Args.c1.emoji).setCustomId("c1").setDisabled(Args.c1.disabled); - const c2 = new ButtonBuilder().setStyle(Args.c2.style).setEmoji(Args.c2.emoji).setCustomId("c2").setDisabled(Args.c2.disabled); - const c3 = new ButtonBuilder().setStyle(Args.c3.style).setEmoji(Args.c3.emoji).setCustomId("c3").setDisabled(Args.c3.disabled); - const a = new ActionRowBuilder().addComponents([a1, a2, a3]); - const b = new ActionRowBuilder().addComponents([b1, b2, b3]); - const c = new ActionRowBuilder().addComponents([c1, c2, c3]); - const buttons = [a, b, c]; - - if (Args.a1.emoji == o_emoji && Args.b1.emoji == o_emoji && Args.c1.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (Args.a2.emoji == o_emoji && Args.b2.emoji == o_emoji && Args.c2.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (Args.a3.emoji == o_emoji && Args.b3.emoji == o_emoji && Args.c3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (Args.a1.emoji == o_emoji && Args.b2.emoji == o_emoji && Args.c3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (Args.a3.emoji == o_emoji && Args.b2.emoji == o_emoji && Args.c1.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (Args.a1.emoji == o_emoji && Args.a2.emoji == o_emoji && Args.a3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (Args.b1.emoji == o_emoji && Args.b2.emoji == o_emoji && Args.b3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (Args.c1.emoji == o_emoji && Args.c2.emoji == o_emoji && Args.c3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (won["<:O_:863314110560993340>"] != false) - if (Args.user == 0) { - const won = await client.users.fetch(fighters[1]).catch(console.error); - resolve(won); - - if (options.resultBtn === true) - return m - .edit({ - content: interaction.translate("fun/tictactoe:WON", { - winner: fighters[1], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - components: buttons, - - embeds: [ - epm.setDescription( - interaction.translate("fun/tictactoe:WON", { - winner: fighters[1], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - ), - ], - }); - else if (!options.resultBtn || options.resultBtn === false) - return m - .edit({ - content: interaction.translate("fun/tictactoe:WON", { - winner: fighters[1], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - - embeds: [ - epm.setDescription( - `${interaction.translate("fun/tictactoe:WON", { - winner: fighters[1], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - })}\n\`\`\`\n${Args.a1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n${Args.b1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n${Args.c1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), - ), - ], - components: [], - }); - } else if (Args.user == 1) { - const won = await client.users.fetch(fighters[0]).catch(console.error); - resolve(won); - - if (options.resultBtn === true) - return m - .edit({ - content: interaction.translate("fun/tictactoe:WON", { - winner: fighters[0], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - components: buttons, - embeds: [ - epm.setDescription( - interaction.translate("fun/tictactoe:WON", { - winner: fighters[0], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - ), - ], - }); - else if (!options.resultBtn || options.resultBtn === false) - return m - .edit({ - content: interaction.translate("fun/tictactoe:WON", { - winner: fighters[0], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - - embeds: [ - epm.setDescription( - `${interaction.translate("fun/tictactoe:WON", { - winner: fighters[0], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - })}\n\`\`\`\n${Args.a1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n${Args.b1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n${Args.c1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), - ), - ], - components: [], - }); - } - - if (Args.a1.emoji == x_emoji && Args.b1.emoji == x_emoji && Args.c1.emoji == x_emoji) won["<:X_:863314044781723668>"] = true; - if (Args.a2.emoji == x_emoji && Args.b2.emoji == x_emoji && Args.c2.emoji == x_emoji) won["<:X_:863314044781723668>"] = true; - if (Args.a3.emoji == x_emoji && Args.b3.emoji == x_emoji && Args.c3.emoji == x_emoji) won["<:X_:863314044781723668>"] = true; - if (Args.a1.emoji == x_emoji && Args.b2.emoji == x_emoji && Args.c3.emoji == x_emoji) won["<:X_:863314044781723668>"] = true; - if (Args.a3.emoji == x_emoji && Args.b2.emoji == x_emoji && Args.c1.emoji == x_emoji) won["<:X_:863314044781723668>"] = true; - if (Args.a1.emoji == x_emoji && Args.a2.emoji == x_emoji && Args.a3.emoji == x_emoji) won["<:X_:863314044781723668>"] = true; - if (Args.b1.emoji == x_emoji && Args.b2.emoji == x_emoji && Args.b3.emoji == x_emoji) won["<:X_:863314044781723668>"] = true; - if (Args.c1.emoji == x_emoji && Args.c2.emoji == x_emoji && Args.c3.emoji == x_emoji) won["<:X_:863314044781723668>"] = true; - if (won["<:X_:863314044781723668>"] != false) - if (Args.user == 0) { - const won = await client.users.fetch(fighters[1]).catch(console.error); - resolve(won); - - if (options.resultBtn === true) - return m - .edit({ - content: interaction.translate("fun/tictactoe:WON", { - winner: fighters[1], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - components: buttons, - embeds: [ - epm.setDescription( - interaction.translate("fun/tictactoe:WON", { - winner: fighters[1], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - ), - ], - }); - else if (!options.resultBtn || options.resultBtn === false) - return m - .edit({ - content: interaction.translate("fun/tictactoe:WON", { - winner: fighters[1], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - embeds: [ - epm.setDescription( - `${interaction.translate("fun/tictactoe:WON", { - winner: fighters[1], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - })}\n\`\`\`\n${Args.a1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n${Args.b1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n${Args.c1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), - ), - ], - components: [], - }); - } else if (Args.user == 1) { - const won = await client.users.fetch(fighters[0]).catch(console.error); - resolve(won); - - if (options.resultBtn === true) - return m - .edit({ - content: interaction.translate("fun/tictactoe:WON", { - winner: fighters[0], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - components: buttons, - embeds: [ - epm.setDescription( - interaction.translate("fun/tictactoe:WON", { - winner: fighters[0], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - ), - ], - }); - else - return m - .edit({ - content: interaction.translate("fun/tictactoe:WON", { - winner: fighters[0], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - }), - embeds: [ - epm.setDescription( - `${interaction.translate("fun/tictactoe:WON", { - winner: fighters[0], - emoji: client.emojis.cache.get(o_emoji) || "⭕", - })}\n\`\`\`\n${Args.a1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n${Args.b1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n${Args.c1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c2.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c3.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), - ), - ], - components: [], - }); - } - - m.edit({ - content: `<@${Args.userid}>`, - embeds: [ - epm.setDescription( - interaction.translate("fun/tictactoe:WAITING", { - user: Args.userid, - emoji: Args.user == 0 ? `${client.emojis.cache.get(o_emoji) || "⭕"}` : `${client.emojis.cache.get(x_emoji) || "❌"}`, - }), - ), - ], - components: [a, b, c], - }); - - const collector = m.createMessageComponentCollector({ - componentType: ComponentType.Button, - max: 1, - }); - - collector.on("collect", b => { - if (b.user.id !== Args.userid) { - b.reply({ - content: interaction.translate("fun/tictactoe:CANT_PLAY"), - ephemeral: true, - }); - - ttt(m); - } else { - if (Args.user == 0) { - Args.user = 1; - Args[b.customId] = { - style: ButtonStyle.Success, - emoji: o_emoji, - disabled: true, - }; - } else { - Args.user = 0; - Args[b.customId] = { - style: ButtonStyle.Danger, - emoji: x_emoji, - disabled: true, - }; - } - b.deferUpdate(); - const map = (obj, fun) => - Object.entries(obj).reduce( - (prev, [key, value]) => ({ - ...prev, - [key]: fun(key, value), - }), - {}, - ); - const objectFilter = (obj, predicate) => - Object.keys(obj) - .filter(key => predicate(obj[key])) - .reduce((res, key) => ((res[key] = obj[key]), res), {}); - const Brgs = objectFilter( - map(Args, (_, fruit) => fruit.emoji == dashmoji), - num => num == true, - ); - - if (Object.keys(Brgs).length == 0) { - if (Args.a1.emoji == o_emoji && Args.b1.emoji == o_emoji && Args.c1.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - if (Args.a2.emoji == o_emoji && Args.b2.emoji == o_emoji && Args.c2.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - if (Args.a3.emoji == o_emoji && Args.b3.emoji == o_emoji && Args.c3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - if (Args.a1.emoji == o_emoji && Args.b2.emoji == o_emoji && Args.c3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - if (Args.a3.emoji == o_emoji && Args.b2.emoji == o_emoji && Args.c1.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - if (Args.a1.emoji == o_emoji && Args.a2.emoji == o_emoji && Args.a3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - if (Args.b1.emoji == o_emoji && Args.b2.emoji == o_emoji && Args.b3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - if (Args.c1.emoji == o_emoji && Args.c2.emoji == o_emoji && Args.c3.emoji == o_emoji) won["<:O_:863314110560993340>"] = true; - - if (won["<:O_:863314110560993340>"] == true) return ttt(m); - else if (won["<:X_:863314044781723668>"] == true) return; - else { - ttt(m); - - if (options.resultBtn === true) - return m - .edit({ - content: interaction.translate("fun/tictactoe:TIE"), - embeds: [epm.setDescription(interaction.translate("fun/tictactoe:TIE_DESC"))], - }); - else - return m - .edit({ - content: interaction.translate("fun/tictactoe:TIE"), - embeds: [ - epm.setDescription( - `${interaction.translate("fun/tictactoe:TIE_DESC")}!\n\`\`\`\n${Args.a1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.a2.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")} | ${Args.a3.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")}\n${Args.b1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.b2.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")} | ${Args.b3.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")}\n${Args.c1.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")} | ${Args.c2.emoji - .replace(o_emoji, "⭕") - .replace(x_emoji, "❌")} | ${Args.c3.emoji.replace(o_emoji, "⭕").replace(x_emoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), - ), - ], - components: [], - }) - .catch(() => {}); - } - } - - ttt(m); - } - }); - collector.on("end", (collected, reason) => { - if (collected.size === 0 && reason == "time") - m.edit({ - content: interaction.translate("fun/tictactoe:NO_ANSWER", { - user: Args.userid, - }), - components: [], - }); - }); - } - } - }); - - collector.on("end", (_, reason) => { - if (reason == "time") { - const embed = client.embed({ - author: { - name: user.getUsername(), - iconURL: user.displayAvatarURL(), - }, - title: interaction.translate("fun/tictactoe:NO_ANSWER_TITLE"), - description: interaction.translate("misc:TIMED_OUT"), - color: options.timeoutEmbedColor || "#C90000", - footer, - }); - - m.interaction.editReply({ - content: interaction.translate("fun/tictactoe:NOT_ANSWERED", { - user: opponent.id, - }), - embeds: [embed], - components: [], - }); - } - if (reason == "decline") { - const embed = client.embed({ - author: { - name: user.getUsername(), - iconURL: user.displayAvatarURL(), - }, - title: interaction.translate("fun/tictactoe:CANCELED"), - description: interaction.translate("fun/tictactoe:CANCELED_DESC", { - user: opponent.id, - }), - color: options.timeoutEmbedColor || "#C90000", - footer, - }); - - m.interaction.editReply({ - embeds: [embed], - components: [], - }); - } - }); - } catch (e) { - console.log("TicTacToe errored:", e); - } - }); -} - -module.exports = tictactoe; diff --git a/index.js b/index.js deleted file mode 100644 index a38734d5..00000000 --- a/index.js +++ /dev/null @@ -1,29 +0,0 @@ -require("./helpers/extenders"); - -const { GatewayIntentBits } = require("discord.js"), - Client = require("./base/Client"); - -const client = new Client({ - intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildEmojisAndStickers, GatewayIntentBits.GuildIntegrations, GatewayIntentBits.GuildInvites, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildPresences, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildMessageTyping, GatewayIntentBits.MessageContent, GatewayIntentBits.DirectMessageTyping, GatewayIntentBits.DirectMessages, GatewayIntentBits.DirectMessageReactions ], - allowedMentions: { parse: ["everyone", "roles", "users"] }, -}); - -(async () => { - console.time("botReady"); - - client.translations = await require("./helpers/languages")(); - - await client.loadEvents("../events"); - await client.loadCommands("../commands"); - await client.init(); -})(); - -client - .on("disconnect", () => client.logger.warn("Bot disconnected.")) - .on("reconnecting", () => client.logger.warn("Bot reconnecting...")) - .on("warn", console.log) - .on("error", console.log); - -process - .on("unhandledRejection", e => console.log(e)) - .on("uncaughtException", e => console.log(e)); \ No newline at end of file diff --git a/languages/language-meta.json b/languages/language-meta.json deleted file mode 100644 index ae102d46..00000000 --- a/languages/language-meta.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "name": "en-US", - "nativeName": "English", - "moment": "en", - "defaultMomentFormat": "HH:mm:ss, MMMM Do YYYY", - "default": true - }, - { - "name": "ru-RU", - "nativeName": "Русский", - "moment": "ru", - "defaultMomentFormat": "HH:mm:ss, Do MMMM YYYY", - "default": false - }, - { - "name": "uk-UA", - "nativeName": "Українська", - "moment": "uk", - "defaultMomentFormat": "HH:mm:ss, Do MMMM YYYY", - "default": false - } -] \ No newline at end of file diff --git a/package.json b/package.json index b1e79467..7e2d26a0 100644 --- a/package.json +++ b/package.json @@ -2,20 +2,21 @@ "name": "jaba", "version": "4.6.7", "description": "My Discord Bot", - "main": "index.js", + "main": "src/index.js", + "type": "module", "scripts": { - "start": "node ." + "start": "node dist/index.js", + "dev": "tsx watch src/index.ts", + "build": "tsc && tsc-alias" }, "author": "https://github.com/JonnyBro", "dependencies": { "@discord-player/extractor": "^4.5.1", "@discordjs/opus": "^0.9.0", - "@discordjs/rest": "^2.4.0", "@discordjs/voice": "^0.18.0", "@napi-rs/canvas": "^0.1.63", "chalk": "^4.1.2", "cron": "^3.2.1", - "discord-api-types": "^0.37.107", "discord-giveaways": "^6.0.1", "discord-player": "^6.7.1", "discord-player-youtubei": "1.3.5", @@ -24,99 +25,24 @@ "i18next": "^24.0.0", "i18next-fs-backend": "^2.6.0", "md5": "^2.3.0", - "moment": "^2.29.4", "mongoose": "^8.8.2", "ms": "^2.1.3", "node-fetch": "^2.7.0" }, "devDependencies": { - "eslint": "^8.57.1" - }, - "eslintConfig": { - "extends": "eslint:recommended", - "env": { - "commonjs": true, - "es6": true, - "es2020": true, - "node": true - }, - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2020 - }, - "rules": { - "arrow-spacing": [ - "warn", - { - "before": true, - "after": true - } - ], - "comma-dangle": [ - "error", - "always-multiline" - ], - "comma-spacing": "error", - "comma-style": "error", - "dot-location": [ - "error", - "property" - ], - "handle-callback-err": "off", - "indent": [ - "error", - "tab", - { - "SwitchCase": 1 - } - ], - "keyword-spacing": "error", - "max-nested-callbacks": [ - "error", - { - "max": 4 - } - ], - "max-statements-per-line": [ - "error", - { - "max": 2 - } - ], - "no-console": "off", - "no-multi-spaces": "error", - "no-multiple-empty-lines": [ - "error", - { - "max": 2, - "maxEOF": 1, - "maxBOF": 0 - } - ], - "no-trailing-spaces": [ - "error" - ], - "no-var": "error", - "object-curly-spacing": [ - "error", - "always" - ], - "prefer-const": "error", - "quotes": [ - "error", - "double" - ], - "semi": [ - "error", - "always" - ], - "space-in-parens": "error", - "space-infix-ops": "error", - "space-unary-ops": "error", - "yoda": "error" - } + "@eslint/js": "^9.16.0", + "@stylistic/eslint-plugin-js": "^2.11.0", + "@types/md5": "^2.3.5", + "@types/ms": "^0.7.34", + "@types/node": "^22.10.5", + "@typescript-eslint/eslint-plugin": "^8.19.1", + "@typescript-eslint/parser": "^8.19.1", + "eslint": "^9.16.0", + "globals": "^15.13.0", + "prettier": "^3.4.2", + "prettier-eslint": "^16.3.0", + "tsc-alias": "^1.8.10", + "tsx": "^4.19.2", + "typescript": "^5.7.3" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 260d182c..a8b69e24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ importers: '@discordjs/opus': specifier: ^0.9.0 version: 0.9.0 - '@discordjs/rest': - specifier: ^2.4.0 - version: 2.4.0 '@discordjs/voice': specifier: ^0.18.0 version: 0.18.0(@discordjs/opus@0.9.0) @@ -29,9 +26,6 @@ importers: cron: specifier: ^3.2.1 version: 3.2.1 - discord-api-types: - specifier: ^0.37.107 - version: 0.37.107 discord-giveaways: specifier: ^6.0.1 version: 6.0.1(discord.js@14.16.3) @@ -49,16 +43,13 @@ importers: version: 5.1.4 i18next: specifier: ^24.0.0 - version: 24.0.0 + version: 24.0.0(typescript@5.7.3) i18next-fs-backend: specifier: ^2.6.0 version: 2.6.0 md5: specifier: ^2.3.0 version: 2.3.0 - moment: - specifier: ^2.29.4 - version: 2.30.1 mongoose: specifier: ^8.8.2 version: 8.8.2(socks@2.8.3) @@ -69,9 +60,48 @@ importers: specifier: ^2.7.0 version: 2.7.0 devDependencies: + '@eslint/js': + specifier: ^9.16.0 + version: 9.16.0 + '@stylistic/eslint-plugin-js': + specifier: ^2.11.0 + version: 2.11.0(eslint@9.16.0) + '@types/md5': + specifier: ^2.3.5 + version: 2.3.5 + '@types/ms': + specifier: ^0.7.34 + version: 0.7.34 + '@types/node': + specifier: ^22.10.5 + version: 22.10.5 + '@typescript-eslint/eslint-plugin': + specifier: ^8.19.1 + version: 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.16.0)(typescript@5.7.3))(eslint@9.16.0)(typescript@5.7.3) + '@typescript-eslint/parser': + specifier: ^8.19.1 + version: 8.19.1(eslint@9.16.0)(typescript@5.7.3) eslint: - specifier: ^8.57.1 - version: 8.57.1 + specifier: ^9.16.0 + version: 9.16.0 + globals: + specifier: ^15.13.0 + version: 15.13.0 + prettier: + specifier: ^3.4.2 + version: 3.4.2 + prettier-eslint: + specifier: ^16.3.0 + version: 16.3.0 + tsc-alias: + specifier: ^1.8.10 + version: 1.8.10 + tsx: + specifier: ^4.19.2 + version: 4.19.2 + typescript: + specifier: ^5.7.3 + version: 5.7.3 packages: @@ -137,28 +167,204 @@ packages: resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==} engines: {node: '>=16.11.0'} + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.0': - resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/config-array@0.19.1': + resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.9.1': + resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/eslintrc@3.2.0': + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@8.57.1': resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/js@9.16.0': + resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.5': + resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.4': + resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -172,6 +378,18 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@mongodb-js/saslprep@1.1.9': resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} @@ -263,10 +481,19 @@ packages: resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sindresorhus/is@5.6.0': resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} + '@stylistic/eslint-plugin-js@2.11.0': + resolution: {integrity: sha512-btchD0P3iij6cIk5RR5QMdEhtCCV0+L6cNheGhGCd//jaHILZMTi/EOqgEDAf1s4ZoViyExoToM+S2Iwa3U9DA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.40.0' + '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -274,14 +501,26 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/http-cache-semantics@4.0.4': resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/luxon@3.4.2': resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} - '@types/node@22.5.5': - resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} + '@types/md5@2.3.5': + resolution: {integrity: sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==} + + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} '@types/webidl-conversions@7.0.3': resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} @@ -292,6 +531,84 @@ packages: '@types/ws@8.5.12': resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} + '@typescript-eslint/eslint-plugin@8.19.1': + resolution: {integrity: sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@8.19.1': + resolution: {integrity: sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/scope-manager@8.19.1': + resolution: {integrity: sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.19.1': + resolution: {integrity: sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/types@8.19.1': + resolution: {integrity: sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@8.19.1': + resolution: {integrity: sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/utils@8.19.1': + resolution: {integrity: sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/visitor-keys@8.19.1': + resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -311,8 +628,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -323,17 +640,33 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} @@ -345,18 +678,33 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} barse@0.4.3: resolution: {integrity: sha512-UEpvriJqAn8zuVinYICuKoPttZy3XxXEoqX/V2uYAL4zzJRuNzCK3+20nAu3YUIa2U7G53kf90wfBIp9/A+Odw==} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + bson@6.9.0: resolution: {integrity: sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==} engines: {node: '>=16.20.1'} @@ -373,6 +721,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + chalk@1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -387,6 +739,10 @@ packages: resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} engines: {node: '>= 6'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -406,6 +762,14 @@ packages: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + + common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -418,8 +782,8 @@ packages: cron@3.2.1: resolution: {integrity: sha512-w2n5l49GMmmkBFEsH9FIDhjZ1n1QgTMOCMGuQtOXs5veNiosZmso6bQGuqOJSYAXXrG84WQFVneNk+Yt0Ua9iw==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} crypt@0.0.2: @@ -467,6 +831,10 @@ packages: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + discord-api-types@0.37.100: resolution: {integrity: sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA==} @@ -502,6 +870,9 @@ packages: resolution: {integrity: sha512-EPCWE9OkA9DnFFNrO7Kl1WHHDYFXu3CNVFJg63bfU7hVtjZGyhShwZtSBImINQRWxWP2tgo2XI+QhdXx28r0aA==} engines: {node: '>=18'} + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -526,6 +897,15 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -534,15 +914,38 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true + eslint@9.16.0: + resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -570,6 +973,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -587,10 +994,18 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + file-type@16.5.4: resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} engines: {node: '>=10'} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -599,6 +1014,10 @@ packages: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} @@ -617,6 +1036,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + gamedig@5.1.4: resolution: {integrity: sha512-MgSbNVGh5QMdrmRTrZ3W7W6sC5/Mx+dMgTy2uZCKQ9vns9eFXkQj61Pw2Y2FNHNMMp4DXFSUMYAPJWLcR16Wwg==} engines: {node: '>=16.20.0'} @@ -638,6 +1062,13 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -650,6 +1081,18 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.13.0: + resolution: {integrity: sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + got@13.0.0: resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} engines: {node: '>=16'} @@ -657,6 +1100,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + has-ansi@2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -715,6 +1162,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -726,6 +1177,10 @@ packages: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} @@ -741,6 +1196,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} @@ -806,6 +1265,13 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loglevel-colored-level-prefix@1.0.0: + resolution: {integrity: sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==} + + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + long@5.2.3: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} @@ -830,6 +1296,14 @@ packages: memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -841,6 +1315,14 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -861,9 +1343,6 @@ packages: engines: {node: '>=10'} hasBin: true - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} @@ -909,6 +1388,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mylas@2.1.13: + resolution: {integrity: sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==} + engines: {node: '>=12.0.0'} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -940,6 +1423,10 @@ packages: engines: {node: '>=6'} hasBin: true + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + normalize-url@8.0.1: resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} engines: {node: '>=14.16'} @@ -996,14 +1483,47 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + peek-readable@4.1.0: resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} engines: {node: '>=8'} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier-eslint@16.3.0: + resolution: {integrity: sha512-Lh102TIFCr11PJKUMQ2kwNmxGhTsv/KzUg9QYF2Gkw259g/kPgndZDWavk7/ycbRvj2oz4BPZ1gCU8bhfZH/Xg==} + engines: {node: '>=16.10.0'} + peerDependencies: + prettier-plugin-svelte: ^3.0.0 + svelte-eslint-parser: '*' + peerDependenciesMeta: + prettier-plugin-svelte: + optional: true + svelte-eslint-parser: + optional: true + + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + prism-media@1.3.5: resolution: {integrity: sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==} peerDependencies: @@ -1028,6 +1548,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1038,6 +1562,9 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + readable-stream@1.0.34: resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} @@ -1052,9 +1579,16 @@ packages: resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} engines: {node: '>=8'} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + require-relative@0.8.7: + resolution: {integrity: sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==} + resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -1062,6 +1596,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} engines: {node: '>=14.16'} @@ -1126,6 +1663,10 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -1167,6 +1708,10 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1179,6 +1724,10 @@ packages: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} + supports-color@2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1193,6 +1742,10 @@ packages: tiny-typed-emitter@2.1.0: resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + token-types@4.2.1: resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} engines: {node: '>=10'} @@ -1204,12 +1757,33 @@ packages: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-api-utils@2.0.0: + resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + tsc-alias@1.8.10: + resolution: {integrity: sha512-Ibv4KAWfFkFdKJxnWfVtdOmB0Zi1RJVxcbPGiCDsFpCQSsmpWyuzHG3rQyI5YkobWwxFPEyQfu1hdo4qLG2zPw==} + hasBin: true + tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1218,8 +1792,13 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -1245,6 +1824,12 @@ packages: varint@6.0.0: resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -1427,12 +2012,101 @@ snapshots: - bufferutil - utf-8-validate + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.0': {} + '@eslint-community/eslint-utils@4.4.0(eslint@9.16.0)': + dependencies: + eslint: 9.16.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.19.1': + dependencies: + '@eslint/object-schema': 2.1.5 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.9.1': + dependencies: + '@types/json-schema': 7.0.15 '@eslint/eslintrc@2.1.4': dependencies: @@ -1448,10 +2122,39 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/eslintrc@3.2.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.7 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + '@eslint/js@8.57.1': {} + '@eslint/js@9.16.0': {} + + '@eslint/object-schema@2.1.5': {} + + '@eslint/plugin-kit@0.2.4': + dependencies: + levn: 0.4.1 + '@fastify/busboy@2.1.1': {} + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -1464,6 +2167,14 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.1': {} + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + '@mongodb-js/saslprep@1.1.9': dependencies: sparse-bitfield: 3.0.3 @@ -1532,21 +2243,37 @@ snapshots: '@sapphire/snowflake@3.5.3': {} + '@sinclair/typebox@0.27.8': {} + '@sindresorhus/is@5.6.0': {} + '@stylistic/eslint-plugin-js@2.11.0(eslint@9.16.0)': + dependencies: + eslint: 9.16.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 '@tokenizer/token@0.3.0': {} + '@types/estree@1.0.6': {} + '@types/http-cache-semantics@4.0.4': {} + '@types/json-schema@7.0.15': {} + '@types/luxon@3.4.2': {} - '@types/node@22.5.5': + '@types/md5@2.3.5': {} + + '@types/ms@0.7.34': {} + + '@types/node@22.10.5': dependencies: - undici-types: 6.19.8 + undici-types: 6.20.0 '@types/webidl-conversions@7.0.3': {} @@ -1556,7 +2283,124 @@ snapshots: '@types/ws@8.5.12': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.10.5 + + '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.16.0)(typescript@5.7.3))(eslint@9.16.0)(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.19.1(eslint@9.16.0)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/type-utils': 8.19.1(eslint@9.16.0)(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.16.0)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.19.1 + eslint: 9.16.0 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.7 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.19.1(eslint@9.16.0)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.19.1 + debug: 4.3.7 + eslint: 9.16.0 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/scope-manager@8.19.1': + dependencies: + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/visitor-keys': 8.19.1 + + '@typescript-eslint/type-utils@8.19.1(eslint@9.16.0)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.16.0)(typescript@5.7.3) + debug: 4.3.7 + eslint: 9.16.0 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/types@8.19.1': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.7 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.3 + ts-api-utils: 1.4.3(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.19.1(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/visitor-keys': 8.19.1 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.19.1(eslint@9.16.0)(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.16.0) + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + eslint: 9.16.0 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@8.19.1': + dependencies: + '@typescript-eslint/types': 8.19.1 + eslint-visitor-keys: 4.2.0 '@ungap/structured-clone@1.2.0': {} @@ -1566,11 +2410,11 @@ snapshots: abbrev@1.1.1: {} - acorn-jsx@5.3.2(acorn@8.12.1): + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - acorn: 8.12.1 + acorn: 8.14.0 - acorn@8.12.1: {} + acorn@8.14.0: {} agent-base@6.0.2: dependencies: @@ -1585,14 +2429,25 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@2.1.1: {} + ansi-regex@5.0.1: {} + ansi-styles@2.2.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + any-promise@1.3.0: {} + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + aproba@2.0.0: {} are-we-there-yet@2.0.0: @@ -1602,12 +2457,16 @@ snapshots: argparse@2.0.1: {} + array-union@2.1.0: {} + balanced-match@1.0.2: {} barse@0.4.3: dependencies: readable-stream: 1.0.34 + binary-extensions@2.3.0: {} + boolbase@1.0.0: {} brace-expansion@1.1.11: @@ -1615,6 +2474,14 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + bson@6.9.0: {} cacheable-lookup@7.0.0: {} @@ -1631,6 +2498,14 @@ snapshots: callsites@3.1.0: {} + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -1657,6 +2532,18 @@ snapshots: parse5: 7.2.1 parse5-htmlparser2-tree-adapter: 7.1.0 + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chownr@2.0.0: {} color-convert@2.0.1: @@ -1669,6 +2556,10 @@ snapshots: commander@6.2.1: {} + commander@9.5.0: {} + + common-tags@1.8.2: {} + concat-map@0.0.1: {} console-control-strings@1.1.0: {} @@ -1680,7 +2571,7 @@ snapshots: '@types/luxon': 3.4.2 luxon: 3.5.0 - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -1718,6 +2609,10 @@ snapshots: detect-libc@2.0.3: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + discord-api-types@0.37.100: {} discord-api-types@0.37.107: {} @@ -1790,6 +2685,8 @@ snapshots: - bufferutil - utf-8-validate + dlv@1.1.3: {} + doctrine@3.0.0: dependencies: esutils: 2.0.3 @@ -1816,6 +2713,35 @@ snapshots: entities@4.5.0: {} + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + escape-string-regexp@1.0.5: {} + escape-string-regexp@4.0.0: {} eslint-scope@7.2.2: @@ -1823,12 +2749,19 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.0: {} + eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/regexpp': 4.12.1 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 '@humanwhocodes/config-array': 0.13.0 @@ -1837,7 +2770,7 @@ snapshots: '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.7 doctrine: 3.0.0 escape-string-regexp: 4.0.0 @@ -1868,10 +2801,55 @@ snapshots: transitivePeerDependencies: - supports-color + eslint@9.16.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.16.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.1 + '@eslint/core': 0.9.1 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.16.0 + '@eslint/plugin-kit': 0.2.4 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + espree@9.6.1: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 esquery@1.6.0: @@ -1890,6 +2868,14 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -1907,12 +2893,20 @@ snapshots: dependencies: flat-cache: 3.2.0 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + file-type@16.5.4: dependencies: readable-web-to-node-stream: 3.0.2 strtok3: 6.3.0 token-types: 4.2.1 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -1924,6 +2918,11 @@ snapshots: keyv: 4.5.4 rimraf: 3.0.2 + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + flatted@3.3.1: {} form-data-encoder@2.1.4: {} @@ -1938,6 +2937,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.3: + optional: true + gamedig@5.1.4: dependencies: cheerio: 1.0.0-rc.12 @@ -1976,6 +2978,14 @@ snapshots: get-stream@6.0.1: {} + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -1993,6 +3003,19 @@ snapshots: dependencies: type-fest: 0.20.2 + globals@14.0.0: {} + + globals@15.13.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + got@13.0.0: dependencies: '@sindresorhus/is': 5.6.0 @@ -2009,6 +3032,10 @@ snapshots: graphemer@1.4.0: {} + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + has-flag@4.0.0: {} has-unicode@2.0.1: {} @@ -2040,9 +3067,11 @@ snapshots: i18next-fs-backend@2.6.0: {} - i18next@24.0.0: + i18next@24.0.0(typescript@5.7.3): dependencies: '@babel/runtime': 7.25.6 + optionalDependencies: + typescript: 5.7.3 iconv-lite@0.6.3: dependencies: @@ -2059,6 +3088,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -2072,6 +3103,10 @@ snapshots: sprintf-js: 1.1.3 optional: true + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + is-buffer@1.1.6: {} is-extglob@2.1.1: {} @@ -2082,6 +3117,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-number@7.0.0: {} + is-path-inside@3.0.3: {} isarray@0.0.1: {} @@ -2097,7 +3134,7 @@ snapshots: jintr@3.0.2: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 js-yaml@4.1.0: dependencies: @@ -2139,6 +3176,13 @@ snapshots: lodash@4.17.21: {} + loglevel-colored-level-prefix@1.0.0: + dependencies: + chalk: 1.1.3 + loglevel: 1.9.2 + + loglevel@1.9.2: {} + long@5.2.3: {} lowercase-keys@3.0.0: {} @@ -2159,6 +3203,13 @@ snapshots: memory-pager@1.5.0: {} + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + mimic-response@3.1.0: {} mimic-response@4.0.0: {} @@ -2167,6 +3218,14 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.1 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + minimist@1.2.8: {} minipass@3.3.6: @@ -2182,8 +3241,6 @@ snapshots: mkdirp@1.0.4: {} - moment@2.30.1: {} - mongodb-connection-string-url@3.0.1: dependencies: '@types/whatwg-url': 11.0.5 @@ -2226,6 +3283,8 @@ snapshots: ms@2.1.3: {} + mylas@2.1.13: {} + natural-compare@1.4.0: {} node-addon-api@5.1.0: {} @@ -2251,6 +3310,8 @@ snapshots: dependencies: abbrev: 1.1.1 + normalize-path@3.0.0: {} + normalize-url@8.0.1: {} npmlog@5.0.1: @@ -2308,10 +3369,43 @@ snapshots: path-key@3.1.1: {} + path-type@4.0.0: {} + peek-readable@4.1.0: {} + picomatch@2.3.1: {} + + plimit-lit@1.6.1: + dependencies: + queue-lit: 1.5.2 + prelude-ls@1.2.1: {} + prettier-eslint@16.3.0: + dependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + common-tags: 1.8.2 + dlv: 1.1.3 + eslint: 8.57.1 + indent-string: 4.0.0 + lodash.merge: 4.6.2 + loglevel-colored-level-prefix: 1.0.0 + prettier: 3.4.2 + pretty-format: 29.7.0 + require-relative: 0.8.7 + typescript: 5.7.3 + vue-eslint-parser: 9.4.3(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + prettier@3.4.2: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + prism-media@1.3.5(@discordjs/opus@0.9.0): optionalDependencies: '@discordjs/opus': 0.9.0 @@ -2320,6 +3414,8 @@ snapshots: punycode@2.3.1: {} + queue-lit@1.5.2: {} + queue-microtask@1.2.3: {} quick-lru@5.1.1: {} @@ -2328,6 +3424,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + react-is@18.3.1: {} + readable-stream@1.0.34: dependencies: core-util-is: 1.0.3 @@ -2355,12 +3453,20 @@ snapshots: dependencies: readable-stream: 3.6.2 + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + regenerator-runtime@0.14.1: {} + require-relative@0.8.7: {} + resolve-alpn@1.2.1: {} resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + responselike@3.0.0: dependencies: lowercase-keys: 3.0.0 @@ -2413,6 +3519,8 @@ snapshots: signal-exit@3.0.7: {} + slash@3.0.0: {} + smart-buffer@4.2.0: optional: true @@ -2461,6 +3569,10 @@ snapshots: dependencies: safe-buffer: 5.2.1 + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -2472,6 +3584,8 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 4.1.0 + supports-color@2.0.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -2489,6 +3603,10 @@ snapshots: tiny-typed-emitter@2.1.0: {} + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + token-types@4.2.1: dependencies: '@tokenizer/token': 0.3.0 @@ -2500,17 +3618,43 @@ snapshots: dependencies: punycode: 2.3.1 + ts-api-utils@1.4.3(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + + ts-api-utils@2.0.0(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + ts-mixer@6.0.4: {} + tsc-alias@1.8.10: + dependencies: + chokidar: 3.6.0 + commander: 9.5.0 + globby: 11.1.0 + mylas: 2.1.13 + normalize-path: 3.0.0 + plimit-lit: 1.6.1 + tslib@2.7.0: {} + tsx@4.19.2: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 type-fest@0.20.2: {} - undici-types@6.19.8: {} + typescript@5.7.3: {} + + undici-types@6.20.0: {} undici@5.28.4: dependencies: @@ -2530,6 +3674,19 @@ snapshots: varint@6.0.0: {} + vue-eslint-parser@9.4.3(eslint@8.57.1): + dependencies: + debug: 4.3.7 + eslint: 8.57.1 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + web-streams-polyfill@3.3.3: {} webidl-conversions@3.0.1: {} diff --git a/src/adapters/cache/ICacheAdapter.ts b/src/adapters/cache/ICacheAdapter.ts new file mode 100644 index 00000000..80fc8c85 --- /dev/null +++ b/src/adapters/cache/ICacheAdapter.ts @@ -0,0 +1,9 @@ +export default class ICacheAdapter { + get(_key: string) {} + + set(_key: string, _value: T) {} + + clear() {} + + delete(_key: string) {} +} diff --git a/src/adapters/cache/MapCache.ts b/src/adapters/cache/MapCache.ts new file mode 100644 index 00000000..bab9ec2e --- /dev/null +++ b/src/adapters/cache/MapCache.ts @@ -0,0 +1,24 @@ +import ICacheAdapter from "./ICacheAdapter.js"; + +export default class MapCache extends ICacheAdapter { + store = new Map(); + constructor() { + super(); + } + + get(key: string) { + return this.store.get(key); + } + + set(key: string, value: T) { + this.store.set(key, value); + } + + clear() { + this.store.clear(); + } + + delete(key: string) { + this.store.delete(key); + } +} diff --git a/src/adapters/database/IDatabaseAdapter.ts b/src/adapters/database/IDatabaseAdapter.ts new file mode 100644 index 00000000..6f6df16f --- /dev/null +++ b/src/adapters/database/IDatabaseAdapter.ts @@ -0,0 +1,9 @@ +export default abstract class IDatabaseAdapter { + abstract connect(): Promise; + abstract disconnect(): Promise; + abstract find(_model: ModelType, _query?: QueryType, _options?: OptionsType): Promise; + abstract findOne(_model: ModelType, _query?: QueryType, _options?: OptionsType): Promise; + abstract updateOne(_model: ModelType, _filter: QueryType, _update: UpdateType, _options?: OptionsType): Promise; + abstract deleteOne(_model: ModelType, _filter: QueryType): Promise; + abstract findOneOrCreate(_model: ModelType, _filter: QueryType): Promise; +} diff --git a/src/adapters/database/MongooseAdapter.ts b/src/adapters/database/MongooseAdapter.ts new file mode 100644 index 00000000..871402c3 --- /dev/null +++ b/src/adapters/database/MongooseAdapter.ts @@ -0,0 +1,80 @@ +import mongoose, { ConnectOptions, Document, Model, FilterQuery, QueryOptions, UpdateQuery } from "mongoose"; +import IDatabaseAdapter from "./IDatabaseAdapter.js"; +import logger from "@/helpers/logger.js"; +import Cache from "../cache/MapCache.js"; + +export default class MongooseAdapter extends IDatabaseAdapter, FilterQuery, UpdateQuery, QueryOptions> { + cache = new Cache(); + options: ConnectOptions; + uri: string; + + constructor(uri: string, options: ConnectOptions = {}) { + super(); + + if (!uri) { + throw new Error("MongooseAdapter: URI is required."); + } + this.options = options; + this.uri = uri; + } + + async connect() { + await mongoose.connect(this.uri, this.options); + logger.log("Database connected."); + } + + async disconnect() { + await mongoose.disconnect(); + logger.warn("Database disconnected."); + this.cache.clear(); + } + + #generateCacheKey(modelName: string, query: {}, options: {}) { + return `${modelName}:${JSON.stringify(query)}:${JSON.stringify(options)}`; + } + + async find(model: Model, query: FilterQuery, options = {}): Promise { + const cacheKey = this.#generateCacheKey(model.modelName, query, options); + if (this.cache.get(cacheKey)) { + return this.cache.get(cacheKey); + } + + const result = await model.find(query, null, options).exec(); + this.cache.set(cacheKey, result); + return result; + } + + async findOne(model: Model, query: FilterQuery, options = {}): Promise { + const cacheKey = this.#generateCacheKey(model.modelName, query, options); + if (this.cache.get(cacheKey)) { + return this.cache.get(cacheKey); + } + + const result = await model.findOne(query, null, options).exec(); + this.cache.set(cacheKey, result); + return result; + } + + async updateOne(model: Model, filter: FilterQuery, update: UpdateQuery, options = {}) { + const result = await model.updateOne(filter, update, options).exec(); + this.cache.clear(); + return result; + } + + async deleteOne(model: Model, filter: FilterQuery) { + const result = await model.deleteOne(filter).exec(); + this.cache.clear(); + return result; + } + + async findOneOrCreate(model: Model, filter: FilterQuery) { + this.cache.clear(); + const result = await model.findOne(filter).then(result => { + if (result) return result; + + return model.create(filter); + }); + + return result; + } +} diff --git a/assets/fonts/KeepCalm-Medium.ttf b/src/assets/fonts/KeepCalm-Medium.ttf similarity index 100% rename from assets/fonts/KeepCalm-Medium.ttf rename to src/assets/fonts/KeepCalm-Medium.ttf diff --git a/assets/fonts/RubikMonoOne-Regular.ttf b/src/assets/fonts/RubikMonoOne-Regular.ttf similarity index 100% rename from assets/fonts/RubikMonoOne-Regular.ttf rename to src/assets/fonts/RubikMonoOne-Regular.ttf diff --git a/assets/img/achievements/achievement1.png b/src/assets/img/achievements/achievement1.png similarity index 100% rename from assets/img/achievements/achievement1.png rename to src/assets/img/achievements/achievement1.png diff --git a/assets/img/achievements/achievement2.png b/src/assets/img/achievements/achievement2.png similarity index 100% rename from assets/img/achievements/achievement2.png rename to src/assets/img/achievements/achievement2.png diff --git a/assets/img/achievements/achievement3.png b/src/assets/img/achievements/achievement3.png similarity index 100% rename from assets/img/achievements/achievement3.png rename to src/assets/img/achievements/achievement3.png diff --git a/assets/img/achievements/achievement4.png b/src/assets/img/achievements/achievement4.png similarity index 100% rename from assets/img/achievements/achievement4.png rename to src/assets/img/achievements/achievement4.png diff --git a/assets/img/achievements/achievement5.png b/src/assets/img/achievements/achievement5.png similarity index 100% rename from assets/img/achievements/achievement5.png rename to src/assets/img/achievements/achievement5.png diff --git a/assets/img/achievements/achievement6.png b/src/assets/img/achievements/achievement6.png similarity index 100% rename from assets/img/achievements/achievement6.png rename to src/assets/img/achievements/achievement6.png diff --git a/assets/img/achievements/achievement7.png b/src/assets/img/achievements/achievement7.png similarity index 100% rename from assets/img/achievements/achievement7.png rename to src/assets/img/achievements/achievement7.png diff --git a/assets/img/achievements/achievement_colored1.png b/src/assets/img/achievements/achievement_colored1.png similarity index 100% rename from assets/img/achievements/achievement_colored1.png rename to src/assets/img/achievements/achievement_colored1.png diff --git a/assets/img/achievements/achievement_colored2.png b/src/assets/img/achievements/achievement_colored2.png similarity index 100% rename from assets/img/achievements/achievement_colored2.png rename to src/assets/img/achievements/achievement_colored2.png diff --git a/assets/img/achievements/achievement_colored3.png b/src/assets/img/achievements/achievement_colored3.png similarity index 100% rename from assets/img/achievements/achievement_colored3.png rename to src/assets/img/achievements/achievement_colored3.png diff --git a/assets/img/achievements/achievement_colored4.png b/src/assets/img/achievements/achievement_colored4.png similarity index 100% rename from assets/img/achievements/achievement_colored4.png rename to src/assets/img/achievements/achievement_colored4.png diff --git a/assets/img/achievements/achievement_colored5.png b/src/assets/img/achievements/achievement_colored5.png similarity index 100% rename from assets/img/achievements/achievement_colored5.png rename to src/assets/img/achievements/achievement_colored5.png diff --git a/assets/img/achievements/achievement_colored6.png b/src/assets/img/achievements/achievement_colored6.png similarity index 100% rename from assets/img/achievements/achievement_colored6.png rename to src/assets/img/achievements/achievement_colored6.png diff --git a/assets/img/achievements/achievement_colored7.png b/src/assets/img/achievements/achievement_colored7.png similarity index 100% rename from assets/img/achievements/achievement_colored7.png rename to src/assets/img/achievements/achievement_colored7.png diff --git a/assets/img/achievements/achievement_unlocked1.png b/src/assets/img/achievements/achievement_unlocked1.png similarity index 100% rename from assets/img/achievements/achievement_unlocked1.png rename to src/assets/img/achievements/achievement_unlocked1.png diff --git a/assets/img/achievements/achievement_unlocked2.png b/src/assets/img/achievements/achievement_unlocked2.png similarity index 100% rename from assets/img/achievements/achievement_unlocked2.png rename to src/assets/img/achievements/achievement_unlocked2.png diff --git a/assets/img/achievements/achievement_unlocked3.png b/src/assets/img/achievements/achievement_unlocked3.png similarity index 100% rename from assets/img/achievements/achievement_unlocked3.png rename to src/assets/img/achievements/achievement_unlocked3.png diff --git a/assets/img/achievements/achievement_unlocked4.png b/src/assets/img/achievements/achievement_unlocked4.png similarity index 100% rename from assets/img/achievements/achievement_unlocked4.png rename to src/assets/img/achievements/achievement_unlocked4.png diff --git a/assets/img/achievements/achievement_unlocked5.png b/src/assets/img/achievements/achievement_unlocked5.png similarity index 100% rename from assets/img/achievements/achievement_unlocked5.png rename to src/assets/img/achievements/achievement_unlocked5.png diff --git a/assets/img/achievements/achievement_unlocked6.png b/src/assets/img/achievements/achievement_unlocked6.png similarity index 100% rename from assets/img/achievements/achievement_unlocked6.png rename to src/assets/img/achievements/achievement_unlocked6.png diff --git a/assets/img/achievements/achievement_unlocked7.png b/src/assets/img/achievements/achievement_unlocked7.png similarity index 100% rename from assets/img/achievements/achievement_unlocked7.png rename to src/assets/img/achievements/achievement_unlocked7.png diff --git a/assets/img/greetings_background.png b/src/assets/img/greetings_background.png similarity index 100% rename from assets/img/greetings_background.png rename to src/assets/img/greetings_background.png diff --git a/base/BaseCommand.js b/src/base_OLD/BaseCommand.js similarity index 71% rename from base/BaseCommand.js rename to src/base_OLD/BaseCommand.js index 7abc79a0..2e5fd642 100644 --- a/base/BaseCommand.js +++ b/src/base_OLD/BaseCommand.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ -const path = require("path"); +import { sep } from "path"; class BaseCommand { constructor(options, client) { @@ -15,8 +15,8 @@ class BaseCommand { /** * @type {String} */ - this.category = this.dirname ? this.dirname.split(path.sep)[parseInt(this.dirname.split(path.sep).length - 1, 10)] : "Other"; + this.category = this.dirname ? this.dirname.split(sep)[parseInt(this.dirname.split(sep).length - 1, 10)] : "Other"; } } -module.exports = BaseCommand; +export default BaseCommand; diff --git a/base/BaseEvent.js b/src/base_OLD/BaseEvent.js similarity index 85% rename from base/BaseEvent.js rename to src/base_OLD/BaseEvent.js index bd69c478..2fa28085 100644 --- a/base/BaseEvent.js +++ b/src/base_OLD/BaseEvent.js @@ -11,4 +11,4 @@ class BaseEvent { } } -module.exports = BaseEvent; +export default BaseEvent; diff --git a/base/Client.js b/src/base_OLD/Client.js similarity index 79% rename from base/Client.js rename to src/base_OLD/Client.js index 9cead419..db50e763 100644 --- a/base/Client.js +++ b/src/base_OLD/Client.js @@ -1,31 +1,40 @@ -const { Client, Collection, SlashCommandBuilder, ContextMenuCommandBuilder, EmbedBuilder, PermissionsBitField, ChannelType } = require("discord.js"), - { GiveawaysManager } = require("discord-giveaways"), - { REST } = require("@discordjs/rest"), - { Player: DiscordPlayer } = require("discord-player"), - { SpotifyExtractor } = require("@discord-player/extractor"), - { YoutubeiExtractor } = require("discord-player-youtubei"), - { Routes } = require("discord-api-types/v10"); +import { Client, Collection, SlashCommandBuilder, ContextMenuCommandBuilder, EmbedBuilder, PermissionsBitField, ChannelType } from "discord.js"; +import { GiveawaysManager } from "discord-giveaways"; +import { REST } from "@discordjs/rest"; +import { Player } from "discord-player"; +import { SpotifyExtractor } from "@discord-player/extractor"; +import { YoutubeiExtractor } from "discord-player-youtubei"; +import { Routes } from "discord-api-types/v10"; +import { join, sep } from "path"; +import { promises as fs } from "fs"; +import { setTimeout } from "timers/promises"; +import mongoose from "mongoose"; -const BaseEvent = require("./BaseEvent.js"), - BaseCommand = require("./BaseCommand.js"), - path = require("path"), - fs = require("fs").promises, - mongoose = require("mongoose"); +import * as emojis from "../../emojis.json"; +import langs from "../languages/language-meta.js"; +import logger from "../helpers/logger.js"; +import * as funcs from "../helpers/functions.js"; + +import BaseEvent from "./BaseEvent.js"; +import BaseCommand from "./BaseCommand.js"; +import guild from "./Guild.js"; +import user from "./User.js"; +import member from "./Member.js"; class JaBaClient extends Client { constructor(options) { super(options); - this.config = require("../config"); - this.customEmojis = require("../emojis"); - this.languages = require("../languages/language-meta"); + this.config = config; + this.customEmojis = emojis; + this.languages = langs; this.commands = new Collection(); - this.logger = require("../helpers/logger"); - this.wait = require("node:timers/promises").setTimeout; - this.functions = require("../helpers/functions"); - this.guildsData = require("../base/Guild"); - this.usersData = require("../base/User"); - this.membersData = require("../base/Member"); + this.logger = logger; + this.wait = setTimeout; + this.functions = funcs; + this.guildsData = guild.default; + this.usersData = user.default; + this.membersData = member.default; this.databaseCache = {}; this.databaseCache.users = new Collection(); @@ -42,7 +51,7 @@ class JaBaClient extends Client { * @returns {Promise} A Promise that resolves when the client is fully initialized. */ async init() { - this.player = new DiscordPlayer(this); + this.player = new Player(this); await this.player.extractors.register(YoutubeiExtractor, { authentication: this.config.youtubeCookie, @@ -62,25 +71,33 @@ class JaBaClient extends Client { this.player.events.on("playerStart", async (queue, track) => { const m = ( await queue.metadata.channel.send({ - content: this.translate("music/play:NOW_PLAYING", { - songName: `${track.title} - ${track.author}`, - songURL: track.url, - }, queue.metadata.data.guild.language), + content: this.translate( + "music/play:NOW_PLAYING", + { + songName: `${track.title} - ${track.author}`, + songURL: track.url, + }, + queue.metadata.data.guild.language, + ), }) ).id; - if (track.durationMS > 1) + if (track.durationMS > 1) { setTimeout(() => { const message = queue.metadata.channel.messages.cache.get(m); if (message && message.deletable) message.delete(); }, track.durationMS); - else - setTimeout(() => { - const message = queue.metadata.channel.messages.cache.get(m); + } else { + setTimeout( + () => { + const message = queue.metadata.channel.messages.cache.get(m); - if (message && message.deletable) message.delete(); - }, 5 * 60 * 1000); + if (message && message.deletable) message.delete(); + }, + 5 * 60 * 1000, + ); + } }); this.player.events.on("emptyQueue", queue => queue.metadata.channel.send(this.translate("music/play:QUEUE_ENDED", null, queue.metadata.data.guild.language))); this.player.events.on("emptyChannel", queue => queue.metadata.channel.send(this.translate("music/play:STOP_EMPTY", null, queue.metadata.data.guild.language))); @@ -105,9 +122,7 @@ class JaBaClient extends Client { mongoose .connect(this.config.mongoDB) - .then(() => { - this.logger.log("Connected to the MongoDB database."); - }) + .then(this.logger.log("Connected to the MongoDB database.")) .catch(e => { this.logger.error(`Unable to connect to the MongoDB database.\nError: ${e.message}\n${e.stack}`); }); @@ -126,8 +141,8 @@ 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)); + filePath = join(__dirname, dir), + folders = (await fs.readdir(filePath)).map(file => join(filePath, file)); const commands = []; for (const folder of folders) { @@ -136,7 +151,7 @@ class JaBaClient extends Client { for (const file of files) { if (!file.endsWith(".js")) continue; - const Command = require(path.join(folder, file)); + const Command = require(join(folder, file)); if (!(Command.prototype instanceof BaseCommand)) continue; @@ -157,8 +172,8 @@ class JaBaClient extends Client { await rest.put(route, { body: commands }); this.logger.log("Successfully registered application commands."); - } catch (err) { - this.logger.error("Error registering application commands:", err); + } catch (e) { + this.logger.error("Error registering application commands:", e); } } @@ -170,7 +185,7 @@ class JaBaClient extends Client { */ async loadCommand(dir, file) { try { - const Command = require(path.join(dir, `${file}.js`)); + const Command = require(join(dir, `${file}.js`)); if (!(Command.prototype instanceof BaseCommand)) { return this.logger.error(`Tried to load a non-command file: "${file}.js"`); @@ -182,8 +197,8 @@ class JaBaClient extends Client { 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); + } catch (e) { + this.logger.error(`Error loading command "${file}":`, e); } } @@ -194,7 +209,7 @@ class JaBaClient extends Client { * @returns {void} This method does not return a value. */ unloadCommand(dir, name) { - delete require.cache[require.resolve(`${dir}${path.sep}${name}.js`)]; + delete require.cache[require.resolve(`${dir}${sep}${name}.js`)]; return; } @@ -205,15 +220,15 @@ class JaBaClient extends Client { * @returns {Promise} This method does not return a value. */ async loadEvents(dir) { - const filePath = path.join(__dirname, dir); + const filePath = join(__dirname, dir); const files = await fs.readdir(filePath); for (const file of files) { - const fullPath = path.join(filePath, file); + const fullPath = join(filePath, file); const stat = await fs.lstat(fullPath); if (stat.isDirectory()) { - await this.loadEvents(path.join(dir, file)); + await this.loadEvents(join(dir, file)); continue; } @@ -236,8 +251,8 @@ class JaBaClient extends Client { 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); + } catch (e) { + this.logger.error(`Error loading event "${file}":`, e); } } } @@ -290,7 +305,7 @@ class JaBaClient extends Client { .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); + .setAuthor(typeof data.author === "string" ? { name: data.author, iconURL: this.user.avatarURL() } : (data.author ?? null)); return embed; } @@ -311,7 +326,7 @@ class JaBaClient extends Client { /** * Returns a User data from the database. * @param {string} userID - The ID of the user to find or create. - * @returns {Promise} The user data object, either retrieved from the database or newly created. + * @returns {Promise} The user data object, either retrieved from the database or newly created. */ async getUserData(userID) { let userData = await this.usersData.findOne({ id: userID }); @@ -330,7 +345,7 @@ class JaBaClient extends Client { * Returns a Member data from the database. * @param {string} memberId - The ID of the member to find or create. * @param {string} guildId - The ID of the guild the member belongs to. - * @returns {Promise} The member data object, either retrieved from the database or newly created. + * @returns {Promise} The member data object, either retrieved from the database or newly created. */ async getMemberData(memberId, guildId) { let memberData = await this.membersData.findOne({ guildID: guildId, id: memberId }); @@ -340,6 +355,7 @@ class JaBaClient extends Client { await memberData.save(); const guildData = await this.getGuildData(guildId); + if (guildData) { guildData.members.push(memberData._id); await guildData.save(); @@ -353,7 +369,7 @@ class JaBaClient extends Client { /** * Returns a Guild data from the database. * @param {string} guildId - The ID of the guild to find or create. - * @returns {Promise} The guild data object, either retrieved from the database or newly created. + * @returns {Promise} The guild data object, either retrieved from the database or newly created. */ async getGuildData(guildId) { let guildData = await this.guildsData.findOne({ id: guildId }).populate("members"); @@ -369,4 +385,4 @@ class JaBaClient extends Client { } } -module.exports = JaBaClient; +export default JaBaClient; diff --git a/src/commands/Economy/birthdate.ts b/src/commands/Economy/birthdate.ts new file mode 100644 index 00000000..929bf0e3 --- /dev/null +++ b/src/commands/Economy/birthdate.ts @@ -0,0 +1,143 @@ +import { replyError, replySuccess } from "@/helpers/extenders.js"; +import { CommandData, SlashCommandProps } from "@/types.js"; +import useClient from "@/utils/use-client.js"; +import { ApplicationCommandOptionType, ApplicationIntegrationType, InteractionContextType } from "discord.js"; + +const client = useClient(); + +export const data: CommandData = { + name: "birthdate", + description: client.translate("economy/birthdate:DESCRIPTION"), + // eslint-disable-next-line camelcase + description_localizations: { + uk: client.translate("economy/birthdate:DESCRIPTION", { lng: "uk-UA" }), + ru: client.translate("economy/birthdate:DESCRIPTION", { lng: "ru-RU" }), + }, + // eslint-disable-next-line camelcase + integration_types: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], + contexts: [InteractionContextType.BotDM, InteractionContextType.Guild, InteractionContextType.PrivateChannel], + options: [ + { + name: "day", + description: client.translate("economy/birthdate:DAY"), + type: ApplicationCommandOptionType.Integer, + // eslint-disable-next-line camelcase + description_localizations: { + uk: client.translate("economy/birthdate:DAY", { lng: "uk-UA" }), + ru: client.translate("economy/birthdate:DAY", { lng: "ru-RU" }), + }, + }, + { + name: "month", + description: client.translate("economy/birthdate:MONTH"), + type: ApplicationCommandOptionType.Integer, + // eslint-disable-next-line camelcase + description_localizations: { + uk: client.translate("economy/birthdate:MONTH", { lng: "uk-UA" }), + ru: client.translate("economy/birthdate:MONTH", { lng: "ru-RU" }), + }, + choices: [ + { name: client.translate("misc:MONTHS:JANUARY"), value: 1 }, + { name: client.translate("misc:MONTHS:FEBRUARY"), value: 2 }, + { name: client.translate("misc:MONTHS:MARCH"), value: 3 }, + { name: client.translate("misc:MONTHS:APRIL"), value: 4 }, + { name: client.translate("misc:MONTHS:MAY"), value: 5 }, + { name: client.translate("misc:MONTHS:JUNE"), value: 6 }, + { name: client.translate("misc:MONTHS:JULY"), value: 7 }, + { name: client.translate("misc:MONTHS:AUGUST"), value: 8 }, + { name: client.translate("misc:MONTHS:SEPTEMBER"), value: 9 }, + { name: client.translate("misc:MONTHS:OCTOBER"), value: 10 }, + { name: client.translate("misc:MONTHS:NOVEMBER"), value: 11 }, + { name: client.translate("misc:MONTHS:DECEMBER"), value: 12 }, + ], + }, + { + name: "year", + description: client.translate("economy/birthdate:YEAR"), + type: ApplicationCommandOptionType.Integer, + // eslint-disable-next-line camelcase + description_localizations: { + uk: client.translate("economy/birthdate:YEAR", { lng: "uk-UA" }), + ru: client.translate("economy/birthdate:YEAR", { lng: "ru-RU" }), + }, + }, + { + name: "clear", + type: ApplicationCommandOptionType.Boolean, + description: client.translate("economy/birthdate:CLEAR"), + // eslint-disable-next-line camelcase + description_localizations: { + uk: client.translate("economy/birthdate:CLEAR", { lng: "uk-UA" }), + ru: client.translate("economy/birthdate:CLEAR", { lng: "ru-RU" }), + }, + }, + { + name: "ephemeral", + type: ApplicationCommandOptionType.Boolean, + description: client.translate("misc:EPHEMERAL_RESPONSE"), + // eslint-disable-next-line camelcase + description_localizations: { + uk: client.translate("misc:EPHEMERAL_RESPONSE", { lng: "uk-UA" }), + ru: client.translate("misc:EPHEMERAL_RESPONSE", { lng: "ru-RU" }), + }, + }, + ], +}; + +export const run = async ({ interaction, client }: SlashCommandProps) => { + await interaction.deferReply({ + ephemeral: interaction.options.getBoolean("ephemeral") || false, + }); + + const userData = await client.getUserData(interaction.user.id); + + if (interaction.options.getBoolean("clear")) { + userData.birthdate = null; + await userData.save(); + + return replySuccess( + interaction, + "economy/birthdate:SUCCESS", + { date: "none" }, + { + edit: true, + }, + ); + } + + const day = interaction.options.getInteger("day")!, + month = interaction.options.getInteger("month")!, + year = interaction.options.getInteger("year")!, + date = new Date(year, month - 1, day); + + date.setHours(12); + + const d = Math.floor(date.getTime() / 1000); + + if (!(day === date.getDate() && month - 1 === date.getMonth() && year === date.getFullYear())) { + return replyError(interaction, "economy/birthdate:INVALID_DATE", null, { edit: true }); + } + + if (date.getTime() > Date.now()) { + return replyError(interaction, "economy/birthdate:DATE_TOO_HIGH", null, { edit: true }); + } + + if (date.getTime() < Date.now() - 2.523e12) { + replyError(interaction, "economy/birthdate:DATE_TOO_LOW", null, { edit: true }); + } + + userData.birthdate = d; + + await userData.save(); + + return replySuccess( + interaction, + "economy/birthdate:SUCCESS", + { + date: ``, + }, + { + edit: true, + }, + ); +}; diff --git a/commands/Administration/addemoji.js b/src/commands_OLD/Administration/addemoji.js similarity index 100% rename from commands/Administration/addemoji.js rename to src/commands_OLD/Administration/addemoji.js diff --git a/commands/Administration/automod.js b/src/commands_OLD/Administration/automod.js similarity index 100% rename from commands/Administration/automod.js rename to src/commands_OLD/Administration/automod.js diff --git a/commands/Administration/autorole.js b/src/commands_OLD/Administration/autorole.js similarity index 100% rename from commands/Administration/autorole.js rename to src/commands_OLD/Administration/autorole.js diff --git a/commands/Administration/config.js b/src/commands_OLD/Administration/config.js similarity index 96% rename from commands/Administration/config.js rename to src/commands_OLD/Administration/config.js index e4b5236a..d4f5f0df 100644 --- a/commands/Administration/config.js +++ b/src/commands_OLD/Administration/config.js @@ -103,7 +103,8 @@ class Config extends BaseCommand { ? interaction.translate("administration/config:WELCOME_CONTENT", { channel: `<#${guildData.plugins.welcome.channel}>`, withImage: guildData.plugins.welcome.withImage ? interaction.translate("common:YES") : interaction.translate("common:NO"), - }) : interaction.translate("common:DISABLED"), + }) + : interaction.translate("common:DISABLED"), inline: true, }, { @@ -112,7 +113,8 @@ class Config extends BaseCommand { ? interaction.translate("administration/config:GOODBYE_CONTENT", { channel: `<#${guildData.plugins.goodbye.channel}>`, withImage: guildData.plugins.goodbye.withImage ? interaction.translate("common:YES") : interaction.translate("common:NO"), - }) : interaction.translate("common:DISABLED"), + }) + : interaction.translate("common:DISABLED"), inline: true, }, { @@ -125,19 +127,22 @@ class Config extends BaseCommand { (guildData.plugins.warnsSanctions.kick ? interaction.translate("administration/config:KICK_CONTENT", { count: guildData.plugins.warnsSanctions.kick, - }) : interaction.translate("administration/config:KICK_NOT_DEFINED")) + + }) + : interaction.translate("administration/config:KICK_NOT_DEFINED")) + "\n" + (guildData.plugins.warnsSanctions.ban ? interaction.translate("administration/config:BAN_CONTENT", { count: guildData.plugins.warnsSanctions.ban, - }) : interaction.translate("administration/config:BAN_NOT_DEFINED")), + }) + : interaction.translate("administration/config:BAN_NOT_DEFINED")), }, { name: interaction.translate("administration/config:AUTOMOD_TITLE"), value: guildData.plugins.automod.enabled ? interaction.translate("administration/config:AUTOMOD_CONTENT", { channels: guildData.plugins.automod.ignored.map(ch => ` ${ch}`), - }) : interaction.translate("common:DISABLED"), + }) + : interaction.translate("common:DISABLED"), }, { name: interaction.translate("administration/config:MONITORING_CHANNELS"), @@ -211,13 +216,14 @@ async function changeSetting(interaction, setting, state, channel) { content: `${interaction.translate(`administration/config:${settingSplitted.length === 2 ? settingSplitted[1].toUpperCase() : setting.toUpperCase()}`)}: **${interaction.translate("common:ENABLED")}** (${channel.toString()})`, ephemeral: true, }); - } else + } else { return interaction.reply({ content: `${interaction.translate(`administration/config:${settingSplitted.length === 2 ? settingSplitted[1].toUpperCase() : setting.toUpperCase()}`)}: ${ data.plugins[setting] ? `**${interaction.translate("common:ENABLED")}** (<#${data.plugins[setting]}>)` : `**${interaction.translate("common:DISABLED")}**` }`, ephemeral: true, }); + } } } else { if (!state) { @@ -241,13 +247,14 @@ async function changeSetting(interaction, setting, state, channel) { content: `${interaction.translate(`administration/config:${setting.toUpperCase()}`)}: **${interaction.translate("common:ENABLED")}** (${channel.toString()})`, ephemeral: true, }); - } else + } else { return interaction.reply({ content: `${interaction.translate(`administration/config:${setting.toUpperCase()}`)}: ${ data.plugins[setting] ? `**${interaction.translate("common:ENABLED")}** (<#${data.plugins[setting]}>)` : `**${interaction.translate("common:DISABLED")}**` }`, ephemeral: true, }); + } } } } diff --git a/commands/Administration/goodbye.js b/src/commands_OLD/Administration/goodbye.js similarity index 100% rename from commands/Administration/goodbye.js rename to src/commands_OLD/Administration/goodbye.js diff --git a/commands/Administration/selectroles.js b/src/commands_OLD/Administration/selectroles.js similarity index 100% rename from commands/Administration/selectroles.js rename to src/commands_OLD/Administration/selectroles.js diff --git a/commands/Administration/set.js b/src/commands_OLD/Administration/set.js similarity index 100% rename from commands/Administration/set.js rename to src/commands_OLD/Administration/set.js diff --git a/commands/Administration/setlang.js b/src/commands_OLD/Administration/setlang.js similarity index 100% rename from commands/Administration/setlang.js rename to src/commands_OLD/Administration/setlang.js diff --git a/commands/Administration/stealemoji.js b/src/commands_OLD/Administration/stealemoji.js similarity index 100% rename from commands/Administration/stealemoji.js rename to src/commands_OLD/Administration/stealemoji.js diff --git a/commands/Administration/welcome.js b/src/commands_OLD/Administration/welcome.js similarity index 100% rename from commands/Administration/welcome.js rename to src/commands_OLD/Administration/welcome.js diff --git a/commands/Beatrun/courses.js b/src/commands_OLD/Beatrun/courses.js similarity index 100% rename from commands/Beatrun/courses.js rename to src/commands_OLD/Beatrun/courses.js diff --git a/commands/Economy/achievements.js b/src/commands_OLD/Economy/achievements.js similarity index 100% rename from commands/Economy/achievements.js rename to src/commands_OLD/Economy/achievements.js diff --git a/commands/Economy/bank.js b/src/commands_OLD/Economy/bank.js similarity index 100% rename from commands/Economy/bank.js rename to src/commands_OLD/Economy/bank.js diff --git a/commands/Economy/birthdate.js b/src/commands_OLD/Economy/birthdate.js similarity index 100% rename from commands/Economy/birthdate.js rename to src/commands_OLD/Economy/birthdate.js diff --git a/commands/Economy/divorce.js b/src/commands_OLD/Economy/divorce.js similarity index 100% rename from commands/Economy/divorce.js rename to src/commands_OLD/Economy/divorce.js diff --git a/commands/Economy/leaderboard.js b/src/commands_OLD/Economy/leaderboard.js similarity index 100% rename from commands/Economy/leaderboard.js rename to src/commands_OLD/Economy/leaderboard.js diff --git a/commands/Economy/marry.js b/src/commands_OLD/Economy/marry.js similarity index 100% rename from commands/Economy/marry.js rename to src/commands_OLD/Economy/marry.js diff --git a/commands/Economy/money.js b/src/commands_OLD/Economy/money.js similarity index 100% rename from commands/Economy/money.js rename to src/commands_OLD/Economy/money.js diff --git a/commands/Economy/pay.js b/src/commands_OLD/Economy/pay.js similarity index 100% rename from commands/Economy/pay.js rename to src/commands_OLD/Economy/pay.js diff --git a/commands/Economy/profile.js b/src/commands_OLD/Economy/profile.js similarity index 100% rename from commands/Economy/profile.js rename to src/commands_OLD/Economy/profile.js diff --git a/commands/Economy/rep.js b/src/commands_OLD/Economy/rep.js similarity index 100% rename from commands/Economy/rep.js rename to src/commands_OLD/Economy/rep.js diff --git a/commands/Economy/rob.js b/src/commands_OLD/Economy/rob.js similarity index 100% rename from commands/Economy/rob.js rename to src/commands_OLD/Economy/rob.js diff --git a/commands/Economy/setbio.js b/src/commands_OLD/Economy/setbio.js similarity index 100% rename from commands/Economy/setbio.js rename to src/commands_OLD/Economy/setbio.js diff --git a/commands/Economy/slots.js b/src/commands_OLD/Economy/slots.js similarity index 100% rename from commands/Economy/slots.js rename to src/commands_OLD/Economy/slots.js diff --git a/commands/Economy/transactions.js b/src/commands_OLD/Economy/transactions.js similarity index 100% rename from commands/Economy/transactions.js rename to src/commands_OLD/Economy/transactions.js diff --git a/commands/Economy/work.js b/src/commands_OLD/Economy/work.js similarity index 100% rename from commands/Economy/work.js rename to src/commands_OLD/Economy/work.js diff --git a/commands/Fun/8ball.js b/src/commands_OLD/Fun/8ball.js similarity index 100% rename from commands/Fun/8ball.js rename to src/commands_OLD/Fun/8ball.js diff --git a/commands/Fun/cat.js b/src/commands_OLD/Fun/cat.js similarity index 100% rename from commands/Fun/cat.js rename to src/commands_OLD/Fun/cat.js diff --git a/commands/Fun/dog.js b/src/commands_OLD/Fun/dog.js similarity index 100% rename from commands/Fun/dog.js rename to src/commands_OLD/Fun/dog.js diff --git a/commands/Fun/lmgtfy.js b/src/commands_OLD/Fun/lmgtfy.js similarity index 100% rename from commands/Fun/lmgtfy.js rename to src/commands_OLD/Fun/lmgtfy.js diff --git a/commands/Fun/lovecalc.js b/src/commands_OLD/Fun/lovecalc.js similarity index 100% rename from commands/Fun/lovecalc.js rename to src/commands_OLD/Fun/lovecalc.js diff --git a/commands/Fun/number.js b/src/commands_OLD/Fun/number.js similarity index 100% rename from commands/Fun/number.js rename to src/commands_OLD/Fun/number.js diff --git a/commands/Fun/tictactoe.js b/src/commands_OLD/Fun/tictactoe.js similarity index 100% rename from commands/Fun/tictactoe.js rename to src/commands_OLD/Fun/tictactoe.js diff --git a/commands/General/afk.js b/src/commands_OLD/General/afk.js similarity index 100% rename from commands/General/afk.js rename to src/commands_OLD/General/afk.js diff --git a/commands/General/avatar.c.js b/src/commands_OLD/General/avatar.c.js similarity index 100% rename from commands/General/avatar.c.js rename to src/commands_OLD/General/avatar.c.js diff --git a/commands/General/avatar.js b/src/commands_OLD/General/avatar.js similarity index 100% rename from commands/General/avatar.js rename to src/commands_OLD/General/avatar.js diff --git a/commands/General/boosters.js b/src/commands_OLD/General/boosters.js similarity index 100% rename from commands/General/boosters.js rename to src/commands_OLD/General/boosters.js diff --git a/commands/General/emoji.js b/src/commands_OLD/General/emoji.js similarity index 100% rename from commands/General/emoji.js rename to src/commands_OLD/General/emoji.js diff --git a/commands/General/help.js b/src/commands_OLD/General/help.js similarity index 100% rename from commands/General/help.js rename to src/commands_OLD/General/help.js diff --git a/commands/General/info.js b/src/commands_OLD/General/info.js similarity index 100% rename from commands/General/info.js rename to src/commands_OLD/General/info.js diff --git a/commands/General/minecraft.js b/src/commands_OLD/General/minecraft.js similarity index 100% rename from commands/General/minecraft.js rename to src/commands_OLD/General/minecraft.js diff --git a/commands/General/ping.js b/src/commands_OLD/General/ping.js similarity index 100% rename from commands/General/ping.js rename to src/commands_OLD/General/ping.js diff --git a/commands/General/remindme.js b/src/commands_OLD/General/remindme.js similarity index 100% rename from commands/General/remindme.js rename to src/commands_OLD/General/remindme.js diff --git a/commands/General/reminds.js b/src/commands_OLD/General/reminds.js similarity index 100% rename from commands/General/reminds.js rename to src/commands_OLD/General/reminds.js diff --git a/commands/General/report.js b/src/commands_OLD/General/report.js similarity index 100% rename from commands/General/report.js rename to src/commands_OLD/General/report.js diff --git a/commands/General/shorturl.js b/src/commands_OLD/General/shorturl.js similarity index 97% rename from commands/General/shorturl.js rename to src/commands_OLD/General/shorturl.js index 06da073d..ee60eb44 100644 --- a/commands/General/shorturl.js +++ b/src/commands_OLD/General/shorturl.js @@ -63,7 +63,7 @@ class Shorturl extends BaseCommand { }).then(res => res.json()); interaction.editReply({ - content: ``, + content: `<${res.shortLink}>`, }); } } diff --git a/commands/General/stats.js b/src/commands_OLD/General/stats.js similarity index 100% rename from commands/General/stats.js rename to src/commands_OLD/General/stats.js diff --git a/commands/General/suggest.js b/src/commands_OLD/General/suggest.js similarity index 100% rename from commands/General/suggest.js rename to src/commands_OLD/General/suggest.js diff --git a/commands/General/whois.js b/src/commands_OLD/General/whois.js similarity index 100% rename from commands/General/whois.js rename to src/commands_OLD/General/whois.js diff --git a/commands/IAT/checkjar.js b/src/commands_OLD/IAT/checkjar.js similarity index 95% rename from commands/IAT/checkjar.js rename to src/commands_OLD/IAT/checkjar.js index 8618c3f4..2aeb8965 100644 --- a/commands/IAT/checkjar.js +++ b/src/commands_OLD/IAT/checkjar.js @@ -1,7 +1,6 @@ const { SlashCommandBuilder, InteractionContextType, ApplicationIntegrationType } = require("discord.js"); const BaseCommand = require("../../base/BaseCommand"), - fetch = require("node-fetch"), - moment = require("moment"); + fetch = require("node-fetch"); class Checkjar extends BaseCommand { /** @@ -56,7 +55,7 @@ class Checkjar extends BaseCommand { jarTransactions.length = 10; jarTransactions.forEach(t => { - const time = moment.unix(t.time); + const time = new Intl.DateTimeFormat("ru-RU").format(t.time); embed.data.fields.push([ { diff --git a/commands/Moderation/clear.js b/src/commands_OLD/Moderation/clear.js similarity index 100% rename from commands/Moderation/clear.js rename to src/commands_OLD/Moderation/clear.js diff --git a/commands/Moderation/clearwarns.js b/src/commands_OLD/Moderation/clearwarns.js similarity index 100% rename from commands/Moderation/clearwarns.js rename to src/commands_OLD/Moderation/clearwarns.js diff --git a/commands/Moderation/giveaway.js b/src/commands_OLD/Moderation/giveaway.js similarity index 100% rename from commands/Moderation/giveaway.js rename to src/commands_OLD/Moderation/giveaway.js diff --git a/commands/Moderation/unban.js b/src/commands_OLD/Moderation/unban.js similarity index 100% rename from commands/Moderation/unban.js rename to src/commands_OLD/Moderation/unban.js diff --git a/commands/Moderation/untimeout.js b/src/commands_OLD/Moderation/untimeout.js similarity index 100% rename from commands/Moderation/untimeout.js rename to src/commands_OLD/Moderation/untimeout.js diff --git a/commands/Moderation/warn.c.js b/src/commands_OLD/Moderation/warn.c.js similarity index 100% rename from commands/Moderation/warn.c.js rename to src/commands_OLD/Moderation/warn.c.js diff --git a/commands/Moderation/warns.js b/src/commands_OLD/Moderation/warns.js similarity index 100% rename from commands/Moderation/warns.js rename to src/commands_OLD/Moderation/warns.js diff --git a/commands/Music/back.js b/src/commands_OLD/Music/back.js similarity index 100% rename from commands/Music/back.js rename to src/commands_OLD/Music/back.js diff --git a/commands/Music/loop.js b/src/commands_OLD/Music/loop.js similarity index 100% rename from commands/Music/loop.js rename to src/commands_OLD/Music/loop.js diff --git a/commands/Music/nowplaying.js b/src/commands_OLD/Music/nowplaying.js similarity index 99% rename from commands/Music/nowplaying.js rename to src/commands_OLD/Music/nowplaying.js index d4667b1a..f5efe5e1 100644 --- a/commands/Music/nowplaying.js +++ b/src/commands_OLD/Music/nowplaying.js @@ -270,7 +270,7 @@ async function updateEmbed(interaction, queue) { { name: "\u200B", value: "\u200B", inline: true }, { name: interaction.translate("common:VIEWS"), - value: track.raw.live ? "Live" : new Intl.NumberFormat(interaction.client.defaultLanguage.moment, { notation: "standard" }).format(track.raw.views), + value: track.raw.live ? "Live" : new Intl.NumberFormat(interaction.client.defaultLanguage.locale, { notation: "standard" }).format(track.raw.views), inline: true, }, { diff --git a/commands/Music/play.c.js b/src/commands_OLD/Music/play.c.js similarity index 100% rename from commands/Music/play.c.js rename to src/commands_OLD/Music/play.c.js diff --git a/commands/Music/play.js b/src/commands_OLD/Music/play.js similarity index 100% rename from commands/Music/play.js rename to src/commands_OLD/Music/play.js diff --git a/commands/Music/queue.js b/src/commands_OLD/Music/queue.js similarity index 100% rename from commands/Music/queue.js rename to src/commands_OLD/Music/queue.js diff --git a/commands/Music/seek.js b/src/commands_OLD/Music/seek.js similarity index 100% rename from commands/Music/seek.js rename to src/commands_OLD/Music/seek.js diff --git a/commands/Music/shuffle.js b/src/commands_OLD/Music/shuffle.js similarity index 100% rename from commands/Music/shuffle.js rename to src/commands_OLD/Music/shuffle.js diff --git a/commands/Music/skip.js b/src/commands_OLD/Music/skip.js similarity index 100% rename from commands/Music/skip.js rename to src/commands_OLD/Music/skip.js diff --git a/commands/Music/stop.js b/src/commands_OLD/Music/stop.js similarity index 100% rename from commands/Music/stop.js rename to src/commands_OLD/Music/stop.js diff --git a/commands/Music/volume.js b/src/commands_OLD/Music/volume.js similarity index 100% rename from commands/Music/volume.js rename to src/commands_OLD/Music/volume.js diff --git a/commands/Owner/debug.js b/src/commands_OLD/Owner/debug.js similarity index 100% rename from commands/Owner/debug.js rename to src/commands_OLD/Owner/debug.js diff --git a/commands/Owner/eval.js b/src/commands_OLD/Owner/eval.js similarity index 100% rename from commands/Owner/eval.js rename to src/commands_OLD/Owner/eval.js diff --git a/commands/Owner/reload.js b/src/commands_OLD/Owner/reload.js similarity index 100% rename from commands/Owner/reload.js rename to src/commands_OLD/Owner/reload.js diff --git a/commands/Owner/say.js b/src/commands_OLD/Owner/say.js similarity index 100% rename from commands/Owner/say.js rename to src/commands_OLD/Owner/say.js diff --git a/commands/Owner/servers.js b/src/commands_OLD/Owner/servers.js similarity index 100% rename from commands/Owner/servers.js rename to src/commands_OLD/Owner/servers.js diff --git a/commands/Tickets/adduser.js b/src/commands_OLD/Tickets/adduser.js similarity index 100% rename from commands/Tickets/adduser.js rename to src/commands_OLD/Tickets/adduser.js diff --git a/commands/Tickets/closeticket.js b/src/commands_OLD/Tickets/closeticket.js similarity index 100% rename from commands/Tickets/closeticket.js rename to src/commands_OLD/Tickets/closeticket.js diff --git a/commands/Tickets/createticketembed.js b/src/commands_OLD/Tickets/createticketembed.js similarity index 100% rename from commands/Tickets/createticketembed.js rename to src/commands_OLD/Tickets/createticketembed.js diff --git a/commands/Tickets/removeuser.js b/src/commands_OLD/Tickets/removeuser.js similarity index 100% rename from commands/Tickets/removeuser.js rename to src/commands_OLD/Tickets/removeuser.js diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 00000000..6d0748f6 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,27 @@ +import path from "node:path"; +import { GatewayIntentBits, MessageMentionOptions } from "discord.js"; +import { AsyncLocalStorage } from "node:async_hooks"; +import { ExtendedClient } from "@/structures/client.js"; + +export const PROJECT_ROOT = path.join(import.meta.dirname, ".."); +export const CONFIG_PATH = path.join(PROJECT_ROOT, "..", "config.json"); +export const CLIENT_INTENTS = [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildModeration, + GatewayIntentBits.GuildEmojisAndStickers, + GatewayIntentBits.GuildIntegrations, + GatewayIntentBits.GuildInvites, + GatewayIntentBits.GuildVoiceStates, + GatewayIntentBits.GuildPresences, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.GuildMessageTyping, + GatewayIntentBits.MessageContent, + GatewayIntentBits.DirectMessageTyping, + GatewayIntentBits.DirectMessages, + GatewayIntentBits.DirectMessageReactions, +]; +export const CLIENT_ALLOWED_MENTIONS: MessageMentionOptions = { parse: ["everyone", "roles", "users"] }; + +export const SUPER_CONTEXT = new AsyncLocalStorage(); diff --git a/src/events/Ready.ts b/src/events/Ready.ts new file mode 100644 index 00000000..4cc94ee6 --- /dev/null +++ b/src/events/Ready.ts @@ -0,0 +1,24 @@ +import logger from "../helpers/logger.js"; +import { resolve } from "node:path"; +import loadCronTasks from "@/utils/loadCronTasks.js"; +import { CronManager } from "@/services/cron/index.js"; +import { ExtendedClient } from "@/structures/client.js"; + +export const data = { + name: "ready", + once: true, +}; + +export async function run(client: ExtendedClient) { + logger.ready(client.user.tag + " is online!"); + + // Fetching all app emojis, because we need to use them + await client.application.emojis.fetch(); + + const taskPath = resolve(client.configService.get("paths.tasks")); + + const cronTasks = await loadCronTasks(taskPath); + + const cronManager = new CronManager(cronTasks); + await cronManager.init(); +} diff --git a/events/CommandHandler.js b/src/events_OLD/CommandHandler.js similarity index 87% rename from events/CommandHandler.js rename to src/events_OLD/CommandHandler.js index 0337da0c..4923eec7 100644 --- a/events/CommandHandler.js +++ b/src/events_OLD/CommandHandler.js @@ -1,5 +1,5 @@ -const { InteractionType } = require("discord.js"); -const BaseEvent = require("../base/BaseEvent"); +import { InteractionType } from "discord.js"; +import BaseEvent from "../base/BaseEvent"; class CommandHandler extends BaseEvent { constructor() { @@ -33,12 +33,10 @@ class CommandHandler extends BaseEvent { 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 }); // First command achievement check const { firstCommand } = interaction.data.user.achievements; @@ -68,4 +66,4 @@ class CommandHandler extends BaseEvent { } } -module.exports = CommandHandler; +export default CommandHandler; diff --git a/events/Guild/guildBanAdd.js b/src/events_OLD/Guild/guildBanAdd.js similarity index 84% rename from events/Guild/guildBanAdd.js rename to src/events_OLD/Guild/guildBanAdd.js index be02d479..dd92d904 100644 --- a/events/Guild/guildBanAdd.js +++ b/src/events_OLD/Guild/guildBanAdd.js @@ -1,4 +1,4 @@ -const BaseEvent = require("../../base/BaseEvent"); +import BaseEvent from "../../base/BaseEvent"; class guildBanAdd extends BaseEvent { constructor() { @@ -26,8 +26,10 @@ class guildBanAdd extends BaseEvent { await ban.user.send({ embeds: [embed], }); - } catch (e) { /**/ } + } catch { + /**/ + } } } -module.exports = guildBanAdd; +export default guildBanAdd; diff --git a/events/Guild/guildCreate.js b/src/events_OLD/Guild/guildCreate.js similarity index 95% rename from events/Guild/guildCreate.js rename to src/events_OLD/Guild/guildCreate.js index ffa6a96f..0856149d 100644 --- a/events/Guild/guildCreate.js +++ b/src/events_OLD/Guild/guildCreate.js @@ -1,4 +1,4 @@ -const BaseEvent = require("../../base/BaseEvent"); +import BaseEvent from "../../base/BaseEvent"; class GuildCreate extends BaseEvent { constructor() { @@ -62,12 +62,13 @@ class GuildCreate extends BaseEvent { const logChannel = client.channels.cache.get(client.config.support.logs); - if (logChannel) + if (logChannel) { await logChannel.send({ embeds: [embed], }); + } } } } -module.exports = GuildCreate; +export default GuildCreate; diff --git a/events/Guild/guildDelete.js b/src/events_OLD/Guild/guildDelete.js similarity index 77% rename from events/Guild/guildDelete.js rename to src/events_OLD/Guild/guildDelete.js index 20b795cc..b87299c0 100644 --- a/events/Guild/guildDelete.js +++ b/src/events_OLD/Guild/guildDelete.js @@ -1,4 +1,4 @@ -const BaseEvent = require("../../base/BaseEvent"); +import BaseEvent from "../../base/BaseEvent"; class GuildDelete extends BaseEvent { constructor() { @@ -25,13 +25,13 @@ class GuildDelete extends BaseEvent { const logChannel = client.channels.cache.get(client.config.support.logs); - if (logChannel) + if (logChannel) { await logChannel.send({ embeds: [embed], }); - else client.logger.warn(`Log channel not found for guild deletion: ${guild.name}`); + } else client.logger.warn(`Log channel not found for guild deletion: ${guild.name}`); } } } -module.exports = GuildDelete; +export default GuildDelete; diff --git a/events/Guild/guildMemberAdd.js b/src/events_OLD/Guild/guildMemberAdd.js similarity index 93% rename from events/Guild/guildMemberAdd.js rename to src/events_OLD/Guild/guildMemberAdd.js index 0112ae64..8bad2c4c 100644 --- a/events/Guild/guildMemberAdd.js +++ b/src/events_OLD/Guild/guildMemberAdd.js @@ -1,4 +1,4 @@ -const BaseEvent = require("../../base/BaseEvent"); +import BaseEvent from "../../base/BaseEvent"; class GuildMemberAdd extends BaseEvent { constructor() { @@ -46,4 +46,4 @@ class GuildMemberAdd extends BaseEvent { } } -module.exports = GuildMemberAdd; +export default GuildMemberAdd; diff --git a/events/Guild/guildMemberRemove.js b/src/events_OLD/Guild/guildMemberRemove.js similarity index 92% rename from events/Guild/guildMemberRemove.js rename to src/events_OLD/Guild/guildMemberRemove.js index c3e1499a..9726d708 100644 --- a/events/Guild/guildMemberRemove.js +++ b/src/events_OLD/Guild/guildMemberRemove.js @@ -1,4 +1,4 @@ -const BaseEvent = require("../../base/BaseEvent"); +import BaseEvent from "../../base/BaseEvent"; class GuildMemberRemove extends BaseEvent { constructor() { @@ -37,4 +37,4 @@ class GuildMemberRemove extends BaseEvent { } } -module.exports = GuildMemberRemove; +export default GuildMemberRemove; diff --git a/events/Guild/guildMemberUpdate.js b/src/events_OLD/Guild/guildMemberUpdate.js similarity index 93% rename from events/Guild/guildMemberUpdate.js rename to src/events_OLD/Guild/guildMemberUpdate.js index 2c48d1a9..fd8d1e3b 100644 --- a/events/Guild/guildMemberUpdate.js +++ b/src/events_OLD/Guild/guildMemberUpdate.js @@ -1,4 +1,4 @@ -const BaseEvent = require("../../base/BaseEvent"); +import BaseEvent from "../../base/BaseEvent"; class GuildMemberUpdate extends BaseEvent { constructor() { @@ -44,4 +44,4 @@ class GuildMemberUpdate extends BaseEvent { } } -module.exports = GuildMemberUpdate; +export default GuildMemberUpdate; diff --git a/events/MessageHandler.js b/src/events_OLD/MessageHandler.js similarity index 95% rename from events/MessageHandler.js rename to src/events_OLD/MessageHandler.js index fd1abac8..6ef06e6a 100644 --- a/events/MessageHandler.js +++ b/src/events_OLD/MessageHandler.js @@ -1,5 +1,5 @@ -const { PermissionsBitField, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); -const BaseEvent = require("../base/BaseEvent"); +import { PermissionsBitField, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; +import BaseEvent from "../base/BaseEvent"; const xpCooldown = {}; @@ -23,7 +23,7 @@ class MessageCreate extends BaseEvent { if (message.guild) { if (!message.member) await message.guild.members.fetch(message.author.id); - + data.guild = await client.getGuildData(message.guildId); data.member = await client.getMemberData(message.author.id, message.guildId); } @@ -69,8 +69,7 @@ class MessageCreate extends BaseEvent { if (msg.content) embed.addFields([{ name: message.translate("misc:QUOTE_CONTENT"), value: msg.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); + if (msg.attachments.find(a => a.contentType.includes("image/"))) embed.setImage(msg.attachments.find(a => a.contentType.includes("image/")).url); embed.addFields([ { @@ -160,4 +159,4 @@ class MessageCreate extends BaseEvent { } } -module.exports = MessageCreate; +export default MessageCreate; diff --git a/events/Monitoring/messageDelete.js b/src/events_OLD/Monitoring/messageDelete.js similarity index 93% rename from events/Monitoring/messageDelete.js rename to src/events_OLD/Monitoring/messageDelete.js index 5b760cd1..643bf78d 100644 --- a/events/Monitoring/messageDelete.js +++ b/src/events_OLD/Monitoring/messageDelete.js @@ -1,4 +1,4 @@ -const BaseEvent = require("../../base/BaseEvent"); +import BaseEvent from "../../base/BaseEvent"; class messageDelete extends BaseEvent { constructor() { @@ -40,4 +40,4 @@ class messageDelete extends BaseEvent { } } -module.exports = messageDelete; +export default messageDelete; diff --git a/events/Monitoring/messageUpdate.js b/src/events_OLD/Monitoring/messageUpdate.js similarity index 94% rename from events/Monitoring/messageUpdate.js rename to src/events_OLD/Monitoring/messageUpdate.js index 72bc91df..38fa9521 100644 --- a/events/Monitoring/messageUpdate.js +++ b/src/events_OLD/Monitoring/messageUpdate.js @@ -1,4 +1,4 @@ -const BaseEvent = require("../../base/BaseEvent"); +import BaseEvent from "../../base/BaseEvent"; class messageUpdate extends BaseEvent { constructor() { @@ -42,4 +42,4 @@ class messageUpdate extends BaseEvent { } } -module.exports = messageUpdate; +export default messageUpdate; diff --git a/events/Ready.js b/src/events_OLD/Ready.js similarity index 94% rename from events/Ready.js rename to src/events_OLD/Ready.js index aa105f17..315bb033 100644 --- a/events/Ready.js +++ b/src/events_OLD/Ready.js @@ -1,5 +1,5 @@ -const { ActivityType } = require("discord.js"); -const BaseEvent = require("../base/BaseEvent"); +import { ActivityType } from "discord.js"; +import BaseEvent from "../base/BaseEvent"; class Ready extends BaseEvent { constructor() { @@ -60,4 +60,4 @@ class Ready extends BaseEvent { } } -module.exports = Ready; +export default Ready; diff --git a/events/TicketsButton.js b/src/events_OLD/TicketsButton.js similarity index 85% rename from events/TicketsButton.js rename to src/events_OLD/TicketsButton.js index dbbf7340..9aa95547 100644 --- a/events/TicketsButton.js +++ b/src/events_OLD/TicketsButton.js @@ -1,5 +1,5 @@ -const { ButtonBuilder, ActionRowBuilder, ButtonStyle, ChannelType, PermissionsBitField } = require("discord.js"); -const BaseEvent = require("../base/BaseEvent"); +import { ButtonBuilder, ActionRowBuilder, ButtonStyle, ChannelType, PermissionsBitField } from "discord.js"; +import BaseEvent from "../base/BaseEvent"; class CommandHandler extends BaseEvent { constructor() { @@ -60,9 +60,13 @@ class CommandHandler extends BaseEvent { }); await logChannel.send({ embeds: [logEmbed] }); - await interaction.success("tickets/createticketembed:TICKET_CREATED", { - channel: channel.toString(), - }, { ephemeral: true }); + await interaction.success( + "tickets/createticketembed:TICKET_CREATED", + { + channel: channel.toString(), + }, + { ephemeral: true }, + ); await channel.send(`<@${interaction.user.id}>`); @@ -75,14 +79,8 @@ class CommandHandler extends BaseEvent { description: interaction.translate("tickets/createticketembed:TICKET_CREATED_DESC"), }); - const closeButton = new ButtonBuilder() - .setCustomId("close_ticket") - .setLabel(interaction.translate("tickets/closeticket:CLOSE_TICKET")) - .setStyle(ButtonStyle.Danger); - const transcriptButton = new ButtonBuilder() - .setCustomId("transcript_ticket") - .setLabel(interaction.translate("tickets/closeticket:TRANSCRIPT_TICKET")) - .setStyle(ButtonStyle.Secondary); + const closeButton = new ButtonBuilder().setCustomId("close_ticket").setLabel(interaction.translate("tickets/closeticket:CLOSE_TICKET")).setStyle(ButtonStyle.Danger); + const transcriptButton = new ButtonBuilder().setCustomId("transcript_ticket").setLabel(interaction.translate("tickets/closeticket:TRANSCRIPT_TICKET")).setStyle(ButtonStyle.Secondary); const row = new ActionRowBuilder().addComponents(closeButton, transcriptButton); guildData.plugins.tickets.count++; @@ -135,14 +133,18 @@ class CommandHandler extends BaseEvent { }); transcript += "---- TICKET CLOSED ----\n"; - if (transcriptionLogs !== null) interaction.guild.channels.cache.get(transcriptionLogs).send({ content: interaction.translate("tickets/closeticket:TRANSCRIPT", { channel: `<#${interaction.channelId}>` }), files: [{ attachment: Buffer.from(transcript), name: `${interaction.channel.name}.txt` }] }); + if (transcriptionLogs !== null) { + interaction.guild.channels.cache + .get(transcriptionLogs) + .send({ content: interaction.translate("tickets/closeticket:TRANSCRIPT", { channel: `<#${interaction.channelId}>` }), files: [{ attachment: Buffer.from(transcript), name: `${interaction.channel.name}.txt` }] }); + } try { await interaction.user.send({ content: interaction.translate("tickets/closeticket:TRANSCRIPT", { channel: interaction.channel.name }), files: [{ attachment: Buffer.from(transcript), name: `${interaction.channel.name}.txt` }], }); - } catch (e) { + } catch { interaction.followUp({ content: interaction.translate("misc:CANT_DM"), ephemeral: true }); } } @@ -181,7 +183,7 @@ class CommandHandler extends BaseEvent { content: interaction.translate("tickets/closeticket:TRANSCRIPT", { channel: `<#${interaction.channelId}>` }), files: [{ attachment: Buffer.from(transcript), name: `${interaction.channel.name}.txt` }], }); - } catch (error) { + } catch { interaction.followUp({ content: interaction.translate("misc:CANT_DM"), ephemeral: true }); } } else return; @@ -189,4 +191,4 @@ class CommandHandler extends BaseEvent { } } -module.exports = CommandHandler; +export default CommandHandler; diff --git a/src/handlers/command-handler/functions/registerCommands.ts b/src/handlers/command-handler/functions/registerCommands.ts new file mode 100644 index 00000000..9cfbf1e0 --- /dev/null +++ b/src/handlers/command-handler/functions/registerCommands.ts @@ -0,0 +1,88 @@ +import { ExtendedClient } from "@/structures/client.js"; +import logger from "@/helpers/logger.js"; +import differentCommands from "../utils/differentcommands.js"; +import { CommandFileObject } from "@/types.js"; +import { ApplicationCommandData, GuildApplicationCommandManager } from "discord.js"; + +type RegisterCommandProps = { + client: ExtendedClient; + commands: CommandFileObject[]; +}; + +export default async function registerCommands(props: RegisterCommandProps) { + props.client.once("ready", () => handleRegistration(props.client, props.commands)); +} + +const handleRegistration = async (client: ExtendedClient, commands: CommandFileObject[]) => { + const devOnlyCommands = commands.filter(cmd => cmd.options?.devOnly); + const globalCommands = commands.filter(cmd => !cmd.options?.devOnly); + + const devGuildsIds = client.configService.get("devGuildsIds"); + + await registerGlobalCommands(client, globalCommands); + await registerDevCommands(client, devOnlyCommands, devGuildsIds); +}; + +const registerGlobalCommands = async (client: ExtendedClient, commands: CommandFileObject[]) => { + const appCommandsManager = client.application!.commands; + await appCommandsManager.fetch(); + + await Promise.all( + commands.map(async ({ data }) => { + const targetCommand = appCommandsManager.cache.find(cmd => cmd.name === data.name); + + if (targetCommand && differentCommands(targetCommand, data)) { + await targetCommand.edit(data as Partial).catch(() => logger.error(`Failed to update command: ${data.name} globally`)); + + logger.log(`Edited command globally: ${data.name}`); + } else if (!targetCommand) { + await appCommandsManager.create(data).catch(() => logger.error(`Failed to register command: ${data.name}`)); + logger.debug(`Command ${data.name} loaded globally`); + } + }), + ); + + logger.log(`Registered ${commands.length} global commands`); +}; + +const registerDevCommands = async (client: ExtendedClient, commands: CommandFileObject[], guildsIds: string[]) => { + const devGuilds = []; + + for (const guildId of guildsIds) { + const guild = client.guilds.cache.get(guildId) || (await client.guilds.fetch(guildId)); + + if (!guild) { + logger.error(`Could not register dev commands, guild ${guildId} not found`); + continue; + } + + devGuilds.push(guild); + } + + const guildCommandsManagers: GuildApplicationCommandManager[] = []; + + for (const guild of devGuilds) { + const guildCommandsManager = guild.commands; + await guildCommandsManager.fetch(); + + guildCommandsManagers.push(guildCommandsManager); + } + + await Promise.all( + commands.map(async ({ data }) => { + guildCommandsManagers.map(async guildCommands => { + const targetCommand = guildCommands.cache.find(cmd => cmd.name === data.name); + if (targetCommand && differentCommands(targetCommand, data)) { + await targetCommand.edit(data as Partial).catch(() => logger.error(`Failed to update command: ${data.name} in ${guildCommands.guild.name} server`)); + + logger.log(`Edited command globally: ${data.name}`); + } else if (!targetCommand) { + await guildCommands.create(data).catch(() => logger.error(`Failed to register command: ${data.name} in ${guildCommands.guild.name} server`)); + logger.debug(`Command ${data.name} loaded in dev`); + } + }); + }), + ); + + logger.log(`Registered dev commands in ${devGuilds.length} server(s)`); +}; diff --git a/src/handlers/command-handler/index.ts b/src/handlers/command-handler/index.ts new file mode 100644 index 00000000..375f56b6 --- /dev/null +++ b/src/handlers/command-handler/index.ts @@ -0,0 +1,101 @@ +import { resolve } from "node:path"; +import logger from "@/helpers/logger.js"; +import { getFilePaths } from "@/utils/get-path.js"; +import { toFileURL } from "@/utils/resolve-file.js"; +import registerCommands from "./functions/registerCommands.js"; +import { ExtendedClient } from "@/structures/client.js"; +import { BuiltInValidation, CommandFileObject } from "@/types.js"; +import builtInValidationsFunctions from "./validations/index.js"; + +export class CommandHandler { + client: ExtendedClient; + commands: CommandFileObject[] = []; + builtInValidations: BuiltInValidation[] = []; + + constructor(client: ExtendedClient) { + this.client = client; + } + + async init() { + await this.#buildCommands(); + + this.buildBuiltInValidations(); + + await registerCommands({ + client: this.client, + commands: this.commands, + }); + + this.handleCommands(); + } + + async #buildCommands() { + const cmdPath = resolve(this.client.configService.get("paths.commands")); + const commandFilePaths = (await getFilePaths(cmdPath, true)).filter(path => path.endsWith(".js") || path.endsWith(".ts")); + + for (const cmdFilePath of commandFilePaths) { + const { data, run, options } = await import(toFileURL(cmdFilePath)); + + if (!data || !data.name) { + logger.warn(`Command ${cmdFilePath} does not have a data object or name`); + continue; + } + + if (typeof run !== "function") { + logger.warn(`Command ${cmdFilePath} does not have a run function or it is not a function`); + continue; + } + + this.commands.push({ data, run, options }); + } + } + + buildBuiltInValidations() { + for (const builtInValidationFunction of builtInValidationsFunctions) { + this.builtInValidations.push(builtInValidationFunction); + } + } + + handleCommands() { + this.client.on("interactionCreate", async interaction => { + if (!interaction.isChatInputCommand() && !interaction.isAutocomplete()) return; + + const isAutocomplete = interaction.isAutocomplete(); + + const targetCommand = this.commands.find(cmd => cmd.data.name === interaction.commandName); + + if (!targetCommand) return; + + // Skip if autocomplete handler is not defined + if (isAutocomplete && !targetCommand.autocompleteRun) return; + + let canRun = true; + + for (const validation of this.builtInValidations) { + const stopValidationLoop = validation({ + targetCommand, + interaction, + client: this.client, + }); + + if (stopValidationLoop) { + canRun = false; + break; + } + } + + if (!canRun) return; + + const command = targetCommand[isAutocomplete ? "autocompleteRun" : "run"]!; + + try { + await command({ + client: this.client, + interaction, + }); + } catch (error) { + logger.error(error, "Command cannot be executed"); + } + }); + } +} diff --git a/src/handlers/command-handler/utils/differentcommands.ts b/src/handlers/command-handler/utils/differentcommands.ts new file mode 100644 index 00000000..e4a6d7ac --- /dev/null +++ b/src/handlers/command-handler/utils/differentcommands.ts @@ -0,0 +1,8 @@ +export default function differentCommands(appCommand: any, localCommand: any) { + const appOptions = appCommand.options || []; + const localOptions = localCommand.options || []; + const appDescription = appCommand.description || ""; + const localDescription = localCommand.description || ""; + + return localDescription !== appDescription || localOptions.length !== appOptions.length; +} diff --git a/src/handlers/command-handler/validations/cooldown.ts b/src/handlers/command-handler/validations/cooldown.ts new file mode 100644 index 00000000..464480bc --- /dev/null +++ b/src/handlers/command-handler/validations/cooldown.ts @@ -0,0 +1,37 @@ +import { BuiltInValidationParams } from "@/types.js"; + +const cooldowns = new Map>(); + +export default function ({ targetCommand, interaction }: BuiltInValidationParams) { + const { cooldown } = targetCommand.options || {}; + + if (!cooldown) return; + + const now = Date.now(); + const userId = interaction.user.id; + const commandName = targetCommand.data.name; + + if (!cooldowns.has(commandName)) { + cooldowns.set(commandName, new Map()); + } + + const userCooldowns = cooldowns.get(commandName)!; + + if (userCooldowns) { + const expirationTime = userCooldowns.get(userId)! + cooldown * 1000; + if (now < expirationTime) { + const timeLeft = (expirationTime - now) / 1000; + + if (!interaction.isRepliable()) return; + + interaction.reply({ + content: `❌ You can use this command again in ${timeLeft.toFixed(1)} seconds.`, + ephemeral: true, + }); + + return true; + } + } + userCooldowns.set(userId, now); + return false; +} diff --git a/src/handlers/command-handler/validations/devOnly.ts b/src/handlers/command-handler/validations/devOnly.ts new file mode 100644 index 00000000..bb77637e --- /dev/null +++ b/src/handlers/command-handler/validations/devOnly.ts @@ -0,0 +1,30 @@ +import { BuiltInValidationParams } from "@/types.js"; +import { ChannelType } from "discord.js"; + +export default function ({ interaction, targetCommand, client }: BuiltInValidationParams) { + if (interaction.isAutocomplete()) return; + + const devGuildsIds = client.configService.get("devGuildsIds"); + + if (!targetCommand.options?.devOnly) return; + + if (!interaction.isRepliable()) return; + + if (interaction.channel?.type === ChannelType.DM) { + interaction.reply({ + content: "❌ This command is only available in development servers.", + ephemeral: true, + }); + + return true; + } + + if (interaction.inGuild() && !devGuildsIds.includes(interaction.guildId)) { + interaction.reply({ + content: "❌ This command is only available in development servers.", + ephemeral: true, + }); + + return true; + } +} diff --git a/src/handlers/command-handler/validations/index.ts b/src/handlers/command-handler/validations/index.ts new file mode 100644 index 00000000..c4e6dc58 --- /dev/null +++ b/src/handlers/command-handler/validations/index.ts @@ -0,0 +1,4 @@ +import devOnly from "./devOnly.js"; +import cooldown from "./cooldown.js"; + +export default [devOnly, cooldown]; diff --git a/src/handlers/event-handler/index.ts b/src/handlers/event-handler/index.ts new file mode 100644 index 00000000..a3980c8a --- /dev/null +++ b/src/handlers/event-handler/index.ts @@ -0,0 +1,77 @@ +import { resolve } from "node:path"; +import logger from "@/helpers/logger.js"; +import { getFilePaths } from "@/utils/get-path.js"; +import { toFileURL } from "@/utils/resolve-file.js"; +import { GuildQueueEvents, useMainPlayer } from "discord-player"; +import { ExtendedClient } from "@/structures/client.js"; +import { ClientEvents } from "discord.js"; + +type EventHandlerEvents = { + data: { + name: keyof ClientEvents; + once?: boolean; + player?: boolean; + }; + run: (...args: unknown[]) => void; +}; + +export class EventHandler { + events: EventHandlerEvents[] = []; + client: ExtendedClient; + constructor(client: ExtendedClient) { + this.client = client; + } + + async init() { + await this.#buildEvents(); + this.$registerEvents(); + } + + async #buildEvents() { + try { + const eventPath = resolve(this.client.configService.get("paths.events")); + const eventFilePaths = (await getFilePaths(eventPath, true)).filter(path => path.endsWith(".js") || path.endsWith(".ts")); + + for (const eventFilePath of eventFilePaths) { + const eventModule = await import(toFileURL(eventFilePath)); + + if (!("data" in eventModule) || !("run" in eventModule)) { + logger.warn(`Event ${eventFilePath} does not have a data object or name`); + continue; + } + + const { data, run } = eventModule; + + if (!data.name) { + logger.warn(`Event ${eventFilePath} does not have a data object or name`); + continue; + } + + if (typeof run !== "function") { + logger.warn(`Event ${eventFilePath} does not have a run function or it is not a function`); + continue; + } + + this.events.push({ data, run }); + + logger.debug(`Event ${eventFilePath} loaded`); + } + } catch (error) { + logger.error("Error build events: ", error); + } + logger.log("Events loaded"); + } + + $registerEvents() { + const player = useMainPlayer(); + this.events.forEach(event => { + if (event.data.player) { + player.events.on(event.data.name as keyof GuildQueueEvents, event.run); + } else if (event.data.once) { + this.client.once(event.data.name, event.run); + } else { + this.client.on(event.data.name, event.run); + } + }); + } +} diff --git a/src/handlers/index.ts b/src/handlers/index.ts new file mode 100644 index 00000000..83c7ad29 --- /dev/null +++ b/src/handlers/index.ts @@ -0,0 +1,20 @@ +import { ExtendedClient } from "@/structures/client.js"; +import { CommandHandler } from "./command-handler/index.js"; +import { EventHandler } from "./event-handler/index.js"; + +export class Handlers { + client: ExtendedClient; + constructor(client: ExtendedClient) { + this.client = client; + + this.init(); + } + + private async init() { + const eventHandler = new EventHandler(this.client); + await eventHandler.init(); + + const commandHandler = new CommandHandler(this.client); + await commandHandler.init(); + } +} diff --git a/src/helpers/extenders.ts b/src/helpers/extenders.ts new file mode 100644 index 00000000..1724f301 --- /dev/null +++ b/src/helpers/extenders.ts @@ -0,0 +1,74 @@ +import { BaseInteraction, CacheType, Interaction, InteractionReplyOptions, Message, User } from "discord.js"; +import useClient from "@/utils/use-client.js"; + +interface Options extends InteractionReplyOptions { + prefixEmoji?: string; + locale?: string; + edit?: boolean; + mention?: boolean; +} + +export const getLocale = async (guildId: string) => { + const client = useClient(); + const guild = await client.getGuildData(guildId); + return guild.language; +}; + +const getAppEmojis = () => { + const client = useClient(); + + return client.application.emojis.cache; +}; + +const formatReply = (message: string, prefixEmoji?: string) => { + const emojis = getAppEmojis(); + const emoji = emojis.find(emoji => emoji.name === prefixEmoji); + return prefixEmoji ? `${emoji?.toString()} ${message}` : `${message}`; +}; + +export const getUsername = (user: User) => (user.discriminator === "0" ? user.username : user.tag); + +export const replyTranslated = async (context: Interaction | Message, key: string, args: Record | null, options: Options) => { + const client = useClient(); + const locale = options.locale || client.configService.get("defaultLang"); + const translated = client.translate(key, { + lng: locale, + ...args, + }); + + const content = formatReply(translated, options.prefixEmoji); + + if (context instanceof BaseInteraction) { + if (!context.isRepliable()) return; + + if (options.edit) { + await context.editReply({ content }); + return; + } + await context.reply({ + content, + ephemeral: options.ephemeral || false, + }); + return; + } + + if (options.edit) { + await context.edit({ + content, + allowedMentions: { repliedUser: options.mention || false }, + }); + return; + } + await context.reply({ + content, + allowedMentions: { repliedUser: options.mention || false }, + }); +}; + +export const replySuccess = async +(context: Interaction | Message, key: string, args: Record | null, + options: Options = { prefixEmoji: "success" }) => await replyTranslated(context, key, args, options); + +export const replyError = async +(context: Interaction | Message, key: string, args: Record | null, + options: Options = { prefixEmoji: "error" }) => await replyTranslated(context, key, args, options); diff --git a/src/helpers/functions.ts b/src/helpers/functions.ts new file mode 100644 index 00000000..9772594e --- /dev/null +++ b/src/helpers/functions.ts @@ -0,0 +1,50 @@ +export async function asyncForEach(collection: T[], callback: (_item: T) => Promise) { + const allPromises = collection.map(async key => { + await callback(key); + }); + + return await Promise.all(allPromises); +} + +export function shuffle(pArray: T[]) { + const array: T[] = []; + + pArray.forEach(element => array.push(element)); + + let currentIndex = array.length, + temporaryValue, + randomIndex; + + while (currentIndex !== 0) { + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + + return array; +} + +export function randomNum(min: number = 0, max: number = 100) { + return (Math.random() * (max - min + 1)) << 0; +} + +export function printDate(date: Date | number, locale: Intl.LocalesArgument = "en-US") { + return new Intl.DateTimeFormat(locale).format(date); +} + +export function getNoun(number: number, wordForms: string[]) { + if (!Array.isArray(wordForms) || wordForms.length !== 3) { + throw new Error("wordForms should be an array with three elements: [one, two, five]"); + } + + const [one, two, five] = wordForms; + let n = Math.abs(number); + n %= 100; + if (n >= 5 && n <= 20) return five; + n %= 10; + if (n === 1) return one; + if (n >= 2 && n <= 4) return two; + return five; +} diff --git a/src/helpers/logger.ts b/src/helpers/logger.ts new file mode 100644 index 00000000..67e3d6cb --- /dev/null +++ b/src/helpers/logger.ts @@ -0,0 +1,51 @@ +import useClient from "@/utils/use-client.js"; +import chalk from "chalk"; + +function format(tDate: Date | number) { + return new Intl.DateTimeFormat("ru-RU", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }).format(tDate); +} + +const logLevels = { + LOG: chalk.bgBlue("LOG"), + WARN: chalk.black.bgYellow("WARN"), + ERROR: chalk.black.bgRed("ERROR"), + DEBUG: chalk.green("DEBUG"), + CMD: chalk.black.bgWhite("CMD"), + READY: chalk.black.bgGreen("READY"), +}; + +export default { + log(...content: unknown[]) { + return console.log(`[${format(Date.now())}]: ${logLevels.LOG} ${content.join(" ")}`); + }, + + warn(...content: unknown[]) { + return console.log(`[${format(Date.now())}]: ${logLevels.WARN} ${content.join(" ")}`); + }, + + error(...content: unknown[]) { + return console.log(`[${format(Date.now())}]: ${logLevels.ERROR} ${content.join(" ")}`); + }, + + debug(...content: unknown[]) { + const client = useClient(); + const isProd = client.configService.get("production"); + if (isProd) return; + return console.log(`[${format(Date.now())}]: ${logLevels.DEBUG} ${content.join(" ")}`); + }, + + cmd(...content: unknown[]) { + return console.log(`[${format(Date.now())}]: ${logLevels.CMD} ${content.join(" ")}`); + }, + + ready(...content: unknown[]) { + return console.log(`[${format(Date.now())}]: ${logLevels.READY} ${content.join(" ")}`); + }, +}; diff --git a/src/helpers/tasks/birthdays.ts b/src/helpers/tasks/birthdays.ts new file mode 100644 index 00000000..2dab5e60 --- /dev/null +++ b/src/helpers/tasks/birthdays.ts @@ -0,0 +1,74 @@ +import useClient from "@/utils/use-client.js"; +import UserModel from "@/models/UserModel.js"; +import { createEmbed } from "@/utils/create-embed.js"; +import logger from "../logger.js"; +import { getNoun } from "../functions.js"; + +export const data = { + name: "birthdays", + task: async () => { + const client = useClient(); + + const guilds = client.guilds.cache.values(); + const users = await client.adapter.find(UserModel, { + birthdate: { $ne: null }, + }); + + const currentData = new Date(); + const currentYear = currentData.getFullYear(); + const currentMonth = currentData.getMonth(); + const currentDate = currentData.getDate(); + + for (const guild of guilds) { + try { + const data = await client.getGuildData(guild.id); + const channel = data.plugins.birthdays ? await client.channels.fetch(data.plugins.birthdays) : null; + + if (!channel) return; + + if (!channel.isSendable()) return; + + const userIDs = users.filter(u => guild.members.cache.has(u.id)).map(u => u.id); + + await Promise.all( + userIDs.map(async userID => { + const user = users.find(u => u.id === userID); + if (!user) return; + + const userData = new Date(user.birthdate!).getFullYear() <= 1970 ? new Date(user.birthdate! * 1000) : new Date(user.birthdate!); + const userYear = userData.getFullYear(); + const userMonth = userData.getMonth(); + const userDate = userData.getDate(); + + const age = currentYear - userYear; + + if (userDate === currentDate && userMonth === currentMonth) { + const embed = createEmbed({ + author: { + name: client.user.username, + }, + fields: [ + { + name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", { + lng: data.language, + }), + value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", { + lng: data.language, + user: user.id, + age: `**${age}** ${getNoun(age, [client.translate("misc:NOUNS:AGE:1", data.language), client.translate("misc:NOUNS:AGE:2", data.language), client.translate("misc:NOUNS:AGE:5", data.language)])}`, + }), + }, + ], + }); + + await channel.send({ embeds: [embed] }).then(m => m.react(" ")); + } + }), + ); + } catch (error) { + logger.error(error); + } + } + }, + schedule: "0 5 * * *", +}; diff --git a/src/helpers/tasks/checkReminds.ts b/src/helpers/tasks/checkReminds.ts new file mode 100644 index 00000000..073da450 --- /dev/null +++ b/src/helpers/tasks/checkReminds.ts @@ -0,0 +1,83 @@ +import { createEmbed } from "@/utils/create-embed.js"; +import UserModel from "../../models/UserModel.js"; +import useClient from "../../utils/use-client.js"; +import { CronTaskData } from "@/types.js"; + +export const data: CronTaskData = { + name: "checkReminds", + task: async () => { + const client = useClient(); + + const users = await client.adapter.find(UserModel, { reminds: { $gt: [] } }); + + for (const user of users) { + if (!client.users.cache.has(user.id)) { + client.users.fetch(user.id); + } + + client.cacheReminds.set(user.id, { + id: user.id, + reminds: user.reminds, + }); + } + + client.cacheReminds.forEach(async ({ id, reminds }) => { + const cachedUser = client.users.cache.get(id); + + if (!cachedUser) return; + + const mustSent = reminds.filter(r => r.sendAt < Math.floor(Date.now() / 1000)); + + if (!mustSent.length) return; + + mustSent.forEach(async r => { + const embed = createEmbed({ + author: { + name: client.translate("general/remindme:EMBED_TITLE"), + }, + fields: [ + { + name: client.translate("general/remindme:EMBED_CREATED"), + value: ``, + inline: true, + }, + { + name: client.translate("general/remindme:EMBED_TIME"), + value: ``, + inline: true, + }, + { + name: client.translate("common:MESSAGE"), + value: r.message, + }, + ], + }); + + await cachedUser.send({ + embeds: [embed], + }); + + await client.adapter.updateOne( + UserModel, + { id }, + { + $pull: { + reminds: { + sendAt: r.sendAt, + }, + }, + }, + ); + }); + + const updatedReminds = reminds.filter(r => r.sendAt >= Math.floor(Date.now() / 1000)); + + if (!updatedReminds.length) { + client.cacheReminds.delete(id); + } else { + client.cacheReminds.set(id, { id, reminds: updatedReminds }); + } + }); + }, + schedule: "* * * * * *", +}; diff --git a/src/helpers/tictactoe.js b/src/helpers/tictactoe.js new file mode 100644 index 00000000..c3d238b9 --- /dev/null +++ b/src/helpers/tictactoe.js @@ -0,0 +1,603 @@ +// Thanks to simply-djs for this =) +// TODO: Refactor this please... + +import { ButtonBuilder, ActionRowBuilder, ButtonStyle, ComponentType } from "discord.js"; + +/** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + * @param {any[]} options Array with options (everything is optional) + * @param {string} options.userSlash Name of the user option in the interaction + * @param {string} options.embedFooter Game's embed footer + * @param {string} options.embedColor Game's embed color + * @param {string} options.timeoutEmbedColor Game's embed timeout color + * @param {string} options.xEmoji Emoji for X + * @param {string} options.oEmoji Emoji for O + * @param {string} options.idleEmoji Emoji for "nothing" + * @returns {Promise} + */ +export async function tictactoe(interaction, options = {}) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async resolve => { + try { + const { client } = interaction; + let opponent; + + if (interaction.commandId) { + opponent = interaction.options.getUser(options.userSlash || "user"); + + if (!opponent) { + return interaction.reply({ + content: interaction.translate("fun/tictactoe:NO_USER"), + ephemeral: true, + }); + } + + if (opponent.bot) { + return interaction.reply({ + content: interaction.translate("fun/tictactoe:BOT_USER"), + ephemeral: true, + }); + } + + if (opponent.id === (interaction.user ? interaction.user : interaction.author).id) { + return interaction.reply({ + content: interaction.translate("misc:CANT_YOURSELF"), + ephemeral: true, + }); + } + } else if (!interaction.commandId) { + opponent = interaction.mentions.members.first()?.user; + + if (!opponent) { + return interaction.reply({ + content: interaction.translate("fun/tictactoe:NO_USER"), + }); + } + + if (opponent.bot) { + return interaction.reply({ + content: interaction.translate("fun/tictactoe:BOT_USER"), + ephemeral: true, + }); + } + + if (opponent.id === interaction.member.id) { + return interaction.reply({ + content: interaction.translate("misc:CANT_YOURSELF"), + }); + } + } + + const footer = options.embedFooter || client.config.embed.footer, + color = options.embedColor || client.config.embed.color, + user = interaction.user ? interaction.user : interaction.author; + + const acceptEmbed = client.embed({ + author: { + name: user.getUsername(), + iconURL: user.displayAvatarURL(), + }, + title: interaction.translate("fun/tictactoe:REQUEST_WAIT", { + user: opponent.getUsername(), + }), + color, + footer, + }); + + const accept = new ButtonBuilder().setLabel(interaction.translate("common:ACCEPT")).setStyle(ButtonStyle.Success).setCustomId("acceptttt"); + const decline = new ButtonBuilder().setLabel(interaction.translate("common:DECLINE")).setStyle(ButtonStyle.Danger).setCustomId("declinettt"); + const accep = new ActionRowBuilder().addComponents([accept, decline]); + + const m = await interaction.reply({ + content: interaction.translate("fun/tictactoe:INVITE_USER", { + opponent: opponent.id, + }), + embeds: [acceptEmbed], + components: [accep], + fetchReply: true, + }); + + const collector = m.createMessageComponentCollector({ + componentType: ComponentType.Button, + time: 30 * 1000, + }); + + collector.on("collect", async button => { + if (button.user.id !== opponent.id) { + return button.reply({ + content: interaction.translate("fun/tictactoe:REQUEST_SEND", { + opponent: opponent.id, + }), + ephemeral: true, + }); + } + + if (button.customId === "declinettt") { + button.deferUpdate(); + return collector.stop("decline"); + } else if (button.customId === "acceptttt") { + button.deferUpdate(); + collector.stop(); + + const fighters = [(interaction.user ? interaction.user : interaction.author).id, opponent.id].sort(() => (Math.random() > 0.5 ? 1 : -1)); + + const xEmoji = options.xEmoji || "❌"; + const oEmoji = options.oEmoji || "⭕"; + + const dashmoji = options.idleEmoji || "➖"; + + const Args = { + user: 0, + a1: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + a2: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + a3: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + b1: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + b2: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + b3: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + c1: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + c2: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + c3: { + style: ButtonStyle.Secondary, + emoji: dashmoji, + disabled: false, + }, + }; + + const epm = client.embed({ + title: interaction.translate("fun/tictactoe:DESCRIPTION"), + color, + footer, + }); + + let msg; + if (interaction.commandId) { + msg = await interaction.editReply({ + embeds: [ + epm.setDescription( + interaction.translate("fun/tictactoe:WAITING", { + user: Args.userid, + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + ), + ], + }); + } else if (!interaction.commandId) { + msg = await button.message.edit({ + embeds: [ + epm.setDescription( + interaction.translate("fun/tictactoe:WAITING", { + user: Args.userid, + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + ), + ], + }); + } + + await ttt(msg); + + async function ttt(m) { + Args.userid = fighters[Args.user]; + const won = { + "<:O_:863314110560993340>": false, + "<:X_:863314044781723668>": false, + }; + + const a1 = new ButtonBuilder().setStyle(Args.a1.style).setEmoji(Args.a1.emoji).setCustomId("a1").setDisabled(Args.a1.disabled); + const a2 = new ButtonBuilder().setStyle(Args.a2.style).setEmoji(Args.a2.emoji).setCustomId("a2").setDisabled(Args.a2.disabled); + const a3 = new ButtonBuilder().setStyle(Args.a3.style).setEmoji(Args.a3.emoji).setCustomId("a3").setDisabled(Args.a3.disabled); + const b1 = new ButtonBuilder().setStyle(Args.b1.style).setEmoji(Args.b1.emoji).setCustomId("b1").setDisabled(Args.b1.disabled); + const b2 = new ButtonBuilder().setStyle(Args.b2.style).setEmoji(Args.b2.emoji).setCustomId("b2").setDisabled(Args.b2.disabled); + const b3 = new ButtonBuilder().setStyle(Args.b3.style).setEmoji(Args.b3.emoji).setCustomId("b3").setDisabled(Args.b3.disabled); + const c1 = new ButtonBuilder().setStyle(Args.c1.style).setEmoji(Args.c1.emoji).setCustomId("c1").setDisabled(Args.c1.disabled); + const c2 = new ButtonBuilder().setStyle(Args.c2.style).setEmoji(Args.c2.emoji).setCustomId("c2").setDisabled(Args.c2.disabled); + const c3 = new ButtonBuilder().setStyle(Args.c3.style).setEmoji(Args.c3.emoji).setCustomId("c3").setDisabled(Args.c3.disabled); + const a = new ActionRowBuilder().addComponents([a1, a2, a3]); + const b = new ActionRowBuilder().addComponents([b1, b2, b3]); + const c = new ActionRowBuilder().addComponents([c1, c2, c3]); + const buttons = [a, b, c]; + + if (Args.a1.emoji === oEmoji && Args.b1.emoji === oEmoji && Args.c1.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (Args.a2.emoji === oEmoji && Args.b2.emoji === oEmoji && Args.c2.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (Args.a3.emoji === oEmoji && Args.b3.emoji === oEmoji && Args.c3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (Args.a1.emoji === oEmoji && Args.b2.emoji === oEmoji && Args.c3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (Args.a3.emoji === oEmoji && Args.b2.emoji === oEmoji && Args.c1.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (Args.a1.emoji === oEmoji && Args.a2.emoji === oEmoji && Args.a3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (Args.b1.emoji === oEmoji && Args.b2.emoji === oEmoji && Args.b3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (Args.c1.emoji === oEmoji && Args.c2.emoji === oEmoji && Args.c3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (won["<:O_:863314110560993340>"] !== false) { + if (Args.user === 0) { + const won = await client.users.fetch(fighters[1]).catch(console.error); + resolve(won); + + if (options.resultBtn === true) { + return m.edit({ + content: interaction.translate("fun/tictactoe:WON", { + winner: fighters[1], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + components: buttons, + + embeds: [ + epm.setDescription( + interaction.translate("fun/tictactoe:WON", { + winner: fighters[1], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + ), + ], + }); + } else if (!options.resultBtn || options.resultBtn === false) { + return m.edit({ + content: interaction.translate("fun/tictactoe:WON", { + winner: fighters[1], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + + embeds: [ + epm.setDescription( + `${interaction.translate("fun/tictactoe:WON", { + winner: fighters[1], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + })}\n\`\`\`\n${Args.a1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n${Args.b1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n${Args.c1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), + ), + ], + components: [], + }); + } + } else if (Args.user === 1) { + const won = await client.users.fetch(fighters[0]).catch(console.error); + resolve(won); + + if (options.resultBtn === true) { + return m.edit({ + content: interaction.translate("fun/tictactoe:WON", { + winner: fighters[0], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + components: buttons, + embeds: [ + epm.setDescription( + interaction.translate("fun/tictactoe:WON", { + winner: fighters[0], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + ), + ], + }); + } else if (!options.resultBtn || options.resultBtn === false) { + return m.edit({ + content: interaction.translate("fun/tictactoe:WON", { + winner: fighters[0], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + + embeds: [ + epm.setDescription( + `${interaction.translate("fun/tictactoe:WON", { + winner: fighters[0], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + })}\n\`\`\`\n${Args.a1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n${Args.b1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n${Args.c1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), + ), + ], + components: [], + }); + } + } + } + + if (Args.a1.emoji === xEmoji && Args.b1.emoji === xEmoji && Args.c1.emoji === xEmoji) won["<:X_:863314044781723668>"] = true; + if (Args.a2.emoji === xEmoji && Args.b2.emoji === xEmoji && Args.c2.emoji === xEmoji) won["<:X_:863314044781723668>"] = true; + if (Args.a3.emoji === xEmoji && Args.b3.emoji === xEmoji && Args.c3.emoji === xEmoji) won["<:X_:863314044781723668>"] = true; + if (Args.a1.emoji === xEmoji && Args.b2.emoji === xEmoji && Args.c3.emoji === xEmoji) won["<:X_:863314044781723668>"] = true; + if (Args.a3.emoji === xEmoji && Args.b2.emoji === xEmoji && Args.c1.emoji === xEmoji) won["<:X_:863314044781723668>"] = true; + if (Args.a1.emoji === xEmoji && Args.a2.emoji === xEmoji && Args.a3.emoji === xEmoji) won["<:X_:863314044781723668>"] = true; + if (Args.b1.emoji === xEmoji && Args.b2.emoji === xEmoji && Args.b3.emoji === xEmoji) won["<:X_:863314044781723668>"] = true; + if (Args.c1.emoji === xEmoji && Args.c2.emoji === xEmoji && Args.c3.emoji === xEmoji) won["<:X_:863314044781723668>"] = true; + if (won["<:X_:863314044781723668>"] !== false) { + if (Args.user === 0) { + const won = await client.users.fetch(fighters[1]).catch(console.error); + resolve(won); + + if (options.resultBtn === true) { + return m.edit({ + content: interaction.translate("fun/tictactoe:WON", { + winner: fighters[1], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + components: buttons, + embeds: [ + epm.setDescription( + interaction.translate("fun/tictactoe:WON", { + winner: fighters[1], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + ), + ], + }); + } else if (!options.resultBtn || options.resultBtn === false) { + return m.edit({ + content: interaction.translate("fun/tictactoe:WON", { + winner: fighters[1], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + embeds: [ + epm.setDescription( + `${interaction.translate("fun/tictactoe:WON", { + winner: fighters[1], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + })}\n\`\`\`\n${Args.a1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n${Args.b1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n${Args.c1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), + ), + ], + components: [], + }); + } + } else if (Args.user === 1) { + const won = await client.users.fetch(fighters[0]).catch(console.error); + resolve(won); + + if (options.resultBtn === true) { + return m.edit({ + content: interaction.translate("fun/tictactoe:WON", { + winner: fighters[0], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + components: buttons, + embeds: [ + epm.setDescription( + interaction.translate("fun/tictactoe:WON", { + winner: fighters[0], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + ), + ], + }); + } else { + return m.edit({ + content: interaction.translate("fun/tictactoe:WON", { + winner: fighters[0], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + }), + embeds: [ + epm.setDescription( + `${interaction.translate("fun/tictactoe:WON", { + winner: fighters[0], + emoji: client.emojis.cache.get(oEmoji) || "⭕", + })}\n\`\`\`\n${Args.a1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n${Args.b1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n${Args.c1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c2.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c3.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), + ), + ], + components: [], + }); + } + } + } + + m.edit({ + content: `<@${Args.userid}>`, + embeds: [ + epm.setDescription( + interaction.translate("fun/tictactoe:WAITING", { + user: Args.userid, + emoji: Args.user === 0 ? `${client.emojis.cache.get(oEmoji) || "⭕"}` : `${client.emojis.cache.get(xEmoji) || "❌"}`, + }), + ), + ], + components: [a, b, c], + }); + + const collector = m.createMessageComponentCollector({ + componentType: ComponentType.Button, + max: 1, + }); + + collector.on("collect", b => { + if (b.user.id !== Args.userid) { + b.reply({ + content: interaction.translate("fun/tictactoe:CANT_PLAY"), + ephemeral: true, + }); + + ttt(m); + } else { + if (Args.user === 0) { + Args.user = 1; + Args[b.customId] = { + style: ButtonStyle.Success, + emoji: oEmoji, + disabled: true, + }; + } else { + Args.user = 0; + Args[b.customId] = { + style: ButtonStyle.Danger, + emoji: xEmoji, + disabled: true, + }; + } + b.deferUpdate(); + const map = (obj, fun) => + Object.entries(obj).reduce( + (prev, [key, value]) => ({ + ...prev, + [key]: fun(key, value), + }), + {}, + ); + const objectFilter = (obj, predicate) => + Object.keys(obj) + .filter(key => predicate(obj[key])) + .reduce((res, key) => ((res[key] = obj[key]), res), {}); + const Brgs = objectFilter( + map(Args, (_, fruit) => fruit.emoji === dashmoji), + num => num === true, + ); + + if (Object.keys(Brgs).length === 0) { + if (Args.a1.emoji === oEmoji && Args.b1.emoji === oEmoji && Args.c1.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + if (Args.a2.emoji === oEmoji && Args.b2.emoji === oEmoji && Args.c2.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + if (Args.a3.emoji === oEmoji && Args.b3.emoji === oEmoji && Args.c3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + if (Args.a1.emoji === oEmoji && Args.b2.emoji === oEmoji && Args.c3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + if (Args.a3.emoji === oEmoji && Args.b2.emoji === oEmoji && Args.c1.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + if (Args.a1.emoji === oEmoji && Args.a2.emoji === oEmoji && Args.a3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + if (Args.b1.emoji === oEmoji && Args.b2.emoji === oEmoji && Args.b3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + if (Args.c1.emoji === oEmoji && Args.c2.emoji === oEmoji && Args.c3.emoji === oEmoji) won["<:O_:863314110560993340>"] = true; + + if (won["<:O_:863314110560993340>"] === true) return ttt(m); + else if (won["<:X_:863314044781723668>"] === true) return; + else { + ttt(m); + + if (options.resultBtn === true) { + return m.edit({ + content: interaction.translate("fun/tictactoe:TIE"), + embeds: [epm.setDescription(interaction.translate("fun/tictactoe:TIE_DESC"))], + }); + } else { + return m + .edit({ + content: interaction.translate("fun/tictactoe:TIE"), + embeds: [ + epm.setDescription( + `${interaction.translate("fun/tictactoe:TIE_DESC")}!\n\`\`\`\n${Args.a1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.a2.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")} | ${Args.a3.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")}\n${Args.b1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.b2.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")} | ${Args.b3.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")}\n${Args.c1.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")} | ${Args.c2.emoji + .replace(oEmoji, "⭕") + .replace(xEmoji, "❌")} | ${Args.c3.emoji.replace(oEmoji, "⭕").replace(xEmoji, "❌")}\n\`\`\``.replaceAll(dashmoji, "➖"), + ), + ], + components: [], + }) + .catch(() => {}); + } + } + } + + ttt(m); + } + }); + collector.on("end", (collected, reason) => { + if (collected.size === 0 && reason === "time") { + m.edit({ + content: interaction.translate("fun/tictactoe:NO_ANSWER", { + user: Args.userid, + }), + components: [], + }); + } + }); + } + } + }); + + collector.on("end", (_, reason) => { + if (reason === "time") { + const embed = client.embed({ + author: { + name: user.getUsername(), + iconURL: user.displayAvatarURL(), + }, + title: interaction.translate("fun/tictactoe:NO_ANSWER_TITLE"), + description: interaction.translate("misc:TIMED_OUT"), + color: options.timeoutEmbedColor || "#C90000", + footer, + }); + + m.interaction.editReply({ + content: interaction.translate("fun/tictactoe:NOT_ANSWERED", { + user: opponent.id, + }), + embeds: [embed], + components: [], + }); + } + if (reason === "decline") { + const embed = client.embed({ + author: { + name: user.getUsername(), + iconURL: user.displayAvatarURL(), + }, + title: interaction.translate("fun/tictactoe:CANCELED"), + description: interaction.translate("fun/tictactoe:CANCELED_DESC", { + user: opponent.id, + }), + color: options.timeoutEmbedColor || "#C90000", + footer, + }); + + m.interaction.editReply({ + embeds: [embed], + components: [], + }); + } + }); + } catch (e) { + console.log("TicTacToe errored:", e); + } + }); +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..e5746e7b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,20 @@ +import { ExtendedClient } from "./structures/client.js"; +import logger from "./helpers/logger.js"; +import { CLIENT_INTENTS, CLIENT_ALLOWED_MENTIONS } from "./constants/index.js"; +import { Partials } from "discord.js"; + +const client = new ExtendedClient({ + intents: CLIENT_INTENTS, + partials: [Partials.Channel], + allowedMentions: CLIENT_ALLOWED_MENTIONS, +}); + +client.init(); + +client + .on("disconnect", () => logger.warn("Bot disconnected.")) + .on("reconnecting", () => logger.warn("Bot reconnecting...")) + .on("warn", logger.warn) + .on("error", logger.error); + +process.on("unhandledRejection", logger.error).on("uncaughtException", logger.error); diff --git a/base/Guild.js b/src/models/GuildModel.ts similarity index 62% rename from base/Guild.js rename to src/models/GuildModel.ts index 634cfdd9..ffc78858 100644 --- a/base/Guild.js +++ b/src/models/GuildModel.ts @@ -1,14 +1,25 @@ -const mongoose = require("mongoose"), - Schema = mongoose.Schema, - languages = require("../languages/language-meta.json"); +import { model, Schema, Types, Document } from "mongoose"; +import useClient from "@/utils/use-client.js"; -module.exports = mongoose.model("Guild", new Schema({ +const client = useClient(); + +interface IGuildSchema extends Document { + id: string; + membersData: { + [key: string]: any; + }; + members: Types.ObjectId[]; + language: string; + plugins: any; +} + +const GuildSchema = new Schema({ id: { type: String }, membersData: { type: Object, default: {} }, members: [{ type: Schema.Types.ObjectId, ref: "Member" }], - language: { type: String, default: languages.find(l => l.default).name }, + language: { type: String, default: client.configService.get("defaultLang") }, plugins: { type: Object, default: { @@ -52,4 +63,6 @@ module.exports = mongoose.model("Guild", new Schema({ modlogs: null, }, }, -})); +}); + +export default model("Guild", GuildSchema); diff --git a/src/models/MemberModel.ts b/src/models/MemberModel.ts new file mode 100644 index 00000000..e0c2683a --- /dev/null +++ b/src/models/MemberModel.ts @@ -0,0 +1,57 @@ +import { Schema, Document, model } from "mongoose"; + +interface IMemberSchema extends Document { + id: string; + guildID: string; + money: number; + workStreak: number; + bankSold: number; + exp: number; + level: number; + transactions: string[]; + registeredAt: number; + cooldowns: { + work: number; + rob: number; + }; + sanctions: string[]; + mute: { + muted: boolean; + case: string | null; + endDate: number | null; + }; +} + +const memberSchema = new Schema({ + id: { type: String }, + guildID: { type: String, ref: "Guild" }, + + money: { type: Number, default: 0 }, + workStreak: { type: Number, default: 0 }, + bankSold: { type: Number, default: 0 }, + exp: { type: Number, default: 0 }, + level: { type: Number, default: 0 }, + transactions: [String], + + registeredAt: { type: Number, default: Date.now() }, + + cooldowns: { + type: Object, + default: { + work: 0, + rob: 0, + }, + }, + + sanctions: [String], + mute: { + type: Object, + default: { + muted: false, + case: null, + endDate: null, + }, + }, +}); + +export default model("Member", memberSchema); diff --git a/src/models/UserModel.ts b/src/models/UserModel.ts new file mode 100644 index 00000000..e482c52c --- /dev/null +++ b/src/models/UserModel.ts @@ -0,0 +1,153 @@ +import { Schema, model, Document } from "mongoose"; +import { createCanvas, loadImage } from "@napi-rs/canvas"; + +export type UserReminds = { + message: string; + createdAt: number; + sendAt: number; +}; + +interface IUserSchema extends Document { + id: string; + rep: number; + bio: string; + birthdate: number | null; + lover: string; + registeredAt: number; + achievements: { + [key: string]: { + achieved: boolean; + progress: { + now: number; + total: number; + }; + }; + }; + cooldowns: { + rep: number; + }; + afk: string; + reminds: UserReminds[]; + logged: boolean; + apiToken: string; +} + +const genToken = () => { + let token = ""; + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzy0123456789.-_"; + for (let i = 0; i < 32; i++) token += characters.charAt(Math.floor(Math.random() * characters.length)); + + return token; +}; + +const userSchema = new Schema({ + id: { type: String }, + + rep: { type: Number, default: 0 }, + bio: { type: String }, + birthdate: { type: Number }, + lover: { type: String }, + + registeredAt: { type: Number, default: Date.now() }, + + achievements: { + type: Object, + default: { + married: { + achieved: false, + progress: { + now: 0, + total: 1, + }, + }, + work: { + achieved: false, + progress: { + now: 0, + total: 10, + }, + }, + firstCommand: { + achieved: false, + progress: { + now: 0, + total: 1, + }, + }, + slots: { + achieved: false, + progress: { + now: 0, + total: 3, + }, + }, + tip: { + achieved: false, + progress: { + now: 0, + total: 1, + }, + }, + rep: { + achieved: false, + progress: { + now: 0, + total: 20, + }, + }, + invite: { + achieved: false, + progress: { + now: 0, + total: 1, + }, + }, + }, + }, + + cooldowns: { + type: Object, + default: { + rep: 0, + }, + }, + + afk: { type: String, default: null }, + reminds: [ + { + type: Object, + default: { + message: null, + createdAt: null, + sendAt: null, + }, + }, + ], + logged: { type: Boolean, default: false }, + apiToken: { type: String, default: genToken() }, +}); + +userSchema.method("getAchievements", async function () { + const canvas = createCanvas(1800, 250), + ctx = canvas.getContext("2d"); + + const images = [ + await loadImage(`./assets/img/achievements/achievement${this.achievements.work.achieved ? "_colored" : ""}1.png`), + await loadImage(`./assets/img/achievements/achievement${this.achievements.firstCommand.achieved ? "_colored" : ""}2.png`), + await loadImage(`./assets/img/achievements/achievement${this.achievements.married.achieved ? "_colored" : ""}3.png`), + await loadImage(`./assets/img/achievements/achievement${this.achievements.slots.achieved ? "_colored" : ""}4.png`), + await loadImage(`./assets/img/achievements/achievement${this.achievements.tip.achieved ? "_colored" : ""}5.png`), + await loadImage(`./assets/img/achievements/achievement${this.achievements.rep.achieved ? "_colored" : ""}6.png`), + await loadImage(`./assets/img/achievements/achievement${this.achievements.invite.achieved ? "_colored" : ""}7.png`), + ]; + let dim = 0; + + for (let i = 0; i < images.length; i++) { + ctx.drawImage(images[i], dim, 10, 350, 200); + dim += 200; + } + + return await canvas.encode("png"); +}); + +export default model("User", userSchema); diff --git a/src/services/config/index.ts b/src/services/config/index.ts new file mode 100644 index 00000000..6e9bb96b --- /dev/null +++ b/src/services/config/index.ts @@ -0,0 +1,44 @@ +import fs from "fs"; +import { CONFIG_PATH } from "@/constants/index.js"; +import logger from "@/helpers/logger.js"; + +class ConfigService { + private config = this.loadConfig(); + + get(key: string): T { + const keys = key.split("."); + return keys.reduce((config, k) => (config && config[k] !== undefined ? config[k] : undefined), this.config); + } + + set(key: string, value: unknown) { + const keys = key.split("."); + keys.reduce((config, k, i) => { + if (i === keys.length - 1) { + config[k] = value; + } else { + config[k] = config[k] || {}; + } + return config[k]; + }, this.config); + this.saveConfig(); + } + + private loadConfig() { + if (fs.existsSync(CONFIG_PATH)) { + return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")); + } else { + logger.error("Config file not found"); + process.exit(1); + } + } + + private saveConfig() { + try { + fs.writeFileSync(CONFIG_PATH, JSON.stringify(this.config, null, 4), "utf-8"); + } catch (e) { + logger.error("Failed to save config: ", e); + } + } +} + +export default ConfigService; diff --git a/src/services/cron/index.ts b/src/services/cron/index.ts new file mode 100644 index 00000000..aef7663a --- /dev/null +++ b/src/services/cron/index.ts @@ -0,0 +1,62 @@ +import { CronJob } from "cron"; +import logger from "../../helpers/logger.js"; +import { CronTaskData } from "@/types.js"; + +export class CronManager { + jobs = new Map(); + tasks: CronTaskData[]; + constructor(tasks: CronTaskData[]) { + this.tasks = tasks; + } + + async init() { + if (!this.tasks.length) { + logger.warn("No cron tasks to schedule."); + return; + } + + for (const task of this.tasks) { + const taskJob = this.jobs.get(task.name); + if (taskJob?.isRunning) { + logger.warn(`Cron task "${task.name}" is already running.`); + continue; + } + + const job = new CronJob( + task.schedule, + async () => { + if (this.jobs.get(task.name)?.isError) { + return; + } + + try { + await task.task(); + } catch (error) { + logger.error(`Error executing cron task "${task.name}":`, error); + this.jobs.get(task.name)!.isError = true; + } + }, + null, + false, + "Europe/Moscow", + ); + + job.start(); + this.jobs.set(task.name, { + job, + isRunning: true, + isError: false, + }); + } + logger.log(`Cron tasks scheduled: ${this.jobs.size}`); + } + + stopAll() { + if (!this.jobs.size) return; + + for (const [_, jobInfo] of this.jobs.entries()) { + jobInfo.job.stop(); + } + logger.log("All cron jobs stopped."); + } +} diff --git a/src/services/languages/index.ts b/src/services/languages/index.ts new file mode 100644 index 00000000..d8a920c1 --- /dev/null +++ b/src/services/languages/index.ts @@ -0,0 +1,84 @@ +import i18next, { TOptionsBase } from "i18next"; +import Backend from "i18next-fs-backend"; +import fs from "fs/promises"; +import { resolve, join } from "path"; +import logger from "@/helpers/logger.js"; +import supportedLanguages from "./language-meta.js"; +import { ExtendedClient } from "@/structures/client.js"; + +interface InternationalizationServiceOptions { + defaultLanguage?: string; +} + +interface WalkDirectoryResult { + namespaces: string[]; + languages: string[]; +} + +export default class InternationalizationService { + private client: ExtendedClient; + private options: { + localesPath: string; + defaultLanguage: string; + }; + + constructor(client: ExtendedClient, options: InternationalizationServiceOptions = {}) { + this.client = client; + this.options = { + localesPath: resolve(this.client.configService.get("paths.locales")), + defaultLanguage: options.defaultLanguage || "en-US", + }; + this.init(); + } + + get getSupportedLanguages(): string[] { + return supportedLanguages.map(lang => lang.locale); + } + + private async walkDirectory(dir: string, namespaces: string[] = [], folderName: string = ""): Promise { + const files = await fs.readdir(dir, { withFileTypes: true }); + + const languages: string[] = []; + for (const file of files) { + if (file.isDirectory()) { + const isLanguage = file.name.includes("-"); + if (isLanguage) languages.push(file.name); + + const folder = await this.walkDirectory(join(dir, file.name), namespaces, isLanguage ? "" : `${file.name}/`); + + namespaces = folder.namespaces; + } else { + namespaces.push(`${folderName}${file.name.substr(0, file.name.length - 5)}`); + } + } + return { namespaces: [...new Set(namespaces)], languages }; + } + + private async init() { + const { namespaces, languages } = await this.walkDirectory(this.options.localesPath); + + const i18nInstance = await i18next.use(Backend).init({ + backend: { + loadPath: resolve(this.options.localesPath, "./{{lng}}/{{ns}}.json"), + }, + debug: this.client.configService.get("production") ? false : true, + fallbackLng: this.options.defaultLanguage, + interpolation: { escapeValue: false }, + load: "currentOnly", + preload: languages, + ns: namespaces, + defaultNS: namespaces[0], + initImmediate: false, + }); + + // Типизированная функция translate + this.client.translate = (key: string, options: TOptionsBase = {}): string => { + const lng = options.lng || this.options.defaultLanguage; + return i18next.t(key, { lng, ...options }); + }; + + logger.log("Internationalization initialized"); + + return i18nInstance; + } +} diff --git a/src/services/languages/language-meta.ts b/src/services/languages/language-meta.ts new file mode 100644 index 00000000..b0d9bab9 --- /dev/null +++ b/src/services/languages/language-meta.ts @@ -0,0 +1,20 @@ +export default [ + { + name: "en-US", + nativeName: "English", + locale: "en-US", + format: "HH:mm:ss, MMMM Do YYYY", + }, + { + name: "ru-RU", + nativeName: "Русский", + locale: "ru-RU", + format: "HH:mm:ss, Do MMMM YYYY", + }, + { + name: "uk-UA", + nativeName: "Українська", + locale: "uk-UA", + format: "HH:mm:ss, Do MMMM YYYY", + }, +]; diff --git a/languages/en-US/administration/addemoji.json b/src/services/languages/locales/en-US/administration/addemoji.json similarity index 100% rename from languages/en-US/administration/addemoji.json rename to src/services/languages/locales/en-US/administration/addemoji.json diff --git a/languages/en-US/administration/automod.json b/src/services/languages/locales/en-US/administration/automod.json similarity index 100% rename from languages/en-US/administration/automod.json rename to src/services/languages/locales/en-US/administration/automod.json diff --git a/languages/en-US/administration/autorole.json b/src/services/languages/locales/en-US/administration/autorole.json similarity index 100% rename from languages/en-US/administration/autorole.json rename to src/services/languages/locales/en-US/administration/autorole.json diff --git a/languages/en-US/administration/config.json b/src/services/languages/locales/en-US/administration/config.json similarity index 93% rename from languages/en-US/administration/config.json rename to src/services/languages/locales/en-US/administration/config.json index ec9ec653..29fc99cf 100644 --- a/languages/en-US/administration/config.json +++ b/src/services/languages/locales/en-US/administration/config.json @@ -23,8 +23,6 @@ "AUTO_SANCTIONS": "Automatic Sanctions", "BAN_CONTENT": "Ban: After **{{count}}** warnings", "BAN_NOT_DEFINED": "Ban: Not set", - "DASHBOARD_TITLE": "Modify Settings", - "DASHBOARD_CONTENT": "Click here to go to the dashboard", "GOODBYE_TITLE": "Farewell", "GOODBYE_CONTENT": "Channel: {{channel}}\nCard: {{withImage}}", "KICK_CONTENT": "Kick: After **{{count}}** warnings", diff --git a/languages/en-US/administration/deletemod.json b/src/services/languages/locales/en-US/administration/deletemod.json similarity index 100% rename from languages/en-US/administration/deletemod.json rename to src/services/languages/locales/en-US/administration/deletemod.json diff --git a/languages/en-US/administration/goodbye.json b/src/services/languages/locales/en-US/administration/goodbye.json similarity index 100% rename from languages/en-US/administration/goodbye.json rename to src/services/languages/locales/en-US/administration/goodbye.json diff --git a/languages/en-US/administration/selectroles.json b/src/services/languages/locales/en-US/administration/selectroles.json similarity index 100% rename from languages/en-US/administration/selectroles.json rename to src/services/languages/locales/en-US/administration/selectroles.json diff --git a/languages/en-US/administration/set.json b/src/services/languages/locales/en-US/administration/set.json similarity index 100% rename from languages/en-US/administration/set.json rename to src/services/languages/locales/en-US/administration/set.json diff --git a/languages/en-US/administration/setlang.json b/src/services/languages/locales/en-US/administration/setlang.json similarity index 100% rename from languages/en-US/administration/setlang.json rename to src/services/languages/locales/en-US/administration/setlang.json diff --git a/languages/en-US/administration/stealemoji.json b/src/services/languages/locales/en-US/administration/stealemoji.json similarity index 100% rename from languages/en-US/administration/stealemoji.json rename to src/services/languages/locales/en-US/administration/stealemoji.json diff --git a/languages/en-US/administration/welcome.json b/src/services/languages/locales/en-US/administration/welcome.json similarity index 100% rename from languages/en-US/administration/welcome.json rename to src/services/languages/locales/en-US/administration/welcome.json diff --git a/languages/en-US/beatrun/courses.json b/src/services/languages/locales/en-US/beatrun/courses.json similarity index 100% rename from languages/en-US/beatrun/courses.json rename to src/services/languages/locales/en-US/beatrun/courses.json diff --git a/languages/en-US/common.json b/src/services/languages/locales/en-US/common.json similarity index 100% rename from languages/en-US/common.json rename to src/services/languages/locales/en-US/common.json diff --git a/languages/en-US/economy/achievements.json b/src/services/languages/locales/en-US/economy/achievements.json similarity index 100% rename from languages/en-US/economy/achievements.json rename to src/services/languages/locales/en-US/economy/achievements.json diff --git a/languages/en-US/economy/bank.json b/src/services/languages/locales/en-US/economy/bank.json similarity index 100% rename from languages/en-US/economy/bank.json rename to src/services/languages/locales/en-US/economy/bank.json diff --git a/languages/en-US/economy/birthdate.json b/src/services/languages/locales/en-US/economy/birthdate.json similarity index 100% rename from languages/en-US/economy/birthdate.json rename to src/services/languages/locales/en-US/economy/birthdate.json diff --git a/languages/en-US/economy/divorce.json b/src/services/languages/locales/en-US/economy/divorce.json similarity index 100% rename from languages/en-US/economy/divorce.json rename to src/services/languages/locales/en-US/economy/divorce.json diff --git a/languages/en-US/economy/importmee6.json b/src/services/languages/locales/en-US/economy/importmee6.json similarity index 100% rename from languages/en-US/economy/importmee6.json rename to src/services/languages/locales/en-US/economy/importmee6.json diff --git a/languages/en-US/economy/leaderboard.json b/src/services/languages/locales/en-US/economy/leaderboard.json similarity index 100% rename from languages/en-US/economy/leaderboard.json rename to src/services/languages/locales/en-US/economy/leaderboard.json diff --git a/languages/en-US/economy/marry.json b/src/services/languages/locales/en-US/economy/marry.json similarity index 100% rename from languages/en-US/economy/marry.json rename to src/services/languages/locales/en-US/economy/marry.json diff --git a/languages/en-US/economy/money.json b/src/services/languages/locales/en-US/economy/money.json similarity index 100% rename from languages/en-US/economy/money.json rename to src/services/languages/locales/en-US/economy/money.json diff --git a/languages/en-US/economy/pay.json b/src/services/languages/locales/en-US/economy/pay.json similarity index 100% rename from languages/en-US/economy/pay.json rename to src/services/languages/locales/en-US/economy/pay.json diff --git a/languages/en-US/economy/profile.json b/src/services/languages/locales/en-US/economy/profile.json similarity index 100% rename from languages/en-US/economy/profile.json rename to src/services/languages/locales/en-US/economy/profile.json diff --git a/languages/en-US/economy/rep.json b/src/services/languages/locales/en-US/economy/rep.json similarity index 100% rename from languages/en-US/economy/rep.json rename to src/services/languages/locales/en-US/economy/rep.json diff --git a/languages/en-US/economy/rob.json b/src/services/languages/locales/en-US/economy/rob.json similarity index 100% rename from languages/en-US/economy/rob.json rename to src/services/languages/locales/en-US/economy/rob.json diff --git a/languages/en-US/economy/setbio.json b/src/services/languages/locales/en-US/economy/setbio.json similarity index 100% rename from languages/en-US/economy/setbio.json rename to src/services/languages/locales/en-US/economy/setbio.json diff --git a/languages/en-US/economy/slots.json b/src/services/languages/locales/en-US/economy/slots.json similarity index 100% rename from languages/en-US/economy/slots.json rename to src/services/languages/locales/en-US/economy/slots.json diff --git a/languages/en-US/economy/transactions.json b/src/services/languages/locales/en-US/economy/transactions.json similarity index 100% rename from languages/en-US/economy/transactions.json rename to src/services/languages/locales/en-US/economy/transactions.json diff --git a/languages/en-US/economy/work.json b/src/services/languages/locales/en-US/economy/work.json similarity index 100% rename from languages/en-US/economy/work.json rename to src/services/languages/locales/en-US/economy/work.json diff --git a/languages/en-US/fun/8ball.json b/src/services/languages/locales/en-US/fun/8ball.json similarity index 100% rename from languages/en-US/fun/8ball.json rename to src/services/languages/locales/en-US/fun/8ball.json diff --git a/languages/en-US/fun/cat.json b/src/services/languages/locales/en-US/fun/cat.json similarity index 100% rename from languages/en-US/fun/cat.json rename to src/services/languages/locales/en-US/fun/cat.json diff --git a/languages/en-US/fun/dog.json b/src/services/languages/locales/en-US/fun/dog.json similarity index 100% rename from languages/en-US/fun/dog.json rename to src/services/languages/locales/en-US/fun/dog.json diff --git a/languages/en-US/fun/lmgtfy.json b/src/services/languages/locales/en-US/fun/lmgtfy.json similarity index 100% rename from languages/en-US/fun/lmgtfy.json rename to src/services/languages/locales/en-US/fun/lmgtfy.json diff --git a/languages/en-US/fun/lovecalc.json b/src/services/languages/locales/en-US/fun/lovecalc.json similarity index 100% rename from languages/en-US/fun/lovecalc.json rename to src/services/languages/locales/en-US/fun/lovecalc.json diff --git a/languages/en-US/fun/memes.json b/src/services/languages/locales/en-US/fun/memes.json similarity index 100% rename from languages/en-US/fun/memes.json rename to src/services/languages/locales/en-US/fun/memes.json diff --git a/languages/en-US/fun/number.json b/src/services/languages/locales/en-US/fun/number.json similarity index 100% rename from languages/en-US/fun/number.json rename to src/services/languages/locales/en-US/fun/number.json diff --git a/languages/en-US/fun/tictactoe.json b/src/services/languages/locales/en-US/fun/tictactoe.json similarity index 100% rename from languages/en-US/fun/tictactoe.json rename to src/services/languages/locales/en-US/fun/tictactoe.json diff --git a/languages/en-US/general/afk.json b/src/services/languages/locales/en-US/general/afk.json similarity index 100% rename from languages/en-US/general/afk.json rename to src/services/languages/locales/en-US/general/afk.json diff --git a/languages/en-US/general/avatar.json b/src/services/languages/locales/en-US/general/avatar.json similarity index 100% rename from languages/en-US/general/avatar.json rename to src/services/languages/locales/en-US/general/avatar.json diff --git a/languages/en-US/general/boosters.json b/src/services/languages/locales/en-US/general/boosters.json similarity index 100% rename from languages/en-US/general/boosters.json rename to src/services/languages/locales/en-US/general/boosters.json diff --git a/languages/en-US/general/emoji.json b/src/services/languages/locales/en-US/general/emoji.json similarity index 100% rename from languages/en-US/general/emoji.json rename to src/services/languages/locales/en-US/general/emoji.json diff --git a/languages/en-US/general/help.json b/src/services/languages/locales/en-US/general/help.json similarity index 100% rename from languages/en-US/general/help.json rename to src/services/languages/locales/en-US/general/help.json diff --git a/languages/en-US/general/info.json b/src/services/languages/locales/en-US/general/info.json similarity index 100% rename from languages/en-US/general/info.json rename to src/services/languages/locales/en-US/general/info.json diff --git a/languages/en-US/general/minecraft.json b/src/services/languages/locales/en-US/general/minecraft.json similarity index 100% rename from languages/en-US/general/minecraft.json rename to src/services/languages/locales/en-US/general/minecraft.json diff --git a/languages/en-US/general/ping.json b/src/services/languages/locales/en-US/general/ping.json similarity index 100% rename from languages/en-US/general/ping.json rename to src/services/languages/locales/en-US/general/ping.json diff --git a/languages/en-US/general/remindme.json b/src/services/languages/locales/en-US/general/remindme.json similarity index 100% rename from languages/en-US/general/remindme.json rename to src/services/languages/locales/en-US/general/remindme.json diff --git a/languages/en-US/general/reminds.json b/src/services/languages/locales/en-US/general/reminds.json similarity index 100% rename from languages/en-US/general/reminds.json rename to src/services/languages/locales/en-US/general/reminds.json diff --git a/languages/en-US/general/report.json b/src/services/languages/locales/en-US/general/report.json similarity index 100% rename from languages/en-US/general/report.json rename to src/services/languages/locales/en-US/general/report.json diff --git a/languages/en-US/general/shorturl.json b/src/services/languages/locales/en-US/general/shorturl.json similarity index 100% rename from languages/en-US/general/shorturl.json rename to src/services/languages/locales/en-US/general/shorturl.json diff --git a/languages/en-US/general/staff.json b/src/services/languages/locales/en-US/general/staff.json similarity index 100% rename from languages/en-US/general/staff.json rename to src/services/languages/locales/en-US/general/staff.json diff --git a/languages/en-US/general/stats.json b/src/services/languages/locales/en-US/general/stats.json similarity index 100% rename from languages/en-US/general/stats.json rename to src/services/languages/locales/en-US/general/stats.json diff --git a/languages/en-US/general/suggest.json b/src/services/languages/locales/en-US/general/suggest.json similarity index 100% rename from languages/en-US/general/suggest.json rename to src/services/languages/locales/en-US/general/suggest.json diff --git a/languages/en-US/general/whois.json b/src/services/languages/locales/en-US/general/whois.json similarity index 100% rename from languages/en-US/general/whois.json rename to src/services/languages/locales/en-US/general/whois.json diff --git a/languages/en-US/iat/checkjar.json b/src/services/languages/locales/en-US/iat/checkjar.json similarity index 100% rename from languages/en-US/iat/checkjar.json rename to src/services/languages/locales/en-US/iat/checkjar.json diff --git a/languages/en-US/misc.json b/src/services/languages/locales/en-US/misc.json similarity index 100% rename from languages/en-US/misc.json rename to src/services/languages/locales/en-US/misc.json diff --git a/languages/en-US/moderation/ban.json b/src/services/languages/locales/en-US/moderation/ban.json similarity index 100% rename from languages/en-US/moderation/ban.json rename to src/services/languages/locales/en-US/moderation/ban.json diff --git a/languages/en-US/moderation/clear.json b/src/services/languages/locales/en-US/moderation/clear.json similarity index 100% rename from languages/en-US/moderation/clear.json rename to src/services/languages/locales/en-US/moderation/clear.json diff --git a/languages/en-US/moderation/clearwarns.json b/src/services/languages/locales/en-US/moderation/clearwarns.json similarity index 100% rename from languages/en-US/moderation/clearwarns.json rename to src/services/languages/locales/en-US/moderation/clearwarns.json diff --git a/languages/en-US/moderation/giveaway.json b/src/services/languages/locales/en-US/moderation/giveaway.json similarity index 100% rename from languages/en-US/moderation/giveaway.json rename to src/services/languages/locales/en-US/moderation/giveaway.json diff --git a/languages/en-US/moderation/kick.json b/src/services/languages/locales/en-US/moderation/kick.json similarity index 100% rename from languages/en-US/moderation/kick.json rename to src/services/languages/locales/en-US/moderation/kick.json diff --git a/languages/en-US/moderation/unban.json b/src/services/languages/locales/en-US/moderation/unban.json similarity index 100% rename from languages/en-US/moderation/unban.json rename to src/services/languages/locales/en-US/moderation/unban.json diff --git a/languages/en-US/moderation/untimeout.json b/src/services/languages/locales/en-US/moderation/untimeout.json similarity index 100% rename from languages/en-US/moderation/untimeout.json rename to src/services/languages/locales/en-US/moderation/untimeout.json diff --git a/languages/en-US/moderation/warn.json b/src/services/languages/locales/en-US/moderation/warn.json similarity index 100% rename from languages/en-US/moderation/warn.json rename to src/services/languages/locales/en-US/moderation/warn.json diff --git a/languages/en-US/moderation/warns.json b/src/services/languages/locales/en-US/moderation/warns.json similarity index 100% rename from languages/en-US/moderation/warns.json rename to src/services/languages/locales/en-US/moderation/warns.json diff --git a/languages/en-US/music/back.json b/src/services/languages/locales/en-US/music/back.json similarity index 100% rename from languages/en-US/music/back.json rename to src/services/languages/locales/en-US/music/back.json diff --git a/languages/en-US/music/clips.json b/src/services/languages/locales/en-US/music/clips.json similarity index 100% rename from languages/en-US/music/clips.json rename to src/services/languages/locales/en-US/music/clips.json diff --git a/languages/en-US/music/loop.json b/src/services/languages/locales/en-US/music/loop.json similarity index 100% rename from languages/en-US/music/loop.json rename to src/services/languages/locales/en-US/music/loop.json diff --git a/languages/en-US/music/nowplaying.json b/src/services/languages/locales/en-US/music/nowplaying.json similarity index 100% rename from languages/en-US/music/nowplaying.json rename to src/services/languages/locales/en-US/music/nowplaying.json diff --git a/languages/en-US/music/play.json b/src/services/languages/locales/en-US/music/play.json similarity index 100% rename from languages/en-US/music/play.json rename to src/services/languages/locales/en-US/music/play.json diff --git a/languages/en-US/music/queue.json b/src/services/languages/locales/en-US/music/queue.json similarity index 100% rename from languages/en-US/music/queue.json rename to src/services/languages/locales/en-US/music/queue.json diff --git a/languages/en-US/music/seek.json b/src/services/languages/locales/en-US/music/seek.json similarity index 100% rename from languages/en-US/music/seek.json rename to src/services/languages/locales/en-US/music/seek.json diff --git a/languages/en-US/music/shuffle.json b/src/services/languages/locales/en-US/music/shuffle.json similarity index 100% rename from languages/en-US/music/shuffle.json rename to src/services/languages/locales/en-US/music/shuffle.json diff --git a/languages/en-US/music/skip.json b/src/services/languages/locales/en-US/music/skip.json similarity index 100% rename from languages/en-US/music/skip.json rename to src/services/languages/locales/en-US/music/skip.json diff --git a/languages/en-US/music/stop.json b/src/services/languages/locales/en-US/music/stop.json similarity index 100% rename from languages/en-US/music/stop.json rename to src/services/languages/locales/en-US/music/stop.json diff --git a/languages/en-US/music/volume.json b/src/services/languages/locales/en-US/music/volume.json similarity index 100% rename from languages/en-US/music/volume.json rename to src/services/languages/locales/en-US/music/volume.json diff --git a/languages/en-US/owner/announcement.json b/src/services/languages/locales/en-US/owner/announcement.json similarity index 100% rename from languages/en-US/owner/announcement.json rename to src/services/languages/locales/en-US/owner/announcement.json diff --git a/languages/en-US/owner/debug.json b/src/services/languages/locales/en-US/owner/debug.json similarity index 100% rename from languages/en-US/owner/debug.json rename to src/services/languages/locales/en-US/owner/debug.json diff --git a/languages/en-US/owner/eval.json b/src/services/languages/locales/en-US/owner/eval.json similarity index 100% rename from languages/en-US/owner/eval.json rename to src/services/languages/locales/en-US/owner/eval.json diff --git a/languages/en-US/owner/reload.json b/src/services/languages/locales/en-US/owner/reload.json similarity index 100% rename from languages/en-US/owner/reload.json rename to src/services/languages/locales/en-US/owner/reload.json diff --git a/languages/en-US/owner/say.json b/src/services/languages/locales/en-US/owner/say.json similarity index 100% rename from languages/en-US/owner/say.json rename to src/services/languages/locales/en-US/owner/say.json diff --git a/languages/en-US/owner/servers.json b/src/services/languages/locales/en-US/owner/servers.json similarity index 100% rename from languages/en-US/owner/servers.json rename to src/services/languages/locales/en-US/owner/servers.json diff --git a/languages/en-US/tickets/adduser.json b/src/services/languages/locales/en-US/tickets/adduser.json similarity index 100% rename from languages/en-US/tickets/adduser.json rename to src/services/languages/locales/en-US/tickets/adduser.json diff --git a/languages/en-US/tickets/closeticket.json b/src/services/languages/locales/en-US/tickets/closeticket.json similarity index 100% rename from languages/en-US/tickets/closeticket.json rename to src/services/languages/locales/en-US/tickets/closeticket.json diff --git a/languages/en-US/tickets/createticketembed.json b/src/services/languages/locales/en-US/tickets/createticketembed.json similarity index 100% rename from languages/en-US/tickets/createticketembed.json rename to src/services/languages/locales/en-US/tickets/createticketembed.json diff --git a/languages/en-US/tickets/removeuser.json b/src/services/languages/locales/en-US/tickets/removeuser.json similarity index 100% rename from languages/en-US/tickets/removeuser.json rename to src/services/languages/locales/en-US/tickets/removeuser.json diff --git a/languages/ru-RU/administration/addemoji.json b/src/services/languages/locales/ru-RU/administration/addemoji.json similarity index 100% rename from languages/ru-RU/administration/addemoji.json rename to src/services/languages/locales/ru-RU/administration/addemoji.json diff --git a/languages/ru-RU/administration/automod.json b/src/services/languages/locales/ru-RU/administration/automod.json similarity index 100% rename from languages/ru-RU/administration/automod.json rename to src/services/languages/locales/ru-RU/administration/automod.json diff --git a/languages/ru-RU/administration/autorole.json b/src/services/languages/locales/ru-RU/administration/autorole.json similarity index 100% rename from languages/ru-RU/administration/autorole.json rename to src/services/languages/locales/ru-RU/administration/autorole.json diff --git a/languages/ru-RU/administration/config.json b/src/services/languages/locales/ru-RU/administration/config.json similarity index 91% rename from languages/ru-RU/administration/config.json rename to src/services/languages/locales/ru-RU/administration/config.json index 277325d3..eb229e73 100644 --- a/languages/ru-RU/administration/config.json +++ b/src/services/languages/locales/ru-RU/administration/config.json @@ -23,8 +23,6 @@ "AUTO_SANCTIONS": "Автоматические наказания", "BAN_CONTENT": "Бан: После **{{count}}** предупреждений", "BAN_NOT_DEFINED": "Бан: Не назначено", - "DASHBOARD_TITLE": "Изменить настройки", - "DASHBOARD_CONTENT": "Нажмите сюда, чтобы перейти в панель управления", "GOODBYE_TITLE": "Прощание", "GOODBYE_CONTENT": "Канал: {{channel}}\nКарточка: {{withImage}}", "KICK_CONTENT": "Кик: После **{{count}}** предупреждений", diff --git a/languages/ru-RU/administration/deletemod.json b/src/services/languages/locales/ru-RU/administration/deletemod.json similarity index 100% rename from languages/ru-RU/administration/deletemod.json rename to src/services/languages/locales/ru-RU/administration/deletemod.json diff --git a/languages/ru-RU/administration/goodbye.json b/src/services/languages/locales/ru-RU/administration/goodbye.json similarity index 100% rename from languages/ru-RU/administration/goodbye.json rename to src/services/languages/locales/ru-RU/administration/goodbye.json diff --git a/languages/ru-RU/administration/selectroles.json b/src/services/languages/locales/ru-RU/administration/selectroles.json similarity index 100% rename from languages/ru-RU/administration/selectroles.json rename to src/services/languages/locales/ru-RU/administration/selectroles.json diff --git a/languages/ru-RU/administration/set.json b/src/services/languages/locales/ru-RU/administration/set.json similarity index 100% rename from languages/ru-RU/administration/set.json rename to src/services/languages/locales/ru-RU/administration/set.json diff --git a/languages/ru-RU/administration/setlang.json b/src/services/languages/locales/ru-RU/administration/setlang.json similarity index 100% rename from languages/ru-RU/administration/setlang.json rename to src/services/languages/locales/ru-RU/administration/setlang.json diff --git a/languages/ru-RU/administration/stealemoji.json b/src/services/languages/locales/ru-RU/administration/stealemoji.json similarity index 100% rename from languages/ru-RU/administration/stealemoji.json rename to src/services/languages/locales/ru-RU/administration/stealemoji.json diff --git a/languages/ru-RU/administration/welcome.json b/src/services/languages/locales/ru-RU/administration/welcome.json similarity index 100% rename from languages/ru-RU/administration/welcome.json rename to src/services/languages/locales/ru-RU/administration/welcome.json diff --git a/languages/ru-RU/beatrun/courses.json b/src/services/languages/locales/ru-RU/beatrun/courses.json similarity index 100% rename from languages/ru-RU/beatrun/courses.json rename to src/services/languages/locales/ru-RU/beatrun/courses.json diff --git a/languages/ru-RU/common.json b/src/services/languages/locales/ru-RU/common.json similarity index 100% rename from languages/ru-RU/common.json rename to src/services/languages/locales/ru-RU/common.json diff --git a/languages/ru-RU/economy/achievements.json b/src/services/languages/locales/ru-RU/economy/achievements.json similarity index 100% rename from languages/ru-RU/economy/achievements.json rename to src/services/languages/locales/ru-RU/economy/achievements.json diff --git a/languages/ru-RU/economy/bank.json b/src/services/languages/locales/ru-RU/economy/bank.json similarity index 100% rename from languages/ru-RU/economy/bank.json rename to src/services/languages/locales/ru-RU/economy/bank.json diff --git a/languages/ru-RU/economy/birthdate.json b/src/services/languages/locales/ru-RU/economy/birthdate.json similarity index 100% rename from languages/ru-RU/economy/birthdate.json rename to src/services/languages/locales/ru-RU/economy/birthdate.json diff --git a/languages/ru-RU/economy/divorce.json b/src/services/languages/locales/ru-RU/economy/divorce.json similarity index 100% rename from languages/ru-RU/economy/divorce.json rename to src/services/languages/locales/ru-RU/economy/divorce.json diff --git a/languages/ru-RU/economy/importmee6.json b/src/services/languages/locales/ru-RU/economy/importmee6.json similarity index 100% rename from languages/ru-RU/economy/importmee6.json rename to src/services/languages/locales/ru-RU/economy/importmee6.json diff --git a/languages/ru-RU/economy/leaderboard.json b/src/services/languages/locales/ru-RU/economy/leaderboard.json similarity index 100% rename from languages/ru-RU/economy/leaderboard.json rename to src/services/languages/locales/ru-RU/economy/leaderboard.json diff --git a/languages/ru-RU/economy/marry.json b/src/services/languages/locales/ru-RU/economy/marry.json similarity index 100% rename from languages/ru-RU/economy/marry.json rename to src/services/languages/locales/ru-RU/economy/marry.json diff --git a/languages/ru-RU/economy/money.json b/src/services/languages/locales/ru-RU/economy/money.json similarity index 100% rename from languages/ru-RU/economy/money.json rename to src/services/languages/locales/ru-RU/economy/money.json diff --git a/languages/ru-RU/economy/pay.json b/src/services/languages/locales/ru-RU/economy/pay.json similarity index 100% rename from languages/ru-RU/economy/pay.json rename to src/services/languages/locales/ru-RU/economy/pay.json diff --git a/languages/ru-RU/economy/profile.json b/src/services/languages/locales/ru-RU/economy/profile.json similarity index 100% rename from languages/ru-RU/economy/profile.json rename to src/services/languages/locales/ru-RU/economy/profile.json diff --git a/languages/ru-RU/economy/rep.json b/src/services/languages/locales/ru-RU/economy/rep.json similarity index 100% rename from languages/ru-RU/economy/rep.json rename to src/services/languages/locales/ru-RU/economy/rep.json diff --git a/languages/ru-RU/economy/rob.json b/src/services/languages/locales/ru-RU/economy/rob.json similarity index 100% rename from languages/ru-RU/economy/rob.json rename to src/services/languages/locales/ru-RU/economy/rob.json diff --git a/languages/ru-RU/economy/setbio.json b/src/services/languages/locales/ru-RU/economy/setbio.json similarity index 100% rename from languages/ru-RU/economy/setbio.json rename to src/services/languages/locales/ru-RU/economy/setbio.json diff --git a/languages/ru-RU/economy/slots.json b/src/services/languages/locales/ru-RU/economy/slots.json similarity index 100% rename from languages/ru-RU/economy/slots.json rename to src/services/languages/locales/ru-RU/economy/slots.json diff --git a/languages/ru-RU/economy/transactions.json b/src/services/languages/locales/ru-RU/economy/transactions.json similarity index 100% rename from languages/ru-RU/economy/transactions.json rename to src/services/languages/locales/ru-RU/economy/transactions.json diff --git a/languages/ru-RU/economy/work.json b/src/services/languages/locales/ru-RU/economy/work.json similarity index 100% rename from languages/ru-RU/economy/work.json rename to src/services/languages/locales/ru-RU/economy/work.json diff --git a/languages/ru-RU/fun/8ball.json b/src/services/languages/locales/ru-RU/fun/8ball.json similarity index 100% rename from languages/ru-RU/fun/8ball.json rename to src/services/languages/locales/ru-RU/fun/8ball.json diff --git a/languages/ru-RU/fun/cat.json b/src/services/languages/locales/ru-RU/fun/cat.json similarity index 100% rename from languages/ru-RU/fun/cat.json rename to src/services/languages/locales/ru-RU/fun/cat.json diff --git a/languages/ru-RU/fun/dog.json b/src/services/languages/locales/ru-RU/fun/dog.json similarity index 100% rename from languages/ru-RU/fun/dog.json rename to src/services/languages/locales/ru-RU/fun/dog.json diff --git a/languages/ru-RU/fun/lmgtfy.json b/src/services/languages/locales/ru-RU/fun/lmgtfy.json similarity index 100% rename from languages/ru-RU/fun/lmgtfy.json rename to src/services/languages/locales/ru-RU/fun/lmgtfy.json diff --git a/languages/ru-RU/fun/lovecalc.json b/src/services/languages/locales/ru-RU/fun/lovecalc.json similarity index 100% rename from languages/ru-RU/fun/lovecalc.json rename to src/services/languages/locales/ru-RU/fun/lovecalc.json diff --git a/languages/ru-RU/fun/memes.json b/src/services/languages/locales/ru-RU/fun/memes.json similarity index 100% rename from languages/ru-RU/fun/memes.json rename to src/services/languages/locales/ru-RU/fun/memes.json diff --git a/languages/ru-RU/fun/number.json b/src/services/languages/locales/ru-RU/fun/number.json similarity index 100% rename from languages/ru-RU/fun/number.json rename to src/services/languages/locales/ru-RU/fun/number.json diff --git a/languages/ru-RU/fun/tictactoe.json b/src/services/languages/locales/ru-RU/fun/tictactoe.json similarity index 100% rename from languages/ru-RU/fun/tictactoe.json rename to src/services/languages/locales/ru-RU/fun/tictactoe.json diff --git a/languages/ru-RU/general/afk.json b/src/services/languages/locales/ru-RU/general/afk.json similarity index 100% rename from languages/ru-RU/general/afk.json rename to src/services/languages/locales/ru-RU/general/afk.json diff --git a/languages/ru-RU/general/avatar.json b/src/services/languages/locales/ru-RU/general/avatar.json similarity index 100% rename from languages/ru-RU/general/avatar.json rename to src/services/languages/locales/ru-RU/general/avatar.json diff --git a/languages/ru-RU/general/boosters.json b/src/services/languages/locales/ru-RU/general/boosters.json similarity index 100% rename from languages/ru-RU/general/boosters.json rename to src/services/languages/locales/ru-RU/general/boosters.json diff --git a/languages/ru-RU/general/emoji.json b/src/services/languages/locales/ru-RU/general/emoji.json similarity index 100% rename from languages/ru-RU/general/emoji.json rename to src/services/languages/locales/ru-RU/general/emoji.json diff --git a/languages/ru-RU/general/help.json b/src/services/languages/locales/ru-RU/general/help.json similarity index 100% rename from languages/ru-RU/general/help.json rename to src/services/languages/locales/ru-RU/general/help.json diff --git a/languages/ru-RU/general/info.json b/src/services/languages/locales/ru-RU/general/info.json similarity index 100% rename from languages/ru-RU/general/info.json rename to src/services/languages/locales/ru-RU/general/info.json diff --git a/languages/ru-RU/general/minecraft.json b/src/services/languages/locales/ru-RU/general/minecraft.json similarity index 100% rename from languages/ru-RU/general/minecraft.json rename to src/services/languages/locales/ru-RU/general/minecraft.json diff --git a/languages/ru-RU/general/ping.json b/src/services/languages/locales/ru-RU/general/ping.json similarity index 100% rename from languages/ru-RU/general/ping.json rename to src/services/languages/locales/ru-RU/general/ping.json diff --git a/languages/ru-RU/general/remindme.json b/src/services/languages/locales/ru-RU/general/remindme.json similarity index 100% rename from languages/ru-RU/general/remindme.json rename to src/services/languages/locales/ru-RU/general/remindme.json diff --git a/languages/ru-RU/general/reminds.json b/src/services/languages/locales/ru-RU/general/reminds.json similarity index 100% rename from languages/ru-RU/general/reminds.json rename to src/services/languages/locales/ru-RU/general/reminds.json diff --git a/languages/ru-RU/general/report.json b/src/services/languages/locales/ru-RU/general/report.json similarity index 100% rename from languages/ru-RU/general/report.json rename to src/services/languages/locales/ru-RU/general/report.json diff --git a/languages/ru-RU/general/shorturl.json b/src/services/languages/locales/ru-RU/general/shorturl.json similarity index 100% rename from languages/ru-RU/general/shorturl.json rename to src/services/languages/locales/ru-RU/general/shorturl.json diff --git a/languages/ru-RU/general/staff.json b/src/services/languages/locales/ru-RU/general/staff.json similarity index 100% rename from languages/ru-RU/general/staff.json rename to src/services/languages/locales/ru-RU/general/staff.json diff --git a/languages/ru-RU/general/stats.json b/src/services/languages/locales/ru-RU/general/stats.json similarity index 100% rename from languages/ru-RU/general/stats.json rename to src/services/languages/locales/ru-RU/general/stats.json diff --git a/languages/ru-RU/general/suggest.json b/src/services/languages/locales/ru-RU/general/suggest.json similarity index 100% rename from languages/ru-RU/general/suggest.json rename to src/services/languages/locales/ru-RU/general/suggest.json diff --git a/languages/ru-RU/general/whois.json b/src/services/languages/locales/ru-RU/general/whois.json similarity index 100% rename from languages/ru-RU/general/whois.json rename to src/services/languages/locales/ru-RU/general/whois.json diff --git a/languages/ru-RU/iat/checkjar.json b/src/services/languages/locales/ru-RU/iat/checkjar.json similarity index 100% rename from languages/ru-RU/iat/checkjar.json rename to src/services/languages/locales/ru-RU/iat/checkjar.json diff --git a/languages/ru-RU/misc.json b/src/services/languages/locales/ru-RU/misc.json similarity index 100% rename from languages/ru-RU/misc.json rename to src/services/languages/locales/ru-RU/misc.json diff --git a/languages/ru-RU/moderation/ban.json b/src/services/languages/locales/ru-RU/moderation/ban.json similarity index 100% rename from languages/ru-RU/moderation/ban.json rename to src/services/languages/locales/ru-RU/moderation/ban.json diff --git a/languages/ru-RU/moderation/clear.json b/src/services/languages/locales/ru-RU/moderation/clear.json similarity index 100% rename from languages/ru-RU/moderation/clear.json rename to src/services/languages/locales/ru-RU/moderation/clear.json diff --git a/languages/ru-RU/moderation/clearwarns.json b/src/services/languages/locales/ru-RU/moderation/clearwarns.json similarity index 100% rename from languages/ru-RU/moderation/clearwarns.json rename to src/services/languages/locales/ru-RU/moderation/clearwarns.json diff --git a/languages/ru-RU/moderation/giveaway.json b/src/services/languages/locales/ru-RU/moderation/giveaway.json similarity index 100% rename from languages/ru-RU/moderation/giveaway.json rename to src/services/languages/locales/ru-RU/moderation/giveaway.json diff --git a/languages/ru-RU/moderation/kick.json b/src/services/languages/locales/ru-RU/moderation/kick.json similarity index 100% rename from languages/ru-RU/moderation/kick.json rename to src/services/languages/locales/ru-RU/moderation/kick.json diff --git a/languages/ru-RU/moderation/unban.json b/src/services/languages/locales/ru-RU/moderation/unban.json similarity index 100% rename from languages/ru-RU/moderation/unban.json rename to src/services/languages/locales/ru-RU/moderation/unban.json diff --git a/languages/ru-RU/moderation/untimeout.json b/src/services/languages/locales/ru-RU/moderation/untimeout.json similarity index 100% rename from languages/ru-RU/moderation/untimeout.json rename to src/services/languages/locales/ru-RU/moderation/untimeout.json diff --git a/languages/ru-RU/moderation/warn.json b/src/services/languages/locales/ru-RU/moderation/warn.json similarity index 100% rename from languages/ru-RU/moderation/warn.json rename to src/services/languages/locales/ru-RU/moderation/warn.json diff --git a/languages/ru-RU/moderation/warns.json b/src/services/languages/locales/ru-RU/moderation/warns.json similarity index 100% rename from languages/ru-RU/moderation/warns.json rename to src/services/languages/locales/ru-RU/moderation/warns.json diff --git a/languages/ru-RU/music/back.json b/src/services/languages/locales/ru-RU/music/back.json similarity index 100% rename from languages/ru-RU/music/back.json rename to src/services/languages/locales/ru-RU/music/back.json diff --git a/languages/ru-RU/music/clips.json b/src/services/languages/locales/ru-RU/music/clips.json similarity index 100% rename from languages/ru-RU/music/clips.json rename to src/services/languages/locales/ru-RU/music/clips.json diff --git a/languages/ru-RU/music/loop.json b/src/services/languages/locales/ru-RU/music/loop.json similarity index 100% rename from languages/ru-RU/music/loop.json rename to src/services/languages/locales/ru-RU/music/loop.json diff --git a/languages/ru-RU/music/nowplaying.json b/src/services/languages/locales/ru-RU/music/nowplaying.json similarity index 100% rename from languages/ru-RU/music/nowplaying.json rename to src/services/languages/locales/ru-RU/music/nowplaying.json diff --git a/languages/ru-RU/music/play.json b/src/services/languages/locales/ru-RU/music/play.json similarity index 100% rename from languages/ru-RU/music/play.json rename to src/services/languages/locales/ru-RU/music/play.json diff --git a/languages/ru-RU/music/queue.json b/src/services/languages/locales/ru-RU/music/queue.json similarity index 100% rename from languages/ru-RU/music/queue.json rename to src/services/languages/locales/ru-RU/music/queue.json diff --git a/languages/ru-RU/music/seek.json b/src/services/languages/locales/ru-RU/music/seek.json similarity index 100% rename from languages/ru-RU/music/seek.json rename to src/services/languages/locales/ru-RU/music/seek.json diff --git a/languages/ru-RU/music/shuffle.json b/src/services/languages/locales/ru-RU/music/shuffle.json similarity index 100% rename from languages/ru-RU/music/shuffle.json rename to src/services/languages/locales/ru-RU/music/shuffle.json diff --git a/languages/ru-RU/music/skip.json b/src/services/languages/locales/ru-RU/music/skip.json similarity index 100% rename from languages/ru-RU/music/skip.json rename to src/services/languages/locales/ru-RU/music/skip.json diff --git a/languages/ru-RU/music/stop.json b/src/services/languages/locales/ru-RU/music/stop.json similarity index 100% rename from languages/ru-RU/music/stop.json rename to src/services/languages/locales/ru-RU/music/stop.json diff --git a/languages/ru-RU/music/volume.json b/src/services/languages/locales/ru-RU/music/volume.json similarity index 100% rename from languages/ru-RU/music/volume.json rename to src/services/languages/locales/ru-RU/music/volume.json diff --git a/languages/ru-RU/owner/announcement.json b/src/services/languages/locales/ru-RU/owner/announcement.json similarity index 100% rename from languages/ru-RU/owner/announcement.json rename to src/services/languages/locales/ru-RU/owner/announcement.json diff --git a/languages/ru-RU/owner/debug.json b/src/services/languages/locales/ru-RU/owner/debug.json similarity index 100% rename from languages/ru-RU/owner/debug.json rename to src/services/languages/locales/ru-RU/owner/debug.json diff --git a/languages/ru-RU/owner/eval.json b/src/services/languages/locales/ru-RU/owner/eval.json similarity index 100% rename from languages/ru-RU/owner/eval.json rename to src/services/languages/locales/ru-RU/owner/eval.json diff --git a/languages/ru-RU/owner/reload.json b/src/services/languages/locales/ru-RU/owner/reload.json similarity index 100% rename from languages/ru-RU/owner/reload.json rename to src/services/languages/locales/ru-RU/owner/reload.json diff --git a/languages/ru-RU/owner/say.json b/src/services/languages/locales/ru-RU/owner/say.json similarity index 100% rename from languages/ru-RU/owner/say.json rename to src/services/languages/locales/ru-RU/owner/say.json diff --git a/languages/ru-RU/owner/servers.json b/src/services/languages/locales/ru-RU/owner/servers.json similarity index 100% rename from languages/ru-RU/owner/servers.json rename to src/services/languages/locales/ru-RU/owner/servers.json diff --git a/languages/ru-RU/tickets/adduser.json b/src/services/languages/locales/ru-RU/tickets/adduser.json similarity index 100% rename from languages/ru-RU/tickets/adduser.json rename to src/services/languages/locales/ru-RU/tickets/adduser.json diff --git a/languages/ru-RU/tickets/closeticket.json b/src/services/languages/locales/ru-RU/tickets/closeticket.json similarity index 100% rename from languages/ru-RU/tickets/closeticket.json rename to src/services/languages/locales/ru-RU/tickets/closeticket.json diff --git a/languages/ru-RU/tickets/createticketembed.json b/src/services/languages/locales/ru-RU/tickets/createticketembed.json similarity index 100% rename from languages/ru-RU/tickets/createticketembed.json rename to src/services/languages/locales/ru-RU/tickets/createticketembed.json diff --git a/languages/ru-RU/tickets/removeuser.json b/src/services/languages/locales/ru-RU/tickets/removeuser.json similarity index 100% rename from languages/ru-RU/tickets/removeuser.json rename to src/services/languages/locales/ru-RU/tickets/removeuser.json diff --git a/languages/uk-UA/administration/addemoji.json b/src/services/languages/locales/uk-UA/administration/addemoji.json similarity index 100% rename from languages/uk-UA/administration/addemoji.json rename to src/services/languages/locales/uk-UA/administration/addemoji.json diff --git a/languages/uk-UA/administration/automod.json b/src/services/languages/locales/uk-UA/administration/automod.json similarity index 100% rename from languages/uk-UA/administration/automod.json rename to src/services/languages/locales/uk-UA/administration/automod.json diff --git a/languages/uk-UA/administration/autorole.json b/src/services/languages/locales/uk-UA/administration/autorole.json similarity index 100% rename from languages/uk-UA/administration/autorole.json rename to src/services/languages/locales/uk-UA/administration/autorole.json diff --git a/languages/uk-UA/administration/config.json b/src/services/languages/locales/uk-UA/administration/config.json similarity index 91% rename from languages/uk-UA/administration/config.json rename to src/services/languages/locales/uk-UA/administration/config.json index 23667af3..63c4c186 100644 --- a/languages/uk-UA/administration/config.json +++ b/src/services/languages/locales/uk-UA/administration/config.json @@ -23,8 +23,6 @@ "AUTO_SANCTIONS": "Автоматичні покарання", "BAN_CONTENT": "Бан: Після **{{count}}** попереджень", "BAN_NOT_DEFINED": "Бан: Не призначено", - "DASHBOARD_TITLE": "Змінити налаштування", - "DASHBOARD_CONTENT": "Натисніть сюди, щоб перейти до панелі керування", "GOODBYE_TITLE": "Прощання", "GOODBYE_CONTENT": "Канал: {{channel}}\nКартка: {{withImage}}", "KICK_CONTENT": "Кік: Після **{{count}}** попереджень", diff --git a/languages/uk-UA/administration/deletemod.json b/src/services/languages/locales/uk-UA/administration/deletemod.json similarity index 100% rename from languages/uk-UA/administration/deletemod.json rename to src/services/languages/locales/uk-UA/administration/deletemod.json diff --git a/languages/uk-UA/administration/goodbye.json b/src/services/languages/locales/uk-UA/administration/goodbye.json similarity index 100% rename from languages/uk-UA/administration/goodbye.json rename to src/services/languages/locales/uk-UA/administration/goodbye.json diff --git a/languages/uk-UA/administration/selectroles.json b/src/services/languages/locales/uk-UA/administration/selectroles.json similarity index 100% rename from languages/uk-UA/administration/selectroles.json rename to src/services/languages/locales/uk-UA/administration/selectroles.json diff --git a/languages/uk-UA/administration/set.json b/src/services/languages/locales/uk-UA/administration/set.json similarity index 100% rename from languages/uk-UA/administration/set.json rename to src/services/languages/locales/uk-UA/administration/set.json diff --git a/languages/uk-UA/administration/setlang.json b/src/services/languages/locales/uk-UA/administration/setlang.json similarity index 100% rename from languages/uk-UA/administration/setlang.json rename to src/services/languages/locales/uk-UA/administration/setlang.json diff --git a/languages/uk-UA/administration/stealemoji.json b/src/services/languages/locales/uk-UA/administration/stealemoji.json similarity index 100% rename from languages/uk-UA/administration/stealemoji.json rename to src/services/languages/locales/uk-UA/administration/stealemoji.json diff --git a/languages/uk-UA/administration/welcome.json b/src/services/languages/locales/uk-UA/administration/welcome.json similarity index 100% rename from languages/uk-UA/administration/welcome.json rename to src/services/languages/locales/uk-UA/administration/welcome.json diff --git a/languages/uk-UA/beatrun/courses.json b/src/services/languages/locales/uk-UA/beatrun/courses.json similarity index 100% rename from languages/uk-UA/beatrun/courses.json rename to src/services/languages/locales/uk-UA/beatrun/courses.json diff --git a/languages/uk-UA/common.json b/src/services/languages/locales/uk-UA/common.json similarity index 100% rename from languages/uk-UA/common.json rename to src/services/languages/locales/uk-UA/common.json diff --git a/languages/uk-UA/economy/achievements.json b/src/services/languages/locales/uk-UA/economy/achievements.json similarity index 100% rename from languages/uk-UA/economy/achievements.json rename to src/services/languages/locales/uk-UA/economy/achievements.json diff --git a/languages/uk-UA/economy/bank.json b/src/services/languages/locales/uk-UA/economy/bank.json similarity index 100% rename from languages/uk-UA/economy/bank.json rename to src/services/languages/locales/uk-UA/economy/bank.json diff --git a/languages/uk-UA/economy/birthdate.json b/src/services/languages/locales/uk-UA/economy/birthdate.json similarity index 100% rename from languages/uk-UA/economy/birthdate.json rename to src/services/languages/locales/uk-UA/economy/birthdate.json diff --git a/languages/uk-UA/economy/divorce.json b/src/services/languages/locales/uk-UA/economy/divorce.json similarity index 100% rename from languages/uk-UA/economy/divorce.json rename to src/services/languages/locales/uk-UA/economy/divorce.json diff --git a/languages/uk-UA/economy/importmee6.json b/src/services/languages/locales/uk-UA/economy/importmee6.json similarity index 100% rename from languages/uk-UA/economy/importmee6.json rename to src/services/languages/locales/uk-UA/economy/importmee6.json diff --git a/languages/uk-UA/economy/leaderboard.json b/src/services/languages/locales/uk-UA/economy/leaderboard.json similarity index 100% rename from languages/uk-UA/economy/leaderboard.json rename to src/services/languages/locales/uk-UA/economy/leaderboard.json diff --git a/languages/uk-UA/economy/marry.json b/src/services/languages/locales/uk-UA/economy/marry.json similarity index 100% rename from languages/uk-UA/economy/marry.json rename to src/services/languages/locales/uk-UA/economy/marry.json diff --git a/languages/uk-UA/economy/money.json b/src/services/languages/locales/uk-UA/economy/money.json similarity index 100% rename from languages/uk-UA/economy/money.json rename to src/services/languages/locales/uk-UA/economy/money.json diff --git a/languages/uk-UA/economy/pay.json b/src/services/languages/locales/uk-UA/economy/pay.json similarity index 100% rename from languages/uk-UA/economy/pay.json rename to src/services/languages/locales/uk-UA/economy/pay.json diff --git a/languages/uk-UA/economy/profile.json b/src/services/languages/locales/uk-UA/economy/profile.json similarity index 100% rename from languages/uk-UA/economy/profile.json rename to src/services/languages/locales/uk-UA/economy/profile.json diff --git a/languages/uk-UA/economy/rep.json b/src/services/languages/locales/uk-UA/economy/rep.json similarity index 100% rename from languages/uk-UA/economy/rep.json rename to src/services/languages/locales/uk-UA/economy/rep.json diff --git a/languages/uk-UA/economy/rob.json b/src/services/languages/locales/uk-UA/economy/rob.json similarity index 100% rename from languages/uk-UA/economy/rob.json rename to src/services/languages/locales/uk-UA/economy/rob.json diff --git a/languages/uk-UA/economy/setbio.json b/src/services/languages/locales/uk-UA/economy/setbio.json similarity index 100% rename from languages/uk-UA/economy/setbio.json rename to src/services/languages/locales/uk-UA/economy/setbio.json diff --git a/languages/uk-UA/economy/slots.json b/src/services/languages/locales/uk-UA/economy/slots.json similarity index 100% rename from languages/uk-UA/economy/slots.json rename to src/services/languages/locales/uk-UA/economy/slots.json diff --git a/languages/uk-UA/economy/transactions.json b/src/services/languages/locales/uk-UA/economy/transactions.json similarity index 100% rename from languages/uk-UA/economy/transactions.json rename to src/services/languages/locales/uk-UA/economy/transactions.json diff --git a/languages/uk-UA/economy/work.json b/src/services/languages/locales/uk-UA/economy/work.json similarity index 100% rename from languages/uk-UA/economy/work.json rename to src/services/languages/locales/uk-UA/economy/work.json diff --git a/languages/uk-UA/fun/8ball.json b/src/services/languages/locales/uk-UA/fun/8ball.json similarity index 100% rename from languages/uk-UA/fun/8ball.json rename to src/services/languages/locales/uk-UA/fun/8ball.json diff --git a/languages/uk-UA/fun/cat.json b/src/services/languages/locales/uk-UA/fun/cat.json similarity index 100% rename from languages/uk-UA/fun/cat.json rename to src/services/languages/locales/uk-UA/fun/cat.json diff --git a/languages/uk-UA/fun/dog.json b/src/services/languages/locales/uk-UA/fun/dog.json similarity index 100% rename from languages/uk-UA/fun/dog.json rename to src/services/languages/locales/uk-UA/fun/dog.json diff --git a/languages/uk-UA/fun/lmgtfy.json b/src/services/languages/locales/uk-UA/fun/lmgtfy.json similarity index 100% rename from languages/uk-UA/fun/lmgtfy.json rename to src/services/languages/locales/uk-UA/fun/lmgtfy.json diff --git a/languages/uk-UA/fun/lovecalc.json b/src/services/languages/locales/uk-UA/fun/lovecalc.json similarity index 100% rename from languages/uk-UA/fun/lovecalc.json rename to src/services/languages/locales/uk-UA/fun/lovecalc.json diff --git a/languages/uk-UA/fun/memes.json b/src/services/languages/locales/uk-UA/fun/memes.json similarity index 100% rename from languages/uk-UA/fun/memes.json rename to src/services/languages/locales/uk-UA/fun/memes.json diff --git a/languages/uk-UA/fun/number.json b/src/services/languages/locales/uk-UA/fun/number.json similarity index 100% rename from languages/uk-UA/fun/number.json rename to src/services/languages/locales/uk-UA/fun/number.json diff --git a/languages/uk-UA/fun/tictactoe.json b/src/services/languages/locales/uk-UA/fun/tictactoe.json similarity index 100% rename from languages/uk-UA/fun/tictactoe.json rename to src/services/languages/locales/uk-UA/fun/tictactoe.json diff --git a/languages/uk-UA/general/afk.json b/src/services/languages/locales/uk-UA/general/afk.json similarity index 100% rename from languages/uk-UA/general/afk.json rename to src/services/languages/locales/uk-UA/general/afk.json diff --git a/languages/uk-UA/general/avatar.json b/src/services/languages/locales/uk-UA/general/avatar.json similarity index 100% rename from languages/uk-UA/general/avatar.json rename to src/services/languages/locales/uk-UA/general/avatar.json diff --git a/languages/uk-UA/general/boosters.json b/src/services/languages/locales/uk-UA/general/boosters.json similarity index 100% rename from languages/uk-UA/general/boosters.json rename to src/services/languages/locales/uk-UA/general/boosters.json diff --git a/languages/uk-UA/general/emoji.json b/src/services/languages/locales/uk-UA/general/emoji.json similarity index 100% rename from languages/uk-UA/general/emoji.json rename to src/services/languages/locales/uk-UA/general/emoji.json diff --git a/languages/uk-UA/general/help.json b/src/services/languages/locales/uk-UA/general/help.json similarity index 100% rename from languages/uk-UA/general/help.json rename to src/services/languages/locales/uk-UA/general/help.json diff --git a/languages/uk-UA/general/info.json b/src/services/languages/locales/uk-UA/general/info.json similarity index 100% rename from languages/uk-UA/general/info.json rename to src/services/languages/locales/uk-UA/general/info.json diff --git a/languages/uk-UA/general/minecraft.json b/src/services/languages/locales/uk-UA/general/minecraft.json similarity index 100% rename from languages/uk-UA/general/minecraft.json rename to src/services/languages/locales/uk-UA/general/minecraft.json diff --git a/languages/uk-UA/general/ping.json b/src/services/languages/locales/uk-UA/general/ping.json similarity index 100% rename from languages/uk-UA/general/ping.json rename to src/services/languages/locales/uk-UA/general/ping.json diff --git a/languages/uk-UA/general/remindme.json b/src/services/languages/locales/uk-UA/general/remindme.json similarity index 100% rename from languages/uk-UA/general/remindme.json rename to src/services/languages/locales/uk-UA/general/remindme.json diff --git a/languages/uk-UA/general/reminds.json b/src/services/languages/locales/uk-UA/general/reminds.json similarity index 100% rename from languages/uk-UA/general/reminds.json rename to src/services/languages/locales/uk-UA/general/reminds.json diff --git a/languages/uk-UA/general/report.json b/src/services/languages/locales/uk-UA/general/report.json similarity index 100% rename from languages/uk-UA/general/report.json rename to src/services/languages/locales/uk-UA/general/report.json diff --git a/languages/uk-UA/general/shorturl.json b/src/services/languages/locales/uk-UA/general/shorturl.json similarity index 100% rename from languages/uk-UA/general/shorturl.json rename to src/services/languages/locales/uk-UA/general/shorturl.json diff --git a/languages/uk-UA/general/staff.json b/src/services/languages/locales/uk-UA/general/staff.json similarity index 100% rename from languages/uk-UA/general/staff.json rename to src/services/languages/locales/uk-UA/general/staff.json diff --git a/languages/uk-UA/general/stats.json b/src/services/languages/locales/uk-UA/general/stats.json similarity index 100% rename from languages/uk-UA/general/stats.json rename to src/services/languages/locales/uk-UA/general/stats.json diff --git a/languages/uk-UA/general/suggest.json b/src/services/languages/locales/uk-UA/general/suggest.json similarity index 100% rename from languages/uk-UA/general/suggest.json rename to src/services/languages/locales/uk-UA/general/suggest.json diff --git a/languages/uk-UA/general/whois.json b/src/services/languages/locales/uk-UA/general/whois.json similarity index 100% rename from languages/uk-UA/general/whois.json rename to src/services/languages/locales/uk-UA/general/whois.json diff --git a/languages/uk-UA/iat/checkjar.json b/src/services/languages/locales/uk-UA/iat/checkjar.json similarity index 100% rename from languages/uk-UA/iat/checkjar.json rename to src/services/languages/locales/uk-UA/iat/checkjar.json diff --git a/languages/uk-UA/misc.json b/src/services/languages/locales/uk-UA/misc.json similarity index 100% rename from languages/uk-UA/misc.json rename to src/services/languages/locales/uk-UA/misc.json diff --git a/languages/uk-UA/moderation/ban.json b/src/services/languages/locales/uk-UA/moderation/ban.json similarity index 100% rename from languages/uk-UA/moderation/ban.json rename to src/services/languages/locales/uk-UA/moderation/ban.json diff --git a/languages/uk-UA/moderation/clear.json b/src/services/languages/locales/uk-UA/moderation/clear.json similarity index 100% rename from languages/uk-UA/moderation/clear.json rename to src/services/languages/locales/uk-UA/moderation/clear.json diff --git a/languages/uk-UA/moderation/clearwarns.json b/src/services/languages/locales/uk-UA/moderation/clearwarns.json similarity index 100% rename from languages/uk-UA/moderation/clearwarns.json rename to src/services/languages/locales/uk-UA/moderation/clearwarns.json diff --git a/languages/uk-UA/moderation/giveaway.json b/src/services/languages/locales/uk-UA/moderation/giveaway.json similarity index 100% rename from languages/uk-UA/moderation/giveaway.json rename to src/services/languages/locales/uk-UA/moderation/giveaway.json diff --git a/languages/uk-UA/moderation/kick.json b/src/services/languages/locales/uk-UA/moderation/kick.json similarity index 100% rename from languages/uk-UA/moderation/kick.json rename to src/services/languages/locales/uk-UA/moderation/kick.json diff --git a/languages/uk-UA/moderation/unban.json b/src/services/languages/locales/uk-UA/moderation/unban.json similarity index 100% rename from languages/uk-UA/moderation/unban.json rename to src/services/languages/locales/uk-UA/moderation/unban.json diff --git a/languages/uk-UA/moderation/untimeout.json b/src/services/languages/locales/uk-UA/moderation/untimeout.json similarity index 100% rename from languages/uk-UA/moderation/untimeout.json rename to src/services/languages/locales/uk-UA/moderation/untimeout.json diff --git a/languages/uk-UA/moderation/warn.json b/src/services/languages/locales/uk-UA/moderation/warn.json similarity index 100% rename from languages/uk-UA/moderation/warn.json rename to src/services/languages/locales/uk-UA/moderation/warn.json diff --git a/languages/uk-UA/moderation/warns.json b/src/services/languages/locales/uk-UA/moderation/warns.json similarity index 100% rename from languages/uk-UA/moderation/warns.json rename to src/services/languages/locales/uk-UA/moderation/warns.json diff --git a/languages/uk-UA/music/back.json b/src/services/languages/locales/uk-UA/music/back.json similarity index 100% rename from languages/uk-UA/music/back.json rename to src/services/languages/locales/uk-UA/music/back.json diff --git a/languages/uk-UA/music/clips.json b/src/services/languages/locales/uk-UA/music/clips.json similarity index 100% rename from languages/uk-UA/music/clips.json rename to src/services/languages/locales/uk-UA/music/clips.json diff --git a/languages/uk-UA/music/loop.json b/src/services/languages/locales/uk-UA/music/loop.json similarity index 100% rename from languages/uk-UA/music/loop.json rename to src/services/languages/locales/uk-UA/music/loop.json diff --git a/languages/uk-UA/music/nowplaying.json b/src/services/languages/locales/uk-UA/music/nowplaying.json similarity index 100% rename from languages/uk-UA/music/nowplaying.json rename to src/services/languages/locales/uk-UA/music/nowplaying.json diff --git a/languages/uk-UA/music/play.json b/src/services/languages/locales/uk-UA/music/play.json similarity index 100% rename from languages/uk-UA/music/play.json rename to src/services/languages/locales/uk-UA/music/play.json diff --git a/languages/uk-UA/music/queue.json b/src/services/languages/locales/uk-UA/music/queue.json similarity index 100% rename from languages/uk-UA/music/queue.json rename to src/services/languages/locales/uk-UA/music/queue.json diff --git a/languages/uk-UA/music/seek.json b/src/services/languages/locales/uk-UA/music/seek.json similarity index 100% rename from languages/uk-UA/music/seek.json rename to src/services/languages/locales/uk-UA/music/seek.json diff --git a/languages/uk-UA/music/shuffle.json b/src/services/languages/locales/uk-UA/music/shuffle.json similarity index 100% rename from languages/uk-UA/music/shuffle.json rename to src/services/languages/locales/uk-UA/music/shuffle.json diff --git a/languages/uk-UA/music/skip.json b/src/services/languages/locales/uk-UA/music/skip.json similarity index 100% rename from languages/uk-UA/music/skip.json rename to src/services/languages/locales/uk-UA/music/skip.json diff --git a/languages/uk-UA/music/stop.json b/src/services/languages/locales/uk-UA/music/stop.json similarity index 100% rename from languages/uk-UA/music/stop.json rename to src/services/languages/locales/uk-UA/music/stop.json diff --git a/languages/uk-UA/music/volume.json b/src/services/languages/locales/uk-UA/music/volume.json similarity index 100% rename from languages/uk-UA/music/volume.json rename to src/services/languages/locales/uk-UA/music/volume.json diff --git a/languages/uk-UA/owner/announcement.json b/src/services/languages/locales/uk-UA/owner/announcement.json similarity index 100% rename from languages/uk-UA/owner/announcement.json rename to src/services/languages/locales/uk-UA/owner/announcement.json diff --git a/languages/uk-UA/owner/debug.json b/src/services/languages/locales/uk-UA/owner/debug.json similarity index 100% rename from languages/uk-UA/owner/debug.json rename to src/services/languages/locales/uk-UA/owner/debug.json diff --git a/languages/uk-UA/owner/eval.json b/src/services/languages/locales/uk-UA/owner/eval.json similarity index 100% rename from languages/uk-UA/owner/eval.json rename to src/services/languages/locales/uk-UA/owner/eval.json diff --git a/languages/uk-UA/owner/reload.json b/src/services/languages/locales/uk-UA/owner/reload.json similarity index 100% rename from languages/uk-UA/owner/reload.json rename to src/services/languages/locales/uk-UA/owner/reload.json diff --git a/languages/uk-UA/owner/say.json b/src/services/languages/locales/uk-UA/owner/say.json similarity index 100% rename from languages/uk-UA/owner/say.json rename to src/services/languages/locales/uk-UA/owner/say.json diff --git a/languages/uk-UA/owner/servers.json b/src/services/languages/locales/uk-UA/owner/servers.json similarity index 100% rename from languages/uk-UA/owner/servers.json rename to src/services/languages/locales/uk-UA/owner/servers.json diff --git a/languages/uk-UA/tickets/adduser.json b/src/services/languages/locales/uk-UA/tickets/adduser.json similarity index 100% rename from languages/uk-UA/tickets/adduser.json rename to src/services/languages/locales/uk-UA/tickets/adduser.json diff --git a/languages/uk-UA/tickets/closeticket.json b/src/services/languages/locales/uk-UA/tickets/closeticket.json similarity index 100% rename from languages/uk-UA/tickets/closeticket.json rename to src/services/languages/locales/uk-UA/tickets/closeticket.json diff --git a/languages/uk-UA/tickets/createticketembed.json b/src/services/languages/locales/uk-UA/tickets/createticketembed.json similarity index 100% rename from languages/uk-UA/tickets/createticketembed.json rename to src/services/languages/locales/uk-UA/tickets/createticketembed.json diff --git a/languages/uk-UA/tickets/removeuser.json b/src/services/languages/locales/uk-UA/tickets/removeuser.json similarity index 100% rename from languages/uk-UA/tickets/removeuser.json rename to src/services/languages/locales/uk-UA/tickets/removeuser.json diff --git a/src/structures/client.ts b/src/structures/client.ts new file mode 100644 index 00000000..99e34427 --- /dev/null +++ b/src/structures/client.ts @@ -0,0 +1,85 @@ +import { Client, ClientOptions } from "discord.js"; +import { GiveawaysManager } from "discord-giveaways"; +import { TOptionsBase } from "i18next"; +import { Handlers } from "@/handlers/index.js"; +import MongooseAdapter from "@/adapters/database/MongooseAdapter.js"; +import logger from "@/helpers/logger.js"; +import ConfigService from "@/services/config/index.js"; +import InternationalizationService from "@/services/languages/index.js"; +import { SUPER_CONTEXT } from "@/constants/index.js"; +import { cacheRemindsData } from "@/types.js"; +import { Player } from "discord-player"; + +export class ExtendedClient extends Client { + configService = new ConfigService(); + adapter = new MongooseAdapter(this.configService.get("mongoDB")); + cacheReminds = new Map(); + i18n = new InternationalizationService(this); + translate!: ( + _key: string, + _options?: + | TOptionsBase + | { + [key: string]: string; + }, + ) => string; + + // @ts-ignore - because ExtendedClient != Client from discord.js + giveaways = new GiveawaysManager(this, { + storage: "./giveaways.json", + default: { + botsCanWin: false, + embedColor: this.configService.get("embed.color"), + embedColorEnd: "#FF0000", + reaction: "🎉", + }, + }); + + constructor(options: ClientOptions) { + if (SUPER_CONTEXT.getStore()) { + return SUPER_CONTEXT.getStore() as ExtendedClient; + } + super(options); + + new Handlers(this); + + // @ts-ignore - because ExtendedClient != Client from discord.js + new Player(this); + SUPER_CONTEXT.enterWith(this); + } + + async init() { + try { + await this.adapter.connect(); + await this.login(this.configService.get("token")); + } catch (error) { + logger.error(error); + } + } + + async getGuildData(guildId: string) { + const { default: GuildModel } = await import("@/models/GuildModel.js"); + const guildData = await this.adapter.findOneOrCreate(GuildModel, { id: guildId }); + + return guildData; + } + + async getUserData(userID: string) { + const { default: UserModel } = await import("@/models/UserModel.js"); + const userData = await this.adapter.findOneOrCreate(UserModel, { id: userID }); + + return userData; + } + + async getMemberData(memberId: string, guildID: string) { + const { default: MemberModel } = await import("@/models/MemberModel.js"); + const memberData = await this.adapter.findOneOrCreate(MemberModel, { id: memberId, guildID }); + + const guildData = await this.getGuildData(guildID); + + guildData.members.push(memberData.id); + await guildData.save(); + + return memberData; + } +} diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 00000000..2ff3cfd0 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,60 @@ +import { + AutocompleteInteraction, + CacheType, + ChatInputCommandInteraction, + ContextMenuCommandInteraction, + Interaction, + MessageContextMenuCommandInteraction, + PermissionsString, + RESTPostAPIApplicationCommandsJSONBody, + UserContextMenuCommandInteraction, +} from "discord.js"; +import { ExtendedClient } from "./structures/client.ts"; +import { UserReminds } from "./models/UserModel.ts"; + +export type CommandData = RESTPostAPIApplicationCommandsJSONBody; + +export type cacheRemindsData = { + id: string; + reminds: UserReminds[]; +}; + +export type BuiltInValidationParams = { + targetCommand: CommandFileObject; + interaction: Interaction; + client: ExtendedClient; +}; + +export type BuiltInValidation = ({}: BuiltInValidationParams) => boolean | void; + +export type CronTaskData = { + name: string; + schedule: string; + task: () => Promise | void; +}; + +export interface CommandProps { + interaction: ChatInputCommandInteraction | ContextMenuCommandInteraction | UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction | AutocompleteInteraction; + client: ExtendedClient; +} + +export interface SlashCommandProps extends CommandProps { + interaction: ChatInputCommandInteraction; +} + +export interface CommandContext<_T extends Interaction, _Cached extends CacheType> { + interaction: Interaction; + client: ExtendedClient; +} + +export interface CommandOptions { + devOnly?: boolean; + cooldown?: number; +} + +export interface CommandFileObject { + data: CommandData; + options?: CommandOptions; + run: (_ctx: CommandContext) => Awaited; + autocompleteRun?: (_ctx: CommandContext) => Awaited; +} diff --git a/src/utils/create-embed.ts b/src/utils/create-embed.ts new file mode 100644 index 00000000..b3abb6d8 --- /dev/null +++ b/src/utils/create-embed.ts @@ -0,0 +1,10 @@ +import { EmbedBuilder, EmbedData } from "discord.js"; +import useClient from "./use-client.js"; + +export const createEmbed = (data: EmbedData) => { + const client = useClient(); + return new EmbedBuilder({ + footer: typeof data.footer === "object" ? data.footer : data.footer ? { text: data.footer } : client.configService.get("embed.footer"), + ...data, + }).setColor(data.color || client.configService.get("embed.color")); +}; diff --git a/src/utils/get-path.ts b/src/utils/get-path.ts new file mode 100644 index 00000000..1cd47c30 --- /dev/null +++ b/src/utils/get-path.ts @@ -0,0 +1,26 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { PROJECT_ROOT } from "@/constants/index.js"; + +export const getFilePaths = async (directory: string, nesting: boolean) => { + let filePaths: string[] = []; + + const absoluteDirectory = path.isAbsolute(directory) ? directory : path.join(PROJECT_ROOT, directory); + + const files = await fs.readdir(absoluteDirectory, { withFileTypes: true }); + + for (const file of files) { + const filePath = path.join(directory, file.name); + + if (file.isFile()) { + filePaths.push(filePath); + } + + if (nesting && file.isDirectory()) { + const nestedFiles = await getFilePaths(filePath, true); + filePaths = [...filePaths, ...nestedFiles]; + } + } + + return filePaths; +}; diff --git a/src/utils/loadCronTasks.ts b/src/utils/loadCronTasks.ts new file mode 100644 index 00000000..45589fd3 --- /dev/null +++ b/src/utils/loadCronTasks.ts @@ -0,0 +1,42 @@ +import { CronTaskData } from "@/types.js"; +import logger from "../helpers/logger.js"; +import { getFilePaths } from "./get-path.js"; +import { toFileURL } from "./resolve-file.js"; + +const loadCronTasks = async (taskPath: string): Promise => { + try { + const filePaths = (await getFilePaths(taskPath, true)).filter(file => file.endsWith(".js") || file.endsWith(".ts")); + + const tasks = []; + + for (const filePath of filePaths) { + const { data } = (await import(toFileURL(filePath))) as { data: CronTaskData }; + + if (!data) continue; + + if (!data.name) { + logger.warn("No name found in task:", filePath); + continue; + } + + if (!data.schedule) { + logger.warn("No schedule found in task:", filePath); + continue; + } + + if (typeof data.task !== "function") { + logger.warn("Task is not a function:", filePath); + continue; + } + + tasks.push(data); + } + + return tasks; + } catch (error) { + logger.error("Error loading cron tasks:", error); + return []; + } +}; + +export default loadCronTasks; diff --git a/src/utils/resolve-file.ts b/src/utils/resolve-file.ts new file mode 100644 index 00000000..0b88d85b --- /dev/null +++ b/src/utils/resolve-file.ts @@ -0,0 +1,7 @@ +import path from "node:path"; +import { PROJECT_ROOT } from "@/constants/index.js"; + +export const toFileURL = (filePath: string) => { + const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(PROJECT_ROOT, filePath); + return "file://" + resolvedPath.replace(/\\\\|\\/g, "/"); +}; diff --git a/src/utils/use-client.ts b/src/utils/use-client.ts new file mode 100644 index 00000000..b44b8db8 --- /dev/null +++ b/src/utils/use-client.ts @@ -0,0 +1,9 @@ +import { SUPER_CONTEXT } from "@/constants/index.js"; + +export default function useClient() { + const store = SUPER_CONTEXT.getStore(); + if (!store) { + throw new Error("Client is not initialized. Please initialize it first."); + } + return store; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..606dd8cf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", // Версия ECMAScript + "module": "NodeNext", // Использование ES-модулей + "moduleResolution": "nodenext", // Разрешение модулей + "outDir": "./dist", // Директория для скомпилированных файлов + "rootDir": "./src", // Директория с исходным кодом + "strict": true, // Включение строгой проверки типов + "esModuleInterop": true, // Для совместимости с CommonJS + "skipLibCheck": true, // Пропуск проверки типов в библиотеках + "forceConsistentCasingInFileNames": true, // Единообразие в именах файлов + "resolveJsonModule": true, // Разрешение импорта JSON-файлов + "isolatedModules": true, // Изолированные модули + "incremental": true, // Инкрементальная сборка (ускоряет повторную компиляцию, сохраняя результаты предыдущих компиляции) + "noEmitOnError": true, // Чтобы не билдил без ошибок + "noUnusedLocals": true, // Не использовать неиспользуемые локальные переменные + "noUnusedParameters": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*"], // Включаемые файлы + "exclude": ["node_modules"] // Исключаемые файлы +}