├── .eslintignore ├── .gitattributes ├── assets ├── eres-crop.png ├── canva │ ├── online.png │ ├── background.png │ ├── coins-asset.png │ ├── level-background.png │ ├── profile-background.png │ └── reputation-asset.png ├── eres-original.png ├── emojis │ ├── red_emoji.png │ ├── flag_emoji.png │ ├── gheart_emoji.png │ ├── green_emoji.png │ ├── heart_emoji.png │ ├── house1_badge.png │ ├── house2_badge.png │ ├── house3_badge.png │ ├── mail_emoji.png │ ├── moon_emoji.png │ ├── music_emoji.png │ ├── owner_emoji.png │ ├── seven_emoji.png │ ├── skull_emoji.png │ ├── staff_badge.png │ ├── star_emoji.png │ ├── system_badge.png │ ├── ticket_emoji.png │ ├── verify_emoji.png │ ├── balance_emoji.png │ ├── partner_badge.png │ ├── partner_emoji.png │ ├── snowman_emoji.png │ ├── hypesquad_badge.png │ ├── verified_bot_badge.png │ ├── active_developer_badge.png │ ├── bughunter_level1_badge.png │ ├── bughunter_level2_badge.png │ ├── early_supporter_badge.png │ ├── verified_developer_badge.png │ └── certified_moderator_badge.png ├── eres-transparent.png ├── github │ ├── analytics.png │ ├── dashboard.png │ ├── eres-widget.png │ ├── id-analytics.png │ └── kofi_button_black.png └── json │ ├── fish.json │ └── hunt.json ├── public ├── static │ ├── Logo.png │ ├── romania.png │ ├── dashboard.png │ ├── eres-crop.png │ ├── coins-asset.png │ ├── united-states.png │ ├── eres-transparent.png │ └── reputation-asset.png └── transcripts │ └── default.html ├── postcss.config.js ├── tailwind.config.js ├── database ├── analyticsModel.js ├── serverModel.js ├── userModel.js └── manager.js ├── events ├── client │ ├── error.js │ └── ready.js ├── player │ ├── connectionError.js │ ├── botDisconnect.js │ ├── error.js │ ├── playerStart.js │ └── channelEmpty.js └── guild │ ├── guildDelete.js │ ├── guildCreate.js │ ├── messageCreate.js │ ├── guildMemberRemove.js │ ├── guildMemberAdd.js │ └── interactionCreate.js ├── structures ├── EventClass.js ├── CommandClass.js └── Client.js ├── middlewares └── checkAuth.js ├── views ├── 404.ejs ├── tos.ejs ├── dashboard │ ├── servers.ejs │ ├── stats.ejs │ └── manage.ejs ├── release.ejs ├── privacy.ejs ├── profile │ └── me.ejs ├── partials │ └── footer.ejs └── stats.ejs ├── functions ├── Player.js ├── Languages.js └── Emojis.js ├── ANALYTICS.md ├── commands ├── developer │ ├── drive.js │ ├── test.js │ ├── eval.js │ ├── database.js │ └── analytics.js ├── images │ ├── ass.js │ ├── gifs.js │ ├── 4k.js │ ├── pussy.js │ ├── tits.js │ ├── condom.js │ ├── creampie.js │ ├── dog.js │ ├── cat.js │ └── imagine.js ├── profile │ ├── about.js │ ├── rep.js │ └── profile.js ├── levels │ ├── xp.js │ ├── leaderboard.js │ └── rank.js ├── player │ ├── stop.js │ ├── resume.js │ ├── pause.js │ ├── shuffle.js │ ├── nowplaying.js │ ├── skip.js │ ├── queue.js │ └── play.js ├── economy │ ├── balance.js │ ├── daily.js │ ├── fish.js │ ├── hunt.js │ └── transfer.js ├── general │ ├── ping.js │ ├── avatar.js │ └── commands.js ├── utils │ ├── plugins.js │ ├── clear.js │ ├── transcript.js │ └── server.js └── app │ ├── commits.js │ ├── bug.js │ └── process.js ├── bot.js ├── handler ├── Event.js └── Command.js ├── deploy.js ├── .eslintrc.js ├── package.json ├── .gitignore ├── sitemap.js ├── server.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /assets/eres-crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/eres-crop.png -------------------------------------------------------------------------------- /public/static/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/public/static/Logo.png -------------------------------------------------------------------------------- /assets/canva/online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/canva/online.png -------------------------------------------------------------------------------- /assets/eres-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/eres-original.png -------------------------------------------------------------------------------- /public/static/romania.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/public/static/romania.png -------------------------------------------------------------------------------- /assets/canva/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/canva/background.png -------------------------------------------------------------------------------- /assets/emojis/red_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/red_emoji.png -------------------------------------------------------------------------------- /assets/eres-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/eres-transparent.png -------------------------------------------------------------------------------- /assets/github/analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/github/analytics.png -------------------------------------------------------------------------------- /assets/github/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/github/dashboard.png -------------------------------------------------------------------------------- /public/static/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/public/static/dashboard.png -------------------------------------------------------------------------------- /public/static/eres-crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/public/static/eres-crop.png -------------------------------------------------------------------------------- /assets/canva/coins-asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/canva/coins-asset.png -------------------------------------------------------------------------------- /assets/emojis/flag_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/flag_emoji.png -------------------------------------------------------------------------------- /assets/emojis/gheart_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/gheart_emoji.png -------------------------------------------------------------------------------- /assets/emojis/green_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/green_emoji.png -------------------------------------------------------------------------------- /assets/emojis/heart_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/heart_emoji.png -------------------------------------------------------------------------------- /assets/emojis/house1_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/house1_badge.png -------------------------------------------------------------------------------- /assets/emojis/house2_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/house2_badge.png -------------------------------------------------------------------------------- /assets/emojis/house3_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/house3_badge.png -------------------------------------------------------------------------------- /assets/emojis/mail_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/mail_emoji.png -------------------------------------------------------------------------------- /assets/emojis/moon_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/moon_emoji.png -------------------------------------------------------------------------------- /assets/emojis/music_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/music_emoji.png -------------------------------------------------------------------------------- /assets/emojis/owner_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/owner_emoji.png -------------------------------------------------------------------------------- /assets/emojis/seven_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/seven_emoji.png -------------------------------------------------------------------------------- /assets/emojis/skull_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/skull_emoji.png -------------------------------------------------------------------------------- /assets/emojis/staff_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/staff_badge.png -------------------------------------------------------------------------------- /assets/emojis/star_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/star_emoji.png -------------------------------------------------------------------------------- /assets/emojis/system_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/system_badge.png -------------------------------------------------------------------------------- /assets/emojis/ticket_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/ticket_emoji.png -------------------------------------------------------------------------------- /assets/emojis/verify_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/verify_emoji.png -------------------------------------------------------------------------------- /assets/github/eres-widget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/github/eres-widget.png -------------------------------------------------------------------------------- /assets/github/id-analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/github/id-analytics.png -------------------------------------------------------------------------------- /public/static/coins-asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/public/static/coins-asset.png -------------------------------------------------------------------------------- /assets/emojis/balance_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/balance_emoji.png -------------------------------------------------------------------------------- /assets/emojis/partner_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/partner_badge.png -------------------------------------------------------------------------------- /assets/emojis/partner_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/partner_emoji.png -------------------------------------------------------------------------------- /assets/emojis/snowman_emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/snowman_emoji.png -------------------------------------------------------------------------------- /public/static/united-states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/public/static/united-states.png -------------------------------------------------------------------------------- /assets/canva/level-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/canva/level-background.png -------------------------------------------------------------------------------- /assets/canva/profile-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/canva/profile-background.png -------------------------------------------------------------------------------- /assets/canva/reputation-asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/canva/reputation-asset.png -------------------------------------------------------------------------------- /assets/emojis/hypesquad_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/hypesquad_badge.png -------------------------------------------------------------------------------- /assets/github/kofi_button_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/github/kofi_button_black.png -------------------------------------------------------------------------------- /public/static/eres-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/public/static/eres-transparent.png -------------------------------------------------------------------------------- /public/static/reputation-asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/public/static/reputation-asset.png -------------------------------------------------------------------------------- /assets/emojis/verified_bot_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/verified_bot_badge.png -------------------------------------------------------------------------------- /assets/emojis/active_developer_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/active_developer_badge.png -------------------------------------------------------------------------------- /assets/emojis/bughunter_level1_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/bughunter_level1_badge.png -------------------------------------------------------------------------------- /assets/emojis/bughunter_level2_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/bughunter_level2_badge.png -------------------------------------------------------------------------------- /assets/emojis/early_supporter_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/early_supporter_badge.png -------------------------------------------------------------------------------- /assets/emojis/verified_developer_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/verified_developer_badge.png -------------------------------------------------------------------------------- /assets/emojis/certified_moderator_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skillzl/eres/HEAD/assets/emojis/certified_moderator_badge.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require ('tailwindcss'), 4 | require ('autoprefixer'), 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /public/transcripts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | test page 4 | 5 | 6 |

Test

7 |

Just a test page.

8 | 9 | 10 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./views/*.ejs'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [ 8 | { 9 | tailwindcss: {}, 10 | autoprefixer: {}, 11 | }, 12 | ], 13 | }; -------------------------------------------------------------------------------- /database/analyticsModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const analyticsSchema = new mongoose.Schema({ 4 | commands_used: { type: Number, default: 0 }, 5 | guilds: { type: Number, default: 0 }, 6 | users: { type: Number, default: 0 }, 7 | reports : { type: Number, default: 0 }, 8 | songs_played : { type: Number, default: 0 }, 9 | }); 10 | 11 | module.exports = mongoose.model('analytics', analyticsSchema); -------------------------------------------------------------------------------- /database/serverModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const serverSchema = new mongoose.Schema({ 4 | serverId: { type: String, required: true }, 5 | autorole: { type: String, default: null }, 6 | djrole : { type: String, default: null }, 7 | welcome: { type: String, default: null }, 8 | leave: { type: String, default: null }, 9 | i18n: { type: String, default: 'en', required: true }, 10 | }); 11 | 12 | module.exports = mongoose.model('server', serverSchema); -------------------------------------------------------------------------------- /assets/json/fish.json: -------------------------------------------------------------------------------- 1 | { 2 | "junk": { 3 | "symbol": ["🔋 (junk)"], 4 | "max": 50, 5 | "min": 1 6 | }, 7 | "common": { 8 | "symbol": ["🐟 (common)"], 9 | "max": 100, 10 | "min": 50 11 | }, 12 | "uncommon": { 13 | "symbol": ["🐡 (uncommon)"], 14 | "max": 300, 15 | "min": 150 16 | }, 17 | "rare": { 18 | "symbol": ["🦈 (rare)"], 19 | "max": 950, 20 | "min": 500 21 | }, 22 | "legendary": { 23 | "symbol": ["🐠 (legendary)"], 24 | "max": 2500, 25 | "min": 1000 26 | } 27 | } -------------------------------------------------------------------------------- /assets/json/hunt.json: -------------------------------------------------------------------------------- 1 | { 2 | "junk": { 3 | "symbol": ["🐰 (junk)"], 4 | "max": 50, 5 | "min": 1 6 | }, 7 | "common": { 8 | "symbol": ["🐺 (common)"], 9 | "max": 100, 10 | "min": 50 11 | }, 12 | "uncommon": { 13 | "symbol": ["🐖 (uncommon)"], 14 | "max": 300, 15 | "min": 150 16 | }, 17 | "rare": { 18 | "symbol": ["🐃 (rare)"], 19 | "max": 950, 20 | "min": 500 21 | }, 22 | "legendary": { 23 | "symbol": ["🐂 (legendary)"], 24 | "max": 2500, 25 | "min": 1000 26 | } 27 | } -------------------------------------------------------------------------------- /events/client/error.js: -------------------------------------------------------------------------------- 1 | const Event = require('../../structures/EventClass'); 2 | 3 | module.exports = class ErrorEvent extends Event { 4 | constructor(client) { 5 | super(client, { 6 | name: 'error', 7 | once: false, 8 | }); 9 | } 10 | /** 11 | * Runs the error handler. 12 | * 13 | * @param {Error} error - The error object. 14 | */ 15 | async run(error) { 16 | if (error.message.includes('Missing')) { 17 | return; 18 | } 19 | else { 20 | console.log(`[Error]: An error 🔴 occurred at (${new Date().toISOString()}): ${error.message}`); 21 | } 22 | } 23 | }; -------------------------------------------------------------------------------- /events/player/connectionError.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'playerError', 3 | /** 4 | * Executes the given queue and handles any errors that occur. 5 | * @param {Queue} queue - The queue to execute. 6 | * @param {Error} error - The error that occurred. 7 | */ 8 | async execute(queue, error) { 9 | console.log(error.message); 10 | 11 | try { 12 | queue.delete(); 13 | } 14 | catch (err) { 15 | console.log(`[Player]: An error 🔴 occurred at (${new Date().toISOString()}): ${err.message}`); 16 | } 17 | 18 | queue.metadata.channel.send({ content: 'A player error occurred whilst attempting to perform this action.' }); 19 | return; 20 | }, 21 | }; -------------------------------------------------------------------------------- /database/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const userSchema = new mongoose.Schema({ 4 | userId: { type: String }, 5 | about: { type: String, default: 'mysterious person' }, 6 | balance: { type: Number, default: 0 }, 7 | reputation: { type: Number, default: 0 }, 8 | xp: { type: Number, default: 0 }, 9 | reputation_cooldown: { type: Number, default: null }, 10 | daily_cooldown: { type: Number, default: null }, 11 | fish_cooldown: { type: Number, default: null }, 12 | hunt_cooldown: { type: Number, default: null }, 13 | report_cooldown: { type: Number, default: null }, 14 | }); 15 | 16 | module.exports = mongoose.model('user', userSchema); -------------------------------------------------------------------------------- /events/player/botDisconnect.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'disconnect', 3 | /** 4 | * Stops the music playback and sends a message to the channel. 5 | * 6 | * @param {Queue} queue - The queue object. 7 | */ 8 | async execute(queue) { 9 | try { 10 | // Delete the queue. 11 | queue.delete(); 12 | } 13 | catch (err) { 14 | // Log the error message with a timestamp. 15 | console.log(`[Player]: An error 🔴 occurred at (${new Date().toISOString()}): ${err.message}`); 16 | } 17 | 18 | // Send a message to the channel indicating that the music was stopped. 19 | queue.metadata.channel.send({ content: 'The music was stopped because I was disconnected from the channel.' }); 20 | }, 21 | }; -------------------------------------------------------------------------------- /events/guild/guildDelete.js: -------------------------------------------------------------------------------- 1 | const Event = require('../../structures/EventClass'); 2 | const db = require('../../database/manager'); 3 | 4 | module.exports = class guildDelete extends Event { 5 | constructor() { 6 | super({ 7 | name: 'guildDelete', once: false, 8 | }); 9 | } 10 | /** 11 | * Remove server from the database if guild is not available 12 | * @param {Guild} guild - The guild object 13 | */ 14 | async run(guild) { 15 | // Check if the guild is available 16 | if (guild.available) { 17 | return; 18 | } 19 | 20 | // Remove the server from the database 21 | await db.removeServer(guild.id); 22 | 23 | // Log the information 24 | console.log(`[Info]: Left guild: ${guild.name} ⬇️.`); 25 | } 26 | }; -------------------------------------------------------------------------------- /events/guild/guildCreate.js: -------------------------------------------------------------------------------- 1 | const Event = require('../../structures/EventClass'); 2 | const db = require('../../database/manager'); 3 | 4 | module.exports = class guildCreate extends Event { 5 | constructor() { 6 | super({ 7 | name: 'guildCreate', once: false, 8 | }); 9 | } 10 | /** 11 | * Runs the provided guild. 12 | * 13 | * @param {Guild} guild - The guild object. 14 | */ 15 | async run(guild) { 16 | // Check if the guild is available 17 | if (!guild.available) { 18 | return; 19 | } 20 | 21 | // Create a server in the database for the guild 22 | await db.createServer(guild.id); 23 | 24 | // Log a message indicating the guild has been joined 25 | console.log(`[Info]: Joined guild: ${guild.name} ⬆️.`); 26 | } 27 | }; -------------------------------------------------------------------------------- /structures/EventClass.js: -------------------------------------------------------------------------------- 1 | module.exports = class Event { 2 | /** 3 | * Represents a constructor for a class. 4 | * @param {object} client - The client object. 5 | * @param {object} options - The options for the constructor. 6 | * @param {string} options.name - The name option. 7 | * @param {boolean} options.raw - The raw option. 8 | * @param {boolean} options.once - The once option. 9 | */ 10 | constructor(client, options = {}) { 11 | this.client = client; 12 | this.name = options.name; 13 | this.raw = options.raw || false; 14 | this.once = options.once || false; 15 | } 16 | 17 | /** 18 | * Runs the event. 19 | * 20 | * @throws {Error} If the event does not provide a run method. 21 | */ 22 | async run() { 23 | throw new Error(`The Event "${this.name}" does not provide a run method.`); 24 | } 25 | }; -------------------------------------------------------------------------------- /middlewares/checkAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware function to check if the user is authenticated. 3 | * If the user is authenticated, call the next middleware function. 4 | * If the user is not authenticated, save the current URL in the session and redirect to the login page. 5 | * 6 | * @param {Object} req - The request object 7 | * @param {Object} res - The response object 8 | * @param {Function} next - The next middleware function 9 | */ 10 | module.exports = async (req, res, next) => { 11 | // Check if the user is authenticated 12 | if (req.isAuthenticated()) { 13 | // If authenticated, call the next middleware function 14 | return next(); 15 | } 16 | 17 | // If not authenticated, save the current URL in the session 18 | req.session.backURL = req.url; 19 | 20 | // Redirect to the login page 21 | res.redirect('/login'); 22 | }; 23 | -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 | <%- include("partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - 404 page 4 | 5 |
6 |
7 |

404

8 |

<%- __('WEB.404')%>

9 | 10 |
11 |
12 | 13 | <%- include("partials/footer") %> -------------------------------------------------------------------------------- /events/player/error.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'error', 3 | /** 4 | * Executes the given queue and handles any errors that occur. 5 | * @param {Queue} queue - The queue to execute. 6 | * @param {Error} error - The error that occurred. 7 | */ 8 | async execute(queue, error) { 9 | // Log the error message with the current date and time 10 | console.log(`[Player]: An error 🔴 occurred at (${new Date().toISOString()}): ${error.message}`); 11 | 12 | try { 13 | // Delete the queue 14 | queue.delete(); 15 | } 16 | catch (err) { 17 | // Log the error message with the current date and time if there is an error deleting the queue 18 | console.log(`[Player]: An error 🔴 occurred at (${new Date().toISOString()}): ${err.message}`); 19 | } 20 | 21 | // Send a message to the queue's metadata channel indicating that an error occurred 22 | queue.metadata.channel.send({ content: 'An error occurred whilst attempting to perform this action. This media may not be supported.' }); 23 | 24 | // Return from the function 25 | return; 26 | }, 27 | }; -------------------------------------------------------------------------------- /functions/Player.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const { Player } = require('discord-player'); 5 | 6 | /** 7 | * Registers player events for the client. 8 | * @param {Client} client - The client object. 9 | */ 10 | module.exports = (client) => { 11 | client.events.player = async () => { 12 | // Define the path to the directory containing player events 13 | const playerEventsPath = path.join(__dirname, '../events/player'); 14 | 15 | // Get the list of player event files in the directory 16 | const playerEventFiles = fs.readdirSync(playerEventsPath).filter((file) => file.endsWith('.js')); 17 | 18 | // Get the singleton instance of the Player class 19 | const player = Player.singleton(); 20 | 21 | // Iterate over each player event file 22 | for (const file of playerEventFiles) { 23 | // Get the path to the current event file 24 | const filePath = path.join(playerEventsPath, file); 25 | 26 | // Require the event file 27 | const event = require(filePath); 28 | 29 | // Register the event listener for the player event 30 | player.events.on(event.name, (...args) => event.execute(...args, client)); 31 | } 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /events/player/playerStart.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder } = require('discord.js'); 2 | 3 | const db = require('../../database/manager'); 4 | 5 | module.exports = { 6 | name: 'playerStart', 7 | /** 8 | * Executes the given track in the specified queue. 9 | * 10 | * @param {Queue} queue - The queue to execute the track in. 11 | * @param {Track} track - The track to be executed. 12 | * @returns {Promise} - A promise that resolves once the track has been executed. 13 | */ 14 | async execute(queue, track, client) { 15 | // Increment the number of songs played in the database 16 | db.incrementSongsPlayed(); 17 | 18 | // Create an embed with the track information and send it to the channel of the queue's metadata 19 | const embed = new EmbedBuilder() 20 | .setColor(0x2F3136) 21 | .setTitle(`${client.emoji.music} Now Playing`) 22 | .setDescription(`Song: **[${track.title}](${track.url})** by **${track.author}**.`); 23 | 24 | // If the track has a thumbnail, set it as the thumbnail of the embed 25 | if (track.thumbnail) { 26 | embed.setThumbnail(track.thumbnail); 27 | } 28 | 29 | // Send the embed to the channel of the queue's metadata 30 | queue.metadata.channel.send({ embeds: [embed] }); 31 | }, 32 | }; -------------------------------------------------------------------------------- /structures/CommandClass.js: -------------------------------------------------------------------------------- 1 | module.exports = class Command { 2 | /** 3 | * Creates a new instance of the API class. 4 | * @param {object} client - The client object. 5 | * @param {object} meta - The metadata object. 6 | * @param {object} meta.data - The data object. 7 | * @param {string|null} meta.contextDescription - The description of the context. 8 | * @param {string} meta.usage - The usage of the API. 9 | * @param {string} [meta.category='Info'] - The category of the API. 10 | * @param {string[]} [meta.permissions=['Use Application Commands', 'Send Messages', 'Embed Links']] - The permissions required for the API. 11 | */ 12 | constructor(client, meta = {}) { 13 | this.client = client; 14 | this.data = meta.data; 15 | this.contextDescription = meta.contextDescription || null; 16 | this.usage = meta.usage || this.name; 17 | this.category = meta.category || 'Info'; 18 | this.permissions = meta.permissions || ['Use Application Commands', 'Send Messages', 'Embed Links']; 19 | } 20 | 21 | /** 22 | * Runs the Slash Command. 23 | * @throws {Error} Throws an error if the Slash Command does not provide a run method. 24 | */ 25 | async run() { 26 | throw new Error(`The Slash Command "${this.name}" does not provide a run method.`); 27 | } 28 | }; -------------------------------------------------------------------------------- /ANALYTICS.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ### 📂 Content 6 | 7 | - [Tutorial Analytics](#-tutorial-analytics) 8 | - [Unique Identifier ](#-unique-identifier) 9 | 10 | ## ❓ Tutorial Analytics 11 | **How to set the analytics unique identifier?** 12 | 13 | First of all, build the bot, deploy the slash-commands and actually use the bot, try to run a slash-command so the analytics collection will be created. 14 | 15 | > **Note**: Without running any command, the analytics mongodb collection will not be created. 16 | 17 |
18 | 19 | After that use the MongoDB Database Compass to get your `_id`. 20 | **[MongoDB Compass Download (GUI)](https://www.mongodb.com/try/download/compass)** 21 | 22 | ## 🔑 Unique Identifier 23 | To use the **MongoDB Compass (GIU)** you need to login with your **mongodb** uri link. 24 | 25 | After you are logged choose your database (default one is `test`) and search the `analytics` collection. 26 | 27 | Open it and then you will see a document where you find your `_id`. (first row) 28 | 29 | **Screenshot:** 30 | 31 | 32 | 33 | > **Hint**: There it is! **656a1c4e7459779a005544ef** is the `_id` you need to copy and put in your .env file. 34 | -------------------------------------------------------------------------------- /commands/developer/drive.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Drive extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('drive') 10 | .setDescription('Drive access to the API 💹') 11 | .setDMPermission(false), 12 | usage: 'drive', 13 | category: 'Developer', 14 | permissions: ['Use Application Commands', 'Send Messages'], 15 | }); 16 | } 17 | /** 18 | * Runs the function. 19 | * 20 | * @param {Client} client - The client object. 21 | * @param {Interaction} interaction - The interaction object. 22 | * @return {Promise} - A promise that resolves when the function is finished. 23 | */ 24 | async run(client, interaction) { 25 | // Fetch a random drive image from the API 26 | const result = await fetch(`https://api.eres.lol/images/drive?token=${process.env.API_ERES_KEY}`).then((res) => res.json()); 27 | 28 | // Create an embed with the image 29 | const embed = new EmbedBuilder() 30 | .setColor(0x2B2D31) 31 | .setTitle(`${result.category} 🌸`) 32 | .setURL(result.url) 33 | .setImage(result.url) 34 | .setFooter({ 35 | text: result.fileName, 36 | }); 37 | 38 | // Send the embed as a reply to the interaction 39 | await interaction.reply({ embeds: [embed] }); 40 | } 41 | }; -------------------------------------------------------------------------------- /commands/images/ass.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Ass extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('ass') 10 | .setDescription('Sends a random 🍑 image') 11 | .setDMPermission(false) 12 | .setNSFW(true), 13 | usage: 'ass', 14 | category: 'Images', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the function. 20 | * 21 | * @param {Client} client - The client object. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @return {Promise} - A promise that resolves when the function is finished. 24 | */ 25 | async run(client, interaction) { 26 | // Fetch a random 🍑 image from the API 27 | const result = await fetch(`https://api.eres.lol/images/ass?token=${process.env.API_ERES_KEY}`).then((res) => res.json()); 28 | 29 | // Create an embed with the 🍑 image 30 | const embed = new EmbedBuilder() 31 | .setColor(0x2B2D31) 32 | .setTitle(`${result.category} 🍑`) 33 | .setURL(result.url) 34 | .setImage(result.url) 35 | .setFooter({ 36 | text: 'api.eres.lol', 37 | }); 38 | 39 | // Send the embed as a reply to the interaction 40 | await interaction.reply({ embeds: [embed] }); 41 | } 42 | }; -------------------------------------------------------------------------------- /commands/images/gifs.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Gifs extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('gifs') 10 | .setDescription('Sends a random 🔞 gif ') 11 | .setDMPermission(false) 12 | .setNSFW(true), 13 | usage: 'gifs', 14 | category: 'Images', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the function. 20 | * 21 | * @param {Client} client - The client object. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @return {Promise} - A promise that resolves when the function is finished. 24 | */ 25 | async run(client, interaction) { 26 | // Fetch a random 🔞 gif from the API 27 | const result = await fetch(`https://api.eres.lol/images/gifs?token=${process.env.API_ERES_KEY}`).then((res) => res.json()); 28 | 29 | // Create an embed with the 🔞 gif 30 | const embed = new EmbedBuilder() 31 | .setColor(0x2B2D31) 32 | .setTitle(`${result.category} 🔞`) 33 | .setURL(result.url) 34 | .setImage(result.url) 35 | .setFooter({ 36 | text: 'api.eres.lol', 37 | }); 38 | 39 | // Send the embed as a reply to the interaction 40 | await interaction.reply({ embeds: [embed] }); 41 | } 42 | }; -------------------------------------------------------------------------------- /commands/images/4k.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class FourK extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('4k') 10 | .setDescription('Sends a random 4k 🔞 image') 11 | .setDMPermission(false) 12 | .setNSFW(true), 13 | usage: '4k', 14 | category: 'Images', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the function. 20 | * 21 | * @param {Client} client - The client object. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @return {Promise} - A promise that resolves when the function is finished. 24 | */ 25 | async run(client, interaction) { 26 | // Fetch a random 4k 🔞 image from the API 27 | const result = await fetch(`https://api.eres.lol/images/4k?token=${process.env.API_ERES_KEY}`).then((res) => res.json()); 28 | 29 | // Create an embed with the 🔞 image 30 | const embed = new EmbedBuilder() 31 | .setColor(0x2B2D31) 32 | .setTitle(`${result.category} 🔞`) 33 | .setURL(result.url) 34 | .setImage(result.url) 35 | .setFooter({ 36 | text: 'api.eres.lol', 37 | }); 38 | 39 | // Send the embed as a reply to the interaction 40 | await interaction.reply({ embeds: [embed] }); 41 | } 42 | }; -------------------------------------------------------------------------------- /commands/images/pussy.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Pussy extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('pussy') 10 | .setDescription('Sends a random 🌸 image') 11 | .setDMPermission(false) 12 | .setNSFW(true), 13 | usage: 'pussy', 14 | category: 'Images', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the function. 20 | * 21 | * @param {Client} client - The client object. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @return {Promise} - A promise that resolves when the function is finished. 24 | */ 25 | async run(client, interaction) { 26 | // Fetch a random 🌸 image from the API 27 | const result = await fetch(`https://api.eres.lol/images/pussy?token=${process.env.API_ERES_KEY}`).then((res) => res.json()); 28 | 29 | // Create an embed with the 🌸 image 30 | const embed = new EmbedBuilder() 31 | .setColor(0x2B2D31) 32 | .setTitle(`${result.category} 🌸`) 33 | .setURL(result.url) 34 | .setImage(result.url) 35 | .setFooter({ 36 | text: 'api.eres.lol', 37 | }); 38 | 39 | // Send the embed as a reply to the interaction 40 | await interaction.reply({ embeds: [embed] }); 41 | } 42 | }; -------------------------------------------------------------------------------- /views/tos.ejs: -------------------------------------------------------------------------------- 1 | <%- include("partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - terms of service 4 | 5 |
6 |
7 |

<%- __('WEB.TOS_TITLE')%>

8 | 9 | eres.lol logo 10 | 11 |

<%- __('WEB.TOS_ACCEPTING')%>

12 |

<%- __('WEB.TOS_ACCEPTING_SUB')%>

13 |

<%- __('WEB.TOS_LICENSE')%>

14 |

<%- __('WEB.TOS_LICENSE_SUB')%>

15 |

<%- __('WEB.TOS_LIMITATIONS')%>

16 |

<%- __('WEB.TOS_LIMITATIONS_SUB')%>

17 |
<%- __('WEB.TOS_ERRATA')%>
18 |

<%- __('WEB.TOS_ERRATA_SUB')%>

19 |
<%- __('WEB.TOS_CONTRIBUTION')%>
20 |

<%- __('WEB.TOS_CONTRIBUTION_SUB')%>

21 |

<%- __('WEB.TOS_LINKS')%>

22 |

<%- __('WEB.TOS_LINKS_SUB')%>

23 |

<%- __('WEB.TOS_MODIFICATIONS')%>

24 |

<%- __('WEB.TOS_MODIFICATIONS_SUB')%>

25 |
26 |
27 | 28 | <%- include("partials/footer") %> 29 | -------------------------------------------------------------------------------- /commands/images/tits.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Tits extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('tits') 10 | .setDescription('Sends a random 🍒 image') 11 | .setDMPermission(false) 12 | .setNSFW(true), 13 | usage: 'tits', 14 | category: 'Images', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the function. 20 | * 21 | * @param {Client} client - The client object. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @return {Promise} - A promise that resolves when the function is finished. 24 | */ 25 | async run(client, interaction) { 26 | // Fetch a random 🍒 image from the API 27 | const result = await fetch(`https://api.eres.lol/images/tits?token=${process.env.API_ERES_KEY}`).then((res) => res.json()); 28 | 29 | // Create an embed with the 🍒 image 30 | const embed = new EmbedBuilder() 31 | .setColor(0x2B2D31) 32 | .setTitle(`${result.category} 🍒`) 33 | .setURL(result.url) 34 | .setImage(result.url) 35 | .setFooter({ 36 | text: 'api.eres.lol', 37 | }); 38 | 39 | // Send the embed as a reply to the interaction 40 | await interaction.reply({ embeds: [embed], emphored: true }); 41 | } 42 | }; -------------------------------------------------------------------------------- /commands/images/condom.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Condom extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('condom') 10 | .setDescription('Sends a random condom 🍆 image') 11 | .setDMPermission(false) 12 | .setNSFW(true), 13 | usage: 'condom', 14 | category: 'Images', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the function. 20 | * 21 | * @param {Client} client - The client object. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @return {Promise} - A promise that resolves when the function is finished. 24 | */ 25 | async run(client, interaction) { 26 | // Fetch a random condom 🍆 image from the API 27 | const result = await fetch(`https://api.eres.lol/images/condom?token=${process.env.API_ERES_KEY}`).then((res) => res.json()); 28 | 29 | // Create an embed with the 🔞 image 30 | const embed = new EmbedBuilder() 31 | .setColor(0x2B2D31) 32 | .setTitle(`${result.category} 🍆`) 33 | .setURL(result.url) 34 | .setImage(result.url) 35 | .setFooter({ 36 | text: 'api.eres.lol', 37 | }); 38 | 39 | // Send the embed as a reply to the interaction 40 | await interaction.reply({ embeds: [embed] }); 41 | } 42 | }; -------------------------------------------------------------------------------- /commands/images/creampie.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Creampie extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('creampie') 10 | .setDescription('Sends a random creampie 💦 image') 11 | .setDMPermission(false) 12 | .setNSFW(true), 13 | usage: '4k', 14 | category: 'Images', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the function. 20 | * 21 | * @param {Client} client - The client object. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @return {Promise} - A promise that resolves when the function is finished. 24 | */ 25 | async run(client, interaction) { 26 | // Fetch a random creampie 💦 image from the API 27 | const result = await fetch(`https://api.eres.lol/images/creampie?token=${process.env.API_ERES_KEY}`).then((res) => res.json()); 28 | 29 | // Create an embed with the 💦 image 30 | const embed = new EmbedBuilder() 31 | .setColor(0x2B2D31) 32 | .setTitle(`${result.category} 💦`) 33 | .setURL(result.url) 34 | .setImage(result.url) 35 | .setFooter({ 36 | text: 'api.eres.lol', 37 | }); 38 | 39 | // Send the embed as a reply to the interaction 40 | await interaction.reply({ embeds: [embed] }); 41 | } 42 | }; -------------------------------------------------------------------------------- /commands/profile/about.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const { SlashCommandBuilder } = require('discord.js'); 5 | 6 | module.exports = class About extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('about') 11 | .setDescription('Customize your profile, by setting a new about me text') 12 | .addStringOption(option => 13 | option.setName('input') 14 | .setDescription('Your new profile\'s description') 15 | .setRequired(true)) 16 | .setDMPermission(false), 17 | usage: 'about [string]', 18 | category: 'Profile', 19 | permissions: ['Use Application Commands', 'Send Messages'], 20 | }); 21 | } 22 | /** 23 | * Updates the user's personal bio in the database. 24 | * @param {Client} client - The Discord client object. 25 | * @param {Interaction} interaction - The interaction object. 26 | */ 27 | async run(client, interaction) { 28 | // Get the input from the interaction options 29 | const input = interaction.options.getString('input'); 30 | 31 | if (input) { 32 | // Update the user's personal bio in the database 33 | db.updateUserById(interaction.user.id, { 34 | about: input, 35 | }); 36 | 37 | // Send a reply to indicate successful update 38 | await interaction.reply(`${client.emoji.green_emoji} Successfully updated your personal bio.`); 39 | } 40 | } 41 | }; -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const Client = require('./structures/Client'); 3 | const { Player } = require('discord-player'); 4 | const { SpotifyExtractor } = require('@discord-player/extractor'); 5 | const { YoutubeiExtractor } = require('discord-player-youtubei'); 6 | 7 | const fs = require('fs'); 8 | 9 | // Initialize client 10 | const client = new Client(); 11 | 12 | // Initialize player 13 | const player = new Player(client, { 14 | autoRegisterExtractor: false, 15 | ytdlOptions: { requestOptions: { 16 | headers: { cookie: process.env.YOUTUBE_COOKIE ? process.env.YOUTUBE_COOKIE : null, 17 | }, 18 | } }, 19 | }); 20 | 21 | // Register extractors to player 22 | player.extractors.register(YoutubeiExtractor); 23 | player.extractors.register(SpotifyExtractor); 24 | 25 | // Load functions from ./functions 26 | const functions = fs.readdirSync('./functions').filter((file) => file.endsWith('.js')); 27 | 28 | for (const file of functions) { 29 | require(`./functions/${file}`)(client); 30 | } 31 | 32 | // Initialize classes 33 | client.events.player(); 34 | client.emoji(); 35 | client.login(); 36 | 37 | // Handle process events 38 | process.on('uncaughtException', err => console.error(err.stack)); 39 | process.on('unhandledRejection', err => console.error(err.stack)); 40 | process.on('uncaughtExceptionMonitor', err => console.error(err.stack)); 41 | 42 | // Handle process exit 43 | process.on('beforeExit', code => console.log(code)); 44 | process.on('exit', code => console.log(code)); -------------------------------------------------------------------------------- /commands/levels/xp.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const { SlashCommandBuilder } = require('discord.js'); 5 | 6 | module.exports = class Xp extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('xp') 11 | .setDescription('Fetch the current xp points an user have') 12 | .addUserOption(option => option.setName('target').setDescription('The user') 13 | .setRequired(false)) 14 | .setDMPermission(false), 15 | usage: 'xp [user]', 16 | category: 'Levels', 17 | permissions: ['Use Application Commands', 'Send Messages'], 18 | }); 19 | } 20 | /** 21 | * Runs the command when invoked by a user. 22 | * 23 | * @param {Client} client - The Discord client instance. 24 | * @param {Interaction} interaction - The interaction object representing the command invocation. 25 | */ 26 | async run(client, interaction) { 27 | // Get the target user from the interaction options, or use the interaction user as the default 28 | const member = interaction.options.getUser('target') || interaction.user; 29 | 30 | // Retrieve the user data from the database based on the member's ID 31 | const { user } = await db.getUserById(member.id); 32 | 33 | // Reply to the interaction with the XP points earned by the member 34 | interaction.reply(`${client.emoji.star} ${member.username} has earned **${user.xp.toLocaleString()}** xp points.`); 35 | } 36 | }; -------------------------------------------------------------------------------- /commands/developer/test.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | 4 | module.exports = class Test extends Command { 5 | constructor(client) { 6 | super(client, { 7 | data: new SlashCommandBuilder() 8 | .setName('test') 9 | .setDescription('Test command') 10 | .setDMPermission(false), 11 | usage: 'test', 12 | category: 'Developer', 13 | permissions: ['Use Application Commands', 'Send Messages'], 14 | }); 15 | } 16 | /** 17 | * Runs the function asynchronously. 18 | * 19 | * @param {Client} client - The client object. 20 | * @param {Interaction} interaction - The interaction object. 21 | * @return {Promise} - Returns a Promise that resolves to nothing. 22 | */ 23 | async run(client, interaction) { 24 | // Check if the user is authorized to run the function 25 | if (interaction.user.id !== process.env.DEVELOPER_ID) { 26 | // If not authorized, reply with an error message 27 | return interaction.reply(`${client.emoji.red_emoji} Missing \`DEVELOPER\` permission.`); 28 | } 29 | 30 | // Get the i18n instance for the guild 31 | const i18n = await client.i18n.get(interaction.guild.id); 32 | 33 | // Reply to the interaction with the success message 34 | interaction.reply({ content: `${client.i18n.handle('GENERAL', 'TEST', i18n)}\n${client.emoji.green_emoji} ${client.i18n.handle('GLOBAL', 'SUCCESS', i18n) ? client.i18n.handle('GLOBAL', 'SUCCESS', i18n) : client.i18n.handle('GLOBAL', 'ERROR', i18n)}` }); 35 | } 36 | }; -------------------------------------------------------------------------------- /functions/Languages.js: -------------------------------------------------------------------------------- 1 | const Server = require('../database/serverModel'); 2 | 3 | /** 4 | * Retrieves the i18n data for the specified server. 5 | * If the server does not exist, returns the default language code. 6 | * @param {string} id - The server ID. 7 | * @returns {Promise} - The i18n data or default language code. 8 | */ 9 | async function get(id) { 10 | // Find the server with the specified ID 11 | const server = await Server.findOne({ serverId: id }); 12 | 13 | // If the server exists, return the i18n data 14 | // Otherwise, return the default language code ('en') 15 | return server ? server.i18n : 'en'; 16 | } 17 | 18 | /** 19 | * @param {string} category - The category of the translation 20 | * @param {string} identifier - The identifier of the translation 21 | * @param {string} language - The language code for the translation file 22 | * @returns {string} - The translated message 23 | */ 24 | function handle(category, identifier, language) { 25 | // Load the translation file for the given language 26 | const file = require(`../i18n/${language}.json`); 27 | 28 | // Retrieve the translation message based on the category and identifier 29 | return file[category][identifier]; 30 | } 31 | 32 | /** 33 | * Sets up the i18n module for the client. 34 | * The i18n module provides internationalization support. 35 | * 36 | * @param {Object} client - The client object. 37 | */ 38 | module.exports = (client) => { 39 | // Define the i18n object with the handle and get functions. 40 | client.i18n = { 41 | handle: handle, 42 | get: get, 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /handler/Event.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require('../structures/EventClass'); 2 | const path = require('path'); 3 | const fs = require('fs').promises; 4 | 5 | /** 6 | * Recursively builds events from the given directory and adds them to the client 7 | * @param {string} dir - The directory to build events from 8 | */ 9 | module.exports = class EventClass { 10 | constructor(client) { 11 | this.client = client; 12 | } 13 | 14 | async build(dir) { 15 | // Get the full file path of the directory 16 | const filePath = path.join(__dirname, dir); 17 | 18 | // Get the list of files in the directory 19 | const files = await fs.readdir(filePath); 20 | 21 | // Loop through each file 22 | for (const file of files) { 23 | // Get the file's stats 24 | const stat = await fs.lstat(path.join(filePath, file)); 25 | 26 | // If the file is a directory, recursively call the build function 27 | if (stat.isDirectory()) this.build(path.join(dir, file)); 28 | 29 | // If the file is a JavaScript file and it extends BaseEvent 30 | if (file.endsWith('.js')) { 31 | const Event = require(path.join(filePath, file)); 32 | 33 | // If the required file is a subclass of BaseEvent 34 | if (Event.prototype instanceof BaseEvent) { 35 | const event = new Event(this.client); 36 | 37 | // Add the event to the client events map 38 | this.client.events.set(event.name, event); 39 | 40 | // Add the event to the client listeners map 41 | event.once ? this.client.once(event.name, event.run.bind(event)) : this.client.on(event.name, event.run.bind(event)); 42 | } 43 | } 44 | } 45 | } 46 | }; -------------------------------------------------------------------------------- /commands/images/dog.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const fetch = require('node-fetch'); 4 | 5 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 6 | 7 | module.exports = class Dog extends Command { 8 | constructor(client) { 9 | super(client, { 10 | data: new SlashCommandBuilder() 11 | .setName('dog') 12 | .setDescription('Sends a random dog image') 13 | .setDMPermission(false), 14 | usage: 'dog', 15 | category: 'Images', 16 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 17 | }); 18 | } 19 | /** 20 | * Run the function 21 | * @param {Client} client - The client object 22 | * @param {Interaction} interaction - The interaction object 23 | */ 24 | async run(client, interaction) { 25 | // Check if SKILLZL_API_KEY is missing or empty in .env file 26 | if (!process.env.SKILLZL_API_KEY || process.env.SKILLZL_API_KEY === '') { 27 | return interaction.reply(`${client.emoji.red_emoji} Missing \`SKILLZL_API_KEY\` in .env file.`); 28 | } 29 | 30 | // Fetch a random dog image from the API 31 | const result = await fetch(`https://api.skillzl.dev/dog/?key=${process.env.SKILLZL_API_KEY}`).then((res) => res.json()); 32 | 33 | // Create an embed to display the dog image 34 | const embed = new EmbedBuilder() 35 | .setColor(0x2B2D31) 36 | .setTitle('dog 🐕') 37 | .setURL(result.url) 38 | .setImage(result.url) 39 | .setFooter({ 40 | text: 'api.skillzl.dev', 41 | }); 42 | 43 | // Reply to the interaction with the embed 44 | await interaction.reply({ embeds: [embed] }); 45 | } 46 | }; -------------------------------------------------------------------------------- /commands/player/stop.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | 4 | const { Player } = require('discord-player'); 5 | 6 | module.exports = class Stop extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('stop') 11 | .setDescription('Stops the current track and clears the queue') 12 | .setDMPermission(false), 13 | usage: 'stop', 14 | category: 'Player', 15 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 16 | }); 17 | } 18 | /** 19 | * Runs the command when a user interacts with it. 20 | * @param {Client} client - The Discord client. 21 | * @param {Interaction} interaction - The user interaction. 22 | */ 23 | async run(client, interaction) { 24 | // Get the singleton instance of the Player class 25 | const player = Player.singleton(); 26 | 27 | // Get the queue for the guild from the player nodes 28 | const queue = player.nodes.get(interaction.guild.id); 29 | 30 | // Check if there is a queue and if it is currently playing 31 | if (!queue || !queue.isPlaying()) { 32 | // Reply with a message indicating that there is no music currently playing 33 | return await interaction.reply(`${client.emoji.red_emoji} There isn't currently any music playing.`); 34 | } 35 | else { 36 | // Delete the queue 37 | queue.delete(); 38 | // Reply with a message indicating that the music has been stopped 39 | return await interaction.reply(`${client.emoji.green_emoji} The music has been stopped.`); 40 | } 41 | } 42 | }; -------------------------------------------------------------------------------- /commands/economy/balance.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const { SlashCommandBuilder } = require('discord.js'); 5 | 6 | module.exports = class Balance extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('balance') 11 | .setDescription('Customize your profile, by setting a new about me text') 12 | .addUserOption(option => option.setName('target').setDescription('The user') 13 | .setRequired(false)) 14 | .setDMPermission(false), 15 | usage: 'balance [user]', 16 | category: 'Economy', 17 | permissions: ['Use Application Commands', 'Send Messages'], 18 | }); 19 | } 20 | /** 21 | * Retrieves the user's balance asynchronously and sends a reply with the balance information. 22 | * 23 | * @param {Client} client - The client object representing the Discord bot. 24 | * @param {Interaction} interaction - The interaction object representing the command interaction. 25 | * @return {Promise} This function does not return anything. 26 | */ 27 | async run(client, interaction) { 28 | // Get the target user from the interaction options, or use the interaction user as the default 29 | const member = interaction.options.getUser('target') || interaction.user; 30 | 31 | // Retrieve the user's data from the database 32 | const { user } = await db.getUserById(member.id); 33 | 34 | // Send a reply with the user's balance information 35 | interaction.reply(`${client.emoji.balance} ${member.username} has **${user.balance.toLocaleString()}** coins in total.`); 36 | } 37 | }; -------------------------------------------------------------------------------- /commands/images/cat.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const fetch = require('node-fetch'); 4 | 5 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 6 | 7 | module.exports = class Cat extends Command { 8 | constructor(client) { 9 | super(client, { 10 | data: new SlashCommandBuilder() 11 | .setName('cat') 12 | .setDescription('Sends a random cat image') 13 | .setDMPermission(false), 14 | usage: 'cat', 15 | category: 'Images', 16 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 17 | }); 18 | } 19 | /** 20 | * Runs the function to fetch a random cat image and send it as a reply to the interaction. 21 | * 22 | * @param {Client} client - The Discord client. 23 | * @param {Interaction} interaction - The interaction object. 24 | */ 25 | async run(client, interaction) { 26 | // Check if SKILLZL_API_KEY is missing or empty 27 | if (!process.env.SKILLZL_API_KEY || process.env.SKILLZL_API_KEY === '') { 28 | return interaction.reply(`${client.emoji.red_emoji} Missing \`SKILLZL_API_KEY\` in .env file.`); 29 | } 30 | 31 | // Fetch a random cat image from the API 32 | const result = await fetch(`https://api.skillzl.dev/cat/?key=${process.env.SKILLZL_API_KEY}`).then((res) => res.json()); 33 | 34 | // Create an embed with the cat image 35 | const embed = new EmbedBuilder() 36 | .setColor(0x2B2D31) 37 | .setTitle('cat 🐈') 38 | .setURL(result.url) 39 | .setImage(result.url) 40 | .setFooter({ 41 | text: 'api.skillzl.dev', 42 | }); 43 | 44 | // Send the embed as a reply to the interaction 45 | await interaction.reply({ embeds: [embed] }); 46 | } 47 | }; -------------------------------------------------------------------------------- /deploy.js: -------------------------------------------------------------------------------- 1 | const { REST } = require('@discordjs/rest'); 2 | const { Routes } = require('discord-api-types/v10'); 3 | 4 | const fs = require('fs'); 5 | require('dotenv').config(); 6 | 7 | /** 8 | * Deploys the slash commands for the application. 9 | */ 10 | const deploy = async () => { 11 | // Array to store the command data 12 | const commandData = []; 13 | 14 | // Read the categories in the 'commands' directory 15 | fs.readdirSync('./commands/').forEach(async (category) => { 16 | // Get the commands in each category 17 | const commands = fs 18 | .readdirSync(`./commands/${category}/`) 19 | .filter((cmd) => cmd.endsWith('.js')); 20 | 21 | for (const command of commands) { 22 | // Import the command class 23 | const Command = require(`./commands/${category}/${command}`); 24 | 25 | // Create an instance of the command 26 | const cmd = new Command(); 27 | 28 | // Convert the command data to JSON and add it to the array 29 | const cmdData = cmd.data.toJSON(); 30 | commandData.push(cmdData); 31 | } 32 | }); 33 | 34 | // Create a REST client with the specified version and token 35 | const rest = new REST({ version: '10' }).setToken(process.env.TOKEN); 36 | 37 | try { 38 | // Get the client ID from the environment variables 39 | const clientId = process.env.CLIENT_ID; 40 | 41 | console.log('[Deploy]: Started refreshing Slash Commands... ⏳'); 42 | 43 | // Deploy the slash commands 44 | await rest.put(Routes.applicationCommands(clientId), { body: commandData }).then(() => { 45 | console.log('[Deploy]: Slash Commands have now been deployed 📈.'); 46 | }); 47 | } 48 | catch (e) { 49 | console.error(e); 50 | } 51 | }; 52 | 53 | deploy(); -------------------------------------------------------------------------------- /views/dashboard/servers.ejs: -------------------------------------------------------------------------------- 1 | <%- include("../partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - servers 4 | 5 |
6 |

7 | <%- __('WEB.DASHBOARD_SERVER_TITLE')%> <%- __('WEB.DASHBOARD_SERVER_SELECT')%> 8 |

9 |
10 | 23 | 24 | <%- include("../partials/footer") %> -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: 'eslint:recommended', 4 | env: { 5 | node: true, 6 | es6: true, 7 | }, 8 | parserOptions: { 9 | ecmaVersion: 2021, 10 | }, 11 | rules: { 12 | 'arrow-spacing': ['warn', { 'before': true, 'after': true }], 13 | 'brace-style': ['error', 'stroustrup', { 'allowSingleLine': true }], 14 | 'comma-dangle': ['error', 'always-multiline'], 15 | 'comma-spacing': 'error', 16 | 'comma-style': 'error', 17 | 'curly': ['error', 'multi-line', 'consistent'], 18 | 'dot-location': ['error', 'property'], 19 | 'handle-callback-err': 'off', 20 | 'indent': ['error', 'tab'], 21 | 'keyword-spacing': 'error', 22 | 'max-nested-callbacks': ['error', { 'max': 4 }], 23 | 'max-statements-per-line': ['error', { 'max': 2 }], 24 | 'no-console': 'off', 25 | 'no-empty-function': 'error', 26 | 'no-floating-decimal': 'error', 27 | 'no-inline-comments': 'error', 28 | 'no-lonely-if': 'error', 29 | 'no-multi-spaces': 'error', 30 | 'no-multiple-empty-lines': ['error', { 'max': 2, 'maxEOF': 1, 'maxBOF': 0 }], 31 | 'no-shadow': ['error', { 'allow': ['err', 'resolve', 'reject'] }], 32 | 'no-trailing-spaces': ['error'], 33 | 'no-var': 'error', 34 | 'object-curly-spacing': ['error', 'always'], 35 | 'prefer-const': 'error', 36 | 'quotes': ['error', 'single'], 37 | 'semi': ['error', 'always'], 38 | 'space-before-blocks': 'error', 39 | 'space-before-function-paren': ['error', { 40 | 'anonymous': 'never', 41 | 'named': 'never', 42 | 'asyncArrow': 'always', 43 | }], 44 | 'space-in-parens': 'error', 45 | 'space-infix-ops': 'error', 46 | 'space-unary-ops': 'error', 47 | 'spaced-comment': 'error', 48 | 'yoda': 'error', 49 | }, 50 | }; -------------------------------------------------------------------------------- /events/guild/messageCreate.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-shadow */ 2 | const Event = require('../../structures/EventClass'); 3 | const db = require('../../database/manager'); 4 | 5 | module.exports = class MessageCreate extends Event { 6 | constructor(client) { 7 | super(client, { 8 | name: 'messageCreate', 9 | category: 'message', 10 | }); 11 | } 12 | 13 | /** 14 | * Update the user's XP, calculate their level, and send a level up message if applicable. 15 | * @param {Message} message - The message object. 16 | */ 17 | async run(message) { 18 | // Check if the message author is not a bot 19 | if (!message.author.bot) { 20 | // Get the user from the database 21 | const { user } = await db.getUserById(message.author.id); 22 | 23 | // Generate a random XP value between 1 and 5 24 | const xp = Math.ceil(Math.random() * (1 * 5)); 25 | 26 | // Function to calculate the user's level based on their XP 27 | const calculateUserXp = (xp) => Math.floor(0.1 * Math.sqrt(xp)); 28 | 29 | // Calculate the user's current level 30 | const level = calculateUserXp(user.xp); 31 | 32 | // Calculate the user's new level if they gain the XP 33 | const newLevel = calculateUserXp(user.xp + xp); 34 | 35 | // Check if the user leveled up 36 | if (newLevel > level) { 37 | // Send a level up message and delete it after 10 seconds 38 | const msg = await message.reply(`${this.client.emoji.star} Congratulations, you leveled up to level \`${newLevel}\`!`); 39 | setTimeout(() => { 40 | msg?.delete(); 41 | }, 10000); 42 | } 43 | 44 | // Update the user's XP in the database 45 | await db.updateUserById(message.author.id, { 46 | xp: user.xp + xp, 47 | }); 48 | } 49 | } 50 | }; -------------------------------------------------------------------------------- /commands/player/resume.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | 4 | const { Player } = require('discord-player'); 5 | 6 | module.exports = class Resume extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('resume') 11 | .setDescription('Resumes the current track') 12 | .setDMPermission(false), 13 | usage: 'resume', 14 | category: 'Player', 15 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 16 | }); 17 | } 18 | async run(client, interaction) { 19 | /** 20 | * Resumes the music playback in the queue if it is paused. 21 | * 22 | * @param {Client} client - The Discord client object. 23 | * @param {Interaction} interaction - The interaction object representing the command. 24 | */ 25 | 26 | // Get the player singleton instance 27 | const player = Player.singleton(); 28 | 29 | // Get the queue for the guild from the player 30 | const queue = player.nodes.get(interaction.guild.id); 31 | 32 | // Check if there is a queue or if the queue is currently playing 33 | if (!queue || !queue.isPlaying()) { 34 | return await interaction.reply(`${client.emoji.red_emoji} There isn't currently any music playing.`); 35 | } 36 | 37 | // Check if the queue is paused 38 | if (!queue.node.isPaused()) { 39 | return await interaction.reply(`${client.emoji.red_emoji} The queue isn't currently paused.`); 40 | } 41 | 42 | // Set the queue to unpaused 43 | queue.node.setPaused(false); 44 | 45 | // Reply with a success message 46 | return await interaction.reply(`${client.emoji.green_emoji} Successfully unpaused **${queue.currentTrack.title}**.`); 47 | } 48 | }; -------------------------------------------------------------------------------- /commands/player/pause.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | 4 | const { Player } = require('discord-player'); 5 | 6 | module.exports = class Pause extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('pause') 11 | .setDescription('Pauses the current track') 12 | .setDMPermission(false), 13 | usage: 'resume', 14 | category: 'Player', 15 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 16 | }); 17 | } 18 | /** 19 | * Runs the function with the given client and interaction parameters. 20 | * 21 | * @param {Client} client - The client object. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @return {Promise} A Promise that resolves to a reply message. 24 | */ 25 | async run(client, interaction) { 26 | // Get the singleton instance of the Player class 27 | const player = Player.singleton(); 28 | 29 | // Get the queue for the current guild from the player nodes 30 | const queue = player.nodes.get(interaction.guild.id); 31 | 32 | // If there is no queue or the queue is not playing, return a message indicating no music is playing 33 | if (!queue || !queue.isPlaying()) { 34 | return await interaction.reply(`${client.emoji.red_emoji} There isn't currently any music playing.`); 35 | } 36 | 37 | // Toggle the pause state of the queue node 38 | queue.node.setPaused(!queue.node.isPaused()); 39 | 40 | // Return a message indicating the success of pausing/unpausing the current track 41 | return await interaction.reply(`${client.emoji.green_emoji} Successfully ${queue.node.isPaused() === true ? 'paused' : 'unpaused'} **${queue.currentTrack.title}**.`); 42 | } 43 | }; -------------------------------------------------------------------------------- /commands/general/ping.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { stripIndents } = require('common-tags'); 4 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 5 | 6 | module.exports = class Ping extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('ping') 11 | .setDescription('Returns the bot ping') 12 | .setDMPermission(false), 13 | usage: 'ping', 14 | category: 'General', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the ping command. 20 | * 21 | * @param {Client} client - The Discord client. 22 | * @param {Interaction} interaction - The interaction object. 23 | * @returns {Promise} - A promise that resolves to the message sent. 24 | */ 25 | async run(client, interaction) { 26 | // Get the i18n instance for the guild 27 | const i18n = await client.i18n.get(interaction.guild.id); 28 | 29 | // Get the current timestamp 30 | const now = Date.now(); 31 | 32 | // Defer the reply to the interaction 33 | await interaction.deferReply(); 34 | 35 | // Create the ping embed 36 | const pingEmbed = new EmbedBuilder() 37 | .setAuthor({ 38 | name: `${client.emoji.heartbeat} ${client.i18n.handle('GENERAL', 'PING', i18n)}`, 39 | iconURL: client.user.displayAvatarURL({ size: 2048 }), 40 | }) 41 | .setColor(0x2B2D31) 42 | .setDescription(stripIndents` 43 | ${client.emoji.clock} roundtrip: **${Math.round(Date.now() - now)} ms** 44 | ${client.emoji.heartbeat} api: **${Math.round(client.ws.ping)} ms** 45 | `); 46 | 47 | // Send the ping embed as a reply to the interaction 48 | return await interaction.followUp({ embeds: [pingEmbed] }); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /commands/utils/plugins.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Plugins extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('plugins') 10 | .setDescription('Discover application\'s plugins') 11 | .setDMPermission(false), 12 | usage: 'plugins', 13 | category: 'Utils', 14 | permissions: ['Use Application Commands', 'Send Messages'], 15 | }); 16 | } 17 | /** 18 | * Run function for handling a specific interaction. 19 | * @param {Client} client - The Discord client instance. 20 | * @param {Interaction} interaction - The interaction object. 21 | */ 22 | async run(client, interaction) { 23 | // Create an embed with the plugins information and send it as a reply to the interaction 24 | const embed = new EmbedBuilder() 25 | .setColor(0x2B2D31) 26 | .setTitle(`${client.emoji.moon} Plugins`) 27 | .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 128, format: 'png' })) 28 | .setDescription('Hey there!\nGlad to have you here. Let me guess. Plugins!\nWell you have some, autorole and the good old welcome message and leave message.') 29 | .addFields( 30 | { name: 'Autorole', value: 'The basic autorole for your newcomers, just add one and see the magic.', inline: true }, 31 | { name: 'Welcome Message', value: 'Classic welcome message, but this time it\'s a minimalistic image, with good taste, tho!', inline: true }, 32 | { name: 'Leave Message', value: 'And also, why not, the leave message, you can either set both in a channel or make it your way.\nBe different!', inline: true }, 33 | ) 34 | .setFooter({ 35 | text: 'New here! You should check out our website to set-up these plugins.', 36 | }); 37 | 38 | // Send the embed as a reply to the interaction 39 | await interaction.reply({ embeds: [embed] }); 40 | } 41 | }; -------------------------------------------------------------------------------- /events/player/channelEmpty.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'emptyChannel', 3 | /** 4 | * Executes the queue operation. 5 | * Deletes the queue and sends a message to the channel. 6 | * If there is an error, logs the error message. 7 | * @param {Queue} queue - The queue object. 8 | */ 9 | async execute(queue) { 10 | try { 11 | queue.delete(); 12 | } 13 | catch (err) { 14 | console.log(`[Player]: An error 🔴 occurred at (${new Date().toISOString()}): ${err.message}`); 15 | } 16 | 17 | // Send a message to the channel 18 | queue.metadata.channel.send({ content: `The music was stopped due to \`${formatMS(300000)}\` inactivity.` }); 19 | }, 20 | }; 21 | 22 | /** 23 | * Format milliseconds into a human-readable string representation of time. 24 | * 25 | * @param {number} ms - The number of milliseconds to format. 26 | * @returns {string} - The formatted time string. 27 | */ 28 | function formatMS(ms) { 29 | // Calculate the number of seconds, minutes, and hours 30 | const s = Math.floor(ms / 1000) % 60; 31 | const m = Math.floor(ms / (1000 * 60)) % 60; 32 | const h = Math.floor(ms / (1000 * 60 * 60)); 33 | 34 | // Initialize an empty string to store the formatted time 35 | let str = ''; 36 | 37 | // Add hours to the string if it's greater than 0 38 | if (h > 0) str += `${h} hour${h > 1 ? 's' : ''}`; 39 | 40 | // Add a comma if both hours and minutes are present 41 | if (h > 0 && m > 0) str += ', '; 42 | 43 | // Add "and" if both hours and seconds are present 44 | if (h > 0 && s > 0) str += ' and '; 45 | 46 | // Add minutes to the string if it's greater than 0 47 | if (m > 0) str += `${m} minute${m > 1 ? 's' : ''}`; 48 | 49 | // Add "and" if both minutes and seconds are present 50 | if (m > 0 && s > 0) str += ' and '; 51 | 52 | // Add seconds to the string if it's greater than 0 53 | if (s > 0) str += `${s} second${s > 1 ? 's' : ''}`; 54 | 55 | // Add "of" to the string if it's not empty 56 | if (str.length > 0) str += ' of'; 57 | 58 | return str; 59 | } 60 | -------------------------------------------------------------------------------- /commands/utils/clear.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, PermissionsBitField } = require('discord.js'); 4 | 5 | module.exports = class Clear extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('clear') 10 | .setDescription('Deletes a bulk of specified messages') 11 | .addNumberOption((option) => 12 | option.setName('number') 13 | .setDescription('Messages count (min: 2 max: 100)') 14 | .setMinValue(2) 15 | .setMaxValue(100) 16 | .setRequired(true)) 17 | .setDMPermission(false), 18 | usage: 'clear [number]', 19 | category: 'Utils', 20 | permissions: ['Use Application Commands', 'Send Messages', 'Manage Messages'], 21 | }); 22 | } 23 | /** 24 | * Runs the command when the corresponding slash command is used. 25 | * 26 | * @param {Client} client - The Discord client instance. 27 | * @param {Interaction} interaction - The interaction object representing the slash command. 28 | */ 29 | async run(client, interaction) { 30 | // Check if the user has the 'MANAGE_MESSAGES' permission 31 | if (!interaction.member.permissions.has(PermissionsBitField.Flags.ManageMessages)) { 32 | // If not, reply with an error message 33 | return await interaction.reply(`${client.emoji.red_emoji} You are missing \`MANAGE_MESSAGES\` permission.`); 34 | } 35 | 36 | // Get the value of the 'number' option from the interaction 37 | const number = interaction.options.getNumber('number'); 38 | 39 | // Fetch the specified number of messages from the channel 40 | const fetched = await interaction.channel.messages.fetch({ limit: number }); 41 | 42 | // Bulk delete the fetched messages 43 | await interaction.channel.bulkDelete(fetched).then(interaction.reply(`${client.emoji.green_emoji} Deleted ${number} messages.`).then(reply => { 44 | // Delete the success message after 2 seconds 45 | setTimeout(() => { 46 | reply.delete(); 47 | }, 2000); 48 | })); 49 | } 50 | }; -------------------------------------------------------------------------------- /handler/Command.js: -------------------------------------------------------------------------------- 1 | const BaseCommand = require('../structures/CommandClass'); 2 | const path = require('path'); 3 | const { readdir, lstat } = require('fs').promises; 4 | 5 | module.exports = class CommandClass { 6 | constructor(client) { 7 | this.client = client; 8 | } 9 | 10 | /** 11 | * Recursively builds a command tree from a directory. 12 | * 13 | * @param {string} dir - The directory path to build the command tree from. 14 | */ 15 | async build(dir) { 16 | // Get the full file path 17 | const filePath = path.join(__dirname, dir); 18 | 19 | // Read the directory 20 | const files = await readdir(filePath); 21 | 22 | // Iterate through each file 23 | for (const file of files) { 24 | // Get the file's stats 25 | const stat = await lstat(path.join(filePath, file)); 26 | 27 | // If the file is a directory, recursively call the build function 28 | if (stat.isDirectory()) { 29 | this.build(path.join(dir, file)); 30 | } 31 | 32 | // If the file is a JavaScript file 33 | if (file.endsWith('.js')) { 34 | // Require the file 35 | const Command = require(path.join(filePath, file)); 36 | 37 | // If the required file is a subclass of BaseCommand 38 | if (Command.prototype instanceof BaseCommand) { 39 | // Create a new instance of the command 40 | const cmd = new Command(this.client); 41 | 42 | // Get the command's data 43 | const cmdData = cmd.data.toJSON(); 44 | 45 | // Create a new command object with selected properties 46 | const cmdSet = { 47 | name: cmdData.name, 48 | description: cmdData.description, 49 | options: cmdData.options, 50 | defaultPermission: cmdData.default_permission, 51 | contextDescription: cmd.contextDescription, 52 | usage: cmd.usage, 53 | category: cmd.category, 54 | permissions: cmd.permissions, 55 | run: cmd.run, 56 | }; 57 | 58 | // Add the command to the client's commands map 59 | this.client.commands.set(cmdSet.name, cmdSet); 60 | } 61 | } 62 | } 63 | } 64 | }; -------------------------------------------------------------------------------- /commands/levels/leaderboard.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const userModel = require('../../database/userModel'); 3 | 4 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 5 | 6 | module.exports = class Leaderboard extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('leaderboard') 11 | .setDescription('Shows top 10 users with the highest amount of XP') 12 | .setDMPermission(false), 13 | usage: 'leaderboard', 14 | category: 'Levels', 15 | permissions: ['Use Application Commands', 'Send Messages'], 16 | }); 17 | } 18 | /** 19 | * Runs the leaderboard command. 20 | * @param {Client} client - The Discord client. 21 | * @param {Interaction} interaction - The interaction object. 22 | */ 23 | async run(client, interaction) { 24 | // Retrieve the users from the database, sorted by xp in descending order 25 | const users = await userModel.find({}).sort('-xp'); 26 | const leaderboard = []; 27 | 28 | // Iterate over the top 10 users 29 | for (let i = 0; i < 10; i++) { 30 | if (users[i]) { 31 | // Get the user object from the Discord cache 32 | const user = client.users.cache.get(users[i].userId); 33 | if (user) { 34 | // Add the user's rank, username, and xp to the leaderboard array 35 | leaderboard.push(`**${i + 1}**. ${user.username} - ${users[i].xp} xp points`); 36 | } 37 | } 38 | } 39 | 40 | // Create the embed object 41 | const embed = new EmbedBuilder() 42 | .setColor(0x2B2D31) 43 | .setTitle(`${client.emoji.star} Ranking Leaderboard`) 44 | .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 128, format: 'png' })) 45 | .setDescription( 46 | 'You can easily earn xp points and achievements by texting on your favorite communities. Our text-based leveling system is global and will reward you at some accomplishments.\n\n' + 47 | leaderboard.join('\n'), 48 | ); 49 | 50 | // Reply to the interaction with the embed 51 | await interaction.reply({ embeds: [embed] }); 52 | } 53 | }; -------------------------------------------------------------------------------- /commands/player/shuffle.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | 4 | const { Player } = require('discord-player'); 5 | 6 | module.exports = class Shuffle extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('shuffle') 11 | .setDescription('Shuffles all tracks currently in the queue') 12 | .setDMPermission(false), 13 | usage: 'shuffle', 14 | category: 'Player', 15 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 16 | }); 17 | } 18 | /** 19 | * Shuffles the tracks in the queue and sends a reply message. 20 | * 21 | * @param {Client} client - The Discord client. 22 | * @param {Interaction} interaction - The interaction object. 23 | */ 24 | async run(client, interaction) { 25 | // Get the singleton instance of the Player class 26 | const player = Player.singleton(); 27 | 28 | // Get the queue for the current guild 29 | const queue = player.nodes.get(interaction.guild.id); 30 | 31 | // Check if there is no queue or the queue is not playing 32 | if (!queue || !queue.isPlaying()) { 33 | // Send a reply message indicating no music is currently playing 34 | return await interaction.reply(`${client.emoji.red_emoji} There isn't currently any music playing.`); 35 | } 36 | 37 | // Check if there are no other tracks in the queue 38 | if (!queue.tracks.toArray()[0]) { 39 | // Send a reply message indicating there are no other tracks in the queue 40 | return await interaction.reply(`${client.emoji.red_emoji} There aren't any other tracks in the queue. Use \`/play\` to add some more.`); 41 | } 42 | 43 | // Shuffle the tracks in the queue 44 | queue.tracks.shuffle(); 45 | 46 | // Send a reply message indicating the shuffle was successful 47 | return await interaction.reply(queue.tracks.length === 1 ? `${client.emoji.green_emoji} Successfully shuffled \`${queue.tracks.toArray().length}\` track.` : `${client.emoji.green_emoji} Successfully shuffled \`${queue.tracks.toArray().length}\` tracks.`); 48 | } 49 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eres-main", 3 | "version": "1.8.7-b", 4 | "description": "🍍 Eres; new Discord application with modern web dashboard and new features for your community.", 5 | "main": "bot.js", 6 | "scripts": { 7 | "start": "node bot", 8 | "deploy": "node deploy", 9 | "build:css": "postcss public/stylesheets/tailwind.css -o public/stylesheets/style.css", 10 | "generate-sitemap": "node generate-sitemap" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/skillzl/eres.git" 15 | }, 16 | "keywords": [ 17 | "eres", 18 | "eres-main", 19 | "discord.js" 20 | ], 21 | "author": "skillzl", 22 | "license": "Apache License 2.0", 23 | "bugs": { 24 | "url": "https://github.com/skillzl/eres/issues" 25 | }, 26 | "homepage": "https://github.com/skillzl/eres#readme", 27 | "dependencies": { 28 | "@discord-player/extractor": "^4.4.5", 29 | "@discordjs/opus": "^0.9.0", 30 | "@discordjs/rest": "^1.7.1", 31 | "@discordjs/voice": "^0.16.1", 32 | "autoprefixer": "^10.4.16", 33 | "axios": "^1.7.9", 34 | "body-parser": "^1.20.2", 35 | "canvas": "^2.11.2", 36 | "common-tags": "^1.8.2", 37 | "cookie-parser": "^1.4.6", 38 | "dayjs": "^1.11.8", 39 | "discord-html-transcripts": "^3.2.0", 40 | "discord-player": "^6.6.6", 41 | "discord-player-youtubei": "^1.3.5", 42 | "discord.js": "^14.11.0", 43 | "dotenv": "^16.3.1", 44 | "ejs": "^3.1.9", 45 | "express": "^4.18.2", 46 | "express-paginate": "^1.0.2", 47 | "express-session": "^1.17.3", 48 | "ffmpeg": "^0.0.4", 49 | "ffmpeg-static": "^5.2.0", 50 | "figlet": "^1.7.0", 51 | "i18n": "^0.15.1", 52 | "mongoose": "^7.6.5", 53 | "node-cron": "^3.0.3", 54 | "passport": "^0.6.0", 55 | "passport-discord": "^0.1.4", 56 | "path": "^0.12.7", 57 | "play-dl": "^1.9.7", 58 | "prism-media": "^1.3.5", 59 | "replicate": "^0.27.1", 60 | "sitemap": "^8.0.0", 61 | "tailwindcss": "^3.3.5", 62 | "validator": "^13.11.0" 63 | }, 64 | "devDependencies": { 65 | "eslint": "^8.42.0", 66 | "eslint-plugin-node": "^11.1.0", 67 | "eslint-plugin-promise": "^6.1.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /commands/player/nowplaying.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | 4 | const { Player } = require('discord-player'); 5 | 6 | module.exports = class Nowplaying extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('nowplaying') 11 | .setDescription('View information about the current track') 12 | .setDMPermission(false), 13 | usage: 'nowplaying', 14 | category: 'Player', 15 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 16 | }); 17 | } 18 | /** 19 | * Runs the command when invoked by a user. 20 | * @param {Client} client - The Discord client. 21 | * @param {Interaction} interaction - The interaction object. 22 | * @returns {Promise} 23 | */ 24 | async run(client, interaction) { 25 | // Get the singleton instance of the Player class 26 | const player = Player.singleton(); 27 | 28 | // Get the queue for the current guild 29 | const queue = player.nodes.get(interaction.guild.id); 30 | 31 | // Create a new EmbedBuilder instance 32 | const embed = new EmbedBuilder() 33 | .setTitle(`${client.emoji.music} Now Playing`) 34 | .setColor(0x2B2D31); 35 | 36 | // Check if there is no queue or the queue is not playing any music 37 | if (!queue || !queue.isPlaying()) { 38 | return await interaction.reply(`${client.emoji.red_emoji} There isn't currently any music playing.`); 39 | } 40 | 41 | // Create a progress bar using the current node 42 | const progress = queue.node.createProgressBar(); 43 | 44 | // Set the description of the embed with the current track information 45 | embed.setDescription(`${progress}\n \n**[${queue.currentTrack.title}](${queue.currentTrack.url})** by **${queue.currentTrack.author}** is currently playing in **${interaction.guild.name}**. This track was requested by <@${queue.currentTrack.requestedBy.id}>.`); 46 | 47 | // Set the thumbnail of the embed to the guild's icon 48 | embed.setThumbnail(interaction.guild.iconURL({ dynamic: true, size: 2048, extension: 'png' })); 49 | 50 | // Reply to the interaction with the embed 51 | return await interaction.reply({ embeds: [embed] }); 52 | } 53 | }; -------------------------------------------------------------------------------- /views/release.ejs: -------------------------------------------------------------------------------- 1 | <%- include("partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - release 4 | 5 | 6 |
7 |
8 |
9 | <% 10 | const owner = 'skillzl'; 11 | const repo = 'eres'; 12 | %> 13 | 14 | 15 | 18 | 19 |
20 |
21 |

<%- __('WEB.RELEASE_NAME')%>: <%= release.name %>

22 |

<%- __('WEB.RELEASE_DATE')%>: <%= release.publishedAt %>

23 |
    24 | <% release.body.split('\n').forEach(function(line) { %> 25 | <% if (line.startsWith('-')) { %> 26 |
  • <%= line.slice(1).trim() %>
  • 27 | <% } else { %> 28 |

    <%= line %>

    29 | <% } %> 30 | <% }); %> 31 |
32 | 39 |
40 |
41 |
42 | 43 | 44 | <%- include("partials/footer") %> -------------------------------------------------------------------------------- /structures/Client.js: -------------------------------------------------------------------------------- 1 | const { Client, GatewayIntentBits, Partials, ActivityType } = require('discord.js'); 2 | const { Collection } = require('@discordjs/collection'); 3 | const CommandHandler = require('../handler/Command'); 4 | const EventHandler = require('../handler/Event'); 5 | 6 | module.exports = class BotClient extends Client { 7 | /** 8 | * Initializes a new instance of the Constructor class. 9 | * @param {...any} opt - Optional arguments. 10 | */ 11 | constructor(...opt) { 12 | super({ 13 | opt, 14 | partials: [ 15 | Partials.GuildMember, 16 | Partials.Message, 17 | Partials.Channel, 18 | Partials.User, 19 | ], 20 | intents: [ 21 | GatewayIntentBits.Guilds, 22 | GatewayIntentBits.GuildMembers, 23 | GatewayIntentBits.GuildMessages, 24 | GatewayIntentBits.GuildPresences, 25 | GatewayIntentBits.GuildBans, 26 | GatewayIntentBits.GuildEmojisAndStickers, 27 | GatewayIntentBits.GuildIntegrations, 28 | GatewayIntentBits.GuildVoiceStates, 29 | ], 30 | presence: { 31 | status: 'online', 32 | activity: [{ name: 'Bot is now starting up...', type: ActivityType.Playing }], 33 | }, 34 | }); 35 | 36 | // Initialize collections for commands and events 37 | this.commands = new Collection(); 38 | this.events = new Collection(); 39 | 40 | // Build event handler and command handler 41 | new EventHandler(this).build('../events'); 42 | new CommandHandler(this).build('../commands'); 43 | } 44 | 45 | /** 46 | * Logs in the user using the token from the environment variables. 47 | * @returns {Promise} A promise that resolves when the login is successful. 48 | */ 49 | async login() { 50 | await super.login(process.env.TOKEN); 51 | } 52 | 53 | /** 54 | * Exit the program gracefully. 55 | */ 56 | exit() { 57 | // Check if already quitting 58 | if (this.quitting) { 59 | return; 60 | } 61 | // Set quitting flag 62 | this.quitting = true; 63 | // Destroy resources 64 | this.destroy(); 65 | } 66 | 67 | /** 68 | * Fetches the command associated with the given command name. 69 | * 70 | * @param {string} cmd - The name of the command to fetch. 71 | * @returns {Command} - The command associated with the given command name. 72 | */ 73 | fetchCommand(cmd) { 74 | return this.commands.get(cmd); 75 | } 76 | }; -------------------------------------------------------------------------------- /commands/economy/daily.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const ms = require('ms'); 5 | 6 | const { SlashCommandBuilder } = require('discord.js'); 7 | 8 | module.exports = class Daily extends Command { 9 | constructor(client) { 10 | super(client, { 11 | data: new SlashCommandBuilder() 12 | .setName('daily') 13 | .setDescription('Get your daily bonus worth of coins') 14 | .setDMPermission(false), 15 | usage: 'daily', 16 | category: 'Economy', 17 | permissions: ['Use Application Commands', 'Send Messages'], 18 | }); 19 | } 20 | /** 21 | * Asynchronously runs the function. 22 | * 23 | * @param {Client} client - The client object. 24 | * @param {Interaction} interaction - The interaction object. 25 | * @return {Promise} - Returns nothing. 26 | */ 27 | async run(client, interaction) { 28 | // Retrieve user from the database based on the interaction user id 29 | const { user } = await db.getUserById(interaction.user.id); 30 | 31 | // Set the timeout for daily rewards to 24 hours (86400000 milliseconds) 32 | const timeout = 86400000; 33 | 34 | // Generate a random amount between 1 and 500 for the daily reward 35 | const amount = Math.floor(Math.random() * 500) + 1; 36 | 37 | // Check if the user has already collected their daily reward 38 | if (user.daily_cooldown !== null && timeout - (Date.now() - user.daily_cooldown) > 0) { 39 | // Calculate the remaining time until the user can collect their next daily reward 40 | const time = ms(timeout - (Date.now() - user.daily_cooldown), { 41 | long: true, 42 | }); 43 | 44 | // Reply to the interaction with a message indicating the remaining time 45 | interaction.reply(`You've already collected your daily reward recently, \`${time}\` remaining.`); 46 | } 47 | else { 48 | // Update the user's daily cooldown time and balance in the database 49 | db.updateUserById(interaction.user.id, { 50 | daily_cooldown: Date.now(), 51 | balance: user.balance + amount, 52 | }); 53 | 54 | // Reply to the interaction with a message indicating the amount of coins collected 55 | interaction.reply(`${client.emoji.balance} ${interaction.user.username}, you collected your daily bonus worth of **${amount.toLocaleString()}** coins.`); 56 | } 57 | } 58 | }; -------------------------------------------------------------------------------- /views/privacy.ejs: -------------------------------------------------------------------------------- 1 | <%- include("partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - privacy policy 4 | 5 | <% 6 | 7 | var mailPrivacy = `admin.skillzl.dev`; 8 | 9 | %> 10 | 11 |
12 |
13 |

<%- __('WEB.PRIVACY_TITLE')%>

14 |
15 | eres.lol logo 16 | 17 |

<%- __('WEB.PRIVACY_INTRODUCTION')%>

18 |

<%- __('WEB.PRIVACY_INTRODUCTION_SUB')%>

19 |

<%- __('WEB.PRIVACY_INTRODUCTION_SUB2')%>

20 |

<%- __('WEB.PRIVACY_INTRODUCTION_SUB3')%>

21 |

<%- __('WEB.PRIVACY_WHAT_DATA_WE_COLLECT')%>

22 |

<%- __('WEB.PRIVACY_WHAT_DATA_WE_COLLECT_SUB')%>

23 |

- <%- __('WEB.PRIVACY_DISCORD_ID')%>

24 |

- <%- __('WEB.PRIVACY_DISCORD_USERNAME')%>

25 |

- <%- __('WEB.PRIVACY_GUILD_ID')%>

26 |

- <%- __('WEB.PRIVACY_GUILD_NAME')%>

27 |

- <%- __('WEB.PRIVACY_CHANNEL_ID')%>

28 |

<%- __('WEB.PRIVACY_WHAT_DATA_WE_COLLECT_SUB2')%>

29 |

<%- __('WEB.PRIVACY_WHAT_DATA_WE_COLLECT_SUB3')%>

30 |

<%- __('WEB.PRIVACY_HOW_USE')%>

31 |

<%- __('WEB.PRIVACY_HOW_USE_SUB')%>

32 |

<%- __('WEB.PRIVACY_HOW_USE_SUB2')%>

33 |

<%- __('WEB.PRIVACY_HOW_USE_SUB3')%>

34 |

<%- __('WEB.PRIVACY_HOW_USE_SUB4')%>

35 |
<%- __('WEB.PRIVACY_DATA_SECURITY')%>
36 |

<%- __('WEB.PRIVACY_DATA_SECURITY_SUB')%>

37 | 38 |
<%- __('WEB.PRIVACY_CHANGES')%>
39 |

<%- __('WEB.PRIVACY_CHANGES_SUB')%>

40 | 41 |

<%- __('WEB.PRIVACY_CONTACT')%>

42 |

<%- __('WEB.PRIVACY_CONTACT_SUB').replace('{mail}', mailPrivacy)%>

43 |
44 |
45 | 46 | <%- include("partials/footer") %> -------------------------------------------------------------------------------- /commands/utils/transcript.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder, PermissionsBitField } = require('discord.js'); 3 | 4 | const fs = require('fs'); 5 | const { createTranscript } = require('discord-html-transcripts'); 6 | 7 | module.exports = class TranscriptCommand extends Command { 8 | constructor(client) { 9 | super(client, { 10 | data: new SlashCommandBuilder() 11 | .setName('transcript') 12 | .setDescription('Generates a transcript of the last 100 messages in the channel') 13 | .setDMPermission(false), 14 | usage: 'transcript', 15 | category: 'Utils', 16 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links', 'Manage Messages'], 17 | }); 18 | } 19 | 20 | /** 21 | * Runs the function asynchronously. 22 | * 23 | * @param {Client} client - The client object. 24 | * @param {Interaction} interaction - The interaction object. 25 | * @return {Promise} - A promise that resolves when the function completes. 26 | */ 27 | async run(client, interaction) { 28 | // Check if the user has the "MANAGE_MESSAGES" permission 29 | if (!interaction.member.permissions.has(PermissionsBitField.Flags.ManageMessages)) { 30 | // Reply with an error message if the user doesn't have the permission 31 | return await interaction.reply(`${client.emoji.red_emoji} You are missing \`MANAGE_MESSAGES\` permission.`); 32 | } 33 | 34 | // Defer the reply to the interaction to prevent timeouts 35 | interaction.deferReply({ fetchReply: true }); 36 | 37 | // Fetch the last 100 messages in the channel 38 | interaction.channel.messages.fetch({ limit: 100 }); 39 | 40 | try { 41 | // Create a transcript of the channel's messages 42 | const transcript = await createTranscript(interaction.channel, { 43 | returnType: 'string', 44 | limit: 100, 45 | }); 46 | 47 | // Write the transcript to a file 48 | fs.writeFile(`./public/transcripts/${interaction.channel.id}-transcript.html`, transcript, err => { 49 | if (err) { 50 | console.error(err); 51 | return; 52 | } 53 | // Edit the interaction's reply with a message containing the transcript URL 54 | interaction.editReply({ 55 | content: `${client.emoji.mail} Here's the transcript of the last \`100 messages\` in this channel: ${interaction.channel}\nhttp://${process.env.DOMAIN}/transcripts/${interaction.channel.id}-transcript.html`, 56 | }); 57 | }); 58 | } 59 | catch (err) { 60 | console.error(err); 61 | } 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /events/client/ready.js: -------------------------------------------------------------------------------- 1 | const Event = require('../../structures/EventClass'); 2 | const cron = require('node-cron'); 3 | const mongoose = require('mongoose'); 4 | 5 | const analyticsModel = require('../../database/analyticsModel'); 6 | const { ActivityType } = require('discord.js'); 7 | 8 | module.exports = class ReadyEvent extends Event { 9 | constructor(client) { 10 | super(client, { 11 | name: 'ready', 12 | once: true, 13 | }); 14 | } 15 | /** 16 | * This function is responsible for running the application. 17 | * It connects to the database, loads the web portal, and sets up scheduled tasks. 18 | */ 19 | async run() { 20 | const client = this.client; 21 | 22 | // Connect to the MongoDB database 23 | await mongoose.connect(process.env.MONGO_URL, { 24 | useNewUrlParser: true, 25 | useUnifiedTopology: true, 26 | }); 27 | console.log('[Database]: Connected to 🥬 mongoose database server.'); 28 | 29 | // Load the web portal 30 | const webPortal = require('../../server'); 31 | webPortal.load(client); 32 | 33 | // Schedule task to update guilds and users analytics stats every Sunday at midnight 34 | cron.schedule('0 0 * * 0', async () => { 35 | const guilds = client.guilds.cache.size; 36 | const users = client.guilds.cache.reduce( 37 | (a, g) => a + g.memberCount, 38 | 0, 39 | ); 40 | 41 | const analytics = await analyticsModel.findOne({}); 42 | if (analytics) { 43 | analytics.guilds = guilds; 44 | analytics.users = users; 45 | await analytics.save(); 46 | } 47 | else { 48 | await analyticsModel.create({ guilds, users }); 49 | } 50 | console.log('[Scheduler]: 🟢 Updated guilds and users analytics stats.'); 51 | }); 52 | 53 | // Schedule task to update user activity every 5 minutes 54 | cron.schedule('*/5 * * * *', async () => { 55 | const users = client.guilds.cache.reduce( 56 | (a, g) => a + g.memberCount, 57 | 0, 58 | ); 59 | 60 | client.user.setActivity('🌴 ' + users.toLocaleString() + ' users', { type: ActivityType.Watching }); 61 | }); 62 | 63 | // Update user activity immediately 64 | const users = client.guilds.cache.reduce( 65 | (a, g) => a + g.memberCount, 66 | 0, 67 | ); 68 | 69 | client.user.setActivity('🌴 ' + users.toLocaleString() + ' users', { type: ActivityType.Watching }); 70 | 71 | console.log(`[Deploy]: 🟢 ${client.user.tag} is online. `); 72 | console.log(`[Info]: Interacted with ${users.toLocaleString()} users 👥 and ${client.guilds.cache.size.toLocaleString()} guilds 🈂️.`); 73 | } 74 | }; -------------------------------------------------------------------------------- /commands/developer/eval.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | 4 | module.exports = class Eval extends Command { 5 | constructor(client) { 6 | super(client, { 7 | data: new SlashCommandBuilder() 8 | .setName('eval') 9 | .setDescription('Evaluates javascript code') 10 | .addStringOption(option => 11 | option.setName('input') 12 | .setDescription('Your code') 13 | .setRequired(true)) 14 | .setDMPermission(false), 15 | usage: 'eval [string]', 16 | category: 'Developer', 17 | permissions: ['Use Application Commands', 'Send Messages'], 18 | }); 19 | } 20 | /** 21 | * Runs the function asynchronously. 22 | * 23 | * @param {Client} client - The client object. 24 | * @param {Interaction} interaction - The interaction object. 25 | * @return {Promise} - Returns a Promise that resolves to nothing. 26 | */ 27 | async run(client, interaction) { 28 | // Check if the user is authorized to run the function 29 | if (interaction.user.id !== process.env.DEVELOPER_ID) { 30 | // If not authorized, reply with an error message 31 | return interaction.reply(`${client.emoji.red_emoji} Missing \`DEVELOPER\` permission.`); 32 | } 33 | 34 | // Get the input from the interaction options 35 | const toEval = interaction.options.getString('input'); 36 | // Define a regular expression to remove code block delimiters 37 | const toRemove = /^(```js)|(```)$/gi; 38 | 39 | // Create a new instance of EmbedBuilder 40 | const embed = new EmbedBuilder(); 41 | try { 42 | // Evaluate the input using the eval function and remove code block delimiters 43 | const evalled = eval(toEval.replace(toRemove, '')); 44 | // Set the title of the embed to indicate successful evaluation 45 | embed.setTitle('Evaluation Successful'); 46 | // Add the evaluated output to the embed as a field 47 | embed.addFields({ name: 'Output', value: '```js\n' + evalled + '\n```' }); 48 | // Set the color of the embed 49 | embed.setColor(0x2B2D31); 50 | } 51 | catch (err) { 52 | // Set the title of the embed to indicate failed evaluation 53 | embed.setTitle('Evaluation Failed'); 54 | // Add the error message to the embed as a field 55 | embed.addFields({ name: 'Output', value: '```js\n' + err + '\n```' }); 56 | // Set the color of the embed 57 | embed.setColor(0x2B2D31); 58 | } 59 | 60 | // Reply to the interaction with the embed 61 | interaction.reply({ embeds: [embed] }); 62 | } 63 | }; -------------------------------------------------------------------------------- /commands/app/commits.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const fetch = require('node-fetch'); 4 | 5 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 6 | 7 | module.exports = class Commits extends Command { 8 | constructor(client) { 9 | super(client, { 10 | data: new SlashCommandBuilder() 11 | .setName('commits') 12 | .setDescription('Shows last 5 commits pushed on main branch') 13 | .setDMPermission(false), 14 | usage: 'commits', 15 | category: 'App', 16 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 17 | }); 18 | } 19 | /** 20 | * Asynchronously runs the function. 21 | * 22 | * @param {Client} client - The client object. 23 | * @param {Interaction} interaction - The interaction object. 24 | * @return {Promise} Returns a promise that resolves to nothing. 25 | */ 26 | async run(client, interaction) { 27 | // Set owner and repo variables 28 | const owner = 'skillzl'; 29 | const repo = 'eres'; 30 | 31 | // Construct the URL for fetching commits 32 | const url = `https://api.github.com/repos/${owner}/${repo}/commits?per_page=5`; 33 | 34 | try { 35 | // Fetch the commits 36 | const response = await fetch(url); 37 | const data = await response.json(); 38 | 39 | // If no commits found, reply and return 40 | if (data.length === 0) { 41 | interaction.reply('No commits found.'); 42 | return; 43 | } 44 | 45 | // Map the commits to a formatted string 46 | const commits = data.map((commit) => { 47 | const isoDate = commit.commit.author.date; 48 | const unixTimestamp = Date.parse(isoDate) / 1000; 49 | const commitSha = commit.sha; 50 | 51 | return `[\`${commitSha.substring(0, 7)}\`](https://github.com/${owner}/${repo}/commit/${commitSha})\n ${commit.commit.message}`; 52 | }); 53 | 54 | // Create an embed with the fetched commits 55 | const embed = new EmbedBuilder() 56 | .setColor(0x2B2D31) 57 | .setTitle(`${client.emoji.ticket} GitHub Commits`) 58 | .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 128, format: 'png' })) 59 | .setDescription(`Last **5** fetched commits from the public repository [github.com/${owner}/${repo}](https://github.com/${owner}/${repo}). Use this information to know what's up with the client's journey.\n\n` + commits.join('\n')); 60 | 61 | // Reply with the embed 62 | await interaction.reply({ embeds: [embed] }); 63 | } 64 | catch (err) { 65 | console.error(err.message); 66 | } 67 | } 68 | }; -------------------------------------------------------------------------------- /commands/profile/rep.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const ms = require('ms'); 5 | 6 | const { SlashCommandBuilder } = require('discord.js'); 7 | 8 | module.exports = class Reputation extends Command { 9 | constructor(client) { 10 | super(client, { 11 | data: new SlashCommandBuilder() 12 | .setName('rep') 13 | .setDescription('Give a reputation point to a user') 14 | .addUserOption(option => option.setName('target').setDescription('The user') 15 | .setRequired(true)) 16 | .setDMPermission(false), 17 | usage: 'rep [user]', 18 | category: 'Profile', 19 | permissions: ['Use Application Commands', 'Send Messages'], 20 | }); 21 | } 22 | /** 23 | * Runs the command to give a reputation point to a user. 24 | * @param {Client} client - The client object. 25 | * @param {Interaction} interaction - The interaction object. 26 | * @returns {Promise} 27 | */ 28 | async run(client, interaction) { 29 | // Get the target user from the interaction options 30 | const member = interaction.options.getUser('target'); 31 | 32 | // Set the timeout and amount for reputation 33 | const timeout = 86400000; 34 | const amount = 1; 35 | 36 | // Get the sender user from the database 37 | const { user } = await db.getUserById(interaction.user.id); 38 | 39 | // Get the receiver user and sender user from the database 40 | const { user: receiver } = await db.getUserById(member.id); 41 | const { user: sender } = await db.getUserById(interaction.user.id); 42 | 43 | // Check if the receiver is the same as the sender 44 | if (receiver.userId === sender.userId) { 45 | return interaction.reply('You cannot give yourself an extra reputation point.'); 46 | } 47 | 48 | // Check if the user has used the rep command recently 49 | if (user.reputation_cooldown !== null && timeout - (Date.now() - user.reputation_cooldown) > 0) { 50 | const time = ms(timeout - (Date.now() - user.reputation_cooldown), { 51 | long: true, 52 | }); 53 | 54 | interaction.reply(`You've already used the rep command recently, \`${time}\` remaining.`); 55 | } 56 | else { 57 | // Update the receiver's reputation and the sender's reputation cooldown time in the database 58 | db.updateUserById(member.id, { 59 | reputation: receiver.reputation + amount, 60 | }); 61 | 62 | db.updateUserById(interaction.user.id, { 63 | reputation_cooldown: Date.now(), 64 | }); 65 | 66 | interaction.reply(`${client.emoji.green_emoji} You gave a reputation point to ${member.username}!`); 67 | } 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /commands/general/avatar.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { EmbedBuilder, SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder } = require('discord.js'); 4 | 5 | module.exports = class Avatar extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('avatar') 10 | .setDescription('Fetches the avatar of a user') 11 | .addUserOption(option => option.setName('target').setDescription('The user') 12 | .setRequired(false)) 13 | .setDMPermission(false), 14 | usage: 'avatar [user]', 15 | category: 'General', 16 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 17 | }); 18 | } 19 | /** 20 | * Run the function. 21 | * @param {Client} client - The Discord client. 22 | * @param {Interaction} interaction - The interaction received. 23 | */ 24 | async run(client, interaction) { 25 | // Get the target user from the interaction options, or use the interaction user as the default 26 | const user = interaction.options.getUser('target') || interaction.user; 27 | 28 | // Create the button row with four buttons 29 | const buttonRow = new ActionRowBuilder() 30 | .addComponents( 31 | // Button to display user avatar as a PNG image 32 | new ButtonBuilder() 33 | .setLabel('png') 34 | .setStyle(ButtonStyle.Link) 35 | .setURL(`${user.displayAvatarURL({ dynamic: true, size: 2048, extension: 'png' })}`), 36 | 37 | // Button to display user avatar as a JPG image 38 | new ButtonBuilder() 39 | .setLabel('jpg') 40 | .setStyle(ButtonStyle.Link) 41 | .setURL(`${user.displayAvatarURL({ dynamic: true, size: 2048, extension: 'jpg' })}`), 42 | 43 | // Button to display user avatar as a JPEG image 44 | new ButtonBuilder() 45 | .setLabel('jpeg') 46 | .setStyle(ButtonStyle.Link) 47 | .setURL(`${user.displayAvatarURL({ dynamic: true, size: 2048, extension: 'jpeg' })}`), 48 | 49 | // Button to display user avatar as a WebP image 50 | new ButtonBuilder() 51 | .setLabel('webp') 52 | .setStyle(ButtonStyle.Link) 53 | .setURL(`${user.displayAvatarURL({ dynamic: true, size: 2048, extension: 'webp' })}`), 54 | ); 55 | 56 | // Create the embed with user information and avatar as a PNG image 57 | const embed = new EmbedBuilder() 58 | .setTitle(user.username) 59 | .setColor(0x2B2D31) 60 | .setImage(user.displayAvatarURL({ dynamic: true, size: 2048, extension: 'png' })); 61 | 62 | // Send the embed and button row as a reply to the interaction 63 | await interaction.reply({ embeds: [embed], components: [buttonRow] }); 64 | } 65 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Storage transcripts 67 | public/transcripts 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* -------------------------------------------------------------------------------- /events/guild/guildMemberRemove.js: -------------------------------------------------------------------------------- 1 | const { AttachmentBuilder } = require('discord.js'); 2 | const { createCanvas, loadImage } = require('canvas'); 3 | 4 | const Event = require('../../structures/EventClass'); 5 | const db = require('../../database/manager'); 6 | 7 | module.exports = class guildMemberRemove extends Event { 8 | constructor(client) { 9 | super(client, { 10 | name: 'guildMemberRemove', 11 | category: 'guild', 12 | }); 13 | } 14 | /** 15 | * Runs the leave logic for a member. 16 | * @param {GuildMember} member - The member to run the function for. 17 | */ 18 | async run(member) { 19 | try { 20 | // Helper function to apply text to a canvas 21 | const applyText = (canvas, text) => { 22 | const ctx = canvas.getContext('2d'); 23 | let fontSize = 70; 24 | do { 25 | ctx.font = `${(fontSize -= 10)}px Sans`; 26 | } while (ctx.measureText(text).width > canvas.width - 256); 27 | return ctx.font; 28 | }; 29 | 30 | // Fetch the server information from the database 31 | const database = await db.findServer(member.guild.id); 32 | const leave = database?.leave; 33 | 34 | // If leave channel is defined 35 | if (leave) { 36 | // Create a canvas 37 | const canvas = createCanvas(750, 256); 38 | const ctx = canvas.getContext('2d'); 39 | 40 | // Load the background image 41 | const background_raw = ('././assets/canva/background.png'); 42 | const background = await loadImage(background_raw); 43 | ctx.drawImage(background, 0, 0, canvas.width, canvas.height); 44 | 45 | // Apply and draw "Goodbye," text 46 | ctx.font = applyText(canvas, 'Goodbye,'); 47 | ctx.fillStyle = '#ffffff'; 48 | ctx.fillText('Goodbye,', canvas.width / 3, canvas.height / 2.45); 49 | 50 | // Apply and draw the username 51 | ctx.font = applyText(canvas, `${member.user.username}`); 52 | ctx.fillStyle = '#ffffff'; 53 | ctx.fillText( 54 | `${member.user.username}`, 55 | canvas.width / 3, 56 | canvas.height / 1.55, 57 | ); 58 | 59 | // Clip the avatar image to a circle and draw it 60 | ctx.beginPath(); 61 | ctx.arc(130, 130, 100, 0, Math.PI * 2, true); 62 | ctx.closePath(); 63 | ctx.clip(); 64 | 65 | const avatar = await loadImage(member.user.displayAvatarURL({ dynamic: true, size: 2048, extension: 'png' })); 66 | ctx.drawImage(avatar, 30, 30, 200, 200); 67 | 68 | // Convert the canvas to a buffer and create an attachment 69 | const attachment = new AttachmentBuilder(canvas.toBuffer(), { name: 'image.png' }); 70 | 71 | // Send the attachment to the leave channel 72 | this.client.channels.cache.get(leave).send({ files: [attachment] }); 73 | } 74 | } 75 | catch (e) { 76 | console.log(e); 77 | } 78 | } 79 | }; -------------------------------------------------------------------------------- /sitemap.js: -------------------------------------------------------------------------------- 1 | const { SitemapStream, streamToPromise } = require('sitemap'); 2 | const { Readable } = require('stream'); 3 | const fs = require('fs'); 4 | const axios = require('axios'); 5 | 6 | // Base URL of your website 7 | const BASE_URL = 'https://www.eres.lol'; 8 | 9 | // Discord API configuration 10 | const DISCORD_API_URL = 'https://discord.com/api/v9/users/@me/guilds'; 11 | const DISCORD_API_TOKEN = process.env.TOKEN; 12 | 13 | // Static pages 14 | const staticPages = [ 15 | { url: '/', changefreq: 'daily', priority: 0.7 }, 16 | { url: '/tos', changefreq: 'monthly', priority: 0.5 }, 17 | { url: '/privacy', changefreq: 'monthly', priority: 0.5 }, 18 | { url: '/stats', changefreq: 'monthly', priority: 0.5 }, 19 | { url: '/release', changefreq: 'monthly', priority: 0.5 }, 20 | { url: '/dashboard/servers', changefreq: 'weekly', priority: 0.8 }, 21 | { url: '/profile/me', changefreq: 'weekly', priority: 0.8 }, 22 | { url: '/admin/panel', changefreq: 'weekly', priority: 0.8 }, 23 | ]; 24 | 25 | /** 26 | * Fetches a list of server IDs from the Discord API and maps them to 27 | * sitemap URLs. 28 | * 29 | * @returns {Promise>} An array of sitemap URLs with 30 | * `url`, `changefreq`, and `priority` properties. 31 | */ 32 | async function fetchServerManageUrls() { 33 | try { 34 | const { data } = await axios.get(DISCORD_API_URL, { 35 | headers: { 36 | Authorization: `Bot ${DISCORD_API_TOKEN}`, 37 | }, 38 | }); 39 | 40 | return data.map((guild) => ({ 41 | url: `/dashboard/server/${guild.id}`, 42 | changefreq: 'weekly', 43 | priority: 0.8, 44 | })); 45 | } 46 | catch (error) { 47 | console.error('Error fetching server IDs from Discord API:', error); 48 | return []; 49 | } 50 | } 51 | 52 | /** 53 | * Generates a sitemap XML file by combining static and dynamic pages. 54 | * Fetches dynamic server management URLs from the Discord API and merges 55 | * them with predefined static page URLs. The combined list of URLs is then 56 | * converted to an XML sitemap format and saved to 'public/sitemap.xml'. 57 | * Logs success message upon completion or logs an error if the process fails. 58 | */ 59 | async function generateSitemap() { 60 | try { 61 | // Fetch dynamic manage pages 62 | const dynamicPages = await fetchServerManageUrls(); 63 | const links = [...staticPages, ...dynamicPages]; 64 | 65 | // Write the sitemap to a file 66 | const stream = new SitemapStream({ hostname: BASE_URL }); 67 | const xml = await streamToPromise(Readable.from(links).pipe(stream)).then((data) => 68 | data.toString(), 69 | ); 70 | 71 | // Save the sitemap to a file 72 | fs.writeFileSync('public/sitemap.xml', xml); 73 | console.log('Sitemap generated successfully!'); 74 | } 75 | catch (error) { 76 | console.error('Error generating sitemap:', error); 77 | } 78 | } 79 | 80 | 81 | // Generate sitemap 82 | generateSitemap(); -------------------------------------------------------------------------------- /functions/Emojis.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-inline-comments */ 2 | module.exports = (client) => { 3 | /** 4 | * This function initializes the client's emoji dictionary. 5 | * Each emoji is assigned a specific key for easy access. 6 | */ 7 | client.emoji = async () => { 8 | // Define the emoji dictionary 9 | client.emoji = { 10 | red_emoji: '<:red_emoji:1126936340022435963>', // Red emoji 11 | green_emoji: '<:green_emoji:1126936345043030026>', // Green emoji 12 | 13 | clock: '⏱', // Clock emoji 14 | heartbeat: '💓', // Heartbeat emoji 15 | 16 | balance: '<:balance_emoji:1129875960188112966>', // Balance emoji 17 | star: '<:star_emoji:1126279940321574913>', // Star emoji 18 | ticket: '<:ticket_emoji:1170117538433212538>', // Ticket emoji 19 | music: '<:music_emoji:1188172803934011442>', // Music emoji 20 | moon: '<:moon_emoji:1139513847238119425>', // Moon emoji 21 | mail: '<:mail_emoji:1170364505616826398>', // Mail emoji 22 | verify: '<:verify_emoji:1139514506884681850>', // Verify emoji 23 | partnered: '<:partner_emoji:1139514892320251905>', // Partnered emoji 24 | crown: '<:owner_emoji:1139506434707574874>', // Crown emoji 25 | 26 | staff_emoji: '<:staff_badge:1139567579371946064>', // Staff badge emoji 27 | partnered_badge: '<:partner_badge:1139567761287282698>', // Partner badge emoji 28 | hypesquad: '<:hypesquad_badge:1139568237764427806>', // Hypesquad badge emoji 29 | hunter_one: '<:bughunter_level1_badge:1139571311534931978>', // Hunter level 1 badge emoji 30 | hunter_two: '<:bughunter_level2_badge:1139571316542947468>', // Hunter level 2 badge emoji 31 | hypesquad_house_one: '<:house1_badge:1139571326621843496>', // Hypesquad house 1 badge emoji 32 | hypesquad_house_two: '<:house2_badge:1139571331671793685>', // Hypesquad house 2 badge emoji 33 | hypesquad_house_tree: '<:house3_badge:1139571337136963695>', // Hypesquad house 3 badge emoji 34 | early_supporter: '<:early_supporter_badge:1139571489671229631>', // Early supporter badge emoji 35 | discord_system: '<:system_badge:1139571345039036487>', // Discord system badge emoji 36 | verified_bot: '<:verified_bot_badge:1139571349262696509>', // Verified bot badge emoji 37 | verified_developer: '<:verified_developer_badge:1139571354325237790>', // Verified developer badge emoji 38 | certified_moderator: '<:certified_moderator_badge:1139571322368835685>', // Certified moderator badge emoji 39 | active_developer: '<:active_developer_badge:1139571306313039872>', // Active developer badge emoji 40 | 41 | flag_emoji: '<:flag_emoji:1129876196549738626>', // Flag emoji 42 | gheart: '<:gheart_emoji:1129876399134625902>', // Green heart emoji 43 | heart: '<:heart_emoji:1129876126047670403>', // Heart emoji 44 | seven: '<:seven_emoji:1129876077041422356>', // Seven emoji 45 | skull: '<:skull_emoji:1129876004748411041>', // Skull emoji 46 | snowman: '<:snowman_emoji:1129876037493334017>', // Snowman emoji 47 | }; 48 | }; 49 | }; -------------------------------------------------------------------------------- /events/guild/guildMemberAdd.js: -------------------------------------------------------------------------------- 1 | const { AttachmentBuilder } = require('discord.js'); 2 | const { createCanvas, loadImage } = require('canvas'); 3 | 4 | const Event = require('../../structures/EventClass'); 5 | const db = require('../../database/manager'); 6 | 7 | module.exports = class guildMemberAdd extends Event { 8 | constructor(client) { 9 | super(client, { 10 | name: 'guildMemberAdd', 11 | category: 'guild', 12 | }); 13 | } 14 | /** 15 | * Runs the welcome and autorole logic for a member. 16 | * @param {GuildMember} member - The member to run the logic for. 17 | */ 18 | async run(member) { 19 | try { 20 | // Helper function to apply text to a canvas and determine the appropriate font size. 21 | const applyText = (canvas, text) => { 22 | const ctx = canvas.getContext('2d'); 23 | let fontSize = 70; 24 | do { 25 | ctx.font = `${(fontSize -= 10)}px Sans`; 26 | } while (ctx.measureText(text).width > canvas.width - 256); 27 | return ctx.font; 28 | }; 29 | 30 | // Find server database for the guild the member belongs to. 31 | const database = await db.findServer(member.guild.id); 32 | const welcome = database?.welcome; 33 | const autorole = database?.autorole; 34 | 35 | // If welcome message is enabled, generate and send the welcome message. 36 | if (welcome) { 37 | const canvas = createCanvas(750, 256); 38 | const ctx = canvas.getContext('2d'); 39 | 40 | const background_raw = ('././assets/canva/background.png'); 41 | 42 | const background = await loadImage(background_raw); 43 | ctx.drawImage(background, 0, 0, canvas.width, canvas.height); 44 | 45 | // Draw the "Welcome," text on the canvas. 46 | ctx.font = applyText(canvas, 'Welcome,'); 47 | ctx.fillStyle = '#ffffff'; 48 | ctx.fillText('Welcome,', canvas.width / 3, canvas.height / 2.45); 49 | 50 | // Draw the member's username on the canvas. 51 | ctx.font = applyText(canvas, `${member.user.username}`); 52 | ctx.fillStyle = '#ffffff'; 53 | ctx.fillText( 54 | `${member.user.username}`, 55 | canvas.width / 3, 56 | canvas.height / 1.55, 57 | ); 58 | 59 | // Clip the canvas to a circle and draw the member's avatar. 60 | ctx.beginPath(); 61 | ctx.arc(130, 130, 100, 0, Math.PI * 2, true); 62 | ctx.closePath(); 63 | ctx.clip(); 64 | 65 | const avatar = await loadImage(member.user.displayAvatarURL({ dynamic: true, size: 2048, extension: 'png' })); 66 | ctx.drawImage(avatar, 30, 30, 200, 200); 67 | 68 | // Create an attachment from the canvas and send it to the welcome channel. 69 | const attachment = new AttachmentBuilder(canvas.toBuffer(), { name: 'image.png' }); 70 | this.client.channels.cache.get(welcome).send({ files: [attachment] }); 71 | } 72 | 73 | // If autorole is enabled, add the specified role to the member. 74 | if (autorole) { 75 | member.roles.add(autorole); 76 | } 77 | } 78 | catch (e) { 79 | console.log(e); 80 | } 81 | } 82 | }; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | 4 | const bodyparser = require('body-parser'); 5 | const session = require('express-session'); 6 | 7 | const path = require('path'); 8 | const ejs = require('ejs'); 9 | 10 | const cookieParser = require('cookie-parser'); 11 | const i18n = require('i18n'); 12 | 13 | const passport = require('passport'); 14 | const { Strategy } = require('passport-discord'); 15 | 16 | /** 17 | * Load function is responsible for initializing and configuring the express app, setting up middleware, 18 | * defining routes, and starting the web server. 19 | * 20 | * @param {Client} client - the client object used for interacting with external services 21 | */ 22 | module.exports.load = async (client) => { 23 | app.use(bodyparser.json()); 24 | app.use(bodyparser.urlencoded({ extended: true })); 25 | app.use(express.json()); 26 | app.engine('html', ejs.renderFile); 27 | app.set('view engine', 'ejs'); 28 | app.set('views', path.join(__dirname, '/views')); 29 | app.use(express.static(path.join(__dirname, '/public'))); 30 | app.use(session({ 31 | secret: process.env.CLIENT_SECRET, 32 | resave: false, 33 | saveUninitialized: false, 34 | })); 35 | 36 | // Configure cookie parser 37 | app.use(cookieParser()); 38 | 39 | // Configure i18n 40 | i18n.configure({ 41 | locales: ['en', 'ro'], 42 | directory: __dirname + '/i18n', 43 | defaultLocale: 'en', 44 | cookie: 'locale', 45 | autoReload: false, 46 | updateFiles: false, 47 | objectNotation: true, 48 | }); 49 | 50 | // Initialize passport and restore authentication state, if any, from the session. 51 | app.use(async function(req, res, next) { 52 | req.client = client; 53 | next(); 54 | }); 55 | 56 | app.use(passport.initialize()); 57 | app.use(passport.session()); 58 | 59 | passport.serializeUser((user, done) => { 60 | done(null, user); 61 | }); 62 | 63 | passport.deserializeUser((obj, done) => { 64 | done(null, obj); 65 | }); 66 | 67 | passport.use(new Strategy({ 68 | clientID: process.env.CLIENT_ID, 69 | clientSecret: process.env.CLIENT_SECRET, 70 | callbackURL: process.env.CALLBACK_URL, 71 | scope: [ 'identify', 'guilds' ], 72 | }, function(accessToken, refreshToken, profile, done) { 73 | process.nextTick(function() { 74 | return done(null, profile); 75 | }); 76 | })); 77 | 78 | 79 | // Initialize i18n middleware and set the default locale to 'en' if it's not set in the session or in the request headers or cookies or in the environment 80 | app.use(i18n.init); 81 | 82 | // Define routes here 83 | app.use('/', require('./routes/index')); 84 | app.use('/dashboard', require('./routes/dashboard')); 85 | 86 | // 404 handler 87 | app.get('*', (req, res) => { 88 | res.render('../views/404', { 89 | bot: req.client, 90 | user: req.user, 91 | }); 92 | }); 93 | 94 | // Start the web server 95 | app.listen(process.env.PORT, () => { 96 | console.log(`[Dashboard]: Web server now online on port:${process.env.PORT} 📶.`); 97 | }); 98 | }; -------------------------------------------------------------------------------- /commands/player/skip.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | const { Collection } = require('discord.js'); 4 | 5 | const db = require('../../database/manager'); 6 | const { Player } = require('discord-player'); 7 | 8 | const skipVotes = new Collection(); 9 | 10 | module.exports = class Skip extends Command { 11 | constructor(client) { 12 | super(client, { 13 | data: new SlashCommandBuilder() 14 | .setName('skip') 15 | .setDescription('Skips the current track (add a djrole on dashboard to skip without the vote request)') 16 | .setDMPermission(false), 17 | usage: 'skip', 18 | category: 'Player', 19 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 20 | }); 21 | } 22 | 23 | /** 24 | * Handles the "skip" command when invoked by a user. 25 | * @param {Client} client - The Discord client object. 26 | * @param {Interaction} interaction - The interaction object representing the command interaction. 27 | */ 28 | async run(client, interaction) { 29 | // Get the player singleton instance and the queue for the current guild 30 | const player = Player.singleton(); 31 | const queue = player.nodes.get(interaction.guild.id); 32 | const database = await db.findServer(interaction.guild.id); 33 | 34 | // Check if there is any music currently playing 35 | if (!queue || !queue.isPlaying()) { 36 | return await interaction.reply(`${client.emoji.red_emoji} There isn't currently any music playing.`); 37 | } 38 | 39 | // Get the number of users in the voice channel 40 | const usersInChannel = interaction.member.voice.channel.members.filter(member => !member.user.bot).size; 41 | 42 | // Get the DJ role from the database 43 | const djRole = database?.djrole; 44 | 45 | // Initialize the skipVotes set if it doesn't exist 46 | if (!skipVotes.has(interaction.guild.id)) { 47 | skipVotes.set(interaction.guild.id, new Set()); 48 | } 49 | 50 | // Add the user's vote to skip the track 51 | const skipVotesTotal = skipVotes.get(interaction.guild.id); 52 | skipVotesTotal.add(interaction.user.id); 53 | 54 | // Check if the number of skip votes reaches the required threshold 55 | if (skipVotesTotal.size >= usersInChannel / 2) { 56 | // Skip the current track and reset the skipVotes set 57 | queue.node.skip(); 58 | skipVotes.delete(interaction.guild.id); 59 | return await interaction.reply(`${client.emoji.green_emoji} The track **${queue.currentTrack.title}** was skipped.`); 60 | } 61 | else if (interaction.member.roles.cache.has(djRole)) { 62 | queue.node.skip(); 63 | return await interaction.reply(`${client.emoji.green_emoji} The track **${queue.currentTrack.title}** was skipped.`); 64 | } 65 | else { 66 | // Calculate the number of votes needed to skip the track 67 | const votesNeeded = usersInChannel / 2; 68 | return await interaction.reply(`You voted to skip. Required ${Math.round(votesNeeded - 1)} more vote(s) needed to skip the track.`); 69 | } 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /commands/app/bug.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const ms = require('ms'); 5 | 6 | const { SlashCommandBuilder, EmbedBuilder, WebhookClient } = require('discord.js'); 7 | 8 | module.exports = class Bug extends Command { 9 | constructor(client) { 10 | super(client, { 11 | data: new SlashCommandBuilder() 12 | .setName('bug') 13 | .setDescription('Report a bug you\'ve discovered') 14 | .addStringOption(option => 15 | option.setName('string') 16 | .setDescription('Bug details (e.g. got less xp than it\'s showed I should get)') 17 | .setRequired(true)) 18 | .setDMPermission(false), 19 | usage: 'bug [string]', 20 | category: 'App', 21 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 22 | }); 23 | } 24 | /** 25 | * Runs the function with the given client and interaction parameters. 26 | * 27 | * @param {Client} client - The Discord client. 28 | * @param {Interaction} interaction - The interaction object. 29 | * @return {Promise} Returns nothing. 30 | */ 31 | async run(client, interaction) { 32 | // Retrieve user and analytics data from the database 33 | const { user } = await db.getUserById(interaction.user.id); 34 | const { data } = await db.getAnalysticsById(process.env.ANALYTICS_ID); 35 | 36 | const timeout = 1800000; 37 | const string = interaction.options.getString('string'); 38 | 39 | // Check if the user is on cooldown 40 | if (user.report_cooldown !== null && timeout - (Date.now() - user.report_cooldown) > 0) { 41 | const time = ms(timeout - (Date.now() - user.report_cooldown), { 42 | long: true, 43 | }); 44 | 45 | interaction.reply(`You've already used the report command recently, \`${time}\` remaining.`); 46 | } 47 | else { 48 | // Create a webhook client and construct the embed 49 | const webhookClient = new WebhookClient({ 50 | url: `https://discord.com/api/webhooks/${process.env.WEBHOOK_ID}/${process.env.WEBHOOK_TOKEN}`, 51 | }); 52 | 53 | const embed = new EmbedBuilder() 54 | .setAuthor({ name: `${interaction.user.username} (ID: ${interaction.user.id})`, iconURL: interaction.user.displayAvatarURL({ extension: 'png', size: 1024 }) }) 55 | .setColor(0x2B2D31) 56 | .addFields( 57 | { name: 'Bug Description', value: `${string}` }, 58 | ) 59 | .setThumbnail(interaction.user.displayAvatarURL({ extension: 'png', size: 1024 })); 60 | 61 | // Send the webhook with the embed 62 | webhookClient.send({ 63 | username: client.user.username, 64 | avatarURL: client.user.displayAvatarURL({ extension: 'png', size: 1024 }), 65 | content: `\`Report: ${data.reports}\``, 66 | embeds: [embed], 67 | }); 68 | 69 | // Increment the reports count and update the user's cooldown 70 | db.incrementReports(); 71 | db.updateUserById(interaction.user.id, { 72 | report_cooldown: Date.now(), 73 | }); 74 | 75 | await interaction.reply(`${client.emoji.green_emoji} Successfully sent the report. Thanks for your feedback!`); 76 | } 77 | } 78 | }; -------------------------------------------------------------------------------- /commands/general/commands.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | const Command = require('../../structures/CommandClass'); 3 | 4 | const { EmbedBuilder, SlashCommandBuilder } = require('discord.js'); 5 | 6 | module.exports = class Commands extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('commands') 11 | .setDescription('Fetches available commands') 12 | .addStringOption(option => 13 | option.setName('command') 14 | .setDescription('Type a command to get specif usage help') 15 | .setRequired(false)) 16 | .setDMPermission(false), 17 | usage: 'commands [commandName]', 18 | category: 'General', 19 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 20 | }); 21 | } 22 | /** 23 | * Runs the command when invoked by a user. 24 | * 25 | * @param {Client} client - The Discord client. 26 | * @param {Interaction} interaction - The interaction object representing the command invocation. 27 | */ 28 | async run(client, interaction) { 29 | // Get the i18n instance for the guild 30 | const i18n = await client.i18n.get(interaction.guild.id); 31 | 32 | // Categorize the commands 33 | const categorizedCommands = {}; 34 | client.commands.forEach(command => { 35 | if (!categorizedCommands[command.category]) { 36 | categorizedCommands[command.category] = []; 37 | } 38 | categorizedCommands[command.category].push(command.name); 39 | }); 40 | 41 | // Check if a specific command is requested 42 | const commandName = interaction.options.getString('command'); 43 | if (commandName) { 44 | const command = client.commands.get(commandName); 45 | if (command) { 46 | const usage = command.usage; 47 | 48 | // Create an embed with the command usage 49 | const embed = new EmbedBuilder() 50 | .setColor(0x2B2D31) 51 | .setDescription(client.i18n.handle('GENERAL', 'COMMAND_USAGE', i18n).replace('{commandName}', commandName).replace('{commandUsage}', usage)); 52 | 53 | // Reply to the interaction with the embed 54 | await interaction.reply({ embeds: [embed] }); 55 | return; 56 | } 57 | } 58 | 59 | // Create the main embed with the list of commands 60 | const embed = new EmbedBuilder() 61 | .setAuthor({ name: `${client.user.username} • ${client.i18n.handle('GENERAL', 'COMMANDS', i18n)}`, iconURL: client.user.displayAvatarURL({ dynamic: true, size: 2048, format: 'png' }) }) 62 | .setColor(0x2B2D31) 63 | .setDescription(client.i18n.handle('GENERAL', 'COMMANDS_SUB', i18n)) 64 | .setFooter({ 65 | text: client.i18n.handle('GENERAL', 'COMMANDS_FOOTER', i18n), 66 | }) 67 | .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 128, format: 'png' })); 68 | 69 | // Add fields to the embed for each category of commands 70 | for (const category in categorizedCommands) { 71 | const commandList = categorizedCommands[category].join(', '); 72 | embed.addFields( 73 | { name: category, value: commandList, inline: true }, 74 | ); 75 | } 76 | 77 | // Reply to the interaction with the embed 78 | await interaction.reply({ embeds: [embed] }); 79 | } 80 | }; -------------------------------------------------------------------------------- /commands/developer/database.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | 4 | const db = require('../../database/manager'); 5 | 6 | module.exports = class Database extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('database') 11 | .setDescription('Change mongoose database values') 12 | .addUserOption(option => option.setName('target').setDescription('The user') 13 | .setRequired(true)) 14 | .addStringOption(option => 15 | option.setName('type') 16 | .setDescription('Database field (e.g. xp, balance, reputation)') 17 | .setRequired(true)) 18 | .addNumberOption((option) => 19 | option.setName('value') 20 | .setDescription('Data value (min: 1 max: 9999999)') 21 | .setMinValue(1) 22 | .setMaxValue(9999999) 23 | .setRequired(true)) 24 | .setDMPermission(false), 25 | usage: 'database [user] [string] [number]', 26 | category: 'Developer', 27 | permissions: ['Use Application Commands', 'Send Messages'], 28 | }); 29 | } 30 | /** 31 | * Runs the function with the given client and interaction parameters. 32 | * 33 | * @param {Client} client - The Discord client object. 34 | * @param {Interaction} interaction - The interaction object representing the interaction with the user. 35 | * @return {Promise} - A promise that is resolved when the function has completed. 36 | */ 37 | async run(client, interaction) { 38 | // Check if the user is authorized 39 | if (interaction.user.id !== process.env.DEVELOPER_ID) { 40 | return interaction.reply(`${client.emoji.red_emoji} Missing \`DEVELOPER\` permission.`); 41 | } 42 | 43 | const member = interaction.options.getUser('target'); 44 | const type = interaction.options.getString('type'); 45 | const value = interaction.options.getNumber('value'); 46 | 47 | switch (type) { 48 | case 'xp': { 49 | // Get the user from the database 50 | const { user } = await db.getUserById(member.id); 51 | 52 | // Update the user's xp 53 | await db.updateUserById(member.id, { 54 | xp: user.xp + Number(value), 55 | }); 56 | 57 | return interaction.reply( 58 | `${client.emoji.green_emoji} Successfully added \`${value}\` xp to ${member.username}.`, 59 | ); 60 | } 61 | case 'balance': { 62 | // Get the user from the database 63 | const { user } = await db.getUserById(member.id); 64 | 65 | // Update the user's balance 66 | await db.updateUserById(member.id, { 67 | balance: user.balance + Number(value), 68 | }); 69 | 70 | return interaction.reply( 71 | `${client.emoji.green_emoji} Successfully added \`${value}\` coins to ${member.username}.`, 72 | ); 73 | } 74 | case 'reputation': { 75 | // Get the user from the database 76 | const { user } = await db.getUserById(member.id); 77 | 78 | // Update the user's reputation 79 | await db.updateUserById(member.id, { 80 | reputation: user.reputation + Number(value), 81 | }); 82 | 83 | return interaction.reply( 84 | `${client.emoji.green_emoji} Successfully added \`${value}\` reputation points to ${member.username}.`, 85 | ); 86 | } 87 | default: { 88 | return interaction.reply(`${client.emoji.red_emoji} Error: \`${type}\` is not a valid type.`); 89 | } 90 | } 91 | } 92 | }; -------------------------------------------------------------------------------- /commands/economy/fish.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const fishJson = require('../../assets/json/fish.json'); 5 | const { stripIndent } = require('common-tags'); 6 | const ms = require('ms'); 7 | 8 | const { SlashCommandBuilder } = require('discord.js'); 9 | 10 | function randomRange(min, max) { 11 | return Math.floor(Math.random() * (max - min + 1)) + min; 12 | } 13 | 14 | module.exports = class Fish extends Command { 15 | constructor(client) { 16 | super(client, { 17 | data: new SlashCommandBuilder() 18 | .setName('fish') 19 | .setDescription('Play at fishing minigame to warn XP and coins') 20 | .setDMPermission(false), 21 | usage: 'fish', 22 | category: 'Economy', 23 | permissions: ['Use Application Commands', 'Send Messages'], 24 | }); 25 | } 26 | /** 27 | * Runs the fish minigame when the user interacts with the command. 28 | * 29 | * @param {Client} client - The Discord client instance. 30 | * @param {Interaction} interaction - The interaction object representing the user's interaction with the command. 31 | * @return {Promise} - Returns a promise that resolves once the function is complete. 32 | */ 33 | async run(client, interaction) { 34 | // Retrieve user data from the database 35 | 36 | const { user } = await db.getUserById(interaction.user.id); 37 | 38 | // Generate a random fish ID 39 | const fishID = Math.floor(Math.random() * 10) + 1; 40 | 41 | // Determine the rarity of the fish based on the fish ID 42 | let rarity; 43 | if (fishID < 5) rarity = 'junk'; 44 | else if (fishID < 8) rarity = 'common'; 45 | else if (fishID < 9) rarity = 'uncommon'; 46 | else if (fishID < 10) rarity = 'rare'; 47 | else rarity = 'legendary'; 48 | 49 | // Get the fish data based on its rarity 50 | const fish = fishJson[rarity]; 51 | 52 | // Generate a random worth for the fish within its min and max values 53 | const worth = randomRange(fish.min, fish.max); 54 | 55 | // Check if the user has a fish cooldown 56 | if (user.fish_cooldown !== null && 600000 - (Date.now() - user.fish_cooldown) > 0) { 57 | // Calculate the remaining time until the fish cooldown is over 58 | const time = ms(600000 - (Date.now() - user.fish_cooldown), { 59 | long: true, 60 | }); 61 | 62 | // Reply to the interaction with the remaining cooldown time 63 | interaction.reply(`You've already fished recently, \`${time}\` remaining.`); 64 | } 65 | else { 66 | // Generate a random XP value for the fish 67 | const xp = Math.floor(Math.random() * 10) + 1; 68 | 69 | // Reply to the interaction with the fish minigame details 70 | interaction.reply(stripIndent ` 71 | [ :: **FISH MINIGAME** :: ] 72 | ---------------------------- 73 | Fisherman: 74 | ${interaction.user.username} 75 | Caught: 76 | ${fish.symbol} 77 | Coins earned: 78 | ${worth} ${client.emoji.balance} 79 | XP earned: 80 | ${xp} ${client.emoji.star} 81 | ---------------------------- 82 | [ :: **FISH MINIGAME** :: ] 83 | `); 84 | 85 | // Update the user's balance, XP, and fish cooldown in the database 86 | await db.updateUserById(interaction.user.id, { 87 | balance: user.balance + worth, 88 | xp: user.xp + xp, 89 | fish_cooldown: Date.now(), 90 | }); 91 | } 92 | } 93 | }; -------------------------------------------------------------------------------- /views/profile/me.ejs: -------------------------------------------------------------------------------- 1 | <%- include("../partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - <%= user.username %><%= user.discriminator && user.discriminator !== '0' ? `#${user.discriminator}` : '' %> profile 4 | 5 |
6 |
7 | 8 |
<%= user.username %><%= user.discriminator && user.discriminator !== '0' ? `#${user.discriminator}` : '' %>
9 |
<%- __('WEB.PROFILE_LEVEL')%> <%= level %> (<%= xp %>/<%= maxXp %>)
10 | 17 |
18 | 21 |
22 | 23 |
24 |
25 |
26 |
<%= about %>
27 | 28 |
29 |
30 | 31 |
32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
eres.fun coins 42 | <%= balance %> 43 |
44 |
45 | 46 |
47 |
eres.fun repu 48 | <%= reputation %> 49 |
50 |
51 |
52 |
53 | 54 | <%- include("../partials/footer") %> -------------------------------------------------------------------------------- /commands/player/queue.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | 4 | const { Player } = require('discord-player'); 5 | 6 | module.exports = class Queue extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('queue') 11 | .setDescription('Shows all tracks currently in the server queue') 12 | .setDMPermission(false), 13 | usage: 'queue', 14 | category: 'Player', 15 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 16 | }); 17 | } 18 | /** 19 | * Runs the command when triggered by a user interaction. 20 | * 21 | * @param {Client} client - The Discord client instance. 22 | * @param {Interaction} interaction - The interaction object representing the user interaction. 23 | * @returns {Promise} - A promise that resolves once the command has finished executing. 24 | */ 25 | async run(client, interaction) { 26 | // Get the singleton instance of the Player class 27 | const player = Player.singleton(); 28 | 29 | // Get the queue for the guild from the player's nodes 30 | const queue = player.nodes.get(interaction.guild.id); 31 | 32 | // Create an EmbedBuilder object 33 | const embed = new EmbedBuilder() 34 | .setColor(0x2F3136); 35 | 36 | // Check if there is no queue or if the queue is not playing any music 37 | if (!queue || !queue.isPlaying()) { 38 | return await interaction.reply(`${client.emoji.red_emoji} There isn't currently any music playing.`); 39 | } 40 | 41 | // Convert the queued tracks to an array 42 | const queuedTracks = queue.tracks.toArray(); 43 | 44 | // Check if there are no other tracks in the queue 45 | if (!queuedTracks[0]) { 46 | return await interaction.reply('There aren\'t any other tracks in the queue. Use `/nowplaying` to show information about the current track.'); 47 | } 48 | 49 | // Set the thumbnail for the embed using the guild's icon URL or the client's user display avatar URL 50 | embed.setThumbnail(interaction.guild.iconURL({ size: 2048, dynamic: true }) || client.user.displayAvatarURL({ size: 2048, dynamic: true })); 51 | 52 | // Set the author for the embed with the server queue name 53 | embed.setAuthor({ name: `Server Queue - ${interaction.guild.name}` }); 54 | 55 | // Map the queued tracks to a formatted string with track information 56 | const tracks = queuedTracks.map((track, i) => { 57 | return `\`${i + 1}\` [${track.title}](${track.url}) by **${track.author}** (Requested by <@${track.requestedBy.id}>)`; 58 | }); 59 | 60 | // Get the number of queued tracks 61 | const songs = queuedTracks.length; 62 | 63 | // Check if there are more than 5 queued tracks 64 | const nextSongs = songs > 5 ? `And **${songs - 5}** other ${songs - 5 > 1 ? 'tracks' : 'track'} currently in queue.` : ''; 65 | 66 | // Create a progress bar using the queue's node 67 | const progress = queue.node.createProgressBar(); 68 | 69 | // Set the description for the embed with the current track, progress bar, and queued tracks 70 | embed.setDescription(`**Current Track:** [${queue.currentTrack.title}](${queue.currentTrack.url}) by **${queue.currentTrack.author}**\n${progress}\n\n${tracks.slice(0, 5).join('\n')}\n\n${nextSongs}`); 71 | 72 | // Reply to the interaction with the embed 73 | return await interaction.reply({ embeds: [embed] }); 74 | } 75 | }; -------------------------------------------------------------------------------- /commands/economy/hunt.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const huntJson = require('../../assets/json/hunt.json'); 5 | const { stripIndent } = require('common-tags'); 6 | const ms = require('ms'); 7 | 8 | const { SlashCommandBuilder } = require('discord.js'); 9 | 10 | /** 11 | * Generates a random number within a specified range. 12 | * 13 | * @param {number} min - The minimum value of the range. 14 | * @param {number} max - The maximum value of the range. 15 | * @return {number} A random number within the specified range. 16 | */ 17 | function randomRange(min, max) { 18 | return Math.floor(Math.random() * (max - min + 1)) + min; 19 | } 20 | 21 | module.exports = class Hunt extends Command { 22 | constructor(client) { 23 | super(client, { 24 | data: new SlashCommandBuilder() 25 | .setName('hunt') 26 | .setDescription('Play at hunting minigame to warn XP and coins') 27 | .setDMPermission(false), 28 | usage: 'hunt', 29 | category: 'Economy', 30 | permissions: ['Use Application Commands', 'Send Messages'], 31 | }); 32 | } 33 | /** 34 | * Runs the function with the given client and interaction. 35 | * 36 | * @param {Client} client - The client object. 37 | * @param {Interaction} interaction - The interaction object. 38 | * @return {Promise} A promise that resolves when the function is finished. 39 | */ 40 | async run(client, interaction) { 41 | // Get user information from the database 42 | const { user } = await db.getUserById(interaction.user.id); 43 | 44 | // Generate a random hunt ID to determine rarity 45 | let rarity; 46 | const huntID = Math.floor(Math.random() * 10) + 1; 47 | if (huntID < 5) rarity = 'junk'; 48 | else if (huntID < 8) rarity = 'common'; 49 | else if (huntID < 9) rarity = 'uncommon'; 50 | else if (huntID < 10) rarity = 'rare'; 51 | else rarity = 'legendary'; 52 | 53 | // Get animal information based on rarity 54 | const animal = huntJson[rarity]; 55 | 56 | // Generate a random worth within the animal's min and max range 57 | const worth = randomRange(animal.min, animal.max); 58 | 59 | // Check if the user has a cooldown for hunting 60 | if (user.hunt_cooldown !== null && 600000 - (Date.now() - user.hunt_cooldown) > 0) { 61 | // Calculate the remaining cooldown time 62 | const time = ms(600000 - (Date.now() - user.hunt_cooldown), { long: true }); 63 | 64 | // Reply with the remaining cooldown time 65 | interaction.reply(`You've already hunted recently, \`${time}\` remaining.`); 66 | } 67 | else { 68 | // Generate a random amount of xp 69 | const xp = Math.floor(Math.random() * 10) + 1; 70 | 71 | // Reply with the hunt results 72 | interaction.reply(stripIndent` 73 | [ :: **HUNT MINIGAME** :: ] 74 | ---------------------------- 75 | Hunter: 76 | ${interaction.user.username} 77 | Killed: 78 | ${animal.symbol} 79 | Coins earned: 80 | ${worth} ${client.emoji.balance} 81 | XP earned: 82 | ${xp} ${client.emoji.star} 83 | ---------------------------- 84 | [ :: **HUNT MINIGAME** :: ] 85 | `); 86 | 87 | // Update the user's balance, xp, and hunt cooldown in the database 88 | await db.updateUserById(interaction.user.id, { 89 | balance: user.balance + worth, 90 | xp: user.xp + xp, 91 | hunt_cooldown: Date.now(), 92 | }); 93 | } 94 | } 95 | }; -------------------------------------------------------------------------------- /events/guild/interactionCreate.js: -------------------------------------------------------------------------------- 1 | const Event = require('../../structures/EventClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const { Player, QueryType } = require('discord-player'); 5 | const { InteractionType } = require('discord.js'); 6 | 7 | module.exports = class InteractionCreate extends Event { 8 | constructor(client) { 9 | super(client, { 10 | name: 'interactionCreate', 11 | category: 'interactions', 12 | }); 13 | } 14 | /** 15 | * Handles the interaction received by the bot. 16 | * @param {Interaction} interaction - The interaction object. 17 | */ 18 | async run(interaction) { 19 | const client = this.client; 20 | const player = Player.singleton(client); 21 | 22 | // Handle application commands 23 | if (interaction.type === InteractionType.ApplicationCommand) { 24 | const command = client.commands.get(interaction.commandName); 25 | 26 | // Ignore interactions from bots 27 | if (interaction.user.bot) { 28 | return; 29 | } 30 | 31 | // Reply if the interaction is not in a guild 32 | if (!interaction.inGuild()) { 33 | return interaction.reply({ 34 | content: 'You must be in a server to use commands.', 35 | }); 36 | } 37 | 38 | // Handle unavailable command 39 | if (!command) { 40 | return interaction.reply({ 41 | content: 'This command is unavailable. *Check back later.*', 42 | ephemeral: true, 43 | }) && client.commands.delete(interaction.commandName); 44 | } 45 | 46 | // Run the command and add value in database 47 | try { 48 | command.run(client, interaction); 49 | db.incrementCommandsUsed(); 50 | } 51 | catch (e) { 52 | console.log(e); 53 | return interaction.reply({ 54 | content: `An error has occurred.\n\n**\`${e.message}\`**`, 55 | }); 56 | } 57 | } 58 | 59 | // Handle modal submit interactions 60 | if (!interaction.isAutocomplete()) return; 61 | if (interaction.commandName === 'play') { 62 | if (interaction.options.getString('song')) { 63 | 64 | // Perform the search operation here 65 | const results = await player.search(interaction.options.getString('song')); 66 | 67 | // Filter the results based on the search engine 68 | const resultsYouTube = await player.search(results, { searchEngine: QueryType.YOUTUBE }); 69 | const resultsSpotify = await player.search(results, { searchEngine: QueryType.SPOTIFY_SEARCH }); 70 | 71 | // Format the Youtube results 72 | const tracksYouTube = resultsYouTube.tracks.slice(0, 5).map((t) => ({ 73 | name: `YouTube: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`, 74 | value: t.url, 75 | })); 76 | 77 | // Format the Spotify results 78 | const tracksSpotify = resultsSpotify.tracks.slice(0, 5).map((t) => ({ 79 | name: `Spotify: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`, 80 | value: t.url, 81 | })); 82 | 83 | // Create an array of tracks 84 | const tracks = []; 85 | 86 | // Push the formatted results 87 | tracksYouTube.forEach((t) => tracks.push({ name: t.name, value: t.value })); 88 | tracksSpotify.forEach((t) => tracks.push({ name: t.name, value: t.value })); 89 | 90 | // Respond with the formatted results 91 | return interaction.respond(tracks); 92 | } 93 | } 94 | } 95 | }; -------------------------------------------------------------------------------- /commands/economy/transfer.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const { SlashCommandBuilder } = require('discord.js'); 5 | 6 | module.exports = class Transfer extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('transfer') 11 | .setDescription('Transfer coins to another user') 12 | .addUserOption(option => option.setName('target').setDescription('The user') 13 | .setRequired(true)) 14 | .addNumberOption((option) => 15 | option.setName('amount') 16 | .setDescription('Amount of coins (min: 1 max: 9999999)') 17 | .setMinValue(1) 18 | .setMaxValue(9999999) 19 | .setRequired(true)) 20 | .setDMPermission(false), 21 | usage: 'transfer [user] [number]', 22 | category: 'Economy', 23 | permissions: ['Use Application Commands', 'Send Messages'], 24 | }); 25 | } 26 | /** 27 | * Runs the function asynchronously. 28 | * 29 | * @param {Client} client - The client object. 30 | * @param {Interaction} interaction - The interaction object. 31 | * @returns {Promise} A Promise that resolves to nothing. 32 | */ 33 | async run(client, interaction) { 34 | // Get the target user from the interaction options or default to the interaction user 35 | const member = interaction.options.getUser('target') || interaction.user; 36 | 37 | // Get the amount from the interaction options 38 | const amount = interaction.options.getNumber('amount'); 39 | 40 | // Calculate the fee based on the amount 41 | const fee = Math.round(amount - amount * 0.729); 42 | 43 | // Get the receiver and sender user objects from the database 44 | const { user: receiver } = await db.getUserById(member.id); 45 | const { user: sender } = await db.getUserById(interaction.user.id); 46 | 47 | // Calculate the coins given (amount minus fee) 48 | const coinsGiven = amount - fee; 49 | 50 | // Check if the amount is less than 0 and return an error message 51 | if (amount < 0) { 52 | return interaction.reply('Make sure you enter a valid number.'); 53 | } 54 | 55 | // Check if the sender has enough balance and return an error message 56 | if (amount > sender.balance) { 57 | return interaction.reply('Sorry, but you don\'t have that amount of coins.'); 58 | } 59 | 60 | // Check if the amount is not a valid number and return an error message 61 | if (isNaN(amount)) { 62 | return interaction.reply('Make sure you enter a valid number.'); 63 | } 64 | 65 | // Check if the amount is finite and return an error message 66 | if (!isFinite(amount)) { 67 | return interaction.reply('Make sure you enter a valid number.'); 68 | } 69 | 70 | // Check if the receiver and sender are the same user and return an error message 71 | if (receiver.userId === sender.userId) { 72 | return interaction.reply('You cannot transfer coins to your own account.'); 73 | } 74 | 75 | // Update the receiver's balance in the database 76 | await db.updateUserById(member.id, { 77 | balance: receiver.balance + coinsGiven, 78 | }); 79 | 80 | // Update the sender's balance in the database 81 | await db.updateUserById(interaction.user.id, { 82 | balance: sender.balance - amount, 83 | }); 84 | 85 | // Return a success message with the transfer details 86 | return interaction.reply(`${client.emoji.green_emoji} Successfully transfered \`${amount.toLocaleString()}\` coins to ${member.username}. \`fee: ${fee.toLocaleString()} (${Math.round((1 - 0.729) * 100)}%)\``); 87 | } 88 | }; -------------------------------------------------------------------------------- /commands/images/imagine.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, SlashCommandBuilder } = require('discord.js'); 4 | 5 | const Replicate = require('replicate'); 6 | 7 | const models = [ 8 | { 9 | name: 'Dreamshaper XL Turbo', 10 | value: 11 | 'lucataco/dreamshaper-xl-turbo:0a1710e0187b01a255302738ca0158ff02a22f4638679533e111082f9dd1b615', 12 | }, 13 | { 14 | name: 'OpenDALL-E (v1.1)', 15 | value: 16 | 'lucataco/open-dalle-v1.1:1c7d4c8dec39c7306df7794b28419078cb9d18b9213ab1c21fdc46a1deca0144', 17 | }, 18 | { 19 | name: 'RealvisXL2 (LCM)', 20 | value: 'lucataco/realvisxl2-lcm:479633443fc6588e1e8ae764b79cdb3702d0c196e0cb2de6db39ce577383be77', 21 | }, 22 | { 23 | name: 'SDXL-Lightning by ByteDance', 24 | value: 'lucataco/sdxl-lightning-4step:727e49a643e999d602a896c774a0658ffefea21465756a6ce24b7ea4165eba6a', 25 | }, 26 | ]; 27 | 28 | module.exports = class Imagine extends Command { 29 | constructor(client) { 30 | super(client, { 31 | data: new SlashCommandBuilder() 32 | .setName('imagine') 33 | .setDescription('Generate an image using OpenAI') 34 | .addStringOption(o => o.setName('prompt').setDescription('What do you want to generate?').setRequired(true)) 35 | .addStringOption(o => o.setName('model').setDescription('The model you want to choose').setRequired(true).setChoices( 36 | { 37 | name: 'Dreamshaper XL Turbo', 38 | value: 39 | 'lucataco/dreamshaper-xl-turbo:0a1710e0187b01a255302738ca0158ff02a22f4638679533e111082f9dd1b615', 40 | }, 41 | { 42 | name: 'OpenDALL-E (v1.1)', 43 | value: 44 | 'lucataco/open-dalle-v1.1:1c7d4c8dec39c7306df7794b28419078cb9d18b9213ab1c21fdc46a1deca0144', 45 | }, 46 | { 47 | name: 'RealvisXL2 (LCM)', 48 | value: 'lucataco/realvisxl2-lcm:479633443fc6588e1e8ae764b79cdb3702d0c196e0cb2de6db39ce577383be77', 49 | }, 50 | { 51 | name: 'SDXL-Lightning by ByteDance', 52 | value: 'lucataco/sdxl-lightning-4step:727e49a643e999d602a896c774a0658ffefea21465756a6ce24b7ea4165eba6a', 53 | })), 54 | usage: 'imagine ', 55 | category: 'Images', 56 | permissions: ['Use Application Commands', 'Send Messages'], 57 | }); 58 | } 59 | /** 60 | * Runs the function. 61 | * 62 | * @param {Client} client - The client object. 63 | * @param {Interaction} interaction - The interaction object. 64 | * @return {Promise} - A promise that resolves when the function is finished. 65 | */ 66 | async run(client, interaction) { 67 | try { 68 | await interaction.deferReply(); 69 | 70 | const replicate = new Replicate({ 71 | auth: process.env.REPLICATE_API_KEY, 72 | }); 73 | 74 | const prompt = interaction.options.getString('prompt'); 75 | const model = interaction.options.getString('model') || models[0].value; 76 | 77 | const output = await replicate.run(model, { input: { prompt } }); 78 | 79 | const row = new ActionRowBuilder().addComponents( 80 | new ButtonBuilder() 81 | .setLabel('Download') 82 | .setStyle(ButtonStyle.Link) 83 | .setURL(`${output[0]}`), 84 | ); 85 | 86 | const embed = new EmbedBuilder() 87 | .setTitle('🖼️ Image Generated') 88 | .addFields({ name: 'Prompt', value: prompt }) 89 | .setImage(output[0]) 90 | .setColor(0x2B2D31); 91 | 92 | await interaction.editReply({ embeds: [embed], components: [row] }); 93 | } 94 | catch (error) { 95 | const errEmbed = new EmbedBuilder() 96 | .setTitle('An error occurred') 97 | .setDescription('```' + error + '```') 98 | .setColor(0x2B2D31); 99 | 100 | interaction.editReply({ embeds: [errEmbed] }); 101 | } 102 | } 103 | }; -------------------------------------------------------------------------------- /views/partials/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | eres.lol logo 7 |
8 |

9 | © skillzl, <%= new Date().getFullYear(); %>. <%- __('WEB.FOOTER')%> 10 |

11 | 20 | 44 |
45 |
46 | 47 | 48 | 49 |
50 |
51 |
52 |
53 | 61 | 75 |
76 | 77 | -------------------------------------------------------------------------------- /commands/app/process.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const dayjs = require('dayjs'); 5 | const packages = require('../../package.json'); 6 | 7 | const { SlashCommandBuilder, EmbedBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle, version } = require('discord.js'); 8 | 9 | module.exports = class Process extends Command { 10 | constructor(client) { 11 | super(client, { 12 | data: new SlashCommandBuilder() 13 | .setName('process') 14 | .setDescription('Application status (also online on website)') 15 | .setDMPermission(false), 16 | usage: 'process', 17 | category: 'App', 18 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 19 | }); 20 | } 21 | /** 22 | * Runs the function with the given client and interaction parameters. 23 | * 24 | * @param {Client} client - The Discord client instance. 25 | * @param {Interaction} interaction - The interaction object. 26 | * @return {Promise} - A promise that resolves when the function is complete. 27 | */ 28 | async run(client, interaction) { 29 | // Create a row of buttons with GitHub and Website links 30 | const buttonRow = new ActionRowBuilder() 31 | .addComponents( 32 | new ButtonBuilder() 33 | .setURL('https://github.com/skillzl/eres') 34 | .setLabel('GitHub') 35 | .setStyle(ButtonStyle.Link), 36 | 37 | new ButtonBuilder() 38 | .setURL(`https://${process.env.DOMAIN}/stats`) 39 | .setLabel('Website') 40 | .setStyle(ButtonStyle.Link), 41 | ); 42 | 43 | // Get analytics data by ID 44 | const { data } = await db.getAnalysticsById(process.env.ANALYTICS_ID); 45 | 46 | // Calculate the total number of users across all guilds 47 | const users = client.guilds.cache.reduce((a, g) => a + g.memberCount, 0); 48 | 49 | // Start measuring CPU usage 50 | const startUsage = process.cpuUsage(); 51 | 52 | // Delay for 500 milliseconds 53 | const now = Date.now(); 54 | while (Date.now() - now < 500); 55 | 56 | // Calculate CPU usage 57 | const endUsage = process.cpuUsage(startUsage); 58 | const userCPU = endUsage.user / 1000000; 59 | const systemCPU = endUsage.system / 1000000; 60 | 61 | const totalCPU = userCPU + systemCPU; 62 | const cpuPercent = (totalCPU / 0.5) * 100; 63 | 64 | // Create an embed with process information 65 | const embed = new EmbedBuilder() 66 | .setAuthor({ 67 | name: `${packages.name}@${packages.version}`, 68 | iconURL: client.user.displayAvatarURL({ 69 | dynamic: true, 70 | size: 2048, 71 | extension: 'png', 72 | }), 73 | }) 74 | .setColor(0x2B2D31) 75 | .setDescription('Live process values, also seen on our website.') 76 | .addFields( 77 | { 78 | name: 'Process', 79 | value: `Memory: ${(process.memoryUsage().rss / 1024 / 1024).toFixed( 80 | 2, 81 | )}mb\nProcess Usage: ${cpuPercent.toFixed(2)}%\nPing: ${ 82 | client.ws.ping || 0 83 | }ms\nUptime: ${dayjs(client.uptime).format('D [d], H [h], m [m], s [s]')}`, 84 | inline: true, 85 | }, 86 | { 87 | name: 'Packages', 88 | value: `discord.js: ^${version}\nexpress: ${ 89 | packages.dependencies['express'] 90 | }\nmongoose: ${ 91 | packages.dependencies['mongoose'] 92 | }\nnode.js: ^${process.version}`, 93 | inline: true, 94 | }, 95 | { 96 | name: 'Cache', 97 | value: `Guilds: ${client.guilds.cache.size || 0}\nChannels: ${ 98 | client.channels.cache.size || 0 99 | }\nEmojis: ${client.emojis.cache.size || 0 }\nUsers: ${users || 0}`, 100 | inline: true, 101 | }, 102 | ) 103 | .setFooter({ 104 | text: `In total used: ${ 105 | data.commands_used + 1 || 0 106 | } slash commands and played ${data.songs_played} songs.`, 107 | }) 108 | .setThumbnail( 109 | client.user.displayAvatarURL({ 110 | dynamic: true, 111 | size: 128, 112 | format: 'png', 113 | }), 114 | ); 115 | 116 | // Send the embed and button row as a reply to the interaction 117 | await interaction.reply({ embeds: [embed], components: [buttonRow] }); 118 | } 119 | }; -------------------------------------------------------------------------------- /commands/developer/analytics.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 3 | 4 | const packages = require('../../package.json'); 5 | const mongoose = require('mongoose'); 6 | 7 | const dayjs = require('dayjs'); 8 | const db = require('../../database/manager'); 9 | 10 | module.exports = class Analytics extends Command { 11 | constructor(client) { 12 | super(client, { 13 | data: new SlashCommandBuilder() 14 | .setName('analytics') 15 | .setDescription('Display client\'s analytics') 16 | .setDMPermission(false), 17 | usage: 'analytics', 18 | category: 'Developer', 19 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 20 | }); 21 | } 22 | /** 23 | * Executes the run function. 24 | * 25 | * @param {Client} client - The client object. 26 | * @param {Interaction} interaction - The interaction object. 27 | * @return {Promise} - Returns nothing. 28 | */ 29 | async run(client, interaction) { 30 | // Check if the user has the DEVELOPER permission 31 | if (interaction.user.id !== process.env.DEVELOPER_ID) { 32 | return interaction.reply(`${client.emoji.red_emoji} Missing \`DEVELOPER\` permission.`); 33 | } 34 | 35 | // Check if the ANALYTICS_ID is set 36 | if (!process.env.ANALYTICS_ID) { 37 | return interaction.reply(`${client.emoji.red_emoji} Analytics unique identifier not set.`); 38 | } 39 | 40 | try { 41 | // Retrieve analytics data by ID 42 | const { data } = await db.getAnalysticsById(process.env.ANALYTICS_ID); 43 | 44 | // Calculate the total number of users in all guilds 45 | const users = client.guilds.cache.reduce( 46 | (a, g) => a + g.memberCount, 47 | 0, 48 | ); 49 | 50 | // Start measuring CPU usage 51 | const startUsage = process.cpuUsage(); 52 | 53 | // Delay for 500 milliseconds 54 | const now = Date.now(); 55 | while (Date.now() - now < 500); 56 | 57 | // Calculate CPU usage 58 | const endUsage = process.cpuUsage(startUsage); 59 | const userCPU = endUsage.user / 1000000; 60 | const systemCPU = endUsage.system / 1000000; 61 | 62 | const totalCPU = userCPU + systemCPU; 63 | const cpuPercent = (totalCPU / 0.5) * 100; 64 | 65 | // Build the embed for the analytics 66 | const embed = new EmbedBuilder() 67 | .setAuthor({ 68 | name: `${packages.name}@${packages.version}`, 69 | iconURL: client.user.displayAvatarURL({ dynamic: true, size: 2048, extension: 'png' }), 70 | }) 71 | .setColor(0x2B2D31) 72 | .setDescription(`Analytics ${process.env.DOMAIN}`) 73 | .addFields( 74 | { name: '7 Days Ago', value: `Guilds: ${data.guilds || 0}\nUsers: ${data.users || 0}`, inline: true }, 75 | { name: 'Now', value: `Guilds: ${client.guilds.cache.size || 0}\nUsers: ${users || 0}`, inline: true }, 76 | { name: 'Others', value: `Commands Used: ${data.commands_used + 1 || 0}\nSongs Played: ${data.songs_played || 0}\nReports: ${data.reports || 0}`, inline: true }, 77 | { 78 | name: 'Client', 79 | value: `\`\`\`js\nLicense :: ${packages.license}\nEmoji :: ${client.emojis.cache.size || 0 }\nChannels :: ${client.channels.cache.size || 0}\nProcess Usage :: ${cpuPercent.toFixed(2)}%\nPing :: ${client.ws.ping || 0}ms\nMemory :: ${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)}mb\nUptime :: ${dayjs(client.uptime).format('D [d], H [h], m [m], s [s]')}\nReady :: ${client.uptime ? 'True' : 'False'}\`\`\``, 80 | }, 81 | ) 82 | .setFooter({ 83 | text: 'Cron scheduler work: \'0 0 * * 0\' (every 7 days).', 84 | }) 85 | .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 128, format: 'png' })); 86 | 87 | // Send the embed as a reply to the interaction 88 | await interaction.reply({ embeds: [embed] }); 89 | } 90 | catch (err) { 91 | if (err instanceof mongoose.Error.CastError) { 92 | console.error(`[Database]: Invalid 🔴 ObjectId: ${err.value} (Analytics _id in .env file)`); 93 | interaction.reply(`${client.emoji.red_emoji} Analytics unique identifier not set.`); 94 | } 95 | else { 96 | console.error(err); 97 | } 98 | } 99 | } 100 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | 🍍 A multi-purpose Discord client written in Javascript with 42 commands and a highly optimised web application with control over the application's settings. Eres is running on over 34 servers, we invite you to try it out and hope you enjoy it! 8 | 9 |

10 | 11 | 12 | 13 |

14 | 15 | ### 📂 Content 16 | - [🌐 Deployment \& Features](#-deployment--features) 17 | - [⌨️ Node start guide](#️-node-start-guide) 18 | - [🖼️ Dashboard UI](#️-dashboard-ui) 19 | - [🚀 Envoirement settings](#-envoirement-settings) 20 | - [📈 Analytics](#-analytics) 21 | - [😃 Emojis](#-emojis) 22 | - [🔐 License](#-license) 23 | - [🙋‍♂️ Contributors](#️-contributors) 24 | 25 | ## 🌐 Deployment & Features 26 | 27 | Deployment support is not here yet! This repository is only for github imigration purposes. 28 | 29 | - ☑️ Modular features with optional congifuration. 30 | - ☑️ Experience system with levels and xp. 31 | - ☑️ Popular canva design for rank and profile command. 32 | - ☑️ Web application (dashboard) for optional configuration settings. 33 | - ☑️ Multi-lingual support (available languages: English, Romanian). 34 | - ☑️ Modern tehnologies for web app (nodejs, express, mongodb, tailwindcss, etc). 35 | - ☑️ Music player for mass-usage (DJ Role setting on dashboard). 36 | 37 | ## ⌨️ Node start guide 38 | 39 | ``` 40 | git clone https://github.com/skillzl/eres 41 | cd eres-main 42 | 43 | npm install 44 | node deploy (before running bot.js, so the slash commands can be loaded) 45 | 46 | npn run start [cls && node bot.js] or nodemon 47 | ``` 48 | 49 | ## 🖼️ Dashboard UI 50 | 51 | 52 | 53 | > **Tip**: Visit this repository to check out the latest version available. 54 | 55 | ## 🚀 Envoirement settings 56 | 57 | ``` 58 | ## Discord Application Token 59 | TOKEN= 60 | 61 | ## Mongoose Connection String 62 | MONGO_URL= 63 | 64 | ## Discord Application Unique Identifier 65 | CLIENT_ID= 66 | 67 | ## Dicord Application Secret Key => (used for dashboard authentication) 68 | CLIENT_SECRET= 69 | 70 | ## Listening Port for web-server 71 | PORT= (e.g: 3000) 72 | 73 | ## Callback Url for web-server => (also applied in discord.com/developers settings) 74 | CALLBACK_URL= (e.g: http://localhost:3000/login) 75 | 76 | ## Support server Url 77 | SUPPORT_SERVER= 78 | 79 | ## Unique identifier developer 80 | DEVELOPER_ID= 81 | 82 | ## Unique identifier webhook for bug reports 83 | WEBHOOK_ID= 84 | 85 | ## Unique token webhook for bug reports 86 | WEBHOOK_TOKEN= 87 | 88 | ## Analytics unique identifier (for analytics) 89 | ANALYTICS_ID= 90 | 91 | ## Domain 92 | DOMAIN= (e.g: localhost:3000 => if used locally) 93 | 94 | ## api.skillzl.dev unique key (visit api.skillzl.dev for key) 95 | SKILLZL_API_KEY= 96 | 97 | ## Custom youtube cookie (for 419 rate limits) 98 | YOUTUBE_COOKIE= 99 | 100 | 101 | ## api.eres.lol unique key (visit api.eres.lol for key) 102 | API_ERES_KEY= 103 | 104 | ## GitHub Personal Token => (used for github integration /admin/panel) 105 | GITHUB_TOKEN= 106 | 107 | ## Replicate API Key (replicate.com for key) 108 | REPLICATE_API_KEY= 109 | ``` 110 | 111 | > **Note**: Envoirement settings need to be in a ".env" file. 112 | 113 | ## 📈 Analytics 114 | 115 | Use this feature to know exactly how many people are using the bot. 116 | This feature requires the `ANALYTICS_ID` to be set. To get your `_id` from the mangoose database, follow this tutorial I made real quick! 117 | 118 | [Analytics Tutorial](./ANALYTICS.md) 119 | 120 | ## 😃 Emojis 121 | 122 | If you want to use custom emojis, you can do it here. 123 | Eres project emojis are stored in assets: [assets/emojis](./assets/emojis/) (import them to your Discord server) 124 | To change emojis you can do it here: [functions/Emojis](./functions/Emojis.js) (currently those emojis are from `Eres - 🍍` Discord server, so you need to change with yours) 125 | ## 🔐 License 126 | 127 | This project is licensed under the `Apache License 2.0 License` - see the [LICENSE](https://github.com/skillzl/eres/blob/main/LICENSE) file for details. 128 | 129 | ## 🙋‍♂️ Contributors 130 | Don't hesitate to make a pull request if you have any suggestions, bugs or just want to add more features. 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /views/stats.ejs: -------------------------------------------------------------------------------- 1 | <%- include("partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - stats 4 | 5 |
6 |
7 |
8 |

<%- __('WEB.STATS_TITLE')%> 9 |

10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |

<%- __('WEB.STATS_USERS')%>

18 |

<%= cachedUsers || 0 %>

19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |

<%- __('WEB.STATS_GUILDS')%>

28 |

<%= bot.guilds.cache.size || 0 %>

29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |

<%- __('WEB.STATS_COMMANDS')%>

38 |

<%= commands_used %>

39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |

<%- __('WEB.STATS_SONGS')%>

48 |

<%= songs_played %>

49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |

<%- __('WEB.STATS_MEMORY')%>

58 |

<%= (process.memoryUsage().rss / 1024 / 1024).toFixed(2) %> MB

59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 | 68 | 99 |
100 |
101 | 102 | <%- include("partials/footer") %> -------------------------------------------------------------------------------- /database/manager.js: -------------------------------------------------------------------------------- 1 | const serverModel = require('./serverModel'); 2 | const userModel = require('./userModel'); 3 | const analyticsModel = require('./analyticsModel'); 4 | 5 | module.exports = class Manager { 6 | static async createServer(id) { 7 | const result = new serverModel({ 8 | serverId: id, 9 | i18n: 'en', 10 | }); 11 | 12 | await result.save(); 13 | 14 | return result; 15 | } 16 | 17 | static async findServer(id) { 18 | if (typeof id !== 'string') { 19 | throw new Error('Invalid ID'); 20 | } 21 | const result = await serverModel.findOne({ serverId: id }); 22 | 23 | return result; 24 | } 25 | 26 | static async createUser(id) { 27 | if (typeof id !== 'string') { 28 | throw new Error('Invalid ID'); 29 | } 30 | const user = new userModel({ 31 | userId: id, 32 | }); 33 | 34 | await user.save(); 35 | return user; 36 | } 37 | 38 | static async createAnalytics() { 39 | const analytics = new analyticsModel({ 40 | commands_used: 0, 41 | guilds: 0, 42 | users: 0, 43 | reports: 0, 44 | }); 45 | 46 | await analytics.save(); 47 | return analytics; 48 | } 49 | 50 | static async getAnalysticsById(id) { 51 | if (typeof id !== 'string') { 52 | throw new Error('Invalid ID'); 53 | } 54 | const data = await analyticsModel.findOne({ _id: id }); 55 | return { data }; 56 | } 57 | 58 | static async incrementReports() { 59 | const analyticsData = await analyticsModel.findOne(); 60 | if (!analyticsData) { this.createAnalytics(); } 61 | 62 | if (analyticsData) { 63 | analyticsData.reports += 1; 64 | await analyticsData.save(); 65 | } 66 | } 67 | 68 | static async incrementSongsPlayed() { 69 | const analyticsData = await analyticsModel.findOne(); 70 | if (!analyticsData) { this.createAnalytics(); } 71 | 72 | if (analyticsData) { 73 | analyticsData.songs_played += 1; 74 | await analyticsData.save(); 75 | } 76 | } 77 | 78 | static async incrementCommandsUsed() { 79 | const analyticsData = await analyticsModel.findOne(); 80 | if (!analyticsData) { this.createAnalytics(); } 81 | 82 | if (analyticsData) { 83 | analyticsData.commands_used += 1; 84 | await analyticsData.save(); 85 | } 86 | } 87 | 88 | static async updateServerDjRole(id, role) { 89 | if (typeof id !== 'string') { 90 | throw new Error('Invalid ID'); 91 | } 92 | const guild = await this.findServer(id); 93 | if (!guild) { await this.createServer(id); } 94 | 95 | guild.djrole = role; 96 | await guild.save(); 97 | } 98 | 99 | static async updateServerAutorole(id, role) { 100 | if (typeof id !== 'string') { 101 | throw new Error('Invalid ID'); 102 | } 103 | const guild = await this.findServer(id); 104 | if (!guild) { await this.createServer(id); } 105 | 106 | guild.autorole = role; 107 | await guild.save(); 108 | } 109 | 110 | static async updateServerWelcome(id, channel) { 111 | if (typeof id !== 'string') { 112 | throw new Error('Invalid ID'); 113 | } 114 | const guild = await this.findServer(id); 115 | if (!guild) { await this.createServer(id); } 116 | 117 | guild.welcome = channel; 118 | await guild.save(); 119 | } 120 | 121 | static async updateServerLeave(id, channel) { 122 | if (typeof id !== 'string') { 123 | throw new Error('Invalid ID'); 124 | } 125 | const guild = await this.findServer(id); 126 | if (!guild) { await this.createServer(id); } 127 | 128 | guild.leave = channel; 129 | await guild.save(); 130 | } 131 | 132 | static async getUserById(id) { 133 | if (typeof id !== 'string') { 134 | throw new Error('Invalid ID'); 135 | } 136 | let user = await userModel.findOne({ userId: id }); 137 | if (!user) { user = await this.createUser(id); } 138 | return { user }; 139 | } 140 | 141 | static async updateUserById(id, data) { 142 | if (typeof id !== 'string') { 143 | throw new Error('Invalid ID'); 144 | } 145 | if (typeof data !== 'object') { throw Error('\'data\' must be an object'); } 146 | const user = await this.getUserById(id); 147 | if (!user) { await this.addUser(id); } 148 | 149 | await userModel.findOneAndUpdate({ userId: id }, data); 150 | } 151 | 152 | static async removeUser(id) { 153 | if (typeof id !== 'string') { 154 | throw new Error('Invalid ID'); 155 | } 156 | await userModel.findOneAndDelete({ userId: id }); 157 | } 158 | 159 | static async updateServerById(id, settings) { 160 | if (typeof id !== 'string') { 161 | throw new Error('Invalid ID'); 162 | } 163 | if (typeof settings !== 'object') { throw Error('\'settings\' must be an object'); } 164 | 165 | const guild = await this.findServer(id); 166 | 167 | if (!guild) { await this.createServer(id); } 168 | 169 | await serverModel.findOneAndUpdate({ guildId: id }, settings); 170 | } 171 | 172 | static async removeServer(id) { 173 | if (typeof id !== 'string') { 174 | throw new Error('Invalid ID'); 175 | } 176 | await serverModel.findOneAndDelete({ guildId: id }); 177 | } 178 | }; -------------------------------------------------------------------------------- /views/dashboard/stats.ejs: -------------------------------------------------------------------------------- 1 | <%- include("../partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - <%= guild.name %> 4 | 5 |
6 | guild image 11 |
12 |

<%- __('WEB.DASHBOARD_STATS')%> <%= guild.name %>

13 | 16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
24 | <%- __('WEB.STATS_TYPE')%> 25 | 27 | <%- __('WEB.STATS_DATA')%> 28 |
<%- __('WEB.STATS_ID')%><%= guild.id %>
<%- __('WEB.STATS_OWNER')%><%= bot.users.cache.get(guild.ownerId).username %><% bot.users.cache.get(guild.ownerId).discriminator && bot.users.cache.get(guild.ownerId).discriminator !== '0' ? `#${bot.users.cache.get(guild.ownerId).discriminator}` : '' %> (<%= bot.users.cache.get(guild.ownerId).id %>)
<%- __('WEB.STATS_CREATED')%><%= dayjs(guild.createdAt).format("DD/MM/YYYY, HH:mm:ss") %>
<%- __('WEB.DASHBOARD_MEMBERS')%><%= guild.memberCount - guild.members.cache.filter(m => m.user.bot).size %> <%- __('WEB.STATS_USERS')%>, <%= guild.members.cache.filter(m => m.user.bot).size %> <%- __('WEB.STATS_BOTS')%>
<%- __('WEB.STATS_CHANNELS')%><%= guild.channels.cache.filter(c => c.type === channelType.GuildText).size %> <%- __('WEB.STATS_TEXT')%>, <%= guild.channels.cache.filter(c => c.type === channelType.GuildVoice).size %> <%- __('WEB.STATS_VOICE')%>
<%- __('WEB.STATS_EMOJIS')%><%= guild.emojis.cache.filter(e => e.animated === false).size %> <%- __('WEB.STATS_NORMAL')%>, <%= guild.emojis.cache.filter(e => e.animated === true).size %> <%- __('WEB.STATS_ANIMATED')%>
<%- __('WEB.STATS_ROLES')%><%= guild.roles.cache.size %>
<%- __('WEB.STATS_HIGHEST_ROLE')%><%= guild.roles.highest.name %>
66 |
67 |
68 |
69 |
70 | 71 | <%- include("../partials/footer") %> -------------------------------------------------------------------------------- /commands/levels/rank.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | const userModel = require('../../database/userModel'); 4 | 5 | const { loadImage, createCanvas } = require('canvas'); 6 | const { AttachmentBuilder, SlashCommandBuilder } = require('discord.js'); 7 | 8 | const calculateUserXp = (xp) => Math.floor(0.1 * Math.sqrt(xp)); 9 | 10 | module.exports = class Rank extends Command { 11 | constructor(client) { 12 | super(client, { 13 | data: new SlashCommandBuilder() 14 | .setName('rank') 15 | .setDescription('Get your current level') 16 | .addUserOption(option => option.setName('target').setDescription('The user') 17 | .setRequired(false)) 18 | .setDMPermission(false), 19 | usage: 'rank [user]', 20 | category: 'Levels', 21 | permissions: ['Use Application Commands', 'Send Messages', 'Attach Files'], 22 | }); 23 | } 24 | /** 25 | * Runs the command when triggered by a user interaction. 26 | * 27 | * @param {Client} client - The Discord client object. 28 | * @param {Interaction} interaction - The interaction object representing the user interaction. 29 | */ 30 | async run(client, interaction) { 31 | // Get the target member from the interaction options or default to the interaction user 32 | const member = interaction.options.getUser('target') || interaction.user; 33 | 34 | // Retrieve the user data from the database 35 | const { user } = await db.getUserById(member.id); 36 | 37 | // Retrieve all users and sort them by xp 38 | const users = await userModel.find({}).sort('-xp'); 39 | 40 | // Find the position of the target user in the sorted users array 41 | let position = -1; 42 | for (let i = 0; i < users.length; i++) { 43 | if (users[i].userId === member.id) { 44 | position = i + 1; 45 | break; 46 | } 47 | } 48 | 49 | // Calculate the level based on the user's xp 50 | const level = calculateUserXp(user.xp); 51 | 52 | // Calculate the minimum and maximum xp required for the current level 53 | const minXp = (level * level) / 0.01; 54 | const maxXp = ((level + 1) * (level + 1)) / 0.01; 55 | 56 | // Create a canvas for drawing the level card 57 | const canvas = createCanvas(1026, 285); 58 | const ctx = canvas.getContext('2d'); 59 | 60 | // Load the background image 61 | const background = await loadImage('././assets/canva/level-background.png'); 62 | 63 | // Draw the background image on the canvas 64 | ctx.drawImage(background, 0, 0, canvas.width, canvas.height); 65 | 66 | // Draw the progress bar background 67 | ctx.beginPath(); 68 | ctx.lineWidth = 4; 69 | ctx.strokeStyle = '#555555'; 70 | ctx.globalAlpha = 1; 71 | ctx.fillStyle = '#a4a4a4'; 72 | ctx.fillRect(0, 270, 1026, 20); 73 | ctx.fill(); 74 | ctx.globalAlpha = 1; 75 | ctx.strokeReact = (0, 270, 1026, 20); 76 | ctx.stroke(); 77 | 78 | // Calculate and draw the progress bar based on the user's xp 79 | ctx.fillStyle = '#0c594e'; 80 | ctx.globalAlpha = 1; 81 | ctx.fillRect(0, 270, ((user.xp - minXp) / (maxXp - minXp)) * 1026, 20); 82 | ctx.fill(); 83 | ctx.globalAlpha = 1; 84 | 85 | // Draw the user's xp text 86 | ctx.font = '25px Arial'; 87 | ctx.textAlign = 'center'; 88 | ctx.fillStyle = '#FFFFFF'; 89 | ctx.fillText(`${user.xp}/${maxXp}`, 810, 163); 90 | 91 | // Draw the "XP" label 92 | ctx.textAlign = 'center'; 93 | ctx.font = 'bold 25px Arial'; 94 | ctx.fillStyle = '#30917d'; 95 | ctx.fillText('XP', 810, 140); 96 | 97 | // Draw the "RANK" label 98 | ctx.textAlign = 'center'; 99 | ctx.font = 'bold 25px Arial'; 100 | ctx.fillStyle = '#30917d'; 101 | ctx.fillText('RANK', 640, 140); 102 | 103 | // Draw the user's rank position 104 | ctx.font = '25px Arial'; 105 | ctx.textAlign = 'center'; 106 | ctx.fillStyle = '#FFFFFF'; 107 | ctx.fillText(`#${position}`, 640, 163); 108 | 109 | // Draw the "LEVEL" label 110 | ctx.textAlign = 'center'; 111 | ctx.font = 'bold 25px Arial'; 112 | ctx.fillStyle = '#30917d'; 113 | ctx.fillText('LEVEL', 460, 140); 114 | 115 | // Draw the user's level 116 | ctx.font = '25px Arial'; 117 | ctx.textAlign = 'center'; 118 | ctx.fillStyle = '#FFFFFF'; 119 | ctx.fillText(`${level}`, 460, 163); 120 | 121 | // Draw the user's username 122 | ctx.font = 'bold 32px Arial'; 123 | ctx.fillStyle = '#30917d'; 124 | ctx.textAlign = 'left'; 125 | ctx.fillText(`${member.username}`, 350, 61); 126 | 127 | // Draw the user's avatar on the card 128 | ctx.arc(170, 135, 125, 0, Math.PI * 2, true); 129 | ctx.lineWidth = 6; 130 | ctx.strokeStyle = '#0c594e'; 131 | ctx.stroke(); 132 | ctx.closePath(); 133 | ctx.clip(); 134 | 135 | // Load the user's avatar and draw it on the canvas 136 | const avatar = await loadImage(member.displayAvatarURL({ extension: 'png', size: 1024 })); 137 | ctx.drawImage(avatar, 45, 10, 250, 250); 138 | 139 | // Convert the canvas to a buffer and send it as an attachment 140 | const attachment = new AttachmentBuilder(canvas.toBuffer(), { name: 'image.png' }); 141 | interaction.reply({ files: [attachment] }); 142 | } 143 | }; -------------------------------------------------------------------------------- /commands/player/play.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const { SlashCommandBuilder } = require('discord.js'); 3 | 4 | const { Player } = require('discord-player'); 5 | 6 | module.exports = class Play extends Command { 7 | constructor(client) { 8 | super(client, { 9 | data: new SlashCommandBuilder() 10 | .setName('play') 11 | .setDescription('Adds a track to the end of the server queue') 12 | .setDMPermission(false) 13 | .addStringOption((option) => option.setName('song') 14 | .setDescription('Enter a track name, artist name, or URL') 15 | .setRequired(true) 16 | .setAutocomplete(true)), 17 | usage: 'play [string]', 18 | category: 'Player', 19 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 20 | }); 21 | } 22 | /** 23 | * Runs the command when invoked by the user. 24 | * 25 | * @param {Client} client - The Discord client. 26 | * @param {Interaction} interaction - The interaction object representing the user's command. 27 | */ 28 | async run(client, interaction) { 29 | // Defer the reply to indicate that the bot is processing the command 30 | await interaction.deferReply(); 31 | 32 | // Get the voice channel that the user is currently in 33 | const channel = interaction.member.voice.channel; 34 | 35 | // Check if the user is not in a voice channel 36 | if (!channel) { 37 | return await interaction.editReply(`${client.emoji.red_emoji} You aren't currently in a voice channel.`); 38 | } 39 | 40 | // Check if the bot is already playing music in a different voice channel 41 | if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) { 42 | return await interaction.editReply(`${client.emoji.red_emoji} I can't play music in that voice channel.`); 43 | } 44 | 45 | // Get the song query from the user's command 46 | const query = interaction.options.getString('song'); 47 | 48 | // Get the player instance 49 | const player = Player.singleton(client); 50 | 51 | // Get the queue for the current guild 52 | let queue = player.nodes.get(interaction.guild.id); 53 | 54 | // If the queue doesn't exist, create a new one 55 | if (!queue) { 56 | player.nodes.create(interaction.guild.id, { 57 | leaveOnEmptyCooldown: 300000, 58 | leaveOnEndCooldown: 300000, 59 | leaveOnStopCooldown: 300000, 60 | selfDeaf: false, 61 | metadata: { 62 | channel: interaction.channel, 63 | client: interaction.guild.members.me, 64 | requestedBy: interaction.user, 65 | }, 66 | }); 67 | } 68 | 69 | // Get the queue again after creating it 70 | queue = player.nodes.get(interaction.guild.id); 71 | 72 | try { 73 | // Search for the song using the player 74 | const res = await player.search(query, { 75 | requestedBy: interaction.user, 76 | }); 77 | 78 | // If no tracks are found, delete the queue and reply with an error message 79 | if (!res || !res.tracks || res.tracks.length === 0) { 80 | if (queue) queue.delete(); 81 | return await interaction.editReply(`${client.emoji.red_emoji} I couldn't find anything with the name \`${query}\`.`); 82 | } 83 | 84 | try { 85 | // If the queue is not connected to a voice channel, connect it to the user's voice channel 86 | if (!queue.connection) await queue.connect(interaction.member.voice.channel); 87 | } 88 | catch (err) { 89 | // If an error occurs while connecting to the voice channel, delete the queue and reply with an error message 90 | if (queue) queue.delete(); 91 | return await interaction.editReply(`${client.emoji.red_emoji} I can't join that voice channel.`); 92 | } 93 | 94 | try { 95 | // Add the tracks to the queue and start playing if the queue is not already playing 96 | res.playlist ? queue.addTrack(res.tracks) : queue.addTrack(res.tracks[0]); 97 | if (!queue.isPlaying()) await queue.node.play(queue.tracks[0]); 98 | } 99 | catch (err) { 100 | // If an error occurs while playing the media, delete the queue and reply with an error message 101 | console.log('An error occurred whilst attempting to play this media:'); 102 | console.log(err); 103 | 104 | await queue.delete(); 105 | return await interaction.followUp(`${client.emoji.red_emoji} This media doesn't seem to be working right now, please try again later.`); 106 | } 107 | 108 | if (!res.playlist) { 109 | // If a single track is loaded, reply with the track information 110 | return await interaction.editReply(`${client.emoji.green_emoji} Loaded **${res.tracks[0].title}** by **${res.tracks[0].author}** into the server queue.`); 111 | } 112 | else { 113 | // If a playlist is loaded, reply with the playlist information 114 | return await interaction.editReply(`${client.emoji.green_emoji} **${res.tracks.length} tracks** from the ${res.playlist.type} **${res.playlist.title}** have been loaded into the server queue.`); 115 | } 116 | } 117 | catch (err) { 118 | // If an error occurs while searching for the media, reply with an error message 119 | console.log(err); 120 | return interaction.editReply(`${client.emoji.red_emoji} An error occurred whilst attempting to play this media.`); 121 | } 122 | } 123 | }; -------------------------------------------------------------------------------- /commands/utils/server.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | 3 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 4 | 5 | module.exports = class Server extends Command { 6 | constructor(client) { 7 | super(client, { 8 | data: new SlashCommandBuilder() 9 | .setName('server') 10 | .setDescription('Guild informations and statistics') 11 | .setDMPermission(false), 12 | usage: 'server', 13 | category: 'Utils', 14 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links'], 15 | }); 16 | } 17 | /** 18 | * Run function for handling a specific interaction. 19 | * @param {Client} client - The Discord client instance. 20 | * @param {Interaction} interaction - The interaction object. 21 | */ 22 | async run(client, interaction) { 23 | /** 24 | * Calculates the number of days between a given time and the current date. 25 | * 26 | * @param {Date} time - The time to calculate the number of days ago from. 27 | * @return {number} The number of days between the given time and the current date. 28 | */ 29 | function daysAgo(time) { 30 | const today = new Date(); 31 | const createdOn = new Date(time); 32 | const msInDay = 24 * 60 * 60 * 1000; 33 | 34 | createdOn.setHours(0, 0, 0, 0); 35 | today.setHours(0, 0, 0, 0); 36 | 37 | const diff = (+today - +createdOn) / msInDay; 38 | 39 | return diff; 40 | } 41 | 42 | // Get the guild creation date 43 | const _createdAt = new Date(interaction.guild.createdAt); 44 | 45 | // Get the guild emojis 46 | const guildEmojis = interaction.guild.emojis.cache.size ? 47 | interaction.guild.emojis.cache 48 | .map( 49 | (emoji) => 50 | `<${emoji.animated == true ? 'a' : ''}:${emoji.name}:${emoji.id}>`, 51 | ) 52 | .join(' ') 53 | .substring(0, 1024) 54 | .replace(/\s\S+[^>]$/, '') : 55 | 'none'; 56 | 57 | // Get the guild roles 58 | const guildRoles = interaction.guild.roles.cache 59 | .map((role) => role.toString()) 60 | .join(' ') 61 | .substring(0, 1024) 62 | .replace(/\s\S+[^>]$/, ''); 63 | 64 | // Get the members in the guild with their statuses 65 | const members = { 66 | online: interaction.guild.members.cache.filter(member => member.presence?.status === 'online').size, 67 | dnd: interaction.guild.members.cache.filter(member => member.presence?.status === 'dnd').size, 68 | idle: interaction.guild.members.cache.filter(member => member.presence?.status === 'idle').size, 69 | bots: interaction.guild.members.cache.filter(member => member.user.bot).size, 70 | offline: interaction.guild.memberCount - (interaction.guild.members.cache.filter(member => member.presence?.status === 'online').size + interaction.guild.members.cache.filter(member => member.presence?.status === 'dnd').size + interaction.guild.members.cache.filter(member => member.presence?.status === 'idle').size), 71 | }; 72 | // Get the guild owner 73 | const owner = await interaction.guild.fetchOwner(); 74 | 75 | // Create an embed with the guild information 76 | const embed = new EmbedBuilder() 77 | .setColor(0x2B2D31) 78 | .setTitle(`${interaction.guild.name}`) 79 | .setThumbnail(interaction.guild.iconURL({ dynamic: true, size: 2048, extension: 'png' })) 80 | .setDescription( 81 | `${ 82 | interaction.guild.description != null 83 | ? interaction.guild.description + '\n\n' 84 | : '' 85 | }ID: ${interaction.guild.id}`, 86 | ) 87 | .addFields( 88 | { name: 'OWNERSHIP', value: `${client.emoji.crown} ${owner}`, inline: true }, 89 | { name: 'PARTNERED', value: `${interaction.guild.partnered ? client.emoji.partnered : client.emoji.red_emoji}`, inline: true }, 90 | { name: 'CUSTOM URL', value: `${interaction.guild.vanityURLCode ?? client.emoji.red_emoji}`, inline: true }, 91 | { name: 'VERIFIED', value: `${interaction.guild.verified ? client.emoji.verify : client.emoji.red_emoji}`, inline: true }, 92 | { name: 'BOOSTS', value: interaction.guild.premiumSubscriptionCount + ` (Level: ${interaction.guild.premiumTier})`, inline: true }, 93 | { name: 'AFK CHANNEL', value: interaction.guild.afkChannel ? `${client.emoji.moon} ${interaction.guild.afkChannel.name}` : client.emoji.red_emoji, inline: true }, 94 | { name: 'CREATED ON', value: `` + `\n${daysAgo(interaction.guild.createdAt).toFixed(0)} (days ago)` }, 95 | { name: `MEMBERS (${interaction.guild.memberCount})`, value: `online (${members.online}) : dnd (${members.dnd}) idle (${members.idle}) : bots (${members.bots}) : offline (${members.offline})`, inline: true }, 96 | { name: `EMOJIS (${interaction.guild.emojis.cache.size})`, value: `${guildEmojis}`, inline: true }, 97 | { name: `ROLES (${interaction.guild.roles.cache.size})`, value: `${guildRoles}`, inline: true }, 98 | ); 99 | 100 | // Add server banner if it exists and send the embed as a reply to the interaction 101 | const inviteBanner = interaction.guild.bannerURL({ 102 | size: 2048, 103 | format: 'png', 104 | }); 105 | 106 | // Add server banner if it exists 107 | if (inviteBanner !== null) { 108 | embed.setImage(inviteBanner); 109 | } 110 | 111 | // Send the embed as a reply to the interaction 112 | await interaction.reply({ embeds: [embed] }); 113 | } 114 | }; -------------------------------------------------------------------------------- /commands/profile/profile.js: -------------------------------------------------------------------------------- 1 | const Command = require('../../structures/CommandClass'); 2 | const db = require('../../database/manager'); 3 | 4 | const { createCanvas, loadImage } = require('canvas'); 5 | 6 | const { SlashCommandBuilder, AttachmentBuilder } = require('discord.js'); 7 | 8 | const calculateUserXp = (xp) => Math.floor(0.1 * Math.sqrt(xp)); 9 | const splice = (s) => (s.length > 40 ? `${s.substring(0, 50)}\n...` : s); 10 | 11 | module.exports = class Profile extends Command { 12 | constructor(client) { 13 | super(client, { 14 | data: new SlashCommandBuilder() 15 | .setName('profile') 16 | .setDescription('Fetch the current profile stats an user have') 17 | .addUserOption(option => option.setName('target').setDescription('The user') 18 | .setRequired(false)) 19 | .setDMPermission(false), 20 | usage: 'profile [user]', 21 | category: 'Profile', 22 | permissions: ['Use Application Commands', 'Send Messages', 'Embed Links', 'Attach Files'], 23 | }); 24 | } 25 | /** 26 | * Runs the profile generation for a user interaction. 27 | * 28 | * @param {Client} client - The Discord client. 29 | * @param {Interaction} interaction - The interaction data. 30 | */ 31 | async run(client, interaction) { 32 | // Defer the reply to indicate that the bot is processing the request. 33 | await interaction.deferReply(); 34 | 35 | // Get the target user from the interaction options, or default to the interaction user. 36 | const member = interaction.options.getUser('target') || interaction.user; 37 | 38 | // Get the user data from the database. 39 | const { user } = await db.getUserById(member.id); 40 | 41 | // Calculate the user's level based on their experience points. 42 | const level = calculateUserXp(user.xp); 43 | 44 | // Set the type based on whether the user is the developer. 45 | let type = 'user'; 46 | if (user.userId == process.env.DEVELOPER_ID) type = 'developer'; 47 | 48 | // Create a canvas and a 2D context. 49 | const canvas = createCanvas(2000, 2000); 50 | const ctx = canvas.getContext('2d'); 51 | 52 | // Load the background image. 53 | const background = await loadImage('././assets/canva/profile-background.png'); 54 | 55 | // Draw the background image on the canvas. 56 | ctx.drawImage(background, 0, 0, canvas.width, canvas.height); 57 | 58 | // Configure the drawing properties. 59 | ctx.beginPath(); 60 | ctx.patternQuality = 'bilinear'; 61 | ctx.filter = 'bilinear'; 62 | ctx.antialias = 'subpixel'; 63 | ctx.shadowColor = 'rgba(0, 0, 0, 0.4)'; 64 | ctx.shadowOffsetY = 5; 65 | ctx.shadowBlur = 5; 66 | 67 | // Draw the level text. 68 | ctx.font = 'bold 124px Arial'; 69 | ctx.fillStyle = '#FFFFFF'; 70 | ctx.textAlign = 'left'; 71 | ctx.fillText('LEVEL', 1425, 280); 72 | 73 | // Draw the level number. 74 | ctx.font = 'bold 286px Arial'; 75 | ctx.fillStyle = '#30917d'; 76 | ctx.textAlign = 'center'; 77 | ctx.fillText(level, 1625, 520); 78 | 79 | // Draw the username. 80 | ctx.font = 'bold 110px Arial'; 81 | ctx.fillStyle = '#30917d'; 82 | ctx.textAlign = 'left'; 83 | ctx.fillText(member.username, 810, 814); 84 | 85 | // Draw the user type. 86 | ctx.font = '69px Arial'; 87 | ctx.fillStyle = '#FFFFFF'; 88 | ctx.textAlign = 'left'; 89 | ctx.fillText(type, 810, 899); 90 | 91 | // Draw the "About" label. 92 | ctx.font = 'bold 75px Arial'; 93 | ctx.fillStyle = '#30917d'; 94 | ctx.textAlign = 'left'; 95 | ctx.fillText('About', 98, 1250); 96 | 97 | // Draw the user's about text. 98 | ctx.font = '69px Arial'; 99 | ctx.fillStyle = '#FFFFFF'; 100 | ctx.textAlign = 'left'; 101 | ctx.fillText(splice(user.about).toString(), 98, 1390); 102 | 103 | // Draw the "Coins" label. 104 | ctx.font = 'bold 75px Arial'; 105 | ctx.fillStyle = '#30917d'; 106 | ctx.textAlign = 'left'; 107 | ctx.fillText('Coins', 273, 1760); 108 | 109 | // Draw the user's balance. 110 | ctx.font = '64px Arial'; 111 | ctx.fillStyle = '#FFFFFF'; 112 | ctx.textAlign = 'left'; 113 | ctx.fillText(user.balance.toLocaleString(), 273, 1850); 114 | 115 | // Draw the "Reputation" label. 116 | ctx.font = 'bold 75px Arial'; 117 | ctx.fillStyle = '#30917d'; 118 | ctx.textAlign = 'left'; 119 | ctx.fillText('Reputation', 1230, 1760); 120 | 121 | // Draw the user's reputation. 122 | ctx.font = '64px Arial'; 123 | ctx.fillStyle = '#FFFFFF'; 124 | ctx.textAlign = 'left'; 125 | ctx.fillText(user.reputation.toLocaleString(), 1230, 1850); 126 | 127 | // Load the user's avatar, reputation badge, and coins badge. 128 | const avatar = await loadImage(member.displayAvatarURL({ extension: 'png', size: 1024 })); 129 | const reputation_badge = await loadImage('././assets/canva/reputation-asset.png'); 130 | const coins_badge = await loadImage('././assets/canva/coins-asset.png'); 131 | 132 | // Draw the user's avatar, reputation badge, and coins badge. 133 | ctx.drawImage(coins_badge, 117, 1700, 124, 124); 134 | ctx.drawImage(reputation_badge, 1095, 1700, 124, 124); 135 | ctx.drawImage(avatar, 117, 420, 550, 550); 136 | ctx.stroke(); 137 | ctx.closePath(); 138 | ctx.clip(); 139 | 140 | // Create an attachment from the canvas and send it as a reply. 141 | const attachment = new AttachmentBuilder(canvas.toBuffer(), { name: 'image.png' }); 142 | interaction.editReply({ files: [attachment] }); 143 | } 144 | }; -------------------------------------------------------------------------------- /views/dashboard/manage.ejs: -------------------------------------------------------------------------------- 1 | <%- include("../partials/header") %> 2 | 3 | <%= bot.user.username.toLowerCase() %>.lol - <%= guild.name %> 4 | 5 | 6 | 7 |
8 | guild image 13 |
14 |

<%- __('WEB.DASHBOARD_MANAGE')%> <%= guild.name %>

15 |
16 |
17 |
18 | 19 | 26 | 27 | 34 | 35 | 42 | 43 | 44 | 51 |
52 | 53 |
54 |
55 | <%- __('WEB.DASHBOARD_FORM_TIPS')%> 56 |
57 |
58 |
59 |
60 | 65 | 66 | <%- include("../partials/footer") %> --------------------------------------------------------------------------------