<commit_before>/*The file to interface with the beam API */

//run 'npm i -S beam-client-node' to get these two things
const BeamClient = require('beam-client-node');
const BeamSocket = require('beam-client-node/lib/ws');
const spawn = require('child_process').spawn;

var config;
var configPath;
try{
  config = require('../'+process.argv[2]);
  configPath = process.argv[2];
}catch(e){
  console.log(e);
  config = require('../config.json');
  configPath = "config.json";
}

const debug = config.debug;
var moderationModule = config.moderationOn;
const commandModule = config.commandsOn;

var deathblossomMode = false;

let userInfo;
let channelId = -1;

const client = new BeamClient();

//OAuth gives permision for all following actions
client.use('oauth',{
  tokens:{
    access: config.OAuth,
    expires: Date.now() + (365 * 24 *60 *60 *1000)
  },
});

//get following channel and join its chat
client.request('GET', 'users/current')
.then(response => {
    if(debug) console.log(response.body);
    userInfo = response.body;
    return client.request('GET','channels/'+config.channelUsername);
})
.then(response =>{
  if(debug){
    console.log(response.body);
    return client.chat.join(userInfo.channel.id);
  }else{
    if(response.body){
      channelId = response.body.id;
      console.log("Joining",response.body.name);
      return client.chat.join(channelId);
    }else{
      return client.chat.join(userInfo.channel.id);
    }
  }
})
.then(response => {
    const body = response.body;
    if(debug) console.log(body);
    if(!body.roles.includes('Mod') && !body.roles.includes('Owner')){
      console.log("Cannot moderate this chat");
      moderationModule = false;
    }
    if(!debug && channelId !== -1){
      return createChatSocket(userInfo.id, channelId, body.endpoints, body.authkey);
    }else{
      return createChatSocket(userInfo.id, userInfo.channel.id, body.endpoints, body.authkey);
    }
})
.catch(error => {
      console.log("Something went wrong:", error);
});

/**
 * Creates a beam chat socket and sets up listeners to various chat events.
 * @param {number} userId The user to authenticate as
 * @param {number} channelId The channel id to join
 * @param {any} endpoints An endpoints array from a beam.chat.join call.
 * @param {any} authkey An authentication key from a beam.chat.join call.
 * @returns {Promise.<>}
 */
function createChatSocket (userId, channelId, endpoints, authkey) {
    const socket = new BeamSocket(endpoints).boot();

    socket.auth(channelId, userId, authkey)
    .then(() => {
        if(debug) console.log('You are now authenticated!');
        // Send a chat message
        console.log("Joined chatroom");
        if(config.joinMessage){
          return socket.call('msg', ['BOOTING UP...']);
        }
    })
    .catch(error => {
        console.log('Oh no! An error occurred!', error);
    });

    // Listen to chat messages, note that you will also receive your own!
    socket.on('ChatMessage', data => {
        if(debug) console.log('We got a ChatMessage packet!');
        if(debug) console.log(data);
        if(debug) console.log(data.message); // lets take a closer look
        var roles = data.user_roles;
        log(socket,data,roles);
        if(!roles.includes('Mod') && !roles.includes('Owner')){
          if(!deathblossomMode && moderationModule){
            moderate(socket,data,roles);
          }else if(deathblossomMode){
            deathblossom(socket,data,roles);
          }
        }
        if(commandModule){
          commands(socket,data,roles);
        }
    });

    // Listen to socket errors, you'll need to handle these!
    socket.on('error', error => {
        console.error('Socket error', error);
    });
}

function moderate(socket,messageData,roles){
  //Prepare data before spawning thread
  if(debug) console.log("MODERATE: In moderate function");
  var msgArr = messageToString(messageData.message.message);
  var role = parseRoles(roles);
  var msg = msgArr[0];
  var hasLink = msgArr[1]; //TODO make link handler
  var toPython = messageData.user_name + " " + role + " " + msg;
  if(debug) console.log("\tMODERATE: Input to python:", toPython);

  if(hasLink){
    //This is how to send the data to be processed by the python
    var linkHandler = spawn('python3', ['modules/ganon.py', configPath]);

    linkHandler.stdin.write(messageData.user_name + " " + role);
    linkHandler.stdin.end();

    linkHandler.stdout.on('data', function(data){
      var pythonOut = data.toString().trim();
      if(debug) console.log("\tLINKHANDLER: From python:",pythonOut);
      var action = pythonOut.split(" ")[0];
      var user = pythonOut.split(" ")[1];
      var indexOfSpace1 = pythonOut.indexOf(" ");
      var indexOfSpace2 = pythonOut.indexOf(" ",indexOfSpace1+1);
      var response = pythonOut.substr(indexOfSpace2);
      if(indexOfSpace2 === -1){
        response = "";
      }
      if(debug){
        console.log("\tLINKHANDLER: User is:", user);
        console.log("\tLINKHANDLER: Action to take:",action);
        console.log("\tLINKHANDLER: Response is: ","@"+user+": "+response);
      }
      //Parse the actions and take action if needed
      if(action === "timeout"){
        timeout(socket,user,response,msg);
      }else if(action === "ban"){
        ban(socket,user,response,msg);
      }else if(action === "purge"){
        purge(socket,user,response,msg);
      }else if(action === "nothing"){
        if(debug){console.log("\tLINKHANDLER:No action to take");}
        //THIS SPACE INTENTIONALLY LEFT BLANK
      }
    });

    linkHandler.stdout.on('end', function(){
      if(debug) console.log("MODERATE: Finished moderate parse");
    });

  }else{
    //This is how to send the data to be processed by the python
    var moderator = spawn('python3', ['modules/hammer.py', configPath]);

    moderator.stdin.write(toPython);
    moderator.stdin.end();

    moderator.stdout.on('data', function(data){
      var pythonOut = data.toString().trim();
      if(debug) console.log("\tMODERATE: From python:",pythonOut);
      var action = pythonOut.split(" ")[0];
      var user = pythonOut.split(" ")[1];
      var indexOfSpace1 = pythonOut.indexOf(" ");
      var indexOfSpace2 = pythonOut.indexOf(" ",indexOfSpace1+1);
      var response = pythonOut.substr(indexOfSpace2);
      if(indexOfSpace2 === -1){
        response = "";
      }
      if(debug){
        console.log("\tMODERATE: User is:", user);
        console.log("\tMODERATE: Action to take:",action);
        console.log("\tMODERATE: Response is: ","@"+user+": "+response);
      }
      //Parse the actions and take action if needed
      if(action === "timeout"){
        timeout(socket,user,response,msg);
      }else if(action === "ban"){
        ban(socket,user,response,msg);
      }else if(action === "purge"){
        purge(socket,user,response,msg);
      }else if(action === "nothing"){
        if(debug){console.log("\tMODERATE:No action to take");}
        //THIS SPACE INTENTIONALLY LEFT BLANK
      }
    });

    moderator.stdout.on('end', function(){
      if(debug) console.log("MODERATE: Finished moderate parse");
    });
  }
}

function ban(socket,user,response,msg){ //TODO check that the call completed successfully
  if(debug){
    console.log("Need to ban",user);
  }else{
    socket.call('timeout',[user,config.banDuration]); //need to timeout for 1 to unban
    if(!config.silentBans && response !== null) socket.call('msg',["@"+user+": "+response]);
    console.log("[BAN]",user,"Message:",msg);
  }
}

function purge(socket,user,response,msg){ //TODO check that the call completed successfully
  if(debug){
    console.log("Need to purge",user);
  }else{
    socket.call('purge',[user]);
    if(!config.silentBans && response !== null) socket.call('msg',["@"+user+": "+response]);
    console.log("[PURGE]",user,"Message:",msg);
  }
}

//To unban, use a duration of 1 with this function
function timeout(socket,user,response,msg,duration){
  //TODO check that the call completed successfully
  if(debug){
    console.log("Need to timeout",user);
  }else{
    if(duration){
      socket.call('timeout',[user,duration]);
    }else{
      socket.call('timeout',[user,config.timeoutDuration]);
    }
    if(!config.silentBans && response !== null) socket.call('msg',["@"+user+": "+response]);
    console.log("[TIMEOUT]",user,"Message:",msg);
  }
}

function unban(socket,user){
  console.log("[UNBANNED]",user);
  timeout(socket,user,"You have been unbanned", "<TO UNBAN>",1);
}

function messageToString(array){
  var result = "";
  var hasLink = false;
  for(var index in array){
    var element = array[index];
    result += element.text;
    if(element.type === "link"){
      hasLink = true;
    }
  }
  if(debug) console.log("\tMESSAGE2STRING: messageToString is:", result);
  return [result, hasLink];
}

function commands(socket,messageData,roles){
  if(debug) console.log("COMMANDS: In commands function");
  var msg = messageToString(messageData.message.message)[0];
  var role = "Normal";
  var username = messageData.user_name;

  //Send command to python script
  if(msg.charAt(0) != '!'){
    return;
  }else{
    role = parseRoles(roles);
    var handleCmds = spawn('python3', ['modules/centurion.py', configPath]);
    var toPython = username + " " + role + " " + msg;
    if(debug) console.log("\tCOMMANDS: Input to python:", toPython);
    handleCmds.stdin.write(toPython);
    handleCmds.stdin.end();

    handleCmds.stdout.on('data', function(data){
      var pythonOut = data.toString().trim();
      if(debug) console.log("\tCOMMANDS: From python:",pythonOut);
      var action = pythonOut.split(" ")[0];
      var user = pythonOut.split(" ")[1];
      var indexOfSpace1 = pythonOut.indexOf(" ");
      var indexOfSpace2 = pythonOut.indexOf(" ",indexOfSpace1+1);
      var response = pythonOut.substr(indexOfSpace2);
      if(indexOfSpace2 === -1){
        response = "";
      }
      if(debug){
        console.log("\tCOMMANDS: User is:", user);
        console.log("\tCOMMANDS: Action to take:",action);
        console.log("\tCOMMANDS: Response is: ","@"+user+": "+response);
      }
      //Parse the actions and take action if needed
      if(action === "timeout"){
        timeout(socket,user,response,msg);
      }else if(action === "ban"){
        ban(socket,user,response,msg);
      }else if(action === "purge"){
        purge(socket,user,response,msg);
      }else if(action === "respond"){
        socket.call('msg', [response]);
      }else if(action === "unban"){
        unban(socket,user);
      }else if(action === "deathblossom"){
        if(!deathblossomMode){
          console.log("[DEATHBLOSSOM ON]");
          socket.call('msg', ["Initializing the DeathBlossom..."]);
          socket.call('msg', ["Now Banning on sight. Lurking in the shadows is recommended"]);
          deathblossomMode = true;
        }else{
          console.log("[DEATHBLOSSOM OFF]");
          socket.call('msg', ["DeathBlossom Deactivated."]);
          deathblossomMode = false;
        }
      }else if(action === "nothing"){
        if(debug){console.log("\tCOMMANDS: No action to take");}
        //THIS SPACE INTENTIONALLY LEFT BLANK
      }
    });

    handleCmds.stdout.on('end', function(){
      if(debug) console.log("COMMANDS: Finished command parse");
    });
  }

}

function parseRoles(roleArr){
  var result = "Normal";
  for(var index in roleArr){
    var role = roleArr[index];
    //TODO parse the remaining Mixer roles'
    if(role === "Owner"){
      result = "Caster";
    }else if(role === "Mod" && result !== "Caster"){
      result = "Mod";
    }else if(role == "User" && result !== "Caster" && result !== "Mod"){
      result = "Normal";
    }
  }
  return result;
}

function log(socket,messageData,roles,action){
  if(debug) console.log("LOG: In logs function");
  var msg = messageToString(messageData.message.message)[0];
  var role = parseRoles(roles);
  var username = messageData.user_name;
  if(action){
    //TODO for the moderation/other actions
  }else{
    console.log("[CHAT] "+username + ": "+msg); //TODO add timestamp? or only in log?
  }
  //TODO connect with python module
}

function deathblossom(socket, messageData, roles){
  var role = parseRoles(roles);
  var username = messageData.user_name;
  timeout(socket, username, null, null, 86400); //Time out for one day

}
<commit_msg>fixed config paths<commit_after>/*The file to interface with the beam API */

//run 'npm i -S beam-client-node' to get these two things
const BeamClient = require('beam-client-node');
const BeamSocket = require('beam-client-node/lib/ws');
const spawn = require('child_process').spawn;

var config;
var configPath;
try{
  config = require('./'+process.argv[2]);
  configPath = process.argv[2];
}catch(e){
  console.log(e);
  config = require('./config.json');
  configPath = "config.json";
}

const debug = config.debug;
var moderationModule = config.moderationOn;
const commandModule = config.commandsOn;

var deathblossomMode = false;

let userInfo;
let channelId = -1;

const client = new BeamClient();

//OAuth gives permision for all following actions
client.use('oauth',{
  tokens:{
    access: config.OAuth,
    expires: Date.now() + (365 * 24 *60 *60 *1000)
  },
});

//get following channel and join its chat
client.request('GET', 'users/current')
.then(response => {
    if(debug) console.log(response.body);
    userInfo = response.body;
    return client.request('GET','channels/'+config.channelUsername);
})
.then(response =>{
  if(debug){
    console.log(response.body);
    return client.chat.join(userInfo.channel.id);
  }else{
    if(response.body){
      channelId = response.body.id;
      console.log("Joining",response.body.name);
      return client.chat.join(channelId);
    }else{
      return client.chat.join(userInfo.channel.id);
    }
  }
})
.then(response => {
    const body = response.body;
    if(debug) console.log(body);
    if(!body.roles.includes('Mod') && !body.roles.includes('Owner')){
      console.log("Cannot moderate this chat");
      moderationModule = false;
    }
    if(!debug && channelId !== -1){
      return createChatSocket(userInfo.id, channelId, body.endpoints, body.authkey);
    }else{
      return createChatSocket(userInfo.id, userInfo.channel.id, body.endpoints, body.authkey);
    }
})
.catch(error => {
      console.log("Something went wrong:", error);
});

/**
 * Creates a beam chat socket and sets up listeners to various chat events.
 * @param {number} userId The user to authenticate as
 * @param {number} channelId The channel id to join
 * @param {any} endpoints An endpoints array from a beam.chat.join call.
 * @param {any} authkey An authentication key from a beam.chat.join call.
 * @returns {Promise.<>}
 */
function createChatSocket (userId, channelId, endpoints, authkey) {
    const socket = new BeamSocket(endpoints).boot();

    socket.auth(channelId, userId, authkey)
    .then(() => {
        if(debug) console.log('You are now authenticated!');
        // Send a chat message
        console.log("Joined chatroom");
        if(config.joinMessage){
          return socket.call('msg', ['BOOTING UP...']);
        }
    })
    .catch(error => {
        console.log('Oh no! An error occurred!', error);
    });

    // Listen to chat messages, note that you will also receive your own!
    socket.on('ChatMessage', data => {
        if(debug) console.log('We got a ChatMessage packet!');
        if(debug) console.log(data);
        if(debug) console.log(data.message); // lets take a closer look
        var roles = data.user_roles;
        log(socket,data,roles);
        if(!roles.includes('Mod') && !roles.includes('Owner')){
          if(!deathblossomMode && moderationModule){
            moderate(socket,data,roles);
          }else if(deathblossomMode){
            deathblossom(socket,data,roles);
          }
        }
        if(commandModule){
          commands(socket,data,roles);
        }
    });

    // Listen to socket errors, you'll need to handle these!
    socket.on('error', error => {
        console.error('Socket error', error);
    });
}

function moderate(socket,messageData,roles){
  //Prepare data before spawning thread
  if(debug) console.log("MODERATE: In moderate function");
  var msgArr = messageToString(messageData.message.message);
  var role = parseRoles(roles);
  var msg = msgArr[0];
  var hasLink = msgArr[1]; //TODO make link handler
  var toPython = messageData.user_name + " " + role + " " + msg;
  if(debug) console.log("\tMODERATE: Input to python:", toPython);

  if(hasLink){
    //This is how to send the data to be processed by the python
    var linkHandler = spawn('python3', ['modules/ganon.py', configPath]);

    linkHandler.stdin.write(messageData.user_name + " " + role);
    linkHandler.stdin.end();

    linkHandler.stdout.on('data', function(data){
      var pythonOut = data.toString().trim();
      if(debug) console.log("\tLINKHANDLER: From python:",pythonOut);
      var action = pythonOut.split(" ")[0];
      var user = pythonOut.split(" ")[1];
      var indexOfSpace1 = pythonOut.indexOf(" ");
      var indexOfSpace2 = pythonOut.indexOf(" ",indexOfSpace1+1);
      var response = pythonOut.substr(indexOfSpace2);
      if(indexOfSpace2 === -1){
        response = "";
      }
      if(debug){
        console.log("\tLINKHANDLER: User is:", user);
        console.log("\tLINKHANDLER: Action to take:",action);
        console.log("\tLINKHANDLER: Response is: ","@"+user+": "+response);
      }
      //Parse the actions and take action if needed
      if(action === "timeout"){
        timeout(socket,user,response,msg);
      }else if(action === "ban"){
        ban(socket,user,response,msg);
      }else if(action === "purge"){
        purge(socket,user,response,msg);
      }else if(action === "nothing"){
        if(debug){console.log("\tLINKHANDLER:No action to take");}
        //THIS SPACE INTENTIONALLY LEFT BLANK
      }
    });

    linkHandler.stdout.on('end', function(){
      if(debug) console.log("MODERATE: Finished moderate parse");
    });

  }else{
    //This is how to send the data to be processed by the python
    var moderator = spawn('python3', ['modules/hammer.py', configPath]);

    moderator.stdin.write(toPython);
    moderator.stdin.end();

    moderator.stdout.on('data', function(data){
      var pythonOut = data.toString().trim();
      if(debug) console.log("\tMODERATE: From python:",pythonOut);
      var action = pythonOut.split(" ")[0];
      var user = pythonOut.split(" ")[1];
      var indexOfSpace1 = pythonOut.indexOf(" ");
      var indexOfSpace2 = pythonOut.indexOf(" ",indexOfSpace1+1);
      var response = pythonOut.substr(indexOfSpace2);
      if(indexOfSpace2 === -1){
        response = "";
      }
      if(debug){
        console.log("\tMODERATE: User is:", user);
        console.log("\tMODERATE: Action to take:",action);
        console.log("\tMODERATE: Response is: ","@"+user+": "+response);
      }
      //Parse the actions and take action if needed
      if(action === "timeout"){
        timeout(socket,user,response,msg);
      }else if(action === "ban"){
        ban(socket,user,response,msg);
      }else if(action === "purge"){
        purge(socket,user,response,msg);
      }else if(action === "nothing"){
        if(debug){console.log("\tMODERATE:No action to take");}
        //THIS SPACE INTENTIONALLY LEFT BLANK
      }
    });

    moderator.stdout.on('end', function(){
      if(debug) console.log("MODERATE: Finished moderate parse");
    });
  }
}

function ban(socket,user,response,msg){ //TODO check that the call completed successfully
  if(debug){
    console.log("Need to ban",user);
  }else{
    socket.call('timeout',[user,config.banDuration]); //need to timeout for 1 to unban
    if(!config.silentBans && response !== null) socket.call('msg',["@"+user+": "+response]);
    console.log("[BAN]",user,"Message:",msg);
  }
}

function purge(socket,user,response,msg){ //TODO check that the call completed successfully
  if(debug){
    console.log("Need to purge",user);
  }else{
    socket.call('purge',[user]);
    if(!config.silentBans && response !== null) socket.call('msg',["@"+user+": "+response]);
    console.log("[PURGE]",user,"Message:",msg);
  }
}

//To unban, use a duration of 1 with this function
function timeout(socket,user,response,msg,duration){
  //TODO check that the call completed successfully
  if(debug){
    console.log("Need to timeout",user);
  }else{
    if(duration){
      socket.call('timeout',[user,duration]);
    }else{
      socket.call('timeout',[user,config.timeoutDuration]);
    }
    if(!config.silentBans && response !== null) socket.call('msg',["@"+user+": "+response]);
    console.log("[TIMEOUT]",user,"Message:",msg);
  }
}

function unban(socket,user){
  console.log("[UNBANNED]",user);
  timeout(socket,user,"You have been unbanned", "<TO UNBAN>",1);
}

function messageToString(array){
  var result = "";
  var hasLink = false;
  for(var index in array){
    var element = array[index];
    result += element.text;
    if(element.type === "link"){
      hasLink = true;
    }
  }
  if(debug) console.log("\tMESSAGE2STRING: messageToString is:", result);
  return [result, hasLink];
}

function commands(socket,messageData,roles){
  if(debug) console.log("COMMANDS: In commands function");
  var msg = messageToString(messageData.message.message)[0];
  var role = "Normal";
  var username = messageData.user_name;

  //Send command to python script
  if(msg.charAt(0) != '!'){
    return;
  }else{
    role = parseRoles(roles);
    var handleCmds = spawn('python3', ['modules/centurion.py', configPath]);
    var toPython = username + " " + role + " " + msg;
    if(debug) console.log("\tCOMMANDS: Input to python:", toPython);
    handleCmds.stdin.write(toPython);
    handleCmds.stdin.end();

    handleCmds.stdout.on('data', function(data){
      var pythonOut = data.toString().trim();
      if(debug) console.log("\tCOMMANDS: From python:",pythonOut);
      var action = pythonOut.split(" ")[0];
      var user = pythonOut.split(" ")[1];
      var indexOfSpace1 = pythonOut.indexOf(" ");
      var indexOfSpace2 = pythonOut.indexOf(" ",indexOfSpace1+1);
      var response = pythonOut.substr(indexOfSpace2);
      if(indexOfSpace2 === -1){
        response = "";
      }
      if(debug){
        console.log("\tCOMMANDS: User is:", user);
        console.log("\tCOMMANDS: Action to take:",action);
        console.log("\tCOMMANDS: Response is: ","@"+user+": "+response);
      }
      //Parse the actions and take action if needed
      if(action === "timeout"){
        timeout(socket,user,response,msg);
      }else if(action === "ban"){
        ban(socket,user,response,msg);
      }else if(action === "purge"){
        purge(socket,user,response,msg);
      }else if(action === "respond"){
        socket.call('msg', [response]);
      }else if(action === "unban"){
        unban(socket,user);
      }else if(action === "deathblossom"){
        if(!deathblossomMode){
          console.log("[DEATHBLOSSOM ON]");
          socket.call('msg', ["Initializing the DeathBlossom..."]);
          socket.call('msg', ["Now Banning on sight. Lurking in the shadows is recommended"]);
          deathblossomMode = true;
        }else{
          console.log("[DEATHBLOSSOM OFF]");
          socket.call('msg', ["DeathBlossom Deactivated."]);
          deathblossomMode = false;
        }
      }else if(action === "nothing"){
        if(debug){console.log("\tCOMMANDS: No action to take");}
        //THIS SPACE INTENTIONALLY LEFT BLANK
      }
    });

    handleCmds.stdout.on('end', function(){
      if(debug) console.log("COMMANDS: Finished command parse");
    });
  }

}

function parseRoles(roleArr){
  var result = "Normal";
  for(var index in roleArr){
    var role = roleArr[index];
    //TODO parse the remaining Mixer roles'
    if(role === "Owner"){
      result = "Caster";
    }else if(role === "Mod" && result !== "Caster"){
      result = "Mod";
    }else if(role == "User" && result !== "Caster" && result !== "Mod"){
      result = "Normal";
    }
  }
  return result;
}

function log(socket,messageData,roles,action){
  if(debug) console.log("LOG: In logs function");
  var msg = messageToString(messageData.message.message)[0];
  var role = parseRoles(roles);
  var username = messageData.user_name;
  if(action){
    //TODO for the moderation/other actions
  }else{
    console.log("[CHAT] "+username + ": "+msg); //TODO add timestamp? or only in log?
  }
  //TODO connect with python module
}

function deathblossom(socket, messageData, roles){
  var role = parseRoles(roles);
  var username = messageData.user_name;
  timeout(socket, username, null, null, 86400); //Time out for one day

}
<|endoftext|>


<commit_before>/*
 * Copyright 2013 Alexander Ivanov
 * Licensed under the MIT License
 */

/*
 *	TODO
 *
 *	Carosuels
 *		Fader
 *	Paralax
 *	Transformables
 *	Test in FF/IE/O/S/Mobile
 */

(function(window){
	var $ = window.jQuery;
	var document = window.document;
	var instance = false;

	var getImages = function() {
		var images = [];
		var path = window.location.href.split('/');
		path.pop();
		path = path.join('/') + '/';

		$('link[rel=stylesheet]').each(function(){
			var path = this.href.split('/');
			path.pop();
			path = path.join('/') + '/';

			$.ajax({
				async: false,
				dataType: 'text',
				url: this.href,
				success: function( data ){
					images = images.concat( extract( data, path ) );
				}
			});
		});
		$('style').each(function(){
			images = images.concat( extract( $(this).html(), path ) );
		});
		$('img').each(function(){
			images = unique( images, this.src );
		});

		return images;
	}

	var create = function( structure ) {
		var element = document.createDocumentFragment();

		for( var key in structure ) {
			var tmp = document.createElement( key );

			for( var attr in structure[key] ) {
				if( attr == 'children' ) {
					tmp.appendChild( create( structure[key][attr] ) );
				} else if( attr == 'content') {
					$(tmp).text( structure[key][attr] );
				} else {
					tmp.setAttribute( attr, structure[key][attr] );
				}
			}

			element.appendChild( tmp );
		}

		return element;
	}

	var unique = function( arr, value ) {
		for( var key in arr ) {
			if( arr[key] == value ) return arr;
		}
		arr.push( value );
		return arr;
	}

	var extract = function( data, path ) {
		var matches = data.match(/url\(.+?\)/g);
		var arr = [];

		for( var key in matches ) {
			var url = matches[key].replace( /(^url\(|\)$|['"])/g, '' );

			if( url.substring( 0, 1 ) != '/' && url.substring( 0, 5 ) != 'http:' && url.substring( 0, 6 ) != 'https:' ) url = path + url;

			arr = unique( arr, url );
		}
		return arr;
	}

	var Present = function( screens, options ) {
		this.screens = screens;
		this.shuttle = screens.parent();
		this.options = $.extend( this.options, options );
		this.moving = false;
		this.currentPage = window.location.hash;

		if( this.options.loadingScreen ) {
			this.initProgress();
		} else {
			this.onload();
		}

		if( this.options.pageTransitions && this.options.pageLinks.length>1 ) {
			this.initTransitions();
		}

		if( this.options.carosuel.length ) {
			this.initCarosuel();
		}

		return this;
	}

	Present.prototype.options = {
		loadingScreen: true,
		loadingStructure: {div:{id:'loading',children:{div:{id:'loading_bar'}}}},
		loadingProgressBar: '#loading_bar',
		loadingContainer: '#loading',

		paging: true,
		pageTransitions: 'snap',
		pageLinks: $(),
		pageLinksClass: 'active',

		screenClass: 'active',

		scrolling: true,

		carosuel: [],
		carosuelDefaultEffect: 'slide',

		onpage: function(){},
		onpagebefore: function(){},
		onpageafter: function(){},
		onload: function(){}
	};

	Present.prototype.transitions = {};

	Present.prototype.transitions.snap = {
		init: function( self ) {
			document.body.style.overflow = 'hidden';
			self.shuttle.css({position: 'relative'});
		},
		transition: function( page, callback ) {
			var index = this.screens.index(page);

			this.shuttle.animate({
				top: (-index*100).toString()+'%'
			}, callback );
		}
	}

	Present.prototype.transitions.deck = {
		init: function( self ) {
			document.body.style.overflow = 'hidden';
			self.shuttle.css({position: 'absolute', top: '0px', left: '0px', width: '100%', height: '100%', zIndex: 0});
			self.screens.css({position: 'absolute', top: '0px', left: '0px', zIndex: 1});
		},
		transition: function( page, callback ) {
			var self = this;
			page.css({top: '100%',zIndex: 3});
			page.animate({
				top: '0px'
			}, function(){
				self.screens.not(page).css({zIndex: 1});
				page.css({zIndex: 2});

				callback();
			});
		}
	}

	Present.prototype.effects = {};

	Present.prototype.effects.slide = {
		init: function( config, self ) {
			config.options = $.extend( this.defaults, config.options );
			config.effect = this;
			config.index = 0;

			config.container.css({overflow: 'hidden'});

			config.width = config.container.width();
			config.height = config.container.height();

			config.shuttle = $(config.container[0].appendChild( document.createElement('div') ));
			config.elements.each(function(){
				if( config.pagingStructure ) {
					config.pagingContainer[0].appendChild( create(config.pagingStructure) );
					config.paging = config.pagingContainer.children();
				}

				config.shuttle[0].appendChild( config.container[0].removeChild( this ));
				var $this = $(this);

				if( config.options.direction == 'horizontal' ) {
					$this.css({float: 'left'});
				}

				$this.css({
					width: config.width,
					height: config.height
				});
			});

			if( config.options.direction == 'horizontal' ) {
				config.shuttle.css({
					height: '100%',
					width: (100*config.elements.length).toString()+'%'
				});
			} else {
				config.shuttle.css({
					width: '100%',
					height: (100*config.elements.length).toString()+'%'
				});
			}

			if( config.paging ) {
				config.paging.click(function(){
					var index = config.paging.index( this );
					config.effect.transition( index, config, self );
				});
			}

			config.left.click(function(){
				config.effect.transition( config.index-1, config, self )
			});
			config.right.click(function(){
				config.effect.transition( config.index+1, config, self )
			});

			if( config.options.autoplay ) {
				this.setTimeout( config );
			}

			if( config.options.waitOnMouse ) {
				config.container.mouseover(function(){
					config.effect.clearTimeout( config );
				});
				config.container.mouseout(function(){
					config.effect.setTimeout( config, self );
				});
			}
		},
		defaults: {
			interval: 2000,
			duration: 500,
			autoplay: true,
			waitOnMouse: true,
			direction: 'horizontal',
		},
		transition: function( index, config, self ) {
			if( config.playing ) return;
			config.playing = true;

			this.clearTimeout( config );

			if( index === false ) index = config.index + 1;

			if( index < 0 ) index = config.elements.length-1;
			else if( index >= config.elements.length ) index = 0;

			config.index = index;

			var animation = config.options.direction == 'horizontal' ? {marginLeft: (-config.width*index).toString()+'px'} : {marginTop: (-config.height*index).toString()+'px'};
			config.shuttle.animate( animation, config.options.duration, function(){
				config.playing = false;

				config.effect.setTimeout( config, self );
			} );
		},
		clearTimeout: function( config ) {
			if( config.timeout ) {
				clearTimeout( config.timeout );
				config.timeout = false;
			}
		},
		setTimeout: function( config, self ) {
			if( config.options.autoplay ) {
				config.timeout = setTimeout( function(){
					config.effect.transition( config.index + 1, config, self );
				}, config.options.interval );
			}
		}
	}

	Present.prototype.goToPage = function( page ) {
		if ( this.moving ) return;
		var self = this;

		var index = this.getPageIndex( page );

		if( index !== false ) {
			this.onpagebefore();

			var screen = this.screens.find('a[name='+page.replace('#','')+']').parent();
			this.moving = true;
			this.currentPage = page;

			this.onpage();

			this.transition( screen, function(){
				window.location.hash = page;
				self.moving = false;

				self.onpageafter();
			});
		}
	}

	Present.prototype.goPageUp = function() {
		var index = this.getPageIndex( this.currentPage ) - 1;

		if( index >= 0 ) this.goToPage( this.pages[index] );
	}

	Present.prototype.goPageDown = function() {
		var index = this.getPageIndex( this.currentPage ) + 1;

		if( index < this.pages.length ) this.goToPage( this.pages[index] );
	}

	Present.prototype.getPageIndex = function ( page ) {
		for( var index in this.pages ) {
			if( page == this.pages[index] ) return parseInt(index);
		}

		return false;
	}

	Present.prototype.initProgress = function() {
		var progressScreen = create( this.options.loadingStructure );
		document.body.appendChild( progressScreen );
		this.progressBar = $( this.options.loadingProgressBar );

		this.preloadImages();
	}

	Present.prototype.hideProgress = function() {
		$( this.options.loadingContainer ).fadeOut();
		this.onload();
	}

	Present.prototype.setProgress = function( progress ) {
		var percent = (progress*100).toString();
		var self = this;

		this.progressBar.stop()
						.animate({
							width: percent+'%'
						}, function() {
							if( progress == 1 ) {
								self.hideProgress();
							}
						}
					);
	}

	Present.prototype.preloadImages = function() {
		var images = getImages();

		var totalImages = images.length;
		var loadedImages = 0;

		if( !totalImages ) {
			this.setProgress( 1 );
		}

		for( var index in images ) {
			$('<img>').on('error load', function(){
				++loadedImages;

				self.setProgress( loadedImages/totalImages );
			}).attr('src', images[index]);
		}
	}

	Present.prototype.initTransitions = function() {
		this.transitions[ this.options.pageTransitions ].init( this );
		this.transition = this.transitions[ this.options.pageTransitions ].transition;

		this.initPages();

		if( this.options.scrolling ) {
			this.bindScroll();
		}

		if( this.options.paging ) {
			this.initPaging();
		}
	}

	Present.prototype.initPages = function() {
		var self = this;
		this.pages = [];

		this.options.pageLinks.each(function(){
			var href = this.getAttribute('href');
			self.pages.push( href );

			if( self.currentPage == '' ) self.currentPage = href;
		});
	}

	Present.prototype.initPaging = function() {
		var self = this;

		this.screens.find('a[name]').css({position:'absolute'});

		this.goToPage( this.currentPage );

		this.options.pageLinks.click(function() {
			self.goToPage( this.getAttribute('href') );

			return false;
		});

		if ( 'onhashchange' in window ) {
			window.onhashchange = function() {
				if( !self.moving ) self.goToPage( window.location.hash );
			}
		}
	}

	Present.prototype.initCarosuel = function() {
		for( var index in this.options.carosuel ) {
			var effect = this.options.carosuel[index].effect ? this.options.carosuel[index].effect : this.options.carosuelDefaultEffect;

			this.effects[ effect ].init( this.options.carosuel[index] , this );
		}
	}

	Present.prototype.bindScroll = function() {
		var self = this;

		$(document).bind('mousewheel DOMMouseScroll', function(event) {
			event.preventDefault();
			var direction = event.originalEvent.wheelDelta || -event.originalEvent.detail;
			if( direction < 0 ) {
				self.goPageDown();
			} else {
				self.goPageUp();
			}
		});
	}

	Present.prototype.getLink = function( page ) {
		return this.options.pageLinks.filter('[href='+page+'],[href=#'+page+']');
	}

	Present.prototype.getCurrentLink = function() {
		return this.getLink( this.currentPage );
	}

	Present.prototype.getScreen = function( page ) {
		return this.screens.find('a[name='+page.replace('#','')+']').parent();
	}

	Present.prototype.getCurrentScreen = function() {
		return this.getScreen( this.currentPage );
	}

	Present.prototype.onpage = function() {
		if ( this.options.paging ) {
			if( this.options.pageLinksClass ) {
				this.options.pageLinks.removeClass( this.options.pageLinksClass );
				this.options.pageLinks.filter('[href='+this.currentPage+']').addClass( this.options.pageLinksClass );
			}
			if( this.options.screenClass ) {
				this.screens.removeClass( this.options.screenClass );
				this.screens.find('a[name='+this.currentPage.replace('#', '')+']').parent().addClass( this.options.screenClass );
			}
		}

		this.options.onpage( this.currentPage );
	}

	Present.prototype.onload = function() {
		this.options.onload();
	}

	Present.prototype.onpagebefore = function() {
		this.options.onpagebefore( this.currentPage );
	}

	Present.prototype.onpageafter = function() {
		this.options.onpageafter( this.currentPage );
	}

	$.fn.Present = function( options ) {
		if( instance && window.console ) {
			window.console.warn('Present.js was already instantiated!');
		} else if( !instance ) {
			instance = new Present( this, options );
		}

		return instance;
	}

})(window);<commit_msg>Added option to ommit carosuel elements<commit_after>/*
 * Copyright 2013 Alexander Ivanov
 * Licensed under the MIT License
 */

/*
 *	TODO
 *
 *	Carosuels
 *		Fader
 *	Paralax
 *	Transformables
 *	Test in FF/IE/O/S/Mobile
 */

(function(window){
	var $ = window.jQuery;
	var document = window.document;
	var instance = false;

	var getImages = function() {
		var images = [];
		var path = window.location.href.split('/');
		path.pop();
		path = path.join('/') + '/';

		$('link[rel=stylesheet]').each(function(){
			var path = this.href.split('/');
			path.pop();
			path = path.join('/') + '/';

			$.ajax({
				async: false,
				dataType: 'text',
				url: this.href,
				success: function( data ){
					images = images.concat( extract( data, path ) );
				}
			});
		});
		$('style').each(function(){
			images = images.concat( extract( $(this).html(), path ) );
		});
		$('img').each(function(){
			images = unique( images, this.src );
		});

		return images;
	}

	var create = function( structure ) {
		var element = document.createDocumentFragment();

		for( var key in structure ) {
			var tmp = document.createElement( key );

			for( var attr in structure[key] ) {
				if( attr == 'children' ) {
					tmp.appendChild( create( structure[key][attr] ) );
				} else if( attr == 'content') {
					$(tmp).text( structure[key][attr] );
				} else {
					tmp.setAttribute( attr, structure[key][attr] );
				}
			}

			element.appendChild( tmp );
		}

		return element;
	}

	var unique = function( arr, value ) {
		for( var key in arr ) {
			if( arr[key] == value ) return arr;
		}
		arr.push( value );
		return arr;
	}

	var extract = function( data, path ) {
		var matches = data.match(/url\(.+?\)/g);
		var arr = [];

		for( var key in matches ) {
			var url = matches[key].replace( /(^url\(|\)$|['"])/g, '' );

			if( url.substring( 0, 1 ) != '/' && url.substring( 0, 5 ) != 'http:' && url.substring( 0, 6 ) != 'https:' ) url = path + url;

			arr = unique( arr, url );
		}
		return arr;
	}

	var Present = function( screens, options ) {
		this.screens = screens;
		this.shuttle = screens.parent();
		this.options = $.extend( this.options, options );
		this.moving = false;
		this.currentPage = window.location.hash;

		if( this.options.loadingScreen ) {
			this.initProgress();
		} else {
			this.onload();
		}

		if( this.options.pageTransitions && this.options.pageLinks.length>1 ) {
			this.initTransitions();
		}

		if( this.options.carosuel.length ) {
			this.initCarosuel();
		}

		return this;
	}

	Present.prototype.options = {
		loadingScreen: true,
		loadingStructure: {div:{id:'loading',children:{div:{id:'loading_bar'}}}},
		loadingProgressBar: '#loading_bar',
		loadingContainer: '#loading',

		paging: true,
		pageTransitions: 'snap',
		pageLinks: $(),
		pageLinksClass: 'active',

		screenClass: 'active',

		scrolling: true,

		carosuel: [],
		carosuelDefaultEffect: 'slide',

		onpage: function(){},
		onpagebefore: function(){},
		onpageafter: function(){},
		onload: function(){}
	};

	Present.prototype.transitions = {};

	Present.prototype.transitions.snap = {
		init: function( self ) {
			document.body.style.overflow = 'hidden';
			self.shuttle.css({position: 'relative'});
		},
		transition: function( page, callback ) {
			var index = this.screens.index(page);

			this.shuttle.animate({
				top: (-index*100).toString()+'%'
			}, callback );
		}
	}

	Present.prototype.transitions.deck = {
		init: function( self ) {
			document.body.style.overflow = 'hidden';
			self.shuttle.css({position: 'absolute', top: '0px', left: '0px', width: '100%', height: '100%', zIndex: 0});
			self.screens.css({position: 'absolute', top: '0px', left: '0px', zIndex: 1});
		},
		transition: function( page, callback ) {
			var self = this;
			page.css({top: '100%',zIndex: 3});
			page.animate({
				top: '0px'
			}, function(){
				self.screens.not(page).css({zIndex: 1});
				page.css({zIndex: 2});

				callback();
			});
		}
	}

	Present.prototype.effects = {};

	Present.prototype.effects.slide = {
		init: function( config, self ) {
			config.options = $.extend( this.defaults, config.options );
			config.effect = this;
			config.index = 0;

			config.container.css({overflow: 'hidden'});

			config.width = config.container.width();
			config.height = config.container.height();

			if( !config.elements ) {
				config.elements = $(config.container).children();
			}

			config.shuttle = $(config.container[0].appendChild( document.createElement('div') ));

			config.elements.each(function(){
				if( config.pagingStructure ) {
					config.pagingContainer[0].appendChild( create(config.pagingStructure) );
					config.paging = config.pagingContainer.children();
				}

				config.shuttle[0].appendChild( config.container[0].removeChild( this ));
				var $this = $(this);

				if( config.options.direction == 'horizontal' ) {
					$this.css({float: 'left'});
				}

				$this.css({
					width: config.width,
					height: config.height
				});
			});

			if( config.options.direction == 'horizontal' ) {
				config.shuttle.css({
					height: '100%',
					width: (100*config.elements.length).toString()+'%'
				});
			} else {
				config.shuttle.css({
					width: '100%',
					height: (100*config.elements.length).toString()+'%'
				});
			}

			if( config.paging ) {
				config.paging.click(function(){
					var index = config.paging.index( this );
					config.effect.transition( index, config, self );
				});
			}

			config.left.click(function(){
				config.effect.transition( config.index-1, config, self )
			});
			config.right.click(function(){
				config.effect.transition( config.index+1, config, self )
			});

			if( config.options.autoplay ) {
				this.setTimeout( config );
			}

			if( config.options.waitOnMouse ) {
				config.container.mouseover(function(){
					config.effect.clearTimeout( config );
				});
				config.container.mouseout(function(){
					config.effect.setTimeout( config, self );
				});
			}
		},
		defaults: {
			interval: 2000,
			duration: 500,
			autoplay: true,
			waitOnMouse: true,
			direction: 'horizontal',
		},
		transition: function( index, config, self ) {
			if( config.playing ) return;
			config.playing = true;

			this.clearTimeout( config );

			if( index === false ) index = config.index + 1;

			if( index < 0 ) index = config.elements.length-1;
			else if( index >= config.elements.length ) index = 0;

			config.index = index;

			var animation = config.options.direction == 'horizontal' ? {marginLeft: (-config.width*index).toString()+'px'} : {marginTop: (-config.height*index).toString()+'px'};
			config.shuttle.animate( animation, config.options.duration, function(){
				config.playing = false;

				config.effect.setTimeout( config, self );
			} );
		},
		clearTimeout: function( config ) {
			if( config.timeout ) {
				clearTimeout( config.timeout );
				config.timeout = false;
			}
		},
		setTimeout: function( config, self ) {
			if( config.options.autoplay ) {
				config.timeout = setTimeout( function(){
					config.effect.transition( config.index + 1, config, self );
				}, config.options.interval );
			}
		}
	}

	Present.prototype.goToPage = function( page ) {
		if ( this.moving ) return;
		var self = this;

		var index = this.getPageIndex( page );

		if( index !== false ) {
			this.onpagebefore();

			var screen = this.screens.find('a[name='+page.replace('#','')+']').parent();
			this.moving = true;
			this.currentPage = page;

			this.onpage();

			this.transition( screen, function(){
				window.location.hash = page;
				self.moving = false;

				self.onpageafter();
			});
		}
	}

	Present.prototype.goPageUp = function() {
		var index = this.getPageIndex( this.currentPage ) - 1;

		if( index >= 0 ) this.goToPage( this.pages[index] );
	}

	Present.prototype.goPageDown = function() {
		var index = this.getPageIndex( this.currentPage ) + 1;

		if( index < this.pages.length ) this.goToPage( this.pages[index] );
	}

	Present.prototype.getPageIndex = function ( page ) {
		for( var index in this.pages ) {
			if( page == this.pages[index] ) return parseInt(index);
		}

		return false;
	}

	Present.prototype.initProgress = function() {
		var progressScreen = create( this.options.loadingStructure );
		document.body.appendChild( progressScreen );
		this.progressBar = $( this.options.loadingProgressBar );

		this.preloadImages();
	}

	Present.prototype.hideProgress = function() {
		$( this.options.loadingContainer ).fadeOut();
		this.onload();
	}

	Present.prototype.setProgress = function( progress ) {
		var percent = (progress*100).toString();
		var self = this;

		this.progressBar.stop()
						.animate({
							width: percent+'%'
						}, function() {
							if( progress == 1 ) {
								self.hideProgress();
							}
						}
					);
	}

	Present.prototype.preloadImages = function() {
		var images = getImages();

		var totalImages = images.length;
		var loadedImages = 0;

		if( !totalImages ) {
			this.setProgress( 1 );
		}

		for( var index in images ) {
			$('<img>').on('error load', function(){
				++loadedImages;

				self.setProgress( loadedImages/totalImages );
			}).attr('src', images[index]);
		}
	}

	Present.prototype.initTransitions = function() {
		this.transitions[ this.options.pageTransitions ].init( this );
		this.transition = this.transitions[ this.options.pageTransitions ].transition;

		this.initPages();

		if( this.options.scrolling ) {
			this.bindScroll();
		}

		if( this.options.paging ) {
			this.initPaging();
		}
	}

	Present.prototype.initPages = function() {
		var self = this;
		this.pages = [];

		this.options.pageLinks.each(function(){
			var href = this.getAttribute('href');
			self.pages.push( href );

			if( self.currentPage == '' ) self.currentPage = href;
		});
	}

	Present.prototype.initPaging = function() {
		var self = this;

		this.screens.find('a[name]').css({position:'absolute'});

		this.goToPage( this.currentPage );

		this.options.pageLinks.click(function() {
			self.goToPage( this.getAttribute('href') );

			return false;
		});

		if ( 'onhashchange' in window ) {
			window.onhashchange = function() {
				if( !self.moving ) self.goToPage( window.location.hash );
			}
		}
	}

	Present.prototype.initCarosuel = function() {
		for( var index in this.options.carosuel ) {
			var effect = this.options.carosuel[index].effect ? this.options.carosuel[index].effect : this.options.carosuelDefaultEffect;

			this.effects[ effect ].init( this.options.carosuel[index] , this );
		}
	}

	Present.prototype.bindScroll = function() {
		var self = this;

		$(document).bind('mousewheel DOMMouseScroll', function(event) {
			event.preventDefault();
			var direction = event.originalEvent.wheelDelta || -event.originalEvent.detail;
			if( direction < 0 ) {
				self.goPageDown();
			} else {
				self.goPageUp();
			}
		});
	}

	Present.prototype.getLink = function( page ) {
		return this.options.pageLinks.filter('[href='+page+'],[href=#'+page+']');
	}

	Present.prototype.getCurrentLink = function() {
		return this.getLink( this.currentPage );
	}

	Present.prototype.getScreen = function( page ) {
		return this.screens.find('a[name='+page.replace('#','')+']').parent();
	}

	Present.prototype.getCurrentScreen = function() {
		return this.getScreen( this.currentPage );
	}

	Present.prototype.onpage = function() {
		if ( this.options.paging ) {
			if( this.options.pageLinksClass ) {
				this.options.pageLinks.removeClass( this.options.pageLinksClass );
				this.options.pageLinks.filter('[href='+this.currentPage+']').addClass( this.options.pageLinksClass );
			}
			if( this.options.screenClass ) {
				this.screens.removeClass( this.options.screenClass );
				this.screens.find('a[name='+this.currentPage.replace('#', '')+']').parent().addClass( this.options.screenClass );
			}
		}

		this.options.onpage( this.currentPage );
	}

	Present.prototype.onload = function() {
		this.options.onload();
	}

	Present.prototype.onpagebefore = function() {
		this.options.onpagebefore( this.currentPage );
	}

	Present.prototype.onpageafter = function() {
		this.options.onpageafter( this.currentPage );
	}

	$.fn.Present = function( options ) {
		if( instance && window.console ) {
			window.console.warn('Present.js was already instantiated!');
		} else if( !instance ) {
			instance = new Present( this, options );
		}

		return instance;
	}

})(window);<|endoftext|>


<commit_before><commit_msg>edit process<commit_after><|endoftext|>


<commit_before>var Discord = require('discord.js');

var ytdl = require('ytdl-core');
var request = require('request');
var url = require('url');

// Output version information in console
var git = require('git-rev');

git.short(commit => git.branch(branch => {
  console.log(`Lethe#${branch}@${commit}`);
}));

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.js');
var YoutubeStream = require('./lib/youtube-stream.js');
var Saved = require('./lib/saved.js');
Saved.read();

var Config = require('./lib/config.js');

var client = new Discord.Client();

// Handle discord.js warnings
client.on('warn', (m) => console.log('[warn]', m));
client.on('debug', (m) => console.log('[debug]', m));

var playQueue = [];
var boundChannel = false;
var currentStream = false;
var currentVideo = false;

var botMention = false;

// Handling api key
if (process.argv[4]) {
  var apiKey = process.argv[4];
} else {
  var apiKey = false;
}

client.on('ready', () => {
  botMention = `<@${client.user.id}>`;
  console.log(`Bot mention: ${botMention}`);
});

client.on('message', m => {
  if (!botMention) return;

  if (m.content.startsWith(`${botMention} info`)) {
    git.short(commit => git.branch(branch => {
      client.reply(m, `Version: \`Lethe#${branch}@${commit}\`. Info about Lethe can be found at https://github.com/meew0/Lethe.`);
    }));
    return;
  }

  if (m.content.startsWith(`${botMention} i`)) { // init
    if (boundChannel) return;
    var channelToJoin = spliceArguments(m.content)[1];
    for (var channel of m.channel.server.channels) {
      if (channel instanceof Discord.VoiceChannel) {
        if (!channelToJoin || channel.name === channelToJoin) {
          boundChannel = m.channel;
          client.reply(m, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``);
          client.joinVoiceChannel(channel).catch(error);
          break;
        }
      }
    }
  }

  if (m.content.startsWith(`${botMention} d`)) { // destroy
    if (!boundChannel) return;
    client.reply(m, `Unbinding from <#${boundChannel.id}> and destroying voice connection`);
    playQueue = [];
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    currentStream = false;
    currentVideo = false;
    return;
  }

  // Only respond to other messages inside the bound channel
  if (!m.channel.equals(boundChannel)) return;

  if (m.content.startsWith(`${botMention} n`)) { // next
    playStopped();
  }

  if (m.content.startsWith(`${botMention} yq`) // youtube query
    || m.content.startsWith(`${botMention} qq`) // queue query
    || m.content.startsWith(`${botMention} pq`) // play query
    || m.content.startsWith(`${botMention} ytq`)) {

    if (apiKey == false) {
      client.reply(m, 'Search is disabled (no API KEY found).');
      return;
    }

    var args = spliceArguments(m.content)[1];

    if (!args) {
      client.reply(m, 'You need to specify a search parameter.');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/search' +
      `?part=snippet&q=${escape(args)}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'Your query gave 0 results.');
          return;
        }

        var vid = body.items[0].id.videoId;
        getInfoAndQueue(vid, m);
      } else {
        client.reply(m, 'There was an error searching.');
        return;
      }
    });

    return; // have to stop propagation
  }

  if (m.content.startsWith(`${botMention} pl`)) { // playlist
    if (apiKey == false) {
      client.reply(m, 'Playlist adding is disabled (no API KEY found).');
      return;
    }

    var pid = spliceArguments(m.content)[1];

    if (!pid) {
      client.reply(m, 'You need to specify a playlist ID!');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems' +
      `?part=contentDetails&maxResults=50&playlistId=${pid}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'That playlist has no videos.');
          return;
        }

        client.reply(m, `Loading ${body.items.length} videos...`);
        var suppress = 0;
        body.items.forEach((elem, idx) => {
          var vid = elem.contentDetails.videoId;
          if (idx == 1) suppress = body.items.length - 2;
          if (idx == 2) suppress = -1;
          getInfoAndQueue(vid, m, suppress);
        });
      } else {
        client.reply(m, 'There was an error finding playlist with that id.');
        return;
      }
    });

    return;
  }

  if (m.content.startsWith(`${botMention} y`) // youtube
    || m.content.startsWith(`${botMention} q`) // queue
    || m.content.startsWith(`${botMention} p`)) { // play

    var vidList = spliceArguments(m.content)[1];

    var vids = vidList.split(',');
    var suppress = 0;
    vids.forEach((vid, idx) => {
      if (idx == 1) suppress = vids.length - 2;
      if (idx == 2) suppress = -1;
      parseVidAndQueue(vid, m, suppress);
    });
  }

  if (m.content.startsWith(`${botMention} r`)) { // replay
    playQueue.push(currentVideo);
    client.reply(m, `Queued ${VideoFormat.prettyPrint(currentVideo)}`);
  }

  if (m.content.startsWith(`${botMention} sh`)) { // shuffle
    if (playQueue.length < 2) {
      client.reply(m, 'Not enough songs in the queue.');
      return;
    } else {
      shuffle(playQueue);
      client.reply(m, 'Songs in the queue have been shuffled.');
    }

    return;
  }

  if (m.content.startsWith(`${botMention} link`)) {
    if (currentVideo) client.reply(m, `<${currentVideo.loaderUrl}>`);
    return; // stop propagation
  }

  if (m.content.startsWith(`${botMention} list s`)) { // list saved
    var formattedList = 'Here are the videos currently saved: \n';
    for (var key in Saved.saved.videos) {
      if (Saved.saved.videos.hasOwnProperty(key)) {
        formattedList += `*${key}*: ${VideoFormat.prettyPrint(Saved.saved.videos[key])}\n`;
      }
    }

    client.reply(m, formattedList);
    return; // so list doesn't get triggered
  }

  if (m.content.startsWith(`${botMention} l`)) { // list
    var formattedList = 'Here are the videos currently in the play queue, from first added to last added: \n';
    formattedList += `Currently playing: ${VideoFormat.prettyPrintWithUser(currentVideo)}\n`;

    var shouldBreak = false;

    playQueue.forEach((video, idx) => {
      if (shouldBreak) return;

      var formattedVideo = `${idx + 1}. ${VideoFormat.prettyPrintWithUser(video)}\n`;

      if ((formattedList.length + formattedVideo.length) > 1950) {
        formattedList += `... and ${playQueue.length - idx} more`;
        shouldBreak = true;
      } else {
        formattedList += formattedVideo;
      }
    });
    client.reply(m, formattedList);
  }

  if (m.content.startsWith(`${botMention} s`)) { // save
    var argument = spliceArguments(m.content)[1];
    if (!argument) {
      client.reply(m, 'You need to specify a video and a keyword!');
      return;
    }

    var splitArgs = spliceArguments(argument, 1);

    var vid = splitArgs[0];

    if (vid === 'current') {
      if (currentVideo) vid = currentVideo.vid;
      else {
        client.reply(m, `Couldn't retrieve video information of current video!`);
      }
    }

    var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else saveVideo(info, vid, splitArgs[1], m);
    });
  }

  if (m.content.startsWith(`${botMention} t`)) { // time
    var streamTime = client.internal.voiceConnection.streamTime; // in ms
    var videoTime = currentVideo.length_seconds;
    client.reply(m, `${VideoFormat.prettyTime(streamTime)} / ${VideoFormat.prettyTime(videoTime * 1000)} (${(streamTime / (videoTime * 10)).toFixed(2)} %)`);
  }
});

function parseVidAndQueue(vid, m, suppress) {
  if (/^http/.test(vid)) {
    if (url.parse(vid, true).query.v) {
      vid = url.parse(vid, true).query.v;
    }
  }

  vid = Saved.possiblyRetrieveVideo(vid);
  if (!vid) {
    client.reply(m, 'You need to specify a video!');
    return;
  }

  getInfoAndQueue(vid, m, suppress);
}

function getInfoAndQueue(vid, m, suppress) {
  requestUrl = 'http://www.youtube.com/watch?v=' + vid;
  ytdl.getInfo(requestUrl, (err, info) => {
    if (err) handleYTError(err);
    else {
      info.vid = vid;
      possiblyQueue(info, m.author.id, m, suppress);
    }
  });
}

function spliceArguments(message, after) {
  after = after || 2;
  var rest = message.split(' ');
  var removed = rest.splice(0, after);
  return [removed.join(' '), rest.join(' ')];
}

function saveVideo(video, vid, keywords, m) {
  simplified = VideoFormat.simplify(video, vid);
  if (Saved.saved.videos.hasOwnProperty(keywords)) client.reply(m, `Warning: ${VideoFormat.simplePrint(Saved.saved.videos[keywords])} is already saved as *${keywords}*! Overwriting.`);

  var key;
  if (key = Saved.isVideoSaved(vid)) client.reply(m, `Warning: This video is already saved as *${key}*! Adding it anyway as *${keywords}*.`);

  Saved.saved.videos[keywords] = simplified;
  client.reply(m, `Saved video ${VideoFormat.prettyPrint(video)} as *${keywords}*`);
  Saved.write();
}

function possiblyQueue(video, userId, m, suppress) {
  video.userId = userId;
  suppress = (suppress === undefined) ? false : suppress;
  reason = shouldDisallowQueue(playQueue, video);
  if (reason) {
    client.reply(m, `You can't queue this video right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    if (suppress == 0) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);
    else if (suppress > -1) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)} and ${suppress} other videos`);

    // Start playing if not playing yet
    if (!currentVideo) nextInQueue();
  }
}

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    boundChannel.sendMessage('This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    boundChannel.sendMessage('An error occurred while getting video information! Please try a different video.');
  }

  console.log(err.toString());
}

function playStopped() {
  if (client.internal.voiceConnection) client.internal.voiceConnection.stopPlaying();

  boundChannel.sendMessage(`Finished playing ${VideoFormat.simplePrint(currentVideo)}`);
  currentVideo = false;
  nextInQueue();
}

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = YoutubeStream.getStream(video.loaderUrl);

    currentStream.on('error', (err) => {
      boundChannel.sendMessage(`There was an error during playback! **${err}**`);
    });

    currentStream.on('end', () => setTimeout(playStopped, Config.timeOffset)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      boundChannel.sendMessage(`Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

function nextInQueue() {
  if (playQueue.length > 0) {
    next = playQueue.shift();
    play(next);
  }
}

function shuffle(array) {
  var counter = array.length;
  var temp;
  var index;

  // While there are elements in the array
  while (counter > 0) {
    index = Math.floor(Math.random() * counter);

    counter--;

    temp = array[counter];
    array[counter] = array[index];
    array[index] = temp;
  }

  return array;
}

function error(argument) {
  console.log(argument.stack);
}

// Email and password over command line
client.login(process.argv[2], process.argv[3]).catch((e) => console.log(e));
<commit_msg>Add a default value in case there's none in the config<commit_after>var Discord = require('discord.js');

var ytdl = require('ytdl-core');
var request = require('request');
var url = require('url');

// Output version information in console
var git = require('git-rev');

git.short(commit => git.branch(branch => {
  console.log(`Lethe#${branch}@${commit}`);
}));

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.js');
var YoutubeStream = require('./lib/youtube-stream.js');
var Saved = require('./lib/saved.js');
Saved.read();

var Config = require('./lib/config.js');

var client = new Discord.Client();

// Handle discord.js warnings
client.on('warn', (m) => console.log('[warn]', m));
client.on('debug', (m) => console.log('[debug]', m));

var playQueue = [];
var boundChannel = false;
var currentStream = false;
var currentVideo = false;

var botMention = false;

// Handling api key
if (process.argv[4]) {
  var apiKey = process.argv[4];
} else {
  var apiKey = false;
}

client.on('ready', () => {
  botMention = `<@${client.user.id}>`;
  console.log(`Bot mention: ${botMention}`);
});

client.on('message', m => {
  if (!botMention) return;

  if (m.content.startsWith(`${botMention} info`)) {
    git.short(commit => git.branch(branch => {
      client.reply(m, `Version: \`Lethe#${branch}@${commit}\`. Info about Lethe can be found at https://github.com/meew0/Lethe.`);
    }));
    return;
  }

  if (m.content.startsWith(`${botMention} i`)) { // init
    if (boundChannel) return;
    var channelToJoin = spliceArguments(m.content)[1];
    for (var channel of m.channel.server.channels) {
      if (channel instanceof Discord.VoiceChannel) {
        if (!channelToJoin || channel.name === channelToJoin) {
          boundChannel = m.channel;
          client.reply(m, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``);
          client.joinVoiceChannel(channel).catch(error);
          break;
        }
      }
    }
  }

  if (m.content.startsWith(`${botMention} d`)) { // destroy
    if (!boundChannel) return;
    client.reply(m, `Unbinding from <#${boundChannel.id}> and destroying voice connection`);
    playQueue = [];
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    currentStream = false;
    currentVideo = false;
    return;
  }

  // Only respond to other messages inside the bound channel
  if (!m.channel.equals(boundChannel)) return;

  if (m.content.startsWith(`${botMention} n`)) { // next
    playStopped();
  }

  if (m.content.startsWith(`${botMention} yq`) // youtube query
    || m.content.startsWith(`${botMention} qq`) // queue query
    || m.content.startsWith(`${botMention} pq`) // play query
    || m.content.startsWith(`${botMention} ytq`)) {

    if (apiKey == false) {
      client.reply(m, 'Search is disabled (no API KEY found).');
      return;
    }

    var args = spliceArguments(m.content)[1];

    if (!args) {
      client.reply(m, 'You need to specify a search parameter.');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/search' +
      `?part=snippet&q=${escape(args)}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'Your query gave 0 results.');
          return;
        }

        var vid = body.items[0].id.videoId;
        getInfoAndQueue(vid, m);
      } else {
        client.reply(m, 'There was an error searching.');
        return;
      }
    });

    return; // have to stop propagation
  }

  if (m.content.startsWith(`${botMention} pl`)) { // playlist
    if (apiKey == false) {
      client.reply(m, 'Playlist adding is disabled (no API KEY found).');
      return;
    }

    var pid = spliceArguments(m.content)[1];

    if (!pid) {
      client.reply(m, 'You need to specify a playlist ID!');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems' +
      `?part=contentDetails&maxResults=50&playlistId=${pid}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'That playlist has no videos.');
          return;
        }

        client.reply(m, `Loading ${body.items.length} videos...`);
        var suppress = 0;
        body.items.forEach((elem, idx) => {
          var vid = elem.contentDetails.videoId;
          if (idx == 1) suppress = body.items.length - 2;
          if (idx == 2) suppress = -1;
          getInfoAndQueue(vid, m, suppress);
        });
      } else {
        client.reply(m, 'There was an error finding playlist with that id.');
        return;
      }
    });

    return;
  }

  if (m.content.startsWith(`${botMention} y`) // youtube
    || m.content.startsWith(`${botMention} q`) // queue
    || m.content.startsWith(`${botMention} p`)) { // play

    var vidList = spliceArguments(m.content)[1];

    var vids = vidList.split(',');
    var suppress = 0;
    vids.forEach((vid, idx) => {
      if (idx == 1) suppress = vids.length - 2;
      if (idx == 2) suppress = -1;
      parseVidAndQueue(vid, m, suppress);
    });
  }

  if (m.content.startsWith(`${botMention} r`)) { // replay
    playQueue.push(currentVideo);
    client.reply(m, `Queued ${VideoFormat.prettyPrint(currentVideo)}`);
  }

  if (m.content.startsWith(`${botMention} sh`)) { // shuffle
    if (playQueue.length < 2) {
      client.reply(m, 'Not enough songs in the queue.');
      return;
    } else {
      shuffle(playQueue);
      client.reply(m, 'Songs in the queue have been shuffled.');
    }

    return;
  }

  if (m.content.startsWith(`${botMention} link`)) {
    if (currentVideo) client.reply(m, `<${currentVideo.loaderUrl}>`);
    return; // stop propagation
  }

  if (m.content.startsWith(`${botMention} list s`)) { // list saved
    var formattedList = 'Here are the videos currently saved: \n';
    for (var key in Saved.saved.videos) {
      if (Saved.saved.videos.hasOwnProperty(key)) {
        formattedList += `*${key}*: ${VideoFormat.prettyPrint(Saved.saved.videos[key])}\n`;
      }
    }

    client.reply(m, formattedList);
    return; // so list doesn't get triggered
  }

  if (m.content.startsWith(`${botMention} l`)) { // list
    var formattedList = 'Here are the videos currently in the play queue, from first added to last added: \n';
    formattedList += `Currently playing: ${VideoFormat.prettyPrintWithUser(currentVideo)}\n`;

    var shouldBreak = false;

    playQueue.forEach((video, idx) => {
      if (shouldBreak) return;

      var formattedVideo = `${idx + 1}. ${VideoFormat.prettyPrintWithUser(video)}\n`;

      if ((formattedList.length + formattedVideo.length) > 1950) {
        formattedList += `... and ${playQueue.length - idx} more`;
        shouldBreak = true;
      } else {
        formattedList += formattedVideo;
      }
    });
    client.reply(m, formattedList);
  }

  if (m.content.startsWith(`${botMention} s`)) { // save
    var argument = spliceArguments(m.content)[1];
    if (!argument) {
      client.reply(m, 'You need to specify a video and a keyword!');
      return;
    }

    var splitArgs = spliceArguments(argument, 1);

    var vid = splitArgs[0];

    if (vid === 'current') {
      if (currentVideo) vid = currentVideo.vid;
      else {
        client.reply(m, `Couldn't retrieve video information of current video!`);
      }
    }

    var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else saveVideo(info, vid, splitArgs[1], m);
    });
  }

  if (m.content.startsWith(`${botMention} t`)) { // time
    var streamTime = client.internal.voiceConnection.streamTime; // in ms
    var videoTime = currentVideo.length_seconds;
    client.reply(m, `${VideoFormat.prettyTime(streamTime)} / ${VideoFormat.prettyTime(videoTime * 1000)} (${(streamTime / (videoTime * 10)).toFixed(2)} %)`);
  }
});

function parseVidAndQueue(vid, m, suppress) {
  if (/^http/.test(vid)) {
    if (url.parse(vid, true).query.v) {
      vid = url.parse(vid, true).query.v;
    }
  }

  vid = Saved.possiblyRetrieveVideo(vid);
  if (!vid) {
    client.reply(m, 'You need to specify a video!');
    return;
  }

  getInfoAndQueue(vid, m, suppress);
}

function getInfoAndQueue(vid, m, suppress) {
  requestUrl = 'http://www.youtube.com/watch?v=' + vid;
  ytdl.getInfo(requestUrl, (err, info) => {
    if (err) handleYTError(err);
    else {
      info.vid = vid;
      possiblyQueue(info, m.author.id, m, suppress);
    }
  });
}

function spliceArguments(message, after) {
  after = after || 2;
  var rest = message.split(' ');
  var removed = rest.splice(0, after);
  return [removed.join(' '), rest.join(' ')];
}

function saveVideo(video, vid, keywords, m) {
  simplified = VideoFormat.simplify(video, vid);
  if (Saved.saved.videos.hasOwnProperty(keywords)) client.reply(m, `Warning: ${VideoFormat.simplePrint(Saved.saved.videos[keywords])} is already saved as *${keywords}*! Overwriting.`);

  var key;
  if (key = Saved.isVideoSaved(vid)) client.reply(m, `Warning: This video is already saved as *${key}*! Adding it anyway as *${keywords}*.`);

  Saved.saved.videos[keywords] = simplified;
  client.reply(m, `Saved video ${VideoFormat.prettyPrint(video)} as *${keywords}*`);
  Saved.write();
}

function possiblyQueue(video, userId, m, suppress) {
  video.userId = userId;
  suppress = (suppress === undefined) ? false : suppress;
  reason = shouldDisallowQueue(playQueue, video);
  if (reason) {
    client.reply(m, `You can't queue this video right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    if (suppress == 0) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);
    else if (suppress > -1) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)} and ${suppress} other videos`);

    // Start playing if not playing yet
    if (!currentVideo) nextInQueue();
  }
}

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    boundChannel.sendMessage('This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    boundChannel.sendMessage('An error occurred while getting video information! Please try a different video.');
  }

  console.log(err.toString());
}

function playStopped() {
  if (client.internal.voiceConnection) client.internal.voiceConnection.stopPlaying();

  boundChannel.sendMessage(`Finished playing ${VideoFormat.simplePrint(currentVideo)}`);
  currentVideo = false;
  nextInQueue();
}

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = YoutubeStream.getStream(video.loaderUrl);

    currentStream.on('error', (err) => {
      boundChannel.sendMessage(`There was an error during playback! **${err}**`);
    });

    currentStream.on('end', () => setTimeout(playStopped, Config.timeOffset || 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      boundChannel.sendMessage(`Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

function nextInQueue() {
  if (playQueue.length > 0) {
    next = playQueue.shift();
    play(next);
  }
}

function shuffle(array) {
  var counter = array.length;
  var temp;
  var index;

  // While there are elements in the array
  while (counter > 0) {
    index = Math.floor(Math.random() * counter);

    counter--;

    temp = array[counter];
    array[counter] = array[index];
    array[index] = temp;
  }

  return array;
}

function error(argument) {
  console.log(argument.stack);
}

// Email and password over command line
client.login(process.argv[2], process.argv[3]).catch((e) => console.log(e));
<|endoftext|>


<commit_before>var Discord = require('discord.js');

var ytdl = require('ytdl-core');
var request = require('request');
var url = require('url');

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.js');
var YoutubeStream = require('./lib/youtube-stream.js');
var Saved = require('./lib/saved.js');
Saved.read();

var client = new Discord.Client();

// Handle discord.js warnings
client.on('warn', (m) => console.log('[warn]', m));
client.on('debug', (m) => console.log('[debug]', m));

var playQueue = [];
var boundChannel = false;
var currentStream = false;
var currentVideo = false;

var botMention = false;

// Handling api key
if (process.argv[4]) {
  var apiKey = process.argv[4];
} else {
  var apiKey = false;
}

client.on('ready', () => {
  botMention = `<@${client.user.id}>`;
  console.log(`Bot mention: ${botMention}`);
});

client.on('message', m => {
  if (!botMention) return;

  if (m.content.startsWith(`${botMention} i`)) { // init
    if (boundChannel) return;
    var channelToJoin = spliceArguments(m.content)[1];
    for (var channel of m.channel.server.channels) {
      if (channel instanceof Discord.VoiceChannel) {
        if (!channelToJoin || channel.name === channelToJoin) {
          boundChannel = m.channel;
          client.reply(m, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``);
          client.joinVoiceChannel(channel).catch(error);
          break;
        }
      }
    }
  }

  if (m.content.startsWith(`${botMention} d`)) { // destroy
    if (!boundChannel) return;
    client.reply(m, `Unbinding from <#${boundChannel.id}> and destroying voice connection`);
    playQueue = [];
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    currentStream = false;
    currentVideo = false;
    return;
  }

  // Only respond to other messages inside the bound channel
  if (!m.channel.equals(boundChannel)) return;

  if (m.content.startsWith(`${botMention} n`)) { // next
    playStopped();
  }

  if (m.content.startsWith(`${botMention} yq`) // youtube query
    || m.content.startsWith(`${botMention} qq`) // queue query
    || m.content.startsWith(`${botMention} pq`) // play query
    || m.content.startsWith(`${botMention} ytq`)) {

    if (apiKey == false) {
      client.reply(m, 'Search is disabled (no API KEY found).');
      return;
    }

    var args = spliceArguments(m.content)[1];

    if (!args) {
      client.reply(m, 'You need to specify a search parameter.');
      return;
    }

    var query = args.join(' ');

    var requestUrl = 'https://www.googleapis.com/youtube/v3/search' +
      '?part=snippet&q=' + escape(query) + '&key=' + apiKey;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'Your query gave 0 results.');
          return;
        }

        var vid = body.items[0].id.videoId;
        requestUrl = 'http://www.youtube.com/watch?v=' + vid;
        ytdl.getInfo(requestUrl, (err, info) => {
          if (err) handleYTError(err);
          else {
            info.vid = vid;
            possiblyQueue(info, m.author.id, m);
          }
        });
      } else {
        client.reply(m, 'There was an error searching.');
        return;
      }
    });

    return; // have to stop propagation
  }

  if (m.content.startsWith(`${botMention} pl`)) { // playlist
    if (apiKey == false) {
      client.reply(m, 'Playlist adding is disabled (no API KEY found).');
      return;
    }

    var pid = spliceArguments(m.content)[1];

    if (!pid) {
      client.reply(m, 'You need to specify a playlist ID!');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems' +
      `?part=contentDetails&maxResults=50&playlistId=${pid}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'That playlist has no videos.');
          return;
        }

        client.reply(m, `Loading ${body.items.length} videos...`);
        var suppress = 0;
        body.items.forEach((elem, idx) => {
          var vid = elem.contentDetails.videoId;
          requestUrl = 'http://www.youtube.com/watch?v=' + vid;
          ytdl.getInfo(requestUrl, (err, info) => {
            if (err) handleYTError(err);
            else {
              if (idx == 1) suppress = body.items.length - 2;
              if (idx == 2) suppress = -1;
              info.vid = vid;
              possiblyQueue(info, m.author.id, m, suppress);
            }
          });
        });
      } else {
        client.reply(m, 'There was an error finding playlist with that id.');
        return;
      }
    });

    return;
  }

  if (m.content.startsWith(`${botMention} y`) // youtube
    || m.content.startsWith(`${botMention} q`) // queue
    || m.content.startsWith(`${botMention} p`)) { // play

    var vidList = spliceArguments(m.content)[1];

    var vids = vidList.split(',');
    var suppress = 0;
    vids.forEach((vid, idx) => {
      if (idx == 1) suppress = vids.length - 2;
      if (idx == 2) suppress = -1;
      parseVidAndQueue(vid, m, suppress);
    });
  }

  if (m.content.startsWith(`${botMention} sh`)) { // shuffle
    if (playQueue.length < 2) {
      client.reply(m, 'Not enough songs in the queue.');
      return;
    } else {
      shuffle(playQueue);
      client.reply(m, 'Songs in the queue have been shuffled.');
    }

    return;
  }

  if (m.content.startsWith(`${botMention} link`)) {
    if (currentVideo) client.reply(m, `<${currentVideo.loaderUrl}>`);
    return; // stop propagation
  }

  if (m.content.startsWith(`${botMention} list s`)) { // list saved
    var formattedList = 'Here are the videos currently saved: \n';
    for (var key in Saved.saved.videos) {
      if (Saved.saved.videos.hasOwnProperty(key)) {
        formattedList += `*${key}*: ${VideoFormat.prettyPrint(Saved.saved.videos[key])}\n`;
      }
    }

    client.reply(m, formattedList);
    return; // so list doesn't get triggered
  }

  if (m.content.startsWith(`${botMention} l`)) { // list
    var formattedList = 'Here are the videos currently in the play queue, from first added to last added: \n';
    formattedList += `Currently playing: ${VideoFormat.prettyPrintWithUser(currentVideo)}\n`;
    playQueue.forEach((video, idx) => {
      formattedList += `${idx + 1}. ${VideoFormat.prettyPrintWithUser(video)}\n`;
    });
    client.reply(m, formattedList);
  }

  if (m.content.startsWith(`${botMention} s`)) { // save
    var argument = spliceArguments(m.content)[1];
    if (!argument) {
      client.reply(m, 'You need to specify a video and a keyword!');
      return;
    }

    var splitArgs = spliceArguments(argument, 1);

    var vid = splitArgs[0];

    if (vid === 'current') {
      if (currentVideo) vid = currentVideo.vid;
      else {
        client.reply(m, `Couldn't retrieve video information of current video!`);
      }
    }

    var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
    console.log(vid);
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else saveVideo(info, vid, splitArgs[1], m);
    });
  }

  if (m.content.startsWith(`${botMention} t`)) { // time
    var streamTime = client.internal.voiceConnection.streamTime; // in ms
    var videoTime = currentVideo.length_seconds;
    client.reply(m, `${VideoFormat.prettyTime(streamTime)} / ${VideoFormat.prettyTime(videoTime * 1000)} (${(streamTime / (videoTime * 1000)).toFixed(2)} %)`);
  }
});

function parseVidAndQueue(vid, m, suppress) {
  if (/^http/.test(vid)) {
    if (url.parse(vid, true).query.v) {
      vid = url.parse(vid, true).query.v;
    }
  }

  vid = Saved.possiblyRetrieveVideo(vid);
  if (!vid) {
    client.reply(m, 'You need to specify a video!');
    return;
  }

  var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
  ytdl.getInfo(requestUrl, (err, info) => {
    if (err) handleYTError(err);
    else {
      info.vid = vid;
      possiblyQueue(info, m.author.id, m, suppress);
    }
  });
}

function spliceArguments(message, after) {
  after = after || 2;
  var rest = message.split(' ');
  var removed = rest.splice(0, after);
  return [removed.join(' '), rest.join(' ')];
}

function saveVideo(video, vid, keywords, m) {
  simplified = VideoFormat.simplify(video, vid);
  if (Saved.saved.videos.hasOwnProperty(keywords)) client.reply(m, `Warning: ${VideoFormat.simplePrint(Saved.saved.videos[keywords])} is already saved as *${keywords}*! Overwriting.`);
  if (Saved.isVideoSaved(vid)) client.reply(m, `Warning: This video is already saved as *${key}*! Adding it anyway as *${keywords}*.`);

  Saved.saved.videos[keywords] = simplified;
  client.reply(m, `Saved video ${VideoFormat.prettyPrint(video)} as *${keywords}*`);
  Saved.write();
}

function possiblyQueue(video, userId, m, suppress) {
  video.userId = userId;
  suppress = (suppress === undefined) ? false : suppress;
  reason = shouldDisallowQueue(playQueue, video);
  if (reason) {
    client.reply(m, `You can't queue this video right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    if (suppress == 0) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);
    else if (suppress > -1) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)} and ${suppress} other videos`);

    // Start playing if not playing yet
    if (!currentVideo) nextInQueue();
  }
}

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    boundChannel.sendMessage('This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    boundChannel.sendMessage('An error occurred while getting video information! Please try a different video.');
  }

  console.log(err.toString());
}

function playStopped() {
  if (client.internal.voiceConnection) client.internal.voiceConnection.stopPlaying();

  boundChannel.sendMessage(`Finished playing ${VideoFormat.simplePrint(currentVideo)}`);
  currentVideo = false;
  nextInQueue();
}

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = YoutubeStream.getStream(video.loaderUrl);

    currentStream.on('error', (err) => {
      boundChannel.sendMessage(`There was an error during playback! **${err}**`);
    });

    currentStream.on('end', () => setTimeout(playStopped, 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      boundChannel.sendMessage(`Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

function nextInQueue() {
  if (playQueue.length > 0) {
    next = playQueue.shift();
    play(next);
  }
}

function shuffle(array) {
  var counter = array.length;
  var temp;
  var index;

  // While there are elements in the array
  while (counter > 0) {
    index = Math.floor(Math.random() * counter);

    counter--;

    temp = array[counter];
    array[counter] = array[index];
    array[index] = temp;
  }

  return array;
}

function error(argument) {
  console.log(argument.stack);
}

// Email and password over command line
client.login(process.argv[2], process.argv[3]).catch((e) => console.log(e));
<commit_msg>Use interpolation for yq<commit_after>var Discord = require('discord.js');

var ytdl = require('ytdl-core');
var request = require('request');
var url = require('url');

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.js');
var YoutubeStream = require('./lib/youtube-stream.js');
var Saved = require('./lib/saved.js');
Saved.read();

var client = new Discord.Client();

// Handle discord.js warnings
client.on('warn', (m) => console.log('[warn]', m));
client.on('debug', (m) => console.log('[debug]', m));

var playQueue = [];
var boundChannel = false;
var currentStream = false;
var currentVideo = false;

var botMention = false;

// Handling api key
if (process.argv[4]) {
  var apiKey = process.argv[4];
} else {
  var apiKey = false;
}

client.on('ready', () => {
  botMention = `<@${client.user.id}>`;
  console.log(`Bot mention: ${botMention}`);
});

client.on('message', m => {
  if (!botMention) return;

  if (m.content.startsWith(`${botMention} i`)) { // init
    if (boundChannel) return;
    var channelToJoin = spliceArguments(m.content)[1];
    for (var channel of m.channel.server.channels) {
      if (channel instanceof Discord.VoiceChannel) {
        if (!channelToJoin || channel.name === channelToJoin) {
          boundChannel = m.channel;
          client.reply(m, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``);
          client.joinVoiceChannel(channel).catch(error);
          break;
        }
      }
    }
  }

  if (m.content.startsWith(`${botMention} d`)) { // destroy
    if (!boundChannel) return;
    client.reply(m, `Unbinding from <#${boundChannel.id}> and destroying voice connection`);
    playQueue = [];
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    currentStream = false;
    currentVideo = false;
    return;
  }

  // Only respond to other messages inside the bound channel
  if (!m.channel.equals(boundChannel)) return;

  if (m.content.startsWith(`${botMention} n`)) { // next
    playStopped();
  }

  if (m.content.startsWith(`${botMention} yq`) // youtube query
    || m.content.startsWith(`${botMention} qq`) // queue query
    || m.content.startsWith(`${botMention} pq`) // play query
    || m.content.startsWith(`${botMention} ytq`)) {

    if (apiKey == false) {
      client.reply(m, 'Search is disabled (no API KEY found).');
      return;
    }

    var args = spliceArguments(m.content)[1];

    if (!args) {
      client.reply(m, 'You need to specify a search parameter.');
      return;
    }

    var query = args.join(' ');

    var requestUrl = 'https://www.googleapis.com/youtube/v3/search' +
      `?part=snippet&q=${escape(query)}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'Your query gave 0 results.');
          return;
        }

        var vid = body.items[0].id.videoId;
        requestUrl = 'http://www.youtube.com/watch?v=' + vid;
        ytdl.getInfo(requestUrl, (err, info) => {
          if (err) handleYTError(err);
          else {
            info.vid = vid;
            possiblyQueue(info, m.author.id, m);
          }
        });
      } else {
        client.reply(m, 'There was an error searching.');
        return;
      }
    });

    return; // have to stop propagation
  }

  if (m.content.startsWith(`${botMention} pl`)) { // playlist
    if (apiKey == false) {
      client.reply(m, 'Playlist adding is disabled (no API KEY found).');
      return;
    }

    var pid = spliceArguments(m.content)[1];

    if (!pid) {
      client.reply(m, 'You need to specify a playlist ID!');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems' +
      `?part=contentDetails&maxResults=50&playlistId=${pid}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'That playlist has no videos.');
          return;
        }

        client.reply(m, `Loading ${body.items.length} videos...`);
        var suppress = 0;
        body.items.forEach((elem, idx) => {
          var vid = elem.contentDetails.videoId;
          requestUrl = 'http://www.youtube.com/watch?v=' + vid;
          ytdl.getInfo(requestUrl, (err, info) => {
            if (err) handleYTError(err);
            else {
              if (idx == 1) suppress = body.items.length - 2;
              if (idx == 2) suppress = -1;
              info.vid = vid;
              possiblyQueue(info, m.author.id, m, suppress);
            }
          });
        });
      } else {
        client.reply(m, 'There was an error finding playlist with that id.');
        return;
      }
    });

    return;
  }

  if (m.content.startsWith(`${botMention} y`) // youtube
    || m.content.startsWith(`${botMention} q`) // queue
    || m.content.startsWith(`${botMention} p`)) { // play

    var vidList = spliceArguments(m.content)[1];

    var vids = vidList.split(',');
    var suppress = 0;
    vids.forEach((vid, idx) => {
      if (idx == 1) suppress = vids.length - 2;
      if (idx == 2) suppress = -1;
      parseVidAndQueue(vid, m, suppress);
    });
  }

  if (m.content.startsWith(`${botMention} sh`)) { // shuffle
    if (playQueue.length < 2) {
      client.reply(m, 'Not enough songs in the queue.');
      return;
    } else {
      shuffle(playQueue);
      client.reply(m, 'Songs in the queue have been shuffled.');
    }

    return;
  }

  if (m.content.startsWith(`${botMention} link`)) {
    if (currentVideo) client.reply(m, `<${currentVideo.loaderUrl}>`);
    return; // stop propagation
  }

  if (m.content.startsWith(`${botMention} list s`)) { // list saved
    var formattedList = 'Here are the videos currently saved: \n';
    for (var key in Saved.saved.videos) {
      if (Saved.saved.videos.hasOwnProperty(key)) {
        formattedList += `*${key}*: ${VideoFormat.prettyPrint(Saved.saved.videos[key])}\n`;
      }
    }

    client.reply(m, formattedList);
    return; // so list doesn't get triggered
  }

  if (m.content.startsWith(`${botMention} l`)) { // list
    var formattedList = 'Here are the videos currently in the play queue, from first added to last added: \n';
    formattedList += `Currently playing: ${VideoFormat.prettyPrintWithUser(currentVideo)}\n`;
    playQueue.forEach((video, idx) => {
      formattedList += `${idx + 1}. ${VideoFormat.prettyPrintWithUser(video)}\n`;
    });
    client.reply(m, formattedList);
  }

  if (m.content.startsWith(`${botMention} s`)) { // save
    var argument = spliceArguments(m.content)[1];
    if (!argument) {
      client.reply(m, 'You need to specify a video and a keyword!');
      return;
    }

    var splitArgs = spliceArguments(argument, 1);

    var vid = splitArgs[0];

    if (vid === 'current') {
      if (currentVideo) vid = currentVideo.vid;
      else {
        client.reply(m, `Couldn't retrieve video information of current video!`);
      }
    }

    var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
    console.log(vid);
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else saveVideo(info, vid, splitArgs[1], m);
    });
  }

  if (m.content.startsWith(`${botMention} t`)) { // time
    var streamTime = client.internal.voiceConnection.streamTime; // in ms
    var videoTime = currentVideo.length_seconds;
    client.reply(m, `${VideoFormat.prettyTime(streamTime)} / ${VideoFormat.prettyTime(videoTime * 1000)} (${(streamTime / (videoTime * 1000)).toFixed(2)} %)`);
  }
});

function parseVidAndQueue(vid, m, suppress) {
  if (/^http/.test(vid)) {
    if (url.parse(vid, true).query.v) {
      vid = url.parse(vid, true).query.v;
    }
  }

  vid = Saved.possiblyRetrieveVideo(vid);
  if (!vid) {
    client.reply(m, 'You need to specify a video!');
    return;
  }

  var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
  ytdl.getInfo(requestUrl, (err, info) => {
    if (err) handleYTError(err);
    else {
      info.vid = vid;
      possiblyQueue(info, m.author.id, m, suppress);
    }
  });
}

function spliceArguments(message, after) {
  after = after || 2;
  var rest = message.split(' ');
  var removed = rest.splice(0, after);
  return [removed.join(' '), rest.join(' ')];
}

function saveVideo(video, vid, keywords, m) {
  simplified = VideoFormat.simplify(video, vid);
  if (Saved.saved.videos.hasOwnProperty(keywords)) client.reply(m, `Warning: ${VideoFormat.simplePrint(Saved.saved.videos[keywords])} is already saved as *${keywords}*! Overwriting.`);
  if (Saved.isVideoSaved(vid)) client.reply(m, `Warning: This video is already saved as *${key}*! Adding it anyway as *${keywords}*.`);

  Saved.saved.videos[keywords] = simplified;
  client.reply(m, `Saved video ${VideoFormat.prettyPrint(video)} as *${keywords}*`);
  Saved.write();
}

function possiblyQueue(video, userId, m, suppress) {
  video.userId = userId;
  suppress = (suppress === undefined) ? false : suppress;
  reason = shouldDisallowQueue(playQueue, video);
  if (reason) {
    client.reply(m, `You can't queue this video right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    if (suppress == 0) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);
    else if (suppress > -1) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)} and ${suppress} other videos`);

    // Start playing if not playing yet
    if (!currentVideo) nextInQueue();
  }
}

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    boundChannel.sendMessage('This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    boundChannel.sendMessage('An error occurred while getting video information! Please try a different video.');
  }

  console.log(err.toString());
}

function playStopped() {
  if (client.internal.voiceConnection) client.internal.voiceConnection.stopPlaying();

  boundChannel.sendMessage(`Finished playing ${VideoFormat.simplePrint(currentVideo)}`);
  currentVideo = false;
  nextInQueue();
}

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = YoutubeStream.getStream(video.loaderUrl);

    currentStream.on('error', (err) => {
      boundChannel.sendMessage(`There was an error during playback! **${err}**`);
    });

    currentStream.on('end', () => setTimeout(playStopped, 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      boundChannel.sendMessage(`Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

function nextInQueue() {
  if (playQueue.length > 0) {
    next = playQueue.shift();
    play(next);
  }
}

function shuffle(array) {
  var counter = array.length;
  var temp;
  var index;

  // While there are elements in the array
  while (counter > 0) {
    index = Math.floor(Math.random() * counter);

    counter--;

    temp = array[counter];
    array[counter] = array[index];
    array[index] = temp;
  }

  return array;
}

function error(argument) {
  console.log(argument.stack);
}

// Email and password over command line
client.login(process.argv[2], process.argv[3]).catch((e) => console.log(e));
<|endoftext|>