diff --git a/src/README.md b/src/README.md index 3fd786d..68c1379 100644 --- a/src/README.md +++ b/src/README.md @@ -1 +1,6 @@ - \ No newline at end of file +# Advanced Message Tracking Bot + +- Library: [discord.js](https://npmjs.com/package/discord.js) +- Developed by: + - [Lorenz#1337](https://discord.com/users/838620835282812969) + - [Npg#0001](https://discord.com/users/852219497763045398) diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..27dbb8a --- /dev/null +++ b/src/package.json @@ -0,0 +1,16 @@ +{ + "name": "messages-tracker", + "version": "1.0.0", + "description": "A discord bot for tracking messages with automated leaderboard , etc", + "main": "src/bot.js", + "scripts": { + "start": "node src/bot.js" + }, + "author": "Sahil", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "discord.js": "^13.6.0", + "mongoose": "^5.12.13" + } +} diff --git a/src/src/Base/leaderboard.js b/src/src/Base/leaderboard.js new file mode 100644 index 0000000..455de51 --- /dev/null +++ b/src/src/Base/leaderboard.js @@ -0,0 +1,41 @@ +const { Guild } = require("discord.js"); +const { Bot } = require("../Structures/Client"); + +/** + * @param { Bot } client + * @param { Guild } _guild + * @returns { Array } + */ + +async function getMessagesArray(client, _guild) { + //fetching messages data + let Messages = await client.User.find({ guild: _guild.id }) + .sort([["messages", "Descending"]]) + .exec(); + + //array + let array = []; + + //looping the data + for (let i = 0; i < Messages.length; i++) { + //breaking loop after 10 , as we only need top 10 users + if (array.length >= 10) break; + + //fetching user + let user = _guild.members.cache.get(Messages[i].user); + + //if user found , to avoid the users who left guild + if (user) { + //pushing into array + array.push({ + User: user.toString(), + Messages: Messages[i].messages, + }); + } + } + + //returning the array + return array; +} + +module.exports = getMessagesArray; diff --git a/src/src/Base/message.js b/src/src/Base/message.js new file mode 100644 index 0000000..3eb8fda --- /dev/null +++ b/src/src/Base/message.js @@ -0,0 +1,43 @@ +const { Message } = require("discord.js"); +const { Bot } = require("../Structures/Client"); + +/** + * + * @param { Bot } client + * @param { Message } message + */ +async function addMessages(client, message) { + try { + //fetching database + const Database = await client.User.findOne({ + guild: message.guild.id, + user: message.author.id, + }); + + //if no database found! + if (!Database) { + //new document + let doc = new client.User({ + user: message.author.id, + guild: message.guild.id, + messages: 1, + }); + //saving document + await doc.save(); + return; + } else { + //increment in messages + Database.messages++; + //saving doc + await Database.save(); + } + } catch (err) { + console.log( + chalk.redBright( + `${err.stack} | ${message.guild.name} (${message.channel.name})` + ) + ); + } +} + +module.exports = addMessages; diff --git a/src/src/Base/permission.js b/src/src/Base/permission.js new file mode 100644 index 0000000..b6e5f7b --- /dev/null +++ b/src/src/Base/permission.js @@ -0,0 +1,122 @@ +const { Message, MessageEmbed } = require("discord.js"); +const { Bot } = require("../Structures/Client"); + +/** + * + * @param { String } of + * @param { Message } message + * @param { Array } permissions + */ +async function checkPermission(of, message, permissions) { + //checking + if (!of || !message || !permissions) + throw new Error("Unable to access of/message/permissions"); + + /** + * @type { Bot } + */ + const client = message.client; + + switch (of.toLowerCase()) { + case "client": + //embed + let cEmbed = new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor(client.config.Embed.Error) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription( + `${client.config.Embed.Denied} **Bot needs some permissions to executive the command**` + ); + + //array + let array = []; + + //looping each permissions + permissions.forEach((permission) => { + //checking permission + if (!message.guild.me.hasPermission(permission)) { + //pushing to array + array.push(permission); + } + }); + + //if it includes administrator + if (array.length && array.includes("ADMINISTRATOR")) { + cEmbed.addField( + `${config.Embed.Stuck} Missing Permission(s)`, + `\`ADMINISTRATOR\`` + ); + message.channel.send({ embeds: [cEmbed] }).catch(() => {}); + return true; + } + + //if not includes administrator + else if (array.length) { + cEmbed.addField( + `${client.config.Embed.Stuck} Missing Permission(s)`, + `\`${array.join(" , ")}\`` + ); + message.channel.send({ embeds: [cEmbed] }).catch(() => {}); + return true; + } + break; + + //case member + case "member": + let mEmbed = new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor(client.config.Embed.Error) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription( + `${client.config.Embed.Denied} **You don't have enough permissions to use this command!**` + ); + + //array + let mArray = []; + + //looping permissions + permissions.forEach((permission) => { + if (!message.member.hasPermission(permission)) { + //pushing to array + mArray.push(permission); + } + }); + + //if includes administrator + if (mArray.length && mArray.includes("ADMINISTRATOR")) { + mEmbed.addField( + `${client.config.Embed.Stuck} Missing Permission(s)`, + `\`ADMINISTRATOR\`` + ); + message.channel.send({ embeds: [mEmbed] }).catch(() => {}); + return true; + } + + //if not includes administrator + else if (mArray.length) { + mEmbed.addField( + `${client.config.Embed.Stuck} Missing Permission(s)`, + `\`${mArray.join(" , ")}\`` + ); + message.channel.send({ embeds: [mEmbed] }).catch(() => {}); + return true; + } + break; + } +} + +module.exports = { + checkPermission, +}; diff --git a/src/src/Commands/Help/help.js b/src/src/Commands/Help/help.js new file mode 100644 index 0000000..d7e64f3 --- /dev/null +++ b/src/src/Commands/Help/help.js @@ -0,0 +1,169 @@ +const chalk = require("chalk"); +const { Message, MessageEmbed } = require("discord.js"); +const { checkPermission } = require("../../Base/permission"); +const { Bot } = require("../../Structures/Client"); + +module.exports = { + help: { + //command name + name: "help", + + //command aliases + aliases: ["h"], + + //permissions required for user + permissions: ["NO PERMISSIONS"], + + //permissions required for client + required: ["ADMINISTRATOR"], + + //command description + description: `\`help\` command provides help for using commands!`, + + //command usage example + usage: [`{prefix}help `], + + //command category + category: "others", + }, + /** + * + * @param { Bot } client + * @param { Message } message + * @param { String[] } args + */ + run: async (client, message, args) => { + try { + //checking client permission + let clientPermission = await checkPermission("client", message, [ + "ADMINISTRATOR", + ]); + if (clientPermission) return; + + //config + const { config } = client; + + //client invite + const inviteURL = `https://discord.com/oauth2/authorize?client_id=${client.user.id}&scope=bot&permissions=8`; + + //if no arguments + if (!args[0]) { + //Messages Array + let Messages = []; + //Others Array + let Others = []; + + //looping commands + client.commands.forEach((command) => { + if (command.help.category === "messages") + Messages.push(`**\`${command.help.name}\`**`); + + if (command.help.category === "others") + Others.push(`**\`${command.help.name}\`**`); + }); + + //embed + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + client.user.username + "'s help panel", + client.user.avatarURL({ dynamic: true }) + ) + .setFooter( + message.member.user.tag + ` | ${config.Embed.footer}`, + message.member.user.avatarURL({ dynamic: true }) + ) + .setColor(message.member.displayColor || config.Embed.Color) + .setDescription( + `Hey ${ + message.author.tag + }, myself a advance messages tracking bot made by ${ + (await client.users.fetch("533955330829451275")).tag + }. I have automated leaderboards , customisable commands , and very easy setup. You can get my source code from [here](${ + config.src + })` + ) + .addField(`๐Ÿงพ Messages`, Messages.join(" , ")) + .addField(`๐Ÿ” Others`, Others.join(" , ")) + .addField( + `Use \`${config.prefix}help \` for more help about each command!`, + "** **" + ) + .addField(`๐Ÿ“ฉ Invite Me`, `**[Link](${inviteURL})**`, true), + ], + }); + } + + //finding command + let command = + (await client.commands.get(args[0])) || + client.commands.get(client.aliases.get(args[0])); + + //if command not found + if (!command) + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor(message.member.displayColor || config.Embed.Color) + .setTimestamp() + .setFooter( + config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription( + `${config.Embed.Denied} **__|__ Unable to find \`${ + args[0] || "Unknown" + }\` command!**` + ), + ], + }); + + //usage array + let usage = []; + + //looping command usages to replace with prefix | Usefull for custom prefix + command.help.usage.forEach((usages) => { + usage.push(usages.split("{prefix}").join(config.prefix)); + }); + + //embed + message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor(message.member.displayColor || config.Embed.Color) + .setTimestamp() + .setFooter( + config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription(`> **${command.help.description}**`) + .addField(`๐Ÿ“š Usage`, `\`${usage.join("` **,** `")}\``, true) + .addField(`๐Ÿ“ฉ Category`, `\`${command.help.category}\``) + .addField( + `๐Ÿšฉ Shortcut(s)`, + `\`${command.help.aliases.join("` **,** `")}\`` + ) + .addField( + `๐Ÿ’ป Permissions Required`, + `\`${command.help.permissions.join("` **,** `").toLowerCase()}\`` + ), + ], + }); + } catch (err) { + console.log( + chalk.redBright( + `${err.stack} | ${message.guild.name} (${message.channel.name})` + ) + ); + } + }, +}; diff --git a/src/src/Commands/Messages/clearmessage.js b/src/src/Commands/Messages/clearmessage.js new file mode 100644 index 0000000..fc00741 --- /dev/null +++ b/src/src/Commands/Messages/clearmessage.js @@ -0,0 +1,182 @@ +const { Message, MessageEmbed } = require("discord.js"); +const { checkPermission } = require("../../Base/permission"); +const { Bot } = require("../../Structures/Client"); +const chalk = require("chalk"); + +module.exports = { + help: { + //command name + name: "resetmessages", + + //command aliases + aliases: ["clearmessages"], + + //permissions required for user + permissions: ["ADMINISTRATOR"], + + //permissions required for client + required: ["SEND_MESSAGES", "EMBED_LINKS"], + + //command description + description: `\`clearmessages\` command clears message for all members/particular user!`, + + //command usage example + usage: [`{prefix}resetmessages `], + + //command category + category: "messages", + }, + + /** + * + * @param { Bot } client + * @param { Message } message + * @param { String[] } args + */ + run: async (client, message, args) => { + try { + //checking client permissions + let clientPermission = await checkPermission("client", message, [ + "SEND_MESSAGES", + "EMBED_LINKS", + ]); + if (clientPermission) return; + + //checking member permission + let memberPermission = await checkPermission("member", message, [ + "ADMINISTRATOR", + ]); + if (memberPermission) return; + + //if no argument + if (!args[0]) + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor(client.config.Embed.Error) + .setTimestamp() + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription(client.config.Embed.Denied + " Missing Arguments") + .addField( + client.config.Embed.Stuck + " Arguments", + `resetmessages ` + ), + ], + }); + + //if argument is "all" + if (args[0].toLowerCase() === "all") { + //updating database + await client.User.updateMany( + { guild: message.guild.id }, + { + $set: { + messages: 0, + }, + } + ); + //embed + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor( + message.member.displayColor || client.config.Embed.Color + ) + .setTimestamp() + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription( + `${client.config.Embed.Succes} | Cleared messages for **all** users` + ), + ], + }); + } + + //if any particular member + else { + //member + let member = + message.mentions.members.first() || + message.guild.members.cache.get(args[0]); + + //if member not found + if (!member) + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor( + message.member.displayColor || client.config.Embed.Color + ) + .setTimestamp() + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription( + `${client.config.Embed.Denied} **__|__ Unable to find member**` + ) + .addField( + `${client.config.Embed.Stuck} Arguments`, + `resetmessages ` + ), + ], + }); + + //if member found | Updating database + await client.User.findOneAndUpdate( + { user: member.id }, + { + $set: { + messages: 0, + }, + } + ); + + //sending message + message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor( + message.member.displayColor || client.config.Embed.Color + ) + .setTimestamp() + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription( + `${client.config.Embed.Succes} | Cleared messages for **${member.user.tag}**` + ), + ], + }); + } + } catch (err) { + console.log( + chalk.yellowBright( + `${err.stack} | ${message.guild.name} (${message.channel.name})` + ) + ); + } + }, +}; diff --git a/src/src/Commands/Messages/leaderboard.js b/src/src/Commands/Messages/leaderboard.js new file mode 100644 index 0000000..57fe83b --- /dev/null +++ b/src/src/Commands/Messages/leaderboard.js @@ -0,0 +1,341 @@ +const chalk = require("chalk"); +const { Message, MessageEmbed, MessageReaction } = require("discord.js"); +const { checkPermission } = require("../../Base/permission"); +const config = require("../../config"); +const { Bot } = require("../../Structures/Client"); + +module.exports = { + help: { + //command name + name: "leaderboard", + + //command aliases + aliases: ["lb", "top"], + + //permissions required for user + permissions: ["NO PERMISSIONS"], + + //permissions required for client + required: ["SEND_MESSAGES", "EMBED_LINKS"], + + //command description + description: `\`leaderboard\` command shows the messages leaderboard!`, + + //command usage example + usage: [`{prefix}leaderboard `], + + //command category + category: "messages", + }, + /** + * + * @param { Bot } client + * @param { Message } message + * @param { String[] } args + */ + run: async (client, message, args) => { + try { + //checking client permission + let clientPermission = await checkPermission("client", message, [ + "ADMINISTRATOR", + ]); + if (clientPermission) return; + + //fetching Database + let Database = await client.User.find({ guild: message.guild.id }) + + //sorting data + .sort([["messages", "Descending"]]) + .exec(); + + //if database not found + if (!Database || !Database.length) + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor( + message.member.displayColor || client.config.Embed.Color + ) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription( + `${client.config.Embed.Stuck} **No data to show leaderboard!**` + ), + ], + }); + + //array + let array = []; + + //looping data + for (i = 0; i < Database.length; i++) { + //fetching user + let user = message.guild.members.cache.get(Database[i].user); + + //if user found , to avoid the users who left guild + if (user) { + //pushing into array + array.push({ + User: user.toString(), + Messages: Database[i].messages, + }); + } + } + + //if no users + if (!array.length) + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor( + message.member.displayColor || client.config.Embed.Color + ) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription( + `${client.config.Embed.Stuck} **No data to show leaderboard!**` + ), + ], + }); + + //if less than 10 users + if (array.length <= 10) { + //index + let index = 1; + + //embed + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.guild.name + "'s messages leaderboard", + message.guild.iconURL({ dynamic: true }) + ) + .setColor( + message.member.displayColor || client.config.Embed.Color + ) + .setFooter( + client.config.Embed.footer + " | Page: 1/1", + client.user.avatarURL({ dynamic: true }) + ) + .setImage( + `https://media.discordapp.net/attachments/612201812262649867/760335107394371624/divider1-1.gif` + ) + .setDescription( + array.map( + (db) => + `\`${index++}.\` ${db.User}ใƒป**${db.Messages}** messages` + ) + ), + ], + }); + } + + //if more than 10 users so pages/finding with index + else if (array.length > 10) { + //leaderboard | with 10 users each page + const Leaderboard = await fetchLeaderboard(array); + + //temp array + let Arr = []; + + //index + let index = 1; + + //looping + for (Users of Leaderboard) { + //description + const description = Users.map( + (db) => `\`${index++}.\` ${db.User}ใƒป**${db.Messages}** message(s)` + ); + //pushing with embed to temp array + Arr.push( + new MessageEmbed() + .setAuthor( + message.guild.name + "'s messages leaderboard", + message.guild.iconURL({ dynamic: true }) + ) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setColor( + message.member.displayColor || client.config.Embed.Color + ) + .setTimestamp() + .setDescription(description) + .setImage( + `https://media.discordapp.net/attachments/612201812262649867/760335107394371624/divider1-1.gif` + ) + ); + } + + //if no args + if (!args[0] || isNaN(args[0])) return await Pages(message, Arr); + + //if args , so finding page and sending + if (args[0]) return await SendPage(message, Arr, args[0]); + } + } catch (err) { + console.log( + chalk.redBright( + `${err.stack} | ${message.guild.name} (${message.channel.name})` + ) + ); + } + }, +}; + +/** + * + * @param { Array } data + * @returns { Array } + */ +async function fetchLeaderboard(data) { + //result array + const result = []; + + //looping data and breaking into pages + for (let i = 0; i < data.length; i += 10) { + //pushing page + result.push(data.slice(i, i + 10)); + } + + //returning pages array + return result; +} + +/** + * + * @param { Message } message + * @param { Array } pages + * @param { String } number + */ + +async function SendPage(message, pages, number) { + //checking message & channel + if (!message && !message.channel) + throw new Error("Unable to access channel...."); + + //checking pages + if (!Pages) throw new Error("Unable to access pages..."); + + //embed + let embed = pages[parseInt(number - 1)]; + + //if page not found + if (!embed) + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setFooter( + message.client.config.Embed.footer, + message.client.user.avatarURL({ dynamic: true }) + ) + .setColor(message.member.displayColor || client.config.Embed.Color) + .setTimestamp() + .setDescription(`${config.Embed.Stuck} | Page not found!`), + ], + }); + + //if embed found , so set footer & send + embed.setFooter( + config.Embed.footer + ` | Page: ${number}/${pages.length}`, + message.client.user.avatarURL({ dynamic: true }) + ); + + //sending embed + message.channel.send({ embeds: [embed] }); +} + +/** + * + * @param { Message } msg + * @param { Array } pages + * @param { Number } timeout + * @param { String[] } emojiList + */ +async function Pages( + msg, + pages, + timeout = 120000, + emojiList = ["โฎ๏ธ", "โ—€๏ธ", "โน๏ธ", "โ–ถ๏ธ", "โญ๏ธ"] +) { + //page + let page = 0; + + //current page + const current = await msg.channel.send( + pages[page].setFooter( + config.Embed.footer + ` | Page: ${page + 1}/${pages.length}`, + msg.client.user.avatarURL({ dynamic: true }) + ) + ); + + //reaction collector + const reactionCollector = current.createReactionCollector( + (reaction, user) => + emojiList.includes(reaction.emoji.name) && user.id === msg.author.id, + { time: timeout } + ); + + //reacting emojis + for (const emoji of emojiList) await current.react(emoji); + + //collector + reactionCollector.on("collect", (reaction) => { + //removing author reaction + reaction.users.remove(msg.author.id); + + //pages editing + switch (reaction.emoji.name) { + case emojiList[0]: + page = 0; + break; + case emojiList[1]: + page = page > 0 ? --page : pages.length - 1; + break; + case emojiList[2]: + curPage.reactions.removeAll(); + break; + case emojiList[3]: + page = page + 1 < pages.length ? ++page : 0; + break; + case emojiList[4]: + page = pages.length - 1; + break; + default: + break; + } + + //editing footer + current.edit( + pages[page].setFooter( + config.Embed.footer + ` | Page: ${page + 1}/${pages.length}`, + msg.client.user.avatarURL({ dynamic: true }) + ) + ); + }); + + //collector end + reactionCollector.on("end", () => { + //removing all reactions + current.reactions.removeAll().catch(() => {}); + }); +} diff --git a/src/src/Commands/Messages/message.js b/src/src/Commands/Messages/message.js new file mode 100644 index 0000000..c5ae128 --- /dev/null +++ b/src/src/Commands/Messages/message.js @@ -0,0 +1,101 @@ +const chalk = require("chalk"); +const { Message, MessageEmbed } = require("discord.js"); +const { checkPermission } = require("../../Base/permission"); +const { Bot } = require("../../Structures/Client"); + +module.exports = { + help: { + //command name + name: "messages", + + //command aliases + aliases: ["message"], + + //permissions required for user + permissions: ["NO PERMISSIONS"], + + //permissions required for client + required: ["SEND_MESSAGES", "EMBED_LINKS"], + + //command description + description: `\`messages\` command shows how many messages you currently have!`, + + //command usage example + usage: [`{prefix}messages `], + + //command category + category: "messages", + }, + /** + * + * @param { Bot } client + * @param { Message } message + * @param { String[] } args + */ + run: async (client, message, args) => { + try { + //checking client permission + let clientPermission = await checkPermission("client", message, [ + "ADMINISTRATOR", + ]); + if (clientPermission) return; + + //member + let member = + message.mentions.members.first() || + message.guild.members.cache.get(args[0]) || + message.member; + + //fetching Database + let Database = await client.User.findOne({ + guild: message.guild.id, + user: member.id, + }); + + //if no database + if (!Database || !Database.messages) { + Database = { + messages: 0, + }; + } else { + //if database + Database.messages = Database.messages; + } + + //fetching leaderboard + let Leaderboard = await client.User.find({ guild: message.guild.id }) + .sort([["messages", "Descending"]]) + .exec(); + + //User position in leaderboard + let Position = Leaderboard.findIndex( + (Member) => Member.user === member.id + ); + + //embed + message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription(`Showing information of ${member.user.tag}`) + .addField(`โœ‰ Messages`, `**${Database.messages}**`, true) + .addField(`๐ŸŽ– Position`, `**${Position + 1 || "Unknown"}**`, true) + .setColor(message.member.displayColor || client.config.Embed.Color), + ], + }); + } catch (err) { + console.log( + chalk.redBright( + `${err.stack} | ${message.guild.name} (${message.channel.name})` + ) + ); + } + }, +}; diff --git a/src/src/Commands/Messages/setleaderboard.js b/src/src/Commands/Messages/setleaderboard.js new file mode 100644 index 0000000..4db9873 --- /dev/null +++ b/src/src/Commands/Messages/setleaderboard.js @@ -0,0 +1,213 @@ +const chalk = require("chalk"); +const { + Message, + MessageEmbed, + MessageActionRow, + MessageButton, +} = require("discord.js"); +const getMessagesArray = require("../../Base/leaderboard"); +const { checkPermission } = require("../../Base/permission"); +const { Bot } = require("../../Structures/Client"); + +module.exports = { + help: { + //command name + name: "setleaderboard", + + //command aliases + aliases: ["setlb"], + + //permissions required for user + permissions: ["ADMINISTRATOR"], + + //permissions required for client + required: ["ADMINISTRATOR"], + + //command description + description: `\`setleaderboard\` command sets the automated leaderboard!`, + + //command usage + usage: [`{prefix}setleaderboard `], + + //command category + category: "messages", + }, + /** + * + * @param { Bot } client + * @param { Message } message + * @param { string[] } args + */ + run: async (client, message, args) => { + try { + //checking client permission + let clientPermission = await checkPermission("client", message, [ + "ADMINISTRATOR", + ]); + if (clientPermission) return; + + //checking member permission + let memberPermission = await checkPermission("member", message, [ + "ADMINISTRATOR", + ]); + if (memberPermission) return; + + //channel + let channel = + message.mentions.channels.first() || + message.guild.channels.cache.get(args[0]) || + message.channel; + + //fetching leaderboard | with top 10 users + let Leaderboard = await getMessagesArray(client, message.guild); + + //index + let index = 1; + + // check messages button + const button = new MessageActionRow().addComponents( + new MessageButton() + .setLabel("Check Messages") + .setCustomId("check-messages") + .setStyle("SECONDARY") + ); + //leaderboard embed + const embed = new MessageEmbed() + .setAuthor( + message.guild.name + "'s messages leaderboard", + message.guild.iconURL({ dynamic: true }) + ) + .setColor(client.config.Embed.Color) + .setFooter("Next refresh in", client.user.avatarURL({ dynamic: true })) + .setTimestamp(Date.now() + client.config.Leaderboard.interval) + //mapping the leaderboard + .setDescription( + Leaderboard.map( + (key) => + `${client.config.Embed.Arrow} \`${index++}\` ${key.User}ใƒป**${ + key.Messages + }** messages` + ) + ); + + //fetching guild data + let Database = await client.Guild.findById(message.guild.id); + + //new message + let lastMessage = await channel.send({ + embeds: [embed], + components: [button], + }); + + //if database not found + if (!Database) { + let doc = new client.Guild({ + _id: message.guild.id, + Loggers: { + Leaderboard: { + LastMessage: lastMessage.id, + Channel: channel.id, + }, + }, + }); + //saving database + await doc.save(); + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor(client.config.Embed.Color) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setTimestamp() + .setDescription( + `${ + client.config.Embed.Succes + } | **New automated leaderboard channel : ${channel.toString()}**` + ), + ], + }); + } else { + //if no channel and message + if ( + !Database.Loggers.Leaderboard.LastMessage && + !Database.Loggers.Leaderboard.Channel + ) { + //updating... + Database.Loggers.Leaderboard.LastMessage = lastMessage.id; + Database.Loggers.Leaderboard.Channel = channel.id; + await Database.save(); + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor(client.config.Embed.Color) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setTimestamp() + .setDescription( + `${ + client.config.Embed.Succes + } | **New automated leaderboard channel : ${channel.toString()}**` + ), + ], + }); + } + + //finding channel + let OldChannel = message.guild.channels.cache.get( + Database.Loggers.Leaderboard.Channel + ); + if (OldChannel) { + //fetching old message + let OldMessage = await client.findMessage( + Database.Loggers.Leaderboard.LastMessage, + OldChannel + ); + if (OldMessage) await OldMessage.delete().catch(() => {}); + } + //saving database + Database.Loggers.Leaderboard.LastMessage = lastMessage.id; + Database.Loggers.Leaderboard.Channel = channel.id; + await Database.save(); + //sending message + return message.channel.send({ + embeds: [ + new MessageEmbed() + .setAuthor( + message.author.tag, + message.author.avatarURL({ dynamic: true }) + ) + .setColor(client.config.Embed.Color) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setTimestamp() + .setDescription( + `${ + client.config.Embed.Succes + } | **New automated leaderboard channel : ${channel.toString()}**` + ), + ], + }); + } + } catch (err) { + console.log( + chalk.redBright( + `${err.message} | ${message.guild.name} (${message.channel.name})` + ) + ); + } + }, +}; diff --git a/src/src/Database/connection.js b/src/src/Database/connection.js new file mode 100644 index 0000000..b35088b --- /dev/null +++ b/src/src/Database/connection.js @@ -0,0 +1,19 @@ +const { green, redBright } = require("chalk"); +const mongoose = require("mongoose"); +const config = require("../config"); + +//connecting database +mongoose + .connect(config.mongoDB, { + //options + useNewUrlParser: true, + useUnifiedTopology: true, + }) + .catch(() => + console.log(redBright(`[ERROR]: Invalid MongoDB Connection String`)) + ); + +//logging +mongoose.connection.on("connected", () => { + console.log(green(`[DATABASE]: Connected`)); +}); diff --git a/src/src/Database/guild.js b/src/src/Database/guild.js new file mode 100644 index 0000000..ee4f818 --- /dev/null +++ b/src/src/Database/guild.js @@ -0,0 +1,20 @@ +const { Schema, model } = require("mongoose"); + +//Guild Schema +const Guild = new Schema({ + //guild id + _id: String, + + //Loggers + Loggers: { + //Leaderboard Object + Leaderboard: { + //LastMessage + LastMessage: String, + //Channel + Channel: String, + }, + }, +}); + +module.exports = model("Guild", Guild); diff --git a/src/src/Database/user.js b/src/src/Database/user.js new file mode 100644 index 0000000..e79e8ed --- /dev/null +++ b/src/src/Database/user.js @@ -0,0 +1,15 @@ +const { Schema, model } = require("mongoose"); + +//User schema +const User = new Schema({ + //user id + user: String, + + //guild id + guild: String, + + //messages + messages: Number, +}); + +module.exports = model("User", User); diff --git a/src/src/Events/interactionCreate.js b/src/src/Events/interactionCreate.js new file mode 100644 index 0000000..69a2366 --- /dev/null +++ b/src/src/Events/interactionCreate.js @@ -0,0 +1,50 @@ +const { ButtonInteraction, MessageEmbed } = require("discord.js"); +const { Bot } = require("../Structures/Client"); + +/** + * @param {Bot} client + * @param {ButtonInteraction} interaction + */ + +module.exports = async (client, interaction) => { + let member = interaction.member; + let Database = await client.User.findOne({ + guild: interaction.guild.id, + user: member.id, + }); + + //if no database + if (!Database || !Database.messages) { + Database = { + messages: 0, + }; + } else { + //if database + Database.messages = Database.messages; + } + + //fetching leaderboard + let Leaderboard = await client.User.find({ guild: interaction.guild.id }) + .sort([["messages", "Descending"]]) + .exec(); + + //User position in leaderboard + let Position = Leaderboard.findIndex((Member) => Member.user === member.id); + if (interaction.customId === "check-messages") { + interaction.reply({ + embeds: [ + new MessageEmbed() + .setAuthor(member.user.tag, member.user.avatarURL({ dynamic: true })) + .setFooter( + client.config.Embed.footer, + client.user.avatarURL({ dynamic: true }) + ) + .setDescription(`Showing information of ${member.user.tag}`) + .addField(`โœ‰ Messages`, `**${Database.messages}**`, true) + .addField(`๐ŸŽ– Position`, `**${Position + 1 || "Unknown"}**`, true) + .setColor(member.displayColor || client.config.Embed.Color), + ], + ephemeral: true, + }); + } +}; diff --git a/src/src/Events/messageCreate.js b/src/src/Events/messageCreate.js new file mode 100644 index 0000000..e1008b6 --- /dev/null +++ b/src/src/Events/messageCreate.js @@ -0,0 +1,32 @@ +const { Message } = require("discord.js"); +const addMessages = require("../Base/message"); +const { Bot } = require("../Structures/Client"); + +/** + * + * @param { Bot } client + * @param { Message } message + */ +module.exports = async (client, message) => { + if (!message.guild || message.author.bot) return; + + const { config } = client; + + if (message.content.toLowerCase().startsWith(config.prefix)) { + if (message.content === config.prefix) { + await addMessages(client, message); + return; + } + let args = message.content.slice(config.prefix.length).trim().split(/ +/g); + let cmd = args.shift().toLowerCase(); + + let command = + client.commands.get(cmd) || client.commands.get(client.aliases.get(cmd)); + + if (!command) await addMessages(client, message); + + if (command) command.run(client, message, args).catch(() => {}); + } else { + addMessages(client, message); + } +}; diff --git a/src/src/Events/ready.js b/src/src/Events/ready.js new file mode 100644 index 0000000..f6c7c2c --- /dev/null +++ b/src/src/Events/ready.js @@ -0,0 +1,15 @@ +const { green } = require("chalk"); +const { Bot } = require("../Structures/Client"); + +/** + * + * @param { Bot } client + */ +module.exports = (client) => { + //logging + console.log( + green( + `[API]: Logged as ${client.user.username} | ${client.config.Embed.footer}` + ) + ); +}; diff --git a/src/src/Loader.js b/src/src/Loader.js new file mode 100644 index 0000000..c712294 --- /dev/null +++ b/src/src/Loader.js @@ -0,0 +1,104 @@ +const chalk = require("chalk"); +const fs = require("fs"); +const { Bot } = require("./Structures/Client"); + +/** + * + * @param { Bot } client + */ +async function loadCommands(client) { + //folders + let folders = await fs.readdirSync(`${__dirname}/Commands`); + + //looping each folders + folders.forEach((folder) => { + //looping files of folders + fs.readdir(`${__dirname}/Commands/${folder}`, (err, files) => { + //if error + if (err) + return console.log( + chalk.redBright( + `An Error Occured While Loading Commands. ${err.stack}` + ) + ); + + //if no files found in folder + if (!files && !files.length) + return console.log( + chalk.yellowBright( + `[WARN]: No Files Found In "${folder.toUpperCase()}" Dir` + ) + ); + + //looping over each files + files.forEach((file) => { + //props + let props = require(`../src/Commands/${folder}/${file}`); + + //logging + console.log(chalk.greenBright(`[LOADED]: ${file}`)); + + /* Name */ + + //if not enough properties + if (!props.help || !props.help.name) + return console.log( + chalk.redBright(`[WARN]: ${file} not have enough help properties`) + ); + + //setting command + client.commands.set(props.help.name, props); + + /* Aliases */ + + //if not enough aliases + if (!props.help.aliases) + return console.log( + chalk.redBright(`[WARN]: ${file} not have enough aliases!`) + ); + + //looping each aliases + for (i = 0; i < props.help.aliases.length; i++) { + client.aliases.set(props.help.aliases[i], props.help.name); + } + }); + }); + }); +} + +async function loadEvents(client) { + //reading events folder + fs.readdir(`${__dirname}/Events`, async (err, files) => { + //if error + if (err) + return console.log( + chalk.redBright(`An Error Occured While Loading Events. ${err.stack}`) + ); + + //if no files found + if (!files) + return console.log( + chalk.redBright(`[WARN]: Events folder not have any files!`) + ); + + //looping each files + for (i = 0; i < files.length; i++) { + //Event + const event = require(`../src/Events/${files[i]}`); + + //Event name + let eventName = files[i].split(".")[0]; + + //client + client.on(eventName, event.bind(null, client)); + + //logging + console.log(chalk.greenBright(`[LOADED]: ${files[i]}`)); + } + }); +} + +module.exports = { + loadCommands, + loadEvents, +}; diff --git a/src/src/Structures/Client.js b/src/src/Structures/Client.js new file mode 100644 index 0000000..c959b3e --- /dev/null +++ b/src/src/Structures/Client.js @@ -0,0 +1,83 @@ +const chalk = require("chalk"); +const { Client, Collection, Message, TextChannel } = require("discord.js"); +const { loadCommands, loadEvents } = require("../Loader"); +const Leaderboard = require("./Leaderboard"); + +class Bot extends Client { + /** + * @param { import("discord.js").ClientOptions } props; + */ + constructor(props) { + if (!props) props = {}; + props.intents = 32767; + props.partials = ["MESSAGE", "CHANNEL", "REACTION"]; + super(props); + } + + _init() { + this.config = require("../config"); + + if (!this.config.token) + return console.log( + chalk.redBright(`[ERROR]: Token not found in config file!`) + ); + + //checking developers + if (!this.config.devs.length) + return console.log(chalk.redBright(`[ERROR]: No devs..??`)); + + //initialising database + require("../Database/connection.js"); + + //commands + this.commands = new Collection(); + + //aliases + this.aliases = new Collection(); + + //User model + this.User = require("../Database/user.js"); + + //Guild model + this.Guild = require("../Database/guild.js"); + + //initialising commands + loadCommands(this); + + //initialising events + loadEvents(this); + + //initialising leaderboard class with edit method + new Leaderboard(this).startLeaderboard("edit"); + + //login + this.login(this.config.token).catch(() => { + console.log(chalk.redBright(`[ERROR]: Invalid token provided!`)); + }); + } + + /** + * @param { TextChannel } channel + * @returns { Message } + */ + async findMessage(id, channel) { + if (!id || !channel) throw new Error("Invalid MessageID/Channel"); + if (!channel instanceof TextChannel) throw new Error("Invalid Channel"); + let fetchedMessage = await channel.messages.fetch(id).catch(() => {}); + return fetchedMessage; + } + + /** + * @param { Message } message + */ + async setLastMessage(message) { + if (!message) throw new Error("No message provided"); + let Database = await this.Guild.findById(message.guild.id); + if (Database) { + Database.Loggers.Leaderboard.LastMessage = message.id; + await Database.save(); + } + } +} + +module.exports.Bot = Bot; diff --git a/src/src/Structures/Leaderboard.js b/src/src/Structures/Leaderboard.js new file mode 100644 index 0000000..e0eae1b --- /dev/null +++ b/src/src/Structures/Leaderboard.js @@ -0,0 +1,108 @@ +const { MessageEmbed, TextChannel } = require("discord.js"); +const getMessagesArray = require("../Base/leaderboard"); +const { Bot } = require("./Client"); + +class Leaderboard { + /** + * @param { Bot } client + */ + constructor(client) { + this.client = client; + } + + async startLeaderboard(method) { + setInterval(async () => { + await this.updateLeaderboards(method); + }, this.client.config.Leaderboard.interval || 300000); // 5 minutes + } + + async updateLeaderboards(method) { + let methods = ["edit", "send"]; + if (!method || !methods.includes(method)) method = "send"; + + let Guilds = await this.client.Guild.find({}); + Guilds.forEach(async (guild) => { + let Server = this.client.guilds.cache.get(guild._id); + + if ( + !Server || + !guild.Loggers || + !guild.Loggers.Leaderboard || + !guild.Loggers.Leaderboard.Channel + ) + return; + + let logChannel = Server.channels.cache.get( + guild.Loggers.Leaderboard.Channel + ); + if (!logChannel) return; + + let Leaderboard = await getMessagesArray(this.client, Server); + if (!Leaderboard.length) return; + + let index = 1; + + const embed = new MessageEmbed() + .setAuthor( + Server.name + "'s messages leaderboard", + Server.iconURL({ dynamic: true }) + ) + .setColor(this.client.config.Embed.Color) + .setFooter( + "Next refresh in", + this.client.user.avatarURL({ dynamic: true }) + ) + .setTimestamp(Date.now() + this.client.config.Leaderboard.interval) + //mapping the leaderboard + .setDescription( + Leaderboard.map( + (key) => + `${this.client.config.Embed.Arrow} \`${index++}\`. ${ + key.User + }ใƒป**${key.Messages}** messages` + ) + ); + + //edit method + if (method === "edit") { + if (guild.Loggers.Leaderboard.LastMessage) { + let fetchedMessage = await this.client.findMessage( + guild.Loggers.Leaderboard.LastMessage, + logChannel + ); + + if (!fetchedMessage) { + let newMessage = await logChannel + .send({ embeds: [embed] }) + .catch(() => {}); + + await this.client.setLastMessage(newMessage); + } else { + fetchedMessage.edit({ embeds: [embed] }).catch(() => {}); + } + } else { + let newMessage = await logChannel + .send({ embeds: [embed] }) + .catch(() => {}); + await this.client.setLastMessage(newMessage); + } + } + + //send method + else if (method === "send") { + if (guild.Loggers.Leaderboard.LastMessage) { + let fetchedMessage = await this.client.findMessage( + guild.Loggers.Leaderboard.LastMessage + ); + if (fetchedMessage) await fetchedMessage.delete().catch(() => {}); + } + let newMessage = await logChannel + .send({ embeds: [embed] }) + .catch(() => {}); + await this.client.setLastMessage(newMessage); + } + }); + } +} + +module.exports = Leaderboard; diff --git a/src/src/bot.js b/src/src/bot.js new file mode 100644 index 0000000..449c39b --- /dev/null +++ b/src/src/bot.js @@ -0,0 +1,8 @@ +//importing Bot class +const { Bot } = require("./Structures/Client"); + +//client +const client = new Bot(); + +//initialising client +client._init(); diff --git a/src/src/config.js b/src/src/config.js new file mode 100644 index 0000000..debae89 --- /dev/null +++ b/src/src/config.js @@ -0,0 +1,29 @@ +module.exports = { + //bot token + token: "abcdefg", + + //bot prefix + prefix: "!", + + //devs String[] + devs: [], + + /* Embeds */ + Embed: { + Color: "", + Error: "", + Succes: "", + Denied: "", + Stuck: "", + Arrow: "", + Dot: "", + footer: "Made by NPG & Lorenz", + }, + + /* Leaderboard */ + Leaderboard: { + interval: 0, // default is 5 min | 300000 ms + }, + + mongoDB: "", +};