<commit_before>
'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;

const scriptRules = require('./script.json');

var myDate = new Date();
var groet = '';

/* hour is before noon */
if ( myDate.getHours() < 12 )
{
groet = "Goeiemorgen 🌝";
}
else  /* Hour is from noon to 5pm (actually to 5:59 pm) */
if ( myDate.getHours() >= 12 && myDate.getHours() <= 17 )
{
groet = "Goedendag 🌞";
}
else  /* the hour is after 5pm, so it is between 6pm and midnight */
if ( myDate.getHours() > 17 && myDate.getHours() <= 24 )
{
groet = "Goedenavond 🌙";
}
else
if (myDate.getHours() > 20  || myDate.getHours() <= 8 )
{
  groet = "Goedenavond 🌙  Bedankt voor je bezoek maar op dit moment is Independer echter gesloten. 🕘 Uiteraard kun je met onze IndyBot verder praten maar er is helaas niemand die jouw vraag specifiek kan beantwoorden. Je kan je vraag ook doormailen 📩 naar info@independer. Dan komt het altijd goed.";
}
else  /* the hour is not between 0 and 24, so something is wrong */
{
groet = "Welkom. ";
}

function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

module.exports = new Script({

 processing: {
prompt: (bot) => bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)'),
 receive: () => 'processing'
 },

start: {
    receive: (bot,message) => {
        const opening = message.text.trim().toUpperCase();
        return bot.say(`${groet}... Wat voor soort hypotheek zoek je? `)
        .then(() => bot.say(`![](http://www.bieg.nl/beeld/woningen.jpg)`))
        .then(() => bot.say(`%[Starters Hypotheek](postback:hypotheektype_starter) %[Nieuwe hypotheek](postback:hypotheektype_nieuw) %[Hypotheek oversluiten](postback:hypotheektype_oversluiten)`))
        .then(() => 'selecteerHypotheek');
    }
},

selecteerHypotheek: {
    receive: (bot, message) => {
      switch(message.text) {
      case 'Hoi':
            return bot.say(`${groet} waar ben je naar op zoek? %[Starters hypotheek](postback:hypotheektype_starter) %[Nieuwe hypotheek](postback:hypotheektype_nieuw) %[Hypotheek oversluiten](postback:hypotheektype_oversluiten)`)
            .then(() => 'askName')
          break;
      case 'Starters Hypotheek':
          return bot.say(`Wat voor type woning zoek je? `)
          .then(() => bot.say(`%[🏬 Appartement](postback:hypotheekkeuze_appartement) %[🏠 Huis](postback:hypotheekkeuze_huis) %[📭 Vakantiewoning](postback:hypotheekkeuze_vakantiewoning)`))
          .then(() => 'woningType')
              break;
        case 'Nieuwe hypotheek':
          return bot.say(`😞 Helaas biedt Independer momenteel alleen Starters een hypotheek aan.`)
          .then(() => bot.say(`Via onderstaande link kun je de beste hypotheekadviseur voor jou vinden. %[💼 Zoek Hypotheek Adviseur](https://www.independer.nl/hypotheekadviseur/jelocatie.aspx)`))
            .then(() => 'finish')
          break;
        case 'Hypotheek oversluiten':
          return bot.say(`😟 Het spijt me maar op dit moment biedt Independer alleen  Starters een hypotheek.`)
          .then(()=> bot.say(`Als het allemaal wel zo ver is, wil je dan een update ontvangen? %[Ja, dat wil ik wel](postback:update_ja) %[Nee, dat hoeft niet](postback:update_nee)`))
            .then(() => 'updateOntvangen')
          break;
        default:
          return bot.say(`![](http://www.bieg.nl/beeld/speechbubble.gif)`)
            .then(() => 'processing')
          break;
      }
    }
},

updateOntvangen: {
    receive: (bot, message) => {
      switch(message.text) {
        case 'Ja, dat wil ik wel':
            return bot.say(`👍 Leuk, dan houd ik  je op de hoogte zodra er weer nieuws is.`)
            .then(() => 'update_ja');
          break;
          case 'Nee, dat hoeft niet':
              receive: () => 'bye'
              break;
          default:
            return bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)')
              .then(() => 'processing')
            break;
}
}
},

update_ja: {
  prompt: (bot) => bot.say('Wat is je email adres?'),
      receive: (bot, message) => {
          const emailVisitor=message.text.trim();
          return bot.setProp('emailVisitor', emailVisitor)
              .then(()  => bot.say(`Ok - dan hou ik je via ✉ ${emailVisitor} op de hoogte.`))
              .then(()  =>'lastCheck')
    }
},
update_nee: {
  receive: () => 'bye'
},

hypotheekkeuze_appartement: {
receive: (bot,message)  => {
    //  prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
    receive: () => 'askName'
}
},
hypotheekkeuze_huis: {
//    prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
  receive: () => 'askName'
},
hypotheekkeuze_vakantiewoning: {
//    prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
    receive: () => 'askName'
},
hypotheekStarter: {
    receive: () => 'askName'
},
hypotheektype_nieuw: {
    prompt: (bot) => bot.say('Independer biedt momenteel alleen voor Starters een hypotheek. Onderstaande link bied je meer informatie %[Hypotheek Adviseur](https://www.independer.nl/hypotheekadviseur/intro.aspx)'),
    receive: () => 'processing'
},

woningType: {
  receive: (bot, message) => {
    switch(message.text) {
        case '🏬 Appartement':
          return bot.say(`Nice!`)
          .then(() => 'vervolgVragen')
          break;
      case '🏠 Huis':
          return bot.say(`Leuk`)
          .then(() => 'vervolgVragen')
          break;
    case '📭 Vakantiewoning':
          return bot.say(`Gezellig`)
          .then(() => 'vervolgVragen')
          break;
      default:
        return bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)')
          .then(() => 'processing')
        break;
        }
    }
},

vervolgVragen: {
  prompt: (bot) => bot.say('Hoe heet je eigelijk? Dat maakt het praten een stuk makkelijker...'),
  receive: (bot, message) => {
      const name = message.text;
      return bot.setProp('name', name)
          .then(() => bot.say('Hoi ${name}. 📋 Ik heb nog wat vragen voor je om verder te kunnen.'))
          .then(() => 'processing');
    }
},

lastCheck: {
    prompt: (bot) => bot.say(' Is er nog iets waar ik  je bij kan helpen?  🚦  %[Ja, nou je het zegt ](postback:speak) %[Nee hoor](postback:bye)'),
          receive: (bot, message) => {
            switch(message.text) {
              case 'Nee hoor':
                  receive: ()  => 'bye'
                  break;
             case 'Ja':
                  receive: () => 'speak'
                  break;
            case 'Nee':
                  receive: () => 'bye'
                  break;
            default:
              return bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)')
                .then(() => 'processing')
              break;
            }
        }
},

bye: {
    prompt: (bot) => bot.say('Fijn je gesproken te hebben. Bedankt voor je tijd ⏲'),
    receive: ()  => 'finish'
},

// error: {
// prompt: (bot) => bot.say('Sorry - kun je dat nog eens zeggen?  Er ging iets mis...'),
// receive: () => ''
// },

finish: {
receive: () => 'finish'
},

  speak: {
          receive: (bot, message) => {

              let upperText = message.text.trim().toUpperCase();

              function updateSilent() {
                  switch (upperText) {
                      case "CONNECT ME":
                          return bot.setProp("silent", true);
                      case "DISCONNECT":
                          return bot.setProp("silent", false);
                      default:
                          return Promise.resolve();
                  }
              }

              function getSilent() {
                  return bot.getProp("silent");
              }

              function processMessage(isSilent) {
                  if (isSilent) {
                      return Promise.resolve("speak");
                  }

                  if (!_.has(scriptRules, upperText)) {
                      return bot.say(`So, I'm good at structured conversations but stickers, emoji and sentences still confuse me. Say 'more' to chat about something else.`).then(() => 'speak');
                  }

                  var response = scriptRules[upperText];
                  var lines = response.split('\n');

                  var p = Promise.resolve();
                  _.each(lines, function(line) {
                      line = line.trim();
                      p = p.then(function() {
                          console.log(line);
                          return wait(50).then(function() {
                              return bot.say(line);
                          });
                      });
                  });

                  return p.then(() => 'speak');
              }

              return updateSilent()
                  .then(getSilent)
                  .then(processMessage);
          }
      }
});
<commit_msg>script<commit_after>
'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;

const scriptRules = require('./script.json');

var myDate = new Date();
var groet = '';

/* hour is before noon */
if ( myDate.getHours() < 12 )
{
groet = "Goeiemorgen 🌝";
}
else  /* Hour is from noon to 5pm (actually to 5:59 pm) */
if ( myDate.getHours() >= 12 && myDate.getHours() <= 17 )
{
groet = "Goedendag 🌞";
}
else  /* the hour is after 5pm, so it is between 6pm and midnight */
if ( myDate.getHours() > 17 && myDate.getHours() <= 24 )
{
groet = "Goedenavond 🌙";
}
else
if (myDate.getHours() > 20  || myDate.getHours() <= 8 )
{
  groet = "Goedenavond 🌙  Bedankt voor je bezoek maar op dit moment is Independer echter gesloten. 🕘 Uiteraard kun je met onze IndyBot verder praten maar er is helaas niemand die jouw vraag specifiek kan beantwoorden. Je kan je vraag ook doormailen 📩 naar info@independer. Dan komt het altijd goed.";
}
else  /* the hour is not between 0 and 24, so something is wrong */
{
groet = "Welkom. ";
}

function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

module.exports = new Script({

 processing: {
prompt: (bot) => bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)'),
 receive: () => 'processing'
 },

start: {
    receive: (bot,message) => {
        const opening = message.text.trim().toUpperCase();
        return bot.say(`${groet}... Wat voor soort hypotheek zoek je? `)
        .then(() => bot.say(`![](http://www.bieg.nl/beeld/woningen.jpg)`))
        .then(() => bot.say(`%[Starters Hypotheek](postback:hypotheektype_starter) %[Nieuwe hypotheek](postback:hypotheektype_nieuw) %[Hypotheek oversluiten](postback:hypotheektype_oversluiten)`))
        .then(() => 'selecteerHypotheek');
    }
},

selecteerHypotheek: {
    receive: (bot, message) => {
      switch(message.text) {
      case 'Hoi':
            return bot.say(`${groet} waar ben je naar op zoek? %[Starters hypotheek](postback:hypotheektype_starter) %[Nieuwe hypotheek](postback:hypotheektype_nieuw) %[Hypotheek oversluiten](postback:hypotheektype_oversluiten)`)
            .then(() => 'askName')
          break;
      case 'Starters Hypotheek':
          return bot.say(`Wat voor type woning zoek je? `)
          .then(() => bot.say(`%[🏬 Appartement](postback:hypotheekkeuze_appartement) %[🏠 Huis](postback:hypotheekkeuze_huis) %[📭 Vakantiewoning](postback:hypotheekkeuze_vakantiewoning)`))
          .then(() => 'woningType')
              break;
        case 'Nieuwe hypotheek':
          return bot.say(`😞 Helaas biedt Independer momenteel alleen Starters een hypotheek aan.`)
          .then(() => bot.say(`Via onderstaande link kun je de beste hypotheekadviseur voor jou vinden. %[💼 Zoek Hypotheek Adviseur](https://www.independer.nl/hypotheekadviseur/jelocatie.aspx)`))
            .then(() => 'finish')
          break;
        case 'Hypotheek oversluiten':
          return bot.say(`😟 Het spijt me maar op dit moment biedt Independer alleen  Starters een hypotheek.`)
          .then(()=> bot.say(`Als het allemaal wel zo ver is, wil je dan een update ontvangen? %[Ja, dat wil ik wel](postback:update_ja) %[Nee, dat hoeft niet](postback:update_nee)`))
            .then(() => 'updateOntvangen')
          break;
        default:
          return bot.say(`![](http://www.bieg.nl/beeld/speechbubble.gif)`)
            .then(() => 'processing')
          break;
      }
    }
},

updateOntvangen: {
    receive: (bot, message) => {
      switch(message.text) {
        case 'Ja, dat wil ik wel':
            return bot.say(`👍 Leuk, dan houd ik  je op de hoogte zodra er weer nieuws is.`)
            .then(() => 'update_ja');
          break;
          case 'Nee, dat hoeft niet':
              receive: () => 'bye'
              break;
          default:
            receive => 'processing'
            break;
}
}
},

update_ja: {
  prompt: (bot) => bot.say('Wat is je email adres?'),
      receive: (bot, message) => {
          const emailVisitor=message.text.trim();
          return bot.setProp('emailVisitor', emailVisitor)
              .then(()  => bot.say(`Ok - dan hou ik je via ✉ ${emailVisitor} op de hoogte.`))
              .then(()  =>'lastCheck')
    }
},
update_nee: {
  receive: () => 'bye'
},

hypotheekkeuze_appartement: {
receive: (bot,message)  => {
    //  prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
    receive: () => 'askName'
}
},
hypotheekkeuze_huis: {
//    prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
  receive: () => 'askName'
},
hypotheekkeuze_vakantiewoning: {
//    prompt: (bot) => bot.say(`![](http:www.bieg.nl/beeld/appartement.jpg)`)
    receive: () => 'askName'
},
hypotheekStarter: {
    receive: () => 'askName'
},
hypotheektype_nieuw: {
    prompt: (bot) => bot.say('Independer biedt momenteel alleen voor Starters een hypotheek. Onderstaande link bied je meer informatie %[Hypotheek Adviseur](https://www.independer.nl/hypotheekadviseur/intro.aspx)'),
    receive: () => 'processing'
},

woningType: {
  receive: (bot, message) => {
    switch(message.text) {
        case '🏬 Appartement':
          return bot.say(`Nice!`)
          .then(() => 'vervolgVragen')
          break;
      case '🏠 Huis':
          return bot.say(`Leuk`)
          .then(() => 'vervolgVragen')
          break;
    case '📭 Vakantiewoning':
          return bot.say(`Gezellig`)
          .then(() => 'vervolgVragen')
          break;
      default:
        receive => 'processing'
        break;
        }
    }
},

vervolgVragen: {
  prompt: (bot) => bot.say('Hoe heet je eigelijk? Dat maakt het praten een stuk makkelijker...'),
  receive: (bot, message) => {
      const name = message.text;
      return bot.setProp('name', name)
          .then(() => bot.say('Hoi ${name}. 📋 Ik heb nog wat vragen voor je om verder te kunnen.'))
          .then(() => 'processing');
    }
},

lastCheck: {
    prompt: (bot) => bot.say(' Is er nog iets waar ik  je bij kan helpen?  🚦  %[Ja, nou je het zegt ](postback:speak) %[Nee hoor](postback:bye)'),
          receive: (bot, message) => {
            switch(message.text) {
              case 'Nee hoor':
                  receive: ()  => 'bye'
                  break;
             case 'Ja':
                  receive: () => 'speak'
                  break;
            case 'Nee':
                  receive: () => 'bye'
                  break;
            default:
              return bot.say('![](http://www.bieg.nl/beeld/speechbubble.gif)')
                .then(() => 'processing')
              break;
            }
        }
},

bye: {
    prompt: (bot) => bot.say('Fijn je gesproken te hebben. Bedankt voor je tijd ⏲'),
    receive: ()  => 'finish'
},

// error: {
// prompt: (bot) => bot.say('Sorry - kun je dat nog eens zeggen?  Er ging iets mis...'),
// receive: () => ''
// },

finish: {
receive: () => 'finish'
},

  speak: {
          receive: (bot, message) => {

              let upperText = message.text.trim().toUpperCase();

              function updateSilent() {
                  switch (upperText) {
                      case "CONNECT ME":
                          return bot.setProp("silent", true);
                      case "DISCONNECT":
                          return bot.setProp("silent", false);
                      default:
                          return Promise.resolve();
                  }
              }

              function getSilent() {
                  return bot.getProp("silent");
              }

              function processMessage(isSilent) {
                  if (isSilent) {
                      return Promise.resolve("speak");
                  }

                  if (!_.has(scriptRules, upperText)) {
                      return bot.say(`So, I'm good at structured conversations but stickers, emoji and sentences still confuse me. Say 'more' to chat about something else.`).then(() => 'speak');
                  }

                  var response = scriptRules[upperText];
                  var lines = response.split('\n');

                  var p = Promise.resolve();
                  _.each(lines, function(line) {
                      line = line.trim();
                      p = p.then(function() {
                          console.log(line);
                          return wait(50).then(function() {
                              return bot.say(line);
                          });
                      });
                  });

                  return p.then(() => 'speak');
              }

              return updateSilent()
                  .then(getSilent)
                  .then(processMessage);
          }
      }
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';
const rp = require('request-promise');
const uuidv4 =  require('uuid/v4');
const foxycmd = 'foxycmd';
const foxycmderror = 'foxycmderror';
var SpotifyConn= require('./spotify');
const timezone = require('./libs/timezone');
var childProcess = require('child_process');

var logOpts = {
  logDirectory: __dirname + '/logs' ,
  fileNamePattern: 'foxy-<date>.log',
  dateFormat:'YYYY.MM.DD-HHa'
};

let decodeArgs = [
  'opusenc',
  '--raw',
  '--raw-rate',
  '16000',
  '--raw-bits',
  '16',
  '--raw-chan',
  '1',
  '--raw-endianness',
  '0',
  '-',
  '-'
];

var logger = require('simple-node-logger').createRollingFileLogger(logOpts);
logger.setLevel('debug');

var gaVisitor = '';
var gUuid = '';

function Parser() {
}

Parser.prototype.setMetrics = function(visitor, uuid) {
  gaVisitor = visitor;
  gUuid = uuid;
}

const FOXY_COMMANDS = {
  'NONE': 'NONE',
  'NEXTSLIDE': 'NEXTSLIDE',
  'PREVIOUSSLIDE': 'PREVIOUSSLIDE',
  'SHUTUP': 'SHUTUP',
  'WEATHER': 'WEATHER',
  'BOOKMARK': 'BOOKMARK',
  'TIMER': 'TIMER',
  'SPOTIFY': 'SPOTIFY',
  'IOT': 'IOT',
  'POCKET': 'POCKET',
  'NPR': 'NPR',
  'GA': 'GA',
  'FEEDBACK': 'FEEDBACK'
};

const asrOptions = {
  uri: 'https://speaktome.services.mozilla.com',
  method: 'POST',
  body: '',
  headers: {'Content-Type': 'application/octet-stream'},
  qs: {'endofspeech': 'false', 'nbest': 10}
};

const aiOptions = {
  uri: 'https://api.api.ai/api/query',
  method: 'POST',
  body: '',
  headers: {
    'Authorization': 'Bearer af2bccd942c24fd68622efc6b4c8526c',
    'Content-Type': 'application/json'
  }
};
const weatherLink = 'http://api.openweathermap.org/data/2.5/weather?' +
  'APPID=aa9502b21136daabe9a2d556938ccfbe&units=imperial&q='
const weatherOptions = {
  uri: '',
  method: 'GET',
  body: '',
  headers: {
  }
};

const shimOptions = {
  uri: 'http://localhost:3000/command',
  method: 'POST',
  body: '',
  headers: {'Content-Type': 'application/json'}
};

Parser.prototype.parseResults = function(foxyBuffer, callback) {
  var utterance = '';
  var ga_params = {
    ec: foxycmd,
    uid: gUuid,
    cd1: gUuid
  };

  // Convert the audio to Opus.
  asrOptions.body = this.convertToOpus(foxyBuffer);
  // Send the speech buffer to Kaldi
  rp(asrOptions)
    .then(function(body) {
      const resBody = body && body.toString('utf8');
      var jsonResults = JSON.parse(resBody);
      if (jsonResults.status != 'ok') {
        logger.debug('Kaldi failed:' + jsonResults.status);
      }

      // Get results from Kaldi Speech rec. Format for Api.ai
      var speechBody = getAiBody(jsonResults);
      utterance = speechBody.query;
      aiOptions.body = JSON.stringify(speechBody);

      // chain to API.ai
      return rp(aiOptions);
    })
    .then(function(aiBody) {
      var payload = parseAIBody(aiBody, utterance);
      payload.utterance = utterance;
      if(payload.cmd == FOXY_COMMANDS.SPOTIFY) {
        let playlistBrowseUri = 'https://api.spotify.com/v1/browse/categories/'
          + payload.param + '/playlists';
        var spotifyCategoryPlaylistOptions = {
          uri: playlistBrowseUri,
          method: 'GET',
          headers: {'Authorization': 'Bearer ' + SpotifyConn.apiToken}
        };
        rp(spotifyCategoryPlaylistOptions)
          .then(function(body) {
            payload.param = parseSpotify(body);
            payload.utterance = cleanSpeech(payload);
            shimOptions.body = JSON.stringify(payload);
            if (payload.param == '') {
              ga_params.ec = 'foxycmderror';
              ga_params.ea = payload.cmd;
              ga_params.el = payload.utterance;
              gaVisitor.event(ga_params).send();
            } else {
              ga_params.ea = payload.cmd;
              ga_params.el = payload.param;
              gaVisitor.event(ga_params).send();
            }
            return rp(shimOptions);
          })
          .catch(function(err) {
            logger.debug('Spotify error:' + err);
            callback('Spotify error');
          });
      } else if (payload.cmd == FOXY_COMMANDS.IOT) {
        let iotUri =
          'https://localhost:4443/things/zwave-efbddb01-4/properties/on';
        var iotOptions = {
          uri: iotUri,
          method: 'PUT',
          rejectUnauthorized: false,
          headers: {
            'Authorization': 'Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZ\
              CI6IjM2NDNkNjdmLTQ1MzctNGEzMS04NmIxLTgyMDk0ZGI0ODU5NCJ9.eyJpYXQi\
              OjE1MDU5MjE3NzV9.C6wKLoieTsR7ZzOqYopKPDXntxYvxY5emb4nKFqXbdE0fL1\
              D8c2DTiRJOF2i4udTrQpIdks20q_TTDLoB-uZZA',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
          },
          body: ''
        };
        (payload.param2 == 'on')?
          iotOptions.body = JSON.stringify({'on': true}):
          iotOptions.body = JSON.stringify({'on': false});
        ga_params.ea = payload.cmd;
        ga_params.el = payload.param;
        ga_params.ev = (iotOptions ? 0 : 1);
        gaVisitor.event(ga_params).send();

        rp(iotOptions)
          .then(function(body) {
            logger.debug('body is:' + body);
            shimOptions.body = JSON.stringify(payload);
            payload.utterance = cleanSpeech(payload);
            shimOptions.body = JSON.stringify(payload);
            return rp(shimOptions);
          })
          .catch(function(err) {
            logger.debug('iot error is:' + err);
            ga_params.ec = foxycmderror;
            ga_params.ea = payload.cmd;
            ga_params.el = payload.utterance;
            gaVisitor.event(ga_params).send();
            callback('iot error');
          });
      } else if (payload.cmd == FOXY_COMMANDS.WEATHER) {
        weatherOptions.uri = weatherLink + payload.param;

        rp(weatherOptions)
          .then(function(body) {
            var jsonResults = JSON.parse(body);
            payload.param = jsonResults.name;             //City name
            payload.param2 = jsonResults.main.temp;       //Current temp
            payload.param3 = jsonResults.main.temp_min;   //Min temp
            payload.param4 = jsonResults.main.temp_max;   //Max temp
            payload.param5 = jsonResults.weather[0].main; //Description
            payload.utterance = cleanSpeech(payload);

            ga_params.ea = payload.cmd;
            ga_params.el = payload.param;
            gaVisitor.event(ga_params).send();

            return timezone.getLocaltime(jsonResults.coord);
          })
          .then(time => {
            payload.localTime = time;
            shimOptions.body = JSON.stringify(payload);
            return rp(shimOptions);
          })
          .catch(function(err) {
            logger.debug('weather error is:' + err);
            ga_params.ec = foxycmderror;
            ga_params.ea = payload.cmd;
            ga_params.el = payload.utterance;
            gaVisitor.event(ga_params).send();
            callback('weather error');
          });
      } else if(payload.cmd == FOXY_COMMANDS.NEXTSLIDE ||
          payload.cmd == FOXY_COMMANDS.PREVIOUSSLIDE) {
        ga_params.ea = payload.cmd;
        gaVisitor.event(ga_params).send();
        callback('ok');
      } else {
        payload.utterance = cleanSpeech(payload);
        shimOptions.body = JSON.stringify(payload);
        if (payload.cmd == FOXY_COMMANDS.NONE) {
          ga_params.ea = payload.cmd;
          ga_params.el = payload.utterance;
          gaVisitor.event(ga_params).send();
        }

        if(payload.cmd != FOXY_COMMANDS.NEXTSLIDE ||
          payload.cmd != FOXY_COMMANDS.PREVIOUSSLIDE) {
          return rp(shimOptions);
        }
      }
    })
    .then(function() {
       callback('ok');
       logger.debug('finished the chain');
    })
    .catch(function(err) {
      callback('error');
      gaVisitor.exception('Kaldi failed ' + err);
      logger.debug('Call failed' + err);
    });
}

Parser.prototype.convertToOpus = function(rawBuffer) {
  let args = decodeArgs;
  const opusdec = childProcess.spawn(args[0], args.slice(1),
    {stdio: ['pipe', 'pipe', 'pipe']});
  opusdec.stdin.write(rawBuffer);
  opusdec.stdin.end();
  return opusdec.stdout;
}

function parseSpotify(body) {
  const resBody = body && body.toString('utf8');
  logger.debug('GOT DATA FROM SPOTIFY');
  var jsonResults = JSON.parse(resBody);
  let plArray = jsonResults.playlists.items;
  var playlistId = '';
  for(var i = 0; i < plArray.length; i++) {
    var obj = plArray[i];
    if (obj.type == 'playlist') {
      logger.debug('Found a playlist: ' + obj.id);
      playlistId = obj.id;
      break;
    }
  }
  return playlistId;
}

String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}

function cleanSpeech(payload) {
  var lower = payload.utterance.toLowerCase();
  var final;
  switch (payload.cmd) {
    case FOXY_COMMANDS.WEATHER:
      final = '"What\'s the weather in ' +
        payload.param + '?"';
      break;
    // TODO: add next slide
    default:
      final = '"' + lower.capitalize() + '."';
      break;
  }
  return final;
}

function parseAIBody(aiBody, theUtterance) {
  let jsonBody = JSON.parse(aiBody);
  var payload = {
    cmd: 'none',
    param: 'none',
    param2: 'none',
  };

  var ga_params = {
    ec: foxycmd,
    uid: gUuid,
    cd1: gUuid
  };

  logger.debug(aiBody);
  //Determine the action from the API.AI intent parser
  switch (jsonBody.result.action) {
    case 'weather':
      logger.debug('weather is action');
      payload.cmd = FOXY_COMMANDS.WEATHER;
      payload.param = jsonBody.result.parameters['geo-city'];
      break;
    case 'timer':
      logger.debug('timer is action');
      payload.cmd = FOXY_COMMANDS.TIMER;
      payload.param = parseTimer(jsonBody.result);
      payload.param2 = jsonBody.result.parameters.any;

      ga_params.ea = payload.cmd;
      ga_params.el = payload.param2;
      ga_params.ev = payload.param;

      gaVisitor.event(ga_params).send();

      break;
    case 'play':
      payload.cmd = FOXY_COMMANDS.SPOTIFY;
      payload.param = jsonBody.result.parameters['music-genre'];
      break;
    case 'iot':
      payload.cmd = FOXY_COMMANDS.IOT;
      payload.param = jsonBody.result.parameters.rooms;
      payload.param2 = jsonBody.result.parameters.onoff;
      break;
    case 'pocket':
      payload.cmd = FOXY_COMMANDS.POCKET;
      ga_params.ea = payload.cmd;
      ga_params.el = 'add';
      gaVisitor.event(ga_params).send();
      break;
    case 'nextslide':
      payload.cmd = FOXY_COMMANDS.NEXTSLIDE;
      childProcess.exec('"/Applications/FoxyExtension/libs/node/bin/node" '
        + './keysend.js right');
      break;
    case 'lastslide':
      payload.cmd = FOXY_COMMANDS.PREVIOUSSLIDE;
      childProcess.exec('"/Applications/FoxyExtension/libs/node/bin/node" '
      + './keysend.js left');
      break;
    case 'npr':
      payload.cmd = FOXY_COMMANDS.NPR;
      ga_params.ea = payload.cmd;
      gaVisitor.event(ga_params).send();
      break;
    case 'feedback':
      payload.cmd = FOXY_COMMANDS.FEEDBACK;
      ga_params.ea = payload.cmd;
      ga_params.el = theUtterance;

      gaVisitor.event(ga_params).send();
      break;
    default:
      payload.cmd = FOXY_COMMANDS.NONE;
      break;
  }
  return payload;
}

function getAiBody(asrBody) {
  let conf = 0;
  let body = {
    'v':'20150910',
    'query': '',
    'lang':'en',
    'sessionId':  uuidv4(),
    'timezone':'2017-07-24T10:45:52-0400'
  };
  let text = '';
  for (var i in asrBody.data) {
    if (asrBody.data[i].confidence > conf) {
      conf = asrBody.data[i].confidence;
      text = asrBody.data[i].text;
    }
  }
  body.query = text;
  return body;
}

function parseTimer(result) {
  logger.debug('Entering parseTimer');
  var durationSecs = 0;
  if (!result.parameters.duration) {
    return durationSecs;
  }

  switch (result.parameters.duration.unit) {
    case 'min':
      durationSecs = result.parameters.duration.amount * 60;
      break;
    case 's':
      durationSecs = result.parameters.duration.amount;
      break;
    case 'h':
      durationSecs = result.parameters.duration.amount * 3600;
      break;
    case 'day':
      durationSecs = result.parameters.duration.amount * 86400;
      break;
    default:
      break;
  }

  return durationSecs;
}
module.exports = {
  Parser:Parser,
  FOXY_COMMANDS: FOXY_COMMANDS
};
<commit_msg>MUTE Command Support<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';
const rp = require('request-promise');
const uuidv4 =  require('uuid/v4');
const foxycmd = 'foxycmd';
const foxycmderror = 'foxycmderror';
var SpotifyConn= require('./spotify');
const timezone = require('./libs/timezone');
var childProcess = require('child_process');

var logOpts = {
  logDirectory: __dirname + '/logs' ,
  fileNamePattern: 'foxy-<date>.log',
  dateFormat:'YYYY.MM.DD-HHa'
};

let decodeArgs = [
  'opusenc',
  '--raw',
  '--raw-rate',
  '16000',
  '--raw-bits',
  '16',
  '--raw-chan',
  '1',
  '--raw-endianness',
  '0',
  '-',
  '-'
];

var logger = require('simple-node-logger').createRollingFileLogger(logOpts);
logger.setLevel('debug');

var gaVisitor = '';
var gUuid = '';

function Parser() {
}

Parser.prototype.setMetrics = function(visitor, uuid) {
  gaVisitor = visitor;
  gUuid = uuid;
}

const FOXY_COMMANDS = {
  'NONE': 'NONE',
  'NEXTSLIDE': 'NEXTSLIDE',
  'PREVIOUSSLIDE': 'PREVIOUSSLIDE',
  'SHUTUP': 'SHUTUP',
  'WEATHER': 'WEATHER',
  'BOOKMARK': 'BOOKMARK',
  'TIMER': 'TIMER',
  'SPOTIFY': 'SPOTIFY',
  'IOT': 'IOT',
  'POCKET': 'POCKET',
  'NPR': 'NPR',
  'GA': 'GA',
  'FEEDBACK': 'FEEDBACK'
};

const asrOptions = {
  uri: 'https://speaktome.services.mozilla.com',
  method: 'POST',
  body: '',
  headers: {'Content-Type': 'application/octet-stream'},
  qs: {'endofspeech': 'false', 'nbest': 10}
};

const aiOptions = {
  uri: 'https://api.api.ai/api/query',
  method: 'POST',
  body: '',
  headers: {
    'Authorization': 'Bearer af2bccd942c24fd68622efc6b4c8526c',
    'Content-Type': 'application/json'
  }
};
const weatherLink = 'http://api.openweathermap.org/data/2.5/weather?' +
  'APPID=aa9502b21136daabe9a2d556938ccfbe&units=imperial&q='
const weatherOptions = {
  uri: '',
  method: 'GET',
  body: '',
  headers: {
  }
};

const shimOptions = {
  uri: 'http://localhost:3000/command',
  method: 'POST',
  body: '',
  headers: {'Content-Type': 'application/json'}
};

Parser.prototype.parseResults = function(foxyBuffer, callback) {
  var utterance = '';
  var ga_params = {
    ec: foxycmd,
    uid: gUuid,
    cd1: gUuid
  };

  // Convert the audio to Opus.
  asrOptions.body = this.convertToOpus(foxyBuffer);
  // Send the speech buffer to Kaldi
  rp(asrOptions)
    .then(function(body) {
      const resBody = body && body.toString('utf8');
      var jsonResults = JSON.parse(resBody);
      if (jsonResults.status != 'ok') {
        logger.debug('Kaldi failed:' + jsonResults.status);
      }

      // Get results from Kaldi Speech rec. Format for Api.ai
      var speechBody = getAiBody(jsonResults);
      utterance = speechBody.query;
      aiOptions.body = JSON.stringify(speechBody);

      // chain to API.ai
      return rp(aiOptions);
    })
    .then(function(aiBody) {
      var payload = parseAIBody(aiBody, utterance);
      payload.utterance = utterance;
      if(payload.cmd == FOXY_COMMANDS.SPOTIFY) {
        let playlistBrowseUri = 'https://api.spotify.com/v1/browse/categories/'
          + payload.param + '/playlists';
        var spotifyCategoryPlaylistOptions = {
          uri: playlistBrowseUri,
          method: 'GET',
          headers: {'Authorization': 'Bearer ' + SpotifyConn.apiToken}
        };
        rp(spotifyCategoryPlaylistOptions)
          .then(function(body) {
            payload.param = parseSpotify(body);
            payload.utterance = cleanSpeech(payload);
            shimOptions.body = JSON.stringify(payload);
            if (payload.param == '') {
              ga_params.ec = 'foxycmderror';
              ga_params.ea = payload.cmd;
              ga_params.el = payload.utterance;
              gaVisitor.event(ga_params).send();
            } else {
              ga_params.ea = payload.cmd;
              ga_params.el = payload.param;
              gaVisitor.event(ga_params).send();
            }
            return rp(shimOptions);
          })
          .catch(function(err) {
            logger.debug('Spotify error:' + err);
            callback('Spotify error');
          });
      } else if (payload.cmd == FOXY_COMMANDS.IOT) {
        let iotUri =
          'https://localhost:4443/things/zwave-efbddb01-4/properties/on';
        var iotOptions = {
          uri: iotUri,
          method: 'PUT',
          rejectUnauthorized: false,
          headers: {
            'Authorization': 'Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZ\
              CI6IjM2NDNkNjdmLTQ1MzctNGEzMS04NmIxLTgyMDk0ZGI0ODU5NCJ9.eyJpYXQi\
              OjE1MDU5MjE3NzV9.C6wKLoieTsR7ZzOqYopKPDXntxYvxY5emb4nKFqXbdE0fL1\
              D8c2DTiRJOF2i4udTrQpIdks20q_TTDLoB-uZZA',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
          },
          body: ''
        };
        (payload.param2 == 'on')?
          iotOptions.body = JSON.stringify({'on': true}):
          iotOptions.body = JSON.stringify({'on': false});
        ga_params.ea = payload.cmd;
        ga_params.el = payload.param;
        ga_params.ev = (iotOptions ? 0 : 1);
        gaVisitor.event(ga_params).send();

        rp(iotOptions)
          .then(function(body) {
            logger.debug('body is:' + body);
            shimOptions.body = JSON.stringify(payload);
            payload.utterance = cleanSpeech(payload);
            shimOptions.body = JSON.stringify(payload);
            return rp(shimOptions);
          })
          .catch(function(err) {
            logger.debug('iot error is:' + err);
            ga_params.ec = foxycmderror;
            ga_params.ea = payload.cmd;
            ga_params.el = payload.utterance;
            gaVisitor.event(ga_params).send();
            callback('iot error');
          });
      } else if (payload.cmd == FOXY_COMMANDS.WEATHER) {
        weatherOptions.uri = weatherLink + payload.param;

        rp(weatherOptions)
          .then(function(body) {
            var jsonResults = JSON.parse(body);
            payload.param = jsonResults.name;             //City name
            payload.param2 = jsonResults.main.temp;       //Current temp
            payload.param3 = jsonResults.main.temp_min;   //Min temp
            payload.param4 = jsonResults.main.temp_max;   //Max temp
            payload.param5 = jsonResults.weather[0].main; //Description
            payload.utterance = cleanSpeech(payload);

            ga_params.ea = payload.cmd;
            ga_params.el = payload.param;
            gaVisitor.event(ga_params).send();

            return timezone.getLocaltime(jsonResults.coord);
          })
          .then(time => {
            payload.localTime = time;
            shimOptions.body = JSON.stringify(payload);
            return rp(shimOptions);
          })
          .catch(function(err) {
            logger.debug('weather error is:' + err);
            ga_params.ec = foxycmderror;
            ga_params.ea = payload.cmd;
            ga_params.el = payload.utterance;
            gaVisitor.event(ga_params).send();
            callback('weather error');
          });
      } else if(payload.cmd == FOXY_COMMANDS.NEXTSLIDE ||
          payload.cmd == FOXY_COMMANDS.PREVIOUSSLIDE) {
        ga_params.ea = payload.cmd;
        gaVisitor.event(ga_params).send();
        callback('ok');
      } else {
        payload.utterance = cleanSpeech(payload);
        shimOptions.body = JSON.stringify(payload);
        if (payload.cmd == FOXY_COMMANDS.NONE) {
          ga_params.ea = payload.cmd;
          ga_params.el = payload.utterance;
          gaVisitor.event(ga_params).send();
        }

        if(payload.cmd != FOXY_COMMANDS.NEXTSLIDE ||
          payload.cmd != FOXY_COMMANDS.PREVIOUSSLIDE) {
          return rp(shimOptions);
        }
      }
    })
    .then(function() {
       callback('ok');
       logger.debug('finished the chain');
    })
    .catch(function(err) {
      callback('error');
      gaVisitor.exception('Kaldi failed ' + err);
      logger.debug('Call failed' + err);
    });
}

Parser.prototype.convertToOpus = function(rawBuffer) {
  let args = decodeArgs;
  const opusdec = childProcess.spawn(args[0], args.slice(1),
    {stdio: ['pipe', 'pipe', 'pipe']});
  opusdec.stdin.write(rawBuffer);
  opusdec.stdin.end();
  return opusdec.stdout;
}

function parseSpotify(body) {
  const resBody = body && body.toString('utf8');
  logger.debug('GOT DATA FROM SPOTIFY');
  var jsonResults = JSON.parse(resBody);
  let plArray = jsonResults.playlists.items;
  var playlistId = '';
  for(var i = 0; i < plArray.length; i++) {
    var obj = plArray[i];
    if (obj.type == 'playlist') {
      logger.debug('Found a playlist: ' + obj.id);
      playlistId = obj.id;
      break;
    }
  }
  return playlistId;
}

String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}

function cleanSpeech(payload) {
  var lower = payload.utterance.toLowerCase();
  var final;
  switch (payload.cmd) {
    case FOXY_COMMANDS.WEATHER:
      final = '"What\'s the weather in ' +
        payload.param + '?"';
      break;
    // TODO: add next slide
    default:
      final = '"' + lower.capitalize() + '."';
      break;
  }
  return final;
}

function parseAIBody(aiBody, theUtterance) {
  let jsonBody = JSON.parse(aiBody);
  var payload = {
    cmd: 'none',
    param: 'none',
    param2: 'none',
  };

  var ga_params = {
    ec: foxycmd,
    uid: gUuid,
    cd1: gUuid
  };

  logger.debug(aiBody);
  //Determine the action from the API.AI intent parser
  switch (jsonBody.result.action) {
    case 'weather':
      logger.debug('weather is action');
      payload.cmd = FOXY_COMMANDS.WEATHER;
      payload.param = jsonBody.result.parameters['geo-city'];
      break;
    case 'timer':
      logger.debug('timer is action');
      payload.cmd = FOXY_COMMANDS.TIMER;
      payload.param = parseTimer(jsonBody.result);
      payload.param2 = jsonBody.result.parameters.any;

      ga_params.ea = payload.cmd;
      ga_params.el = payload.param2;
      ga_params.ev = payload.param;

      gaVisitor.event(ga_params).send();

      break;
    case 'play':
      payload.cmd = FOXY_COMMANDS.SPOTIFY;
      payload.param = jsonBody.result.parameters['music-genre'];
      break;
    case 'iot':
      payload.cmd = FOXY_COMMANDS.IOT;
      payload.param = jsonBody.result.parameters.rooms;
      payload.param2 = jsonBody.result.parameters.onoff;
      break;
    case 'pocket':
      payload.cmd = FOXY_COMMANDS.POCKET;
      ga_params.ea = payload.cmd;
      ga_params.el = 'add';
      gaVisitor.event(ga_params).send();
      break;
    case 'nextslide':
      payload.cmd = FOXY_COMMANDS.NEXTSLIDE;
      childProcess.exec('"/Applications/FoxyExtension/libs/node/bin/node" '
        + './keysend.js right');
      break;
    case 'lastslide':
      payload.cmd = FOXY_COMMANDS.PREVIOUSSLIDE;
      childProcess.exec('"/Applications/FoxyExtension/libs/node/bin/node" '
      + './keysend.js left');
      break;
    case 'npr':
      payload.cmd = FOXY_COMMANDS.NPR;
      ga_params.ea = payload.cmd;
      gaVisitor.event(ga_params).send();
      break;
    case 'feedback':
      payload.cmd = FOXY_COMMANDS.FEEDBACK;
      ga_params.ea = payload.cmd;
      ga_params.el = theUtterance;

      gaVisitor.event(ga_params).send();
      break;
    case 'shutup':
      payload.cmd = FOXY_COMMANDS.SHUTUP;
      ga_params.ea = payload.cmd;
      gaVisitor.event(ga_params).send();
      break;
    default:
      payload.cmd = FOXY_COMMANDS.NONE;
      break;
  }
  return payload;
}

function getAiBody(asrBody) {
  let conf = 0;
  let body = {
    'v':'20150910',
    'query': '',
    'lang':'en',
    'sessionId':  uuidv4(),
    'timezone':'2017-07-24T10:45:52-0400'
  };
  let text = '';
  for (var i in asrBody.data) {
    if (asrBody.data[i].confidence > conf) {
      conf = asrBody.data[i].confidence;
      text = asrBody.data[i].text;
    }
  }
  body.query = text;
  return body;
}

function parseTimer(result) {
  logger.debug('Entering parseTimer');
  var durationSecs = 0;
  if (!result.parameters.duration) {
    return durationSecs;
  }

  switch (result.parameters.duration.unit) {
    case 'min':
      durationSecs = result.parameters.duration.amount * 60;
      break;
    case 's':
      durationSecs = result.parameters.duration.amount;
      break;
    case 'h':
      durationSecs = result.parameters.duration.amount * 3600;
      break;
    case 'day':
      durationSecs = result.parameters.duration.amount * 86400;
      break;
    default:
      break;
  }

  return durationSecs;
}
module.exports = {
  Parser:Parser,
  FOXY_COMMANDS: FOXY_COMMANDS
};
<|endoftext|>


<commit_before>var veyron = require('veyron');
var mercury = require('mercury');
var LRU = require('lru-cache');
var EventEmitter = require('events').EventEmitter;
var jsonStableStringify = require('json-stable-stringify');
var namespaceUtil = veyron.namespaceUtil;
var veyronConfig = require('../../veyron-config');
var itemFactory = require('./item');
var freeze = require('../../lib/mercury/freeze');
var adaptSignature = require('./signature-adapter');
var log = require('../../lib/log')('services:namespace:service');

module.exports = {
  getChildren: getChildren,
  getNamespaceItem: getNamespaceItem,
  getSignature: getSignature,
  hashSignature: hashSignature,
  makeRPC: makeRPC,
  search: search,
  util: namespaceUtil
};

/*
 * Lazy getter and initializer for Veyron runtime
 */
var _runtimePromiseInstance;

function getRuntime() {
  if (!_runtimePromiseInstance) {
    _runtimePromiseInstance = veyron.init(veyronConfig);
  }
  return _runtimePromiseInstance;
}

/*
 * globCache holds (name + globQuery, result) cache entry for
 * GLOB_CACHE_MAX_SIZE items in an LRU cache
 */
var GLOB_CACHE_MAX_SIZE = 100;
var globCache = new LRU({
  max: GLOB_CACHE_MAX_SIZE
});
/*
 * Given a name and a glob query returns a promise of an observable array
 * of items as defined in @see item.js
 * As new items become available the observable array will change to reflect
 * the changes.
 *
 * The observable result has an events property which is an EventEmitter
 * and emits 'end', 'streamError' and 'itemError' events.
 *
 * @param {string} pattern Glob pattern
 * @return {Promise.<mercury.array>} Promise of an observable array
 * of namespace items
 */
function glob(pattern) {
  var cacheKey = 'glob|' + pattern;
  var cacheHit = globCache.get(cacheKey);
  if (cacheHit) {
    // The addition of the end event to mark the end of a glob requires that
    // our cache also causes the same event to be emitted.
    if (cacheHit._hasEnded) {
      // Remove old listeners to avoid memory leak but now we need to set
      // _hasEnded to false until we trigger again otherwise a new request
      // could wipe out an outstanding listener.
      cacheHit._hasEnded = false;
      cacheHit.events.removeAllListeners();
      process.nextTick(function() {
        cacheHit.events.emit('end');
        cacheHit._hasEnded = true;
      });
    }
    return Promise.resolve(cacheHit);
  }

  var globItemsObservArr = mercury.array([]);
  var immutableResult = freeze(globItemsObservArr);
  immutableResult.events = new EventEmitter();
  var globItemsObservArrPromise =
    getRuntime().then(function callGlobOnNamespace(rt) {
      // TODO(aghassemi) use watchGlob when available
      var namespace = rt.namespace();
      return namespace.glob(pattern).stream;
    }).then(function updateResult(globStream) {
      var itemPromises = [];

      globStream.on('data', function createItem(result) {
        // Create an item as glob results come in and add the item to result
        var createItemPromise = createNamespaceItem(result.name, result.servers)
          .then(function(item) {
            // TODO(aghassemi) namespace glob can return duplicate results, this
            // temporary fix keeps the one that's a server. Is this correct?
            // If a name can be more than one thing, UI needs change too.
            var existingItem = globItemsObservArr.filter(function(curItem) {
              return curItem().objectName === item().objectName;
            }).get(0);
            if (existingItem) {
              // override the old one if new item is a server
              if (item().isServer) {
                var index = globItemsObservArr.indexOf(existingItem);
                globItemsObservArr.put(index, item);
              }
            } else {
              globItemsObservArr.push(item);
            }
          }).catch(function(err) {
            globCache.del(cacheKey);
            immutableResult.events.emit('itemError', {
              name: result.name,
              error: err
            });
            log.error('Failed to create item for "' + result.name + '"', err);
          });

        itemPromises.push(createItemPromise);
      });

      globStream.on('end', function() {
        var triggerEnd = function() {
          immutableResult.events.emit('end');
          immutableResult._hasEnded = true;
        };

        // Wait until all createItem promises return before triggering has ended
        Promise.all(itemPromises).then(triggerEnd).catch(triggerEnd);
      });

      globStream.on('error', function invalidateCacheAndLog(err) {
        globCache.del(cacheKey);
        immutableResult.events.emit('streamError', err);
        log.error('Glob stream error for', name, err);
      });

    }).then(function cacheAndReturnResult() {
      globCache.set(cacheKey, immutableResult);
      return immutableResult;
    }).catch(function invalidateCacheAndRethrow(err) {
      globCache.del(cacheKey);
      return Promise.reject(err);
    });

  // Return our Promise of observable array. It will get filled as data comes in
  return globItemsObservArrPromise;
}

/*
 * Given a name, provide information about a the name as defined in @see item.js
 * @param {string} objectName Object name to get namespace item for.
 * @return {Promise.<mercury.value>} Promise of an observable value of an item
 * as defined in @see item.js
 */
function getNamespaceItem(objectName) {

  // Globbing the name itself would provide information about the name.
  return glob(objectName).then(function(resultsObs) {
    // Wait until the glob finishes before returning the item
    return new Promise(function(resolve, reject) {
      resultsObs.events.on('streamError', function(err) {
        reject(err);
      });
      resultsObs.events.on('itemError', function(err) {
        reject(err);
      });
      resultsObs.events.on('end', function() {
        var results = resultsObs();
        if (results.length === 0) {
          reject(new Error(objectName + ' Not Found'));
        } else {
          var item = new mercury.value(results[0]);
          var immutableItem = freeze(item);
          resolve(immutableItem);
        }
      });
    });
  });
}

/*
 * Given a name returns a promise of an observable array of immediate children
 * @param {string} parentName Object name to glob
 * @return {Promise.<mercury.array>} Promise of an observable array
 */
function getChildren(parentName) {
  parentName = parentName || '';
  var pattern = '*';
  if (parentName) {
    pattern = namespaceUtil.join(parentName, pattern);
  }
  return glob(pattern);
}

/*
 * Given a name and a glob search query returns a promise of an observable array
 * of items as defined in @see item.js
 * As new items become available the observable array will change to reflect
 * the changes.
 * @param {name} parentName Object name to search in.
 * @param {string} pattern Glob search pattern.
 * @return {Promise.<mercury.array>} Promise of an observable array
 * of namespace items
 */
function search(parentName, pattern) {
  parentName = parentName || '';
  if (parentName) {
    pattern = namespaceUtil.join(parentName, pattern);
  }
  return glob(pattern);
}

/*
 * signatureCache holds (name, signature) cache entry for
 * SIGNATURECACHE_MAX_SIZE items in an LRU cache
 */
var SIGNATURE_CACHE_MAX_SIZE = 10000;
var signatureCache = new LRU({
  max: SIGNATURE_CACHE_MAX_SIZE
});
/*
 * Given a object name, returns a promise of the signature of methods available
 * on the object represented by that name.
 * @param {string} objectName Object name to get signature for
 * @return {object} signature for the object represented by the given name
 */
function getSignature(objectName) {
  var cacheKey = 'getSignature|' + objectName;
  var cacheHit = signatureCache.get(cacheKey);
  if (cacheHit) {
    return Promise.resolve(cacheHit);
  }
  return getRuntime().then(function bindToName(rt) {
    return rt.bindTo(objectName);
  }).then(function invokeSignatureMethod(service) {
    return service._signature();
  }).then(function cacheAndReturnSignature(signatures) {
    var adaptedSignature = adaptSignature(signatures);
    signatureCache.set(cacheKey, adaptedSignature);
    return adaptedSignature;
  });
}

/*
 * Given a service signature, compute a reasonable hash that uniquely identifies
 * a service without containing unnecessary information.
 * TODO(alexfandrianto): This heuristic comes close, but it does not properly
 * distinguish services from each other.
 * Once available, add type info, streaming info, interface name, etc.
 */
function hashSignature(signature) {
  var cp = adaptSignature([]);
  signature.forEach(function(method, methodName) {
    cp[methodName] = method.inArgs.length;
  });
  return jsonStableStringify(cp);
}

/*
 * TODO(aghassemi) Technically right now every server is globbable
 * so our definition of globbable is whether the server in question
 * has any children.
 * We may want to consider exposing some metadata about a service on
 * whether that service actually implements Glob or GetChildren
 * interfaces in a custom way or not.
 *
 * Given a object name, returns whether the service referenced by the name
 * supports globbing.
 * @param {string} objectName Object name to check to see if globbale
 * @return {boolean} Whether the service is globbable
 */
function isGlobbable(objectName) {
  return getChildren(objectName).then(function(obs) {
    return new Promise(function(resolve, reject) {
      var removeListeners = function() {
        removeWatch();
        obs.events.removeListener('end', onEndListener);
      };
      var onEndListener = function() {
        // no children
        resolve(false);
        removeListeners();
      };
      // resolve as soon as we find one child
      var removeWatch = mercury.watch(obs, function(children) {
        if (children.length > 0) {
          resolve(true);
          removeListeners();
        }
      });
      obs.events.on('end', onEndListener);
    });
  }).catch(function() {
    return false;
  });
}

/*
 * Make an RPC call on a service object.
 * name: string representing the name of the service
 * methodName: string for the service method name
 * args (optional): array of arguments for the service method
 */
function makeRPC(name, methodName, args) {
  return getRuntime().then(function bindToName(rt) {
    return rt.bindTo(name);
  }).then(function callMethod(service) {
    log.debug('Calling', methodName, 'on', name, 'with', args);
    var ctx = veyron.context.Context();
    args.unshift(ctx);
    return service[methodName].apply(null, args);
  }).then(function returnResult(result) {
    return result;
  });
}

/*
 * Creates an observable struct representing basic information about
 * an item in the namespace.
 * @param {string} name The full hierarchical object name of the item e.g.
 * "bar/baz/foo"
 * @param {string} name The object name
 * @param {Array<string>} List of server addresses this name points to, if any.
 * @return {mercury.struct}
 */
function createNamespaceItem(name, servers) {

  // mounted name relative to parent
  var mountedName = namespaceUtil.basename(name);

  // Find out if the object referenced by name is globbable and get
  // server related information about it.
  var isServer = servers.length > 0;
  return Promise.all([
    (!isServer ? Promise.resolve(true) : isGlobbable(name)),
    (isServer ? getServerInfo(name) : Promise.resolve(null))
  ]).then(function(results) {
    return itemFactory.createItem({
      objectName: name,
      mountedName: mountedName,
      isGlobbable: results[0],
      isServer: isServer,
      serverInfo: results[1]
    });
  });
}

/*
 * Creates an observable struct representing information about a server such as
 * type information, signature, etc...
 * @see item.js for details.
 * @param {string} objectName Object name to get serverInfo for.
 * @return {mercury.struct}
 */
function getServerInfo(objectName) {
  var signature;
  var isAccessible;
  return getSignature(objectName).then(function gotSignature(sig) {
    signature = sig;
    isAccessible = true;
    return getServerTypeInfo(sig);
  }, function failedToGetSignature(err) {
    signature = adaptSignature([]);
    //TODO(aghassemi): We should at least be able to tell if inaccessible
    //because not authorized vs other reasons.
    isAccessible = false;
    return createUnknownServiceTypeInfo();
  }).then(function(serverTypeInfo) {
    return itemFactory.createServerInfo({
      typeInfo: serverTypeInfo,
      signature: signature,
      isAccessible: isAccessible
    });
  });
}

/*
 * Creates an observable struct representing information about a server type.
 * For example if a server is known to be a mounttable or a store, the struct
 * would have information such as a key, human readable name and description for
 * the type of server.
 * @see item.js for details.
 * @param {string} objectName Object name to get serverTypeInfo for.
 * @return {mercury.struct}
 */
function getServerTypeInfo(signature) {
  // TODO(aghassemi) Ideally we want a .meta or maybe piggy backing on
  // .meta/stats to get information about a server. For now we just understand
  // mounttable and store. Everything else is unknown.
  var isMounttable = (signature &&
    signature.get('mount') &&
    signature.get('unmount'));

  if (isMounttable) {
    return itemFactory.createServerTypeInfo({
      key: 'veyron-mounttable',
      typeName: 'Mount Table',
      description: 'Mount table service allows registration ' +
        'and resolution of object names.'
    });
  } else {
    return createUnknownServiceTypeInfo();
  }
}

function createUnknownServiceTypeInfo() {
  return itemFactory.createServerTypeInfo({
    key: 'veyron-unknown',
    typeName: 'Service',
    description: null
  });
}<commit_msg>veyron-browser: context on bindTo is now required.<commit_after>var veyron = require('veyron');
var mercury = require('mercury');
var LRU = require('lru-cache');
var EventEmitter = require('events').EventEmitter;
var jsonStableStringify = require('json-stable-stringify');
var namespaceUtil = veyron.namespaceUtil;
var veyronConfig = require('../../veyron-config');
var itemFactory = require('./item');
var freeze = require('../../lib/mercury/freeze');
var adaptSignature = require('./signature-adapter');
var log = require('../../lib/log')('services:namespace:service');

module.exports = {
  getChildren: getChildren,
  getNamespaceItem: getNamespaceItem,
  getSignature: getSignature,
  hashSignature: hashSignature,
  makeRPC: makeRPC,
  search: search,
  util: namespaceUtil
};

/*
 * Lazy getter and initializer for Veyron runtime
 */
var _runtimePromiseInstance;

function getRuntime() {
  if (!_runtimePromiseInstance) {
    _runtimePromiseInstance = veyron.init(veyronConfig);
  }
  return _runtimePromiseInstance;
}

/*
 * globCache holds (name + globQuery, result) cache entry for
 * GLOB_CACHE_MAX_SIZE items in an LRU cache
 */
var GLOB_CACHE_MAX_SIZE = 100;
var globCache = new LRU({
  max: GLOB_CACHE_MAX_SIZE
});
/*
 * Given a name and a glob query returns a promise of an observable array
 * of items as defined in @see item.js
 * As new items become available the observable array will change to reflect
 * the changes.
 *
 * The observable result has an events property which is an EventEmitter
 * and emits 'end', 'streamError' and 'itemError' events.
 *
 * @param {string} pattern Glob pattern
 * @return {Promise.<mercury.array>} Promise of an observable array
 * of namespace items
 */
function glob(pattern) {
  var cacheKey = 'glob|' + pattern;
  var cacheHit = globCache.get(cacheKey);
  if (cacheHit) {
    // The addition of the end event to mark the end of a glob requires that
    // our cache also causes the same event to be emitted.
    if (cacheHit._hasEnded) {
      // Remove old listeners to avoid memory leak but now we need to set
      // _hasEnded to false until we trigger again otherwise a new request
      // could wipe out an outstanding listener.
      cacheHit._hasEnded = false;
      cacheHit.events.removeAllListeners();
      process.nextTick(function() {
        cacheHit.events.emit('end');
        cacheHit._hasEnded = true;
      });
    }
    return Promise.resolve(cacheHit);
  }

  var globItemsObservArr = mercury.array([]);
  var immutableResult = freeze(globItemsObservArr);
  immutableResult.events = new EventEmitter();
  var globItemsObservArrPromise =
    getRuntime().then(function callGlobOnNamespace(rt) {
      // TODO(aghassemi) use watchGlob when available
      var namespace = rt.namespace();
      return namespace.glob(pattern).stream;
    }).then(function updateResult(globStream) {
      var itemPromises = [];

      globStream.on('data', function createItem(result) {
        // Create an item as glob results come in and add the item to result
        var createItemPromise = createNamespaceItem(result.name, result.servers)
          .then(function(item) {
            // TODO(aghassemi) namespace glob can return duplicate results, this
            // temporary fix keeps the one that's a server. Is this correct?
            // If a name can be more than one thing, UI needs change too.
            var existingItem = globItemsObservArr.filter(function(curItem) {
              return curItem().objectName === item().objectName;
            }).get(0);
            if (existingItem) {
              // override the old one if new item is a server
              if (item().isServer) {
                var index = globItemsObservArr.indexOf(existingItem);
                globItemsObservArr.put(index, item);
              }
            } else {
              globItemsObservArr.push(item);
            }
          }).catch(function(err) {
            globCache.del(cacheKey);
            immutableResult.events.emit('itemError', {
              name: result.name,
              error: err
            });
            log.error('Failed to create item for "' + result.name + '"', err);
          });

        itemPromises.push(createItemPromise);
      });

      globStream.on('end', function() {
        var triggerEnd = function() {
          immutableResult.events.emit('end');
          immutableResult._hasEnded = true;
        };

        // Wait until all createItem promises return before triggering has ended
        Promise.all(itemPromises).then(triggerEnd).catch(triggerEnd);
      });

      globStream.on('error', function invalidateCacheAndLog(err) {
        globCache.del(cacheKey);
        immutableResult.events.emit('streamError', err);
        log.error('Glob stream error for', name, err);
      });

    }).then(function cacheAndReturnResult() {
      globCache.set(cacheKey, immutableResult);
      return immutableResult;
    }).catch(function invalidateCacheAndRethrow(err) {
      globCache.del(cacheKey);
      return Promise.reject(err);
    });

  // Return our Promise of observable array. It will get filled as data comes in
  return globItemsObservArrPromise;
}

/*
 * Given a name, provide information about a the name as defined in @see item.js
 * @param {string} objectName Object name to get namespace item for.
 * @return {Promise.<mercury.value>} Promise of an observable value of an item
 * as defined in @see item.js
 */
function getNamespaceItem(objectName) {

  // Globbing the name itself would provide information about the name.
  return glob(objectName).then(function(resultsObs) {
    // Wait until the glob finishes before returning the item
    return new Promise(function(resolve, reject) {
      resultsObs.events.on('streamError', function(err) {
        reject(err);
      });
      resultsObs.events.on('itemError', function(err) {
        reject(err);
      });
      resultsObs.events.on('end', function() {
        var results = resultsObs();
        if (results.length === 0) {
          reject(new Error(objectName + ' Not Found'));
        } else {
          var item = new mercury.value(results[0]);
          var immutableItem = freeze(item);
          resolve(immutableItem);
        }
      });
    });
  });
}

/*
 * Given a name returns a promise of an observable array of immediate children
 * @param {string} parentName Object name to glob
 * @return {Promise.<mercury.array>} Promise of an observable array
 */
function getChildren(parentName) {
  parentName = parentName || '';
  var pattern = '*';
  if (parentName) {
    pattern = namespaceUtil.join(parentName, pattern);
  }
  return glob(pattern);
}

/*
 * Given a name and a glob search query returns a promise of an observable array
 * of items as defined in @see item.js
 * As new items become available the observable array will change to reflect
 * the changes.
 * @param {name} parentName Object name to search in.
 * @param {string} pattern Glob search pattern.
 * @return {Promise.<mercury.array>} Promise of an observable array
 * of namespace items
 */
function search(parentName, pattern) {
  parentName = parentName || '';
  if (parentName) {
    pattern = namespaceUtil.join(parentName, pattern);
  }
  return glob(pattern);
}

/*
 * signatureCache holds (name, signature) cache entry for
 * SIGNATURECACHE_MAX_SIZE items in an LRU cache
 */
var SIGNATURE_CACHE_MAX_SIZE = 10000;
var signatureCache = new LRU({
  max: SIGNATURE_CACHE_MAX_SIZE
});
/*
 * Given a object name, returns a promise of the signature of methods available
 * on the object represented by that name.
 * @param {string} objectName Object name to get signature for
 * @return {object} signature for the object represented by the given name
 */
function getSignature(objectName) {
  var cacheKey = 'getSignature|' + objectName;
  var cacheHit = signatureCache.get(cacheKey);
  if (cacheHit) {
    return Promise.resolve(cacheHit);
  }
  return getRuntime().then(function bindToName(rt) {
    var ctx = veyron.context.Context();
    return rt.bindTo(ctx, objectName);
  }).then(function invokeSignatureMethod(service) {
    return service._signature();
  }).then(function cacheAndReturnSignature(signatures) {
    var adaptedSignature = adaptSignature(signatures);
    signatureCache.set(cacheKey, adaptedSignature);
    return adaptedSignature;
  });
}

/*
 * Given a service signature, compute a reasonable hash that uniquely identifies
 * a service without containing unnecessary information.
 * TODO(alexfandrianto): This heuristic comes close, but it does not properly
 * distinguish services from each other.
 * Once available, add type info, streaming info, interface name, etc.
 */
function hashSignature(signature) {
  var cp = adaptSignature([]);
  signature.forEach(function(method, methodName) {
    cp[methodName] = method.inArgs.length;
  });
  return jsonStableStringify(cp);
}

/*
 * TODO(aghassemi) Technically right now every server is globbable
 * so our definition of globbable is whether the server in question
 * has any children.
 * We may want to consider exposing some metadata about a service on
 * whether that service actually implements Glob or GetChildren
 * interfaces in a custom way or not.
 *
 * Given a object name, returns whether the service referenced by the name
 * supports globbing.
 * @param {string} objectName Object name to check to see if globbale
 * @return {boolean} Whether the service is globbable
 */
function isGlobbable(objectName) {
  return getChildren(objectName).then(function(obs) {
    return new Promise(function(resolve, reject) {
      var removeListeners = function() {
        removeWatch();
        obs.events.removeListener('end', onEndListener);
      };
      var onEndListener = function() {
        // no children
        resolve(false);
        removeListeners();
      };
      // resolve as soon as we find one child
      var removeWatch = mercury.watch(obs, function(children) {
        if (children.length > 0) {
          resolve(true);
          removeListeners();
        }
      });
      obs.events.on('end', onEndListener);
    });
  }).catch(function() {
    return false;
  });
}

/*
 * Make an RPC call on a service object.
 * name: string representing the name of the service
 * methodName: string for the service method name
 * args (optional): array of arguments for the service method
 */
function makeRPC(name, methodName, args) {
  return getRuntime().then(function bindToName(rt) {
    var ctx = veyron.context.Context();
    return rt.bindTo(ctx, name);
  }).then(function callMethod(service) {
    log.debug('Calling', methodName, 'on', name, 'with', args);
    var ctx = veyron.context.Context();
    args.unshift(ctx);
    return service[methodName].apply(null, args);
  }).then(function returnResult(result) {
    return result;
  });
}

/*
 * Creates an observable struct representing basic information about
 * an item in the namespace.
 * @param {string} name The full hierarchical object name of the item e.g.
 * "bar/baz/foo"
 * @param {string} name The object name
 * @param {Array<string>} List of server addresses this name points to, if any.
 * @return {mercury.struct}
 */
function createNamespaceItem(name, servers) {

  // mounted name relative to parent
  var mountedName = namespaceUtil.basename(name);

  // Find out if the object referenced by name is globbable and get
  // server related information about it.
  var isServer = servers.length > 0;
  return Promise.all([
    (!isServer ? Promise.resolve(true) : isGlobbable(name)),
    (isServer ? getServerInfo(name) : Promise.resolve(null))
  ]).then(function(results) {
    return itemFactory.createItem({
      objectName: name,
      mountedName: mountedName,
      isGlobbable: results[0],
      isServer: isServer,
      serverInfo: results[1]
    });
  });
}

/*
 * Creates an observable struct representing information about a server such as
 * type information, signature, etc...
 * @see item.js for details.
 * @param {string} objectName Object name to get serverInfo for.
 * @return {mercury.struct}
 */
function getServerInfo(objectName) {
  var signature;
  var isAccessible;
  return getSignature(objectName).then(function gotSignature(sig) {
    signature = sig;
    isAccessible = true;
    return getServerTypeInfo(sig);
  }, function failedToGetSignature(err) {
    signature = adaptSignature([]);
    //TODO(aghassemi): We should at least be able to tell if inaccessible
    //because not authorized vs other reasons.
    isAccessible = false;
    return createUnknownServiceTypeInfo();
  }).then(function(serverTypeInfo) {
    return itemFactory.createServerInfo({
      typeInfo: serverTypeInfo,
      signature: signature,
      isAccessible: isAccessible
    });
  });
}

/*
 * Creates an observable struct representing information about a server type.
 * For example if a server is known to be a mounttable or a store, the struct
 * would have information such as a key, human readable name and description for
 * the type of server.
 * @see item.js for details.
 * @param {string} objectName Object name to get serverTypeInfo for.
 * @return {mercury.struct}
 */
function getServerTypeInfo(signature) {
  // TODO(aghassemi) Ideally we want a .meta or maybe piggy backing on
  // .meta/stats to get information about a server. For now we just understand
  // mounttable and store. Everything else is unknown.
  var isMounttable = (signature &&
    signature.get('mount') &&
    signature.get('unmount'));

  if (isMounttable) {
    return itemFactory.createServerTypeInfo({
      key: 'veyron-mounttable',
      typeName: 'Mount Table',
      description: 'Mount table service allows registration ' +
        'and resolution of object names.'
    });
  } else {
    return createUnknownServiceTypeInfo();
  }
}

function createUnknownServiceTypeInfo() {
  return itemFactory.createServerTypeInfo({
    key: 'veyron-unknown',
    typeName: 'Service',
    description: null
  });
}<|endoftext|>


<commit_before>'use strict';

const Script = require('smooch-bot').Script;

module.exports = new Script({
    processing: {
        prompt: (bot) => bot.say('Beep boop...'),
        receive: () => 'processing'
    },

    start: {
        receive: (bot) => {
            return bot.say('Hi! I\'m Emissay!')
                .then(() => 'askName');
        }
    },

    askName: {
        prompt: (bot) => bot.say('What\'s your name?'),
        receive: (bot, message) => {
            const name = message.text;
            return bot.setProp('name', name)
                .then(() => bot.say(`Great! I'll call you ${name}`))
                .then(() => 'howToHelp');
        }
    },

    howToHelp: {
        prompt: (bot) => bot.say("How can I help you today? \n 1) New Appointment (NA) \n 2) Cancel Appointment (CA) \n 3) Reschedule Appointment (RA) \n 4) Live Chat (LC)"),
        receive: (bot, message) => {
            const task = message.text;
            switch(task) {
                case "NA":
                    return bot.setProp('task', task)
                        .then(() => bot.say(`Great! I'll help you create an appointment.`))
                        .then(() => 'finish');
                    break;
                case "CA":
                    return bot.setProp('task', task)
                                    .then(() => bot.say(`Great! I'll help you cancel an appointment`))
                                    .then(() => 'finish');
                    break;
                case "RA":
                    return bot.setProp('task', task)
                                    .then(() => bot.say(`Great! I'll help you reschedule an appointment`))
                                    .then(() => 'finish');
                    break;
                case "LC":
                    return bot.setProp('task', task)
                                    .then(() => bot.say(`Great! I'll go fetch a Emissary representative`))
                                    .then(() => 'finish');
                    break;
                default:
                    return bot.setProp('task', task)
                                    .then(() => bot.say(`An error occurred`))
                                    .then(() => 'finish');
                }
        }
    },

    finish: {
        receive: (bot, message) => {
            return bot.getProp('name')
                .then((name) => bot.say(`Hello again, ${name}!`))
                .then(() => 'howToHelp');
        }
    }
});<commit_msg>Trying to rebuild script.<commit_after>'use strict';

const Script = require('smooch-bot').Script;

module.exports = new Script({
    processing: {
        prompt: (bot) => bot.say('Beep boop...'),
        receive: () => 'processing'
    },

    start: {
        receive: (bot) => {
            return bot.say('Hi! I\'m Emissay!')
                .then(() => 'askName');
        }
    },

    askName: {
        prompt: (bot) => bot.say('What\'s your name?'),
        receive: (bot, message) => {
            const name = message.text;
            return bot.setProp('name', name)
                .then(() => bot.say(`Great! I'll call you ${name}`))
                .then(() => 'howToHelp');
        }
    },

    howToHelp: {
        prompt: (bot) => bot.say("How can I help you today? \n 1) New Appointment (NA) \n 2) Cancel Appointment (CA) \n 3) Reschedule Appointment (RA) \n 4) Live Chat (LC)"),
        receive: (bot, message) => {
            const task = message.text;
            switch(task) {
                case "NA":
                    return bot.setProp('task', task)
                        .then(() => bot.say(`Great! I'll help you create an appointment.`))
                        .then(() => 'finish');
                    break;
                case "CA":
                    return bot.setProp('task', task)
                                    .then(() => bot.say(`Great! I'll help you cancel an appointment`))
                                    .then(() => bot.say(`What time/day would you like to create an appointment for? (MM/DD/YYYY 24:00)`))
                                    .then(() => console.log("New appointment created.")
                                    .then(() => 'finish');
                    break;
                case "RA":
                    return bot.setProp('task', task)
                                    .then(() => bot.say(`Great! I'll help you reschedule an appointment`))
                                    .then(() => bot.say(`Which of the following appointments do you want to cancel?'))
                                    .then(() => 'finish');
                    break;
                case "LC":
                    return bot.setProp('task', task)
                                    .then(() => bot.say(`Great! I'll go fetch a Emissary representative`))
                                    .then(() => bot.say(`Which of the following appointments do you want to reschedule?'))
                                    .then(() => 'finish');
                    break;
                default:
                    return bot.setProp('task', task)
                                    .then(() => bot.say(`An error occurred`))
                                    .then(() => 'finish');
                }
        }
    },

    finish: {
        receive: (bot, message) => {
            return bot.getProp('name')
                .then((name) => bot.say(`Hello again, ${name}!`))
                .then(() => 'howToHelp');
        }
    }
});<|endoftext|>


<commit_before>/* eslint-disable dot-notation */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */
import { pageInfoStrings, programStrings, tableStrings } from '../../localization';
import { MODAL_KEYS, formatDate } from '../../utilities';
import { ROUTES } from '../../navigation/constants';
/**
 * PageInfo rows/columns for use with the PageInfo component.
 *
 *
 * To add a page, add the pages routeName (see: pages/index.js)
 * and a 2d array of page info rows to PER_PAGE_INFO_COLUMNS,
 * of the desired pageinfo rows needed on the page.
 *
 * To use in the new page: use the usePageReducer hook.
 * usePageReducer will inject a your pages reducer with a field
 * pageInfo - a function returned by getPageInfo. Passing
 * this function the particular pages base object - i.e,
 * transaction, requisition or stock and it's dispatch will return the
 * required pageInfo columns for the page.
 */

const PER_PAGE_INFO_COLUMNS = {
  [ROUTES.CUSTOMER_INVOICE]: [
    ['entryDate', 'confirmDate', 'enteredBy'],
    ['customer', 'theirRef', 'transactionComment'],
  ],
  [ROUTES.SUPPLIER_INVOICE]: [
    ['entryDate', 'confirmDate'],
    ['otherParty', 'theirRef', 'transactionComment'],
  ],
  [ROUTES.SUPPLIER_REQUISITION]: [
    ['entryDate', 'enteredBy'],
    ['otherParty', 'editableMonthsToSupply', 'requisitionComment'],
  ],
  [ROUTES.SUPPLIER_REQUISITION_WITH_PROGRAM]: [
    ['program', 'orderType', 'entryDate', 'enteredBy'],
    ['period', 'otherParty', 'monthsToSupply', 'requisitionComment'],
  ],
  [ROUTES.STOCKTAKE_EDITOR]: [['stocktakeName', 'stocktakeComment']],
  [ROUTES.STOCKTAKE_EDITOR_WITH_REASONS]: [['stocktakeName', 'stocktakeComment']],
  [ROUTES.CUSTOMER_REQUISITION]: [
    ['monthsToSupply', 'entryDate'],
    ['customer', 'requisitionComment'],
  ],
  stocktakeBatchEditModal: [['itemName']],
  stocktakeBatchEditModalWithReasons: [['itemName']],
};

const PAGE_INFO_ROWS = (pageObject, dispatch, PageActions) => ({
  entryDate: {
    title: `${pageInfoStrings.entry_date}:`,
    info: formatDate(pageObject.entryDate) || 'N/A',
  },
  confirmDate: {
    title: `${pageInfoStrings.confirm_date}:`,
    info: formatDate(pageObject.confirmDate),
  },
  enteredBy: {
    title: `${pageInfoStrings.entered_by}:`,
    info: pageObject.enteredBy && pageObject.enteredBy.username,
  },
  customer: {
    title: `${pageInfoStrings.customer}:`,
    info: pageObject.otherPartyName,
  },
  theirRef: {
    title: `${pageInfoStrings.their_ref}:`,
    info: pageObject.theirRef,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.THEIR_REF_EDIT)),
    editableType: 'text',
  },
  transactionComment: {
    title: `${pageInfoStrings.comment}:`,
    info: pageObject.comment,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.TRANSACTION_COMMENT_EDIT)),
    editableType: 'text',
  },
  stocktakeComment: {
    title: `${pageInfoStrings.comment}:`,
    info: pageObject.comment,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.STOCKTAKE_COMMENT_EDIT)),
    editableType: 'text',
  },
  requisitionComment: {
    title: `${pageInfoStrings.comment}:`,
    info: pageObject.comment,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.REQUISITION_COMMENT_EDIT)),
    editableType: 'text',
  },
  otherParty: {
    title: `${pageInfoStrings.supplier}:`,
    info: pageObject.otherPartyName,
  },
  program: {
    title: `${programStrings.program}:`,
    info: pageObject.program && pageObject.program.name,
  },
  orderType: {
    title: `${programStrings.order_type}:`,
    info: pageObject.orderType,
  },
  editableMonthsToSupply: {
    title: `${pageInfoStrings.months_stock_required}:`,
    info: pageObject.monthsToSupply,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.SELECT_MONTH)),
    editableType: 'selectable',
  },
  period: {
    title: `${programStrings.period}:`,
    info: pageObject.period && pageObject.period.toInfoString(),
  },
  monthsToSupply: {
    title: `${pageInfoStrings.months_stock_required}:`,
    info: pageObject.monthsToSupply,
  },
  stocktakeName: {
    title: `${pageInfoStrings.stocktake_name}:`,
    info: pageObject.name,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.STOCKTAKE_NAME_EDIT)),
    editableType: 'text',
  },
  itemName: {
    title: `${tableStrings.item_name}`,
    info: pageObject.itemName,
    onPress: null,
  },
});

const getPageInfoColumns = page => {
  const pageInfoColumns = PER_PAGE_INFO_COLUMNS[page];

  if (!pageInfoColumns) return null;
  return (pageObjectParameter, dispatch, PageActions) => {
    const pageInfoRows = PAGE_INFO_ROWS(pageObjectParameter, dispatch, PageActions);
    return pageInfoColumns.map(pageInfoColumn =>
      pageInfoColumn.map(pageInfoKey => pageInfoRows[pageInfoKey])
    );
  };
};

export default getPageInfoColumns;
<commit_msg>Add update PageInfoColumns<commit_after>/* eslint-disable dot-notation */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2016
 */
import { pageInfoStrings, programStrings, tableStrings } from '../../localization';
import { MODAL_KEYS, formatDate } from '../../utilities';
import { ROUTES } from '../../navigation/constants';
import { PageActions } from './actions';

/**
 * PageInfo rows/columns for use with the PageInfo component.
 *
 *
 * To add a page, add the pages routeName (see: pages/index.js)
 * and a 2d array of page info rows to PER_PAGE_INFO_COLUMNS,
 * of the desired pageinfo rows needed on the page.
 *
 * To use in the new page: use the usePageReducer hook.
 * usePageReducer will inject a your pages reducer with a field
 * pageInfo - a function returned by getPageInfo. Passing
 * this function the particular pages base object - i.e,
 * transaction, requisition or stock and it's dispatch will return the
 * required pageInfo columns for the page.
 */

const PER_PAGE_INFO_COLUMNS = {
  [ROUTES.CUSTOMER_INVOICE]: [
    ['entryDate', 'confirmDate', 'enteredBy'],
    ['customer', 'theirRef', 'transactionComment'],
  ],
  [ROUTES.SUPPLIER_INVOICE]: [
    ['entryDate', 'confirmDate'],
    ['otherParty', 'theirRef', 'transactionComment'],
  ],
  [ROUTES.SUPPLIER_REQUISITION]: [
    ['entryDate', 'enteredBy'],
    ['otherParty', 'editableMonthsToSupply', 'requisitionComment'],
  ],
  [ROUTES.SUPPLIER_REQUISITION_WITH_PROGRAM]: [
    ['program', 'orderType', 'entryDate', 'enteredBy'],
    ['period', 'otherParty', 'monthsToSupply', 'requisitionComment'],
  ],
  [ROUTES.STOCKTAKE_EDITOR]: [['stocktakeName', 'stocktakeComment']],
  [ROUTES.STOCKTAKE_EDITOR_WITH_REASONS]: [['stocktakeName', 'stocktakeComment']],
  [ROUTES.CUSTOMER_REQUISITION]: [
    ['monthsToSupply', 'entryDate'],
    ['customer', 'requisitionComment'],
  ],
  stocktakeBatchEditModal: [['itemName']],
  stocktakeBatchEditModalWithReasons: [['itemName']],
};

const PAGE_INFO_ROWS = (pageObject, dispatch, route) => ({
  entryDate: {
    title: `${pageInfoStrings.entry_date}:`,
    info: formatDate(pageObject.entryDate) || 'N/A',
  },
  confirmDate: {
    title: `${pageInfoStrings.confirm_date}:`,
    info: formatDate(pageObject.confirmDate),
  },
  enteredBy: {
    title: `${pageInfoStrings.entered_by}:`,
    info: pageObject.enteredBy && pageObject.enteredBy.username,
  },
  customer: {
    title: `${pageInfoStrings.customer}:`,
    info: pageObject.otherPartyName,
  },
  theirRef: {
    title: `${pageInfoStrings.their_ref}:`,
    info: pageObject.theirRef,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.THEIR_REF_EDIT, route)),
    editableType: 'text',
  },
  transactionComment: {
    title: `${pageInfoStrings.comment}:`,
    info: pageObject.comment,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.TRANSACTION_COMMENT_EDIT, route)),
    editableType: 'text',
  },
  stocktakeComment: {
    title: `${pageInfoStrings.comment}:`,
    info: pageObject.comment,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.STOCKTAKE_COMMENT_EDIT, route)),
    editableType: 'text',
  },
  requisitionComment: {
    title: `${pageInfoStrings.comment}:`,
    info: pageObject.comment,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.REQUISITION_COMMENT_EDIT, route)),
    editableType: 'text',
  },
  otherParty: {
    title: `${pageInfoStrings.supplier}:`,
    info: pageObject.otherPartyName,
  },
  program: {
    title: `${programStrings.program}:`,
    info: pageObject.program && pageObject.program.name,
  },
  orderType: {
    title: `${programStrings.order_type}:`,
    info: pageObject.orderType,
  },
  editableMonthsToSupply: {
    title: `${pageInfoStrings.months_stock_required}:`,
    info: pageObject.monthsToSupply,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.SELECT_MONTH, route)),
    editableType: 'selectable',
  },
  period: {
    title: `${programStrings.period}:`,
    info: pageObject.period && pageObject.period.toInfoString(),
  },
  monthsToSupply: {
    title: `${pageInfoStrings.months_stock_required}:`,
    info: pageObject.monthsToSupply,
  },
  stocktakeName: {
    title: `${pageInfoStrings.stocktake_name}:`,
    info: pageObject.name,
    onPress: () => dispatch(PageActions.openModal(MODAL_KEYS.STOCKTAKE_NAME_EDIT, route)),
    editableType: 'text',
  },
  itemName: {
    title: `${tableStrings.item_name}`,
    info: pageObject.itemName,
    onPress: null,
  },
});

const getPageInfoColumns = page => {
  const pageInfoColumns = PER_PAGE_INFO_COLUMNS[page];

  if (!pageInfoColumns) return null;
  return (pageObjectParameter, dispatch, route) => {
    const pageInfoRows = PAGE_INFO_ROWS(pageObjectParameter, dispatch, route);
    return pageInfoColumns.map(pageInfoColumn =>
      pageInfoColumn.map(pageInfoKey => pageInfoRows[pageInfoKey])
    );
  };
};

export default getPageInfoColumns;
<|endoftext|>


<commit_before>var ctx = document.getElementById("chart").getContext("2d");
var myLineChart = new Chart(ctx).Line(data, {
    tooltipTemplate = function(valuesObject){
      var label = valuesObject.label;
      var idx = data.labels.indexOf(label);
      var result = data.datasets[0].time[idx];
      if (data.datasets[0].languages[idx] !== 0)
        result += '[' + data.datasets[0].languages[idx].join(", ") + "]"
      return result;
    }
});
document.getElementById("summary").innerHTML = "I  have written code for " + totalTime + " in the the last week in mostly " + languages.join(", ") + ".";
<commit_msg>Update dashboard at Fri Dec 18 02:17:47 NPT 2015<commit_after>var data = {"labels": ["12 December", "13 December", "14 December", "15 December", "16 December", "17 December", "18 December"], "datasets": [{"languages": [[], [], [], [], [], ["Python", "Other"], ["Python", "JavaScript", "Bash"]], "pointHighlightFill": "#fff", "fillColor": "rgba(151,187,205,0.2)", "pointHighlightStroke": "rgba(151,187,205,1)", "time": [" 0 secs", " 0 secs", " 0 secs", " 0 secs", " 0 secs", "53 mins", "2 hrs 17 mins"], "pointColor": "rgba(151,187,205,1)", "strokeColor": "rgba(151,187,205,1)", "pointStrokeColor": "#fff", "data": [0.0, 0.0, 0.0, 0.0, 0.0, 0.8852777777777778, 2.2880555555555557], "label": "Dataset"}]};
var totalTime = "3 hours 10 minutes";
var languages = ["Python", "JavaScript", "Bash"];
var ctx = document.getElementById("chart").getContext("2d");
var myLineChart = new Chart(ctx).Line(data, {
    tooltipTemplate = function(valuesObject){
      var label = valuesObject.label;
      var idx = data.labels.indexOf(label);
      var result = data.datasets[0].time[idx];
      if (data.datasets[0].languages[idx] !== 0)
        result += '[' + data.datasets[0].languages[idx].join(", ") + "]"
      return result;
    }
});
document.getElementById("summary").innerHTML = "I  have written code for " + totalTime + " in the the last week in mostly " + languages.join(", ") + ".";
<|endoftext|>


<commit_before>function Cube (element_id) {
	var DOM = document.getElementById(element_id);
	this.DOM = DOM;

	var sides = ['front','top','bottom','back','left','right'];

	for (i in sides) {this[sides[i]]=['z','-y','y','-z','-x','x'][i]}

	function fill () { // fill DOM cube sides with appropriate values
		for (i in sides) {
			var element = document.getElementById(sides[i]);
			var value = this[sides[i]];
			element.innerHTML=value;
			element.style.backgroundColor = get_random_color(value);
		}
	}
	this.fill = fill;

	function make (direction) {
	    rotate(direction)
	}
	this.make = make;

	function rotate (direction) {
		var t_angles = [0,0,0];
		switch (direction) {
	        case "top" : {
	            t_angles = convert_angles(1,0);
	        }; break;
	        case "bottom" : {
	            t_angles = convert_angles(-1,0);
	        }; break;
	        case "left" : {
	            t_angles = convert_angles(0,-1);
	        }; break;
	        case "right" : {
	            t_angles = convert_angles(0,1);
	        }; break;
	        default: throw new TypeError('direction must be "top", "bottom", "left" or "right", but not: "'+direction+'"');
	    };
		rotate3d(t_angles[0],t_angles[1],t_angles[2],90);
	}

	var coords = ['x','y','z']; // initial coord system

	function convert_angles (x,y) {
		return [x,y,0];
	}

	function rotate3d(x,y,z,degree) {
		var matrix = new WebKitCSSMatrix(DOM.style.webkitTransform);
		matrix = matrix.rotateAxisAngle(x,y,z,degree);
		DOM.style.webkitTransform = matrix.toString();
	}
	rotate3d(-1,-1,0,30); // initial rotation

	return this;
}

var cube = new Cube('cube3d') // main cube object
cube.fill();

shortcut.add('up',function () {cube.make('top')});
shortcut.add('down',function () {cube.make('bottom')});
shortcut.add('left',function () {cube.make('left')});
shortcut.add('Right',function () {cube.make('right')});

function get_random_color (value) {
	Math.seedrandom(value);
    var color = 'rgba('+(Math.round(Math.random()*150)+55)+','+(Math.round(Math.random()*250)+5)+','+(Math.round(Math.random()*250)+5)+','+'.5)';
    Math.seedrandom();
    return color;
}<commit_msg>add defauld ords and angles<commit_after>function Cube (element_id) {
	var DOM = document.getElementById(element_id);
	this.DOM = DOM;

	var sides = ['front','top','bottom','back','left','right'];

	for (i in sides) {this[sides[i]]=['z','-y','y','-z','-x','x'][i]}

	function fill () { // fill DOM cube sides with appropriate values
		for (i in sides) {
			var element = document.getElementById(sides[i]);
			var value = this[sides[i]];
			element.innerHTML=value;
			element.style.backgroundColor = get_random_color(value);
		}
	}
	this.fill = fill;

	function make (direction) {
	    rotate(direction)
	}
	this.make = make;

	function rotate (direction) {
		var t_angles = [0,0,0];
		switch (direction) {
	        case "top" : {
	            t_angles = convert_angles(1,0);
	        }; break;
	        case "bottom" : {
	            t_angles = convert_angles(-1,0);
	        }; break;
	        case "left" : {
	            t_angles = convert_angles(0,-1);
	        }; break;
	        case "right" : {
	            t_angles = convert_angles(0,1);
	        }; break;
	        default: throw new TypeError('direction must be "top", "bottom", "left" or "right", but not: "'+direction+'"');
	    };
		rotate3d(t_angles[0],t_angles[1],t_angles[2],90);
	}

	var coords = ['x','y','z']; // initial coord system
	var Ox = 'x',Oy='y'; // initial default ords

	function convert_angles (x,y) {
		var new_angles = [x,y,0];
		var tX = 0,tY=0,tZ=0;
		return new_angles;
	}

	function rotate3d(x,y,z,degree) {
		var matrix = new WebKitCSSMatrix(DOM.style.webkitTransform);
		matrix = matrix.rotateAxisAngle(x,y,z,degree);
		DOM.style.webkitTransform = matrix.toString();
	}
	rotate3d(-1,-1,0,30); // initial rotation

	return this;
}

var cube = new Cube('cube3d') // main cube object
cube.fill();

shortcut.add('up',function () {cube.make('top')});
shortcut.add('down',function () {cube.make('bottom')});
shortcut.add('left',function () {cube.make('left')});
shortcut.add('Right',function () {cube.make('right')});

function get_random_color (value) {
	Math.seedrandom(value);
    var color = 'rgba('+(Math.round(Math.random()*150)+55)+','+(Math.round(Math.random()*250)+5)+','+(Math.round(Math.random()*250)+5)+','+'.5)';
    Math.seedrandom();
    return color;
}<|endoftext|>


<commit_before>var arr = ['assets/taeyeon1.mp4', 'assets/taeyeon.mp4', 'assets/FantasticBaby.mp4', 'assets/ptdemo1.mp4', 'assets/ptdemo2.mp4', 'assets/ptdemo3.mp4'];
var aa = 3;

function vid (a) {
	document.querySelector("#coaster").src = arr[a];
	document.querySelector("#currentvid").load();
	play1();
	toggle();
	aa = a;
	document.getElementById('coaster').addEventListener('ended',nextvid,false);
    function nextvid() {
        forward();
    }
}

function toggle() {
	document.getElementById('test').setAttribute('visible', !document.getElementById('test').getAttribute('visible'));
	document.getElementById('test1').setAttribute('visible', !document.getElementById('test1').getAttribute('visible'));
	document.getElementById('baby').setAttribute('visible', !document.getElementById('baby').getAttribute('visible'));
	document.getElementById('pt').setAttribute('visible', !document.getElementById('pt').getAttribute('visible'));
}

function play1() {
	document.getElementById('coaster').play();
	document.getElementById('play').setAttribute('visible', false);
	document.getElementById('pause').setAttribute('visible', true);
}

function pause1() {
	document.getElementById('coaster').pause();
	document.getElementById('play').setAttribute('visible', true);
	document.getElementById('pause').setAttribute('visible', false);
}

function sound() {
	document.getElementById('coaster').muted = true;
	document.getElementById('mute').setAttribute('visible', true);
	document.getElementById('sound').setAttribute('visible', false);
}

function mute() {
	document.getElementById('coaster').muted = false;
	document.getElementById('mute').setAttribute('visible', false);
	document.getElementById('sound').setAttribute('visible', true);
}

function rewind() {
	document.getElementById('coaster').currentTime = 0;

	if(document.getElementById('coaster').currentTime < 4) {
		if(aa-1 > -1) {
		document.getElementById("coaster").src = arr[aa-1];
		aa -= 1;
		} else {
		document.getElementById('coaster').src= arr[arr.length-1];
		aa = arr.length-1;
		}
	}
}

function forward() {
	if(aa+1 < arr.length) {
		document.getElementById("coaster").src = arr[aa+1];
		aa += 1;
		} else {
		document.getElementById('coaster').src= arr[0];
		aa = 0;
		}
}

function start () {
	vid(3);
	pause1();
}<commit_msg>forward update<commit_after>var arr = ['assets/taeyeon1.mp4', 'assets/taeyeon.mp4', 'assets/FantasticBaby.mp4', 'assets/ptdemo1.mp4', 'assets/ptdemo2.mp4', 'assets/ptdemo3.mp4'];
var aa = 3;

function vid (a) {
	document.querySelector("#coaster").src = arr[a];
	document.querySelector("#currentvid").load();
	play1();
	toggle();
	aa = a;
	document.getElementById('coaster').addEventListener('ended',nextvid,false);
    function nextvid() {
        forward();
    }
}

function toggle() {
	document.getElementById('test').setAttribute('visible', !document.getElementById('test').getAttribute('visible'));
	document.getElementById('test1').setAttribute('visible', !document.getElementById('test1').getAttribute('visible'));
	document.getElementById('baby').setAttribute('visible', !document.getElementById('baby').getAttribute('visible'));
	document.getElementById('pt').setAttribute('visible', !document.getElementById('pt').getAttribute('visible'));
}

function play1() {
	document.getElementById('coaster').play();
	document.getElementById('play').setAttribute('visible', false);
	document.getElementById('pause').setAttribute('visible', true);
}

function pause1() {
	document.getElementById('coaster').pause();
	document.getElementById('play').setAttribute('visible', true);
	document.getElementById('pause').setAttribute('visible', false);
}

function sound() {
	document.getElementById('coaster').muted = true;
	document.getElementById('mute').setAttribute('visible', true);
	document.getElementById('sound').setAttribute('visible', false);
}

function mute() {
	document.getElementById('coaster').muted = false;
	document.getElementById('mute').setAttribute('visible', false);
	document.getElementById('sound').setAttribute('visible', true);
}

function rewind() {
	document.getElementById('coaster').currentTime = 0;

	if(document.getElementById('coaster').currentTime < 4) {
		if(aa-1 > -1) {
		document.getElementById("coaster").src = arr[aa-1];
		aa -= 1;
		} else {
		document.getElementById('coaster').src= arr[arr.length-1];
		aa = arr.length-1;
		}
	}
}

function forward() {
	if(aa+1 < arr.length) {
		document.getElementById("coaster").src = arr[aa+1];
		aa += 1;
		} else {
		document.getElementById('coaster').src= arr[0];
		aa = 0;
		}

		play1();
}

function start () {
	vid(3);
	pause1();
}<|endoftext|>