From 5b1ea0f681de7530d4f6c7f77dad3a8a515b2c31 Mon Sep 17 00:00:00 2001 From: danbulant Date: Sun, 6 Sep 2020 21:06:34 +0200 Subject: [PATCH] Add rank and leaderboard commands --- lib/middleware.js | 30 ++++++++++- src/commands.js | 135 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 21 +++++++- 3 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/commands.js diff --git a/lib/middleware.js b/lib/middleware.js index 8360c6b..750ac73 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -16,6 +16,10 @@ module.exports = class Router { */ middlewares = []; + constructor(name) { + this.name = name; + } + /** * Registers a command * @param {string} path command format @@ -37,6 +41,16 @@ module.exports = class Router { return this; } + /** + * Registers a middleware + * @param {string} path + * @param {function|Router} router + * @param {{ + guildOnly: boolean, + permissions: string[], + clientPermissions: string[] + }} opts + */ use(path, router, opts = {}) { if(typeof path !== "string") { [router, opts] = [path, router]; @@ -51,11 +65,21 @@ module.exports = class Router { }); return this; } + if(router instanceof Router) { + this.middlewares.push({ + callback: (...args) => router.message(...args), + path: (path).trim(), + opts: Object.assign(router.opts, opts), + break: opts ? !!opts.break : false + }) + return this; + } + if(!router.middlewares) throw new Error("Unknown middleware type"); for(var middleware of router.middlewares) { this.middlewares.push({ ...middleware, path: (path + " " + middleware.path).trim(), - opts: Object.assign(middleware.opts, opts) + opts: Object.assign({}, middleware.opts, opts) }); } return this; @@ -113,7 +137,9 @@ module.exports = class Router { if(typeof result === "string") { await msg.channel.send(result); } - if(result === false || middleware.break) break; + if(middleware.break || result === false) { + return false; + } } } } \ No newline at end of file diff --git a/src/commands.js b/src/commands.js new file mode 100644 index 0000000..60280b1 --- /dev/null +++ b/src/commands.js @@ -0,0 +1,135 @@ +const App = require("../lib/middleware"); +const app = new App("commands"); +const discord = require("discord.js"); + +/** + * Gets top members + * @param {discord.Guild} guild + */ +function getLeaderboard(guild) { + return guild.members.cache + .filter(m => !m.user.bot) + .filter(m => /\[[0-9.]+\].*/.test(m.displayName)) + .map(m => { + m.score = parseFloat(m.displayName.match(/\[([0-9.]+)\].*/)[1]); + return m; + }) + .map((m, i, self) => { + m.name = m.displayName.match(/\[[0-9.]+\](.*)/)[1].trim(); + m.rank = self.filter((m, i) => { + return self.indexOf(self.filter(e => e.score === m.score)[0]) === i; + }).filter(e => e.score > m.score).length + 1; + return m; + }) + .sort((a, b) => b.score - a.score) +} +/** + * Tests if given string is a number + * @param {string} str to test + */ +function isNumber(str) { + if (typeof str != "string") return false; + return !isNaN(str) && !isNaN(parseFloat(str)) +} + +/** + * Gets leaderboard embed + * @param {discord.Guild} guild + * @param {number} page + */ +function getLeaderboardEmbed(guild, page = 1) { + page--; + const leaderboard = getLeaderboard(guild).slice(page * 10, (page + 1) * 10); + const maxPage = Math.ceil(getLeaderboard(guild).length / 10); + if(!leaderboard.length) return "Couldn't find any results."; + + const embed = new discord.MessageEmbed(); + embed.setTitle("Leaderboard - Page " + (page+1)); + embed.setDescription(leaderboard + .map((m, i) => { + switch(m.rank) { + case 1: + m.rank = ":first_place:"; + break; + case 2: + m.rank = ":second_place:"; + break; + case 3: + m.rank = ":third_place:"; + break; + default: + m.rank += "."; + } + return m; + }) + .map( + (m, i) => `**${m.rank} ${m.name}** - ${m.score} point${m.score === 1 ? "" : "s"}` + ).join("\n") + ); + embed.setFooter(`Page ${page+1} out of ${maxPage}.`); + + return embed; +} + +/** + * Gets user rank embed + * @param {discord.GuildMember} member + * @param {discord.Guild} guild + * @param {boolean} motivation + */ +function getUserEmbed(omember, guild, motivation) { + var leaderboard = getLeaderboard(guild); + var member = leaderboard.filter(m => m.id === omember.id)[0]; + var index = leaderboard.indexOf(member); + + member.rankNumber = member.rank; + + switch(member.rank) { + case 1: + member.rank = ":first_place:"; + break; + case 2: + member.rank = ":second_place:"; + break; + case 3: + member.rank = ":third_place:"; + break; + } + + const embed = new discord.MessageEmbed(); + embed.setAuthor(member.name, member.user.avatarURL()); + embed.setDescription(`Rank: **${member.rank}**\nScore: **${member.score}**`) + + if(member.rankNumber !== 1 && motivation) { + var before = leaderboard.filter((m, i) => { + return leaderboard.indexOf(leaderboard.filter(e => e.score === m.score)[0]) === i; + }).filter(e => e.score > member.score).pop(); + embed.setFooter(`\n${before.score - member.score} points needed to get rank ${before.rank}.`); + } + return embed; +} + +app + .command("leaderboard :page", async (msg, { page }) => { + if(!isNumber(page)) return msg.channel.send("Invalid page argument. Use number."); + msg.channel.send(getLeaderboardEmbed(msg.guild, parseInt(page))); + }) + .command("leaderboard", async msg => { + msg.channel.send(getLeaderboardEmbed(msg.guild)); + }) + .command("rank :user", async (msg, { user }) => { + var members = await msg.guild.members.fetch({ + query: user, + limit: 5 + }); + if(members.size > 1) { + return msg.channel.send("Multiple users found, be more specific: " + members.map(m => "`" + m.displayName + "`").join(", ") + "."); + } + if(!members.size) return msg.channel.send("Couldn't find any user"); + msg.channel.send(getUserEmbed(members.first(), msg.guild, msg.member.id === members.first().id)); + }) + .command("rank", async msg => { + msg.channel.send(getUserEmbed(msg.member, msg.guild, true)); + }) + +module.exports = app; \ No newline at end of file diff --git a/src/index.js b/src/index.js index b19e520..2f2e1f6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,32 @@ const { MessageEmbed } = require("discord.js"); const App = require("../lib/middleware"); -const app = new App(); +const app = new App("main"); +const commands = require("./commands"); const answers = new Map(); const challenge = new Map(); const points = new Map(); +function makeWriteable(object, property) { + var val; + var proto = Object.getPrototypeOf(object); + Object.defineProperty(object, property, { + get: function () { return typeof val !== "undefined" ? val : proto[property]; }, + set: function(value) { val = value; } + }); +} + app .use(msg => !msg.author.bot) // ignore bots + .use(msg => { + if(msg.channel.type === "dm") return; + if(!msg.content.startsWith("!")) return; + msg.content = msg.content.substr(1); + makeWriteable(msg, "guild"); + msg.guild = msg.client.guilds.resolve("745985920116850781"); + commands.message(msg); + return false; + }) .use(async msg => { if(!challenge.get("type")) challenge.set("type", null); if(msg.author.id === "694395936809418816" && msg.content === "!reset") {