├── 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 |

No Coding Needed Discord Music Bot

2 | 3 | ## ⚙ Configuration 4 | 5 | Basic configuration 6 | 7 | + `botToken` : The token of the bot available on the [Discord Developers Portal](https://discordapp.com/developers/applications). 8 | - `supportServer` : The discord support server link. 9 | + `owners` : An array of owner ids, Ex: `"owners": ["12345678901234"]`. 10 | 11 | ## 📑 Installation 12 | 13 | To use the project correctly you will need some tools. 14 | 15 | [NPM](https://www.npmjs.org) (>= v7) to install packages 16 | 17 | [Node JS](https://nodejs.org/en/) (>= v16.6) for environment 18 | 19 | **Run the following to start the bot** 20 | 21 | + `npm install` 22 | + `npm start` or `node src/index.js` 23 | 24 | ## 🤝 Contributing 25 | 26 | 1. [Fork the repository](https://github.com/BlackKnight683/Broken-Disc/fork) 27 | 2. Clone your fork: `git clone https://github.com/your-username/Broken-Disc.git` 28 | 3. Create your feature branch: `git checkout -b my-new-feature` 29 | 4. Commit your changes: `git commit -am 'Add some feature'` 30 | 5. Push to the branch: `git push origin my-new-feature` 31 | 6. Submit a pull request 32 | 33 | -------------------------------------------------------------------------------- /src/commands/music/jump.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "jump", 3 | description: "Jump to a specific track in the queue.", 4 | category: "music", 5 | usage: "", 6 | options: [{ 7 | name: "index", 8 | description: "The song index to jump to", 9 | type: "NUMBER", 10 | required: true 11 | }], 12 | async execute(bot, interaction) { 13 | let index = await interaction.options.getNumber("index", true); 14 | index = index - 1; 15 | 16 | const queue = bot.player.getQueue(interaction.guild.id); 17 | 18 | if (!queue || !queue.playing) 19 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 20 | 21 | if (!bot.utils.modifyQueue(interaction)) return; 22 | 23 | if (queue.tracks.length < 1) 24 | return bot.say.errorMessage(interaction, "There is no song in the queue."); 25 | 26 | if (index > queue.tracks.length || index < 0 || !queue.tracks[index]) 27 | return bot.say.errorMessage(interaction, "Provided song index does not exist."); 28 | 29 | queue.jump(index); 30 | 31 | return bot.say.successMessage(interaction, `Jumped to song \`${index}\`.`); 32 | } 33 | }; -------------------------------------------------------------------------------- /src/commands/music/remove.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "remove", 3 | description: "Removes a specific song from the queue", 4 | usage: "", 5 | category: "music", 6 | options: [{ 7 | name: "index", 8 | description: "The song index to remove", 9 | type: "NUMBER", 10 | required: true 11 | }], 12 | async execute(bot, interaction) { 13 | let index = await interaction.options.getNumber("index", true); 14 | 15 | const queue = bot.player.getQueue(interaction.guild.id); 16 | 17 | if (!queue || !queue.playing) 18 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 19 | 20 | if (!bot.utils.modifyQueue(interaction)) return; 21 | 22 | if (queue.tracks.length < 1) 23 | return bot.say.warnMessage(interaction, "There's no song to remove in the queue."); 24 | 25 | index = index - 1; 26 | 27 | if (index < 0 || index > queue.tracks.length || !queue.tracks[index]) 28 | return bot.say.warnMessage(interaction, "Provided Song Index does not exist."); 29 | 30 | queue.remove(index); 31 | 32 | return bot.say.successMessage(interaction, `Removed track \`${index}\`.`); 33 | } 34 | }; -------------------------------------------------------------------------------- /src/modules/Logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | 3 | class Logger { 4 | get now() { 5 | return Intl.DateTimeFormat("be-BE", { 6 | minute: "2-digit", 7 | day: "2-digit", 8 | hour: "2-digit", 9 | month: "2-digit", 10 | year: "numeric", 11 | second: "2-digit", 12 | }).format(Date.now()); 13 | } 14 | 15 | /** 16 | * @param {string} type 17 | * @param {string} error 18 | */ 19 | error(type, error) { 20 | const err = error instanceof Error ? error.message : error; 21 | return console.error(`${chalk.red("[ERROR]")}-[${this.now}]: ${err}`); 22 | } 23 | 24 | /** 25 | * @param {string} type 26 | * @param {string} warning 27 | */ 28 | warn(type, warning) { 29 | return console.warn(`${chalk.yellow("[WARNING]")}-[${this.now}]: ${warning}`); 30 | } 31 | 32 | /** 33 | * @param {string} type 34 | * @param {string} content 35 | */ 36 | info(type, content) { 37 | return console.log(`${chalk.blueBright("[INFO]")}-[${this.now}]: ${content}`); 38 | } 39 | 40 | /** 41 | * @param {string} type 42 | * @param {string} text 43 | */ 44 | debug(type, text) { 45 | return console.log(`${chalk.green("[DEBUG]")}-[${this.now}]: ${text}`); 46 | } 47 | } 48 | 49 | module.exports = new Logger(); -------------------------------------------------------------------------------- /src/commands/music/nowplaying.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "nowplaying", 3 | description: "Shows details of the currently playing song.", 4 | category: "music", 5 | options: [{ 6 | name: "index", 7 | type: "NUMBER", 8 | description: "That song index.", 9 | required: true 10 | }], 11 | async execute(bot, interaction) { 12 | let index = await interaction.options.getNumber("index", true); 13 | 14 | const queue = bot.player.getQueue(interaction.guild.id); 15 | 16 | if (!queue || !queue.current) 17 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 18 | 19 | index = index - 1; 20 | 21 | if (!queue.tracks[index] || index > queue.tracks.length || index < 0) 22 | return bot.say.errorMessage(interaction, "Provided Song Index does not exist."); 23 | 24 | const song = queue.tracks[index] 25 | 26 | const embed = bot.say.baseEmbed(interaction) 27 | .setAuthor("Now Playing 🎵") 28 | .setTitle(`${song.title}`) 29 | .setURL(`${song.url}`) 30 | .setThumbnail(`${song.thumbnail}`) 31 | .setDescription(`~ Requested by: ${song.requestedBy.toString()} 32 | Duration: ${song.duration} 33 | Position in queue: ${index}`); 34 | 35 | return interaction.reply({ ephemeral: true, embeds: [embed] }).catch(console.error); 36 | } 37 | }; -------------------------------------------------------------------------------- /src/commands/music/volume.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "volume", 3 | description: "Check or change the volume", 4 | category: "music", 5 | options: [{ 6 | name: "amount", 7 | description: "Changes the bot’s output volume", 8 | type: "NUMBER", 9 | required: false 10 | }], 11 | async execute(bot, interaction) { 12 | const newVol = await interaction.options.getNumber("amount", false); 13 | 14 | const queue = bot.player.getQueue(interaction.guild.id); 15 | 16 | if (!queue || !queue.playing) 17 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 18 | 19 | if (!bot.utils.modifyQueue(interaction)) return; 20 | 21 | if (!newVol) { 22 | const embed = bot.say.baseEmbed(interaction) 23 | .setDescription(`Volume is at \`${queue.volume}%\`.`) 24 | .setFooter(`Use \'\/volume <1-200>\' to change the volume.`); 25 | 26 | return interaction.reply({ ephemeral: true, embeds: [embed] }).catch(console.error); 27 | } 28 | 29 | if (!Number.isInteger(newVol) || newVol >= 200 || newVol <= 0) 30 | return bot.say.warnMessage(interaction, "Provide a valid number between 1 to 200."); 31 | 32 | queue.setVolume(newVol); 33 | 34 | return bot.say.successMessage(interaction, `Volume is updated to \`${await queue.volume}%\`.`); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brokendisc", 3 | "version": "2.0.1", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "homepage": "https://github.com/BlackKnight683/BrokenDisc#README", 7 | "main": "src/index.js", 8 | "private": true, 9 | "author": { 10 | "name": "blackknight683", 11 | "url": "https://solonodes.xyz" 12 | }, 13 | "scripts": { 14 | "test": "echo \"error: no test specified\" && exit 1", 15 | "start": "node src/index.js" 16 | }, 17 | "engines": { 18 | "npm": ">=7.0.0", 19 | "node": ">=16.6.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+ssh://git@github.com/BlackKnight683/BrokenDisc.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/BlackKnight683/BrokenDisc/issues", 27 | "email": "oofy@solonodes.xyz" 28 | }, 29 | "keywords": [ 30 | "discord-bot", 31 | "discord.js", 32 | "discord-js", 33 | "discord-music-bot", 34 | "music-bot", 35 | "djs-v13", 36 | "discord-player" 37 | ], 38 | "dependencies": { 39 | "@discord-player/extractor": "^3.0.2", 40 | "@discordjs/builders": "^0.11.0", 41 | "@discordjs/opus": "^0.7.0", 42 | "@discordjs/voice": "^0.7.5", 43 | "chalk": "^4.1.2", 44 | "discord-player": "^5.2.0", 45 | "discord.js": "^13.3.1", 46 | "ffmpeg-static": "^4.4.0", 47 | "glob": "^7.1.7", 48 | "ytdl-core": "^4.9.2" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/music/seek.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "seek", 3 | description: "Seeks to a specific position in the current song.", 4 | usage: "", 5 | category: "music", 6 | options: [{ 7 | name: "duration", 8 | description: "The duration to seek ", 9 | type: "STRING", 10 | required: true 11 | }], 12 | async execute(bot, interaction) { 13 | let timeString = interaction.options.getString("duration", true); 14 | 15 | const queue = bot.player.getQueue(interaction.guild.id); 16 | 17 | if (!queue || !queue.playing) 18 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 19 | 20 | if (!bot.utils.modifyQueue(interaction)) return; 21 | 22 | const song = queue.current; 23 | 24 | if (song.live) 25 | return bot.say.warnMessage(interaction, "Can't seek this live streaming song."); 26 | 27 | if (isNaN(timeString) && !timeString.includes(":")) 28 | return bot.say.errorMessage(interaction, "Provide a valid duration to seek."); 29 | 30 | if (!isNaN(timeString)) timeString = `00:${timeString}`; 31 | 32 | const time = bot.utils.toMilliseconds(timeString); 33 | 34 | if (!time || isNaN(time) || time > song.durationMS || time < 0) 35 | return bot.say.warnMessage(interaction, "Provide a valid duration to seek."); 36 | 37 | queue.seek(time); 38 | 39 | return bot.say.successMessage(interaction, `Seeked to \`${timeString}\`.`); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/commands/music/lyrics.js: -------------------------------------------------------------------------------- 1 | const { Lyrics } = require("@discord-player/extractor"); 2 | const lyricsClient = Lyrics.init(); 3 | 4 | module.exports = { 5 | name: "lyrics", 6 | description: "Get lyrics for a song.", 7 | usage: "[songName]", 8 | category: "music", 9 | options: [{ 10 | type: "STRING", 11 | name: "query", 12 | description: "The song title to search lyrics", 13 | required: false 14 | }], 15 | async execute(bot, interaction) { 16 | await interaction.deferReply({ ephemeral: true }); 17 | 18 | const queue = bot.player.getQueue(interaction.guild.id); 19 | 20 | const query = interaction.options.getString("query", false) ?? queue?.current?.title; 21 | 22 | if (!query) 23 | return bot.say.errorMessage(interaction, "You forgot to provide the song name."); 24 | 25 | const queryFormated = query 26 | .toLowerCase() 27 | .replace(/\(lyrics|lyric|official music video|official video hd|official video|audio|official|clip officiel|clip|extended|hq\)/g, ""); 28 | 29 | const result = await lyricsClient.search(`${queryFormated}`); 30 | 31 | if (!result || !result.lyrics) 32 | return bot.say.errorMessage(interaction, "No lyrics were found for this song."); 33 | 34 | const embed = bot.say.baseEmbed(interaction) 35 | .setTitle(`${query}`) 36 | .setDescription(`${result.lyrics.slice(0, 4090)}...`); 37 | 38 | return interaction.editReply({ embeds: [embed] }).catch(console.error); 39 | } 40 | }; -------------------------------------------------------------------------------- /src/commands/music/queue.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "queue", 3 | description: "Shows the queue.", 4 | category: "music", 5 | options: [{ 6 | name: "page", 7 | description: "The page number of the queue", 8 | type: "NUMBER", 9 | required: false 10 | }], 11 | async execute(bot, interaction) { 12 | let page = (await interaction.options.getNumber("page", false)) ?? 1; 13 | 14 | const queue = bot.player.getQueue(interaction.guild.id); 15 | 16 | if (!queue || !queue.playing) 17 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 18 | 19 | if (!queue.tracks.length) 20 | return bot.say.warnMessage(interaction, "There is currently no song in the queue."); 21 | 22 | const multiple = 10; 23 | 24 | const maxPages = Math.ceil(queue.tracks.length / multiple); 25 | 26 | if (page < 1 || page > maxPages) page = 1; 27 | 28 | const end = page * multiple; 29 | const start = end - multiple; 30 | 31 | const tracks = queue.tracks.slice(start, end); 32 | 33 | const embed = bot.say.baseEmbed(interaction) 34 | .setDescription( 35 | `${tracks.map((song, i) => 36 | `${start + (++i)} - [${song.title}](${song.url}) ~ [${song.requestedBy.toString()}]` 37 | ).join("\n")}` 38 | ) 39 | .setFooter( 40 | `Page ${page} of ${maxPages} | song ${start + 1} to ${end > queue.tracks.length ? `${queue.tracks.length}` : `${end}`} of ${queue.tracks.length}`, 41 | interaction.user.displayAvatarURL({ dynamic: true }) 42 | ); 43 | 44 | return interaction.reply({ ephemeral: true, embeds: [embed] }).catch(console.error); 45 | } 46 | }; -------------------------------------------------------------------------------- /src/commands/music/filter.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "filter", 3 | description: "Filters commands", 4 | category: "music", 5 | options: [ 6 | { 7 | type: "SUB_COMMAND", 8 | name: "reset", 9 | description: "Reset all applied filters." 10 | }, 11 | { 12 | type: "SUB_COMMAND", 13 | name: "show", 14 | description: "Shows all filters." 15 | } 16 | ], 17 | async execute(bot, interaction) { 18 | const subCmd = await interaction.options.getSubcommand(true); 19 | 20 | const queue = bot.player.getQueue(interaction.guild.id); 21 | 22 | if (!queue || !queue.playing) 23 | return bot.say.errorMessage(interaction, "I’m currently not playing in this guild."); 24 | 25 | if (!bot.utils.modifyQueue(interaction)) return; 26 | 27 | const filters = queue.getFiltersEnabled(); 28 | 29 | 30 | if (subCmd === "reset") { 31 | if (!filters.length) 32 | return bot.say.warnMessage(interaction, "No filter is applied now."); 33 | 34 | queue.setFilters({}); 35 | 36 | return bot.say.successMessage(interaction, "Removed all applied filters."); 37 | 38 | } else { 39 | const enabledFilters = queue.getFiltersDisabled(); 40 | const disabledFilters = queue.getFiltersDisabled(); 41 | 42 | const enFDes = enabledFilters.map((f) => `**${bot.utils.toCapitalize(f)}** --> ✅`).join("\n"); 43 | 44 | const disFDes = disabledFilters.map((f) => `**${bot.utils.toCapitalize(f)}** --> ❌`).join("\n"); 45 | 46 | const embed = bot.say.baseEmbed(interaction) 47 | .setTitle("All Audio Filters") 48 | .setDescription(`${enFDes}\n\n${disFDes}`); 49 | 50 | return interaction.reply({ ephemeral: true, embeds: [embed] }); 51 | } 52 | } 53 | }; -------------------------------------------------------------------------------- /src/commands/music/watchtogether.js: -------------------------------------------------------------------------------- 1 | const { MessageActionRow, MessageButton } = require("discord.js"); 2 | 3 | module.exports = { 4 | name: "watchtogether", 5 | description: "Starts a youtube watch together activity voice session.", 6 | category: "activity", 7 | options: [ 8 | { 9 | type: "CHANNEL", 10 | name: "channel", 11 | description: "Mention the voice channel. (default: your voice channel)", 12 | required: false 13 | } 14 | ], 15 | async execute(bot, interaction) { 16 | const channel = (await interaction.options.getChannel("channel", false)) ?? interaction.member?.voice?.channel; 17 | 18 | if (!channel) 19 | return bot.say.warnMessage(interaction, "You have to join or mention a voice channel."); 20 | 21 | if (!channel.viewable) 22 | return bot.say.warnMessage(interaction, "I need \`View Channel\` permission."); 23 | 24 | if (channel.type !== "GUILD_VOICE") 25 | return bot.say.warnMessage(interaction, "Provide a valid guild voice channel."); 26 | 27 | if (!channel.permissionsFor(interaction.guild.me)?.has(1n)) 28 | return bot.say.warnMessage(interaction, "I need \`Create Invite\` permission."); 29 | 30 | const invite = await channel.createInvite({ 31 | targetApplication: "755600276941176913", 32 | targetType: 2 33 | }); 34 | 35 | const embed = bot.say.rootEmbed(interaction) 36 | .setTitle(`Successfully setup **YouTube Watch Together** activity to **${channel.name}** channel.`); 37 | 38 | const btnRow = new MessageActionRow().addComponents([ 39 | new MessageButton() 40 | .setLabel("Join") 41 | .setStyle("LINK") 42 | .setURL(`${invite.url}`) 43 | ]); 44 | 45 | return interaction.reply({ embeds: [embed], components: [btnRow] }).catch(console.error); 46 | } 47 | }; -------------------------------------------------------------------------------- /src/handler/CommandHandler.js: -------------------------------------------------------------------------------- 1 | const glob = require("glob"); 2 | const chalk = require('chalk') 3 | module.exports = async function loadCommands(bot) { 4 | const commandFiles = glob.sync("./src/commands/**/*.js"); 5 | 6 | bot.logger.info("COMMANDS", `Loading ${commandFiles.length} commands... (This may take a while)`) 7 | 8 | for await (const file of commandFiles) { 9 | const command = require(`../../${file}`); 10 | 11 | if (!command.name) { 12 | throw new TypeError(`[ERROR] name is required for commands! (${file})`); 13 | } 14 | 15 | if (!command.execute) { 16 | throw new TypeError( 17 | `[ERROR] execute function is required for commands! (${file})` 18 | ); 19 | } 20 | 21 | if (!command.category) { 22 | bot.logger.warn("[COMMANDS]", `${command.name} command will not be shown in the help command because no category is set.`); 23 | } 24 | 25 | const data = { 26 | name: command.name, 27 | description: command?.description ?? "Empty description", 28 | options: command?.options ?? [] 29 | }; 30 | 31 | 32 | const cmd = bot.application?.commands.cache.find((c) => c.name === command.name); 33 | if (!cmd) { 34 | await bot.application?.commands.create(data); 35 | } 36 | 37 | // debug 38 | bot.logger.debug(`CMD DEBUG`, `Loaded ${command.name}.js`); 39 | 40 | delete require.cache[require.resolve(`../../${file}`)]; 41 | bot.commands.set(command.name, command); 42 | } 43 | 44 | console.log(chalk.green('[BlackKnight683]') + chalk.cyan(' Thanks for using BrokenDisc v2 | Subscribe to my youtube @ https://youtube.com/c/BlackKnight683 💜')) 45 | console.log(chalk.red('=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=')) 46 | console.log(chalk.green('Bot: ') + chalk.cyan(`${bot.user.tag}`)) 47 | console.log(chalk.green('Status: ') + chalk.cyan('Initialized')) 48 | console.log(chalk.red('=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=')) 49 | 50 | }; -------------------------------------------------------------------------------- /src/commands/botowner/eval.js: -------------------------------------------------------------------------------- 1 | const { codeBlock } = require("@discordjs/builders"); 2 | const { inspect } = require("util"); 3 | 4 | module.exports = { 5 | name: "eval", 6 | description: "Execute a piece of javascript code", 7 | category: "botowner", 8 | ownerOnly: true, 9 | usage: "", 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 | --------------------------------------------------------------------------------