├── .github └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── changelog.md ├── examples └── examples.md ├── index.js ├── install-debian.sh ├── install-opensuse.sh └── package.json /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug/Error Report 3 | about: Follow this template to submit a bug report. 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | To reproduce this bug: 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your problem. 18 | 19 | **Versions** 20 | - OS/Env/Hosting Service 21 | - Module version 22 | - Discord.js version 23 | - Node.js version 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for the module. 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .config 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .config 3 | node_modules 4 | changelog.md 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 - 2019, Darko Pendragon 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## A Little Notice: 2 | This project is NO LONGER SUPPORTED and does NOT function. Do not use this. This repo serves as an archive of my work. If anyone wishes to re-upload this project, re-do it, or use it in any way you have my full permission. I am no longer supporting or working on this or any other projects not listed on my personal Discord. You may still join the Discord [by clicking here](https://discord.gg/4c8Rh7tWhv). 3 | 4 | # Discord MusicBot Addon 5 | *** 6 | This module is a simple Node.js based music extension/bot for Discord.js projects using YouTube. This was originally an update of an older addon for newer versions of Discord.js but not serves as it's own module. 7 | 8 | __The commands available are: (default names)__ 9 | * `musichelp [command]`: Displays help text for commands by this addon, or help for a specific command. 10 | * `play |`: Play audio from YouTube. 11 | * `search `: Search's for up to 10 videos from YT. 12 | * `skip [number]`: Skip a song or multi songs with skip [some number]. 13 | * `queue [position]`: Display the current queue. 14 | * `pause`: Pause music playback. 15 | * `resume`: Resume music playback. 16 | * `remove [position]`: Remove a song from the queue by position. 17 | * `volume`: Adjust the playback volume between 1 and 200. 18 | * `leave`: Clears the song queue and leaves the channel. 19 | * `clearqueue`: Clears the song queue. 20 | * `np`: Show the current playing song. 21 | 22 | __Permissions:__ 23 | * If `anyoneCanSkip` is true, anyone can skip songs in the queue. 24 | * If `anyoneCanAdjust` is true, anyone can adjust the volume. 25 | * If `ownerOverMember` is true, the set ID of the user (`ownerID`) will over-ride permissions from the bot. 26 | 27 | *** 28 | # Installation 29 | *** 30 | __Pre-installation:__ 31 | 1. `npm install discord.js` 32 | It is recommended to have the stable branch. 33 | 34 | 2. `ffmpeg installed` __correctly__ for your OS/env. 35 | Allows the bot to join voice as well as speak. 36 | 37 | 3. `npm install node-opus` or `npm install opusscript` 38 | Required for voice. Discord.js _prefers_ node-opus. 39 | 40 | __Installation:__ 41 | * `npm install discord.js-musicbot-addon` 42 | If you have troubles installing, see [this link](https://github.com/DarkoPendragon/discord.js-musicbot-addon/wiki/Installation-&-Troubleshooting) or [join the discord server](https://discord.gg/4c8Rh7tWhv). 43 | Note that the NPM version will be *slightly behind* the GitHub version. 44 | 45 | # Examples 46 | *** 47 | See [this page](https://github.com/DarkoPendragon/discord.js-musicbot-addon/blob/master/examples/examples.md) on the repo for examples. 48 | 49 | # Options & Config. 50 | *** 51 | __Most options are optional and thus not needed.__ 52 | The options you can pass in `music.start(client, {options})` and their types is as followed: 53 | 54 | ## Basic Options. 55 | | Option | Type | Description | Default | 56 | | --- | --- | --- | --- | 57 | | youtubeKey | String | A YouTube Data API3 key. Required to run. | NaN | 58 | | botPrefix | String | The prefix of the bot. Defaults to "!". Can also be a Map of prefix's. | ! | 59 | | messageNewSong | Boolean | Whether or not to send a message when a new song starts playing. | true | 60 | | bigPicture | Boolean | Whether to use a large (true) image or small (false) for embeds. | false | 61 | | maxQueueSize | Number | Max queue size allowed. Defaults 100. Set to 0 for unlimited. | 50 | 62 | | defVolume | Number | The default volume of music. 1 - 200. | 50 | 63 | | anyoneCanSkip | Boolean | Whether or not anyone can skip. | false | 64 | | messageHelp | Boolean | Whether to message the user on help command usage. If it can't, it will send it in the channel like normal. | false | 65 | | botAdmins | Object/Array | An array of Discord user ID's to be admins as the bot. They will ignore permissions for the bot. | [ ] | 66 | | anyoneCanAdjust | Boolean | Whether anyone can adjust volume. | false | 67 | | ownerOverMember | Boolean | Whether the owner over-rides `CanAdjust` and `CanSkip`. | false | 68 | | anyoneCanLeave | Boolean | Whether anyone can make the bot leave the currently connected channel. | false | 69 | | ownerID | String | The ID of the Discord user to be seen as the owner. Required if using `ownerOverMember`. | NaN | 70 | | logging | Boolean | Some extra none needed logging (such as caught errors that didn't crash the bot, etc). | true | 71 | | requesterName | Boolean | Whether or not to display the username of the song requester. | true | 72 | | inlineEmbeds | Boolean | Whether or not to make embed fields inline (help command and some fields are excluded). | false | 73 | | musicPresence | Boolean | Whether or not to make the bot set its presence to currently playing music. | false | 74 | | clearPresence | Boolean | Whether or not to clear the presence instead of setting it to "nothing" | false | 75 | | insertMusic | Boolean | Whether or not to insert the music bot data into `.music` on start. | false | 76 | | channelWhitelist | Object/Array | Sets a list of ID's allow when running messages. | [ ] | 77 | | channelBlacklist | Object/Array | Sets a list of ID's ignore when running messages. | [ ] | 78 | | bitRate | String | Sets the preferred bitRate for the Discord.js stream to use. | "120000" | 79 | | nextPresence | [PresenceData](https://discord.js.org/#/docs/main/stable/typedef/PresenceData) | PresenceData to set after instead of clearing it (clearPresence). | null | 80 | 81 | ## Multi-Prefix Option Example 82 | ```js 83 | .guilds.forEach 84 | .start(, { 85 | youtubeKey: "Data Key", 86 | botPrefix: 87 | }); 88 | 89 | // Exmaple Map Structure 90 | {serverID: { prefix: "!" } } 91 | ``` 92 | See [examples](https://github.com/DarkoPendragon/discord.js-musicbot-addon/blob/master/examples/examples.md) for more info. 93 | ## Cooldown 94 | | Option | Type | Description | Default | 95 | | --- | --- | --- | --- | 96 | | cooldown | Object | The main cooldown object | | 97 | | cooldown.enabled | Boolean | Whether or not cooldowns are enabled. | true | 98 | | cooldown.timer | Number | Time in MS that cooldowns last. | 10000 | 99 | | cooldown.exclude | Object/Array | Array of command names to exclude. Uses default names, not set names | ["volume","queue","pause","resume","np"] | 100 | 101 | ## Command Options. 102 | Commands pass a bit different. Each command follows the same format as below. Valid entries are `play`, `remove`, `help`, `np`, `queue`, `volume`, `pause`, `resume`, `skip`, `clearqueue`, `loop`, `leave`, `shuffle`, `deletequeue`. 103 | ```js 104 | music.start(client, { 105 | : { 106 | enabled: false, // True/False statement. 107 | alt: ["name1","name2","name3"], // Array of alt names (aliases). 108 | help: "Help text.", // String of help text. 109 | name: "play" // Name of the command. 110 | usage: "{{prefix}}play bad memes", // Usage text. {{prefix}} will insert the bots prefix. 111 | exclude: false // Excludes the command from the help command. 112 | } 113 | }); 114 | ``` 115 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # NPM Version Change Log 2 | Note that the NPM version will be behind the GitHub version, but more stable. 3 | If any issues arise open an issue, or for a faster response join [the Discord server](https://discord.gg/JHMtwhG). 4 | *** 5 | ## 13.9.1 6 | * Merged PR #133 7 | * Updated ytdl-core 8 | * Added a small error check for definitions 9 | 10 | ## 13.9.0 11 | * Added a check for ongoing searches, in the searchFunction 12 | * Added a command to completely delete a queue 13 | * PR's #120 and #126 14 | 15 | ## 13.8.1 16 | * Fixes for `TypeError: Cannot convert undefined or null to object` 17 | 18 | ## 13.8.0 19 | * Improved issues from 13.7. 20 | * Updated ytdl-core/ytpl. 21 | * Added shuffle command. 22 | * Added `working` and `needsRefresh` to the queue object. 23 | 24 | ## 13.7.0 25 | * Improved issues from 13.6. 26 | * Updated ytdl-core. 27 | 28 | ## 13.6.0 29 | * Fixed some issues causing streams not to play. 30 | * Fixed extra logging. 31 | * Added some temporary fixes that should keep the module form skipping every other/third video. 32 | * Made a work around for playlists not working. 33 | 34 | ## 13.5.0 35 | * Fixed a queue issue causing it to skip a song every now and then. 36 | * Added module checkers to help some people. 37 | 38 | ## 13.4.4 39 | * Added a `isNan` check for the volume command (issue #88). 40 | 41 | ## 13.4.3 42 | * Added the `bitRate` option. 43 | 44 | ## 13.4.2 45 | * Some small fixes. 46 | * Added `channelBlacklist` and `channelWhitelist`. 47 | * Added `nextPresence`. 48 | 49 | ## 13.4.1 50 | * Merged PR #83 and #82. 51 | 52 | ## 13.4.0 53 | * Fixed aliase commands not working (`aCmd is not a function`). 54 | * Added `insertMusic`, `defaultPrefix`. 55 | * Added multi prefix system (see examples). 56 | * More touch ups I forgot about. 57 | 58 | ## 13.3.1 59 | * [Merged PR #75](https://github.com/DarkoPendragon/discord.js-musicbot-addon/pull/75) to fix some options. 60 | * Removed logging involving the entire queue on play. 61 | * Other touch ups. 62 | 63 | ## 13.3.0 64 | * Fixed fatal Promise handaling errors. 65 | * Fixed `musicPresence`, `clearPresence` and the `updatePresence` function. 66 | * Dealt with [issue #71](https://github.com/DarkoPendragon/discord.js-musicbot-addon/issues/71). 67 | 68 | ## 13.2.1 69 | * Made the `remove` command run if the author is an admin. 70 | -------------------------------------------------------------------------------- /examples/examples.md: -------------------------------------------------------------------------------- 1 | # Basic Bot Example 2 | *** 3 | ```js 4 | // Require the Discord.js library. 5 | const Discord = require('discord.js'); 6 | 7 | // Start a new Client from Discord.js. You can name this to whatever you like. 8 | const client = new Discord.Client(); 9 | 10 | // Put the Music module in the new Client object. 11 | // This allows for easy access to all the modules 12 | // functions and data. 13 | client.music = require("discord.js-musicbot-addon"); 14 | 15 | // Now we start the music module. 16 | client.music.start(client, { 17 | // Set the api key used for YouTube. 18 | // This is required to run the bot. 19 | youtubeKey: "YouTubeAPIKeyHere" 20 | }); 21 | 22 | // Connect the bot with your Discord applications bot token. 23 | client.login("token"); 24 | ``` 25 | That example will run the bot with default settings (as seen in the readme file) and will respond to its commands. Now, let's add a few options to it: 26 | ```js 27 | // Following the previous example. 28 | client.music.start(client, { 29 | // Set the api key used for YouTube. 30 | youtubeKey: "YouTubeAPIKeyHere", 31 | 32 | // The PLAY command Object. 33 | play: { 34 | // Usage text for the help command. 35 | usage: "{{prefix}}play some tunes", 36 | // Whether or not to exclude the command from the help command. 37 | exclude: false 38 | }, 39 | 40 | // Make it so anyone in the voice channel can skip the 41 | // currently playing song. 42 | anyoneCanSkip: true, 43 | 44 | // Make it so the owner (you) bypass permissions for music. 45 | ownerOverMember: true, 46 | ownerID: "yourDiscordId", 47 | 48 | // The cooldown Object. 49 | cooldown: { 50 | // This disables the cooldown. Not recommended. 51 | enabled: false 52 | } 53 | }); 54 | ``` 55 | Following that, we've added some custom options to the bot. It will now use those instead of it's preset ones. 56 | 57 | # Calling Commands 58 | We'll now go over interacting with the bot and it's data. The first will be of changing your set YouTube Data key. 59 | ```js 60 | // Assuming you've followed the first example. 61 | // will stand for the client object. 62 | // Replace it with your actual client. 63 | // will be the .music.bot Object. 64 | 65 | .changeKey("some key").then((res) => { 66 | // Resolves the MUSICBOT Object when set. 67 | }).catch((res) => { 68 | // Rejcts when no key was passed or something 69 | // that isn't a string is passed. 70 | console.error(res); 71 | }) 72 | ``` 73 | 74 | Now we'll go over calling functions outside of the module. 75 | ```js 76 | // Here is a list of command functions for refence. 77 | .playFunction(); // PLAY command. 78 | .helpFunction(); // HELP command. 79 | .queueFunction(); // QUEUE command. 80 | .npFunction(); // NOWPLAYING command. 81 | .loopFunction(); // LOOP command. 82 | .skipFunction(); // SKIP command. 83 | .pauseFunction(); // PAUSE command. 84 | .resumeFunction(); // RESUME command. 85 | .clearFunction(); // CLEARQUEUE command. 86 | .leaveFunction(); // LEAVE command. 87 | .searchFunction(); // SEARCH command. 88 | .volumeFunction(); // VOLUME command. 89 | .removeFunction(); // REMOVE command. 90 | 91 | // All commands need two values passed to them: 92 | // : The Message Object. 93 | // Suffix: The string typically after the command. 94 | .playFunction(, suffix); 95 | 96 | // Now we'll make a simple "play" command using 97 | // these methods. 98 | client.on("message", (msg) => { 99 | if (msg.author.bot) return; // Good practice to do this. 100 | 101 | // I set the Client to this just for ease. 102 | // You'll probably have access to it another 103 | // way, but this still works. 104 | const client = msg.client; 105 | 106 | // Get the command from the message. 107 | const command = message.substring(musicbot.botPrefix.length).split(/[ \n]/)[0].trim(); 108 | 109 | // Get the suffix, the String after the command. 110 | const suffix = message.substring(musicbot.botPrefix.length + command.length).trim(); 111 | 112 | // Set the prefix to "!". This is a horrible way to set 113 | // one, but it will do for now. 114 | let prefix = "!" 115 | 116 | // Now we check if the message starts with the prefix, 117 | // and asks for the PLAY command. 118 | if (msg.content.startsWith(prefix) && command == "play") { 119 | // Now we pass the Message Object (msg) and 120 | // the suffix. It will then proceed as it would 121 | // with the bot normally. 122 | client.music.bot.playFunction(msg, suffix); 123 | }; 124 | }); 125 | ``` 126 | 127 | # Multi-Prefix Setup 128 | First, you'll need a list of your servers and the custom prefix for the server if it uses one. You'll also need to specify the default prefix. 129 | ```js 130 | var options = { 131 | 427239929924288532: { 132 | id: 427239929924288532, 133 | djRole: "DJ", 134 | prefix: "!" 135 | }, 136 | 464524721174609928: { 137 | id: 464524721174609928, 138 | prefix: "dev!", 139 | modRole: "Modz" 140 | } 141 | } 142 | 143 | // You'll notice the options have more than just a prefix. 144 | // We'll just get the prefix for each, and put it into the bot. 145 | // This is a very dumbed down way to do this. 146 | 147 | let newObj = new Map(); // Make a new map. 148 | options.forEach(option => { 149 | // "option" will be the servers in "options" in order. 150 | 151 | // Here we set the prefix for the server ID. 152 | newObj.set(options.id, {prefix: option.prefix}); 153 | }); 154 | 155 | // Now we start the module with the newObj map. 156 | .start(, { 157 | youtubeKey: "", 158 | botPrefix: newObj, 159 | defaultPrefix: "!" 160 | }); 161 | 162 | // You can also just update the prefix latter: 163 | // If you're using insertMusic remove the ".bot" from this. 164 | .bot.updatePrefix("serverID", "prefix"); 165 | ``` 166 | 167 | # Insert the MusicBot object automatically 168 | In newer updates you can set the bot to automatically add the MusicBot object to `.music`. This eliminates the need for `.music.bot` and essentially replaces it. 169 | You can do this simply by setting `insertMusic` to true in the options on start: 170 | ```js 171 | // Following the above examples 172 | Music.start(client, { 173 | youtubeKey: "", // Again, you ALWAYS need this. 174 | insertMusic: true // Set to true, the Client will now have "Client.music". 175 | }); 176 | ``` 177 | 178 | # Other questions? 179 | Feel free [to join my Discord](https://discordapp.com/invite/JHMtwhG) and I or someone else will assist you. 180 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const ytdl = require('ytdl-core'); 2 | const {YTSearcher} = require('ytsearcher'); 3 | const ytpl = require('ytpl'); 4 | const Discord = require('discord.js'); 5 | const PACKAGE = require('./package.json'); 6 | 7 | exports.start = (client, options) => { 8 | try { 9 | if (process.version.slice(1).split('.')[0] < 8) console.error(new Error(`[MusicBot] node v8 or higher is needed, please update`)); 10 | function moduleAvailable(name) { 11 | try { 12 | require.resolve(name); 13 | return true; 14 | } catch(e){} 15 | return false; 16 | }; 17 | if (moduleAvailable("ffmpeg-binaries")) console.error(new Error("[MUSIC] ffmpeg-binaries was found, this will likely cause problems")); 18 | if (!moduleAvailable("ytdl-core") || !moduleAvailable("ytpl") || !moduleAvailable("ytsearcher")) console.error(new Error("[MUSIC] one or more youtube specific modules not found, this module will not work")); 19 | 20 | class Music { 21 | constructor(client, options) { 22 | // Data Objects 23 | this.commands = new Map(); 24 | this.commandsArray = []; 25 | this.aliases = new Map(); 26 | this.queues = new Map(); 27 | this.client = client; 28 | 29 | // Play Command options 30 | this.play = { 31 | enabled: (options.play == undefined ? true : (options.play && typeof options.play.enabled !== 'undefined' ? options.play && options.play.enabled : true)), 32 | run: "playFunction", 33 | alt: (options && options.play && options.play.alt) || [], 34 | help: (options && options.play && options.play.help) || "Queue a song/playlist by URL or name.", 35 | name: (options && options.play && options.play.name) || "play", 36 | usage: (options && options.play && options.play.usage) || null, 37 | exclude: Boolean((options && options.play && options.play.exclude)), 38 | masked: "play" 39 | }; 40 | 41 | // Help Command options 42 | this.help = { 43 | enabled: (options.help == undefined ? true : (options.help && typeof options.help.enabled !== 'undefined' ? options.help && options.help.enabled : true)), 44 | run: "helpFunction", 45 | alt: (options && options.help && options.help.alt) || [], 46 | help: (options && options.help && options.help.help) || "Help for commands.", 47 | name: (options && options.help && options.help.name) || "help", 48 | usage: (options && options.help && options.help.usage) || null, 49 | exclude: Boolean((options && options.help && options.help.exclude)), 50 | masked: "help" 51 | }; 52 | 53 | // Pause Command options 54 | this.pause = { 55 | enabled: (options.pause == undefined ? true : (options.pause && typeof options.pause.enabled !== 'undefined' ? options.pause && options.pause.enabled : true)), 56 | run: "pauseFunction", 57 | alt: (options && options.pause && options.pause.alt) || [], 58 | help: (options && options.pause && options.pause.help) || "Pauses playing music.", 59 | name: (options && options.pause && options.pause.name) || "pause", 60 | usage: (options && options.pause && options.pause.usage) || null, 61 | exclude: Boolean((options && options.pause && options.pause.exclude)), 62 | masked: "pause" 63 | }; 64 | 65 | // Resume Command options 66 | this.resume = { 67 | enabled: (options.resume == undefined ? true : (options.resume && typeof options.resume.enabled !== 'undefined' ? options.resume && options.resume.enabled : true)), 68 | run: "resumeFunction", 69 | alt: (options && options.resume && options.resume.alt) || [], 70 | help: (options && options.resume && options.resume.help) || "Resumes a paused queue.", 71 | name: (options && options.resume && options.resume.name) || "resume", 72 | usage: (options && options.resume && options.resume.usage) || null, 73 | exclude: Boolean((options && options.resume && options.resume.exclude)), 74 | masked: "resume" 75 | }; 76 | 77 | // Leave Command options 78 | this.leave = { 79 | enabled: (options.leave == undefined ? true : (options.leave && typeof options.leave.enabled !== 'undefined' ? options.leave && options.leave.enabled : true)), 80 | run: "leaveFunction", 81 | alt: (options && options.leave && options.leave.alt) || [], 82 | help: (options && options.leave && options.leave.help) || "Leaves the voice channel.", 83 | name: (options && options.leave && options.leave.name) || "leave", 84 | usage: (options && options.leave && options.leave.usage) || null, 85 | exclude: Boolean((options && options.leave && options.leave.exclude)), 86 | masked: "leave" 87 | }; 88 | 89 | // Queue Command options 90 | this.queue = { 91 | enabled: (options.queue == undefined ? true : (options.queue && typeof options.queue.enabled !== 'undefined' ? options.queue && options.queue.enabled : true)), 92 | run: "queueFunction", 93 | alt: (options && options.queue && options.queue.alt) || [], 94 | help: (options && options.queue && options.queue.help) || "View the current queue.", 95 | name: (options && options.queue && options.queue.name) || "queue", 96 | usage: (options && options.queue && options.queue.usage) || null, 97 | exclude: Boolean((options && options.queue && options.queue.exclude)), 98 | masked: "queue" 99 | }; 100 | 101 | // Nowplaying Command options 102 | this.np = { 103 | enabled: (options.np == undefined ? true : (options.np && typeof options.np.enabled !== 'undefined' ? options.np && options.np.enabled : true)), 104 | run: "npFunction", 105 | alt: (options && options.np && options.np.alt) || [], 106 | help: (options && options.np && options.np.help) || "Shows the now playing text.", 107 | name: (options && options.np && options.np.name) || "np", 108 | usage: (options && options.np && options.np.usage) || null, 109 | exclude: Boolean((options && options.np && options.np.exclude)), 110 | masked: "np" 111 | }; 112 | 113 | // Loop Command options 114 | this.loop = { 115 | enabled: (options.loop == undefined ? true : (options.loop && typeof options.loop.enabled !== 'undefined' ? options.loop && options.loop.enabled : true)), 116 | run: "loopFunction", 117 | alt: (options && options.loop && options.loop.alt) || [], 118 | help: (options && options.loop && options.loop.help) || "Sets the loop state for the queue.", 119 | name: (options && options.loop && options.loop.name) || "loop", 120 | usage: (options && options.loop && options.loop.usage) || null, 121 | exclude: Boolean((options && options.loop && options.loop.exclude)), 122 | masked: "loop" 123 | }; 124 | 125 | // Search Command options 126 | this.search = { 127 | enabled: (options.search == undefined ? true : (options.search && typeof options.search.enabled !== 'undefined' ? options.search && options.search.enabled : true)), 128 | run: "searchFunction", 129 | alt: (options && options.search && options.search.alt) || [], 130 | help: (options && options.search && options.search.help) || "Searchs for up to 10 videos from YouTube.", 131 | name: (options && options.search && options.search.name) || "search", 132 | usage: (options && options.search && options.search.usage) || null, 133 | exclude: Boolean((options && options.search && options.search.exclude)), 134 | masked: "search" 135 | }; 136 | 137 | // Clear Command options 138 | this.clearqueue = { 139 | enabled: (options.clearqueue == undefined ? true : (options.clearqueue && typeof options.clearqueue.enabled !== 'undefined' ? options.clearqueue && options.clearqueue.enabled : true)), 140 | run: "clearFunction", 141 | alt: (options && options.clear && options.clear.alt) || [], 142 | help: (options && options.clear && options.clear.help) || "Clears the entire queue.", 143 | name: (options && options.clear && options.clear.name) || "clear", 144 | usage: (options && options.clear && options.clear.usage) || null, 145 | exclude: Boolean((options && options.clearqueue && options.clearqueue.exclude)), 146 | masked: "clearqueue" 147 | }; 148 | 149 | // Volume Command options 150 | this.volume = { 151 | enabled: (options.volume == undefined ? true : (options.volume && typeof options.volume.enabled !== 'undefined' ? options.volume && options.volume.enabled : true)), 152 | run: "volumeFunction", 153 | alt: (options && options.volume && options.volume.alt) || [], 154 | help: (options && options.volume && options.volume.help) || "Changes the volume output of the bot.", 155 | name: (options && options.volume && options.volume.name) || "volume", 156 | usage: (options && options.volume && options.volume.usage) || null, 157 | exclude: Boolean((options && options.volume && options.volume.exclude)), 158 | masked: "volume" 159 | }; 160 | 161 | this.remove = { 162 | enabled: (options.remove == undefined ? true : (options.remove && typeof options.remove.enabled !== 'undefined' ? options.remove && options.remove.enabled : true)), 163 | run: "removeFunction", 164 | alt: (options && options.remove && options.remove.alt) || [], 165 | help: (options && options.remove && options.remove.help) || "Remove a song from the queue by position in the queue.", 166 | name: (options && options.remove && options.remove.name) || "remove", 167 | usage: (options && options.remove && options.remove.usage) || "{{prefix}}remove [position]", 168 | exclude: Boolean((options && options.remove && options.remove.exclude)), 169 | masked: "remove" 170 | }; 171 | 172 | // Skip Command options 173 | this.skip = { 174 | enabled: (options.skip == undefined ? true : (options.skip && typeof options.skip.enabled !== 'undefined' ? options.skip && options.skip.enabled : true)), 175 | run: "skipFunction", 176 | alt: (options && options.skip && options.skip.alt) || [], 177 | help: (options && options.skip && options.skip.help) || "Skip a song or songs with `skip [number]`", 178 | name: (options && options.skip && options.skip.name) || "skip", 179 | usage: (options && options.skip && options.skip.usage) || null, 180 | exclude: Boolean((options && options.skip && options.skip.exclude)), 181 | masked: "skip" 182 | }; 183 | this.shuffle = { 184 | enabled: (options.shuffle == undefined ? true : (options.shuffle && typeof options.shuffle.enabled !== 'undefined' ? options.shuffle && options.shuffle.enabled : true)), 185 | run: "shuffleFunction", 186 | alt: (options && options.shuffle && options.shuffle.alt) || [], 187 | help: (options && options.shuffle && options.shuffle.help) || "Shuffle the queue", 188 | name: (options && options.shuffle && options.shuffle.name) || "shuffle", 189 | usage: (options && options.shuffle && options.shuffle.usage) || null, 190 | exclude: Boolean((options && options.shuffle && options.shuffle.exclude)), 191 | masked: "shuffle" 192 | }; 193 | this.deleteQueue = { 194 | enabled: (options.deleteQueue == undefined ? true : (options.deleteQueue && typeof options.deleteQueue.enabled !== 'undefined' ? options.deleteQueue && options.deleteQueue.enabled : true)), 195 | run: "deleteQueueFunction", 196 | alt: (options && options.deleteQueue && options.deleteQueue.alt) || [], 197 | help: (options && options.deleteQueue && options.deleteQueue.help) || "Delete and re-make an ongoing queue", 198 | name: (options && options.deleteQueue && options.deleteQueue.name) || "deletequeue", 199 | usage: (options && options.deleteQueue && options.deleteQueue.usage) || null, 200 | exclude: Boolean((options && options.deleteQueue && options.deleteQueue.exclude)), 201 | masked: "deletequeue" 202 | }; 203 | 204 | this.embedColor = (options && options.embedColor) || 'GREEN'; 205 | this.anyoneCanSkip = (options && typeof options.anyoneCanSkip !== 'undefined' ? options && options.anyoneCanSkip : false); 206 | this.anyoneCanLeave = (options && typeof options.anyoneCanLeave !== 'undefined' ? options && options.anyoneCanLeave : false); 207 | this.djRole = (options && options.djRole) || "DJ"; 208 | this.anyoneCanPause = (options && typeof options.anyoneCanPause !== 'undefined' ? options && options.anyoneCanPause : false); 209 | this.anyoneCanAdjust = (options && typeof options.anyoneCanAdjust !== 'undefined' ? options && options.anyoneCanAdjust : false); 210 | this.youtubeKey = (options && options.youtubeKey); 211 | this.botPrefix = (options && options.botPrefix) || "!"; 212 | this.defVolume = (options && options.defVolume) || 50; 213 | if (options.maxQueueSize === 0) { 214 | this.maxQueueSize = 0; 215 | } else { 216 | this.maxQueueSize = (options && options.maxQueueSize) || 50; 217 | } 218 | this.ownerOverMember = (options && typeof options.ownerOverMember !== 'undefined' ? options && options.ownerOverMember : false); 219 | this.botAdmins = (options && options.botAdmins) || []; 220 | this.ownerID = (options && options.ownerID); 221 | this.logging = (options && typeof options.logging !== 'undefined' ? options && options.logging : true); 222 | this.requesterName = (options && typeof options.requesterName !== 'undefined' ? options && options.requesterName : true); 223 | this.inlineEmbeds = (options && typeof options.inlineEmbeds !== 'undefined' ? options && options.inlineEmbeds : false); 224 | this.clearOnLeave = (options && typeof options.clearOnLeave !== 'undefined' ? options && options.clearOnLeave : true); 225 | this.messageHelp = (options && typeof options.messageHelp !== 'undefined' ? options && options.messageHelp : false); 226 | this.dateLocal = (options && options.dateLocal) || 'en-US'; 227 | this.bigPicture = (options && typeof options.bigPicture !== 'undefined' ? options && options.bigPicture : false); 228 | this.messageNewSong = (options && typeof options.messageNewSong !== 'undefined' ? options && options.messageNewSong : true); 229 | this.insertMusic = (options && typeof options.insertMusic !== 'undefined' ? options && options.insertMusic : false); 230 | this.defaultPrefix = (options && options.defaultPrefix) || "!"; 231 | this.channelWhitelist = (options && options.channelWhitelist) || []; 232 | this.channelBlacklist = (options && options.channelBlacklist) || []; 233 | this.minShuffle = (options && options.shuffle) || 3; 234 | this.bitRate = (options && options.bitRate) || "120000"; 235 | this.userSearching = new Map(); 236 | 237 | // Cooldown Settings 238 | this.cooldown = { 239 | enabled: (options && options.cooldown ? options && options.cooldown.enabled : true), 240 | timer: parseInt((options && options.cooldown && options.cooldown.timer) || 10000), 241 | exclude: (options && options.cooldown && options.cooldown.exclude) || ["volume","queue","pause","resume","np"] 242 | }; 243 | 244 | this.musicPresence = options.musicPresence || false; 245 | this.clearPresence = options.clearPresence || false; 246 | this.nextPresence = (options && options.nextPresence) || null; 247 | this.recentTalk = new Set(); 248 | } 249 | 250 | checkVoice(mem, bot) { 251 | return new Promise((resolve, reject) => { 252 | if (!mem || !bot) reject("invalid args"); 253 | if (!mem.voice.channel) reject("You're not in a voice channel!"); 254 | if (bot.voice.channel) { 255 | if (bot.voice.channel.id == mem.voice.channel.id) resolve(mem.voice.channel) 256 | else reject("You're in a different voice channel!") 257 | } else { 258 | resolve(mem.voice.channel); 259 | }; 260 | }); 261 | }; 262 | 263 | async updatePositions(obj, server) { 264 | return new Promise((resolve, reject) => { 265 | if (!server) reject("stage 0: no server passed for @updatePositions"); 266 | if (!obj) resolve(this.getQueue(server)); 267 | if (obj.working == true) reject("The queue is already performing a task!"); 268 | if (server != "000000") { 269 | obj.working = true; 270 | this.queues.set(server, obj); 271 | } 272 | try { 273 | var songs = typeof obj == "object" ? Array.from(obj.songs) : []; 274 | } catch (e) { 275 | console.log("aidjbasiubd"); 276 | }; 277 | try { 278 | if (!songs || songs.length <= 0 || typeof obj.songs != "object") { 279 | if (this.debug) console.log("[MUSICBOT] @updatePositions: songs object was invalid, reseting queue for "+ obj.id); 280 | this.queues.set(obj.id, {songs: [], last: obj.last ? obj.last : null, loop: obj.loop ? obj.loop : "none", id: obj.id, volume: this.defVolume, oldSongs: [],working: false, needsRefresh: false}) 281 | resolve([]) 282 | } 283 | let mm = 0; 284 | var newsongs = []; 285 | songs.forEach(s => { 286 | try { 287 | // console.log(s); 288 | if (!s) return; 289 | if (s.position !== mm) s.position = mm; 290 | newsongs.push(s); 291 | mm++; 292 | } catch (e) { 293 | console.log(e); 294 | }; 295 | }); 296 | } catch (e) { 297 | console.log(e); 298 | if (server != "000000") { 299 | obj.working = false; 300 | this.queues.set(server, obj); 301 | } 302 | reject("stage 1: @updatePositions " + e) 303 | }; 304 | obj.songs = newsongs; 305 | obj.last.position = 0; 306 | if (server != "000000") { 307 | obj.working = false; 308 | this.queues.set(server, obj); 309 | } 310 | setTimeout(() => { 311 | resolve(obj); 312 | }, 2000) 313 | }); 314 | }; 315 | 316 | isAdmin(member) { 317 | if (member.roles.find(r => r.name == this.djRole)) return true; 318 | if (this.ownerOverMember && member.id === this.botOwner) return true; 319 | if (this.botAdmins.includes(member.id)) return true; 320 | return member.hasPermission("ADMINISTRATOR"); 321 | }; 322 | 323 | canSkip(member, queue) { 324 | if (this.anyoneCanSkip) return true; 325 | else if (this.botAdmins.includes(member.id)) return true; 326 | else if (this.ownerOverMember && member.id === this.botOwner) return true; 327 | else if (queue.last.requester === member.id) return true; 328 | else if (this.isAdmin(member)) return true; 329 | else return false; 330 | }; 331 | 332 | canAdjust(member, queue) { 333 | if (this.anyoneCanAdjust) return true; 334 | else if (this.botAdmins.includes(member.id)) return true; 335 | else if (this.ownerOverMember && member.id === this.botOwner) return true; 336 | else if (queue.last.requester === member.id) return true; 337 | else if (this.isAdmin(member)) return true; 338 | else return false; 339 | }; 340 | 341 | getQueue(server) { 342 | if (!this.queues.has(server)) { 343 | this.queues.set(server, {songs: [], last: null, loop: "none", id: server,volume: this.defVolume, oldSongs: [],working: false, needsRefresh: false}); 344 | }; 345 | return this.queues.get(server); 346 | }; 347 | 348 | setLast(server, last) { 349 | return new Promise((resolve, reject) => { 350 | if (this.queues.has(server)) { 351 | let q = this.queues.get(server); 352 | q.last = last; 353 | this.queues.set(server, q); 354 | resolve(this.queues.get(server)); 355 | } else { 356 | reject("no server queue"); 357 | }; 358 | }); 359 | }; 360 | 361 | emptyQueue(server) { 362 | return new Promise((resolve, reject) => { 363 | if (!server || typeof server != "string") reject("no server id passed or passed obj was no a string @emptyQueue") 364 | this.queues.set(server, {songs: [], last: null, loop: "none", id: server, volume: this.defVolume, oldSongs: [],working: false, needsRefresh: false}); 365 | resolve(this.queues.get(server)); 366 | }); 367 | }; 368 | 369 | async updatePresence(queue, client, clear) { 370 | return new Promise((resolve, reject) => { 371 | if (this.nextPresence !== null) clear = false; 372 | if (!queue || !client) reject("invalid arguments"); 373 | if (queue.songs.length > 0 && queue.last) { 374 | client.user.setPresence({ 375 | game: { 376 | name: "🎵 | " + queue.last.title, 377 | type: 'PLAYING' 378 | } 379 | }); 380 | resolve(client.user.presence); 381 | } else { 382 | if (clear) { 383 | client.user.setPresence({ game: { name: null} }); 384 | resolve(client.user.presence); 385 | } else { 386 | if (this.nextPresence !== null) { 387 | let props; 388 | if (this.nextPresence.status && ["online","dnd","idle","invisible"].includes(this.nextPresence.status)) props.status = this.nextPresence.status; 389 | if (this.nextPresence.afk && typeof this.nextPresence.afk == "boolean") props.afk = this.nextPresence.afk; 390 | if (this.nextPresence.game && typeof this.nextPresence.game == "string") props.game = {name: this.nextPresence.game} 391 | else if (this.nextPresence.game && typeof this.nextPresence.game == "object") props.game = this.nextPresence.game; 392 | client.user.setPresence(props).catch((res) => { 393 | console.error("[MUSICBOT] Could not update presence\n" + res); 394 | client.user.setPresence({ game: { name: null} }); 395 | resolve(client.user.presence); 396 | }).then((res) => { 397 | resolve(res); 398 | }); 399 | } else { 400 | client.user.setPresence({ 401 | game: { 402 | name: "🎵 | nothing", 403 | type: 'PLAYING' 404 | } 405 | }); 406 | } 407 | resolve(client.user.presence); 408 | }; 409 | }; 410 | }); 411 | }; 412 | 413 | updatePrefix(server, prefix) { 414 | if (typeof prefix == undefined) prefix = this.defaultPrefix; 415 | if (typeof this.botPrefix != "object") this.botPrefix = new Map(); 416 | this.botPrefix.set(server, {prefix: prefix}); 417 | }; 418 | }; 419 | 420 | var musicbot = new Music(client, options); 421 | if (musicbot.insertMusic == true) client.music = musicbot; 422 | else exports.bot = musicbot; 423 | 424 | musicbot.searcher = new YTSearcher(musicbot.youtubeKey); 425 | musicbot.changeKey = (key) => { 426 | return new Promise((resolve, reject) => { 427 | if (!key || typeof key !== "string") reject("key must be a string"); 428 | musicbot.youtubeKey = key; 429 | musicbot.searcher.key = key; 430 | resolve(musicbot); 431 | }); 432 | }; 433 | 434 | client.on("ready", () => { 435 | console.log(`------- Music Bot -------\n> Version: ${PACKAGE.version}\n> Extra Logging: ${musicbot.logging}.\n> Node.js Version: ${process.version}\n------- Music Bot -------`); 436 | if (musicbot.cooldown.exclude.includes("skip")) console.warn(`[MUSIC] Excluding SKIP CMD from cooldowns can cause issues.`); 437 | if (musicbot.cooldown.exclude.includes("remove")) console.warn(`[MUSIC] Excluding REMOVE CMD from cooldowns can cause issues.`); 438 | setTimeout(() => { if (musicbot.musicPresence == true && musicbot.client.guilds.length > 1) console.warn(`[MUSIC] MusicPresence is enabled with more than one server!`); }, 2000); 439 | }); 440 | 441 | client.on("message", (msg) => { 442 | if (msg.author.bot || musicbot.channelBlacklist.includes(msg.channel.id)) return; 443 | if (musicbot.channelWhitelist.length > 0 && !musicbot.channelWhitelist.includes(msg.channel.id)) return; 444 | const message = msg.content.trim(); 445 | const prefix = typeof musicbot.botPrefix == "object" ? (musicbot.botPrefix.has(msg.guild.id) ? musicbot.botPrefix.get(msg.guild.id).prefix : musicbot.defaultPrefix) : musicbot.botPrefix; 446 | const command = message.substring(prefix.length).split(/[ \n]/)[0].trim(); 447 | const suffix = message.substring(prefix.length + command.length).trim(); 448 | const args = message.slice(prefix.length + command.length).trim().split(/ +/g); 449 | 450 | if (message.startsWith(prefix) && msg.channel.type == "text") { 451 | if (musicbot.commands.has(command)) { 452 | let tCmd = musicbot.commands.get(command); 453 | if (tCmd.enabled) { 454 | if (!musicbot.cooldown.enabled == true && !musicbot.cooldown.exclude.includes(tCmd.masked)) { 455 | if (musicbot.recentTalk.has(msg.author.id)) { 456 | if (musicbot.cooldown.enabled == true && !musicbot.cooldown.exclude.includes(tCmd.masked)) return msg.channel.send(musicbot.note("fail", "You must wait to use music commands again.")); 457 | } 458 | musicbot.recentTalk.add(msg.author.id); 459 | setTimeout(() => { musicbot.recentTalk.delete(msg.author.id) }, musicbot.cooldown.timer); 460 | } 461 | return musicbot[tCmd.run](msg, suffix, args); 462 | } 463 | } else if (musicbot.aliases.has(command)) { 464 | let aCmd = musicbot.aliases.get(command); 465 | if (aCmd.enabled) { 466 | if (!musicbot.cooldown.enabled == true && !musicbot.cooldown.exclude.includes(aCmd.masked)) { 467 | if (musicbot.recentTalk.has(msg.author.id)) { 468 | if (musicbot.cooldown.enabled == true && !musicbot.cooldown.exclude.includes(aCmd.masked)) return msg.channel.send(musicbot.note("fail", "You must wait to use music commands again.")); 469 | } 470 | musicbot.recentTalk.add(msg.author.id); 471 | setTimeout(() => { musicbot.recentTalk.delete(msg.author.id) }, musicbot.cooldown.timer); 472 | } 473 | return musicbot[aCmd.run](msg, suffix, args); 474 | } 475 | }; 476 | }; 477 | }); 478 | 479 | musicbot.playFunction = (msg, suffix, args, ignore) => { 480 | if (msg.member.voice.channel === undefined) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 481 | if (!suffix) return msg.channel.send(musicbot.note('fail', 'No video specified!')); 482 | let q = musicbot.getQueue(msg.guild.id); 483 | 484 | let vc = client.voice.connections.find(val => val.channel.guild.id == msg.member.guild.id) 485 | if (vc && vc.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 486 | if (q.songs.length >= musicbot.maxQueueSize && musicbot.maxQueueSize !== 0) return msg.channel.send(musicbot.note('fail', 'Maximum queue size reached!')); 487 | var searchstring = suffix.trim(); 488 | if (searchstring.includes("https://youtu.be/") || searchstring.includes("https://www.youtube.com/") && searchstring.includes("&")) searchstring = searchstring.split("&")[0]; 489 | 490 | 491 | if (searchstring.startsWith('http') && searchstring.includes("list=")) { 492 | msg.channel.send(musicbot.note("search", `Searching playlist items~`)); 493 | var playid = searchstring.toString() 494 | .split('list=')[1]; 495 | if (playid.toString() 496 | .includes('?')) playid = playid.split('?')[0]; 497 | if (playid.toString() 498 | .includes('&t=')) playid = playid.split('&t=')[0]; 499 | 500 | ytpl(playid, {limit: musicbot.maxQueueSize}, function(err, playlist) { 501 | if(err) return msg.channel.send(musicbot.note('fail', `Something went wrong fetching that playlist!`)); 502 | if (playlist.items.length <= 0) return msg.channel.send(musicbot.note('fail', `Couldn't get any videos from that playlist.`)); 503 | if (playlist.total_items >= musicbot.maxQueueSize && musicbot.maxQueueSize != 0) return msg.channel.send(musicbot.note('fail', `Too many videos to queue. A maximum of ` + musicbot.maxQueueSize + ` is allowed.`)); 504 | var index = 0; 505 | var ran = 0; 506 | var queue = musicbot.getQueue(msg.guild.id); 507 | 508 | playlist.items.forEach(async (video) => { 509 | ran++; 510 | if (queue.songs.length == (musicbot.maxQueueSize + 1) && musicbot.maxQueueSize !== 0 || !video) return; 511 | video.url = video.url_simple ? video.url_simple : `https://www.youtube.com/watch?v=` + video.id; 512 | musicbot.playFunction(msg, video.url, [], true); 513 | index++; 514 | 515 | if (ran >= playlist.items.length) { 516 | console.log(queue); 517 | if (queue.songs.length >= 1) musicbot.executeQueue(msg, queue); 518 | if (index == 0) msg.channel.send(musicbot.note('fail', `Coudln't get any songs from that playlist!`)) 519 | else if (index == 1) msg.channel.send(musicbot.note('note', `Queued one song.`)); 520 | else if (index > 1) msg.channel.send(musicbot.note('note', `Queued ${index} songs.`)); 521 | } 522 | }); 523 | }); 524 | } else { 525 | if (!ignore) msg.channel.send(musicbot.note("search", `\`Searching: ${searchstring}\`~`)); 526 | new Promise(async (resolve, reject) => { 527 | let result = await musicbot.searcher.search(searchstring, { type: 'video' }).catch((err) => { 528 | var errorMsg = err.message; 529 | if (errorMsg.includes('\"reason\": \"dailyLimitExceeded\",')) { 530 | errorMsg = errorMsg.slice(errorMsg.indexOf('Daily Limit Exceeded. ')); 531 | errorMsg = errorMsg.slice(0, errorMsg.indexOf('\",')); 532 | if (!ignore) msg.channel.send(musicbot.note("fail", "**Unable to complete playback:**\n" + errorMsg)); 533 | return; 534 | } else if (errorMsg.includes('\"reason\": \"quotaExceeded\",')) { 535 | if (!ignore) msg.channel.send(musicbot.note("fail", "Unable to complete playback! Google API quota exceeded!")); 536 | return; 537 | } else { 538 | if (!ignore) msg.channel.send(musicbot.note("fail", "Unknown error occurred! Playback could not be completed, check the logs for more details.")); 539 | return console.log(err); 540 | } 541 | }); 542 | if (result === undefined) return; 543 | resolve(result.first) 544 | }).then((res) => { 545 | if (!res) return msg.channel.send(musicbot.note("fail", "Something went wrong. Try again!")); 546 | res.requester = msg.author.id; 547 | if (searchstring.startsWith("https://www.youtube.com/") || searchstring.startsWith("https://youtu.be/")) res.url = searchstring; 548 | res.channelURL = `https://www.youtube.com/channel/${res.channelId}`; 549 | res.queuedOn = new Date().toLocaleDateString(musicbot.dateLocal, { weekday: 'long', hour: 'numeric' }); 550 | if (musicbot.requesterName) res.requesterAvatarURL = msg.author.displayAvatarURL(); 551 | const queue = musicbot.getQueue(msg.guild.id) 552 | res.position = queue.songs.length ? queue.songs.length : 0; 553 | queue.songs.push(res); 554 | 555 | if (!ignore) { 556 | if (msg.channel.permissionsFor(msg.guild.me).has('EMBED_LINKS')) { 557 | const embed = new Discord.RichEmbed(); 558 | try { 559 | embed.setAuthor('Adding To Queue', client.user.avatarURL()); 560 | var songTitle = res.title.replace(/\\/g, '\\\\') 561 | .replace(/\`/g, '\\`') 562 | .replace(/\*/g, '\\*') 563 | .replace(/_/g, '\\_') 564 | .replace(/~/g, '\\~') 565 | .replace(/`/g, '\\`'); 566 | embed.setColor(musicbot.embedColor); 567 | embed.addField(res.channelTitle, `[${songTitle}](${res.url})`, musicbot.inlineEmbeds); 568 | embed.addField("Queued On", res.queuedOn, musicbot.inlineEmbeds); 569 | if (!musicbot.bigPicture) embed.setThumbnail(`https://img.youtube.com/vi/${res.id}/maxresdefault.jpg`); 570 | if (musicbot.bigPicture) embed.setImage(`https://img.youtube.com/vi/${res.id}/maxresdefault.jpg`); 571 | const resMem = client.users.cache.get(res.requester); 572 | if (musicbot.requesterName && resMem) embed.setFooter(`Requested by ${client.users.cache.get(res.requester).username}`, res.requesterAvatarURL); 573 | if (musicbot.requesterName && !resMem) embed.setFooter(`Requested by \`UnknownUser (ID: ${res.requester})\``, res.requesterAvatarURL); 574 | msg.channel.send({ 575 | embed 576 | }); 577 | } catch (e) { 578 | console.error(`[${msg.guild.name}] [playCmd] ` + e.stack); 579 | }; 580 | } else { 581 | try { 582 | var songTitle = res.title.replace(/\\/g, '\\\\') 583 | .replace(/\`/g, '\\`') 584 | .replace(/\*/g, '\\*') 585 | .replace(/_/g, '\\_') 586 | .replace(/~/g, '\\~') 587 | .replace(/`/g, '\\`'); 588 | msg.channel.send(`Now Playing: **${songTitle}**\nRequested By: ${client.users.cache.get(res.requester).username}\nQueued On: ${res.queuedOn}`) 589 | } catch (e) { 590 | console.error(`[${msg.guild.name}] [npCmd] ` + e.stack); 591 | }; 592 | }; 593 | }; 594 | if (queue.songs.length === 1 || !client.voice.connections.find(val => val.channel.guild.id == msg.guild.id)) musicbot.executeQueue(msg, queue); 595 | }).catch((res) => { 596 | console.log(new Error(res)); 597 | }); 598 | }; 599 | }; 600 | 601 | musicbot.helpFunction = (msg, suffix, args) => { 602 | const prefix = typeof musicbot.botPrefix == "object" ? (musicbot.botPrefix.has(msg.guild.id) ? musicbot.botPrefix.get(msg.guild.id).prefix : musicbot.defaultPrefix) : musicbot.botPrefix; 603 | let command = suffix.trim(); 604 | if (!suffix) { 605 | if (msg.channel.permissionsFor(msg.guild.me) 606 | .has('EMBED_LINKS')) { 607 | const embed = new Discord.RichEmbed(); 608 | embed.setAuthor("Commands", client.user.avatarURL()); 609 | embed.setDescription(`Use \`${prefix}${musicbot.help.name} command name\` for help on usage. Anyone with a role named \`${musicbot.djRole}\` can use any command.`); 610 | // embed.addField(musicbot.helpCmd, musicbot.helpHelp); 611 | const newCmds = Array.from(musicbot.commands); 612 | let index = 0; 613 | let max = musicbot.commandsArray.length; 614 | embed.setColor(musicbot.embedColor); 615 | for (var i = 0; i < musicbot.commandsArray.length; i++) { 616 | if (!musicbot.commandsArray[i].exclude) embed.addField(musicbot.commandsArray[i].name, musicbot.commandsArray[i].help); 617 | index++; 618 | if (index == max) { 619 | if (musicbot.messageHelp) { 620 | let sent = false; 621 | msg.author.send({ 622 | embed 623 | }) 624 | .then(() => { 625 | sent = true; 626 | }); 627 | setTimeout(() => { 628 | if (!sent) return msg.channel.send({ 629 | embed 630 | }); 631 | }, 1200); 632 | } else { 633 | return msg.channel.send({ 634 | embed 635 | }); 636 | }; 637 | } 638 | }; 639 | } else { 640 | var cmdmsg = `= Music Commands =\nUse ${prefix}${musicbot.help.name} [command] for help on a command. Anyone with a role named \`${musicbot.djRole}\` can use any command.\n`; 641 | let index = 0; 642 | let max = musicbot.commandsArray.length; 643 | for (var i = 0; i < musicbot.commandsArray.length; i++) { 644 | if (!musicbot.commandsArray[i].disabled || !musicbot.commandsArray[i].exclude) { 645 | cmdmsg = cmdmsg + `\n• ${musicbot.commandsArray[i].name}: ${musicbot.commandsArray[i].help}`; 646 | index++; 647 | if (index == musicbot.commandsArray.length) { 648 | if (musicbot.messageHelp) { 649 | let sent = false; 650 | msg.author.send(cmdmsg, { 651 | code: 'asciidoc' 652 | }) 653 | .then(() => { 654 | sent = true; 655 | }); 656 | setTimeout(() => { 657 | if (!sent) return msg.channel.send(cmdmsg, { 658 | code: 'asciidoc' 659 | }); 660 | }, 500); 661 | } else { 662 | return msg.channel.send(cmdmsg, { 663 | code: 'asciidoc' 664 | }); 665 | }; 666 | } 667 | }; 668 | }; 669 | }; 670 | } else if (musicbot.commands.has(command) || musicbot.aliases.has(command)) { 671 | if (msg.channel.permissionsFor(msg.guild.me) 672 | .has('EMBED_LINKS')) { 673 | const embed = new Discord.RichEmbed(); 674 | command = musicbot.commands.get(command) || musicbot.aliases.get(command); 675 | if (command.exclude) return msg.channel.send(musicbot.note('fail', `${suffix} is not a valid command!`)); 676 | embed.setAuthor(command.name, msg.client.user.avatarURL()); 677 | embed.setDescription(command.help); 678 | if (command.alt.length > 0) embed.addField(`Aliases`, command.alt.join(", "), musicbot.inlineEmbeds); 679 | if (command.usage && typeof command.usage == "string") embed.addField(`Usage`, command.usage.replace(/{{prefix}})/g, prefix), musicbot.inlineEmbeds); 680 | embed.setColor(musicbot.embedColor); 681 | msg.channel.send({ 682 | embed 683 | }); 684 | } else { 685 | command = musicbot.commands.get(command) || musicbot.aliases.get(command); 686 | if (command.exclude) return msg.channel.send(musicbot.note('fail', `${suffix} is not a valid command!`)); 687 | var cmdhelp = `= ${command.name} =\n`; 688 | cmdhelp = cmdhelp + `\n${command.help}`; 689 | if (command.usage !== null) cmdhelp = cmdhelp + `\nUsage: ${command.usage.replace(/{{prefix}})/g, prefix)}`; 690 | if (command.alt.length > 0) cmdhelp = cmdhelp + `\nAliases: ${command.alt.join(", ")}`; 691 | msg.channel.send(cmdhelp, { 692 | code: 'asciidoc' 693 | }); 694 | }; 695 | } else { 696 | msg.channel.send(musicbot.note('fail', `${suffix} is not a valid command!`)); 697 | }; 698 | }; 699 | 700 | musicbot.skipFunction = (msg, suffix, args) => { 701 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 702 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 703 | if (voiceConnection === null) return msg.channel.send(musicbot.note('fail', 'No music being played.')); 704 | if (voiceConnection && voiceConnection.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 705 | 706 | const queue = musicbot.getQueue(msg.guild.id); 707 | if (!musicbot.canSkip(msg.member, queue)) return msg.channel.send(musicbot.note('fail', `You cannot skip this as you didn't queue it.`)); 708 | 709 | if (musicbot.queues.get(msg.guild.id).loop == "song") return msg.channel.send(musicbot.note("fail", "Cannot skip while loop is set to single.")); 710 | 711 | const dispatcher = voiceConnection.player.dispatcher; 712 | if (!dispatcher || dispatcher === null) { 713 | if (musicbot.logging) return console.log(new Error(`dispatcher null on skip cmd [${msg.guild.name}] [${msg.author.username}]`)); 714 | return msg.channel.send(musicbot.note("fail", "Something went wrong running skip.")); 715 | }; 716 | if (voiceConnection.paused) dispatcher.destroy(); 717 | dispatcher.destroy(); 718 | msg.channel.send(musicbot.note("note", "Skipped song.")); 719 | }; 720 | 721 | musicbot.pauseFunction = (msg, suffix, args) => { 722 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 723 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 724 | if (voiceConnection === null) return msg.channel.send(musicbot.note('fail', 'No music being played.')); 725 | if (voiceConnection && voiceConnection.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 726 | if (!musicbot.isAdmin(msg.member) && !musicbot.anyoneCanPause) return msg.channel.send(musicbot.note('fail', 'You cannot pause queues.')); 727 | 728 | const dispatcher = voiceConnection.player.dispatcher; 729 | if (dispatcher.paused) return msg.channel.send(musicbot.note(`fail`, `Music already paused!`)) 730 | else dispatcher.pause(); 731 | msg.channel.send(musicbot.note('note', 'Playback paused.')); 732 | }; 733 | 734 | musicbot.resumeFunction = (msg, suffix, args) => { 735 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 736 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 737 | if (voiceConnection === null) return msg.channel.send(musicbot.note('fail', 'No music being played.')); 738 | if (voiceConnection && voiceConnection.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 739 | if (!musicbot.isAdmin(msg.member) && !musicbot.anyoneCanPause) return msg.channel.send(musicbot.note('fail', `You cannot resume queues.`)); 740 | 741 | const dispatcher = voiceConnection.player.dispatcher; 742 | if (!dispatcher.paused) return msg.channel.send(musicbot.note('fail', `Music already playing.`)) 743 | else dispatcher.resume(); 744 | msg.channel.send(musicbot.note('note', 'Playback resumed.')); 745 | }; 746 | 747 | musicbot.leaveFunction = (msg, suffix) => { 748 | if (musicbot.isAdmin(msg.member) || musicbot.anyoneCanLeave === true) { 749 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 750 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 751 | if (voiceConnection === null) return msg.channel.send(musicbot.note('fail', 'I\'m not in a voice channel.')); 752 | if (voiceConnection && voiceConnection.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 753 | musicbot.emptyQueue(msg.guild.id).then(() => { 754 | if (!voiceConnection.player.dispatcher) return; 755 | voiceConnection.player.dispatcher.destroy(); 756 | voiceConnection.disconnect(); 757 | msg.channel.send(musicbot.note('note', 'Successfully left the voice channel.')); 758 | }).catch((res) => { 759 | console.log("["+msg.guild.id+"] " + res); 760 | musicbot.queues.delete(msg.guild.id); 761 | musicbot.queues.set(msg.guild.id, {songs: [], last: null, loop: "none", id: msg.guild.id, volume: musicbot.defVolume, oldSongs: [],working: false, needsRefresh: false}); 762 | }) 763 | 764 | } else { 765 | const chance = Math.floor((Math.random() * 100) + 1); 766 | if (chance <= 10) return msg.channel.send(musicbot.note('fail', `I'm afraid I can't let you do that, ${msg.author.username}.`)) 767 | else return msg.channel.send(musicbot.note('fail', 'Sorry, you\'re not allowed to do that.')); 768 | } 769 | } 770 | 771 | musicbot.npFunction = (msg, suffix, args) => { 772 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 773 | if (voiceConnection === null) return msg.channel.send(musicbot.note('fail', 'No music is being played.')); 774 | const queue = musicbot.getQueue(msg.guild.id, true); 775 | const dispatcher = voiceConnection.player.dispatcher; 776 | 777 | if (musicbot.queues.get(msg.guild.id).songs.length <= 0) return msg.channel.send(musicbot.note('note', 'Queue empty.')); 778 | 779 | if (msg.channel.permissionsFor(msg.guild.me) 780 | .has('EMBED_LINKS')) { 781 | const embed = new Discord.RichEmbed(); 782 | try { 783 | embed.setAuthor('Now Playing', client.user.avatarURL()); 784 | var songTitle = queue.last.title.replace(/\\/g, '\\\\') 785 | .replace(/\`/g, '\\`') 786 | .replace(/\*/g, '\\*') 787 | .replace(/_/g, '\\_') 788 | .replace(/~/g, '\\~') 789 | .replace(/`/g, '\\`'); 790 | embed.setColor(musicbot.embedColor); 791 | embed.addField(queue.last.channelTitle, `[${songTitle}](${queue.last.url})`, musicbot.inlineEmbeds); 792 | embed.addField("Queued On", queue.last.queuedOn, musicbot.inlineEmbeds); 793 | if (!musicbot.bigPicture) embed.setThumbnail(`https://img.youtube.com/vi/${queue.last.id}/maxresdefault.jpg`); 794 | if (musicbot.bigPicture) embed.setImage(`https://img.youtube.com/vi/${queue.last.id}/maxresdefault.jpg`); 795 | const resMem = client.users.cache.get(queue.last.requester); 796 | if (musicbot.requesterName && resMem) embed.setFooter(`Requested by ${client.users.cache.get(queue.last.requester).username}`, queue.last.requesterAvatarURL); 797 | if (musicbot.requesterName && !resMem) embed.setFooter(`Requested by \`UnknownUser (ID: ${queue.last.requester})\``, queue.last.requesterAvatarURL); 798 | msg.channel.send({ 799 | embed 800 | }); 801 | } catch (e) { 802 | console.error(`[${msg.guild.name}] [npCmd] ` + e.stack); 803 | }; 804 | } else { 805 | try { 806 | var songTitle = queue.last.title.replace(/\\/g, '\\\\') 807 | .replace(/\`/g, '\\`') 808 | .replace(/\*/g, '\\*') 809 | .replace(/_/g, '\\_') 810 | .replace(/~/g, '\\~') 811 | .replace(/`/g, '\\`'); 812 | msg.channel.send(`Now Playing: **${songTitle}**\nRequested By: ${client.users.cache.get(queue.last.requester).username}\nQueued On: ${queue.last.queuedOn}`) 813 | } catch (e) { 814 | console.error(`[${msg.guild.name}] [npCmd] ` + e.stack); 815 | }; 816 | } 817 | }; 818 | 819 | musicbot.deleteQueueFunction = async (msg, suffix, args) => { 820 | if (!musicbot.isAdmin(msg.member)) return msg.channel.send(musicbot.note("fail", "Only and Admin can do this.")); 821 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 822 | musicbot.emptyQueue(msg.guild.id).then(() => { 823 | if (voiceConnection !== null) { 824 | const dispatcher = voiceConnection.player.dispatcher; 825 | dispatcher.destroy() 826 | } 827 | return msg.channel.send(musicbot.note("note", "The queue should now be emptied.")) 828 | }).catch(async (res) => { 829 | console.log("["+msg.guild.id+"] " + e); 830 | // force the queue delete 831 | musicbot.queues.delete(msg.guild.id); 832 | musicbot.queues.set(msg.guild.id, {songs: [], last: null, loop: "none", id: msg.guild.id, volume: musicbot.defVolume, oldSongs: [],working: false, needsRefresh: false}); 833 | if (voiceConnection !== null) { 834 | const dispatcher = voiceConnection.player.dispatcher; 835 | dispatcher.destroy() 836 | } 837 | msg.channel.send(musicbot.note("note", "The queue should now be deleted.")) 838 | }) 839 | } 840 | 841 | musicbot.queueFunction = (msg, suffix, args) => { 842 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 843 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 844 | if (voiceConnection === null) return msg.channel.send(musicbot.note('fail', 'No music being played.')); 845 | if (voiceConnection && voiceConnection.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 846 | if (!musicbot.queues.has(msg.guild.id)) return msg.channel.send(musicbot.note("fail", "Could not find a queue for this server.")); 847 | else if (musicbot.queues.get(msg.guild.id).songs.length <= 0) return msg.channel.send(musicbot.note("fail", "Queue is empty.")); 848 | const queue = musicbot.queues.get(msg.guild.id); 849 | if (suffix) { 850 | let video = queue.songs.find(s => s.position == parseInt(suffix) - 1); 851 | if (!video) return msg.channel.send(musicbot.note("fail", "Couldn't find that video.")); 852 | const embed = new Discord.RichEmbed() 853 | .setAuthor('Queued Song', client.user.avatarURL()) 854 | .setColor(musicbot.embedColor) 855 | .addField(video.channelTitle, `[${video.title.replace(/\\/g, '\\\\').replace(/\`/g, '\\`').replace(/\*/g, '\\*').replace(/_/g, '\\_').replace(/~/g, '\\~').replace(/`/g, '\\`')}](${video.url})`, musicbot.inlineEmbeds) 856 | .addField("Queued On", video.queuedOn, musicbot.inlineEmbeds) 857 | .addField("Position", video.position + 1, musicbot.inlineEmbeds); 858 | if (!musicbot.bigPicture) embed.setThumbnail(`https://img.youtube.com/vi/${video.id}/maxresdefault.jpg`); 859 | if (musicbot.bigPicture) embed.setImage(`https://img.youtube.com/vi/${video.id}/maxresdefault.jpg`); 860 | const resMem = client.users.cache.get(video.requester); 861 | if (musicbot.requesterName && resMem) embed.setFooter(`Requested by ${client.users.cache.get(video.requester).username}`, video.requesterAvatarURL); 862 | if (musicbot.requesterName && !resMem) embed.setFooter(`Requested by \`UnknownUser (ID: ${video.requester})\``, video.requesterAvatarURL); 863 | msg.channel.send({embed}); 864 | } else { 865 | if (queue.songs.length > 11) { 866 | let pages = []; 867 | let page = 1; 868 | const newSongs = queue.songs.musicArraySort(10); 869 | newSongs.forEach(s => { 870 | var i = s.map((video, index) => ( 871 | `**${video.position + 1}:** __${video.title.replace(/\\/g, '\\\\').replace(/\`/g, '\\`').replace(/\*/g, '\\*').replace(/_/g, '\\_').replace(/~/g, '\\~').replace(/`/g, '\\`')}__` 872 | )).join('\n\n'); 873 | if (i !== undefined) pages.push(i) 874 | }); 875 | 876 | const embed = new Discord.RichEmbed(); 877 | embed.setAuthor('Queued Songs', client.user.avatarURL()); 878 | embed.setColor(musicbot.embedColor); 879 | embed.setFooter(`Page ${page} of ${pages.length}`); 880 | embed.setDescription(pages[page - 1]); 881 | msg.channel.send(embed).then(m => { 882 | m.react('⏪').then( r => { 883 | m.react('⏩') 884 | let forwardsFilter = m.createReactionCollector((reaction, user) => reaction.emoji.name === '⏩' && user.id === msg.author.id, { time: 120000 }); 885 | let backFilter = m.createReactionCollector((reaction, user) => reaction.emoji.name === '⏪' && user.id === msg.author.id, { time: 120000 }); 886 | 887 | forwardsFilter.on('collect', r => { 888 | if (page === pages.length) return; 889 | page++; 890 | embed.setDescription(pages[page - 1]); 891 | embed.setFooter(`Page ${page} of ${pages.length}`, msg.author.displayAvatarURL()); 892 | m.edit(embed); 893 | }) 894 | backFilter.on('collect', r => { 895 | if (page === 1) return; 896 | page--; 897 | embed.setDescription(pages[page - 1]); 898 | embed.setFooter(`Page ${page} of ${pages.length}`); 899 | m.edit(embed); 900 | }) 901 | }) 902 | }) 903 | } else { 904 | try { 905 | var newSongs = musicbot.queues.get(msg.guild.id).songs.map((video, index) => (`**${video.position + 1}:** __${video.title.replace(/\\/g, '\\\\').replace(/\`/g, '\\`').replace(/\*/g, '\\*').replace(/_/g, '\\_').replace(/~/g, '\\~').replace(/`/g, '\\`')}__`)).join('\n\n'); 906 | const embed = new Discord.RichEmbed(); 907 | embed.setAuthor('Queued Songs', client.user.avatarURL()); 908 | embed.setColor(musicbot.embedColor); 909 | embed.setDescription(newSongs); 910 | embed.setFooter(`Page 1 of 1`, msg.author.displayAvatarURL()); 911 | return msg.channel.send(embed); 912 | } catch (e) { 913 | console.log("["+msg.guild.id+"] " + e); 914 | return msg.channel.send(msicbot.note("fail", "Something went wrong mapping out the queue! Please delete the queue if this persists.")); 915 | }; 916 | }; 917 | }; 918 | }; 919 | 920 | musicbot.searchFunction = (msg, suffix, args) => { 921 | if (msg.member.voice.channel === undefined) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 922 | let vc = client.voice.connections.find(val => val.channel.guild.id == msg.member.guild.id) 923 | if (vc && vc.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 924 | let us = `${msg.guild.id}-${msg.author.id}`; 925 | if (musicbot.userSearching.has(us)) return msg.channel.send(musicbot.note("fail", `You already have a search on-going for \`${musicbot.userSearching.get(us).title}\`.\nYou may type \`cancel\` to cancel it.`)); 926 | 927 | if (!suffix) return msg.channel.send(musicbot.note('fail', 'No video specified!')); 928 | const queue = musicbot.getQueue(msg.guild.id); 929 | if (queue.songs.length >= musicbot.maxQueueSize && musicbot.maxQueueSize !== 0) return msg.channel.send(musicbot.note('fail', 'Maximum queue size reached!')); 930 | musicbot.userSearching.set(us, {guild: msg.guild.id, user: msg.author.id, title: suffix}) 931 | let searchstring = suffix.trim(); 932 | msg.channel.send(musicbot.note('search', `Searching: \`${searchstring}\``)) 933 | .then(response => { 934 | musicbot.searcher.search(searchstring, { 935 | type: 'video' 936 | }) 937 | .then(searchResult => { 938 | if (!searchResult.totalResults || searchResult.totalResults === 0) return response.edit(musicbot.note('fail', 'Failed to get search results.')); 939 | 940 | const startTheFun = async (videos, max) => { 941 | if (msg.channel.permissionsFor(msg.guild.me).has('EMBED_LINKS')) { 942 | const embed = new Discord.RichEmbed(); 943 | embed.setTitle(`Choose Your Video`); 944 | embed.setColor(musicbot.embedColor); 945 | var index = 0; 946 | videos.forEach(function(video) { 947 | index++; 948 | embed.addField(`${index} (${video.channelTitle})`, `[${musicbot.note('font', video.title)}](${video.url})`, musicbot.inlineEmbeds); 949 | }); 950 | embed.setFooter(`Search by: ${msg.author.username}`, msg.author.displayAvatarURL()); 951 | msg.channel.send({ 952 | embed 953 | }) 954 | .then(firstMsg => { 955 | var filter = null; 956 | if (max === 0) { 957 | filter = m => m.author.id === msg.author.id && 958 | m.content.includes('1') || 959 | m.content.trim() === (`cancel`); 960 | } else if (max === 1) { 961 | filter = m => m.author.id === msg.author.id && 962 | m.content.includes('1') || 963 | m.content.includes('2') || 964 | m.content.trim() === (`cancel`); 965 | } else if (max === 2) { 966 | filter = m => m.author.id === msg.author.id && 967 | m.content.includes('1') || 968 | m.content.includes('2') || 969 | m.content.includes('3') || 970 | m.content.trim() === (`cancel`); 971 | } else if (max === 3) { 972 | filter = m => m.author.id === msg.author.id && 973 | m.content.includes('1') || 974 | m.content.includes('2') || 975 | m.content.includes('3') || 976 | m.content.includes('4') || 977 | m.content.trim() === (`cancel`); 978 | } else if (max === 4) { 979 | filter = m => m.author.id === msg.author.id && 980 | m.content.includes('1') || 981 | m.content.includes('2') || 982 | m.content.includes('3') || 983 | m.content.includes('4') || 984 | m.content.includes('5') || 985 | m.content.trim() === (`cancel`); 986 | } else if (max === 5) { 987 | filter = m => m.author.id === msg.author.id && 988 | m.content.includes('1') || 989 | m.content.includes('2') || 990 | m.content.includes('3') || 991 | m.content.includes('4') || 992 | m.content.includes('5') || 993 | m.content.includes('6') || 994 | m.content.trim() === (`cancel`); 995 | } else if (max === 6) { 996 | filter = m => m.author.id === msg.author.id && 997 | m.content.includes('1') || 998 | m.content.includes('2') || 999 | m.content.includes('3') || 1000 | m.content.includes('4') || 1001 | m.content.includes('5') || 1002 | m.content.includes('6') || 1003 | m.content.includes('7') || 1004 | m.content.trim() === (`cancel`); 1005 | } else if (max === 7) { 1006 | filter = m => m.author.id === msg.author.id && 1007 | m.content.includes('1') || 1008 | m.content.includes('2') || 1009 | m.content.includes('3') || 1010 | m.content.includes('4') || 1011 | m.content.includes('5') || 1012 | m.content.includes('6') || 1013 | m.content.includes('7') || 1014 | m.content.includes('8') || 1015 | m.content.trim() === (`cancel`); 1016 | } else if (max === 8) { 1017 | filter = m => m.author.id === msg.author.id && 1018 | m.content.includes('1') || 1019 | m.content.includes('2') || 1020 | m.content.includes('3') || 1021 | m.content.includes('4') || 1022 | m.content.includes('5') || 1023 | m.content.includes('6') || 1024 | m.content.includes('7') || 1025 | m.content.includes('8') || 1026 | m.content.includes('9') || 1027 | m.content.trim() === (`cancel`); 1028 | } else if (max === 9) { 1029 | filter = m => m.author.id === msg.author.id && 1030 | m.content.includes('1') || 1031 | m.content.includes('2') || 1032 | m.content.includes('3') || 1033 | m.content.includes('4') || 1034 | m.content.includes('5') || 1035 | m.content.includes('6') || 1036 | m.content.includes('7') || 1037 | m.content.includes('8') || 1038 | m.content.includes('9') || 1039 | m.content.includes('10') || 1040 | m.content.trim() === (`cancel`); 1041 | } 1042 | msg.channel.awaitMessages(filter, { 1043 | max: 1, 1044 | time: 60000, 1045 | errors: ['time'] 1046 | }) 1047 | .then(collected => { 1048 | const newColl = Array.from(collected); 1049 | const mcon = newColl[0][1].content; 1050 | 1051 | if (mcon === "cancel") { 1052 | musicbot.userSearching.delete(us); 1053 | return firstMsg.edit(musicbot.note('note', 'Searching canceled.')); 1054 | }; 1055 | const song_number = parseInt(mcon) - 1; 1056 | if (song_number >= 0) { 1057 | musicbot.userSearching.delete(us); 1058 | firstMsg.delete(); 1059 | 1060 | videos[song_number].requester = msg.author.id; 1061 | videos[song_number].position = queue.songs.length ? queue.songs.length : 0; 1062 | var embed = new Discord.RichEmbed(); 1063 | embed.setAuthor('Adding To Queue', client.user.avatarURL()); 1064 | var songTitle = videos[song_number].title.replace(/\\/g, '\\\\') 1065 | .replace(/\`/g, '\\`') 1066 | .replace(/\*/g, '\\*') 1067 | .replace(/_/g, '\\_') 1068 | .replace(/~/g, '\\~') 1069 | .replace(/`/g, '\\`'); 1070 | embed.setColor(musicbot.embedColor); 1071 | embed.addField(videos[song_number].channelTitle, `[${songTitle}](${videos[song_number].url})`, musicbot.inlineEmbeds); 1072 | embed.addField("Queued On", videos[song_number].queuedOn, musicbot.inlineEmbeds); 1073 | if (!musicbot.bigPicture) embed.setThumbnail(`https://img.youtube.com/vi/${videos[song_number].id}/maxresdefault.jpg`); 1074 | if (musicbot.bigPicture) embed.setImage(`https://img.youtube.com/vi/${videos[song_number].id}/maxresdefault.jpg`); 1075 | const resMem = client.users.cache.get(videos[song_number].requester); 1076 | if (musicbot.requesterName && resMem) embed.setFooter(`Requested by ${client.users.cache.get(videos[song_number].requester).username}`, videos[song_number].requesterAvatarURL); 1077 | if (musicbot.requesterName && !resMem) embed.setFooter(`Requested by \`UnknownUser (ID: ${videos[song_number].requester})\``, videos[song_number].requesterAvatarURL); 1078 | msg.channel.send({ 1079 | embed 1080 | }).then(() => { 1081 | queue.songs.push(videos[song_number]); 1082 | if (queue.songs.length === 1 || !client.voice.connections.find(val => val.channel.guild.id == msg.guild.id)) musicbot.executeQueue(msg, queue); 1083 | }) 1084 | .catch(console.log); 1085 | }; 1086 | }) 1087 | .catch(collected => { 1088 | musicbot.userSearching.delete(us); 1089 | if (collected.toString().match(/error|Error|TypeError|RangeError|Uncaught/)) return firstMsg.edit(`\`\`\`xl\nSearching canceled. ${collected}\n\`\`\``); 1090 | return firstMsg.edit(`\`\`\`xl\nSearching canceled.\n\`\`\``); 1091 | }); 1092 | }) 1093 | } else { 1094 | const vids = videos.map((video, index) => ( 1095 | `**${index + 1}:** __${video.title.replace(/\\/g, '\\\\').replace(/\`/g, '\\`').replace(/\*/g, '\\*').replace(/_/g, '\\_').replace(/~/g, '\\~').replace(/`/g, '\\`')}__` 1096 | )).join('\n\n'); 1097 | msg.channel.send(`\`\`\`\n= Pick Your Video =\n${vids}\n\n= Say Cancel To Cancel =`).then(firstMsg => { 1098 | var filter = null; 1099 | if (max === 0) { 1100 | filter = m => m.author.id === msg.author.id && 1101 | m.content.includes('1') || 1102 | m.content.trim() === (`cancel`); 1103 | } else if (max === 1) { 1104 | filter = m => m.author.id === msg.author.id && 1105 | m.content.includes('1') || 1106 | m.content.includes('2') || 1107 | m.content.trim() === (`cancel`); 1108 | } else if (max === 2) { 1109 | filter = m => m.author.id === msg.author.id && 1110 | m.content.includes('1') || 1111 | m.content.includes('2') || 1112 | m.content.includes('3') || 1113 | m.content.trim() === (`cancel`); 1114 | } else if (max === 3) { 1115 | filter = m => m.author.id === msg.author.id && 1116 | m.content.includes('1') || 1117 | m.content.includes('2') || 1118 | m.content.includes('3') || 1119 | m.content.includes('4') || 1120 | m.content.trim() === (`cancel`); 1121 | } else if (max === 4) { 1122 | filter = m => m.author.id === msg.author.id && 1123 | m.content.includes('1') || 1124 | m.content.includes('2') || 1125 | m.content.includes('3') || 1126 | m.content.includes('4') || 1127 | m.content.includes('5') || 1128 | m.content.trim() === (`cancel`); 1129 | } else if (max === 5) { 1130 | filter = m => m.author.id === msg.author.id && 1131 | m.content.includes('1') || 1132 | m.content.includes('2') || 1133 | m.content.includes('3') || 1134 | m.content.includes('4') || 1135 | m.content.includes('5') || 1136 | m.content.includes('6') || 1137 | m.content.trim() === (`cancel`); 1138 | } else if (max === 6) { 1139 | filter = m => m.author.id === msg.author.id && 1140 | m.content.includes('1') || 1141 | m.content.includes('2') || 1142 | m.content.includes('3') || 1143 | m.content.includes('4') || 1144 | m.content.includes('5') || 1145 | m.content.includes('6') || 1146 | m.content.includes('7') || 1147 | m.content.trim() === (`cancel`); 1148 | } else if (max === 7) { 1149 | filter = m => m.author.id === msg.author.id && 1150 | m.content.includes('1') || 1151 | m.content.includes('2') || 1152 | m.content.includes('3') || 1153 | m.content.includes('4') || 1154 | m.content.includes('5') || 1155 | m.content.includes('6') || 1156 | m.content.includes('7') || 1157 | m.content.includes('8') || 1158 | m.content.trim() === (`cancel`); 1159 | } else if (max === 8) { 1160 | filter = m => m.author.id === msg.author.id && 1161 | m.content.includes('1') || 1162 | m.content.includes('2') || 1163 | m.content.includes('3') || 1164 | m.content.includes('4') || 1165 | m.content.includes('5') || 1166 | m.content.includes('6') || 1167 | m.content.includes('7') || 1168 | m.content.includes('8') || 1169 | m.content.includes('9') || 1170 | m.content.trim() === (`cancel`); 1171 | } else if (max === 9) { 1172 | filter = m => m.author.id === msg.author.id && 1173 | m.content.includes('1') || 1174 | m.content.includes('2') || 1175 | m.content.includes('3') || 1176 | m.content.includes('4') || 1177 | m.content.includes('5') || 1178 | m.content.includes('6') || 1179 | m.content.includes('7') || 1180 | m.content.includes('8') || 1181 | m.content.includes('9') || 1182 | m.content.includes('10') || 1183 | m.content.trim() === (`cancel`); 1184 | } 1185 | msg.channel.awaitMessages(filter, { 1186 | max: 1, 1187 | time: 60000, 1188 | errors: ['time'] 1189 | }) 1190 | .then(collected => { 1191 | musicbot.userSearching.delete(us); 1192 | const newColl = Array.from(collected); 1193 | const mcon = newColl[0][1].content; 1194 | 1195 | if (mcon === "cancel") return firstMsg.edit(musicbot.note('note', 'Searching canceled.')); 1196 | const song_number = parseInt(mcon) - 1; 1197 | if (song_number >= 0) { 1198 | firstMsg.delete(); 1199 | 1200 | videos[song_number].requester = msg.author.id; 1201 | videos[song_number].position = queue.songs.length ? queue.songs.length : 0; 1202 | var embed = new Discord.RichEmbed(); 1203 | embed.setAuthor('Adding To Queue', client.user.avatarURL()); 1204 | var songTitle = videos[song_number].title.replace(/\\/g, '\\\\') 1205 | .replace(/\`/g, '\\`') 1206 | .replace(/\*/g, '\\*') 1207 | .replace(/_/g, '\\_') 1208 | .replace(/~/g, '\\~') 1209 | .replace(/`/g, '\\`'); 1210 | embed.setColor(musicbot.embedColor); 1211 | embed.addField(videos[song_number].channelTitle, `[${songTitle}](${videos[song_number].url})`, musicbot.inlineEmbeds); 1212 | embed.addField("Queued On", videos[song_number].queuedOn, musicbot.inlineEmbeds); 1213 | if (!musicbot.bigPicture) embed.setThumbnail(`https://img.youtube.com/vi/${videos[song_number].id}/maxresdefault.jpg`); 1214 | if (musicbot.bigPicture) embed.setImage(`https://img.youtube.com/vi/${videos[song_number].id}/maxresdefault.jpg`); 1215 | const resMem = client.users.cache.get(videos[song_number].requester); 1216 | if (musicbot.requesterName && resMem) embed.setFooter(`Requested by ${client.users.cache.get(videos[song_number].requester).username}`, videos[song_number].requesterAvatarURL); 1217 | if (musicbot.requesterName && !resMem) embed.setFooter(`Requested by \`UnknownUser (ID: ${videos[song_number].requester})\``, videos[song_number].requesterAvatarURL); 1218 | msg.channel.send({ 1219 | embed 1220 | }).then(() => { 1221 | queue.songs.push(videos[song_number]); 1222 | if (queue.songs.length === 1 || !client.voice.connections.find(val => val.channel.guild.id == msg.guild.id)) musicbot.executeQueue(msg, queue); 1223 | }) 1224 | .catch(console.log); 1225 | }; 1226 | }) 1227 | .catch(collected => { 1228 | musicbot.userSearching.delete(us); 1229 | if (collected.toString() 1230 | .match(/error|Error|TypeError|RangeError|Uncaught/)) return firstMsg.edit(`\`\`\`xl\nSearching canceled. ${collected}\n\`\`\``); 1231 | return firstMsg.edit(`\`\`\`xl\nSearching canceled.\n\`\`\``); 1232 | }); 1233 | }) 1234 | } 1235 | }; 1236 | 1237 | const max = searchResult.totalResults >= 10 ? 9 : searchResult.totalResults - 1; 1238 | var videos = []; 1239 | for (var i = 0; i < 99; i++) { 1240 | var result = searchResult.currentPage[i]; 1241 | result.requester = msg.author.id; 1242 | if (musicbot.requesterName) result.requesterAvatarURL = msg.author.displayAvatarURL(); 1243 | result.channelURL = `https://www.youtube.com/channel/${result.channelId}`; 1244 | result.queuedOn = new Date().toLocaleDateString(musicbot.dateLocal, { weekday: 'long', hour: 'numeric' }); 1245 | videos.push(result); 1246 | if (i === max) { 1247 | i = 101; 1248 | startTheFun(videos, max); 1249 | } 1250 | }; 1251 | }); 1252 | }) 1253 | .catch(console.log); 1254 | }; 1255 | 1256 | musicbot.volumeFunction = (msg, suffix, args) => { 1257 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 1258 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1259 | if (voiceConnection === null) return msg.channel.send(musicbot.note('fail', 'No music is being played.')); 1260 | if (voiceConnection && voiceConnection.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 1261 | if (!musicbot.canAdjust(msg.member, musicbot.queues.get(msg.guild.id))) return msg.channel.send(musicbot.note('fail', `Only admins or DJ's may change volume.`)); 1262 | const dispatcher = voiceConnection.player.dispatcher; 1263 | 1264 | if (!suffix || isNaN(suffix)) return msg.channel.send(musicbot.note('fail', 'No volume specified.')); 1265 | suffix = parseInt(suffix); 1266 | if (suffix > 200 || suffix <= 0) return msg.channel.send(musicbot.note('fail', 'Volume out of range, must be within 1 to 200')); 1267 | 1268 | dispatcher.setVolume((suffix / 100)); 1269 | musicbot.queues.get(msg.guild.id).volume = suffix; 1270 | msg.channel.send(musicbot.note('note', `Volume changed to ${suffix}%.`)); 1271 | }; 1272 | 1273 | musicbot.clearFunction = (msg, suffix, args) => { 1274 | if (!musicbot.queues.has(msg.guild.id)) return msg.channel.send(musicbot.note("fail", "No queue found for this server.")); 1275 | if (!musicbot.isAdmin(msg.member)) return msg.channel.send(musicbot.note("fail", `Only Admins or people with the ${musicbot.djRole} can clear queues.`)); 1276 | let vc = client.voice.connections.find(val => val.channel.guild.id == msg.member.guild.id) 1277 | if (vc && vc.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 1278 | musicbot.emptyQueue(msg.guild.id).then(res => { 1279 | msg.channel.send(musicbot.note("note", "Queue cleared.")); 1280 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1281 | if (voiceConnection !== null) { 1282 | const dispatcher = voiceConnection.player.dispatcher; 1283 | if (!dispatcher || dispatcher === null) { 1284 | if (musicbot.logging) return console.log(new Error(`dispatcher null on skip cmd [${msg.guild.name}] [${msg.author.username}]`)); 1285 | return msg.channel.send(musicbot.note("fail", "Something went wrong.")); 1286 | }; 1287 | if (voiceConnection.paused) dispatcher.destroy(); 1288 | dispatcher.destroy(); 1289 | } 1290 | }).catch(res => { 1291 | console.error(new Error(`[clearCmd] [${msg.guild.id}] ${res}`)) 1292 | return msg.channel.send(musicbot.note("fail", "Something went wrong clearing the queue.")); 1293 | }) 1294 | }; 1295 | 1296 | musicbot.removeFunction = (msg, suffix, args) => { 1297 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 1298 | if (!musicbot.queues.has(msg.guild.id)) return msg.channel.send(musicbot.note('fail', `No queue for this server found!`)); 1299 | if (!suffix) return msg.channel.send(musicbot.note("fail", "No video position given.")); 1300 | let vc = client.voice.connections.find(val => val.channel.guild.id == msg.member.guild.id) 1301 | if (vc && vc.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 1302 | if (parseInt(suffix) - 1 == 0) return msg.channel.send(musicbot.note("fail", "You cannot clear the currently playing music.")); 1303 | let test = musicbot.queues.get(msg.guild.id).songs.find(x => x.position == parseInt(suffix) - 1); 1304 | if (test) { 1305 | if (test.requester !== msg.author.id && !musicbot.isAdmin(msg.member)) return msg.channel.send(musicbot.note("fail", "You cannot remove that item.")); 1306 | let newq = musicbot.queues.get(msg.guild.id).songs.filter(s => s !== test); 1307 | musicbot.updatePositions(musicbot.queues.get(msg.guild.id), msg.guild.id).then(res => { 1308 | musicbot.queues.get(msg.guild.id).songs = res; 1309 | msg.channel.send(musicbot.note("note", `Removed: \`${test.title.replace(/`/g, "'")}\``)); 1310 | }).catch(e=> { 1311 | console.log(e) 1312 | console.log("@ remove function"); 1313 | }) 1314 | } else { 1315 | msg.channel.send(musicbot.note("fail", "Couldn't find that video or something went wrong.")); 1316 | } 1317 | }; 1318 | 1319 | musicbot.loopFunction = (msg, suffix, args) => { 1320 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 1321 | if (!musicbot.queues.has(msg.guild.id)) return msg.channel.send(musicbot.note('fail', `No queue for this server found!`)); 1322 | let vc = client.voice.connections.find(val => val.channel.guild.id == msg.member.guild.id) 1323 | if (vc && vc.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 1324 | if (musicbot.queues.get(msg.guild.id).loop == "none" || musicbot.queues.get(msg.guild.id).loop == null) { 1325 | musicbot.queues.get(msg.guild.id).loop = "song"; 1326 | msg.channel.send(musicbot.note('note', 'Looping single enabled! :repeat_one:')); 1327 | } else if (musicbot.queues.get(msg.guild.id).loop == "song") { 1328 | musicbot.queues.get(msg.guild.id).loop = "queue"; 1329 | msg.channel.send(musicbot.note('note', 'Looping queue enabled! :repeat:')); 1330 | } else if (musicbot.queues.get(msg.guild.id).loop == "queue") { 1331 | musicbot.queues.get(msg.guild.id).loop = "none"; 1332 | msg.channel.send(musicbot.note('note', 'Looping disabled! :arrow_forward:')); 1333 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1334 | const dispatcher = voiceConnection.player.dispatcher; 1335 | let wasPaused = dispatcher.paused; 1336 | if (wasPaused) dispatcher.pause(); 1337 | let newq = musicbot.queues.get(msg.guild.id).songs.slice(musicbot.queues.get(msg.guild.id).last.position - 1); 1338 | if (newq !== musicbot.queues.get(msg.guild.id).songs) musicbot.updatePositions(musicbot.queues.get(msg.guild.id), msg.guild.id).then(res => { 1339 | musicbot.queues.get(msg.guild.id).songs = res; 1340 | }).catch(e=> { 1341 | console.log(e) 1342 | console.log("@ loop function"); 1343 | }) 1344 | if (wasPaused) dispatcher.resume(); 1345 | } 1346 | }; 1347 | musicbot.shuffleFunction = (msg, suffix, args) => { 1348 | let q = musicbot.getQueue(msg.guild.id); 1349 | if (q.working == true) return msg.channel.send(musicbot.note('fail', `This servers queue is already performing a task!`)); 1350 | if (!msg.member.voice.channel) return msg.channel.send(musicbot.note('fail', `You're not in a voice channel.`)); 1351 | if (!musicbot.queues.has(msg.guild.id)) return msg.channel.send(musicbot.note('fail', `No queue for this server found!`)); 1352 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1353 | if (voiceConnection && voiceConnection.channel.id != msg.member.voice.channel.id) return msg.channel.send(musicbot.note('fail', `You must be in the same voice channel as me.`)); 1354 | if (musicbot.queues.get(msg.guild.id).songs.length < musicbot.minShuffle) return msg.channel.send(musicbot.note('fail', `Queue must a minimum of ${musicbot.minShuffle} songs to shuffle!`)); 1355 | if (musicbot.queues.get(msg.guild.id).loop == "song") return msg.channel.send(musicbot.note("fail", `Cannot shuffle while loop is set to single.`)); 1356 | const dispatcher = voiceConnection.player.dispatcher; 1357 | q.oldSongs = q.songs; 1358 | q.songs.musicBotShuffle(); 1359 | q.needsRefresh = true; 1360 | musicbot.updatePositions(q, msg.guild.id).then((res) => { 1361 | q.songs = res.songs; 1362 | musicbot.queues.set(msg.guild.id, q); 1363 | if (voiceConnection.paused) dispatcher.resume(); 1364 | msg.channel.send(musicbot.note('note', `Queue was shuffled!`)); 1365 | 1366 | dispatcher.destroy(); 1367 | }).catch((res) => { 1368 | message.channel.send(musicbot.note("fail", "Something went wrong shuffling the queue!")) 1369 | console.log("@shuffleFunction " + res); 1370 | }) 1371 | 1372 | // } 1373 | }; 1374 | 1375 | musicbot.loadCommand = (obj) => { 1376 | return new Promise((resolve, reject) => { 1377 | let props = { 1378 | enabled: obj.enabled, 1379 | run: obj.run, 1380 | alt: obj.alt, 1381 | help: obj.help, 1382 | name: obj.name, 1383 | exclude: obj.exclude, 1384 | masked: obj.masked 1385 | }; 1386 | if (props.enabled == undefined || null) props.enabled = true; 1387 | if (obj.alt.length > 0) { 1388 | obj.alt.forEach((a) => { 1389 | musicbot.aliases.set(a, props); 1390 | }) 1391 | }; 1392 | musicbot.commands.set(obj.name, props); 1393 | musicbot.commandsArray.push(props); 1394 | if (musicbot.logging) console.log(`[MUSIC_LOADCMD] Loaded ${obj.name}`); 1395 | resolve(musicbot.commands.get(obj.name)); 1396 | }); 1397 | } 1398 | 1399 | musicbot.executeQueue = (msg, queue) => { 1400 | musicbot.queues.set(queue.id, queue); 1401 | if (queue.songs.length == 0) { 1402 | msg.channel.send(musicbot.note('note', 'Playback finished~')); 1403 | if (musicbot.musicPresence) musicbot.updatePresence(musicbot.queues.get(msg.guild.id), msg.client, musicbot.clearPresence).catch((res) => { console.warn(`[MUSIC] Problem updating MusicPresence`); }); 1404 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1405 | if (voiceConnection !== null) return voiceConnection.disconnect(); 1406 | }; 1407 | 1408 | new Promise((resolve, reject) => { 1409 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1410 | if (voiceConnection === null) { 1411 | if (msg.member.voice.channel && msg.member.voice.channel.joinable) { 1412 | msg.member.voice.channel.join() 1413 | .then(connection => { 1414 | resolve(connection); 1415 | }) 1416 | .catch((error) => { 1417 | console.log(error); 1418 | }); 1419 | } else if (!msg.member.voice.channel.joinable || msg.member.voice.channel.full) { 1420 | msg.channel.send(musicbot.note('fail', 'I do not have permission to join your voice channel!')) 1421 | reject(); 1422 | } else { 1423 | musicbot.emptyQueue(msg.guild.id).then(() => { 1424 | reject(); 1425 | }) 1426 | } 1427 | } else { 1428 | resolve(voiceConnection); 1429 | } 1430 | }).then(connection => { 1431 | let video; 1432 | if (!queue.last) { 1433 | video = queue.songs[0]; 1434 | } else { 1435 | if (queue.loop == "queue") { 1436 | video = queue.songs.find(s => s.position == queue.last.position + 1); 1437 | if (!video || video && !video.url) video = queue.songs[0]; 1438 | } else if (queue.loop == "single") { 1439 | video = queue.last; 1440 | } else { 1441 | video = queue.songs.find(s => s.position == queue.last.position); 1442 | }; 1443 | } 1444 | if (!video) { 1445 | video = queue.songs ? queue.songs[0] : false; 1446 | if (!video) { 1447 | msg.channel.send(musicbot.note('note', 'Playback finished!')); 1448 | musicbot.emptyQueue(msg.guild.id); 1449 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1450 | if (voiceConnection !== null) return voiceConnection.disconnect(); 1451 | } 1452 | } 1453 | 1454 | if (musicbot.messageNewSong == true && queue.last && queue.loop !== "song") { 1455 | let req = client.users.cache.get(video.requester); 1456 | if (msg.channel.permissionsFor(msg.guild.me).has('EMBED_LINKS')) { 1457 | const embed = new Discord.RichEmbed() 1458 | .setTitle("Now Playing", `${req !== null ? req.displayAvatarURL() : null}`) 1459 | .setThumbnail(`https://img.youtube.com/vi/${video.id}/maxresdefault.jpg`) 1460 | .setDescription(`[${video.title.replace(/\\/g, '\\\\').replace(/\`/g, '\\`').replace(/\*/g, '\\*').replace(/_/g, '\\_').replace(/~/g, '\\~').replace(/`/g, '\\`')}](${video.url}) by [${video.channelTitle}](${video.channelURL})`) 1461 | .setColor(musicbot.embedColor) 1462 | .setFooter(`Requested by ${req !== null ? req.username : "Unknown User"}`, `${req !== null ? req.displayAvatarURL() : null}`); 1463 | msg.channel.send({embed}); 1464 | } else { 1465 | msg.channel.send(musicbot.note("note", `\`${video.title.replace(/`/g, "''")}\` by \`${video.channelURL.replace(/`/g, "''")}\``)) 1466 | } 1467 | } 1468 | 1469 | try { 1470 | musicbot.setLast(msg.guild.id, video).then(() => { 1471 | if (musicbot.musicPresence) musicbot.updatePresence(queue, msg.client, musicbot.clearPresence).catch((res) => { console.warn(`[MUSIC] Problem updating MusicPresence`); }); 1472 | }); 1473 | 1474 | let dispatcher = connection.play(ytdl(video.url, { 1475 | filter: 'audioonly', 1476 | quality: 'highestaudio' 1477 | }), { 1478 | bitrate: musicbot.bitRate, 1479 | volume: (queue.volume / 100) 1480 | }) 1481 | 1482 | connection.on('error', (error) => { 1483 | console.error(error); 1484 | if (msg && msg.channel) msg.channel.send(musicbot.note('fail', `Something went wrong with the connection. Retrying queue...`)); 1485 | musicbot.executeQueue(msg, queue); 1486 | }); 1487 | 1488 | dispatcher.on('error', (error) => { 1489 | console.error(error); 1490 | if (msg && msg.channel) msg.channel.send(musicbot.note('fail', `Something went wrong while playing music. Retrying queue...`)); 1491 | musicbot.executeQueue(msg, queue); 1492 | }); 1493 | 1494 | 1495 | dispatcher.on('debug', (d) => { 1496 | console.log(d); 1497 | }); 1498 | 1499 | dispatcher.on('end', () => { 1500 | setTimeout(() => { 1501 | if (musicbot.queues.get(queue.id).needsRefresh) { 1502 | queue = musicbot.queues.get(queue.id); 1503 | queue.needsRefresh = false; 1504 | musicbot.queues.set(queue.id, queue) 1505 | } 1506 | let loop = queue.loop; 1507 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1508 | if (voiceConnection !== null && voiceConnection.channel.members.size <= 1){ 1509 | msg.channel.send(musicbot.note('note', 'No one in the voice channel, leaving...')) 1510 | musicbot.queues.set(msg.guild.id, {songs: [], last: null, loop: "none", id: msg.guild.id, volume: musicbot.defVolume, oldSongs: [],working: false, needsRefresh: false}); 1511 | if (musicbot.musicPresence) musicbot.updatePresence(musicbot.queues.get(msg.guild.id), msg.client, musicbot.clearPresence).catch((res) => { console.warn(`[MUSIC] Problem updating MusicPresence`); }); 1512 | return voiceConnection.disconnect(); 1513 | } 1514 | if (queue.songs.length > 0) { 1515 | if (loop == "none" || loop == null) { 1516 | queue.songs.shift(); 1517 | musicbot.updatePositions(queue, msg ? msg.guild.id : 000000).then(res => { 1518 | queue.songs = typeof res.songs == "object" ? Array.from(res.songs) : []; 1519 | musicbot.executeQueue(msg, queue); 1520 | }).catch(e=> { 1521 | console.log(e) 1522 | console.log("@ dispatcher function"); 1523 | }) 1524 | } else if (loop == "queue" || loop == "song") { 1525 | musicbot.executeQueue(msg, queue); 1526 | }; 1527 | } else if (queue.songs.length <= 0) { 1528 | if (msg && msg.channel) msg.channel.send(musicbot.note('note', 'Playback finished.')); 1529 | musicbot.queues.set(msg.guild.id, {songs: [], last: null, loop: "none", id: msg.guild.id, volume: musicbot.defVolume, oldSongs: [],working: false, needsRefresh: false}); 1530 | if (musicbot.musicPresence) musicbot.updatePresence(queue, msg.client, musicbot.clearPresence).catch((res) => { console.warn(`[MUSIC] Problem updating MusicPresence`); }); 1531 | const voiceConnection = client.voice.connections.find(val => val.channel.guild.id == msg.guild.id); 1532 | if (voiceConnection !== null) return voiceConnection.disconnect(); 1533 | } 1534 | }, 1250); 1535 | }); 1536 | } catch (error) { 1537 | console.log(error); 1538 | } 1539 | }) 1540 | .catch((error) => { 1541 | console.log(error); 1542 | }); 1543 | } 1544 | 1545 | musicbot.note = (type, text) => { 1546 | if (type === 'wrap') { 1547 | let ntext = text 1548 | .replace(/`/g, '`' + String.fromCharCode(8203)) 1549 | .replace(/@/g, '@' + String.fromCharCode(8203)) 1550 | .replace(client.token, 'REMOVED'); 1551 | return '```\n' + ntext + '\n```'; 1552 | } else if (type === 'note') { 1553 | return ':musical_note: | ' + text.replace(/`/g, '`' + String.fromCharCode(8203)); 1554 | } else if (type === 'search') { 1555 | return ':mag: | ' + text.replace(/`/g, '`' + String.fromCharCode(8203)); 1556 | } else if (type === 'fail') { 1557 | return ':no_entry_sign: | ' + text.replace(/`/g, '`' + String.fromCharCode(8203)); 1558 | } else if (type === 'font') { 1559 | return text.replace(/`/g, '`' + String.fromCharCode(8203)) 1560 | .replace(/@/g, '@' + String.fromCharCode(8203)) 1561 | .replace(/\\/g, '\\\\') 1562 | .replace(/\*/g, '\\*') 1563 | .replace(/_/g, '\\_') 1564 | .replace(/~/g, '\\~') 1565 | .replace(/`/g, '\\`'); 1566 | } else { 1567 | console.error(new Error(`${type} was an invalid type`)); 1568 | } 1569 | }; 1570 | 1571 | musicbot.loadCommands = async () => { 1572 | try { 1573 | await musicbot.loadCommand(musicbot.play); 1574 | await musicbot.loadCommand(musicbot.remove); 1575 | await musicbot.loadCommand(musicbot.help); 1576 | await musicbot.loadCommand(musicbot.skip); 1577 | await musicbot.loadCommand(musicbot.leave); 1578 | await musicbot.loadCommand(musicbot.search); 1579 | await musicbot.loadCommand(musicbot.pause); 1580 | await musicbot.loadCommand(musicbot.resume); 1581 | await musicbot.loadCommand(musicbot.volume); 1582 | await musicbot.loadCommand(musicbot.queue); 1583 | await musicbot.loadCommand(musicbot.loop); 1584 | await musicbot.loadCommand(musicbot.clearqueue); 1585 | await musicbot.loadCommand(musicbot.np); 1586 | await musicbot.loadCommand(musicbot.shuffle) 1587 | await musicbot.loadCommand(musicbot.deleteQueue) 1588 | } catch (e) { 1589 | console.error(new Error(e)); 1590 | }; 1591 | } 1592 | musicbot.loadCommands(); 1593 | 1594 | try { 1595 | Object.defineProperty(Array.prototype, 'musicArraySort', {value: function(n) { 1596 | return Array.from(Array(Math.ceil(this.length/n)), (_,i)=>this.slice(i*n,i*n+n)); 1597 | }}); 1598 | Object.defineProperty(Array.prototype, 'musicBotShuffle', {value: function() { 1599 | let input = this; 1600 | for (let i = input.length - 1; i >= 0; i--) { 1601 | let randomIndex = Math.floor(Math.random() * (i + 1)); 1602 | let itemAtIndex = input[randomIndex]; 1603 | input[randomIndex] = input[i]; 1604 | input[i] = itemAtIndex; 1605 | } 1606 | return input; 1607 | }}); 1608 | } catch (e) { 1609 | throw new Error("could not defineProperty(s) musicArraySort or musicBotShuffle, you are likely running this script twice. actual error: " + e.stack) 1610 | }; 1611 | } catch (e) { 1612 | console.error(e); 1613 | }; 1614 | } 1615 | -------------------------------------------------------------------------------- /install-debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo apt-get install curl software-properties-common 3 | curl -sL https://deb.nodesource.com/setup_12.x | sudo bash - 4 | sudo apt-get install nodejs ffmpeg -y 5 | npm uninstall discord.js node-opus opusscript discord.js-musicbot-addon 6 | npm install discord.js 7 | npm install opusscript 8 | npm install discord.js-musicbot-addon 9 | echo "Done" 10 | -------------------------------------------------------------------------------- /install-opensuse.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo zypper install nodejs10 nodejs10-devl nodejs-common 3 | sudo zypper install ffmpeg 4 | npm uninstall discord.js node-opus opusscript discord.js-musicbot-addon 5 | npm install discord.js 6 | npm install opusscript 7 | npm install discord.js-musicbot-addon 8 | echo "Done" 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord.js-musicbot-addon", 3 | "version": "13.9.1", 4 | "description": "A simple Node.js based music extension/bot for Discord.js projects using YouTube.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "dependencies": { 11 | "ytdl-core": "^1.0.7", 12 | "ytpl": "^0.1.18", 13 | "ytsearcher": "^1.2.2" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/DarkoPendragon/discord.js-musicbot-addon" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/DarkoPendragon/discord.js-musicbot-addon/issues" 21 | }, 22 | "homepage": "https://github.com/DarkoPendragon/discord.js-musicbot-addon", 23 | "keywords": [ 24 | "discord", 25 | "discord.js", 26 | "music", 27 | "bot", 28 | "addon", 29 | "plugin" 30 | ], 31 | "author": "demisex", 32 | "license": "ISC" 33 | } 34 | --------------------------------------------------------------------------------