├── src ├── data │ ├── categories.json │ ├── categoryDetails.json │ └── filters.json ├── events │ ├── client │ │ ├── error.js │ │ ├── warn.js │ │ └── ready.js │ ├── player │ │ ├── player.tracksAdd.js │ │ ├── player.queueEnd.js │ │ ├── player.channelEmpty.js │ │ ├── player.botDisconnect.js │ │ ├── player.error.js │ │ ├── player.connectionError.js │ │ ├── player.connectionCreate.js │ │ ├── player.trackAdd.js │ │ └── player.trackStart.js │ └── interactions │ │ └── interactionCreate.js ├── commands │ ├── botowner │ │ ├── shutdown.js │ │ └── eval.js │ ├── utility │ │ ├── uptime.js │ │ ├── ping.js │ │ ├── support.js │ │ ├── invite.js │ │ ├── botinfo.js │ │ └── help.js │ ├── music │ │ ├── stop.js │ │ ├── clear.js │ │ ├── pause.js │ │ ├── resume.js │ │ ├── shuffle.js │ │ ├── skip.js │ │ ├── jump.js │ │ ├── remove.js │ │ ├── nowplaying.js │ │ ├── volume.js │ │ ├── seek.js │ │ ├── lyrics.js │ │ ├── queue.js │ │ ├── filter.js │ │ ├── watchtogether.js │ │ ├── loop.js │ │ └── play.js │ └── filters │ │ ├── 8d.js │ │ ├── mono.js │ │ ├── treble.js │ │ ├── karaoke.js │ │ ├── reverse.js │ │ ├── vibrato.js │ │ ├── pulsator.js │ │ ├── subboost.js │ │ ├── nightcore.js │ │ ├── vaporwave.js │ │ ├── surrounding.js │ │ └── bassboost.js ├── modules │ ├── checkValid.js │ ├── Logger.js │ ├── Embeds.js │ └── Util.js ├── index.js └── handler │ ├── EventHandler.js │ └── CommandHandler.js ├── config.json ├── README.md ├── package.json └── LICENSE /src/data/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | "music", 3 | "filters", 4 | "utility", 5 | "botowner" 6 | ] -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "botToken": "", 3 | "supportServer": "https://discord.gg/", 4 | "owners": [""] 5 | } -------------------------------------------------------------------------------- /src/events/client/error.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "error", 3 | execute(bot, error) { 4 | return bot.utils.sendErrorLog(bot, error, "error"); 5 | } 6 | }; -------------------------------------------------------------------------------- /src/events/client/warn.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "warn", 3 | execute(bot, error) { 4 | return bot.utils.sendErrorLog(bot, error, "warning"); 5 | } 6 | }; -------------------------------------------------------------------------------- /src/data/categoryDetails.json: -------------------------------------------------------------------------------- 1 | { 2 | "music": "Music Commands", 3 | "filters": "Music Filters", 4 | "utility": "Utility Commands", 5 | "botowner": "Owner Commands" 6 | } -------------------------------------------------------------------------------- /src/events/player/player.tracksAdd.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "tracksAdd", 3 | execute(bot, queue, tracks) { 4 | return bot.say.queueMessage(queue, `Enqueued ${tracks.length} tracks.`); 5 | } 6 | }; -------------------------------------------------------------------------------- /src/events/player/player.queueEnd.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "queueEnd", 3 | execute(bot, queue) { 4 | return bot.say.queueMessage(queue, "No more songs to play. Left the voice channel."); 5 | } 6 | }; -------------------------------------------------------------------------------- /src/events/player/player.channelEmpty.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "channelEmpty", 3 | execute(bot, queue) { 4 | return bot.say.queueMessage(queue, "I have left the voice channel as I was left alone."); 5 | } 6 | }; -------------------------------------------------------------------------------- /src/data/filters.json: -------------------------------------------------------------------------------- 1 | [ 2 | "8D", 3 | "bassboost", 4 | "karaoke", 5 | "mono", 6 | "nightcore", 7 | "pulsator", 8 | "reverse", 9 | "subboost", 10 | "surrounding", 11 | "treble", 12 | "vaporwave", 13 | "vibrato" 14 | ] -------------------------------------------------------------------------------- /src/events/player/player.botDisconnect.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "botDisconnect", 3 | execute(bot, queue) { 4 | return bot.say.queueMessage(queue, "Music has been stopped as I was disconnected from the voice channel.", "RED"); 5 | } 6 | }; -------------------------------------------------------------------------------- /src/events/player/player.error.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "error", 3 | execute(bot, queue, error) { 4 | bot.utils.sendErrorLog(bot, error, "error"); 5 | 6 | return bot.say.queueMessage(queue, `An error occurred while playing.\nReason: ${error.message}`, "RED"); 7 | } 8 | }; -------------------------------------------------------------------------------- /src/events/player/player.connectionError.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "connectionError", 3 | execute(bot, queue, error) { 4 | bot.utils.sendErrorLog(bot, error, "error"); 5 | 6 | return bot.say.queueMessage(queue, `An error occurred while playing.\nReason: ${error.message}`, "RED"); 7 | } 8 | }; -------------------------------------------------------------------------------- /src/commands/botowner/shutdown.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "shutdown", 3 | description: "Shuts the bot down", 4 | category: "botowner", 5 | ownerOnly: true, 6 | async execute(bot, interaction) { 7 | await bot.say.successMessage(interaction, "Shutting the bot down.....", true); 8 | 9 | process.exit(1); 10 | } 11 | }; -------------------------------------------------------------------------------- /src/events/player/player.connectionCreate.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "connectionCreate", 3 | execute(bot, queue, connection) { 4 | const embed = bot.say.baseEmbed(queue) 5 | .setAuthor(`${bot.user.username}`, bot.user.displayAvatarURL()) 6 | .setDescription(`👍 Joined ${connection.channel.toString()} and 📄 bound to ${queue.metadata.channel.toString()}`); 7 | 8 | return queue.metadata.reply({ embeds: [embed] }).catch(console.error); 9 | } 10 | }; -------------------------------------------------------------------------------- /src/commands/utility/uptime.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "uptime", 3 | description: "Returns the uptime of the bot", 4 | category: "utility", 5 | execute(bot, interaction) { 6 | const uptime = bot.utils.formatDuration(bot.uptime); 7 | 8 | const embed = bot.say.baseEmbed(interaction) 9 | .setAuthor("Uptime", bot.user.displayAvatarURL()) 10 | .setDescription(`${uptime}`); 11 | 12 | return interaction.reply({ ephemeral: true, embeds: [embed] }); 13 | } 14 | }; -------------------------------------------------------------------------------- /src/events/player/player.trackAdd.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "trackAdd", 3 | execute(bot, queue, track) { 4 | if (!queue.playing || queue.tracks.length <= 0) return; 5 | 6 | const embed = bot.say.baseEmbed(queue) 7 | .setTitle(`Track queued - Position ${queue.tracks.indexOf(track) +1}`) 8 | .setDescription(`[${track.title}](${track.url}) ~ [${track.requestedBy.toString()}]`); 9 | 10 | return queue.metadata.reply({ embeds: [embed] }).catch(console.error); 11 | } 12 | }; -------------------------------------------------------------------------------- /src/events/player/player.trackStart.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed } = require("discord.js"); 2 | 3 | module.exports = { 4 | name: "trackStart", 5 | execute(bot, queue, track) { 6 | if (!bot.utils.havePermissions(queue.metadata.channel)) return; 7 | 8 | const embed = bot.say.baseEmbed(queue) 9 | .setTitle("Now playing") 10 | .setDescription(`[${track.title}](${track.url}) ~ [${track.requestedBy.toString()}]`); 11 | 12 | return queue.metadata.channel.send({ embeds: [embed] }); 13 | } 14 | }; -------------------------------------------------------------------------------- /src/commands/music/stop.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "stop", 3 | description: "Stops the playback.", 4 | category: "music", 5 | execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | queue.stop(); 14 | 15 | return bot.say.successMessage(interaction, "Stopped the music."); 16 | } 17 | }; -------------------------------------------------------------------------------- /src/commands/music/clear.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "clear", 3 | description: "Clears the current queue.", 4 | category: "music", 5 | execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | if (queue.tracks.length < 1) 14 | return bot.say.warnMessage(interaction, "There is currently no song in the queue."); 15 | 16 | queue.clear(); 17 | 18 | return bot.say.successMessage(interaction, "Cleared the queue."); 19 | } 20 | }; -------------------------------------------------------------------------------- /src/commands/filters/8d.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "8d", 3 | description: "Toggles the 8D filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | "8D": !queue.getFiltersEnabled().includes("8D") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("8D") ? "Applied" : "Removed"} the 8D filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/music/pause.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "pause", 3 | description: "Pauses the current playing song.", 4 | category: "music", 5 | execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | if (queue.connection.paused) 14 | return bot.say.warnMessage(interaction, "The song is already paused."); 15 | 16 | queue.setPaused(true); 17 | 18 | return bot.say.successMessage(interaction, "Paused the current song."); 19 | } 20 | }; -------------------------------------------------------------------------------- /src/commands/music/resume.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "resume", 3 | description: "Resumes the current paused song.", 4 | category: "music", 5 | execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | if (!queue.connection.paused) 14 | return bot.say.warnMessage(interaction, "The song is not paused."); 15 | 16 | queue.setPaused(false); 17 | 18 | return bot.say.successMessage(interaction, "Resumed the corrent song."); 19 | } 20 | }; -------------------------------------------------------------------------------- /src/commands/music/shuffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "shuffle", 3 | description: "Shuffles the queue.", 4 | category: "music", 5 | execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | if (queue.tracks.length < 3) 14 | return bot.say.warnMessage(interaction, "Need at least \`3\` songs in the queue to shuffle."); 15 | 16 | queue.shuffle(); 17 | 18 | return bot.say.successMessage(interaction, "Shuffled the queue."); 19 | } 20 | }; -------------------------------------------------------------------------------- /src/commands/filters/mono.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "mono", 3 | description: "Toggles the mono filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | mono: !queue.getFiltersEnabled().includes("mono") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("mono") ? "Applied" : "Removed"} the mono filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/music/skip.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "skip", 3 | description: "Skips the current song", 4 | category: "music", 5 | execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | if (queue.tracks.length < 1 && queue.repeatMode !== 3) 14 | return bot.say.warnMessage(interaction, "No more songs in the queue to skip."); 15 | 16 | queue.skip(); 17 | 18 | return bot.say.successMessage(interaction, "Skipped to the next song."); 19 | } 20 | }; -------------------------------------------------------------------------------- /src/commands/filters/treble.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "treble", 3 | description: "Toggles the treble filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | treble: !queue.getFiltersEnabled().includes("treble") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("treble") ? "Applied" : "Removed"} the treble filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/utility/ping.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "ping", 3 | description: "Ping? Pong!", 4 | category: "utility", 5 | async execute(bot, interaction) { 6 | try{ 7 | 8 | const embed1 = bot.say.baseEmbed(interaction) 9 | .setDescription("Pinging..."); 10 | 11 | await interaction.reply({ ephemeral: true, embeds: [embed1] }).catch(console.error); 12 | 13 | const embed2 = bot.say.baseEmbed(interaction) 14 | .setTitle("🏓 Pong") 15 | .setDescription(`💓: ${Math.round(bot.ws.ping)} ms 16 | ⏱️: ${Date.now() - interaction.createdTimestamp} ms`); 17 | 18 | return interaction.editReply({ embeds: [embed2] }); 19 | } catch (e) { 20 | console.log(e) 21 | } 22 | } 23 | }; -------------------------------------------------------------------------------- /src/commands/filters/karaoke.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "karaoke", 3 | description: "Toggles the karaoke filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | kakaoke: !queue.getFiltersEnabled().includes("karaoke") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("karaoke") ? "Applied" : "Removed"} the kakaoke filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/filters/reverse.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "reverse", 3 | description: "Toggles the reverse filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | reverse: !queue.getFiltersEnabled().includes("reverse") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("reverse") ? "Applied" : "Removed"} the reverse filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/filters/vibrato.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "vibrato", 3 | description: "Toggles the vibrato filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | vibrato: !queue.getFiltersEnabled().includes("vibrato") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("vibrato") ? "Applied" : "Removed"} the vibrato filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/filters/pulsator.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "pulsator", 3 | description: "Toggles the pulsator filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | pulsator: !queue.getFiltersEnabled().includes("pulsator") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("pulsator") ? "Applied" : "Removed"} the pulsator filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/filters/subboost.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "subboost", 3 | description: "Toggles the subboost filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | subboost: !queue.getFiltersEnabled().includes("subboost") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("subboost") ? "Applied" : "Removed"} the subboost filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/filters/nightcore.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "nightcore", 3 | description: "Toggles the nightcore filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | nightcore: !queue.getFiltersEnabled().includes("nightcore") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("nightcore") ? "Applied" : "Removed"} the nightcore filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/filters/vaporwave.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "vaporwave", 3 | description: "Toggles the vaporwave filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | vaporwave: !queue.getFiltersEnabled().includes("vaporwave") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("vaporwave") ? "Applied" : "Removed"} the vaporwave filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/filters/surrounding.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "surrounding", 3 | description: "Toggles the surrounding filter.", 4 | category: "filters", 5 | async execute(bot, interaction) { 6 | const queue = bot.player.getQueue(interaction.guild.id); 7 | 8 | if (!queue || !queue.playing) 9 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 10 | 11 | if (!bot.utils.modifyQueue(interaction)) return; 12 | 13 | await queue.setFilters({ 14 | surrounding: !queue.getFiltersEnabled().includes("surrounding") 15 | }); 16 | 17 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes("surrounding") ? "Applied" : "Removed"} the surrounding filter.`); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/commands/utility/support.js: -------------------------------------------------------------------------------- 1 | const { supportServer } = require("../../../config.json"); 2 | const { MessageActionRow, MessageButton } = require("discord.js"); 3 | 4 | module.exports = { 5 | name: "support", 6 | description: "Join the discord support server", 7 | category: "utility", 8 | execute(bot, interaction) { 9 | const embed = bot.say.baseEmbed(interaction) 10 | .setDescription(`[Click to join the support server.](${supportServer})`); 11 | 12 | const row = new MessageActionRow().addComponents([ 13 | new MessageButton() 14 | .setLabel("Server Link") 15 | .setStyle("LINK") 16 | .setURL(`${supportServer}`) 17 | ]); 18 | 19 | 20 | return interaction.reply({ ephemeral: true, embeds: [embed], components: [row] }); 21 | } 22 | }; -------------------------------------------------------------------------------- /src/modules/checkValid.js: -------------------------------------------------------------------------------- 1 | const config = require("../../config.json"); 2 | const logger = require("../modules/Logger"); 3 | 4 | function checkValid() { 5 | const nodeV = parseFloat(process.versions.node); 6 | const npmV = parseFloat(process.versions.node); 7 | 8 | if (nodeV < 16) { 9 | throw Error("[ERROR]: This bot requires version 16.6 of nodejs! Please upgrade to version 16.6 or more."); 10 | } 11 | 12 | if (npmV < 7) { 13 | throw Error("[ERROR]: Please upgrade npm to version 7 or more."); 14 | } 15 | 16 | if (!config.botToken || config.botToken === "") { 17 | throw Error("[ERROR]: Bot Token must be required."); 18 | } 19 | 20 | if (!config.supportServer || config.supportServer === "") { 21 | logger.warn("config", "Support Server is required for discord support."); 22 | } 23 | 24 | if (!config.owners[0]) { 25 | logger.warn("config", "ownerId is required for bot-owner only commands."); 26 | } 27 | } 28 | 29 | checkValid(); -------------------------------------------------------------------------------- /src/commands/utility/invite.js: -------------------------------------------------------------------------------- 1 | const { MessageActionRow, MessageButton } = require("discord.js"); 2 | 3 | module.exports = { 4 | name: "invite", 5 | description: "Invite the bot to your server", 6 | category: "utility", 7 | execute(bot, interaction) { 8 | const embed = bot.say.baseEmbed(interaction) 9 | .setAuthor({name: `${bot.user.tag}`}) 10 | .setDescription(`[Click to invite me to your server.](https://discord.com/api/oauth2/authorize?client_id=${bot.user.id}&permissions=8&scope=applications.commands%20bot)`) 11 | .setTimestamp(); 12 | const row = new MessageActionRow().addComponents([ 13 | new MessageButton() 14 | .setLabel("Invite Link") 15 | .setStyle("LINK") 16 | .setURL(`https://discord.com/api/oauth2/authorize?client_id=${bot.user.id}&permissions=8&scope=applications.commands%20bot`) 17 | ]); 18 | 19 | 20 | return interaction.reply({ ephemeral: true, embeds: [embed], components: [row] }); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/events/client/ready.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | module.exports = { 4 | name: "ready", 5 | once: true, 6 | async execute(bot) { 7 | //initializing commands 8 | require("../../handler/CommandHandler")(bot); 9 | 10 | const formatNum = bot.utils.formatNumber; 11 | 12 | const serverCount = formatNum(bot.guilds.cache.size); 13 | const channelCount = formatNum(bot.channels.cache.size); 14 | const userCount = formatNum( 15 | bot.guilds.cache.reduce((a, g) => a + g.memberCount, 0), 16 | ); 17 | 18 | const statuses = [ 19 | { "name": `${serverCount} servers & ${userCount} users and ${channelCount} channels`, "type": "WATCHING" }, 20 | { "name": "\/play", "type": "LISTENING" }, 21 | { "name": "\/help", "type": "PLAYING" } 22 | ]; 23 | 24 | setInterval(() => { 25 | const status = statuses[Math.floor(Math.random() * statuses.length)]; 26 | bot.user.setActivity(status.name, { type: status.type }); 27 | }, 60000); 28 | } 29 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require("./modules/checkValid"); 2 | 3 | const { Collection, Client, Intents } = require("discord.js"); 4 | const { Player } = require("discord-player"); 5 | 6 | const { botToken } = require("../config.json"); 7 | const Logger = require("./modules/Logger"); 8 | const Embeds = require("./modules/Embeds"); 9 | const Util = require("./modules/Util"); 10 | 11 | const bot = new Client({ 12 | intents: [ 13 | Intents.FLAGS.GUILDS, 14 | Intents.FLAGS.GUILD_MESSAGES, 15 | Intents.FLAGS.GUILD_VOICE_STATES 16 | ], 17 | allowedMentions: { parse: ["roles", "users"], repliedUser: false } 18 | }); 19 | 20 | bot.commands = new Collection(); 21 | 22 | bot.logger = Logger; 23 | bot.utils = Util; 24 | bot.say = Embeds; 25 | 26 | bot.player = new Player(bot, { 27 | leaveOnEnd: true, 28 | leaveOnStop: true, 29 | leaveOnEmpty: true, 30 | leaveOnEmptyCooldown: 60000, 31 | autoSelfDeaf: true, 32 | initialVolume: 100 33 | }); 34 | 35 | require("./handler/EventHandler")(bot); 36 | 37 | bot.login(botToken); 38 | -------------------------------------------------------------------------------- /src/events/interactions/interactionCreate.js: -------------------------------------------------------------------------------- 1 | const { owners } = require("../../../config.json"); 2 | 3 | module.exports = { 4 | name: "interactionCreate", 5 | async execute(bot, interaction) { 6 | if (!interaction.isCommand()) return; 7 | if (!interaction.inGuild()) return; 8 | 9 | await bot.application?.commands.fetch(interaction.commandId).catch((e) => console.log(e)); 10 | 11 | try { 12 | const command = bot.commands.get(interaction.command?.name ?? "") 13 | 14 | if (!command) return; 15 | if (!interaction.commandId) return; 16 | 17 | if ((command.category === "botowner" || command.ownerOnly === true) && !owners.includes(interaction.user.id)) 18 | return bot.say.errorMessage(interaction, "This command can only be used by the bot owners."); 19 | 20 | await command.execute(bot, interaction); 21 | } catch (err) { 22 | bot.utils.sendErrorLog(bot, err, "error"); 23 | if (interaction.replied) return; 24 | return bot.say.errorMessage(interaction, "Something went wrong. Sorry for the inconveniences."); 25 | } 26 | } 27 | }; -------------------------------------------------------------------------------- /src/handler/EventHandler.js: -------------------------------------------------------------------------------- 1 | const glob = require("glob"); 2 | 3 | module.exports = function loadEvents(bot) { 4 | const eventFiles = glob.sync("./src/events/**/*.js"); 5 | bot.logger.info("EVENTS", `Loading ${eventFiles.length} events...`); 6 | 7 | eventFiles.forEach((file) => { 8 | const event = require(`../../${file}`); 9 | 10 | let type = "bot"; 11 | if (file.includes("player.")) type = "player"; 12 | 13 | if (!event.execute) { 14 | throw new TypeError(`[ERROR] execute function is required for events! (${file})`); 15 | } 16 | 17 | if (!event.name) { 18 | throw new TypeError(`[ERROR] name is required for events! (${file})`); 19 | } 20 | 21 | 22 | if (type === "player") { 23 | bot.player.on(event.name, event.execute.bind(null, bot)); 24 | } else if (event.once) { 25 | bot.once(event.name, event.execute.bind(null, bot)); 26 | } else { 27 | bot.on(event.name, event.execute.bind(null, bot)); 28 | } 29 | 30 | delete require.cache[require.resolve(`../../${file}`)]; 31 | 32 | // debug 33 | bot.logger.debug(`EVT DEBUG`, `Loaded ${event.name}.js`); 34 | 35 | }); 36 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
",
10 | options: [{
11 | name: "code",
12 | description: "The code you want to execute",
13 | type: "STRING",
14 | required: true
15 | }],
16 | async execute(bot, interaction) {
17 | await interaction.deferReply({ ephemeral: true });
18 |
19 | const code = interaction.options.getString("code", true);
20 |
21 | try {
22 | let evaled = await eval(code);
23 |
24 | const type = typeof evaled;
25 | evaled = inspect(evaled, {
26 | depth: 0,
27 | maxArrayLength: null
28 | });
29 |
30 | if (type === "object") evaled = JSON.stringify(evaled);
31 |
32 | const embed1 = bot.say.baseEmbed(interaction)
33 | .setTitle("Eval Command")
34 | .setDescription(`Eval Type: \`${type}\``);
35 |
36 | const embed2 = bot.say.baseEmbed(interaction)
37 | .setTitle("Eval Input")
38 | .setDescription(`${codeBlock("js", code)}`);
39 |
40 | const embed3 = bot.say.baseEmbed(interaction)
41 | .setTitle("Eval Output")
42 | .setDescription(`${codeBlock("js", evaled)}`);
43 |
44 | return interaction.editReply({ embeds: [embed1, embed2, embed3] });
45 | } catch (error) {
46 | const err = error instanceof Error ? error.message : "An unknown error occurred";
47 |
48 | const wrEmbed = bot.say.baseEmbed(interaction)
49 | .setTitle("Something went wrong")
50 | .setDescription(codeBlock(clean(err)));
51 |
52 | return interaction.editReply({ embeds: [wrEmbed] });
53 | }
54 | }
55 | };
56 |
57 | function clean(text) {
58 | if (typeof(text) === "string")
59 | return text.replace(/`/g, "`" + String.fromCharCode(8203)).replace(/@/g, "@" + String.fromCharCode(8203));
60 | else
61 | return text;
62 | }
--------------------------------------------------------------------------------
/src/commands/utility/botinfo.js:
--------------------------------------------------------------------------------
1 | const config = require("../../../config.json");
2 | const { version: djsVersion, MessageActionRow, MessageButton } = require("discord.js");
3 |
4 | module.exports = {
5 | name: "botinfo",
6 | description: "Shows info about the bot",
7 | category: "utility",
8 | async execute(bot, interaction) {
9 | const util = bot.utils;
10 | const uptime = util.formatDuration(bot.uptime);
11 | const createdAt = ``
12 | const users = bot.guilds.cache.reduce((a, g) => a + g.memberCount, 0);
13 |
14 | const embed = bot.say.baseEmbed(interaction)
15 | .setAuthor(`${bot.user.username}’s Information`, bot.user.displayAvatarURL())
16 | .addField("General Info",
17 | `**Bot Id:** ${bot.user.id}
18 | **Bot Tag:** ${bot.user.tag}
19 | **Created At :** ${createdAt}
20 | **Developer:** [•OofyOofOof•#2018](https:\/\/youtube.com\/c\/BlackKnight683)
21 | **Github Repo:** __[BlackKnight683/BrokenDisc](https:\/\/github.com\/BlackKnight683\/BrokenDisc)__
22 | **Prefix:** \/`
23 | )
24 | .addField("Bot Stats",
25 | `**Users:** ${util.formatNumber(users)}
26 | **Servers:** ${util.formatNumber(bot.guilds.cache.size)}
27 | **Channels:** ${util.formatNumber(bot.channels.cache.size)}
28 | **Command Count:** ${util.formatNumber(bot.commands.size)}`
29 | )
30 | .addField("System Info",
31 | `**RAM Usage:** ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB
32 | **Bot Uptime:** ${uptime}
33 | **Node Version:** ${process.version}
34 | **Platform:** ${util.toCapitalize(process.platform)}`
35 | );
36 |
37 | const button1 = new MessageButton()
38 | .setLabel("Support")
39 | .setStyle("LINK")
40 | .setURL(`${config.supportServer}`);
41 |
42 | const button2 = new MessageButton()
43 | .setLabel("Invite")
44 | .setStyle("LINK")
45 | .setURL(`https://discord.com/api/oauth2/authorize?client_id=${bot.user.id}&permissions=8&scope=applications.commands%20bot`);
46 |
47 | const row = new MessageActionRow().addComponents([button1, button2]);
48 |
49 |
50 | return interaction.reply({ ephemeral: true, embeds: [embed], components: [row] });
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/src/commands/filters/bassboost.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: "bassboost",
3 | description: "Sets the bassboost filter",
4 | category: "filters",
5 | usage: "",
6 | options: [{
7 | name: "level",
8 | description: "Choose the bassboost level",
9 | type: "STRING",
10 | required: true,
11 | choices: [
12 | {
13 | name: "Low",
14 | value: "low"
15 | },
16 | {
17 | name: "Medium",
18 | value: "medium"
19 | },
20 | {
21 | name: "High",
22 | value: "high"
23 | },
24 | {
25 | name: "Earrape",
26 | value: "earrape"
27 | },
28 | {
29 | name: "OFF",
30 | value: "off"
31 | }
32 | ]
33 | }],
34 | async execute(bot, interaction) {
35 | const level = await interaction.options.getString("level", true);
36 |
37 | const queue = bot.player.getQueue(interaction.guild.id);
38 |
39 | if (!queue || !queue.playing)
40 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild.");
41 |
42 | if (!bot.utils.modifyQueue(interaction)) return;
43 |
44 | let filterName;
45 | switch (level) {
46 | case "low":
47 | filterName = "bassboost_low";
48 | await queue.setFilters({
49 | bassboost_low: true
50 | });
51 | break;
52 |
53 | case "medium":
54 | filterName = "bassboost";
55 | await queue.setFilters({
56 | bassboost: true
57 | });
58 | break;
59 |
60 | case "high":
61 | filterName = "bassboost_high";
62 | await queue.setFilters({
63 | bassboost_high: true
64 | });
65 | break;
66 |
67 | case "earrape":
68 | filterName = "earrape";
69 | await queue.setFilters({
70 | earrape: true
71 | });
72 | break;
73 |
74 | case "off":
75 | filterName = "none";
76 | await queue.setFilters({
77 | bassboost_low: false,
78 | bassboost: false,
79 | bassboost_high: false,
80 | earrape: false
81 | });
82 | break;
83 | }
84 |
85 | return bot.say.successMessage(interaction, `${queue.getFiltersEnabled().includes(`${filterName}`) ? `Bassboost filter level set to \`${level}\`` : "Disabled the bassboost filter"}.`);
86 | }
87 | };
--------------------------------------------------------------------------------
/src/commands/utility/help.js:
--------------------------------------------------------------------------------
1 | const catDetails = require("../../data/categoryDetails.json");
2 | const categories = require("../../data/categories.json");
3 | const config = require("../../../config.json");
4 | const { MessageActionRow, MessageButton } = require("discord.js");
5 |
6 | module.exports = {
7 | name: "help",
8 | description: "Shows the commands menu",
9 | category: "utility",
10 | options: [{
11 | name: "command",
12 | type: "STRING",
13 | description: "The command you’r looking for",
14 | required: false
15 | }],
16 | execute(bot, interaction) {
17 | const arg = interaction.options.getString("command", false);
18 |
19 | if (arg) {
20 | const cmd = bot.commands.get(arg);
21 | if (!cmd)
22 | return bot.say.warnMessage(interaction, `No command was found named \`${arg}\`.`);
23 |
24 | const cmdUsage = cmd.usage ? `\/${cmd.name} ${cmd.usage}` : `\/${cmd.name}`;
25 |
26 | const embed = bot.say.baseEmbed(interaction)
27 | .setAuthor(`${cmd.category} command: ${cmd.name}`, bot.user.displayAvatarURL())
28 | .addField(`${cmdUsage}`, `${cmd.description ?? "Not specified"}`)
29 | .setFooter("[] : optional • <> : required • | : or");
30 |
31 | return interaction.reply({ ephemeral: true, embeds: [embed] });
32 | }
33 |
34 | const cates = [];
35 | for (let i = 0; i < categories.length; i++) {
36 | const category = bot.commands.filter(({ category }) => category === categories[i])
37 | .map(({ name }) => name);
38 | cates.push(category);
39 | }
40 |
41 | const embed = bot.say.baseEmbed(interaction)
42 | .setAuthor("Help Commands", bot.user.displayAvatarURL())
43 | .setFooter(`Type '\/help ' for more details on a command`);
44 |
45 | for (let j = 0; j < cates.length; j++) {
46 | const name = catDetails[categories[j]];
47 |
48 | if (categories[j] === "botowner" && !config.owners.includes(interaction.user.id)) continue;
49 |
50 | embed.addField(`${name}`, `\`\`\`${cates[j].join(", ")}\`\`\``);
51 | };
52 |
53 | const button1 = new MessageButton()
54 | .setLabel("Support")
55 | .setStyle("LINK")
56 | .setURL(`${config.supportServer}`);
57 |
58 | const button2 = new MessageButton()
59 | .setLabel("Invite")
60 | .setStyle("LINK")
61 | .setURL(`https://discord.com/api/oauth2/authorize?client_id=${bot.user.id}&permissions=8&scope=applications.commands%20bot`);
62 |
63 | const row = new MessageActionRow().addComponents([button1, button2]);
64 |
65 |
66 | return interaction.reply({ ephemeral: true, embeds: [embed], components: [row] });
67 | }
68 | };
--------------------------------------------------------------------------------
/src/commands/music/loop.js:
--------------------------------------------------------------------------------
1 | const { QueueRepeatMode } = require("discord-player");
2 |
3 | module.exports = {
4 | name: "loop",
5 | description: "Change the loop mode (autoplay|track|queue|off)",
6 | category: "music",
7 | options: [
8 | {
9 | type: "SUB_COMMAND",
10 | name: "mode",
11 | description: "Shows current set loop mode."
12 | },
13 | {
14 | type: "SUB_COMMAND",
15 | name: "off",
16 | description: "Turn the looping off"
17 | },
18 | {
19 | type: "SUB_COMMAND",
20 | name: "queue",
21 | description: "Loop the queue (all songs)"
22 | },
23 | {
24 | type: "SUB_COMMAND",
25 | name: "track",
26 | description: "Repeat the current song"
27 | },
28 | {
29 | type: "SUB_COMMAND",
30 | name: "autoplay",
31 | description: "Autoplay related songs after queue end"
32 | }
33 | ],
34 | async execute(bot, interaction) {
35 | const subCmd = await interaction.options.getSubcommand(true);
36 |
37 | const queue = bot.player.getQueue(interaction.guild.id);
38 |
39 | if (!queue || !queue.playing)
40 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild.");
41 |
42 | if (!bot.utils.modifyQueue(interaction)) return;
43 |
44 | let mode;
45 | switch (subCmd) {
46 | case "off":
47 | queue.setRepeatMode(QueueRepeatMode.OFF);
48 | mode = "Turned off loop mode.";
49 | break;
50 | case "track":
51 | queue.setRepeatMode(QueueRepeatMode.TRACK);
52 | mode = "Repeating track activated";
53 | break;
54 | case "queue":
55 | queue.setRepeatMode(QueueRepeatMode.QUEUE);
56 | mode = "Looping queue enabled.";
57 | break;
58 | case "autoplay":
59 | queue.setRepeatMode(QueueRepeatMode.AUTOPLAY);
60 | mode = "Autoplay mode activated.";
61 | break;
62 | default:
63 | let md = "none";
64 | if (queue.repeatMode === 3) {
65 | md = "autoplay";
66 | } else if (queue.repeatMode == 2) {
67 | md = "queue";
68 | } else if (queue.repeatMode == 1) {
69 | md = "track";
70 | } else if (queue.repeatMode == 0) {
71 | md = "off";
72 | }
73 |
74 | const embed = bot.say.baseEmbed(interaction)
75 | .setDescription(`Loop mode is set to: \`${md}\`.`)
76 | .setFooter(`Use \'\/loop \' to change loop mode.`);
77 | return interaction.reply({ ephemeral: true, embeds: [embed] }).catch(console.error);
78 | }
79 |
80 | return bot.say.successMessage(interaction, `${mode}`);
81 | }
82 | };
--------------------------------------------------------------------------------
/src/commands/music/play.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: "play",
3 | description: "Play a song or playlist from url or name",
4 | category: "music",
5 | usage: "",
6 | options: [{
7 | name: "song",
8 | description: "The song name/url, you want to play.",
9 | type: "STRING",
10 | required: true
11 | }],
12 | async execute(bot, interaction) {
13 | try {
14 |
15 | if (!bot.utils.havePermissions(interaction))
16 | return bot.say.errorMessage(interaction, "I need **\`EMBED_LINKS\`** permission.");
17 |
18 | const string = await interaction.options.getString("song", true);
19 |
20 | const guildQueue = bot.player.getQueue(interaction.guild.id);
21 |
22 | const channel = interaction.member?.voice?.channel;
23 |
24 | if (!channel)
25 | return bot.say.warnMessage(interaction, "You have to join a voice channel first.");
26 |
27 | if (guildQueue) {
28 | if (channel.id !== interaction.guild.me?.voice?.channelId)
29 | return bot.say.warnMessage(interaction, "I'm already playing in a different voice channel!");
30 | } else {
31 | if (!channel.viewable)
32 | return bot.say.warnMessage(interaction, "I need **\`VIEW_CHANNEL\`** permission.");
33 | if (!channel.joinable)
34 | return bot.say.warnMessage(interaction, "I need **\`CONNECT_CHANNEL\`** permission.");
35 | if (!channel.speakable)
36 | return bot.say.warnMessage(interaction, "I need **\`SPEAK\`** permission.");
37 | if (channel.full)
38 | return bot.say.warnMessage(interaction, "Can't join, the voice channel is full.");
39 | }
40 |
41 | let result = await bot.player.search(string, { requestedBy: interaction.user }).catch(() => { });
42 | if (!result || !result.tracks.length)
43 | return bot.say.errorMessage(interaction, `No result was found for \`${string}\`.`);
44 |
45 | let queue;
46 | if (guildQueue) {
47 | queue = guildQueue;
48 | queue.metadata = interaction;
49 | } else {
50 | queue = await bot.player.createQueue(interaction.guild, {
51 | metadata: interaction
52 | });
53 | }
54 |
55 | try {
56 | if (!queue.connection) await queue.connect(channel);
57 | } catch (error) {
58 | bot.logger.error("JOIN", error);
59 | bot.player.deleteQueue(interaction.guild.id);
60 | return bot.say.errorMessage(interaction, `Could not join your voice channel!\n\`${error}\``);
61 | }
62 |
63 | result.playlist ? queue.addTracks(result.tracks) : queue.addTrack(result.tracks[0]);
64 |
65 | if (!queue.playing) await queue.play();
66 | } catch (e) {
67 | console.log(e)
68 | }
69 | }
70 | };
--------------------------------------------------------------------------------
/src/modules/Embeds.js:
--------------------------------------------------------------------------------
1 | const DJS = require("discord.js");
2 |
3 | /**
4 | * Returns a custom embed
5 | * @param {(DJS.Interaction|DJS.Guild|import("discord.player").Queue|string)} [resolvable]
6 | */
7 | function baseEmbed(resolvable) {
8 | let colour = "#00FFFF";
9 | if (resolvable && typeof resolvable === "string") colour = resolvable;
10 | if (resolvable && typeof resolvable === "object") colour = ("guild" in resolvable ? resolvable.guild : resolvable)?.me?.displayColor || "#00FFFF";
11 |
12 | return new DJS.MessageEmbed()
13 | .setColor(colour);
14 | }
15 |
16 |
17 | /**
18 | * Reply a custom embed to interaction
19 | * @param {DJS.Interaction} interaction
20 | * @param {string} text
21 | * @param {boolean} [ephemeral=false]
22 | */
23 | function successMessage(interaction, text, ephemeral = false) {
24 | if (!interaction) {
25 | throw Error("'interaction' must be passed down as param! (successMessage)");
26 | }
27 |
28 | if (!text) {
29 | throw Error("'text' must be passed down as param! (successMessage)");
30 | }
31 |
32 | const embedS = new DJS.MessageEmbed()
33 | .setDescription(text)
34 | .setColor(interaction.guild.me.displayColor || "#00FFFF");
35 |
36 | if (interaction.deferred || interaction.replied) {
37 | return interaction.editReply({ embeds: [embedS] }).catch(console.error);
38 | } else {
39 | return interaction.reply({ ephemeral, embeds: [embedS] }).catch(console.error);
40 | }
41 | }
42 |
43 | /**
44 | * Reply a custom embed to interaction
45 | * @param {DJS.Interaction} interaction
46 | * @param {string} text
47 | */
48 | function warnMessage(interaction, text) {
49 | if (!interaction) {
50 | throw Error("'interaction' must be passed down as param! (warnMessage)");
51 | }
52 |
53 | if (!text) {
54 | throw Error("'text' must be passed down as param! (warnMessage)");
55 | }
56 |
57 | const embedW = new DJS.MessageEmbed()
58 | .setDescription(text)
59 | .setColor("ORANGE");
60 |
61 | if (interaction.deferred || interaction.replied) {
62 | return interaction.editReply({ embeds: [embedW] }).catch(console.error);
63 | } else {
64 | return interaction.reply({ ephemeral: true, embeds: [embedW] }).catch(console.error);
65 | }
66 | }
67 |
68 | /**
69 | * Reply a custom embed to interaction
70 | * @param {DJS.Interaction} interaction
71 | * @param {string} text
72 | */
73 | function errorMessage(interaction, text) {
74 | if (!interaction) {
75 | throw Error("'interaction' must be passed down as param! (errorMessage)");
76 | }
77 |
78 | if (!text) {
79 | throw Error("'text' must be passed down as param! (errorMessage)");
80 | }
81 |
82 | const embedE = new DJS.MessageEmbed()
83 | .setDescription(text)
84 | .setColor("RED");
85 |
86 | if (interaction.deferred || interaction.replied) {
87 | return interaction.editReply({ embeds: [embedE] }).catch(console.error);
88 | } else {
89 | return interaction.reply({ ephemeral: true, embeds: [embedE] }).catch(console.error);
90 | }
91 | }
92 |
93 |
94 | /**
95 | * Send a custom embed to queue textChannel
96 | * @param {import("discord.player").Queue} queue
97 | * @param {string} text
98 | * @param {string | number} [color]
99 | */
100 | function queueMessage(queue, text, color) {
101 | if (!queue) {
102 | throw Error("'queue' must be passed down as param! (queueMessage)");
103 | }
104 |
105 | if (!text) {
106 | throw Error("'text' must be passed down as param! (queueMessage)");
107 | }
108 |
109 | const { havePermissions } = require("./Util");
110 | if (!havePermissions(queue.metadata.channel)) return;
111 |
112 | let colour = queue.guild.me.displayColor || "#00FFFF";
113 | if (color) colour = color;
114 |
115 | const embedQ = new DJS.MessageEmbed()
116 | .setDescription(text)
117 | .setColor(colour);
118 |
119 | return queue.metadata.channel.send({ embeds: [embedQ] });
120 | }
121 |
122 | module.exports = {
123 | baseEmbed,
124 | successMessage,
125 | warnMessage,
126 | errorMessage,
127 | queueMessage
128 | };
--------------------------------------------------------------------------------
/src/modules/Util.js:
--------------------------------------------------------------------------------
1 | const DJS = require("discord.js");
2 |
3 | /**
4 | * Send error log to discord channel
5 | * @param {DJS.Client} bot
6 | * @param {DJS.DiscordAPIError | DJS.HTTPError | Error } error
7 | * @param {"warning" | "error"} type
8 | */
9 | async function sendErrorLog(bot, error, type) {
10 | try {
11 | if (
12 | error.message?.includes("Missing Access") ||
13 | error.message?.includes("Unknown Message") ||
14 | error.message?.includes("Missing Permissions")
15 | ) return;
16 |
17 | bot.logger.error("ERR_LOG", error);
18 |
19 | } catch (e) {
20 | console.error({ error });
21 | console.error(e);
22 | }
23 | }
24 |
25 | /**
26 | * Check if the bot has the default permissions
27 | * @param {DJS.Interaction | DJS.TextChannel} resolveable
28 | * @returns {boolean}
29 | */
30 | function havePermissions(resolveable) {
31 | const ch = "channel" in resolveable ? resolveable.channel : resolveable;
32 | if (ch instanceof DJS.ThreadChannel || ch instanceof DJS.DMChannel) return true;
33 |
34 | return (
35 | ch.permissionsFor(resolveable.guild.me)?.has(DJS.Permissions.FLAGS.VIEW_CHANNEL) &&
36 | ch.permissionsFor(resolveable.guild.me)?.has(DJS.Permissions.FLAGS.SEND_MESSAGES) &&
37 | ch.permissionsFor(resolveable.guild.me)?.has(DJS.Permissions.FLAGS.EMBED_LINKS)
38 | );
39 | }
40 |
41 | /**
42 | * Capitalise a word
43 | * @param {string} word
44 | * @returns {string}
45 | */
46 | function toCapitalize(word) {
47 | if (!word) return null;
48 | word = word.toString();
49 |
50 | return word.replace(/\w\S*/g,
51 | function(txt) {
52 | return txt.charAt(0).toUpperCase() +
53 | txt.substr(1).toLowerCase();
54 | });
55 | }
56 |
57 | /**
58 | * Format a number to local string
59 | * @param {number | string} n
60 | * @returns {string}
61 | */
62 | function formatNumber(n) {
63 | return Number.parseFloat(String(n)).toLocaleString("be-BE");
64 | }
65 |
66 | /**
67 | * Format duration to string
68 | * @param {number} millisec Duration in milliseconds
69 | * @returns {string}
70 | */
71 | function formatDuration(millisec) {
72 | if (!millisec || !Number(millisec)) return "0 Second";
73 | const seconds = Math.round((millisec % 60000) / 1000);
74 | const minutes = Math.floor((millisec % 3600000) / 60000);
75 | const hours = Math.floor(millisec / 3600000);
76 | if (hours > 0) return `${hours} Hour, ${minutes} Minute, ${seconds} Second`;
77 | if (minutes > 0) return `${minutes} Minute, ${seconds} Second`;
78 | return `${seconds} Second`;
79 | };
80 |
81 |
82 | /**
83 | * Convert formatted duration to milliseconds
84 | * @param {string} formatted duration input
85 | * @returns {number}
86 | */
87 | function toMilliseconds(input) {
88 | if (!input) return 0;
89 | if (typeof input !== "string") return Number(input) || 0;
90 | if (input.match(/:/g)) {
91 | const time = input.split(":").reverse();
92 | let s = 0;
93 | for (let i = 0; i < 3; i++)
94 | if (time[i]) s += Number(time[i].replace(/[^\d.]+/g, "")) * Math.pow(60, i);
95 | if (time.length > 3) s += Number(time[3].replace(/[^\d.]+/g, "")) * 24 * 60 * 60;
96 | return Number(s * 1000);
97 | } else {
98 | return Number(input.replace(/[^\d.]+/g, "") * 1000) || 0;
99 | }
100 | }
101 |
102 |
103 | /**
104 | * Parse number from input
105 | * @param {*} input Any
106 | * @returns {number}
107 | */
108 | function parseNumber(input) {
109 | if (typeof input === "string") return Number(input.replace(/[^\d.]+/g, "")) || 0;
110 | return Number(input) || 0;
111 | }
112 |
113 | /**
114 | * Check if interaction member can modify queue
115 | * @param {DJS.Interaction} interaction
116 | * @returns {boolean}
117 | */
118 | function modifyQueue(interaction) {
119 | const memberChannelId = interaction.member?.voice?.channelId;
120 | const botChannelId = interaction.guild.me?.voice?.channelId;
121 |
122 | if (!memberChannelId) {
123 | return interaction.client.say.errorMessage(interaction, "You need to join a voice channel first!");
124 | }
125 |
126 | if (memberChannelId !== botChannelId) {
127 | return interaction.client.say.warnMessage(interaction, "You must be in the same voice channel as me!");
128 | }
129 |
130 | return true;
131 | }
132 |
133 | module.exports = {
134 | sendErrorLog,
135 | havePermissions,
136 | toCapitalize,
137 | formatNumber,
138 | formatDuration,
139 | toMilliseconds,
140 | modifyQueue
141 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------