<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, Config);
  if (reason) {
    client.reply(m, `You can't queue ${VideoFormat.simplePrint(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 userIsAdmin(user) {
  return config.adminIds.indexOf(user.id) > -1;
}

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>Allow admins to queue videos even if there's a check in place<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, Config);
  if (!userIsAdmin(userId) && reason) {
    client.reply(m, `You can't queue ${VideoFormat.simplePrint(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 userIsAdmin(user) {
  return config.adminIds.indexOf(user.id) > -1;
}

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>/*
 * (C) Copyright 2017 o2r project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/* eslint-env mocha */
const assert = require('chai').assert;
const request = require('request');
const config = require('../config/config');

require("./setup")
const cookie = 's:C0LIrsxGtHOGHld8Nv2jedjL4evGgEHo.GMsWD5Vveq0vBt7/4rGeoH5Xx7Dd2pgZR9DvhKCyDTY';
const cookie_plain = 's:yleQfdYnkh-sbj9Ez--_TWHVhXeXNEgq.qRmINNdkRuJ+iHGg5woRa9ydziuJ+DzFG9GnAZRvaaM';
const requestLoadingTimeout = 20000;

describe('Sciebo loader with workspaces', function () {
    describe('create new compendium based on a workspace in a public WebDAV', () => {
        it('should respond with a compendium ID', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/index.php/s/96XJFrmd3cSYGko',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                assert.equal(res.statusCode, 200);
                assert.isObject(JSON.parse(body), 'returned JSON');
                assert.isDefined(JSON.parse(body).id, 'returned id');
                done();
            });
        }).timeout(20000);

        it('should download the files in the workspace and list them via the API', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/index.php/s/96XJFrmd3cSYGko',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                let compendium_id = JSON.parse(body).id;

                request({
                    uri: global.test_host_read + '/api/v1/compendium/' + compendium_id,
                    method: 'GET',
                    jar: j,
                    timeout: requestLoadingTimeout
                }, (err, res, body) => {
                    assert.ifError(err);
                    assert.equal(res.statusCode, 200);
                    let response = JSON.parse(body);

                    assert.include(JSON.stringify(response), 'data/main.Rmd');
                    assert.include(JSON.stringify(response), 'data/display.html');

                    done();
                });
            });
        }).timeout(20000);
    });

    describe('create new compendium based on a workspace in a public WebDAV with workspace in a subdirectory', () => {
        let compendium_id = null;

        it('should respond with a compendium ID', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/index.php/s/VEX8Bd88hPAm7fa',
                path: '/my-research',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                assert.equal(res.statusCode, 200);
                assert.isObject(JSON.parse(body), 'returned JSON');
                assert.isDefined(JSON.parse(body).id, 'returned id');
                compendium_id = JSON.parse(body).id;
                done();
            });
        }).timeout(20000);

        it('should have downloaded the files in the workspace and list them via the API', (done) => {
            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host_read + '/api/v1/compendium/' + compendium_id,
                method: 'GET',
                jar: j,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                assert.equal(res.statusCode, 200);
                let response = JSON.parse(body);

                assert.include(JSON.stringify(response), 'data/main.Rmd');
                assert.include(JSON.stringify(response), 'data/display.html');
                assert.notInclude(JSON.stringify(response), 'shouldnotbethere');

                done();
            });
        });
    });

    describe('create new compendium based on a workspace in a public WebDAV with a single zip file', () => {
        it('should respond with a compendium ID', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/index.php/s/Pu58dfPYcIvM2GX',
                path: '/',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                assert.equal(res.statusCode, 200);
                assert.isObject(JSON.parse(body), 'returned JSON');
                assert.isDefined(JSON.parse(body).id, 'returned id');
                done();
            });
        }).timeout(20000);

        it('should download the files in the workspace and list them via the API', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/index.php/s/Pu58dfPYcIvM2GX',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                let compendium_id = JSON.parse(body).id;

                request({
                    uri: global.test_host_read + '/api/v1/compendium/' + compendium_id,
                    method: 'GET',
                    jar: j,
                    timeout: requestLoadingTimeout
                }, (err, res, body) => {
                    assert.ifError(err);
                    assert.equal(res.statusCode, 200);
                    let response = JSON.parse(body);

                    assert.include(JSON.stringify(response), 'data/main.R');
                    assert.include(JSON.stringify(response), 'data/display.png');

                    done();
                });
            });
        }).timeout(20000);
    });
});

describe('Sciebo loader with invalid requests', () => {
    it('should respond with an error 422 for an invalid share URL', (done) => {
        let form = {
            share_url: 'htts:/uni-muenster.sciebo.de/index.php/s/7EoWgjLSFV',
            path: '/',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 422);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'public share URL is invalid');
            done();
        });
    }).timeout(10000);

    it('invalid host (not a sciebo public share): should respond with an error 403', (done) => {
        let form = {
            share_url: 'https://myowncloud.wxyz/index.php/s/G8vxQ1h50V4HpuA',
            path: '/',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 403);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'host is not allowed');
            done();
        });
    }).timeout(10000);

    it('invalid token: should respond with an error 404', (done) => {
        let form = {
            share_url: 'https://uni-muenster.sciebo.de/index.php/s/89k3ljf93kjfa',
            path: '/',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 404);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'could not read webdav contents');
            done();
        });
    }).timeout(10000);

    it('insufficient user level: should respond with an error 401', (done) => {
        let form = {
            share_url: 'https://uni-muenster.sciebo.de/index.php/s/G8vxQ1h50V4HpuA',
            path: '/ekjsle5',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie_plain);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 401);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'user level does not allow compendium creation');
            done();
        });
    }).timeout(10000);
});

describe('Sciebo loader with empty workspace', function () {
    it('should respond with an error 404 and valid JSON with error message', (done) => {
        let form = {
            share_url: 'https://uni-muenster.sciebo.de/index.php/s/KScGmzyCIMJkFa9',
            path: '/',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 400);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'public share is empty');
            done();
        });
    }).timeout(10000);

});
<commit_msg>use new share url style of sciebo in some tests<commit_after>/*
 * (C) Copyright 2017 o2r project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/* eslint-env mocha */
const assert = require('chai').assert;
const request = require('request');
const config = require('../config/config');

require("./setup")
const cookie = 's:C0LIrsxGtHOGHld8Nv2jedjL4evGgEHo.GMsWD5Vveq0vBt7/4rGeoH5Xx7Dd2pgZR9DvhKCyDTY';
const cookie_plain = 's:yleQfdYnkh-sbj9Ez--_TWHVhXeXNEgq.qRmINNdkRuJ+iHGg5woRa9ydziuJ+DzFG9GnAZRvaaM';
const requestLoadingTimeout = 20000;

describe('Sciebo loader with workspaces', function () {
    describe('create new compendium based on a workspace in a public WebDAV', () => {
        it('should respond with a compendium ID', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/s/96XJFrmd3cSYGko',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                assert.equal(res.statusCode, 200);
                assert.isObject(JSON.parse(body), 'returned JSON');
                assert.isDefined(JSON.parse(body).id, 'returned id');
                done();
            });
        }).timeout(20000);

        it('should download the files in the workspace and list them via the API', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/index.php/s/96XJFrmd3cSYGko',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                let compendium_id = JSON.parse(body).id;

                request({
                    uri: global.test_host_read + '/api/v1/compendium/' + compendium_id,
                    method: 'GET',
                    jar: j,
                    timeout: requestLoadingTimeout
                }, (err, res, body) => {
                    assert.ifError(err);
                    assert.equal(res.statusCode, 200);
                    let response = JSON.parse(body);

                    assert.include(JSON.stringify(response), 'data/main.Rmd');
                    assert.include(JSON.stringify(response), 'data/display.html');

                    done();
                });
            });
        }).timeout(20000);
    });

    describe('create new compendium based on a workspace in a public WebDAV with workspace in a subdirectory', () => {
        let compendium_id = null;

        it('should respond with a compendium ID', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/s/VEX8Bd88hPAm7fa',
                path: '/my-research',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                assert.equal(res.statusCode, 200);
                assert.isObject(JSON.parse(body), 'returned JSON');
                assert.isDefined(JSON.parse(body).id, 'returned id');
                compendium_id = JSON.parse(body).id;
                done();
            });
        }).timeout(20000);

        it('should have downloaded the files in the workspace and list them via the API', (done) => {
            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host_read + '/api/v1/compendium/' + compendium_id,
                method: 'GET',
                jar: j,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                assert.equal(res.statusCode, 200);
                let response = JSON.parse(body);

                assert.include(JSON.stringify(response), 'data/main.Rmd');
                assert.include(JSON.stringify(response), 'data/display.html');
                assert.notInclude(JSON.stringify(response), 'shouldnotbethere');

                done();
            });
        });
    });

    describe('create new compendium based on a workspace in a public WebDAV with a single zip file', () => {
        it('should respond with a compendium ID', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/index.php/s/Pu58dfPYcIvM2GX',
                path: '/',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                assert.equal(res.statusCode, 200);
                assert.isObject(JSON.parse(body), 'returned JSON');
                assert.isDefined(JSON.parse(body).id, 'returned id');
                done();
            });
        }).timeout(20000);

        it('should download the files in the workspace and list them via the API', (done) => {
            let form = {
                share_url: 'https://uni-muenster.sciebo.de/index.php/s/Pu58dfPYcIvM2GX',
                content_type: 'workspace'
            };

            let j = request.jar();
            let ck = request.cookie('connect.sid=' + cookie);
            j.setCookie(ck, global.test_host);

            request({
                uri: global.test_host + '/api/v1/compendium',
                method: 'POST',
                jar: j,
                form: form,
                timeout: requestLoadingTimeout
            }, (err, res, body) => {
                assert.ifError(err);
                let compendium_id = JSON.parse(body).id;

                request({
                    uri: global.test_host_read + '/api/v1/compendium/' + compendium_id,
                    method: 'GET',
                    jar: j,
                    timeout: requestLoadingTimeout
                }, (err, res, body) => {
                    assert.ifError(err);
                    assert.equal(res.statusCode, 200);
                    let response = JSON.parse(body);

                    assert.include(JSON.stringify(response), 'data/main.R');
                    assert.include(JSON.stringify(response), 'data/display.png');

                    done();
                });
            });
        }).timeout(20000);
    });
});

describe('Sciebo loader with invalid requests', () => {
    it('should respond with an error 422 for an invalid share URL', (done) => {
        let form = {
            share_url: 'htts:/uni-muenster.sciebo.de/index.php/s/7EoWgjLSFV',
            path: '/',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 422);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'public share URL is invalid');
            done();
        });
    }).timeout(10000);

    it('invalid host (not a sciebo public share): should respond with an error 403', (done) => {
        let form = {
            share_url: 'https://myowncloud.wxyz/index.php/s/G8vxQ1h50V4HpuA',
            path: '/',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 403);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'host is not allowed');
            done();
        });
    }).timeout(10000);

    it('invalid token: should respond with an error 404', (done) => {
        let form = {
            share_url: 'https://uni-muenster.sciebo.de/index.php/s/89k3ljf93kjfa',
            path: '/',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 404);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'could not read webdav contents');
            done();
        });
    }).timeout(10000);

    it('insufficient user level: should respond with an error 401', (done) => {
        let form = {
            share_url: 'https://uni-muenster.sciebo.de/index.php/s/G8vxQ1h50V4HpuA',
            path: '/ekjsle5',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie_plain);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 401);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'user level does not allow compendium creation');
            done();
        });
    }).timeout(10000);
});

describe('Sciebo loader with empty workspace', function () {
    it('should respond with an error 404 and valid JSON with error message', (done) => {
        let form = {
            share_url: 'https://uni-muenster.sciebo.de/index.php/s/KScGmzyCIMJkFa9',
            path: '/',
            content_type: 'workspace',
        };

        let j = request.jar();
        let ck = request.cookie('connect.sid=' + cookie);
        j.setCookie(ck, global.test_host);

        request({
            uri: global.test_host + '/api/v1/compendium',
            method: 'POST',
            jar: j,
            form: form,
            timeout: requestLoadingTimeout
        }, (err, res, body) => {
            assert.ifError(err);
            assert.equal(res.statusCode, 400);
            assert.isUndefined(JSON.parse(body).id, 'returned no id');
            assert.propertyVal(JSON.parse(body), 'error', 'public share is empty');
            done();
        });
    }).timeout(10000);

});
<|endoftext|>


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

var youtubeStream = require('youtube-audio-stream');
var ytdl = require('ytdl-core');

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.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));

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

var botMention = false;

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

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

  if (m.content.startsWith(`${botMention} i`)) { // init
    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 = [];
    currentStream.destroy();
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    return;
  }

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

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

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

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

  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)[2];
    var splitArgs = spliceArguments(argument, 1);
    Saved.saved.videos[splitArgs[0]] = splitArgs[1];
    Saved.write();
  }
});

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

function possiblyQueue(video, userId, m) {
  video.userId = userId;
  reason = shouldDisallowQueue(playQueue, video);
  if (reason) {
    client.reply(m, `You can't queue this video right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);

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

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    client.sendMessage(boundChannel, 'This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    client.sendMessage(boundChannel, '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();

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

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = youtubeStream(video.loaderUrl);
    currentStream.on('end', () => setTimeout(playStopped, 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      client.sendMessage(boundChannel, `Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

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

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 some feedback to save<commit_after>var Discord = require('discord.js');

var youtubeStream = require('youtube-audio-stream');
var ytdl = require('ytdl-core');

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.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));

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

var botMention = false;

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

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

  if (m.content.startsWith(`${botMention} i`)) { // init
    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 = [];
    currentStream.destroy();
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    return;
  }

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

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

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

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

  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)[2];
    var splitArgs = spliceArguments(argument, 1);
    var requestUrl = 'http://www.youtube.com/watch?v=' + splitArgs[0];
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else {
        Saved.saved.videos[splitArgs[1]] = splitArgs[0];
        client.reply(m, `Saved video ${VideoFormat.prettyPrint(info)} as ${splitArgs[1]}`);
        Saved.write();
      }
    });
  }
});

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

function possiblyQueue(video, userId, m) {
  video.userId = userId;
  reason = shouldDisallowQueue(playQueue, video);
  if (reason) {
    client.reply(m, `You can't queue this video right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);

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

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    client.sendMessage(boundChannel, 'This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    client.sendMessage(boundChannel, '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();

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

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = youtubeStream(video.loaderUrl);
    currentStream.on('end', () => setTimeout(playStopped, 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      client.sendMessage(boundChannel, `Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

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

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>import React, { PropTypes } from 'react';
import { Collapse, Label } from 'react-bootstrap';
import classNames from 'classnames';
import moment from 'moment';
import { reduce } from 'lodash';

import ConsortiumResultMeta from './consortium-result-meta';
import ConsortiumResultTable from './consortium-result-table';

export default function ConsortiumResult({
  computationInputs,
  complete,
  computation,
  data,
  endDate,
  expanded,
  pluginState,
  startDate,
  toggleCollapse,
  userErrors,
  usernames,
}) {
  let computationOutput;
  let dataOutput;
  let errors;
  let heading;
  let indicator;

  if (userErrors.length) {
    indicator = Error!;
  } else if (complete) {
    indicator = Complete!;
  } else {
    indicator = In Progress;
  }

  if (userErrors.length) {
    errors = (

User errors:

        {userErrors.map((error, i) => {
          return
{error}
;
        })}

    );
  }

  if (computation) {
    computationOutput = (
      <ConsortiumResultMeta
        computation={computation}
        computationInputs={computationInputs}
        step={pluginState['group-step'].step}
        usernames={usernames}
      />
    );
  }

  if (data) {
    /**
     * @todo This assumes covariates are placed at a specific location in
     * `computationInputs`. Don't hard-code this!
     */
    const covariates =
      computation.name === 'decentralized-single-shot-ridge-regression' ?
      computationInputs[0][2].map(x => x.name) :
      computationInputs[0][3].map(x => x.name);

    dataOutput = (

        {reduce(data, (memo, item, prop) => {
          /**
           * Computations store users' data in numeric properties and global
           * data under the `global` property.
           */
          if (prop === 'global') {
            return [
              <ConsortiumResultTable
                betaVector={item.betaVector}
                covariates={covariates}
                degreesOfFreedom={item.degreesOfFreedom}
                key={prop}
                name={'Global'}
                pValue={item.pValue}
                rSquared={item.rSquared}
                tValue={item.tValue}
              />,
              ...memo,
            ];
          } else if (Number(prop) == prop) { // eslint-disable-line eqeqeq
            return [
              ...memo,
              <ConsortiumResultTable
                betaVector={item.betaVector}
                covariates={covariates}
                degreesOfFreedom={item.degreesOfFreedom}
                key={prop}
                name={usernames[prop]}
                pValue={item.pValueOriginal}
                rSquared={item.rSquaredOriginal}
                tValue={item.tValueOriginal}
              />,
            ];
          }

          return memo;
        }, [])}

    );
  }

  if (endDate) {
    heading = `Ended ${moment(endDate).fromNow()}`;
  } else {
    heading = `Started ${moment(startDate).fromNow()}`;
  }

  return (

            {heading}
            {' '}
            {indicator}

      <Collapse in={expanded}>

          {computationOutput}
          {errors}
          {dataOutput}

      </Collapse>

  );
}

ConsortiumResult.displayName = 'ConsortiumResult';

ConsortiumResult.propTypes = {

  complete: PropTypes.bool.isRequired,
  computation: PropTypes.shape({
    meta: PropTypes.shape({
      name: PropTypes.string.isRequired,
    }),
    version: PropTypes.string.isRequired,
  }).isRequired,
  computationInputs: PropTypes.arrayOf(PropTypes.arrayOf(
    PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.string),
      PropTypes.arrayOf(PropTypes.object),
      PropTypes.number,
    ])
  )).isRequired,
  data: PropTypes.shape({
    global: PropTypes.shape({
      betaVector: PropTypes.arrayOf(PropTypes.number).isRequired,
      degreesOfFreedom: PropTypes.number.isRequired,
      pValue: PropTypes.arrayOf(PropTypes.number).isRequired,
      rSquared: PropTypes.number.isRequired,
      tValue: PropTypes.arrayOf(PropTypes.number).isRequired,
    }).isRequired,
  }),
  endDate: PropTypes.number,
  expanded: PropTypes.bool.isRequired,
  pipelineState: PropTypes.object.isRequired,
  pluginState: PropTypes.object.isRequired,
  startDate: PropTypes.number.isRequired,
  toggleCollapse: PropTypes.func.isRequired,
  usernames: PropTypes.array.isRequired,
  userErrors: PropTypes.array.isRequired,
};
<commit_msg>Alpha-sort covariates in result table.<commit_after>import React, { PropTypes } from 'react';
import { Collapse, Label } from 'react-bootstrap';
import classNames from 'classnames';
import moment from 'moment';
import { camelCase, reduce } from 'lodash';

import ConsortiumResultMeta from './consortium-result-meta';
import ConsortiumResultTable from './consortium-result-table';

export default function ConsortiumResult({
  computationInputs,
  complete,
  computation,
  data,
  endDate,
  expanded,
  pluginState,
  startDate,
  toggleCollapse,
  userErrors,
  usernames,
}) {
  let computationOutput;
  let dataOutput;
  let errors;
  let heading;
  let indicator;

  if (userErrors.length) {
    indicator = Error!;
  } else if (complete) {
    indicator = Complete!;
  } else {
    indicator = In Progress;
  }

  if (userErrors.length) {
    errors = (

User errors:

        {userErrors.map((error, i) => {
          return
{error}
;
        })}

    );
  }

  if (computation) {
    computationOutput = (
      <ConsortiumResultMeta
        computation={computation}
        computationInputs={computationInputs}
        step={pluginState['group-step'].step}
        usernames={usernames}
      />
    );
  }

  if (data) {
    /**
     * @todo This assumes covariates are placed at a specific location in
     * `computationInputs`. Don't hard-code this!
     */
    const covariatesIndex =
      computation.name === 'decentralized-single-shot-ridge-regression' ?
      2 :
      3;

    const covariates = computationInputs[0][covariatesIndex]
      .slice(0)
      .sort(ConsortiumResult.sortCovariates)
      .map(x => x.name);

    dataOutput = (

        {reduce(data, (memo, item, prop) => {
          /**
           * Computations store users' data in numeric properties and global
           * data under the `global` property.
           */
          if (prop === 'global') {
            return [
              <ConsortiumResultTable
                betaVector={item.betaVector}
                covariates={covariates}
                degreesOfFreedom={item.degreesOfFreedom}
                key={prop}
                name={'Global'}
                pValue={item.pValue}
                rSquared={item.rSquared}
                tValue={item.tValue}
              />,
              ...memo,
            ];
          } else if (Number(prop) == prop) { // eslint-disable-line eqeqeq
            return [
              ...memo,
              <ConsortiumResultTable
                betaVector={item.betaVector}
                covariates={covariates}
                degreesOfFreedom={item.degreesOfFreedom}
                key={prop}
                name={usernames[prop]}
                pValue={item.pValueOriginal}
                rSquared={item.rSquaredOriginal}
                tValue={item.tValueOriginal}
              />,
            ];
          }

          return memo;
        }, [])}

    );
  }

  if (endDate) {
    heading = `Ended ${moment(endDate).fromNow()}`;
  } else {
    heading = `Started ${moment(startDate).fromNow()}`;
  }

  return (

            {heading}
            {' '}
            {indicator}

      <Collapse in={expanded}>

          {computationOutput}
          {errors}
          {dataOutput}

      </Collapse>

  );
}

ConsortiumResult.displayName = 'ConsortiumResult';

ConsortiumResult.propTypes = {
  complete: PropTypes.bool.isRequired,
  computation: PropTypes.shape({
    meta: PropTypes.shape({
      name: PropTypes.string.isRequired,
    }),
    version: PropTypes.string.isRequired,
  }).isRequired,
  computationInputs: PropTypes.arrayOf(PropTypes.arrayOf(
    PropTypes.oneOfType([
      PropTypes.arrayOf(PropTypes.string),
      PropTypes.arrayOf(PropTypes.object),
      PropTypes.number,
    ])
  )).isRequired,
  data: PropTypes.shape({
    global: PropTypes.shape({
      betaVector: PropTypes.arrayOf(PropTypes.number).isRequired,
      degreesOfFreedom: PropTypes.number.isRequired,
      pValue: PropTypes.arrayOf(PropTypes.number).isRequired,
      rSquared: PropTypes.number.isRequired,
      tValue: PropTypes.arrayOf(PropTypes.number).isRequired,
    }).isRequired,
  }),
  endDate: PropTypes.number,
  expanded: PropTypes.bool.isRequired,
  pipelineState: PropTypes.object.isRequired,
  pluginState: PropTypes.object.isRequired,
  startDate: PropTypes.number.isRequired,
  toggleCollapse: PropTypes.func.isRequired,
  usernames: PropTypes.array.isRequired,
  userErrors: PropTypes.array.isRequired,
};

ConsortiumResult.sortCovariates = ({ name: a }, { name: b }) =>
  camelCase(a) > camelCase(b);
<|endoftext|>


<commit_before>'use strict'
module.exports = function (protocol, key) {
  describe('reconnect', function () {
    var a, b, receiver
    var Hub = require('../../lib')
    var mock = key === 'mock'

    it('can create multiple hubs', function () {
      a = new Hub({
        key: 'server_reconnect_a',
        adapter: {
          id: 'server_reconnect_a',
          inject: protocol
        }
      })
      b = new Hub({
        key: 'server_reconnect_b',
        adapter: {
          id: 'server_reconnect_b',
          inject: protocol
        }
      })
      receiver = new Hub({
        key: 'receiver_reconnect',
        adapter: {
          id: 'receiver_reconnect',
          inject: protocol,
          [key]: {}
        }
      })
      a.adapter.set({ [key]: { server: mock ? 'server_reconnect_a' : 6002 } })
      b.adapter.set({ [key]: { server: mock ? 'server_reconnect_b' : 6003 } })
    })

    it('can connect to server_a', function (done) {
      receiver.adapter[key].once('connect', function () {
        a.get('clients', {}).once(function () {
          expect(a.clients).to.have.property('receiver_reconnect')
          done()
        })
      })
      receiver.adapter[key].val = mock ? 'server_reconnect_a' : 'ws://localhost:6002'
    })

    it('connected should be true', function () {
      expect(receiver.adapter[key].connected.val).to.equal(true)
    })

    it('can connect to another server server_b', function (done) {
      receiver.adapter[key].once('connect', function () {
        // console.log('ok ok ok ok ok')
        // expect(b.clients).to.have.property('receiver_reconnect').which.is.ok
      })

      b.get('clients', {}).once(function () {
        // console.log('ok ok ok ok ok')
        expect(a.clients['receiver_reconnect']).to.not.be.ok
        done()
      })
      receiver.adapter[key].val = mock ? 'server_reconnect_b' : 'ws://localhost:6003'
    })

    it('can connect to another server server_a', function (done) {
      receiver.adapter[key].once('connect', function () {
        // expect(a.clients).to.have.property('receiver_reconnect').which.is.ok
        // expect(b.clients['return  ;eceiver_reconnect']).to.not.be.ok
        done()
      })
      receiver.adapter[key].val = mock ? 'server_reconnect_a' : 'ws://localhost:6002'
    })

    it('server connections gets removed, client reconnects after 1 attempt', function (done) {
      receiver.adapter[key].once('close', function () {
        expect(receiver.adapter[key].connected.val).to.equal(false)
        this.once('reconnect', function () {
          this.once('connect', done)
        })
      })
      // does not result in a close fire
      // make a protocol .close method or something or disconnect
      // client.disconnect // protocol.disconnect
      a.clients['receiver_reconnect'].connection.origin.remove()
    })
  })
}
<commit_msg>found error in websockets setting client when reconnecting<commit_after>'use strict'
module.exports = function (protocol, key) {
  describe('reconnect', function () {
    var a, b, receiver
    var Hub = require('../../lib')
    var mock = key === 'mock'

    it('can create multiple hubs', function () {
      a = new Hub({
        key: 'server_reconnect_a',
        adapter: {
          id: 'server_reconnect_a',
          inject: protocol
        }
      })
      b = new Hub({
        key: 'server_reconnect_b',
        adapter: {
          id: 'server_reconnect_b',
          inject: protocol
        }
      })
      receiver = new Hub({
        key: 'receiver_reconnect',
        adapter: {
          id: 'receiver_reconnect',
          inject: protocol,
          [key]: {}
        }
      })
      a.adapter.set({ [key]: { server: mock ? 'server_reconnect_a' : 6002 } })
      b.adapter.set({ [key]: { server: mock ? 'server_reconnect_b' : 6003 } })
    })

    it('can connect to server_a', function (done) {
      receiver.adapter[key].once('connect', function () {
        a.get('clients', {}).once(function () {
          expect(a.clients).to.have.property('receiver_reconnect')
          done()
        })
      })
      receiver.adapter[key].val = mock ? 'server_reconnect_a' : 'ws://localhost:6002'
    })

    it('connected should be true', function () {
      expect(receiver.adapter[key].connected.val).to.equal(true)
    })

    it('can connect to another server server_b', function (done) {
      receiver.adapter[key].once('connect', function () {
        expect(a.clients['receiver_reconnect']).to.not.be.ok
        b.get('clients', {}).once(function () {
          expect(b.clients).to.have.property('receiver_reconnect').which.is.ok
          done()
        })
      })
      receiver.adapter[key].val = mock ? 'server_reconnect_b' : 'ws://localhost:6003'
    })

    it('can connect to another server server_a', function (done) {
      receiver.adapter[key].once('connect', function () {
        a.get('clients', {}).once(function () {
          expect(a.clients).to.have.property('receiver_reconnect')
          expect(b.clients['receiver_reconnect']).to.not.be.ok
          done()
        })
      })
      receiver.adapter[key].val = mock ? 'server_reconnect_a' : 'ws://localhost:6002'
    })

    it('server connections gets removed, client reconnects after 1 attempt', function (done) {
      receiver.adapter[key].once('close', function () {
        expect(receiver.adapter[key].connected.val).to.equal(false)
        this.once('reconnect', function () {
          this.once('connect', done)
        })
      })
      a.clients['receiver_reconnect'].connection.origin.remove()
    })
  })
}
<|endoftext|>


<commit_before>var debug = require('debug')('connect:cachify-static');
var parseUrl = require('parseurl');
var onHeaders = require('on-headers');
var url = require('url');
var hashStore = require('./hash-store');

var store;

var format = {
  path: function(path, hash) {
    return '/' + hash + path;
  },
  name: function(path, hash) {
    var index = path.lastIndexOf('/') + 1;
    return path.slice(0, index) + hash + '-' + path.slice(index);
  }
};

function cachify(path, integrity) {
  return store.cachify(path, integrity);
}

function filter(pattern) {
  return store.filter(pattern);
}

function init(root, opts) {
  opts = opts || {};
  opts.match = opts.match || /\.(js|css|svg|eot|woff|ttf|cur|png|gif|jpg)$/;

  if (typeof opts.format === 'string') {
    opts.format = format[opts.format];
  }
  if (typeof opts.format !== 'function') {
    opts.format = format.path;
  }

  store = hashStore(root, opts.match, opts.format);
}

exports = module.exports = function(root, opts) {
  opts = opts || {};
  init(root, opts);

  return function cachifyStatic(req, res, next) {
    var parsedUrl = parseUrl(req);
    var path = store.getPath(parsedUrl.pathname);

    if (res.locals) {
      res.locals.cachify = cachify;
    }

    if (!path) {
      return next();
    }
    // this is where magic happens
    res.setHeader('Cache-Control', 'public, max-age=31536000');
    parsedUrl.pathname = path;
    req.url = url.format(parsedUrl);
    debug('Updated URL: %s', req.url);

    if (opts.control_headers) {
      // strip cache related headers
      onHeaders(res, function () {
        ['ETag', 'Last-Modified'].forEach(res.removeHeader, res);
      });
    }
    next();
  };
};

exports.cachify = cachify;
exports.filter = filter;
exports.init = init;
<commit_msg>add .webp to the default list of cachified extentions<commit_after>var debug = require('debug')('connect:cachify-static');
var parseUrl = require('parseurl');
var onHeaders = require('on-headers');
var url = require('url');
var hashStore = require('./hash-store');

var store;

var format = {
  path: function(path, hash) {
    return '/' + hash + path;
  },
  name: function(path, hash) {
    var index = path.lastIndexOf('/') + 1;
    return path.slice(0, index) + hash + '-' + path.slice(index);
  }
};

function cachify(path, integrity) {
  return store.cachify(path, integrity);
}

function filter(pattern) {
  return store.filter(pattern);
}

function init(root, opts) {
  opts = opts || {};
  opts.match = opts.match || /\.(js|css|svg|eot|woff|ttf|cur|png|gif|jpg|webp)$/;

  if (typeof opts.format === 'string') {
    opts.format = format[opts.format];
  }
  if (typeof opts.format !== 'function') {
    opts.format = format.path;
  }

  store = hashStore(root, opts.match, opts.format);
}

exports = module.exports = function(root, opts) {
  opts = opts || {};
  init(root, opts);

  return function cachifyStatic(req, res, next) {
    var parsedUrl = parseUrl(req);
    var path = store.getPath(parsedUrl.pathname);

    if (res.locals) {
      res.locals.cachify = cachify;
    }

    if (!path) {
      return next();
    }
    // this is where magic happens
    res.setHeader('Cache-Control', 'public, max-age=31536000');
    parsedUrl.pathname = path;
    req.url = url.format(parsedUrl);
    debug('Updated URL: %s', req.url);

    if (opts.control_headers) {
      // strip cache related headers
      onHeaders(res, function () {
        ['ETag', 'Last-Modified'].forEach(res.removeHeader, res);
      });
    }
    next();
  };
};

exports.cachify = cachify;
exports.filter = filter;
exports.init = init;
<|endoftext|>


<commit_before>import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { unstable_composeClasses as composeClasses } from '@mui/base';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
import ButtonBase from '../ButtonBase';
import unsupportedProp from '../utils/unsupportedProp';
import bottomNavigationActionClasses, {
  getBottomNavigationActionUtilityClass,
} from './bottomNavigationActionClasses';

const useUtilityClasses = (ownerState) => {
  const { classes, showLabel, selected } = ownerState;

  const slots = {
    root: ['root', !showLabel && !selected && 'iconOnly', selected && 'selected'],
    label: ['label', !showLabel && !selected && 'iconOnly', selected && 'selected'],
  };

  return composeClasses(slots, getBottomNavigationActionUtilityClass, classes);
};

const BottomNavigationActionRoot = styled(ButtonBase, {
  name: 'MuiBottomNavigationAction',
  slot: 'Root',
  overridesResolver: (props, styles) => {
    const { ownerState } = props;

    return [styles.root, !ownerState.showLabel && !ownerState.selected && styles.iconOnly];
  },
})(({ theme, ownerState }) => ({
  transition: theme.transitions.create(['color', 'padding-top'], {
    duration: theme.transitions.duration.short,
  }),
  padding: '6px 12px 8px',
  minWidth: 80,
  maxWidth: 168,
  color: theme.palette.text.secondary,
  flexDirection: 'column',
  flex: '1',
  ...(!ownerState.showLabel &&
    !ownerState.selected && {
      paddingTop: 16,
    }),
  [`&.${bottomNavigationActionClasses.selected}`]: {
    paddingTop: 6,
    color: theme.palette.primary.main,
  },
}));

const BottomNavigationActionLabel = styled('span', {
  name: 'MuiBottomNavigationAction',
  slot: 'Label',
  overridesResolver: (props, styles) => styles.label,
})(({ theme, ownerState }) => ({
  fontFamily: theme.typography.fontFamily,
  fontSize: theme.typography.pxToRem(12),
  opacity: 1,
  transition: 'font-size 0.2s, opacity 0.2s',
  transitionDelay: '0.1s',
  ...(!ownerState.showLabel &&
    !ownerState.selected && {
      opacity: 0,
      transitionDelay: '0s',
    }),
  [`&.${bottomNavigationActionClasses.selected}`]: {
    fontSize: theme.typography.pxToRem(14),
  },
}));

const BottomNavigationAction = React.forwardRef(function BottomNavigationAction(inProps, ref) {
  const props = useThemeProps({ props: inProps, name: 'MuiBottomNavigationAction' });
  const {
    className,
    icon,
    label,
    onChange,
    onClick,
    // eslint-disable-next-line react/prop-types -- private, always overridden by BottomNavigation
    selected,
    showLabel,
    value,
    ...other
  } = props;

  const ownerState = props;
  const classes = useUtilityClasses(ownerState);

  const handleChange = (event) => {
    if (onChange) {
      onChange(event, value);
    }

    if (onClick) {
      onClick(event);
    }
  };

  return (
    <BottomNavigationActionRoot
      ref={ref}
      className={clsx(classes.root, className)}
      focusRipple
      onClick={handleChange}
      ownerState={ownerState}
      {...other}
    >
      {icon}
      <BottomNavigationActionLabel className={classes.label} ownerState={ownerState}>
        {label}
      </BottomNavigationActionLabel>
    </BottomNavigationActionRoot>
  );
});

BottomNavigationAction.propTypes /* remove-proptypes */ = {
  // ----------------------------- Warning --------------------------------
  // | These PropTypes are generated from the TypeScript type definitions |
  // |     To update them edit the d.ts file and run "yarn proptypes"     |
  // ----------------------------------------------------------------------
  /**
   * This prop isn't supported.
   * Use the `component` prop if you need to change the children structure.
   */
  children: unsupportedProp,
  /**
   * Override or extend the styles applied to the component.
   */
  classes: PropTypes.object,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * The icon to display.
   */
  icon: PropTypes.node,
  /**
   * The label element.
   */
  label: PropTypes.node,
  /**
   * @ignore
   */
  onChange: PropTypes.func,
  /**
   * @ignore
   */
  onClick: PropTypes.func,
  /**
   * If `true`, the `BottomNavigationAction` will show its label.
   * By default, only the selected `BottomNavigationAction`
   * inside `BottomNavigation` will show its label.
   *
   * The prop defaults to the value (`false`) inherited from the parent BottomNavigation component.
   */
  showLabel: PropTypes.bool,
  /**
   * The system prop that allows defining system overrides as well as additional CSS styles.
   */
  sx: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
    PropTypes.func,
    PropTypes.object,
  ]),
  /**
   * You can provide your own value. Otherwise, we fallback to the child position index.
   */
  value: PropTypes.any,
};

export default BottomNavigationAction;
<commit_msg>[BottomNavigation] Action icon `padding` fix  (#32030)<commit_after>import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { unstable_composeClasses as composeClasses } from '@mui/base';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
import ButtonBase from '../ButtonBase';
import unsupportedProp from '../utils/unsupportedProp';
import bottomNavigationActionClasses, {
  getBottomNavigationActionUtilityClass,
} from './bottomNavigationActionClasses';

const useUtilityClasses = (ownerState) => {
  const { classes, showLabel, selected } = ownerState;

  const slots = {
    root: ['root', !showLabel && !selected && 'iconOnly', selected && 'selected'],
    label: ['label', !showLabel && !selected && 'iconOnly', selected && 'selected'],
  };

  return composeClasses(slots, getBottomNavigationActionUtilityClass, classes);
};

const BottomNavigationActionRoot = styled(ButtonBase, {
  name: 'MuiBottomNavigationAction',
  slot: 'Root',
  overridesResolver: (props, styles) => {
    const { ownerState } = props;

    return [styles.root, !ownerState.showLabel && !ownerState.selected && styles.iconOnly];
  },
})(({ theme, ownerState }) => ({
  transition: theme.transitions.create(['color', 'padding-top'], {
    duration: theme.transitions.duration.short,
  }),
  padding: '0px 12px',
  minWidth: 80,
  maxWidth: 168,
  color: theme.palette.text.secondary,
  flexDirection: 'column',
  flex: '1',
  ...(!ownerState.showLabel &&
    !ownerState.selected && {
      paddingTop: 14,
    }),
  ...(!ownerState.showLabel &&
    !ownerState.selected &&
    !ownerState.label && {
      paddingTop: 0,
    }),
  [`&.${bottomNavigationActionClasses.selected}`]: {
    color: theme.palette.primary.main,
  },
}));

const BottomNavigationActionLabel = styled('span', {
  name: 'MuiBottomNavigationAction',
  slot: 'Label',
  overridesResolver: (props, styles) => styles.label,
})(({ theme, ownerState }) => ({
  fontFamily: theme.typography.fontFamily,
  fontSize: theme.typography.pxToRem(12),
  opacity: 1,
  transition: 'font-size 0.2s, opacity 0.2s',
  transitionDelay: '0.1s',
  ...(!ownerState.showLabel &&
    !ownerState.selected && {
      opacity: 0,
      transitionDelay: '0s',
    }),
  [`&.${bottomNavigationActionClasses.selected}`]: {
    fontSize: theme.typography.pxToRem(14),
  },
}));

const BottomNavigationAction = React.forwardRef(function BottomNavigationAction(inProps, ref) {
  const props = useThemeProps({ props: inProps, name: 'MuiBottomNavigationAction' });
  const {
    className,
    icon,
    label,
    onChange,
    onClick,
    // eslint-disable-next-line react/prop-types -- private, always overridden by BottomNavigation
    selected,
    showLabel,
    value,
    ...other
  } = props;

  const ownerState = props;
  const classes = useUtilityClasses(ownerState);

  const handleChange = (event) => {
    if (onChange) {
      onChange(event, value);
    }

    if (onClick) {
      onClick(event);
    }
  };

  return (
    <BottomNavigationActionRoot
      ref={ref}
      className={clsx(classes.root, className)}
      focusRipple
      onClick={handleChange}
      ownerState={ownerState}
      {...other}
    >
      {icon}
      <BottomNavigationActionLabel className={classes.label} ownerState={ownerState}>
        {label}
      </BottomNavigationActionLabel>
    </BottomNavigationActionRoot>
  );
});

BottomNavigationAction.propTypes /* remove-proptypes */ = {
  // ----------------------------- Warning --------------------------------
  // | These PropTypes are generated from the TypeScript type definitions |
  // |     To update them edit the d.ts file and run "yarn proptypes"     |
  // ----------------------------------------------------------------------
  /**
   * This prop isn't supported.
   * Use the `component` prop if you need to change the children structure.
   */
  children: unsupportedProp,
  /**
   * Override or extend the styles applied to the component.
   */
  classes: PropTypes.object,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * The icon to display.
   */
  icon: PropTypes.node,
  /**
   * The label element.
   */
  label: PropTypes.node,
  /**
   * @ignore
   */
  onChange: PropTypes.func,
  /**
   * @ignore
   */
  onClick: PropTypes.func,
  /**
   * If `true`, the `BottomNavigationAction` will show its label.
   * By default, only the selected `BottomNavigationAction`
   * inside `BottomNavigation` will show its label.
   *
   * The prop defaults to the value (`false`) inherited from the parent BottomNavigation component.
   */
  showLabel: PropTypes.bool,
  /**
   * The system prop that allows defining system overrides as well as additional CSS styles.
   */
  sx: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
    PropTypes.func,
    PropTypes.object,
  ]),
  /**
   * You can provide your own value. Otherwise, we fallback to the child position index.
   */
  value: PropTypes.any,
};

export default BottomNavigationAction;
<|endoftext|>