123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- // Load up the discord.js library
- const Discord = require("discord.js");
- // Load up the shell command library
- var exec = require('child_process').exec;
- // Load up the queue library
- const Queue = require('./Queue.js');
- // Define a function to execute a command
- function execute(command, callback)
- {
- exec(command, function(error, stdout, stderr)
- {
- callback(stdout);
- });
- };
- // Define a new function to search + replace a char in a string
- String.prototype.replaceAll = function(remove, replace)
- {
- var target = this;
- return target.split(remove).join(replace);
- };
- // Initialize the bot.
- const client = new Discord.Client();
- // this allows us to define a voice connection with global scope
- var connection;
- var dispatcher;
- // this is the playlist queue for music
- var playlist = new Queue();
- // Array of music classes users can call (artist, track, etc)
- const musicTypes = ['track', 'title', 'song', 'artist', 'album'];
- const musicTypesString = "track, title, song, artist, album";
- // Here we load the config.json file that contains our token and our prefix values.
- const config = require("./config.json");
- // config.token contains the bot's token
- // config.prefix contains the message prefix.
- client.on("ready", () =>
- {
- // This event will run if the bot starts, and logs in, successfully.
- console.log(`Bot has started, with ${client.users.size} users, in ${client.channels.size} channels of ${client.guilds.size} guilds.`);
-
- // Example of changing the bot's playing game to something useful. `client.user` is what the
- // docs refer to as the "ClientUser".
- client.user.setActivity("Taking Over Chicago");
- });
- client.on("guildCreate", guild =>
- {
- // This event triggers when the bot joins a guild.
- console.log(`New guild joined: ${guild.name} (id: ${guild.id}). This guild has ${guild.memberCount} members!`);
- client.user.setActivity(`Serving ${client.guilds.size} servers`);
- });
- client.on("guildDelete", guild => {
- // this event triggers when the bot is removed from a guild.
- console.log(`I have been removed from: ${guild.name} (id: ${guild.id})`);
- client.user.setActivity("Taking Over Chicago");
- });
- // This plays the next song in the queue, and logs that in the channel where it was requested.
- function play()
- {
- let nextSong = playlist.dequeue();
- dispatcher = connection.playFile(nextSong[0]);
- console.log(`Playing ${nextSong[2]}.`);
- nextSong[1].reply(`Playing ${nextSong[2]}.`);
- dispatcher.setVolume(0.2);
- dispatcher.setBitrate(96);
- dispatcher.on("end", reason =>
- {
- if (!(playlist.isEmpty()))
- {
- play();
- console.log(reason);
- }
- else
- {
- console.log("Playlist exhausted, music playback stopped.");
- }
- });
- }
- client.on("message", async message =>
- {
- // This event will run on every single message received, from any channel or DM.
-
- // Ignores bot msgs
- if (message.author.bot) return;
-
- // ignores if message isn't prefixed
- if (message.content.indexOf(config.prefix) !== 0) return;
-
- // Here we separate our "command" name, and our "arguments" for the command.
- // e.g. if we have the message "+say Is this the real life?" , we'll get the following:
- // command = say
- // args = ["Is", "this", "the", "real", "life?"]
- const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
- const command = args.shift().toLowerCase();
-
- // Let's go with a few common example commands! Feel free to delete or change those.
-
- if (command === "ping")
- {
- // Calculates ping between sending a message and editing it, giving a nice round-trip latency.
- // The second ping is an average latency between the bot and the websocket server (one-way, not round-trip)
- const m = await message.channel.send("Ping?");
- m.edit(`Pong! Latency is ${m.createdTimestamp - message.createdTimestamp}ms. API Latency is ${Math.round(client.ping)}ms`);
- }
-
- if (command === "say")
- {
- // makes the bot say something and delete the original message. As an example, it's open to anyone to use.
- // To get the "message" itself we join the `args` back into a string with spaces:
- const sayMessage = args.join(" ");
- // Then we delete the command message (sneaky, right?). The catch just ignores the error with a cute smiley thing.
- message.delete().catch(O_o=>{});
- // And we get the bot to say the thing:
- message.channel.send(sayMessage);
- }
-
- if (command === "kick") {
- // This command must be limited to mods and admins. In this example we just hardcode the role names.
- // Please read on Array.some() returns true if any element meets the passed condition (map-like)
- if (!message.member.roles.some(r=>["Administrator", "Moderator","Admin","Mod"].includes(r.name)) )
- return message.reply("Sorry, you don't have permissions to use this!");
-
- // Let's first check if we have a member and if we can kick them!
- // message.mentions.members is a collection of people that have been mentioned, as GuildMembers.
- // We can also support getting the member by ID, which would be args[0]
- let member = message.mentions.members.first() || message.guild.members.get(args[0]);
- if (!member)
- return message.reply("Please mention a valid member of this server");
- if (!member.kickable)
- return message.reply("I cannot kick this user! Do they have a higher role? Do I have kick permissions?");
-
- // slice(1) removes the first part, which here should be the user mention or ID
- // join(' ') takes all the various parts to make it a single string.
- let reason = args.slice(1).join(' ');
- if (!reason) reason = "No reason provided";
-
- // Now, time for a swift kick in the nuts!
- await member.kick(reason)
- .catch(error => message.reply(`Sorry ${message.author} I couldn't kick because of : ${error}`));
- message.reply(`${member.user.tag} has been kicked by ${message.author.tag} because: ${reason}`);
- }
-
- if (command === "ban") {
- // Most of this command is identical to kick, except that here we'll only let admins do it.
- // In the real world mods could ban too, but this is just an example, right? ;)
- if (!message.member.roles.some(r=>["Administrator"].includes(r.name)) )
- return message.reply("Sorry, you don't have permissions to use this!");
-
- let member = message.mentions.members.first();
- if (!member)
- return message.reply("Please mention a valid member of this server");
- if (!member.bannable)
- return message.reply("I cannot ban this user! Do they have a higher role? Do I have ban permissions?");
- let reason = args.slice(1).join(' ');
- if (!reason) reason = "No reason provided";
-
- await member.ban(reason)
- .catch(error => message.reply(`Sorry ${message.author} I couldn't ban because of : ${error}`));
- message.reply(`${member.user.tag} has been banned by ${message.author.tag} because: ${reason}`);
- }
-
- if (command === "join")
- {
- // This command moves the bot to the channel given as the first arg.
-
- // This is the passed channel
- const channel = args.join(' ');
- const channelVar = message.guild.channels.find("name", channel);
- // Checks if channel is valid.
- if (!(message.guild.channels.exists("name", channel) && channelVar.type === "voice"))
- return message.reply(`Sorry ${message.author}, that channel doesn't appear to exist.`);
-
- // Joins the channel
- channelVar.join().then(conn =>
- {
- connection = conn;
- console.log('Connected!');
- }).catch(console.error);
- }
- if (command === "leave")
- {
- // this command remove the bot from the channel passed
- const channel = args.join(' ');
- const channelVar = message.guild.channels.find("name", channel);
- if (!(message.guild.channels.exists("name", channel) && channelVar.type === "voice"))
- return message.reply(`Sorry ${message.author}, that channel doesn't appear to exist.`);
- channelVar.leave();
- }
-
- if (command === "addmusic") // adds songs to queue, starts playback if none already
- {
- var type = args[0];
- if (!(musicTypes.includes(type)))
- {
- return message.reply("Sorry, that is not a valid command. Please enter something from: " + musicTypesString);
- }
- if (type == 'song' || type == 'track') type = 'title'; // account for poor beets API
- args.splice(0, 1);
- const query = args.join(' '); // creates a single string of all args (the query)
- var path; // this will hold the filepaths from our query
- exec(`beet ls -p ${type}:${query} | wc -l`, function (error, stdout, stderr)
- {
- if (error)
- {
- return message.reply(`Sorry, I encountered an issue looking for that: ${error}`);
- }
- else if (stdout === '\n' || stdout === '' || stdout === undefined)
- {
- return message.reply(`There were no results that matched your search. Please give the type and name of your query (e.g. song songname, album albumname...)`);
- }
- else
- {
- exec(`beet ls -p ${type}:${query}`, function (error, stdout, stderr)
- {
- if (error)
- {
- return message.reply(`Sorry, I encountered an issue looking for that: ${error}`);
- }
- else
- {
- path = stdout.trim();
- path = path.split("\n"); // now an array of paths (with spaces)
-
- // for each song, get the path and readable info, send to queue
- for (var i = 0; i < path.length; i++)
- {
- let filepathRaw = path[i];
- path[i] = path[i].replaceAll(" ", "\\ ");
- path[i] = path[i].replaceAll("'", "\\'");
- path[i] = path[i].replaceAll("&", "\\\46");
- path[i] = path[i].replaceAll("(", "\\(");
- path[i] = path[i].replaceAll(")", "\\)");
- let filepath = path[i]; // path[i] descoped in callback
- exec(`beet ls ${path[i]}`, function (error, stdouts, stderr)
- {
- if (error)
- {
- return message.reply(`Sorry, I encountered an issue looking for song ${i}: ${error}`);
- }
- else
- {
- stdouts = stdouts.trim();
- playlist.enqueue([filepathRaw, message, stdouts]);
-
- // check if music is playing, if not start it
- if ((dispatcher === undefined || dispatcher.destroyed == true) && !(playlist.isEmpty()))
- {
- play();
- }
- }
- });
- }
- }
- });
- }
- let amt = stdout.trim();
- message.reply(`${amt} songs added!`);
- });
- }
-
- if (command === 'stop') // clears playlist, stops music
- {
- playlist.reset();
- dispatcher.end();
- console.log("Playback stopped, playlist cleared.")
- }
-
- if (command === 'next') // returns next song in playlist, or informs that there is none
- {
- if (playlist.isEmpty())
- {
- message.reply("The playlist is empty.");
- }
- else
- {
- const next = playlist.peek();
- message.reply(`Next song is: ${next[2]}.`);
- }
- }
-
- if (command === 'pause') // pauses the dispatcher if playing, or does nothing
- {
- dispatcher.pause();
- message.reply("Playback paused.");
- }
-
- if (command === 'resume') // resumes the dispatcher, or does nothing
- {
- dispatcher.resume();
- message.reply("Playback resumed.");
- }
-
- if (command === 'skip') // starts playing the next song in the queue if it exists
- {
- if (playlist.isEmpty())
- {
- message.reply("Sorry, the playlist is empty.");
- }
- else
- {
- function resolveEnd()
- {
- return new Promise((success, fail) =>
- {
- dispatcher.end();
- dispatcher.on("end", () =>
- {
- success('Track skipped!');
- });
- dispatcher.on("error", () =>
- {
- fail('Couldn\'t skip :(');
- });
- });
- }
- resolveEnd();
- }
- }
- });
- client.login(config.token);
|