123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- // 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');
- // Initialize the bot.
- const client = new Discord.Client();
- // this allows us to define a voice connection with global scope
- var connection;
- var dispatcher;
- // playlist-related globals
- var playlist = new Queue();
- var played = []
- var cursong;
- var repeatall = false;
- var repeatone = false;
- // 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");
- // 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);
- };
- // This plays the next song in the queue, and logs that in the channel where it was requested.
- function play()
- {
- var nextSong = cursong;
-
- // if we aren't repeating cursong, dequeue
- if (!repeatone && cursong)
- {
- played.push(cursong);
- nextSong = playlist.dequeue();
- }
- // if we set repeat, but had no songs played yet
- // we should dequeue
- else if (!nextSong)
- {
- nextSong = playlist.dequeue();
- }
- dispatcher = connection.play(nextSong[0]);
- console.log(`Playing ${nextSong[2]}.`);
- nextSong[1].channel.send(`Playing ${nextSong[2]}.`);
- dispatcher.setVolume(0.2);
- dispatcher.setBitrate(96);
- cursong = nextSong;
- var endHandler = function endHandler(reason)
- {
- if (!(playlist.isEmpty()))
- {
- play();
- }
- else
- {
- if (repeatall)
- {
- playlist.makeFilled(played);
- played = [];
- play();
- console.log("Repeat all encountered.");
- }
- else
- {
- console.log("Playlist exhausted, music playback stopped.");
- }
- }
- }
- // what to do if it ends
- dispatcher.on("finish", endHandler);
- //dispatcher.on("close", endHandler);
- }
- // riven stuff
- // Load up the request library
- var Request = require("request");
- var unrolledStats = new Map();
- var rolledStats = new Map();
- var cacheTime = 0;
- function updateRivens()
- {
- Request.get("http://n9e5v4d8.ssl.hwcdn.net/repos/weeklyRivensPC.json", (error, response, body) =>
- {
- if (error) return console.dir(error);
- var rivenArr = JSON.parse(body);
- for (var i = 0; i < rivenArr.length; i++)
- {
- var info = Object.assign({}, rivenArr[i]);
- delete info.itemType;
- delete info.compatibility;
- delete info.rerolled;
- // veiled rivens
- if (!(rivenArr[i].compatibility))
- {
- // set value in map to info
- unrolledStats.set(rivenArr[i].itemType.toUpperCase(), info);
- }
- else // weapon-specific, so check if rolled or unrolled
- {
- if (rivenArr[i].rerolled === true)
- {
- rolledStats.set(rivenArr[i].compatibility, info);
- }
- else
- {
- unrolledStats.set(rivenArr[i].compatibility, info);
- }
- }
- }
- });
- }
- updateRivens();
- cacheTime = new Date().getTime() / 1000;
- client.on("ready", () =>
- {
- // This event will run if the bot starts, and logs in, successfully.
- console.log(`Bot has started, with ${client.users.cache.size} users, in ${client.channels.cache.size} channels of ${client.guilds.cache.size} guilds.`);
- console.log(`users: ${Array.from(client.users.cache.values()).map(each => each.username)}`);
- // 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.cache.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");
- });
- client.on('message', async msg =>
- {
- // Ignores bot msgs
- if (msg.author.bot) return;
-
- // ignores if message isn't prefixed
- if (msg.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 = msg.content.slice(config.prefix.length).trim().split(/ +/g);
- const command = args.shift().toLowerCase();
- switch (command)
- {
- // respond w/ bot latency
- case 'ping':
- var m = await msg.channel.send('pong');
- m.edit(`Pong! Latency is ${m.createdTimestamp - msg.createdTimestamp}ms. API Latency is ${Math.round(client.ws.ping)}ms`);
- break;
- // join the specified void channel
- case 'join':
- var channelName = args.join(' ');
- var channel = msg.guild.channels.cache.find(each => each.name === channelName && each.type === "voice");
- if (channel)
- {
- channel.join().then(conn =>
- {
- connection = conn;
- }).catch(console.error);
- }
- else
- {
- msg.reply(`Sorry ${msg.author.username}, that channel doesn't appear to exist.`);
- }
- break;
- // leave the specified voice channel
- case 'leave':
- var channelName = args.join(' ');
- var channel = msg.guild.channels.cache.find(each => each.name === channelName && each.type === "voice");
- if (channel)
- {
- channel.leave();
- }
- else
- {
- msg.reply(`Sorry ${msg.author.username}, that channel doesn't appear to exist.`);
- }
- break;
- // return the price of a riven for the specified weapon
- case 'price':
- // parse args
- var type = args[0];
- args.splice(0, 1);
- var query = args.join(' ');
- query = query.toUpperCase();
- // check cache freshness
- delta = (new Date().getTime() / 1000) - cacheTime;
- if (delta > 3600) updateRivens();
- if (type == "rolled")
- {
- var result = rolledStats.get(query);
- if (!(result))
- {
- return msg.channel.send("Sorry, I couldn't find that weapon. Please check your message and try again.");
- }
- result = JSON.stringify(result, undefined, 2);
- return msg.channel.send(result);
- }
- else if (type == "unrolled")
- {
- var result = unrolledStats.get(query);
- if (!(result))
- {
- return msg.channel.send("Sorry, I couldn't find that weapon. Please check your message and try again.");
- }
- result = JSON.stringify(result, undefined, 2);
- return msg.channel.send(result);
- }
- else
- {
- return msg.channel.send("Sorry, please enter a command in the form: price unrolled/rolled [weapon_name]");
- }
- break;
- // searches the DB for songs matching the user query, and responds w/ that info in chat
- case 'search':
- if (!(config.whitelist.includes(msg.author.tag)))
- {
- return msg.channel.send("Sorry, you're not allowed to run this command. Please contact the server owner to acquire that permission.")
- }
- var type = args[0];
- if (!(musicTypes.includes(type)))
- {
- return msg.channel.send("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);
- var 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 ${type}:${query} | wc -l`, function (error, stdout, stderr)
- {
- if (error)
- {
- return msg.channel.send(`Sorry, I encountered an issue looking for that: ${error}`);
- }
- else if (stdout === '\n' || stdout === '' || stdout === undefined)
- {
- return msg.channel.send('There were no results that matched your search. Please give the type and name of your query (e.g. song songname, album albumname...)');
- }
- else
- {
- var result = 'Results:\n';
- result += stdout.trim();
- msg.channel.send(result);
- }
- });
- break;
- // add the songs returned by the user query to the playlist, start if nothing playing
- case 'addmusic':
- if (!(config.whitelist.includes(msg.author.tag)))
- {
- return msg.channel.send("Sorry, you're not allowed to run this command. Please contact the server owner to acquire that permission.")
- }
- if (!connection)
- {
- return msg.channel.send("Please add me to a voice channel before adding music.")
- }
- var type = args[0];
- if (!(musicTypes.includes(type)))
- {
- return msg.channel.send("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);
- 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 msg.channel.send(`Sorry, I encountered an issue looking for that: ${error}`);
- }
- else if (stdout === '\n' || stdout === '' || stdout === undefined)
- {
- return msg.channel.send(`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 msg.channel.send(`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 msg.channel.send(`Sorry, I encountered an issue looking for song ${i}: ${error}`);
- }
- else
- {
- stdouts = stdouts.trim();
- playlist.enqueue([filepathRaw, msg, stdouts]);
-
- // check if music is playing, if not start it
- if ((!dispatcher || dispatcher.ended || dispatcher.destroyed || dispatcher.writableFinished || dispatcher.writableEnded) && !(playlist.isEmpty()))
- {
- play();
- }
- }
- });
- }
- }
- });
- }
- let amt = stdout.trim();
- msg.channel.send(`${amt} songs added!`);
- });
- break;
- // stops playback
- case 'stop':
- playlist.reset();
- repeatone = false;
- repeatall = false;
- played = [];
- dispatcher.end();
- console.log("Playback stopped, playlist cleared.");
- msg.channel.send("Playback stopped, playlist cleared.");
- break;
- // returns the next song in the playlist
- case 'next':
- if (playlist.isEmpty())
- {
- msg.channel.send("The playlist is empty.");
- }
- else
- {
- var next = playlist.peek();
- msg.channel.send(`Next song is: ${next[2]}.`);
- }
- break;
- // returns the last song played before the current one
- case 'previous':
- if (played.length <= 1)
- {
- msg.channel.send("No previous song.");
- }
- else
- {
- let temp = played.slice(-1).pop();
- msg.channel.send(`Previous song was: ${temp[2]}`);
- }
- break;
- // returns the playlist
- case 'playlist':
- if (playlist.isEmpty())
- {
- msg.channel.send("The playlist is empty.");
- }
- else
- {
- var list = playlist.read();
- var retstr = ""
-
- for (var i = 0; i < list.length; i++)
- {
- retstr += `Song #${i + 1} is: ${list[i][2]}.\n`;
- }
- msg.channel.send(retstr);
- }
- break;
- // pauses the playback
- case 'pause':
- dispatcher.pause(true);
- msg.channel.send("Playback paused.");
- break;
- // resumes the playback
- case 'resume':
- dispatcher.resume();
- msg.channel.send("Playback resumed.");
- break;
- // sets repeat behaviour
- case 'repeat':
- var param = args[0];
- if (param === 'one' && cursong)
- {
- repeatone = true; // causes play function to repeat current cursong
- repeatall = false;
- msg.channel.send(`Repeating ${cursong[2]}.`);
- }
- else if (param === 'all') // track playlist, and repeat whole thing once empty
- {
- repeatone = false;
- repeatall = true;
- msg.channel.send("Repeating playlist.");
- }
- else if (param === 'off') // resets repeat variables
- {
- repeatone = false;
- repeatall = false;
- msg.channel.send("Repeat off.");
- }
- else
- {
- msg.channel.send("There was nothing to repeat, or an invalid option was given. Valid options are one, all, and off.");
- }
- break;
- // skips to the next song
- case 'skip':
- if (playlist.isEmpty())
- {
- msg.channel.send("Sorry, the playlist is empty.");
- }
- else
- {
- function resolveEnd()
- {
- return new Promise((success, fail) =>
- {
- dispatcher.end();
-
- dispatcher.on("finish", () =>
- {
- success('Track skipped!');
- });
-
- dispatcher.on("error", () =>
- {
- fail('Couldn\'t skip :(');
- });
- });
- }
- resolveEnd();
- }
- break;
- // goes back 1 song (plays last song, adds cursong back to the queue)
- case 'back':
- if (played.length == 0)
- {
- msg.channel.send("Sorry, there is no song to skip back to.");
- }
- else
- {
- function resolveEnd()
- {
- return new Promise((success, fail) =>
- {
- playlist.insert(cursong, 0); // put cursong back on the front
- let tempsong = played[played.length - 1]; // captures the song to go back to
- played = played.splice(played.length - 1, 1); // removes the last song from played
- playlist.insert(tempsong, 0); // put old song on the front
- dispatcher.end(); // stop playing wrong song
- dispatcher.on("finish", () =>
- {
- success('Track reversed!');
- });
-
- dispatcher.on("error", () =>
- {
- fail('Couldn\'t skip :(');
- });
- });
- }
- resolveEnd();
- }
- break;
- // respond w/ error if command not recognized
- default:
- msg.channel.send("Sorry, that command isn't recognized.");
- }
- });
- client.login(config.token);
|