From 1cd9e13c46a79f3e28a90611528e6231a18d99bc Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:10:53 +0530 Subject: [PATCH 01/26] music - initial commit --- package.json | 3 ++ src/commands/music/play.js | 58 +++++++++++++++++++++++++++++++++++ src/commands/music/stop.js | 29 ++++++++++++++++++ src/events/ready.js | 5 ++- src/handlers/index.js | 1 + src/handlers/music-handler.js | 38 +++++++++++++++++++++++ src/structures/BotClient.js | 5 +++ src/structures/command.js | 2 +- 8 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/commands/music/play.js create mode 100644 src/commands/music/stop.js create mode 100644 src/handlers/music-handler.js diff --git a/package.json b/package.json index ae451262a..ef95877a1 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,17 @@ "url": "saiteja-madha/discord-js-bot" }, "dependencies": { + "@discordjs/opus": "^0.5.3", "ascii-table": "0.0.9", "axios": "^0.21.1", "btoa": "^1.2.1", "country-language": "^0.1.7", + "discord-player": "^5.1.0", "discord.js": "^13.1.0", "ejs": "^3.1.6", "express": "^4.17.1", "express-session": "^1.17.2", + "ffmpeg-static": "^4.4.0", "fs": "0.0.1-security", "iso-639-1": "^2.1.9", "module-alias": "^2.2.2", diff --git a/src/commands/music/play.js b/src/commands/music/play.js new file mode 100644 index 000000000..32c64cc04 --- /dev/null +++ b/src/commands/music/play.js @@ -0,0 +1,58 @@ +const { Command } = require("@src/structures"); +const { QueryType } = require("discord-player"); +const { Message } = require("discord.js"); + +module.exports = class Play extends Command { + constructor(client) { + super(client, { + name: "play", + description: "play a song from youtube", + command: { + enabled: true, + usage: "", + minArgsCount: 1, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const { guild, channel, member } = message; + const query = args.join(" "); + + if (!member.voice.channel) return message.reply("You need to join a voice channel first"); + + let searchResult; + try { + searchResult = await message.client.player.search(query, { + requestedBy: message.author, + searchEngine: QueryType.AUTO, + }); + } catch (ex) { + console.log(ex); + message.channel.send("Failed to fetch results"); + } + + const queue = message.client.player.createQueue(guild, { + metadata: channel, + }); + + try { + if (!queue.connection) await queue.connect(member.voice.channel); + } catch { + message.client.player.deleteQueue(message.guildId); + return message.channel.send("Could not join your voice channel!"); + } + + await message.channel.send(`ā± | Loading your ${searchResult.playlist ? "playlist" : "track"}...`); + searchResult.playlist ? queue.addTracks(searchResult.tracks) : queue.addTrack(searchResult.tracks[0]); + if (!queue.playing) await queue.play(); + } +}; diff --git a/src/commands/music/stop.js b/src/commands/music/stop.js new file mode 100644 index 000000000..793a4c01e --- /dev/null +++ b/src/commands/music/stop.js @@ -0,0 +1,29 @@ +const { Command } = require("@src/structures"); +const { Message } = require("discord.js"); + +module.exports = class Play extends Command { + constructor(client) { + super(client, { + name: "stop", + description: "stop the music player", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + queue.destroy(); + return message.channel.send("Stopped the player!"); + } +}; diff --git a/src/events/ready.js b/src/events/ready.js index 2a0b06166..225f828c3 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -1,5 +1,5 @@ const { BotClient } = require("@src/structures"); -const { counterHandler, inviteHandler } = require("@src/handlers"); +const { counterHandler, inviteHandler, musicHandler } = require("@src/handlers"); const { loadReactionRoles } = require("@schemas/reactionrole-schema"); const { getSettings } = require("@schemas/guild-schema"); @@ -15,6 +15,9 @@ module.exports = async (client) => { else await client.registerSlashCommands(client.config.SLASH_COMMANDS.TEST_GUILD_ID); } + // register player events + musicHandler.registerPlayerEvents(client); + // Load reaction roles to cache await loadReactionRoles(); diff --git a/src/handlers/index.js b/src/handlers/index.js index 0a5ebd756..dea78e2e9 100644 --- a/src/handlers/index.js +++ b/src/handlers/index.js @@ -3,5 +3,6 @@ module.exports = { counterHandler: require("./counter-handler"), greetingHandler: require("./greeting-handler"), inviteHandler: require("./invite-handler"), + musicHandler: require("./music-handler"), reactionHandler: require("./reaction-handler"), }; diff --git a/src/handlers/music-handler.js b/src/handlers/music-handler.js new file mode 100644 index 000000000..2c67cec6b --- /dev/null +++ b/src/handlers/music-handler.js @@ -0,0 +1,38 @@ +const { BotClient } = require("@src/structures"); + +/** + * @param {BotClient} client + */ +function registerPlayerEvents(client) { + client.player.on("error", (queue, error) => { + console.log(`[${queue.guild.name}] Error emitted from the queue: ${error.message}`); + }); + + client.player.on("connectionError", (queue, error) => { + console.log(`[${queue.guild.name}] Error emitted from the connection: ${error.message}`); + }); + + client.player.on("trackStart", (queue, track) => { + queue.metadata.send(`šŸŽ¶ | Started playing: **${track.title}** in **${queue.connection.channel.name}**!`); + }); + + client.player.on("trackAdd", (queue, track) => { + queue.metadata.send(`šŸŽ¶ | Track **${track.title}** queued!`); + }); + + client.player.on("botDisconnect", (queue) => { + queue.metadata.send("āŒ | I was manually disconnected from the voice channel, clearing queue!"); + }); + + client.player.on("channelEmpty", (queue) => { + queue.metadata.send("āŒ | Nobody is in the voice channel, leaving..."); + }); + + client.player.on("queueEnd", (queue) => { + queue.metadata.send("āœ… | Queue finished!"); + }); +} + +module.exports = { + registerPlayerEvents, +}; diff --git a/src/structures/BotClient.js b/src/structures/BotClient.js index a5cf6e2dd..b341dcb77 100644 --- a/src/structures/BotClient.js +++ b/src/structures/BotClient.js @@ -5,6 +5,7 @@ const Ascii = require("ascii-table"); const mongoose = require("mongoose"); const Command = require("./command"); mongoose.plugin(require("mongoose-lean-defaults").default); +const { Player } = require("discord-player"); module.exports = class BotClient extends Client { constructor() { @@ -16,6 +17,7 @@ module.exports = class BotClient extends Client { Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_PRESENCES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS, + Intents.FLAGS.GUILD_VOICE_STATES, ], partials: ["USER", "MESSAGE", "REACTION"], }); @@ -37,6 +39,9 @@ module.exports = class BotClient extends Client { this.joinLeaveWebhook = this.config.JOIN_LEAVE_WEBHOOK ? new WebhookClient({ url: this.config.JOIN_LEAVE_WEBHOOK }) : undefined; + + // Music Player + this.player = new Player(this); } /** diff --git a/src/structures/command.js b/src/structures/command.js index c64f69d7b..a5ca3b380 100644 --- a/src/structures/command.js +++ b/src/structures/command.js @@ -23,7 +23,7 @@ class Command { */ /** - * @typedef {"ADMIN" | "AUTOMOD" | "ECONOMY" | "FUN" | "IMAGE" | "INFORMATION" | "INVITE" | "MODERATION" | "NONE" | "OWNER" | "SOCIAL" | "TICKET" | "UTILITY" } CommandCategory + * @typedef {"ADMIN" | "AUTOMOD" | "ECONOMY" | "FUN" | "IMAGE" | "INFORMATION" | "INVITE" | "MODERATION" | "MUSIC" |"NONE" | "OWNER" | "SOCIAL" | "TICKET" | "UTILITY" } CommandCategory */ /** From c77ef17ecd84fa6a8048b043a6189995531b9b23 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Thu, 16 Sep 2021 23:13:56 +0530 Subject: [PATCH 02/26] this.client for intellisense --- src/commands/music/play.js | 6 +++--- src/commands/music/stop.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/music/play.js b/src/commands/music/play.js index 32c64cc04..494ff6c72 100644 --- a/src/commands/music/play.js +++ b/src/commands/music/play.js @@ -31,7 +31,7 @@ module.exports = class Play extends Command { let searchResult; try { - searchResult = await message.client.player.search(query, { + searchResult = await this.client.player.search(query, { requestedBy: message.author, searchEngine: QueryType.AUTO, }); @@ -40,14 +40,14 @@ module.exports = class Play extends Command { message.channel.send("Failed to fetch results"); } - const queue = message.client.player.createQueue(guild, { + const queue = this.client.player.createQueue(guild, { metadata: channel, }); try { if (!queue.connection) await queue.connect(member.voice.channel); } catch { - message.client.player.deleteQueue(message.guildId); + this.client.player.deleteQueue(message.guildId); return message.channel.send("Could not join your voice channel!"); } diff --git a/src/commands/music/stop.js b/src/commands/music/stop.js index 793a4c01e..4e9a00e22 100644 --- a/src/commands/music/stop.js +++ b/src/commands/music/stop.js @@ -21,7 +21,7 @@ module.exports = class Play extends Command { * @param {string[]} args */ async messageRun(message, args) { - const queue = message.client.player.getQueue(message.guildId); + const queue = this.client.player.getQueue(message.guildId); if (!queue || !queue.playing) return message.channel.send("No music is being played!"); queue.destroy(); return message.channel.send("Stopped the player!"); From c6b5d228991eb47ccbbd9874c61dbda09459a171 Mon Sep 17 00:00:00 2001 From: Sparker <60593579+Sparker-99@users.noreply.github.com> Date: Fri, 17 Sep 2021 13:48:02 +0530 Subject: [PATCH 03/26] Added more music stuffs --- src/commands/music/bassboost.js | 36 +++++++++++++++++++++++++++++++++ src/commands/music/pause.js | 33 ++++++++++++++++++++++++++++++ src/commands/music/resume.js | 33 ++++++++++++++++++++++++++++++ src/commands/music/seek.js | 35 ++++++++++++++++++++++++++++++++ src/commands/music/skip.js | 34 +++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+) create mode 100644 src/commands/music/bassboost.js create mode 100644 src/commands/music/pause.js create mode 100644 src/commands/music/resume.js create mode 100644 src/commands/music/seek.js create mode 100644 src/commands/music/skip.js diff --git a/src/commands/music/bassboost.js b/src/commands/music/bassboost.js new file mode 100644 index 000000000..e3ee1cb00 --- /dev/null +++ b/src/commands/music/bassboost.js @@ -0,0 +1,36 @@ +const { Command } = require("@src/structures"); +const { MessageEmbed, Message } = require("discord.js"); + +module.exports = class Play extends Command { + constructor(client) { + super(client, { + name: "bassboost", + description: "Toggles bassboost", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + + await queue.setFilters({ + bassboost: !queue.getFiltersEnabled().includes("bassboost"), + normalizer2: !queue.getFiltersEnabled().includes("bassboost") + }); + + const embed = new MessageEmbed() + .setDescription(`šŸŽµ | Bassboost ${queue.getFiltersEnabled().includes("bassboost") ? "Enabled | āœ…" : "Disabled | āŒ"}`) + return message.channel.send({ embeds: [embed] }); + } +}; \ No newline at end of file diff --git a/src/commands/music/pause.js b/src/commands/music/pause.js new file mode 100644 index 000000000..25d013027 --- /dev/null +++ b/src/commands/music/pause.js @@ -0,0 +1,33 @@ +const { Command } = require("@src/structures"); +const { MessageEmbed, Message } = require("discord.js"); + +module.exports = class Play extends Command { + constructor(client) { + super(client, { + name: "pause", + description: "Pause the current song", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + + const paused = queue.setPaused(true); + + const embed = new MessageEmbed() + .setDescription(paused ? "šŸŽµ | Music Paused | āø" : "šŸŽµ | Music Already Paused") + return message.channel.send({ embeds: [embed] }); + } +}; \ No newline at end of file diff --git a/src/commands/music/resume.js b/src/commands/music/resume.js new file mode 100644 index 000000000..69ffa5eb3 --- /dev/null +++ b/src/commands/music/resume.js @@ -0,0 +1,33 @@ +const { Command } = require("@src/structures"); +const { MessageEmbed, Message } = require("discord.js"); + +module.exports = class Play extends Command { + constructor(client) { + super(client, { + name: "resume", + description: "Resumes the paused song", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + + const paused = queue.setPaused(false); + + const embed = new MessageEmbed() + .setDescription(paused ? "šŸŽµ | Music Resumed | ā–¶" : "šŸŽµ | Music not Paused") + return message.channel.send({ embeds: [embed] }); + } +}; \ No newline at end of file diff --git a/src/commands/music/seek.js b/src/commands/music/seek.js new file mode 100644 index 000000000..25eec2caa --- /dev/null +++ b/src/commands/music/seek.js @@ -0,0 +1,35 @@ +const { Command } = require("@src/structures"); +const { MessageEmbed, Message } = require("discord.js"); + +module.exports = class Play extends Command { + constructor(client) { + super(client, { + name: "seek", + description: "Seeks to the given time in seconds", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + if (isNaN(args[0])) return message.channel.send("Input must be in seconds"); + + const time = args[0] * 1000; + await queue.seek(time); + + const embed = new MessageEmbed() + .setDescription("ā© | Seeked to " + time / 1000 + " seconds") + return message.channel.send({ embeds: [embed] }); + } +}; \ No newline at end of file diff --git a/src/commands/music/skip.js b/src/commands/music/skip.js new file mode 100644 index 000000000..8009512f2 --- /dev/null +++ b/src/commands/music/skip.js @@ -0,0 +1,34 @@ +const { Command } = require("@src/structures"); +const { MessageEmbed, Message } = require("discord.js"); + +module.exports = class Play extends Command { + constructor(client) { + super(client, { + name: "skip", + description: "Skip the current song", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + + const currentTrack = queue.current; + const success = queue.skip(); + + const embed = new MessageEmbed() + .setDescription(success ? `ā­ļø | Skipped **${currentTrack}**` : "āŒ | Something went wrong!") + return message.channel.send({ embeds: [embed] }); + } +}; \ No newline at end of file From 14719273ae7b80c99baab38b8db78efcedc36509 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Fri, 17 Sep 2021 20:11:45 +0530 Subject: [PATCH 04/26] Create volume.js --- src/commands/music/volume.js | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/commands/music/volume.js diff --git a/src/commands/music/volume.js b/src/commands/music/volume.js new file mode 100644 index 000000000..1c4e6ab9a --- /dev/null +++ b/src/commands/music/volume.js @@ -0,0 +1,43 @@ +const { Command } = require("@src/structures"); +const { Message } = require("discord.js"); + +module.exports = class Volume extends Command { + constructor(client) { + super(client, { + name: "volume", + description: "set the music player volume", + command: { + enabled: true, + usage: "<1-100>", + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const { channel } = message; + const volume = args[0]; + + const queue = this.client.player.getQueue(message.guild); + if (!queue || !queue.playing) return channel.send("No music currently playing !"); + + if (!volume) return channel.send(`Current volume is \`${queue.volume}\`%!`); + if (isNaN(volume) || volume < 1 || volume > 100) return channel.send("Volume must be a number between 1 and 100!"); + + if (!message.member.voice.channel) return channel.send("You're not in a voice channel !"); + + if (message.guild.me.voice.channel && message.member.voice.channel.id !== message.guild.me.voice.channel.id) + return channel.send("We are not in the same voice channel!"); + + const success = queue.setVolume(volume); + if (success) channel.send(`Volume set to \`${parseInt(volume)}%\` !`); + else channel.send("Failed to set volume"); + } +}; From 83adab4119d990a5851a39a5b91867b4811a7ebe Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Fri, 17 Sep 2021 20:12:00 +0530 Subject: [PATCH 05/26] Update .prettierrc.json --- .prettierrc.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.prettierrc.json b/.prettierrc.json index 479993c13..45a0c08e5 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,7 +1,10 @@ { "trailingComma": "es5", "tabWidth": 2, + "useTabs": false, "semi": true, "singleQuote": false, - "printWidth": 120 + "printWidth": 120, + "bracketSpacing": true, + "arrowParens": "always" } From 52a912836c9c11e90b90a3ae699b7cb9ba036a47 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Fri, 17 Sep 2021 20:12:16 +0530 Subject: [PATCH 06/26] spacing --- src/commands/music/bassboost.js | 61 +++++++++++++++++---------------- src/commands/music/pause.js | 53 ++++++++++++++-------------- src/commands/music/resume.js | 53 ++++++++++++++-------------- src/commands/music/seek.js | 59 +++++++++++++++---------------- src/commands/music/skip.js | 57 +++++++++++++++--------------- src/commands/music/stop.js | 2 +- 6 files changed, 143 insertions(+), 142 deletions(-) diff --git a/src/commands/music/bassboost.js b/src/commands/music/bassboost.js index e3ee1cb00..6e1b312c6 100644 --- a/src/commands/music/bassboost.js +++ b/src/commands/music/bassboost.js @@ -1,36 +1,37 @@ const { Command } = require("@src/structures"); const { MessageEmbed, Message } = require("discord.js"); -module.exports = class Play extends Command { - constructor(client) { - super(client, { - name: "bassboost", - description: "Toggles bassboost", - command: { - enabled: true, - category: "MUSIC", - }, - slashCommand: { - enabled: false, - }, - }); - } +module.exports = class Bassboost extends Command { + constructor(client) { + super(client, { + name: "bassboost", + description: "Toggles bassboost", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } - /** - * @param {Message} message - * @param {string[]} args - */ - async messageRun(message, args) { - const queue = message.client.player.getQueue(message.guildId); - if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); - await queue.setFilters({ - bassboost: !queue.getFiltersEnabled().includes("bassboost"), - normalizer2: !queue.getFiltersEnabled().includes("bassboost") - }); + await queue.setFilters({ + bassboost: !queue.getFiltersEnabled().includes("bassboost"), + normalizer2: !queue.getFiltersEnabled().includes("bassboost"), + }); - const embed = new MessageEmbed() - .setDescription(`šŸŽµ | Bassboost ${queue.getFiltersEnabled().includes("bassboost") ? "Enabled | āœ…" : "Disabled | āŒ"}`) - return message.channel.send({ embeds: [embed] }); - } -}; \ No newline at end of file + const embed = new MessageEmbed().setDescription( + `šŸŽµ | Bassboost ${queue.getFiltersEnabled().includes("bassboost") ? "Enabled | āœ…" : "Disabled | āŒ"}` + ); + return message.channel.send({ embeds: [embed] }); + } +}; diff --git a/src/commands/music/pause.js b/src/commands/music/pause.js index 25d013027..aed643506 100644 --- a/src/commands/music/pause.js +++ b/src/commands/music/pause.js @@ -1,33 +1,32 @@ const { Command } = require("@src/structures"); const { MessageEmbed, Message } = require("discord.js"); -module.exports = class Play extends Command { - constructor(client) { - super(client, { - name: "pause", - description: "Pause the current song", - command: { - enabled: true, - category: "MUSIC", - }, - slashCommand: { - enabled: false, - }, - }); - } +module.exports = class Pause extends Command { + constructor(client) { + super(client, { + name: "pause", + description: "Pause the current song", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } - /** - * @param {Message} message - * @param {string[]} args - */ - async messageRun(message, args) { - const queue = message.client.player.getQueue(message.guildId); - if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); - const paused = queue.setPaused(true); + const paused = queue.setPaused(true); - const embed = new MessageEmbed() - .setDescription(paused ? "šŸŽµ | Music Paused | āø" : "šŸŽµ | Music Already Paused") - return message.channel.send({ embeds: [embed] }); - } -}; \ No newline at end of file + const embed = new MessageEmbed().setDescription(paused ? "šŸŽµ | Music Paused | āø" : "šŸŽµ | Music Already Paused"); + return message.channel.send({ embeds: [embed] }); + } +}; diff --git a/src/commands/music/resume.js b/src/commands/music/resume.js index 69ffa5eb3..dd0220ebb 100644 --- a/src/commands/music/resume.js +++ b/src/commands/music/resume.js @@ -1,33 +1,32 @@ const { Command } = require("@src/structures"); const { MessageEmbed, Message } = require("discord.js"); -module.exports = class Play extends Command { - constructor(client) { - super(client, { - name: "resume", - description: "Resumes the paused song", - command: { - enabled: true, - category: "MUSIC", - }, - slashCommand: { - enabled: false, - }, - }); - } +module.exports = class Resume extends Command { + constructor(client) { + super(client, { + name: "resume", + description: "Resumes the paused song", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } - /** - * @param {Message} message - * @param {string[]} args - */ - async messageRun(message, args) { - const queue = message.client.player.getQueue(message.guildId); - if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); - const paused = queue.setPaused(false); + const paused = queue.setPaused(false); - const embed = new MessageEmbed() - .setDescription(paused ? "šŸŽµ | Music Resumed | ā–¶" : "šŸŽµ | Music not Paused") - return message.channel.send({ embeds: [embed] }); - } -}; \ No newline at end of file + const embed = new MessageEmbed().setDescription(paused ? "šŸŽµ | Music Resumed | ā–¶" : "šŸŽµ | Music not Paused"); + return message.channel.send({ embeds: [embed] }); + } +}; diff --git a/src/commands/music/seek.js b/src/commands/music/seek.js index 25eec2caa..f082bbd57 100644 --- a/src/commands/music/seek.js +++ b/src/commands/music/seek.js @@ -1,35 +1,36 @@ const { Command } = require("@src/structures"); const { MessageEmbed, Message } = require("discord.js"); -module.exports = class Play extends Command { - constructor(client) { - super(client, { - name: "seek", - description: "Seeks to the given time in seconds", - command: { - enabled: true, - category: "MUSIC", - }, - slashCommand: { - enabled: false, - }, - }); - } +module.exports = class Seek extends Command { + constructor(client) { + super(client, { + name: "seek", + description: "Seeks to the given time in seconds", + command: { + enabled: true, + usage: "", + minArgsCount: 1, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } - /** - * @param {Message} message - * @param {string[]} args - */ - async messageRun(message, args) { - const queue = message.client.player.getQueue(message.guildId); - if (!queue || !queue.playing) return message.channel.send("No music is being played!"); - if (isNaN(args[0])) return message.channel.send("Input must be in seconds"); + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + if (isNaN(args[0])) return message.channel.send("Input must be in seconds"); - const time = args[0] * 1000; - await queue.seek(time); + const time = args[0] * 1000; + await queue.seek(time); - const embed = new MessageEmbed() - .setDescription("ā© | Seeked to " + time / 1000 + " seconds") - return message.channel.send({ embeds: [embed] }); - } -}; \ No newline at end of file + const embed = new MessageEmbed().setDescription("ā© | Seeked to " + time / 1000 + " seconds"); + return message.channel.send({ embeds: [embed] }); + } +}; diff --git a/src/commands/music/skip.js b/src/commands/music/skip.js index 8009512f2..77f4b069f 100644 --- a/src/commands/music/skip.js +++ b/src/commands/music/skip.js @@ -1,34 +1,35 @@ const { Command } = require("@src/structures"); const { MessageEmbed, Message } = require("discord.js"); -module.exports = class Play extends Command { - constructor(client) { - super(client, { - name: "skip", - description: "Skip the current song", - command: { - enabled: true, - category: "MUSIC", - }, - slashCommand: { - enabled: false, - }, - }); - } +module.exports = class Skip extends Command { + constructor(client) { + super(client, { + name: "skip", + description: "Skip the current song", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } - /** - * @param {Message} message - * @param {string[]} args - */ - async messageRun(message, args) { - const queue = message.client.player.getQueue(message.guildId); - if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); - const currentTrack = queue.current; - const success = queue.skip(); + const currentTrack = queue.current; + const success = queue.skip(); - const embed = new MessageEmbed() - .setDescription(success ? `ā­ļø | Skipped **${currentTrack}**` : "āŒ | Something went wrong!") - return message.channel.send({ embeds: [embed] }); - } -}; \ No newline at end of file + const embed = new MessageEmbed().setDescription( + success ? `ā­ļø | Skipped **${currentTrack}**` : "āŒ | Something went wrong!" + ); + return message.channel.send({ embeds: [embed] }); + } +}; diff --git a/src/commands/music/stop.js b/src/commands/music/stop.js index 4e9a00e22..204a8b22b 100644 --- a/src/commands/music/stop.js +++ b/src/commands/music/stop.js @@ -1,7 +1,7 @@ const { Command } = require("@src/structures"); const { Message } = require("discord.js"); -module.exports = class Play extends Command { +module.exports = class Stop extends Command { constructor(client) { super(client, { name: "stop", From f290eeb33327a3602379b718f15a88d3147de93f Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Fri, 17 Sep 2021 20:25:27 +0530 Subject: [PATCH 07/26] music help --- src/commands/utility/help.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/commands/utility/help.js b/src/commands/utility/help.js index bcf31c53a..367c94eea 100644 --- a/src/commands/utility/help.js +++ b/src/commands/utility/help.js @@ -43,6 +43,11 @@ const CMD_CATEGORIES = { image: "https://icons.iconarchive.com/icons/lawyerwordpress/law/128/Gavel-Law-icon.png", emoji: "\uD83D\uDD28", }, + MUSIC: { + name: "Music", + image: "https://icons.iconarchive.com/icons/wwalczyszyn/iwindows/256/Music-Library-icon.png", + emoji: "šŸŽµ", + }, SOCIAL: { name: "Social", image: "https://icons.iconarchive.com/icons/dryicons/aesthetica-2/128/community-users-icon.png", From df545b133138b378cef780e29d6a0c7cbb2017b7 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sat, 18 Sep 2021 02:43:21 +0530 Subject: [PATCH 08/26] better moderation, warn command --- src/commands/moderation/ban.js | 4 +- src/commands/moderation/kick.js | 4 +- src/commands/moderation/mute.js | 4 +- src/commands/moderation/softban.js | 4 +- src/commands/moderation/unmute.js | 4 +- src/commands/moderation/warn.js | 52 +++++++++++++++++++++++ src/handlers/automod-handler.js | 17 ++------ src/utils/modUtils.js | 66 ++++++++++++++++++++++++++---- 8 files changed, 124 insertions(+), 31 deletions(-) create mode 100644 src/commands/moderation/warn.js diff --git a/src/commands/moderation/ban.js b/src/commands/moderation/ban.js index 1cff62276..c47e8cdf6 100644 --- a/src/commands/moderation/ban.js +++ b/src/commands/moderation/ban.js @@ -1,6 +1,6 @@ const { resolveMember } = require("@root/src/utils/guildUtils"); const { Command } = require("@src/structures"); -const { canInteract, banTarget } = require("@utils/modUtils"); +const { canInteract, addModAction } = require("@utils/modUtils"); const { Message } = require("discord.js"); module.exports = class BanCommand extends Command { @@ -47,7 +47,7 @@ module.exports = class BanCommand extends Command { async function ban(message, target, reason) { if (!canInteract(message.member, target, "ban", message.channel)) return; - const status = await banTarget(message.member, target, reason); + const status = await addModAction(message.member, target, reason, "BAN"); if (status) message.channel.send(`${target.user.tag} is banned from this server`); else message.channel.send(`Failed to ban ${target.user.tag}`); } diff --git a/src/commands/moderation/kick.js b/src/commands/moderation/kick.js index cdcfbd813..ef9315be8 100644 --- a/src/commands/moderation/kick.js +++ b/src/commands/moderation/kick.js @@ -1,6 +1,6 @@ const { resolveMember } = require("@root/src/utils/guildUtils"); const { Command } = require("@src/structures"); -const { canInteract, kickTarget } = require("@utils/modUtils"); +const { canInteract, addModAction } = require("@utils/modUtils"); const { Message } = require("discord.js"); module.exports = class KickCommand extends Command { @@ -47,7 +47,7 @@ module.exports = class KickCommand extends Command { async function kick(message, target, reason) { if (!canInteract(message.member, target, "kick", message.channel)) return; - let status = await kickTarget(message.member, target, reason); + let status = await addModAction(message.member, target, reason, "KICK"); if (status) message.channel.send(`${target.user.tag} is kicked from this server`); else message.channel.send(`Failed to kick ${target.user.tag}`); } diff --git a/src/commands/moderation/mute.js b/src/commands/moderation/mute.js index 0417d8acf..c49a5f9ca 100644 --- a/src/commands/moderation/mute.js +++ b/src/commands/moderation/mute.js @@ -1,5 +1,5 @@ const { Command } = require("@src/structures"); -const { setupMutedRole, canInteract, muteTarget } = require("@utils/modUtils"); +const { setupMutedRole, canInteract, addModAction } = require("@utils/modUtils"); const { getRoleByName, resolveMember } = require("@utils/guildUtils"); const { Message } = require("discord.js"); @@ -81,7 +81,7 @@ async function muteSetup(message) { async function mute(message, target, reason) { if (!canInteract(message.member, target, "mute", message.channel)) return; - const status = await muteTarget(message.member, target, reason); + const status = await addModAction(message.member, target, reason, "MUTE"); if (status === "ALREADY_MUTED") return message.channel.send(`${target.user.tag} is already muted`); if (status) message.channel.send(`${target.user.tag} is now muted on this server`); else message.channel.send(`Failed to add muted role to ${target.user.tag}`); diff --git a/src/commands/moderation/softban.js b/src/commands/moderation/softban.js index 1f8151c21..611862a22 100644 --- a/src/commands/moderation/softban.js +++ b/src/commands/moderation/softban.js @@ -1,6 +1,6 @@ const { resolveMember } = require("@root/src/utils/guildUtils"); const { Command } = require("@src/structures"); -const { canInteract, softbanTarget } = require("@utils/modUtils"); +const { canInteract, addModAction } = require("@utils/modUtils"); const { Message } = require("discord.js"); module.exports = class SoftBan extends Command { @@ -47,7 +47,7 @@ module.exports = class SoftBan extends Command { async function softban(message, target, reason) { if (!canInteract(message.member, target, "softban", message.channel)) return; - const status = await softbanTarget(message.member, target, reason); + const status = await addModAction(message.member, target, reason, "SOFTBAN"); if (status) message.channel.send(`${target.user.tag} is soft-banned from this server`); else message.channel.send(`Failed to softban ${target.user.tag}`); } diff --git a/src/commands/moderation/unmute.js b/src/commands/moderation/unmute.js index 3e695c4cd..8bfc62246 100644 --- a/src/commands/moderation/unmute.js +++ b/src/commands/moderation/unmute.js @@ -1,5 +1,5 @@ const { Command } = require("@src/structures"); -const { canInteract, unmuteTarget } = require("@utils/modUtils"); +const { canInteract, addModAction } = require("@utils/modUtils"); const { getRoleByName, resolveMember } = require("@utils/guildUtils"); const { Message } = require("discord.js"); @@ -54,7 +54,7 @@ module.exports = class UnmuteCommand extends Command { async function unmute(message, target, reason) { if (!canInteract(message.member, target, "unmute", message.channel)) return; - const status = await unmuteTarget(message.member, target, reason); + const status = await addModAction(message.member, target, reason, "UNMUTE"); if (status === "NOT_MUTED") return message.channel.send(`${target.user.tag} is not muted`); if (status) message.channel.send(`${target.user.tag} is unmuted`); else message.channel.send(`Failed to unmute ${target.user.tag}`); diff --git a/src/commands/moderation/warn.js b/src/commands/moderation/warn.js new file mode 100644 index 000000000..d5d6b32b0 --- /dev/null +++ b/src/commands/moderation/warn.js @@ -0,0 +1,52 @@ +const { resolveMember } = require("@root/src/utils/guildUtils"); +const { Command } = require("@src/structures"); +const { canInteract, addModAction } = require("@utils/modUtils"); +const { Message } = require("discord.js"); + +module.exports = class Warn extends Command { + constructor(client) { + super(client, { + name: "warn", + description: "warns the specified member(s)", + command: { + enabled: true, + usage: " [reason]", + minArgsCount: 1, + category: "MODERATION", + userPermissions: ["KICK_MEMBERS"], + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const { content } = message; + const mentions = message.mentions.members; + + // !warn ID + if (mentions.size === 0) { + const target = await resolveMember(message, args[0], true); + if (!target) return message.reply(`No user found matching ${args[0]}`); + const reason = content.split(args[0])[1].trim(); + return warn(message, target, reason); + } + + // !kick @m1 @m2 ... + const regex = /<@!?(\d+)>/g; + const matches = content.match(regex); + const lastMatch = matches[matches.length - 1]; + const reason = content.split(lastMatch)[1].trim(); + + mentions.forEach(async (target) => await warn(message, target, reason)); + } +}; + +async function warn(message, target, reason) { + if (!canInteract(message.member, target, "warn", message.channel)) return; + let status = await addModAction(message.member, target, reason, "WARN"); + if (status) message.channel.send(`${target.user.tag} is warned by ${message.author.tag}`); + else message.channel.send(`Failed to warn ${target.user.tag}`); +} diff --git a/src/handlers/automod-handler.js b/src/handlers/automod-handler.js index d96558206..f822af665 100644 --- a/src/handlers/automod-handler.js +++ b/src/handlers/automod-handler.js @@ -2,7 +2,7 @@ const { Message, MessageEmbed } = require("discord.js"); const { sendMessage, safeDM } = require("@utils/botUtils"); const { containsLink, containsDiscordInvite } = require("@utils/miscUtils"); const { addStrikes } = require("@schemas/profile-schema"); -const { muteTarget, kickTarget, banTarget } = require("@utils/modUtils"); +const { addModAction } = require("@utils/modUtils"); const { EMBED_COLORS } = require("@root/config"); const Ascii = require("ascii-table"); @@ -142,19 +142,8 @@ async function performAutomod(message, settings) { if (profile.strikes >= automod.strikes) { const reason = "Automod: Max strikes received"; - switch (automod.action) { - case "MUTE": - await muteTarget(message.guild.me, message.member, reason); - break; - - case "KICK": - await kickTarget(message.guild.me, message.member, reason); - break; - - case "BAN": - await banTarget(message.guild.me, message.member, reason); - break; - } + // Add Moderation + await addModAction(message.guild.me, message.member, reason, automod.action); // Reset Strikes await addStrikes(message.guildId, message.member.id, -profile.strikes); diff --git a/src/utils/modUtils.js b/src/utils/modUtils.js index 696c4fd95..36cf7d888 100644 --- a/src/utils/modUtils.js +++ b/src/utils/modUtils.js @@ -1,7 +1,7 @@ const { Message, Guild, GuildMember, TextChannel, Collection, MessageEmbed } = require("discord.js"); const { sendMessage } = require("@utils/botUtils"); const { containsLink, timeformat } = require("@utils/miscUtils"); -const { addModLogToDb, removeMutes, getMuteInfo } = require("@schemas/modlog-schema"); +const { addModLogToDb, removeMutes, getMuteInfo, getWarnings } = require("@schemas/modlog-schema"); const { getSettings } = require("@schemas/guild-schema"); const { EMOJIS, EMBED_COLORS } = require("@root/config"); const { getRoleByName } = require("./guildUtils"); @@ -148,6 +148,28 @@ async function purgeMessages(message, type, amount, argument) { } } +/** + * warns the target and logs to the database, channel + * @param {GuildMember} issuer + * @param {GuildMember} target + * @param {string} reason + */ +async function warnTarget(issuer, target, reason) { + if (!memberInteract(issuer.guild.me, target)) return; + try { + await logModeration(issuer, target, reason, "Warn"); + const current = await getWarnings(issuer.guild.id, target.id); + const settings = await getSettings(issuer.guild); + if (current.length > settings.max_warnings) { + await addModAction(issuer.guild.me, target, "Max warnings reached", settings.max_warn_action); + } + return true; + } catch (ex) { + console.log(ex); + return false; + } +} + /** * Checks if the target has the muted role * @param {GuildMember} target @@ -339,15 +361,45 @@ async function logModeration(issuer, target, reason, type, data = {}) { sendMessage(logChannel, { embeds: [embed] }); } +/** + * + * @param {GuildMember} issuer + * @param {GuildMember} target + * @param {string} reason + * @param {"WARN"|"MUTE"|"UNMUTE"|"KICK"|"SOFTBAN"|"BAN"} action + */ +async function addModAction(issuer, target, reason, action) { + switch (action) { + case "WARN": + await warnTarget(issuer, target, reason); + break; + + case "MUTE": + await muteTarget(issuer, target, reason); + break; + + case "UNMUTE": + await unmuteTarget(issuer, target, reason); + break; + + case "KICK": + await kickTarget(issuer, target, reason); + break; + + case "SOFTBAN": + await softbanTarget(issuer, target, reason); + break; + + case "BAN": + await banTarget(issuer, target, reason); + break; + } +} + module.exports = { memberInteract, canInteract, setupMutedRole, purgeMessages, - muteTarget, - unmuteTarget, - kickTarget, - softbanTarget, - banTarget, - logModeration, + addModAction, }; From e6aab3649b79d545c758a89f1939990f50a84f77 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sat, 18 Sep 2021 02:43:44 +0530 Subject: [PATCH 09/26] better schema caching --- src/schemas/counter-schema.js | 69 +++++++----- src/schemas/guild-schema.js | 175 ++++++++++++++++++++++------- src/schemas/modlog-schema.js | 7 ++ src/schemas/reactionrole-schema.js | 4 +- 4 files changed, 187 insertions(+), 68 deletions(-) diff --git a/src/schemas/counter-schema.js b/src/schemas/counter-schema.js index 167b6a211..f2149f33e 100644 --- a/src/schemas/counter-schema.js +++ b/src/schemas/counter-schema.js @@ -31,54 +31,73 @@ module.exports = { return config; }, - setTotalCountChannel: async (guildId, channelId, name) => - Model.updateOne( + getCounterGuilds: async () => Model.find().select("_id").lean({ defaults: true }), + + setTotalCountChannel: async (guildId, channelId, name) => { + await Model.updateOne( { _id: guildId }, { - _id: guildId, tc_channel: channelId, tc_name: name, }, { upsert: true } - ).then(cache.remove(guildId)), + ); - setMemberCountChannel: async (guildId, channelId, name) => - Model.updateOne( + if (cache.contains(guildId)) { + const config = cache.get(guildId); + config.tc_channel = channelId; + config.tc_name = name; + } + }, + + setMemberCountChannel: async (guildId, channelId, name) => { + await Model.updateOne( { _id: guildId }, { - _id: guildId, mc_channel: channelId, mc_name: name, }, { upsert: true } - ).then(cache.remove(guildId)), + ); + + if (cache.contains(guildId)) { + const config = cache.get(guildId); + config.mc_channel = channelId; + config.mc_name = name; + } + }, - setBotCountChannel: async (guildId, channelId, name) => - Model.updateOne( + setBotCountChannel: async (guildId, channelId, name) => { + await Model.updateOne( { _id: guildId }, { - _id: guildId, bc_channel: channelId, bc_name: name, }, { upsert: true } - ).then(cache.remove(guildId)), + ); + + if (cache.contains(guildId)) { + const config = cache.get(guildId); + config.bc_channel = channelId; + config.bc_name = name; + } + }, updateBotCount: async (guildId, count, isIncrement = false) => { + // Increment Count if (isIncrement) { - return Model.updateOne( - { _id: guildId }, - { - _id: guildId, - $inc: { bot_count: count }, - }, - { upsert: true } - ).then(cache.remove(guildId)); + await Model.updateOne({ _id: guildId }, { $inc: { bot_count: count } }, { upsert: true }); + if (cache.contains(guildId)) { + cache.get(guildId).bot_count += count; + } + } + // Update Count + else { + await Model.updateOne({ _id: guildId }, { bot_count: count }, { upsert: true }); + if (cache.contains(guildId)) { + cache.get(guildId).bot_count = count; + } } - return Model.updateOne({ _id: guildId }, { _id: guildId, bot_count: count }, { upsert: true }).then( - cache.remove(guildId) - ); }, - - getCounterGuilds: async () => Model.find().select("_id").lean({ defaults: true }), }; diff --git a/src/schemas/guild-schema.js b/src/schemas/guild-schema.js index b90b090b6..3dcd52d91 100644 --- a/src/schemas/guild-schema.js +++ b/src/schemas/guild-schema.js @@ -75,6 +75,14 @@ const Schema = mongoose.Schema({ ], }, modlog_channel: String, + max_warnings: { + type: Number, + default: 5, + }, + max_warn_action: { + type: String, + default: "BAN", + }, }); const Model = mongoose.model("guild", Schema); @@ -111,80 +119,165 @@ module.exports = { return guildData; }, + registerGuild, + + updateGuildLeft: async (guild) => + Model.updateOne({ _id: guild.id }, { "data.leftAt": new Date() }).then(cache.remove(guild.id)), + setPrefix: async (_id, prefix) => { - await Model.updateOne({ _id }, { prefix }).then(cache.remove(_id)); + await Model.updateOne({ _id }, { prefix }); + if (cache.contains(_id)) { + cache.get(_id).prefix = prefix; + } }, xpSystem: async (_id, status) => { - await Model.updateOne({ _id }, { "ranking.enabled": status }).then(cache.remove(_id)); + await Model.updateOne({ _id }, { "ranking.enabled": status }); + if (cache.contains(_id)) { + cache.get(_id).ranking.enabled = status; + } }, setTicketLogChannel: async (_id, channelId) => { - await Model.updateOne({ _id }, { "ticket.log_channel": channelId }).then(cache.remove(_id)); + await Model.updateOne({ _id }, { "ticket.log_channel": channelId }); + if (cache.contains(_id)) { + cache.get(_id).ticket.log_channel = channelId; + } }, setTicketLimit: async (_id, limit) => { - await Model.updateOne({ _id }, { "ticket.limit": limit }).then(cache.remove(_id)); + await Model.updateOne({ _id }, { "ticket.limit": limit }); + if (cache.contains(_id)) { + cache.get(_id).ticket.limit = limit; + } }, - maxStrikes: async (_id, strikes) => Model.updateOne({ _id }, { "automod.strikes": strikes }).then(cache.remove(_id)), + maxStrikes: async (_id, strikes) => { + await Model.updateOne({ _id }, { "automod.strikes": strikes }); + if (cache.contains(_id)) { + cache.get(_id).automod.strikes = strikes; + } + }, - automodAction: async (_id, action) => Model.updateOne({ _id }, { "automod.action": action }).then(cache.remove(_id)), + automodAction: async (_id, action) => { + await Model.updateOne({ _id }, { "automod.action": action }); + if (cache.contains(_id)) { + cache.get(_id).automod.action = action; + } + }, - automodDebug: async (_id, status) => Model.updateOne({ _id }, { "automod.debug": status }).then(cache.remove(_id)), + automodDebug: async (_id, status) => { + await Model.updateOne({ _id }, { "automod.debug": status }); + if (cache.contains(_id)) { + cache.get(_id).automod.debug = status; + } + }, - antiLinks: async (_id, status) => Model.updateOne({ _id }, { "automod.anti_links": status }).then(cache.remove(_id)), + antiLinks: async (_id, status) => { + await Model.updateOne({ _id }, { "automod.anti_links": status }); + if (cache.contains(_id)) { + cache.get(_id).automod.anti_links = status; + } + }, - antiScam: async (_id, status) => Model.updateOne({ _id }, { "automod.anti_scam": status }).then(cache.remove(_id)), + antiScam: async (_id, status) => { + await Model.updateOne({ _id }, { "automod.anti_scam": status }); + if (cache.contains(_id)) { + cache.get(_id).automod.anti_scam = status; + } + }, - antiInvites: async (_id, status) => - Model.updateOne({ _id }, { "automod.anti_invites": status }).then(cache.remove(_id)), + antiInvites: async (_id, status) => { + await Model.updateOne({ _id }, { "automod.anti_invites": status }); + if (cache.contains(_id)) { + cache.get(_id).automod.anti_invites = status; + } + }, - antiGhostPing: async (_id, status) => - Model.updateOne({ _id }, { "automod.anti_ghostping": status }).then(cache.remove(_id)), + antiGhostPing: async (_id, status) => { + await Model.updateOne({ _id }, { "automod.anti_ghostping": status }); + if (cache.contains(_id)) { + cache.get(_id).automod.anti_ghostping = status; + } + }, - maxMentions: async (_id, amount) => - Model.updateOne({ _id }, { "automod.max_mentions": amount }).then(cache.remove(_id)), + maxMentions: async (_id, amount) => { + await Model.updateOne({ _id }, { "automod.max_mentions": amount }); + if (cache.contains(_id)) { + cache.get(_id).automod.max_mentions = amount; + } + }, - maxRoleMentions: async (_id, amount) => - Model.updateOne({ _id }, { "automod.max_role_mentions": amount }).then(cache.remove(_id)), + maxRoleMentions: async (_id, amount) => { + await Model.updateOne({ _id }, { "automod.max_role_mentions": amount }); + if (cache.contains(_id)) { + cache.get(_id).automod.max_role_mentions = amount; + } + }, - maxLines: async (_id, amount) => Model.updateOne({ _id }, { "automod.max_lines": amount }).then(cache.remove(_id)), + maxLines: async (_id, amount) => { + await Model.updateOne({ _id }, { "automod.max_lines": amount }); + if (cache.contains(_id)) { + cache.get(_id).automod.max_lines = amount; + } + }, inviteTracking: async (_id, status) => { - Model.updateOne({ _id }, { $set: { "invite.tracking": status } }).then(cache.remove(_id)); + await Model.updateOne({ _id }, { "invite.tracking": status }); + if (cache.contains(_id)) { + cache.get(_id).invite.tracking = status; + } }, - addInviteRank: async (_id, roleId, invites) => - Model.updateOne( - { _id }, - { - $push: { - "invite.ranks": { - _id: roleId, - invites, - }, - }, - } - ).then(cache.remove(_id)), - - removeInviteRank: async (_id, roleId) => - Model.updateOne({ _id }, { $pull: { "invite.ranks": { _id: roleId } } }).then(cache.remove(_id)), + addInviteRank: async (_id, roleId, invites) => { + const toPush = { + _id: roleId, + invites, + }; - registerGuild, + await Model.updateOne({ _id }, { $push: { "invite.ranks": toPush } }); + if (cache.contains(_id)) { + cache.get(_id).invite.ranks.push(toPush); + } + }, - updateGuildLeft: async (guild) => { - await Model.updateOne({ _id: guild.id }, { "data.leftAt": new Date() }).then(cache.remove(guild.id)); + removeInviteRank: async (_id, roleId) => { + await Model.updateOne({ _id }, { $pull: { "invite.ranks": { _id: roleId } } }); + cache.remove(_id); }, flagTranslation: async (_id, status) => { - await Model.updateOne({ _id }, { $set: { "flag_translation.enabled": status } }).then(cache.remove(_id)); + await Model.updateOne({ _id }, { "flag_translation.enabled": status }); + if (cache.contains(_id)) { + cache.get(_id).flag_translation.enabled = status; + } }, setFlagTrChannels: async (_id, channels) => { - await Model.updateOne({ _id }, { "flag_translation.channels": channels }).then(cache.remove(_id)); + await Model.updateOne({ _id }, { "flag_translation.channels": channels }); + if (cache.contains(_id)) { + cache.get(_id).flag_translation.channels = channels; + } + }, + + modLogChannel: async (_id, channelId) => { + await Model.updateOne({ _id }, { modlog_channel: channelId }); + if (cache.contains(_id)) { + cache.get(_id).modlog_channel = channelId; + } }, - modLogChannel: async (_id, channelId) => - Model.updateOne({ _id }, { modlog_channel: channelId }).then(cache.remove(_id)), + maxWarnings: async (_id, amount) => { + await Model.updateOne({ _id }, { max_warnings: amount }); + if (cache.contains(_id)) { + cache.get(_id).max_warnings = amount; + } + }, + + maxWarnAction: async (_id, action) => { + await Model.updateOne({ _id }, { max_warn_action: action }); + if (cache.contains(_id)) { + cache.get(_id).max_warn_action = action; + } + }, }; diff --git a/src/schemas/modlog-schema.js b/src/schemas/modlog-schema.js index 43774efa4..4c20079fc 100644 --- a/src/schemas/modlog-schema.js +++ b/src/schemas/modlog-schema.js @@ -82,4 +82,11 @@ module.exports = { }, { "data.current": false } ), + + getWarnings: async (guildId, targetId) => + Model.find({ + guild_id: guildId, + member_id: targetId, + type: "WARN", + }), }; diff --git a/src/schemas/reactionrole-schema.js b/src/schemas/reactionrole-schema.js index adf2c878f..62db2e48e 100644 --- a/src/schemas/reactionrole-schema.js +++ b/src/schemas/reactionrole-schema.js @@ -35,6 +35,8 @@ module.exports = { }); }, + getReactionRole: (guildId, channelId, messageId) => cache.get(getKey(guildId, channelId, messageId)), + addReactionRole: async (guildId, channelId, messageId, emote, roleId) => { const filter = { guild_id: guildId, @@ -70,6 +72,4 @@ module.exports = { await Model.deleteOne(filter); cache.delete(getKey(guildId, channelId, messageId)); }, - - getReactionRole: (guildId, channelId, messageId) => cache.get(getKey(guildId, channelId, messageId)), }; From 4ba362f510369dd5437aa04592fc640df20b7c27 Mon Sep 17 00:00:00 2001 From: ishitajs <78543191+ishitajs@users.noreply.github.com> Date: Sat, 18 Sep 2021 18:10:11 +0530 Subject: [PATCH 10/26] Create emoji-info.js --- src/commands/info/emoji-info.js | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/commands/info/emoji-info.js diff --git a/src/commands/info/emoji-info.js b/src/commands/info/emoji-info.js new file mode 100644 index 000000000..a267493c1 --- /dev/null +++ b/src/commands/info/emoji-info.js @@ -0,0 +1,39 @@ +const { Command } = require("@src/structures"); +const Discord = require('discord.js') +const { Message } = require("discord.js"); + +module.exports = class EmojiInfoCommand extends Command { + constructor(client) { + super(client, { + name: "emojiinfo", + aliases: ["emoji"], + description: "shows info about an emoji", + command: { + enabled: true, + category: "INFORMATION", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const emoji = args[0]; + if (!emoji) return message.channel.send("No emoji provided!"); + let custom = Discord.Util.parseEmoji(emoji); + let url = `https://cdn.discordapp.com/emojis/${custom.id}` + let link = "" + if (custom.animated === true) { + link = `${url}.gif?v=1` + } + else { + link = `${url}.png` + } + + return message.channel.send(`**Id:** ${custom.id}\n**Name:** ${custom.name}\n**Animated:** ${custom.animated ? "Yes" : "No"}\n**Url:** ${link}`); + }} \ No newline at end of file From a8f4abf83c0a6a0268667b62e4649c7800df0797 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sat, 18 Sep 2021 21:16:58 +0530 Subject: [PATCH 11/26] Update emoji-info.js --- src/commands/info/emoji-info.js | 38 ++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/commands/info/emoji-info.js b/src/commands/info/emoji-info.js index a267493c1..39bf71210 100644 --- a/src/commands/info/emoji-info.js +++ b/src/commands/info/emoji-info.js @@ -1,15 +1,16 @@ const { Command } = require("@src/structures"); -const Discord = require('discord.js') -const { Message } = require("discord.js"); +const { Message, Util, MessageEmbed } = require("discord.js"); -module.exports = class EmojiInfoCommand extends Command { +module.exports = class EmojiInfo extends Command { constructor(client) { super(client, { name: "emojiinfo", - aliases: ["emoji"], description: "shows info about an emoji", command: { enabled: true, + usage: "", + minArgsCount: 1, + aliases: ["emoji"], category: "INFORMATION", }, slashCommand: { @@ -24,16 +25,19 @@ module.exports = class EmojiInfoCommand extends Command { */ async messageRun(message, args) { const emoji = args[0]; - if (!emoji) return message.channel.send("No emoji provided!"); - let custom = Discord.Util.parseEmoji(emoji); - let url = `https://cdn.discordapp.com/emojis/${custom.id}` - let link = "" - if (custom.animated === true) { - link = `${url}.gif?v=1` - } - else { - link = `${url}.png` - } - - return message.channel.send(`**Id:** ${custom.id}\n**Name:** ${custom.name}\n**Animated:** ${custom.animated ? "Yes" : "No"}\n**Url:** ${link}`); - }} \ No newline at end of file + let custom = Util.parseEmoji(emoji); + if (!custom.id) return message.channel.send("This is not a valid guild emoji"); + + let url = `https://cdn.discordapp.com/emojis/${custom.id}.${custom.animated ? "gif?v=1" : "png"}`; + + const embed = new MessageEmbed() + .setColor(this.client.config.EMBED_COLORS.BOT_EMBED) + .setAuthor("Emoji Info") + .setDescription( + `**Id:** ${custom.id}\n` + `**Name:** ${custom.name}\n` + `**Animated:** ${custom.animated ? "Yes" : "No"}` + ) + .setImage(url); + + return message.channel.send({ embeds: [embed] }); + } +}; From 78486e36f6023e7513d74846e9ff702cd523026b Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 00:48:51 +0530 Subject: [PATCH 12/26] profile, rank command --- src/commands/info/profile.js | 54 ++++++++++++++++++++++++++++ src/commands/info/rank.js | 63 +++++++++++++++++++++++++++++++++ src/handlers/automod-handler.js | 4 +-- src/schemas/profile-schema.js | 33 ++++++++++++++++- src/utils/modUtils.js | 18 ++++++---- 5 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 src/commands/info/profile.js create mode 100644 src/commands/info/rank.js diff --git a/src/commands/info/profile.js b/src/commands/info/profile.js new file mode 100644 index 000000000..6d1d5b666 --- /dev/null +++ b/src/commands/info/profile.js @@ -0,0 +1,54 @@ +const { Command } = require("@src/structures"); +const { Message, MessageEmbed } = require("discord.js"); +const { EMBED_COLORS, EMOJIS } = require("@root/config"); +const { getProfile } = require("@schemas/profile-schema"); +const { getSettings } = require("@schemas/guild-schema"); +const { resolveMember } = require("@utils/guildUtils"); +const { getUser } = require("@root/src/schemas/user-schema"); + +module.exports = class Profile extends Command { + constructor(client) { + super(client, { + name: "profile", + description: "shows members profile", + cooldown: 5, + command: { + enabled: true, + category: "INFORMATION", + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const target = (await resolveMember(message, args[0])) || message.member; + const { user } = target; + + const settings = await getSettings(message.guild); + const profile = await getProfile(message.guildId, user.id); + const userData = await getUser(user.id); + + const embed = new MessageEmbed() + .setThumbnail(user.displayAvatarURL()) + .setColor(EMBED_COLORS.BOT_EMBED) + .addField("User Tag", user.tag, true) + .addField("ID", user.id, true) + .addField("Discord Registered", user.createdAt.toDateString(), false) + .addField("Cash", `${userData?.coins || 0} ${EMOJIS.CURRENCY}`, true) + .addField("Bank", `${userData?.bank || 0} ${EMOJIS.CURRENCY}`, true) + .addField("Net Worth", `${(userData?.coins || 0) + (userData?.bank || 0)}${EMOJIS.CURRENCY}`, true) + .addField("Messages*", `${settings.ranking.enabled ? profile?.messages + " " || 0 + " " : "Not Tracked"}`, true) + .addField("XP*", `${settings.ranking.enabled ? (profile?.xp || 0) + " " : "Not Tracked"}`, true) + .addField("Level*", `${settings.ranking.enabled ? (profile?.level || 0) + " " : "Not Tracked"}`, true) + .addField("Strikes*", profile?.strikes || 0 + " ", true) + .addField("Warnings*", profile?.warnings || 0 + " ", true) + .addField("Reputation", `${userData?.reputation?.received || 0}`, true) + .addField("Avatar-URL", user.displayAvatarURL({ format: "png" })) + .setFooter(`Requested By ${message.author.tag}\nFields marked (*) are guild specific`); + + message.channel.send({ embeds: [embed] }); + } +}; diff --git a/src/commands/info/rank.js b/src/commands/info/rank.js new file mode 100644 index 000000000..e6d5a366f --- /dev/null +++ b/src/commands/info/rank.js @@ -0,0 +1,63 @@ +const { Command } = require("@src/structures"); +const { Message, MessageAttachment } = require("discord.js"); +const { API, EMBED_COLORS } = require("@root/config"); +const { getProfile, getTop100 } = require("@schemas/profile-schema"); +const { downloadImage } = require("@utils/httpUtils"); +const { getSettings } = require("@schemas/guild-schema"); +const { resolveMember } = require("@utils/guildUtils"); + +module.exports = class Rank extends Command { + constructor(client) { + super(client, { + name: "rank", + description: "shows members rank in this server", + cooldown: 5, + command: { + enabled: true, + category: "INFORMATION", + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const target = (await resolveMember(message, args[0])) || message.member; + const { user } = target; + + const settings = await getSettings(message.guild); + if (!settings.ranking.enabled) return message.channel.send("Ranking is disabled on this server"); + + const profile = await getProfile(message.guildId, user.id); + if (!profile) return message.channel.send(`${user.tag} is not ranked yet!`); + + const lb = await getTop100(message.guildId); + let pos = -1; + lb.forEach((doc, i) => { + if (doc.member_id == target.id) { + pos = i + 1; + } + }); + + const xpNeeded = profile.level * profile.level * 100; + + const url = new URL(`${API.IMAGE_API}/utils/rank-card`); + url.searchParams.append("name", user.username); + url.searchParams.append("discriminator", user.discriminator); + url.searchParams.append("avatar", user.displayAvatarURL({ format: "png", size: 128 })); + url.searchParams.append("currentxp", profile.xp); + url.searchParams.append("reqxp", xpNeeded); + url.searchParams.append("level", profile.level); + url.searchParams.append("barcolor", EMBED_COLORS.BOT_EMBED); + url.searchParams.append("status", message.member.presence.status.toString()); + if (pos !== -1) url.searchParams.append("rank", pos); + + const rankCard = await downloadImage(url.href); + if (!rankCard) return message.reply("Failed to generate rank-card"); + + const attachment = new MessageAttachment(rankCard, "rank.png"); + message.channel.send({ files: [attachment] }); + } +}; diff --git a/src/handlers/automod-handler.js b/src/handlers/automod-handler.js index f822af665..6291fed1e 100644 --- a/src/handlers/automod-handler.js +++ b/src/handlers/automod-handler.js @@ -140,10 +140,8 @@ async function performAutomod(message, settings) { // check if max strikes are received if (profile.strikes >= automod.strikes) { - const reason = "Automod: Max strikes received"; - // Add Moderation - await addModAction(message.guild.me, message.member, reason, automod.action); + await addModAction(message.guild.me, message.member, "Automod: Max strikes received", automod.action); // Reset Strikes await addStrikes(message.guildId, message.member.id, -profile.strikes); diff --git a/src/schemas/profile-schema.js b/src/schemas/profile-schema.js index cce54815f..f82780a14 100644 --- a/src/schemas/profile-schema.js +++ b/src/schemas/profile-schema.js @@ -24,11 +24,29 @@ const Schema = mongoose.Schema({ type: Number, default: 0, }, + warnings: { + type: Number, + default: 0, + }, }); const Model = mongoose.model("profile", Schema); module.exports = { + getProfile: async (guildId, memberId) => + Model.findOne({ + guild_id: guildId, + member_id: memberId, + }).lean({ defaults: true }), + + getTop100: async (guildId) => + Model.find({ + guild_id: guildId, + }) + .limit(100) + .sort({ level: -1, xp: -1 }) + .lean({ defaults: true }), + incrementXP: async (guildId, memberId, xp) => Model.findOneAndUpdate( { @@ -82,5 +100,18 @@ module.exports = { upsert: true, new: true, } - ), + ).lean({ defaults: true }), + + addWarnings: async (guildId, memberId, warnings) => + Model.findOneAndUpdate( + { + guild_id: guildId, + member_id: memberId, + }, + { $inc: { warnings } }, + { + upsert: true, + new: true, + } + ).lean({ defaults: true }), }; diff --git a/src/utils/modUtils.js b/src/utils/modUtils.js index 36cf7d888..9e17741d8 100644 --- a/src/utils/modUtils.js +++ b/src/utils/modUtils.js @@ -1,8 +1,9 @@ const { Message, Guild, GuildMember, TextChannel, Collection, MessageEmbed } = require("discord.js"); const { sendMessage } = require("@utils/botUtils"); const { containsLink, timeformat } = require("@utils/miscUtils"); -const { addModLogToDb, removeMutes, getMuteInfo, getWarnings } = require("@schemas/modlog-schema"); +const { addModLogToDb, removeMutes, getMuteInfo } = require("@schemas/modlog-schema"); const { getSettings } = require("@schemas/guild-schema"); +const { addWarnings } = require("@schemas/profile-schema"); const { EMOJIS, EMBED_COLORS } = require("@root/config"); const { getRoleByName } = require("./guildUtils"); @@ -155,13 +156,18 @@ async function purgeMessages(message, type, amount, argument) { * @param {string} reason */ async function warnTarget(issuer, target, reason) { - if (!memberInteract(issuer.guild.me, target)) return; + const { guild } = issuer; + if (!memberInteract(guild.me, target)) return; + try { await logModeration(issuer, target, reason, "Warn"); - const current = await getWarnings(issuer.guild.id, target.id); - const settings = await getSettings(issuer.guild); - if (current.length > settings.max_warnings) { - await addModAction(issuer.guild.me, target, "Max warnings reached", settings.max_warn_action); + const profile = await addWarnings(guild.id, target.id, 1); + const settings = await getSettings(guild); + + // check if max warnings are reached + if (profile.warnings > settings.max_warnings) { + await addModAction(guild.me, target, "Max warnings reached", settings.max_warn_action); // moderate + await addWarnings(guild.id, target.id, -profile.warnings); // reset warnings } return true; } catch (ex) { From 6fc89aa00a8777ce06ed6c5251bc732a161cdc94 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 01:08:31 +0530 Subject: [PATCH 13/26] typos --- src/commands/automod/automod.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/automod/automod.js b/src/commands/automod/automod.js index d79e410e4..4e3c42e17 100644 --- a/src/commands/automod/automod.js +++ b/src/commands/automod/automod.js @@ -24,7 +24,7 @@ module.exports = class Automod extends Command { }, { trigger: "action ", - description: "set automod action to performed after maximum strikes", + description: "set action to be performed after receiving maximum strikes", }, { trigger: "debug ", @@ -107,7 +107,7 @@ async function setAction(message, args, prefix) { if (action === "MUTE") { let mutedRole = getRoleByName(message.guild, "muted"); if (!mutedRole) { - return await message.reply(`Muted role doesn't exist in this guild. Use \`${prefix}mute setup\` to create one`); + return message.reply(`Muted role doesn't exist in this guild. Use \`${prefix}mute setup\` to create one`); } if (!mutedRole.editable) { From ad3a595fb4e1d814c13597e641c5159334003ca9 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 01:10:44 +0530 Subject: [PATCH 14/26] max-warnings, max-warn action --- src/commands/admin/maxwarn.js | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/commands/admin/maxwarn.js diff --git a/src/commands/admin/maxwarn.js b/src/commands/admin/maxwarn.js new file mode 100644 index 000000000..c403330e4 --- /dev/null +++ b/src/commands/admin/maxwarn.js @@ -0,0 +1,77 @@ +const { Command } = require("@src/structures"); +const { maxWarnings, maxWarnAction } = require("@schemas/guild-schema"); +const { Message } = require("discord.js"); +const { getRoleByName } = require("@utils/guildUtils"); + +module.exports = class MaxWarn extends Command { + constructor(client) { + super(client, { + name: "maxwarn", + description: "set max warnings configuration", + command: { + enabled: true, + minArgsCount: 1, + subcommands: [ + { + trigger: "limit ", + description: "set max warnings a member can receive before taking an action", + }, + { + trigger: "action ", + description: "set action to performed after receiving maximum warnings", + }, + ], + category: "ADMIN", + userPermissions: ["ADMINISTRATOR"], + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args, invoke, prefix) { + const input = args[0].toUpperCase(); + + // Limit configuration + if (input === "LIMIT") { + const max = args[1]; + if (isNaN(max) || Number.parseInt(max) < 1) + return message.reply("Max Warnings must be a valid number greater than 0"); + + await maxWarnings(message.guildId, max); + message.channel.send(`Configuration saved! Maximum warnings is set to ${max}`); + } + + // Action + else if (input === "ACTION") { + const action = args[1]?.toUpperCase(); + + if (!action) message.reply("Please choose an action. Action can be `Mute`/`Kick`/`Ban`"); + if (!["MUTE", "KICK", "BAN"].includes(action)) + return message.reply("Not a valid action. Action can be `Mute`/`Kick`/`Ban`"); + + if (action === "MUTE") { + let mutedRole = getRoleByName(message.guild, "muted"); + if (!mutedRole) { + return message.reply(`Muted role doesn't exist in this guild. Use \`${prefix}mute setup\` to create one`); + } + + if (!mutedRole.editable) { + return message.reply( + "I do not have permission to move members to `Muted` role. Is that role below my highest role?" + ); + } + } + + await maxWarnAction(message.guildId, action); + message.channel.send(`Configuration saved! Max Warnings action is set to ${action}`); + } + + // send usage + else { + return this.sendUsage(message.channel, prefix, invoke, "Incorrect Arguments"); + } + } +}; From aea0526581fe61b36f1ab0b5476e48b44aae29f0 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 01:39:49 +0530 Subject: [PATCH 15/26] interaction - error handling --- src/events/interactions/interactionCreate.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/events/interactions/interactionCreate.js b/src/events/interactions/interactionCreate.js index 995ad2b93..fcd27742b 100644 --- a/src/events/interactions/interactionCreate.js +++ b/src/events/interactions/interactionCreate.js @@ -32,8 +32,12 @@ module.exports = async (client, interaction) => { await interaction.deferReply({ ephemeral: command.slashCommand.ephemeral }).catch(() => {}); // Run the event - await command.interactionRun(interaction, interaction.options); - command.applyCooldown(interaction.user.id); + try { + await command.interactionRun(interaction, interaction.options); + command.applyCooldown(interaction.user.id); + } catch { + interaction.deferReply("Oops! An error occurred while running the command"); + } } // Context Menu @@ -61,7 +65,11 @@ module.exports = async (client, interaction) => { await interaction.deferReply({ ephemeral: command.contextMenu.ephemeral }).catch(() => {}); // Run the event - await command.contextRun(interaction, interaction.options); - command.applyCooldown(interaction.user.id); + try { + await command.contextRun(interaction, interaction.options); + command.applyCooldown(interaction.user.id); + } catch { + interaction.deferReply("Oops! An error occurred while running the command"); + } } }; From bb9706284ea6d11ceeaec4a564635d45c4d738ec Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 01:40:22 +0530 Subject: [PATCH 16/26] context menu commands --- src/commands/info/avatar.js | 22 ++++++++++- src/commands/info/profile.js | 65 ++++++++++++++++++++------------- src/commands/info/userinfo.js | 2 +- src/commands/moderation/warn.js | 19 +++++++++- 4 files changed, 80 insertions(+), 28 deletions(-) diff --git a/src/commands/info/avatar.js b/src/commands/info/avatar.js index 759bec4af..fd8d211b6 100644 --- a/src/commands/info/avatar.js +++ b/src/commands/info/avatar.js @@ -1,5 +1,11 @@ const { Command } = require("@src/structures"); -const { MessageEmbed, Message, CommandInteraction, CommandInteractionOptionResolver } = require("discord.js"); +const { + MessageEmbed, + Message, + CommandInteraction, + CommandInteractionOptionResolver, + ContextMenuInteraction, +} = require("discord.js"); const { EMOJIS, EMBED_COLORS } = require("@root/config.js"); const { resolveMember } = require("@utils/guildUtils"); @@ -8,6 +14,7 @@ module.exports = class AvatarCommand extends Command { super(client, { name: "avatar", description: "displays avatar information about the user", + cooldown: 5, command: { enabled: true, usage: "[@member|id]", @@ -25,6 +32,10 @@ module.exports = class AvatarCommand extends Command { }, ], }, + contextMenu: { + enabled: true, + type: "USER", + }, }); } @@ -48,6 +59,15 @@ module.exports = class AvatarCommand extends Command { const embed = buildEmbed(target); interaction.followUp({ embeds: [embed] }); } + + /** + * @param {ContextMenuInteraction} interaction + */ + async contextRun(interaction) { + const user = await interaction.client.users.fetch(interaction.targetId); + const embed = buildEmbed(user); + interaction.followUp({ embeds: [embed] }); + } }; const buildEmbed = (user) => { diff --git a/src/commands/info/profile.js b/src/commands/info/profile.js index 6d1d5b666..571fcb0cf 100644 --- a/src/commands/info/profile.js +++ b/src/commands/info/profile.js @@ -1,10 +1,10 @@ const { Command } = require("@src/structures"); -const { Message, MessageEmbed } = require("discord.js"); +const { Message, MessageEmbed, ContextMenuInteraction } = require("discord.js"); const { EMBED_COLORS, EMOJIS } = require("@root/config"); const { getProfile } = require("@schemas/profile-schema"); const { getSettings } = require("@schemas/guild-schema"); const { resolveMember } = require("@utils/guildUtils"); -const { getUser } = require("@root/src/schemas/user-schema"); +const { getUser } = require("@schemas/user-schema"); module.exports = class Profile extends Command { constructor(client) { @@ -16,6 +16,10 @@ module.exports = class Profile extends Command { enabled: true, category: "INFORMATION", }, + contextMenu: { + enabled: true, + type: "USER", + }, }); } @@ -25,30 +29,41 @@ module.exports = class Profile extends Command { */ async messageRun(message, args) { const target = (await resolveMember(message, args[0])) || message.member; - const { user } = target; + const embed = await buildEmbed(message.guild, target); + message.channel.send({ embeds: [embed] }); + } - const settings = await getSettings(message.guild); - const profile = await getProfile(message.guildId, user.id); - const userData = await getUser(user.id); + /** + * @param {ContextMenuInteraction} interaction + */ + async contextRun(interaction) { + const target = (await interaction.guild.members.fetch(interaction.targetId)) || interaction.member; + const embed = await buildEmbed(interaction.guild, target); + interaction.followUp({ embeds: [embed] }); + } +}; - const embed = new MessageEmbed() - .setThumbnail(user.displayAvatarURL()) - .setColor(EMBED_COLORS.BOT_EMBED) - .addField("User Tag", user.tag, true) - .addField("ID", user.id, true) - .addField("Discord Registered", user.createdAt.toDateString(), false) - .addField("Cash", `${userData?.coins || 0} ${EMOJIS.CURRENCY}`, true) - .addField("Bank", `${userData?.bank || 0} ${EMOJIS.CURRENCY}`, true) - .addField("Net Worth", `${(userData?.coins || 0) + (userData?.bank || 0)}${EMOJIS.CURRENCY}`, true) - .addField("Messages*", `${settings.ranking.enabled ? profile?.messages + " " || 0 + " " : "Not Tracked"}`, true) - .addField("XP*", `${settings.ranking.enabled ? (profile?.xp || 0) + " " : "Not Tracked"}`, true) - .addField("Level*", `${settings.ranking.enabled ? (profile?.level || 0) + " " : "Not Tracked"}`, true) - .addField("Strikes*", profile?.strikes || 0 + " ", true) - .addField("Warnings*", profile?.warnings || 0 + " ", true) - .addField("Reputation", `${userData?.reputation?.received || 0}`, true) - .addField("Avatar-URL", user.displayAvatarURL({ format: "png" })) - .setFooter(`Requested By ${message.author.tag}\nFields marked (*) are guild specific`); +const buildEmbed = async (guild, target) => { + const { user } = target; + const settings = await getSettings(guild); + const profile = await getProfile(guild.id, user.id); + const userData = await getUser(user.id); - message.channel.send({ embeds: [embed] }); - } + return new MessageEmbed() + .setThumbnail(user.displayAvatarURL()) + .setColor(EMBED_COLORS.BOT_EMBED) + .addField("User Tag", user.tag, true) + .addField("ID", user.id, true) + .addField("Discord Registered", user.createdAt.toDateString(), false) + .addField("Cash", `${userData?.coins || 0} ${EMOJIS.CURRENCY}`, true) + .addField("Bank", `${userData?.bank || 0} ${EMOJIS.CURRENCY}`, true) + .addField("Net Worth", `${(userData?.coins || 0) + (userData?.bank || 0)}${EMOJIS.CURRENCY}`, true) + .addField("Messages*", `${settings.ranking.enabled ? (profile?.messages || 0) + " " : "Not Tracked"}`, true) + .addField("XP*", `${settings.ranking.enabled ? (profile?.xp || 0) + " " : "Not Tracked"}`, true) + .addField("Level*", `${settings.ranking.enabled ? (profile?.level || 0) + " " : "Not Tracked"}`, true) + .addField("Strikes*", (profile?.strikes || 0) + " ", true) + .addField("Warnings*", (profile?.warnings || 0) + " ", true) + .addField("Reputation", `${userData?.reputation?.received || 0}`, true) + .addField("Avatar-URL", user.displayAvatarURL({ format: "png" })) + .setFooter("Fields marked (*) are guild specific"); }; diff --git a/src/commands/info/userinfo.js b/src/commands/info/userinfo.js index 1519db781..1997039e3 100644 --- a/src/commands/info/userinfo.js +++ b/src/commands/info/userinfo.js @@ -33,7 +33,7 @@ module.exports = class UserInfo extends Command { ], }, contextMenu: { - enabled: true, + enabled: false, type: "USER", }, }); diff --git a/src/commands/moderation/warn.js b/src/commands/moderation/warn.js index d5d6b32b0..1fc328b67 100644 --- a/src/commands/moderation/warn.js +++ b/src/commands/moderation/warn.js @@ -1,7 +1,7 @@ const { resolveMember } = require("@root/src/utils/guildUtils"); const { Command } = require("@src/structures"); const { canInteract, addModAction } = require("@utils/modUtils"); -const { Message } = require("discord.js"); +const { Message, ContextMenuInteraction } = require("discord.js"); module.exports = class Warn extends Command { constructor(client) { @@ -15,6 +15,10 @@ module.exports = class Warn extends Command { category: "MODERATION", userPermissions: ["KICK_MEMBERS"], }, + contextMenu: { + enabled: true, + type: "USER", + }, }); } @@ -42,6 +46,19 @@ module.exports = class Warn extends Command { mentions.forEach(async (target) => await warn(message, target, reason)); } + + /** + * @param {ContextMenuInteraction} interaction + */ + async contextRun(interaction) { + const target = (await interaction.guild.members.fetch(interaction.targetId)) || interaction.member; + if (!canInteract(interaction.member, target, "warn")) { + interaction.followUp("Missing permission to warn this member"); + } + let status = await addModAction(interaction.member, target, "", "WARN"); + if (status) interaction.followUp(`${target.user.tag} is warned by ${interaction.member.user.tag}`); + else interaction.followUp(`Failed to warn ${target.user.tag}`); + } }; async function warn(message, target, reason) { From 5b6326239e0d4911d30059a2b36303981095c422 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 01:40:25 +0530 Subject: [PATCH 17/26] Update modUtils.js --- src/utils/modUtils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/modUtils.js b/src/utils/modUtils.js index 9e17741d8..ab60ef2d9 100644 --- a/src/utils/modUtils.js +++ b/src/utils/modUtils.js @@ -400,6 +400,8 @@ async function addModAction(issuer, target, reason, action) { await banTarget(issuer, target, reason); break; } + + return true; } module.exports = { From 5e8b3362006653916d61210b34ae39021ba5f672 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 19:33:15 +0530 Subject: [PATCH 18/26] flag translation - fix --- src/handlers/reaction-handler.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/handlers/reaction-handler.js b/src/handlers/reaction-handler.js index 54d89f539..a453d42fc 100644 --- a/src/handlers/reaction-handler.js +++ b/src/handlers/reaction-handler.js @@ -94,13 +94,17 @@ async function handleFlagReaction(emoji, message, user) { let src; let desc = ""; + let translated = 0; for (const tc of targetCodes) { const response = await translate(message.content, tc); - if (!response.success) continue; + if (!response) continue; src = response.inputLang; desc += `**${response.outputLang}:**\n${response.output}\n\n`; + translated += 1; } + if (translated === 0) return; + const head = `Original Message: [here](${message.url})\nSource Language: ${src}\n\n`; const embed = new MessageEmbed() .setColor(message.client.config.EMBED_COLORS.BOT_EMBED) From 46801db14e323708cdc3da6c280478a332a2186e Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 19:33:38 +0530 Subject: [PATCH 19/26] bot presence --- src/events/ready.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/events/ready.js b/src/events/ready.js index 818a2325e..cfac18698 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -9,6 +9,10 @@ const { getSettings } = require("@schemas/guild-schema"); module.exports = async (client) => { console.log(`Logged in as ${client.user.tag}! (${client.user.id})`); + // Update Bot Presence + updatePresence(client); + setInterval(() => updatePresence(client), 10 * 60 * 1000); + // Register Interactions if (client.config.INTERACTIONS.GLOBAL) await client.registerInteractions(); else await client.registerInteractions(client.config.INTERACTIONS.TEST_GUILD_ID); @@ -28,3 +32,21 @@ module.exports = async (client) => { if (settings.invite.tracking) inviteHandler.cacheGuildInvites(guild); }); }; + +/** + * @param {BotClient} client + */ +const updatePresence = (client) => { + const guilds = client.guilds.cache; + const members = guilds.map((g) => g.memberCount).reduce((partial_sum, a) => partial_sum + a, 0); + + client.user.setPresence({ + status: "online", + activities: [ + { + name: `${members} members in ${guilds.size} servers`, + type: "WATCHING", + }, + ], + }); +}; From 26cfbf0800a30684855b3db804d50834431f725d Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 19:34:20 +0530 Subject: [PATCH 20/26] daily coins --- config-sample.js | 3 +++ src/commands/economy/daily.js | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/config-sample.js b/config-sample.js index 76d3e23e5..888de7efd 100644 --- a/config-sample.js +++ b/config-sample.js @@ -28,6 +28,9 @@ module.exports = { IMAGE_API: "https://discord-js-image-manipulation.herokuapp.com", // Image commands won't work without this WEATHERSTACK_KEY: "", // https://weatherstack.com/ }, + MISCELLANEOUS: { + DAILY_COINS: 100, // coins to be received by daily command + }, /* Bot Embed Colors */ EMBED_COLORS: { BOT_EMBED: "#068ADD", diff --git a/src/commands/economy/daily.js b/src/commands/economy/daily.js index 4209ba4d6..c1a97c813 100644 --- a/src/commands/economy/daily.js +++ b/src/commands/economy/daily.js @@ -1,7 +1,7 @@ const { Command } = require("@src/structures"); const { MessageEmbed, Message } = require("discord.js"); const { getUser, updateDailyStreak } = require("@schemas/user-schema"); -const { EMBED_COLORS, EMOJIS } = require("@root/config.js"); +const { EMBED_COLORS, EMOJIS, MISCELLANEOUS } = require("@root/config.js"); const { diffHours, getRemainingTime } = require("@utils/miscUtils"); module.exports = class DailyCommand extends Command { @@ -39,13 +39,13 @@ module.exports = class DailyCommand extends Command { else streak = 0; } - const updated = await updateDailyStreak(member.id, 1000, streak); + const updated = await updateDailyStreak(member.id, MISCELLANEOUS.DAILY_COINS, streak); const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) .setAuthor(member.displayName, member.user.displayAvatarURL()) .setDescription( - `You got 1000${EMOJIS.CURRENCY} as your daily reward\n` + + `You got ${MISCELLANEOUS.DAILY_COINS}${EMOJIS.CURRENCY} as your daily reward\n` + `**Updated Balance:** ${updated?.coins || 0}${EMOJIS.CURRENCY}` ); From 5f47994c2f1c8a58eed5f0ebe26d549a94a9cc11 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 19:34:37 +0530 Subject: [PATCH 21/26] added alias --- src/commands/admin/xpsystem.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/admin/xpsystem.js b/src/commands/admin/xpsystem.js index 3085c9dbf..9b4c56e51 100644 --- a/src/commands/admin/xpsystem.js +++ b/src/commands/admin/xpsystem.js @@ -11,6 +11,7 @@ module.exports = class XPSystem extends Command { enabled: true, usage: "", minArgsCount: 1, + aliases: ["xptracking"], category: "ADMIN", userPermissions: ["ADMINISTRATOR"], }, From 5f0ced6383d9180e83e6c9ffadcd2db263cc7551 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 20:01:02 +0530 Subject: [PATCH 22/26] fixedsize-map dependency --- package.json | 1 + src/schemas/counter-schema.js | 4 +-- src/schemas/greeting-schema.js | 4 +-- src/schemas/guild-schema.js | 4 +-- src/structures/BotClient.js | 2 +- src/structures/cache.js | 53 ---------------------------------- src/structures/index.js | 4 +-- 7 files changed, 9 insertions(+), 63 deletions(-) delete mode 100644 src/structures/cache.js diff --git a/package.json b/package.json index ef95877a1..e2a16c994 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "express": "^4.17.1", "express-session": "^1.17.2", "ffmpeg-static": "^4.4.0", + "fixedsize-map": "^1.0.1", "fs": "0.0.1-security", "iso-639-1": "^2.1.9", "module-alias": "^2.2.2", diff --git a/src/schemas/counter-schema.js b/src/schemas/counter-schema.js index f2149f33e..fd1f590dd 100644 --- a/src/schemas/counter-schema.js +++ b/src/schemas/counter-schema.js @@ -1,8 +1,8 @@ const mongoose = require("mongoose"); const { CACHE_SIZE } = require("@root/config.js"); -const { FixedSizeCache } = require("@src/structures"); +const FixedSizeMap = require("fixedsize-map"); -const cache = new FixedSizeCache(CACHE_SIZE.GUILDS); +const cache = new FixedSizeMap(CACHE_SIZE.GUILDS); const Schema = mongoose.Schema({ _id: { diff --git a/src/schemas/greeting-schema.js b/src/schemas/greeting-schema.js index 4d61aa94b..d4580cf90 100644 --- a/src/schemas/greeting-schema.js +++ b/src/schemas/greeting-schema.js @@ -1,8 +1,8 @@ const mongoose = require("mongoose"); const { CACHE_SIZE } = require("@root/config.js"); -const { FixedSizeCache } = require("@src/structures"); +const FixedSizeMap = require("fixedsize-map"); -const cache = new FixedSizeCache(CACHE_SIZE.GUILDS); +const cache = new FixedSizeMap(CACHE_SIZE.GUILDS); const Schema = mongoose.Schema({ _id: { diff --git a/src/schemas/guild-schema.js b/src/schemas/guild-schema.js index 3dcd52d91..cccbf801a 100644 --- a/src/schemas/guild-schema.js +++ b/src/schemas/guild-schema.js @@ -1,8 +1,8 @@ const mongoose = require("mongoose"); const { CACHE_SIZE, PREFIX } = require("@root/config.js"); -const { FixedSizeCache } = require("@src/structures"); +const FixedSizeMap = require("fixedsize-map"); -const cache = new FixedSizeCache(CACHE_SIZE.GUILDS); +const cache = new FixedSizeMap(CACHE_SIZE.GUILDS); const Schema = mongoose.Schema({ _id: { diff --git a/src/structures/BotClient.js b/src/structures/BotClient.js index 9d6ebb7ae..aaff63f54 100644 --- a/src/structures/BotClient.js +++ b/src/structures/BotClient.js @@ -3,7 +3,7 @@ const path = require("path"); const fs = require("fs"); const Ascii = require("ascii-table"); const mongoose = require("mongoose"); -const Command = require("./command"); +const Command = require("./Command"); mongoose.plugin(require("mongoose-lean-defaults").default); const { Player } = require("discord-player"); diff --git a/src/structures/cache.js b/src/structures/cache.js deleted file mode 100644 index 2356c55a0..000000000 --- a/src/structures/cache.js +++ /dev/null @@ -1,53 +0,0 @@ -module.exports = class Cache { - constructor(size) { - this.map = new Map(); - if (size < 1) throw new Error("Cache size must at least be 1"); - this.keys = new Array(size); - this.currIndex = 0; - } - - /** - * Adds a key and pairs it with a value - * If this is already at maximum occupation, this will remove the oldest element. - * @param {any} key - * @param {any} value - */ - add(key, value) { - if (this.map.has(key)) { - this.map.set(key, value); - return; - } - - if (this.keys[this.currIndex] != null) { - this.map.delete(this.keys[this.currIndex]); - } - - this.keys[this.currIndex] = key; - this.currIndex = (this.currIndex + 1) % this.keys.length; - this.map.set(key, value); - } - - /** - * Checks if this cache contains a key - * @param {any} key - */ - contains(key) { - return this.map.has(key); - } - - /** - * Retrieves a value from this cache corresponding to the specified key - * @param {any} key - */ - get(key) { - return this.map.get(key); - } - - /** - * Removed the key value entry from this cache corresponding to the specified key - * @param {any} key - */ - remove(key) { - this.map.delete(key); - } -}; diff --git a/src/structures/index.js b/src/structures/index.js index e701689b6..ea6847018 100644 --- a/src/structures/index.js +++ b/src/structures/index.js @@ -1,9 +1,7 @@ -const FixedSizeCache = require("./cache"); -const Command = require("./command"); +const Command = require("./Command"); const BotClient = require("./BotClient"); module.exports = { BotClient, Command, - FixedSizeCache, }; From a08045920ccb0429bc3430a023cd8d2365ad3a54 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 20:19:55 +0530 Subject: [PATCH 23/26] Revert "fixedsize-map dependency" This reverts commit 5f0ced6383d9180e83e6c9ffadcd2db263cc7551. --- package.json | 1 - src/schemas/counter-schema.js | 4 +-- src/schemas/greeting-schema.js | 4 +-- src/schemas/guild-schema.js | 4 +-- src/structures/BotClient.js | 2 +- src/structures/cache.js | 53 ++++++++++++++++++++++++++++++++++ src/structures/index.js | 4 ++- 7 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 src/structures/cache.js diff --git a/package.json b/package.json index e2a16c994..ef95877a1 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "express": "^4.17.1", "express-session": "^1.17.2", "ffmpeg-static": "^4.4.0", - "fixedsize-map": "^1.0.1", "fs": "0.0.1-security", "iso-639-1": "^2.1.9", "module-alias": "^2.2.2", diff --git a/src/schemas/counter-schema.js b/src/schemas/counter-schema.js index fd1f590dd..f2149f33e 100644 --- a/src/schemas/counter-schema.js +++ b/src/schemas/counter-schema.js @@ -1,8 +1,8 @@ const mongoose = require("mongoose"); const { CACHE_SIZE } = require("@root/config.js"); -const FixedSizeMap = require("fixedsize-map"); +const { FixedSizeCache } = require("@src/structures"); -const cache = new FixedSizeMap(CACHE_SIZE.GUILDS); +const cache = new FixedSizeCache(CACHE_SIZE.GUILDS); const Schema = mongoose.Schema({ _id: { diff --git a/src/schemas/greeting-schema.js b/src/schemas/greeting-schema.js index d4580cf90..4d61aa94b 100644 --- a/src/schemas/greeting-schema.js +++ b/src/schemas/greeting-schema.js @@ -1,8 +1,8 @@ const mongoose = require("mongoose"); const { CACHE_SIZE } = require("@root/config.js"); -const FixedSizeMap = require("fixedsize-map"); +const { FixedSizeCache } = require("@src/structures"); -const cache = new FixedSizeMap(CACHE_SIZE.GUILDS); +const cache = new FixedSizeCache(CACHE_SIZE.GUILDS); const Schema = mongoose.Schema({ _id: { diff --git a/src/schemas/guild-schema.js b/src/schemas/guild-schema.js index cccbf801a..3dcd52d91 100644 --- a/src/schemas/guild-schema.js +++ b/src/schemas/guild-schema.js @@ -1,8 +1,8 @@ const mongoose = require("mongoose"); const { CACHE_SIZE, PREFIX } = require("@root/config.js"); -const FixedSizeMap = require("fixedsize-map"); +const { FixedSizeCache } = require("@src/structures"); -const cache = new FixedSizeMap(CACHE_SIZE.GUILDS); +const cache = new FixedSizeCache(CACHE_SIZE.GUILDS); const Schema = mongoose.Schema({ _id: { diff --git a/src/structures/BotClient.js b/src/structures/BotClient.js index aaff63f54..9d6ebb7ae 100644 --- a/src/structures/BotClient.js +++ b/src/structures/BotClient.js @@ -3,7 +3,7 @@ const path = require("path"); const fs = require("fs"); const Ascii = require("ascii-table"); const mongoose = require("mongoose"); -const Command = require("./Command"); +const Command = require("./command"); mongoose.plugin(require("mongoose-lean-defaults").default); const { Player } = require("discord-player"); diff --git a/src/structures/cache.js b/src/structures/cache.js new file mode 100644 index 000000000..2356c55a0 --- /dev/null +++ b/src/structures/cache.js @@ -0,0 +1,53 @@ +module.exports = class Cache { + constructor(size) { + this.map = new Map(); + if (size < 1) throw new Error("Cache size must at least be 1"); + this.keys = new Array(size); + this.currIndex = 0; + } + + /** + * Adds a key and pairs it with a value + * If this is already at maximum occupation, this will remove the oldest element. + * @param {any} key + * @param {any} value + */ + add(key, value) { + if (this.map.has(key)) { + this.map.set(key, value); + return; + } + + if (this.keys[this.currIndex] != null) { + this.map.delete(this.keys[this.currIndex]); + } + + this.keys[this.currIndex] = key; + this.currIndex = (this.currIndex + 1) % this.keys.length; + this.map.set(key, value); + } + + /** + * Checks if this cache contains a key + * @param {any} key + */ + contains(key) { + return this.map.has(key); + } + + /** + * Retrieves a value from this cache corresponding to the specified key + * @param {any} key + */ + get(key) { + return this.map.get(key); + } + + /** + * Removed the key value entry from this cache corresponding to the specified key + * @param {any} key + */ + remove(key) { + this.map.delete(key); + } +}; diff --git a/src/structures/index.js b/src/structures/index.js index ea6847018..e701689b6 100644 --- a/src/structures/index.js +++ b/src/structures/index.js @@ -1,7 +1,9 @@ -const Command = require("./Command"); +const FixedSizeCache = require("./cache"); +const Command = require("./command"); const BotClient = require("./BotClient"); module.exports = { BotClient, Command, + FixedSizeCache, }; From 4c7876174bd2d302a2f9f2bbd7ad061400a28be9 Mon Sep 17 00:00:00 2001 From: Sai Teja Madha <42540377+saiteja-madha@users.noreply.github.com> Date: Sun, 19 Sep 2021 20:20:34 +0530 Subject: [PATCH 24/26] xp cooldown bug --- src/events/message/messageCreate.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/events/message/messageCreate.js b/src/events/message/messageCreate.js index 19e327754..222c13a56 100644 --- a/src/events/message/messageCreate.js +++ b/src/events/message/messageCreate.js @@ -41,11 +41,13 @@ module.exports = async (client, message) => { * @param {Message} message */ async function xpHandler(message) { - const key = `${message.guild.id}|${message.member.id}`; + const key = `${message.guildId}|${message.member.id}`; + + // Ignore possible bot commands // Cooldown check to prevent Message Spamming if (message.client.xpCooldownCache.has(key)) { - const difference = Date.now() - message.client.xpCooldownCache.get(key) / 0.001; + const difference = Date.now() - message.client.xpCooldownCache.get(key) * 0.001; if (difference < message.client.config.XP_SYSTEM.COOLDOWN) { return incrementMessages(message.guildId, message.member.id); // if on cooldown only increment messages } From 1ff8e2e7b2400f27e706a65630c5cec848860986 Mon Sep 17 00:00:00 2001 From: Sparker <60593579+Sparker-99@users.noreply.github.com> Date: Sun, 19 Sep 2021 20:38:49 +0530 Subject: [PATCH 25/26] Paste command uses sourcebin --- src/commands/utility/paste.js | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/commands/utility/paste.js diff --git a/src/commands/utility/paste.js b/src/commands/utility/paste.js new file mode 100644 index 000000000..49ffffd79 --- /dev/null +++ b/src/commands/utility/paste.js @@ -0,0 +1,46 @@ +const fetch = require('node-fetch'); +const { Command } = require("@src/structures"); +const { MessageEmbed } = require("discord.js"); + +module.exports = class CatCommand extends Command { + constructor(client) { + super(client, { + name: "paste", + description: "Paste something in sourceb.in", + cooldown: 5, + command: { + enabled: true, + category: "UTILITY", + botPermissions: ["EMBED_LINKS"], + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + + async messageRun(message, args) { + + if (!args || args.length < 3) return message.channel.send("You need to add title, description and content"); + + + const { key } = await fetch("https://sourceb.in/api/bins", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ "title": args[0], "description": args[1], "files": [{ "content": args.splice(2).join(' ') }] }) + }).then(res => res.json()); + + + if (!key) return message.channel.send("āŒ Something went wrong"); + + const embed = new MessageEmbed() + .setTitle("Paste links") + .setDescription("šŸ”ø Normal: https://sourceb.in/" + key + "\nšŸ”¹ Raw: https://cdn.sourceb.in/bins/" + key + "/0") + message.channel.send({ embeds: [embed] }); + } +}; From 2ad8b9d19934ee2cdda1e314c3ef588d3fe413e1 Mon Sep 17 00:00:00 2001 From: Sparker <60593579+Sparker-99@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:43:09 +0530 Subject: [PATCH 26/26] Queue and np --- src/commands/music/np.js | 35 +++++++++++++++++++++++++++++++ src/commands/music/queue.js | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 src/commands/music/np.js create mode 100644 src/commands/music/queue.js diff --git a/src/commands/music/np.js b/src/commands/music/np.js new file mode 100644 index 000000000..259509fe2 --- /dev/null +++ b/src/commands/music/np.js @@ -0,0 +1,35 @@ +const { Command } = require("@src/structures"); +const { MessageEmbed, Message } = require("discord.js"); + +module.exports = class Skip extends Command { + constructor(client) { + super(client, { + name: "np", + description: "SHow what is playing currently", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + + const progress = queue.createProgressBar(); + const perc = queue.getPlayerTimestamp(); + + const embed = new MessageEmbed() + .setTitle("Currently Playing") + .setDescription(`šŸŽ¶ | **${queue.current.title}**! (\`${perc.progress == 'Infinity' ? 'Live' : perc.progress + '%'}\`)\n\n${progress.replace(/ 0:00/g, ' ā—‰ LIVE')}`) + return message.channel.send({ embeds: [embed] }); + } +}; diff --git a/src/commands/music/queue.js b/src/commands/music/queue.js new file mode 100644 index 000000000..1dc848fbb --- /dev/null +++ b/src/commands/music/queue.js @@ -0,0 +1,42 @@ +const { Command } = require("@src/structures"); +const { MessageEmbed, Message } = require("discord.js"); + +module.exports = class Skip extends Command { + constructor(client) { + super(client, { + name: "queue", + description: "Shows the current music queue", + command: { + enabled: true, + category: "MUSIC", + }, + slashCommand: { + enabled: false, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const queue = message.client.player.getQueue(message.guildId); + if (!queue || !queue.playing) return message.channel.send("No music is being played!"); + if (!args[0] || isNaN(args[0])) args[0] = 1; + + const pageStart = 10 * (args[0] - 1); + const pageEnd = pageStart + 10; + const currentTrack = queue.current; + + const tracks = queue.tracks.slice(pageStart, pageEnd).map((m, i) => { + return `${i + pageStart + 1}. **${m.title}** ([link](${m.url}))`; + }); + + const embed = new MessageEmbed() + .setTitle(`Music Queue`) + .setDescription(`${tracks.join('\n')}${queue.tracks.length > pageEnd ? `\n...${queue.tracks.length - pageEnd} more track(s)` : ''}`) + .addField('Now Playing', `šŸŽ¶ | **${currentTrack.title}** ([link](${currentTrack.url}))`); + return message.channel.send({ embeds: [embed] }); + } +};