<commit_before>var express = require('express');
var scrapy  = require('node-scrapy');
var moment  = require('moment');
var _       = require('lodash');
var fs      = require('fs');
var app     = express();

app.listen('8085')

console.log('Scraping server running on 8085');

var jsonbuilds = {builds: {}, last_updated: ''};

var characterJson = ["Abathur", "Anub'arak", "Artanis", "Arthas", "Azmodan", "Brightwing", "Chen", "Cho", "Chromie", "Dehaka", "Diablo", "E.T.C.", "Falstad", "Gall", "Gazlowe", "Greymane", "Illidan", "Jaina", "Johanna", "Kael'thas", "Kerrigan", "Kharazim", "Leoric", "Li Li", "Li-Ming", "Lt. Morales", "Lunara", "Malfurion", "Medivh", "Muradin", "Murky", "Nazeebo", "Nova", "Raynor", "Rehgar", "Rexxar", "Sgt. Hammer", "Sonya", "Stitches", "Sylvanas", "Tassadar", "The Butcher", "The Lost Vikings", "Thrall", "Tracer", "Tychus", "Tyrael", "Tyrande", "Uther", "Valla", "Xul", "Zagara", "Zeratul"];

jsonbuilds.last_updated = moment().format('MMMM Do YYYY');

_.forEach(characterJson, function (value, key)  {

  var url = 'http://www.hotslogs.com/Sitewide/HeroDetails?Hero=' + value

  var model =
    {
      build1: {
        lv01: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent01', get: 'title' },
        lv04: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent04', get: 'title' },
        lv07: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent07', get: 'title' },
        lv10: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent10', get: 'title' },
        lv13: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent13', get: 'title' },
        lv16: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent16', get: 'title' },
        lv20: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent20', get: 'title' }
      },
      build2: {
        lv01: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent01', get: 'title' },
        lv04: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent04', get: 'title' },
        lv07: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent07', get: 'title' },
        lv10: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent10', get: 'title' },
        lv13: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent13', get: 'title' },
        lv16: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent16', get: 'title' },
        lv20: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent20', get: 'title' }
      }
    }

  scrapy.scrape(url, model, function(err, data) {
      if (err) return console.error(err)

      jsonbuilds.builds[value] = data

      console.log(Object.size(jsonbuilds.builds), characterJson.length);

      if (Object.size(jsonbuilds.builds) === characterJson.length){

        fs.writeFile('hotsbuilds.json', JSON.stringify(jsonbuilds, null, 4), function(err){
          console.log('File successfully written! - Check your project directory for the hotsbuilds.json file');
        })

      }
  });

})

Object.size = function(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

exports = module.exports = app;<commit_msg>added cron job<commit_after>var express = require('express');
var scrapy  = require('node-scrapy');
var moment  = require('moment');
var _       = require('lodash');
var fs      = require('fs');
var app     = express();
var CronJob = require('cron').CronJob;

app.listen('8085')

console.log('Scraping server running on 8085');

var jsonbuilds = {builds: {}, last_updated: ''};

var characterJson = ["Abathur", "Anub'arak", "Artanis", "Arthas", "Azmodan", "Brightwing", "Chen", "Cho", "Chromie", "Dehaka", "Diablo", "E.T.C.", "Falstad", "Gall", "Gazlowe", "Greymane", "Illidan", "Jaina", "Johanna", "Kael'thas", "Kerrigan", "Kharazim", "Leoric", "Li Li", "Li-Ming", "Lt. Morales", "Lunara", "Malfurion", "Medivh", "Muradin", "Murky", "Nazeebo", "Nova", "Raynor", "Rehgar", "Rexxar", "Sgt. Hammer", "Sonya", "Stitches", "Sylvanas", "Tassadar", "The Butcher", "The Lost Vikings", "Thrall", "Tracer", "Tychus", "Tyrael", "Tyrande", "Uther", "Valla", "Xul", "Zagara", "Zeratul"];

jsonbuilds.last_updated = moment().format('MMMM Do YYYY, h:mm a');

var job = new CronJob({
  cronTime: '0 40 17 * * 3',
  onTick: function() {
    /*
     * Runs every Wednesday
     * at 06:00:00 AM.
     */
    console.log('Cron Job Started ', moment().format('LLL'))

    _.forEach(characterJson, function (value, key)  {

      var url = 'http://www.hotslogs.com/Sitewide/HeroDetails?Hero=' + value
      var model =
          {
            build1: {
              lv01: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent01', get: 'title' },
              lv04: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent04', get: 'title' },
              lv07: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent07', get: 'title' },
              lv10: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent10', get: 'title' },
              lv13: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent13', get: 'title' },
              lv16: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent16', get: 'title' },
              lv20: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl04_imgTalent20', get: 'title' }
            },
            build2: {
              lv01: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent01', get: 'title' },
              lv04: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent04', get: 'title' },
              lv07: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent07', get: 'title' },
              lv10: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent10', get: 'title' },
              lv13: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent13', get: 'title' },
              lv16: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent16', get: 'title' },
              lv20: { selector: '#ctl00_MainContent_RadGridPopularTalentBuilds_ctl00_ctl06_imgTalent20', get: 'title' }
            }
          }

      scrapy.scrape(url, model, function(err, data) {
          if (err) return console.error(err)

          jsonbuilds.builds[value] = data

          console.log(Object.size(jsonbuilds.builds), characterJson.length);

          if (Object.size(jsonbuilds.builds) === characterJson.length){

            fs.writeFile('hotsbuilds.json', JSON.stringify(jsonbuilds, null, 4), function(err){
              console.log('File successfully written!');
            })

          }
      });
    })

  },
  start: false,
  runOnInit: true,
  timeZone: 'America/Caracas'
});

job.start();

Object.size = function(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

exports = module.exports = app;<|endoftext|>


<commit_before>/**
 * Created by rebel on 22/03/2017.
 */

const {ipcMain, dialog, shell} = require('electron');
const fs = require('fs');
const child = require('child_process').execFile;
const _ = require('lodash');

const SettingsFile = require('./SettingsFile');
const Notifications = require('./Notifications');
const ChannelCheck = require('./ChannelCheck');
const Globals = require('./Globals');

let lastClosed = null;

ipcMain.on('channel_play', (event, channelLink, LQ = null, autoRestart = false) => {
    let channelObj = SettingsFile.settingsJson.findChannelByLink(channelLink);

    if (!channelObj) {
        return false;
    }

    channelObj.autoRestart = autoRestart;

    launchPlayerObj(channelObj, LQ);
});

ipcMain.on('channel_disable_autoRestart', (event, channelLink) => {
    let channelObj = SettingsFile.settingsJson.findChannelByLink(channelLink);

    if (!channelObj) {
        return false;
    }

    channelObj.autoRestart = false;
});

function launchPlayerLink(channelLink, LQ = null) {
    let channelObj = Globals.buildChannelObj(channelLink);

    if (channelObj === false) {
        return false;
    }

    launchPlayerObj(channelObj, LQ);
}

function launchPlayerObj(channelObj, LQ = null) {
    let config = SettingsFile.settingsJson;

    if (LQ === null) {
        LQ = config.settings.LQ;
    }

    let playLink = channelObj.link;
    let params = [];

    if (channelObj.protocol === 'rtmp:') {
        playLink = `${playLink} live=1`;

        if (LQ && ['klpq-vps', 'klpq-main'].includes(channelObj.service)) {
            playLink = playLink.replace('/live/', '/restream/');
        }
    } else {
        if (LQ) {
            params = params.concat(['--stream-sorting-excludes', '>=720p,>=high']);
        }
    }

    launchStreamLink(playLink, params, channelObj);
}

function launchStreamLink(playLink, params, channelObj) {
    child('streamlink', [playLink, 'best', '--twitch-disable-hosting'].concat(params), function (err, data, stderr) {
        console.log(err);
        console.log(data);
        console.log('streamlink exited.');

        if (err) {
            if (err.code === 'ENOENT') {
                dialog.showMessageBox({
                    type: 'error',
                    message: 'Streamlink not found.'
                });

                return shell.openExternal(`https://github.com/streamlink/streamlink/releases`);
            } else {
                Notifications.printNotification('Error', err.message);
            }
        }

        if (data.indexOf('error: ') >= 0) {
            let error = data.split('error: ');

            Notifications.printNotification('Error', error[1]);
        }

        if (channelObj.isLive && channelObj.autoRestart) {
            launchStreamLink(playLink, params, channelObj);
        }
    });
}

function launchLastClosed() {
}

exports.launchPlayerLink = launchPlayerLink;
exports.launchPlayerObj = launchPlayerObj;
exports.launchLastClosed = launchLastClosed;
<commit_msg>no message<commit_after>/**
 * Created by rebel on 22/03/2017.
 */

const {ipcMain, dialog, shell} = require('electron');
const fs = require('fs');
const child = require('child_process').execFile;
const _ = require('lodash');

const SettingsFile = require('./SettingsFile');
const Notifications = require('./Notifications');
const ChannelCheck = require('./ChannelCheck');
const Globals = require('./Globals');

ipcMain.on('channel_play', (event, channelLink, LQ = null, autoRestart = false) => {
    let channelObj = SettingsFile.settingsJson.findChannelByLink(channelLink);

    if (!channelObj) {
        return false;
    }

    channelObj.autoRestart = autoRestart;

    launchPlayerObj(channelObj, LQ);
});

ipcMain.on('channel_disable_autoRestart', (event, channelLink) => {
    let channelObj = SettingsFile.settingsJson.findChannelByLink(channelLink);

    if (!channelObj) {
        return false;
    }

    channelObj.autoRestart = false;
});

function launchPlayerLink(channelLink, LQ = null) {
    let channelObj = Globals.buildChannelObj(channelLink);

    if (channelObj === false) {
        return false;
    }

    launchPlayerObj(channelObj, LQ);
}

function launchPlayerObj(channelObj, LQ = null) {
    let config = SettingsFile.settingsJson;

    if (LQ === null) {
        LQ = config.settings.LQ;
    }

    let playLink = channelObj.link;
    let params = [];

    if (channelObj.protocol === 'rtmp:') {
        playLink = `${playLink} live=1`;

        if (LQ && ['klpq-vps', 'klpq-main'].includes(channelObj.service)) {
            playLink = playLink.replace('/live/', '/restream/');
        }
    } else {
        if (LQ) {
            params = params.concat(['--stream-sorting-excludes', '>=720p,>=high']);
        }
    }

    launchStreamLink(playLink, params, channelObj);
}

function launchStreamLink(playLink, params, channelObj) {
    child('streamlink', [playLink, 'best', '--twitch-disable-hosting'].concat(params), function (err, data, stderr) {
        console.log(err);
        console.log(data);
        console.log('streamlink exited.');

        if (err) {
            if (err.code === 'ENOENT') {
                dialog.showMessageBox({
                    type: 'error',
                    message: 'Streamlink not found.'
                });

                return shell.openExternal(`https://github.com/streamlink/streamlink/releases`);
            } else {
                Notifications.printNotification('Error', err.message);
            }
        }

        if (data.indexOf('error: ') >= 0) {
            let error = data.split('error: ');

            Notifications.printNotification('Error', error[1]);
        }

        if (channelObj.isLive && channelObj.autoRestart) {
            launchStreamLink(playLink, params, channelObj);
        }
    });
}

function launchLastClosed() {
}

exports.launchPlayerLink = launchPlayerLink;
exports.launchPlayerObj = launchPlayerObj;
exports.launchLastClosed = launchLastClosed;
<|endoftext|>


<commit_before>
var Promise = require('bluebird');
var inherits = require('../inherits');
var invoker = require('../invoker');
var errors = require('../errors');
var camelcase = require('../camelcase');
var slice = Array.prototype.slice;

module.exports = Connector;

function Connector(portalURL, auth) {
  this.portalURL = portalURL;
  this.auth = auth || {};

  this.auth.type || (this.auth.type = 'basic');

  this.invoke = this.invoke.bind(this);
}

Object.defineProperty(Connector.prototype, 'version', {
	enumerable: false,
	configurable: true,
	get: function () {
		return this.constructor.version;
	}
});

inherits.methods(Connector, {

  raw: function (payload) {
    throw new Error("raw() not available on base connector");
  },

  mangle: function (payload) {
    return payload;
  },

  getDefaultParameterType: function (parameter) {
    if (parameter === 'serviceContext') {
      return 'com.liferay.portal.service.ServiceContext';
    }
  },

  mangleInstantiation: function (payload) {
    var that = this;
    if (Array.isArray(payload)) {
      return payload.map(this.mangleInstantiation, this);
    }
    else {
      return Object.keys(payload).reduce(function (outer, path) {
        outer[ path ] = that.mangleInstantiationCommandBody(payload[ path ]);
        return outer;
      }, {});
    }
  },

  mangleInstantiationCommandBody: function (command) {
    var that = this;
    return Object.keys(command).reduce(function (memo, parameter) {
      var parts, name, type;
      var value = command[ parameter ];
      if (parameter.trim().charAt(0) === '$') {
        memo[ parameter ] = that.mangleInstantiationCommandBody(value);
      }
      else if (typeof value === 'object') {
        parts = parameter.match(/^\+?([^:]+):?([\s\S]+)?$/);
        name = parts[ 1 ];
        type = parts[ 2 ] || that.getDefaultParameterType(parameter);
        if (type) {
          memo[ '+' + name ] = type;
          Object.keys(value).reduce(function (memo, key) {
            memo[ name + '.' + key ] = value[ key ];
            return memo;
          }, memo);
        }
        else {
          memo[ parameter ] = value;
        }
      }
      else {
        memo[ parameter ] = value;
      }
      return memo;
    }, {});
  },

  mangleCamelCase: function (payload) {
    var that = this;
    if (Array.isArray(payload)) {
      return payload.map(this.mangleCamelCase, this);
    }
    else {
      return Object.keys(payload).reduce(function (outer, path) {
        outer[ path ] = that.mangleCamelCaseCommandBody(payload[ path ]);
        return outer;
      }, {});
    }
  },

  mangleCamelCaseCommandBody: function (command) {
    var that = this;
    return Object.keys(command).reduce(function (memo, parameter) {
      if (parameter.trim().charAt(0) === '$') {
        memo[ parameter ] = that.mangleCamelCaseCommandBody(command[ parameter ]);
      }
      else {
        memo[ camelcase.normalizeParameter(parameter) ] = command[ parameter ];
      }
      return memo;
    }, {});
  },

  invoke: function (payload, callback) {
    return this.raw(payload)
    .end()
    .catch(invoker.parseErrors)
    .then(invoker.parseLiferayResponse)
    .then(invoker.getBody)
    .nodeify(callback);
  },

  all: function () {
    return Promise.all(payloads(arguments).map(this.invoke)).cancellable();
  },

  any: function () {
    return Promise.any(payloads(arguments).map(this.invoke)).cancellable();
  }

});

Connector.matches = function (buildnumber) {
  return false;
};

Connector.authenticate = function (portalURL, auth) {
  var connection = new this(portalURL, auth);

  return connection.invoke({
    "/group/get-user-sites": {}
  })
  .then(function (sites) {
    connection.sites = sites;

    if (sites.length) {
      connection.companyId = sites[0].companyId;
    }

    return connection.getUser();
  })
  .catch(errors.NotFound, function () {
    throw new errors.Unauthorized("Could not retrieve user informations");
  })
  .then(function (user) {
    connection.user = user;
    return connection;
  });
};

// Utils

function payloads(args) {
  var results;

  if (args.length === 1) {
    results = args[0];
  }
  else {
    results = slice.call(args);
  }

  return results.filter(function (payload) {
    return payload != null;
  });
}
<commit_msg>Aargh SF<commit_after>
var Promise = require('bluebird');
var inherits = require('../inherits');
var invoker = require('../invoker');
var errors = require('../errors');
var camelcase = require('../camelcase');
var slice = Array.prototype.slice;

module.exports = Connector;

function Connector(portalURL, auth) {
  this.portalURL = portalURL;
  this.auth = auth || {};

  this.auth.type || (this.auth.type = 'basic');

  this.invoke = this.invoke.bind(this);
}

Object.defineProperty(Connector.prototype, 'version', {
  enumerable: false,
  configurable: true,
  get: function () {
    return this.constructor.version;
  }
});

inherits.methods(Connector, {

  raw: function (payload) {
    throw new Error("raw() not available on base connector");
  },

  mangle: function (payload) {
    return payload;
  },

  getDefaultParameterType: function (parameter) {
    if (parameter === 'serviceContext') {
      return 'com.liferay.portal.service.ServiceContext';
    }
  },

  mangleInstantiation: function (payload) {
    var that = this;
    if (Array.isArray(payload)) {
      return payload.map(this.mangleInstantiation, this);
    }
    else {
      return Object.keys(payload).reduce(function (outer, path) {
        outer[ path ] = that.mangleInstantiationCommandBody(payload[ path ]);
        return outer;
      }, {});
    }
  },

  mangleInstantiationCommandBody: function (command) {
    var that = this;
    return Object.keys(command).reduce(function (memo, parameter) {
      var parts, name, type;
      var value = command[ parameter ];
      if (parameter.trim().charAt(0) === '$') {
        memo[ parameter ] = that.mangleInstantiationCommandBody(value);
      }
      else if (typeof value === 'object') {
        parts = parameter.match(/^\+?([^:]+):?([\s\S]+)?$/);
        name = parts[ 1 ];
        type = parts[ 2 ] || that.getDefaultParameterType(parameter);
        if (type) {
          memo[ '+' + name ] = type;
          Object.keys(value).reduce(function (memo, key) {
            memo[ name + '.' + key ] = value[ key ];
            return memo;
          }, memo);
        }
        else {
          memo[ parameter ] = value;
        }
      }
      else {
        memo[ parameter ] = value;
      }
      return memo;
    }, {});
  },

  mangleCamelCase: function (payload) {
    var that = this;
    if (Array.isArray(payload)) {
      return payload.map(this.mangleCamelCase, this);
    }
    else {
      return Object.keys(payload).reduce(function (outer, path) {
        outer[ path ] = that.mangleCamelCaseCommandBody(payload[ path ]);
        return outer;
      }, {});
    }
  },

  mangleCamelCaseCommandBody: function (command) {
    var that = this;
    return Object.keys(command).reduce(function (memo, parameter) {
      if (parameter.trim().charAt(0) === '$') {
        memo[ parameter ] = that.mangleCamelCaseCommandBody(command[ parameter ]);
      }
      else {
        memo[ camelcase.normalizeParameter(parameter) ] = command[ parameter ];
      }
      return memo;
    }, {});
  },

  invoke: function (payload, callback) {
    return this.raw(payload)
    .end()
    .catch(invoker.parseErrors)
    .then(invoker.parseLiferayResponse)
    .then(invoker.getBody)
    .nodeify(callback);
  },

  all: function () {
    return Promise.all(payloads(arguments).map(this.invoke)).cancellable();
  },

  any: function () {
    return Promise.any(payloads(arguments).map(this.invoke)).cancellable();
  }

});

Connector.matches = function (buildnumber) {
  return false;
};

Connector.authenticate = function (portalURL, auth) {
  var connection = new this(portalURL, auth);

  return connection.invoke({
    "/group/get-user-sites": {}
  })
  .then(function (sites) {
    connection.sites = sites;

    if (sites.length) {
      connection.companyId = sites[0].companyId;
    }

    return connection.getUser();
  })
  .catch(errors.NotFound, function () {
    throw new errors.Unauthorized("Could not retrieve user informations");
  })
  .then(function (user) {
    connection.user = user;
    return connection;
  });
};

// Utils

function payloads(args) {
  var results;

  if (args.length === 1) {
    results = args[0];
  }
  else {
    results = slice.call(args);
  }

  return results.filter(function (payload) {
    return payload != null;
  });
}
<|endoftext|>


<commit_before>$(function(){
    var app_name = storage.getItem(config.storageAppNameKey),
    app_dir = get_app_dir(app_name);
    $page = $('#setup-wrapper');
    if(!app_name)
        return;
    awsS3Ready(workOnAwsS3);
    $page.find('input[type=text], textarea').prop('disabled', true);
    function workOnAwsS3()
    {
        loadSetupPage(app_dir, $page);
        $page.find('.fileinput').each(function()
           {
               s3UploadInit($(this), {
                   s3: awsS3,
                   type: 'Image',
                   Bucket: config.s3Bucket,
                   Prefix: app_dir + '/APP_/Uploads/',
                   signExpires: function()
                   {
                       return awsExpireReverse(config.awsExpireReverseInHours);
                   },
                   onerror: handleAWSS3Error
               });
           });
    }
    var isSaving = false;
    $page.find('input[type=text], textarea')
        .bind('focus', function()
           {
               $(this).data('prev_value', $(this).val());
           })
        .bind('blur', function()
           {
               var $this = $(this),
               prev_val = $this.data('prev_value'),
               val = $this.val();
               if(!awsS3)
                   return;
               if(prev_val != val)
               {
                   $this.prop('disabled', true);
                   $this.val(_('Saving...'))
                       .data('value', val);
                   if(isSaving)
                   {
                       var dataSaved;
                       $page.bind('setupPlistSaved', function()
                          {
                              if(!isSaving && !dataSaved)
                              {
                                  $page.unbind('setupPlistSaved',
                                               arguments.callee);
                                  isSaving = true;
                                  saveSetupPlist(app_dir, $page, save_handler);
                              }
                              else if(dataSaved)
                              {
                                  $this.prop('disabled', false);
                                  $this.val(val);
                                  $page.unbind('setupPlistSaved',
                                               arguments.callee);
                              }
                              else
                              {
                                  dataSaved = true;
                              }
                          });
                       return;
                   }
                   isSaving = true;
                   saveSetupPlist(app_dir, $page, save_handler);
                   function save_handler(res)
                   {
                       $this.prop('disabled', false);
                       $this.val(val)
                           .data('value', null);

                       isSaving = false;
                       $page.trigger('setupPlistSaved', res);
                   }
               }
           });
    function loadSetupPage(app_dir, $page, cb)
    {
        $page.find('input[type=text], textarea').prop('disabled', true);
        // load setup plist file and set its content in the form
        awsS3.getObject({
            Bucket: config.s3Bucket,
            Key: app_dir + '/APP_/Uploads/setup_.plist',
            ResponseContentEncoding: 'utf8'
        }, function(err, res)
           {
               $page.find('input[type=text], textarea').prop('disabled', false);
               if(err && err.code != 'NoSuchKey')
               {
                   handleAWSS3Error(err)
                   return;
               }
               var obj = res ? $.plist($.parseXML(res.Body.toString())) : {},
               $inps = $page.find('input[type=hidden], input[type=text], textarea');
               for(var key in obj)
               {
                   $inps.each(function()
                      {
                          var $this = $(this);
                          if($this.attr('name') == key && obj[key])
                          {
                              switch($this.data('type'))
                              {
                              case 'BOOL':
                                  $this.val(obj[key] ? 'True' : 'False');
                                  break;
                              default:
                                  $this.val(obj[key]);
                              }
                          }
                      });
               }
           });
    }
    function saveSetupPlist(app_dir, $page, cb)
    {
        function getObjectOfSetupPage($el)
        {
            var ret = {};
            $el.find('input[type=hidden], input[type=text], textarea')
                .each(function()
                  {
                      var $this = $(this),
                      val = $this.data('value') ||
                          $this.val() || '';
                      switch($this.data('type'))
                      {
                      case 'BOOL':
                          ret[$this.attr('name')] = val == 'True' ? true : false;
                          break;
                      default:
                          ret[$this.attr('name')] = val;
                      }
                  });
            return ret;
        }
        // get attrs
        var obj = getObjectOfSetupPage($page),
        body = $.plist('toString', obj);
        // save setup plist file for input app
        awsS3.putObject({
            Bucket: config.s3Bucket,
            Key: app_dir + '/APP_/Uploads/setup_.plist',
            Body: body
        }, function(err, res)
           {
               if(err)
               {
                   handleAWSS3Error(err);
                   return;
               }
               if(cb)
                   cb(res);
           });
    }
});
<commit_msg>#114 setup_.plist to setup.plist<commit_after>$(function(){
    var app_name = storage.getItem(config.storageAppNameKey),
    app_dir = get_app_dir(app_name);
    $page = $('#setup-wrapper');
    if(!app_name)
        return;
    awsS3Ready(workOnAwsS3);
    $page.find('input[type=text], textarea').prop('disabled', true);
    function workOnAwsS3()
    {
        loadSetupPage(app_dir, $page);
        $page.find('.fileinput').each(function()
           {
               s3UploadInit($(this), {
                   s3: awsS3,
                   type: 'Image',
                   Bucket: config.s3Bucket,
                   Prefix: app_dir + '/APP_/Uploads/',
                   signExpires: function()
                   {
                       return awsExpireReverse(config.awsExpireReverseInHours);
                   },
                   onerror: handleAWSS3Error
               });
           });
    }
    var isSaving = false;
    $page.find('input[type=text], textarea')
        .bind('focus', function()
           {
               $(this).data('prev_value', $(this).val());
           })
        .bind('blur', function()
           {
               var $this = $(this),
               prev_val = $this.data('prev_value'),
               val = $this.val();
               if(!awsS3)
                   return;
               if(prev_val != val)
               {
                   $this.prop('disabled', true);
                   $this.val(_('Saving...'))
                       .data('value', val);
                   if(isSaving)
                   {
                       var dataSaved;
                       $page.bind('setupPlistSaved', function()
                          {
                              if(!isSaving && !dataSaved)
                              {
                                  $page.unbind('setupPlistSaved',
                                               arguments.callee);
                                  isSaving = true;
                                  saveSetupPlist(app_dir, $page, save_handler);
                              }
                              else if(dataSaved)
                              {
                                  $this.prop('disabled', false);
                                  $this.val(val);
                                  $page.unbind('setupPlistSaved',
                                               arguments.callee);
                              }
                              else
                              {
                                  dataSaved = true;
                              }
                          });
                       return;
                   }
                   isSaving = true;
                   saveSetupPlist(app_dir, $page, save_handler);
                   function save_handler(res)
                   {
                       $this.prop('disabled', false);
                       $this.val(val)
                           .data('value', null);

                       isSaving = false;
                       $page.trigger('setupPlistSaved', res);
                   }
               }
           });
    function loadSetupPage(app_dir, $page, cb)
    {
        $page.find('input[type=text], textarea').prop('disabled', true);
        // load setup plist file and set its content in the form
        awsS3.getObject({
            Bucket: config.s3Bucket,
            Key: app_dir + '/APP_/Uploads/setup.plist',
            ResponseContentEncoding: 'utf8'
        }, function(err, res)
           {
               $page.find('input[type=text], textarea').prop('disabled', false);
               if(err && err.code != 'NoSuchKey')
               {
                   handleAWSS3Error(err)
                   return;
               }
               var obj = res ? $.plist($.parseXML(res.Body.toString())) : {},
               $inps = $page.find('input[type=hidden], input[type=text], textarea');
               for(var key in obj)
               {
                   $inps.each(function()
                      {
                          var $this = $(this);
                          if($this.attr('name') == key && obj[key])
                          {
                              switch($this.data('type'))
                              {
                              case 'BOOL':
                                  $this.val(obj[key] ? 'True' : 'False');
                                  break;
                              default:
                                  $this.val(obj[key]);
                              }
                          }
                      });
               }
           });
    }
    function saveSetupPlist(app_dir, $page, cb)
    {
        function getObjectOfSetupPage($el)
        {
            var ret = {};
            $el.find('input[type=hidden], input[type=text], textarea')
                .each(function()
                  {
                      var $this = $(this),
                      val = $this.data('value') ||
                          $this.val() || '';
                      switch($this.data('type'))
                      {
                      case 'BOOL':
                          ret[$this.attr('name')] = val == 'True' ? true : false;
                          break;
                      default:
                          ret[$this.attr('name')] = val;
                      }
                  });
            return ret;
        }
        // get attrs
        var obj = getObjectOfSetupPage($page),
        body = $.plist('toString', obj);
        // save setup plist file for input app
        awsS3.putObject({
            Bucket: config.s3Bucket,
            Key: app_dir + '/APP_/Uploads/setup.plist',
            Body: body
        }, function(err, res)
           {
               if(err)
               {
                   handleAWSS3Error(err);
                   return;
               }
               if(cb)
                   cb(res);
           });
    }
});
<|endoftext|>


<commit_before>/**
 * @class yootil.create
 * @static
 * Contains useful methods to creating things quickly (i.e containers).
 */

yootil.create = (function(){

	return {

		/**
		 * Creates ProBoards div containers.
		 *
		 * Very basic example:
		 *
		 *     var container = yootil.create.container("My Title", "My Content");
		 *
		 *     $("#content").prepend(container);
		 *
		 * This example would make titles and content safe:
		 *
		 *     var container = yootil.create.container("My Title", "My Content", true);
		 *
		 *     $("#content").prepend(container);
		 *
		 * @param {String} title The container title.
		 * @param {String} content The container content.
		 * @param {Mixed} [safe] True or false, or pass an object to make html safe (i.e {title: false, content: true}).
		 * @param {Boolean} safe.title If true, then yootil will handle making the title safe.
		 * @param {Boolean} safe.content If true, then yootil will handle making the content safe.
		 * @param {Object} [title_styles] Key value par for css styles (i.e {color: "red", padding: "11px"}).
		 * @param {Object} [content_styles] Key value par for css styles (i.e {color: "red", padding: "11px"}).
		 * @param {Object} [container_styles] Key value par for css styles (i.e {color: "red", padding: "11px"}).
		 * @param {Boolean} [no_h2] If set to true, it will not wrap the title with an h2 tag.
		 * @param {Boolean} [jquery_obj] If false, returned content will be an html string.
		 * @return {Mixed} Depends on what jquery_obj is set too, default is jquery object.
		 */

		container: function(title, content, safe, title_styles, content_styles, container_styles, no_h2, jquery_obj){
			var html = "";
			var safe_title = safe_content = (typeof safe != "undefined" || safe)? true : false;

			title = (title || "");
			content = (content || "");

			if(typeof safe === "object"){
				if(typeof safe.title != "undefined"){
					safe_title = safe.title;
				}

				if(typeof safe.content != "undefined"){
					safe_content = safe.content;
				}
			}

			container_styles = (container_styles)? container_styles : {};
			title_styles = (title_styles)? title_styles : {};
			content_styles = (content_styles)? content_styles : {};

			title = (safe_title)? yootil.html_encode(title) : title;
			title = (typeof no_h2 != "undefined" && !no_h2)? title : ("
" + title + "
");

			content = (safe_content)? yootil.html_encode(content) : content;

			html += "
";
			html += $("
" + title + "
").css(title_styles).wrap("").parent().html();
			html += $("
" + content + "
").css(content_styles).wrap("").parent().html();
			html += "
";

			if(typeof jquery_obj == "undefined" || jquery_obj){
				return $(html).css(container_styles);
			} else {
				return $(html).css(container_styles).wrap("").parent().html();
			}
		},

		/**
		 * Quickly create a blank page that matches a certain URL.
		 *
		 *     yootil.create.page("shop", "Shop");
		 *
		 * An exanple adding a container to the page as well:
		 *
		 *     yootil.create.page("shop", "Shop").container("The Shop", "Welcome to the Shop").appendTo("#content");
		 *
		 * @param {Mixed} locate This will get matched against the location.href value, can be a string or RegExp object.
		 * @param {String} [document_title] Gets Added onto the current document title.
		 * @param {Boolean} [hide_content] By default the children of #content gets hidden, you can override this.
		 * @chainable
		 */

		page: function(locate, document_title, hide_content){
			var reg = (locate.constructor == RegExp)? locate : new RegExp("\/" + locate, "i");

			if(locate && location.href.match(reg)){
				if(typeof document_title != "undefined" && document_title.length){
					document.title += " - " + document_title;
				}

				if(typeof hide_content == "undefined" || hide_content){
					$("#content[role=main]").children().hide();
				}
			}

			return yootil.create;
		},

		/**
		 * Extend the nav tree easily.
		 *
		 *     yootil.create.nav_branch("/shop/", "Shop");
		 *
		 * @param {String} url URL of the branch.
		 * @param {String} text Text of the branch.
		 * @return {Object} Branch jQuery wrapped.
		 */

		nav_branch: function(url, text){
			var branch = $("#nav-tree li:last").clone();

			if(branch && branch.length){
				branch.find("a").attr("href", url).find("span").html(text);
				branch.appendTo("#nav-tree");
			}

			return branch;
		},

		/**
		 * Create a new tab on the profile
		 *
		 *     yootil.create.profile_tab("Test", "test");
		 *
		 * @param {String} text Text of the branch.
		 * @param {String} page URL of the branch.
		 * @param {Boolean} [active] Active page or not.
		 * @chainable
		 */

		profile_tab: function(text, page, active){
			if(yootil.location.check.profile()){
				var active_class = (active)? " class='ui-active'" : "";
				var ul = $("div.show-user div.ui-tabMenu ul");

				if(ul.length){
					ul.append($("
" + text + "
"));
				}
			}

			return this;
		},

		/**
		 * Creates a profile content box.
		 *
		 *     yootil.create.profile_content_box();
		 *
		 * @param {String} id Enter a ID, or a unique one will be created.
		 * @return {Object} The box is returned wrapped with jQuery.
		 */

		profile_content_box: function(id){
			var uid = (id || $.unique_id("yootil-"));
			var box = $("
").addClass("content-box center-col").attr("id", uid);

			return box;
		},

		/**
		 * Adds a new BBC button to the end on the reply page.
		 *
		 * @param {Object} The image you wish to add
		 * @chainable
		 */

		bbc_button: function(img){
			$(".controls").find(".bbcode-editor, .visual-editor").ready(function(){
				$(".controls").find(".bbcode-editor, .visual-editor").find(".group:last ul:last").append($("
").addClass("button").append($(img)));
			});

			return this;
		}

	};

})();<commit_msg>bbc_button has a function parameter<commit_after>/**
 * @class yootil.create
 * @static
 * Contains useful methods to creating things quickly (i.e containers).
 */

yootil.create = (function(){

	return {

		/**
		 * Creates ProBoards div containers.
		 *
		 * Very basic example:
		 *
		 *     var container = yootil.create.container("My Title", "My Content");
		 *
		 *     $("#content").prepend(container);
		 *
		 * This example would make titles and content safe:
		 *
		 *     var container = yootil.create.container("My Title", "My Content", true);
		 *
		 *     $("#content").prepend(container);
		 *
		 * @param {String} title The container title.
		 * @param {String} content The container content.
		 * @param {Mixed} [safe] True or false, or pass an object to make html safe (i.e {title: false, content: true}).
		 * @param {Boolean} safe.title If true, then yootil will handle making the title safe.
		 * @param {Boolean} safe.content If true, then yootil will handle making the content safe.
		 * @param {Object} [title_styles] Key value par for css styles (i.e {color: "red", padding: "11px"}).
		 * @param {Object} [content_styles] Key value par for css styles (i.e {color: "red", padding: "11px"}).
		 * @param {Object} [container_styles] Key value par for css styles (i.e {color: "red", padding: "11px"}).
		 * @param {Boolean} [no_h2] If set to true, it will not wrap the title with an h2 tag.
		 * @param {Boolean} [jquery_obj] If false, returned content will be an html string.
		 * @return {Mixed} Depends on what jquery_obj is set too, default is jquery object.
		 */

		container: function(title, content, safe, title_styles, content_styles, container_styles, no_h2, jquery_obj){
			var html = "";
			var safe_title = safe_content = (typeof safe != "undefined" || safe)? true : false;

			title = (title || "");
			content = (content || "");

			if(typeof safe === "object"){
				if(typeof safe.title != "undefined"){
					safe_title = safe.title;
				}

				if(typeof safe.content != "undefined"){
					safe_content = safe.content;
				}
			}

			container_styles = (container_styles)? container_styles : {};
			title_styles = (title_styles)? title_styles : {};
			content_styles = (content_styles)? content_styles : {};

			title = (safe_title)? yootil.html_encode(title) : title;
			title = (typeof no_h2 != "undefined" && !no_h2)? title : ("
" + title + "
");

			content = (safe_content)? yootil.html_encode(content) : content;

			html += "
";
			html += $("
" + title + "
").css(title_styles).wrap("").parent().html();
			html += $("
" + content + "
").css(content_styles).wrap("").parent().html();
			html += "
";

			if(typeof jquery_obj == "undefined" || jquery_obj){
				return $(html).css(container_styles);
			} else {
				return $(html).css(container_styles).wrap("").parent().html();
			}
		},

		/**
		 * Quickly create a blank page that matches a certain URL.
		 *
		 *     yootil.create.page("shop", "Shop");
		 *
		 * An exanple adding a container to the page as well:
		 *
		 *     yootil.create.page("shop", "Shop").container("The Shop", "Welcome to the Shop").appendTo("#content");
		 *
		 * @param {Mixed} locate This will get matched against the location.href value, can be a string or RegExp object.
		 * @param {String} [document_title] Gets Added onto the current document title.
		 * @param {Boolean} [hide_content] By default the children of #content gets hidden, you can override this.
		 * @chainable
		 */

		page: function(locate, document_title, hide_content){
			var reg = (locate.constructor == RegExp)? locate : new RegExp("\/" + locate, "i");

			if(locate && location.href.match(reg)){
				if(typeof document_title != "undefined" && document_title.length){
					document.title += " - " + document_title;
				}

				if(typeof hide_content == "undefined" || hide_content){
					$("#content[role=main]").children().hide();
				}
			}

			return yootil.create;
		},

		/**
		 * Extend the nav tree easily.
		 *
		 *     yootil.create.nav_branch("/shop/", "Shop");
		 *
		 * @param {String} url URL of the branch.
		 * @param {String} text Text of the branch.
		 * @return {Object} Branch jQuery wrapped.
		 */

		nav_branch: function(url, text){
			var branch = $("#nav-tree li:last").clone();

			if(branch && branch.length){
				branch.find("a").attr("href", url).find("span").html(text);
				branch.appendTo("#nav-tree");
			}

			return branch;
		},

		/**
		 * Create a new tab on the profile
		 *
		 *     yootil.create.profile_tab("Test", "test");
		 *
		 * @param {String} text Text of the branch.
		 * @param {String} page URL of the branch.
		 * @param {Boolean} [active] Active page or not.
		 * @chainable
		 */

		profile_tab: function(text, page, active){
			if(yootil.location.check.profile()){
				var active_class = (active)? " class='ui-active'" : "";
				var ul = $("div.show-user div.ui-tabMenu ul");

				if(ul.length){
					ul.append($("
" + text + "
"));
				}
			}

			return this;
		},

		/**
		 * Creates a profile content box.
		 *
		 *     yootil.create.profile_content_box();
		 *
		 * @param {String} id Enter a ID, or a unique one will be created.
		 * @return {Object} The box is returned wrapped with jQuery.
		 */

		profile_content_box: function(id){
			var uid = (id || $.unique_id("yootil-"));
			var box = $("
").addClass("content-box center-col").attr("id", uid);

			return box;
		},

		/**
		 * Adds a new BBC button to the end on the reply page.
		 *
		 * @param {Object} img The image element to append.
		 * @param {Function} [func] Adds an onlick event.
		 * @chainable
		 */

		bbc_button: function(img, func){
			$(".controls").find(".bbcode-editor, .visual-editor").ready(function(){
				var li = $("
").addClass("button").append($(img));

				if(func){
					li.click(func);
				}

				$(".controls").find(".bbcode-editor, .visual-editor").find(".group:last ul:last").append(li);
			});

			return this;
		}

	};

})();<|endoftext|>


<commit_before>'use strict';

const express = require('express');
const Slapp = require('slapp');
const Utils = require('./Utils');
const ConvoStore = require('slapp-convo-beepboop');
const Context = require('slapp-context-beepboop');
const ApiHelper = require('./ApiHelper');

// use `PORT` env var on Beep Boop - default to 3000 locally
var port = process.env.PORT || 3000

var slapp = Slapp({
  // Beep Boop sets the SLACK_VERIFY_TOKEN env var
  verify_token: process.env.SLACK_VERIFY_TOKEN,
  convo_store: ConvoStore(),
  context: Context()
})

// enable debugging
require('beepboop-slapp-presence-polyfill')(slapp, {
  debug: true
})
/***************************************************
//
/**************************************************/
var sendMatchConfirmation = (msg, state) => {
  msg.say({
    channel: state.loserId,
    as_user: true,
    text: '',
    attachments: [{
      fallback: 'Match log confirmation',
      title: `Do you confirm that you lost ${state.winnerScore}-${state.loserScore} against <@${state.winnerId}> ?`,
      callback_id: 'match_confirmation_callback',
      color: '#3AA3E3',
      attachment_type: 'default',
      actions: [{
        name: 'match_confirmation_yes',
        text: 'Yep, good game.',
        style: 'primary',
        type: 'button',
        value: Utils.marshall({ state: state, value: 'yes' })
      },
        {
          name: 'match_confirmation_no',
          text: 'NO WAY ! That\' a lie!',
          type: 'button',
          value: Utils.marshall({ state: state, value: 'no' })
        }]
    }]
  });
};

var sendLeaderboard = (msg, playerId) => {
  msg.say({
      channel: playerId,
      as_user: true,
      text: '*Leaderboard:*'
  });

  ApiHelper.getRankings().then((rankings) => {
    var leaderBoard = rankings.sort((element1, element2) => {
      return element1.rank > element2.rank;
    });

    var leaderBoard = rankings.map((element) => {
      return element.rank + "- <@" + element.playerId + ">"
    });

    msg.say({
      channel: playerId,
      as_user: true,
      text: leaderBoard.join('\n')
    });
  });
};

var sendChallengers = (msg, playerId) => {
  msg.say({
    channel: playerId,
    as_user: true,
    text: '*List of challengers:*'
  });

  ApiHelper.getChallengers(playerId).then((challengers) => {
    var messages = [];

    if (typeof(challengers.toBeat) !== 'undefined') {
      messages.push(':up: if you beat <@' + challengers.toBeat.playerId + '> ('+challengers.toBeat.rank+' th)');
    }

    if (typeof(challengers.notToLose) !== 'undefined') {
      messages.push(':down: if you lose against <@' + challengers.notToLose.playerId + '> ('+challengers.notToLose.rank+' th)');
    }

    msg.say({
      channel: playerId,
      as_user: true,
      text: messages.join('\n')
    });
  });
};

//*********************************************
// Register handler
//*********************************************
slapp.message('^.register', ['direct_message'], (msg) => {
  ApiHelper.isPlayerRegistered(msg.meta.user_id)
  .then((isRegistered) => {
    if (!isRegistered) {
      msg.say({
        text: '',
        attachments: [{
          fallback: 'Do you want to register ?',
          title: 'Do you want to join the Akeneo Baby Foot Star League (ABSL) ?',
          callback_id: 'register_callback',
          color: '#3AA3E3',
          attachment_type: 'default',
          actions: [{
            name: 'register_answer',
            text: 'Hell yeah!',
            style: 'primary',
            type: 'button',
            value: 'yes'
          },
          {
            name: 'register_answer',
            text: 'Nope, not intrested',
            type: 'button',
            value: 'no'
          }]
        }]
      })
    } else {
      msg.say('You are already enrolled into the Akeneo Baby Foot Star League');
      msg.say('type .leaderboard to see who is the better players');
      msg.say('type .challengers to see who you need to beat to advance in the leaderboard');
    }
  })
  .catch(function(error) {
    // TODO: Send message if an error occured
  });
})

// Two != callbacks for this
slapp.action('register_callback', 'register_answer', (msg, value) => {
  var registerAnswer = '';

  if (value === 'yes') {
    registerAnswer = 'Awesome! let me register your account before you can start playing.';
    ApiHelper.registerPlayer(msg.meta.user_id);
  } else {
    registerAnswer = 'Alright, then come back to me when you are ready! :soccer:';
    // TODO: Call the helper route
  }

  var responseAnswer = {
    text: '',
    attachments: [{
      fallback: 'Do you want to register ?',
      title: 'Do you want to join the Akeneo Baby Foot Star League (ABSL) ?',
      text: registerAnswer,
      callback_id: 'register_callback',
      color: '#3AA3E3',
    }]
  };

  msg.respond(msg.body.response_url, responseAnswer);
  sendLeaderboard(msg, msg.meta.user_id);
  sendChallengers(msg, msg.meta.user_id);
});

//*********************************************
// Feature match logging: .win
//*********************************************
// TODO: Protected route
slapp.message('^.win <@([^>]+)> ([^>]+)\s*-\s*([^>]+)$', ['direct_message'], (msg, text, loserId, winnerScore, loserScore) => {
  var winnerId = msg.meta.user_id;
  winnerScore = Math.floor(parseInt(winnerScore, 10));
  loserScore = Math.floor(parseInt(loserScore, 10));

  var isInputError = false;
  var textResponse = [];

  // Parsing score values
  if (isNaN(winnerScore) || isNaN(loserScore)) {
    textResponse.push('The scores are not valid values.');
  } else {
    if (loserScore >= winnerScore) {
      isInputError = true;
      textResponse.push(`- Winner\'s score *(${winnerScore})* cannot be greater than the loser\'s score *(${loserScore})*.`);
    }
    if (winnerScore !== 10) {
      isInputError = true;
      textResponse.push(`- Winner\'s score *(${winnerScore})* should be 10.`);
    }
  }

  // Checking player's registration
  if (winnerId === loserId) {
    isInputError = true;
    textResponse.push('- Winner and loser cannot be the same user.');
  }

  ApiHelper.isPlayerRegistered(loserId)
  .then((isRegistered) => {
    if (isRegistered === false) {
      isInputError = true;
      textResponse.push(`- Losing player (<@${loserId}>) is not registered in the Akeneo Baby Foot League.`);
      // TODO: Show the list of user with the rankings
    }

    if (isInputError) {
      msg.say('Oups, an error occured while saving the game result.\n' + textResponse.join('\n'));
    } else {
      var state = {winnerId: winnerId, loserId: loserId, winnerScore, loserScore};
      msg
      .say(`Congratz for this huge win ! Let me check the result with <@${loserId}>.\n I\'ll come back to you when I\m done.`);
      sendMatchConfirmation(msg, state);
    }
  })
  .catch((error) => {
    console.log('An error occured while checking if the loserId is registered');
    console.log(error);
  });
});

slapp.action('match_confirmation_callback', 'match_confirmation_yes', (msg, args) => {
  args = Utils.unmarshall(args);

  if (typeof(args.state) !== 'undefined') {
    var state = args.state;
    ApiHelper.addMatchResult(state.winnerId, state.loserId, state.winnerScore, state.loserScore)
    .then(() => {
      msg.respond(msg.body.response_url, 'Ok, the match result has been successfully registered.');
      ApiHelper.refreshRank(state.winnerId, state.loserId)
      .then(() => {
        // Show leaderboard and challengers
        sendLeaderboard(msg, state.loserId);
        sendChallengers(msg, state.loserId);

        msg.say({
          channel: state.winnerId,
          as_user: true,
          text: `<@${state.loserId}> confirmed your victory :tada:. Good game.`
        });
        sendLeaderboard(msg, state.winnerId);
        sendChallengers(msg, state.winnerId);
      });
    });
  }
});

slapp.action('match_confirmation_callback', 'match_confirmation_no', (msg, args) => {
  args = Utils.unmarshall(args);

  if (typeof(args.state) !== 'undefined') {
    var state = args.state;
    ApiHelper.addMatchResult(state.winnerId, state.loserId, state.winnerScore, state.loserScore)
    .then(()=> {
      msg.respond(msg.body.response_url, `Ok, You\'ll have to see this IRL with <@${state.winnerId}>`);
    });
  }
});

//*********************************************
// Feature leaderboard
//*********************************************
slapp.message('^.leaderboard', ['mention', 'direct_message'], (msg, text) => {
  if (msg.isDirectMessage()) {
    sendLeaderboard(msg, msg.meta.user_id);
  } else if (msg.isMention()) {
    sendLeaderboard(msg, msg.meta.channel_id)
  }
})

slapp.message('^.my-games$', ['direct_message'], (msg, text) => {
  var playerId = msg.meta.user_id;
  ApiHelper.getPlayerGames(playerId).then((playerGames) => {
    playerGames = playerGames.map((game) => {
      var matchResult = '';
      var opponentId = '';

      if (game.winnerId === playerId) {
        matchResult += '*win*';
        opponentId = game.loserId;
      } else {
        matchResult += '*lost*';
        opponentId = game.winnerId;
      }

      var date = new Date(game.date);
      var dateDescription = date.getDate() + '/' + (date.getMonth()+1) + '/' + date.getFullYear();

      return `${dateDescription} - ${matchResult} against <@${opponentId}> ${game.winnerScore}-${game.loserScore}`;
    });

    msg.say({
      text: 'Here is the list of all the games you played:\n' + playerGames.join('\n')
    })
  });
});

slapp.message('^.last-games$', ['direct_message'], (msg, text) => {
  ApiHelper.getGames().then((latestGames) => {
    latestGames = latestGames.map((game) => {
      var date = new Date(game.date);
      var dateDescription = date.getDate() + '/' + (date.getMonth()+1) + '/' + date.getFullYear();

      return `${dateDescription} - <@${game.winnerId}> won against <@${game.loserId}> ${game.winnerScore}-${game.loserScore}`;
    });

    msg.say({
      text: 'Here is the list of the last 15 games:\n' + latestGames.join('\n')
    })
  });
});

//*********************************************
// Feature challengers
//*********************************************
slapp.message('^.challengers', ['direct_message'], (msg, text) => {
  sendChallengers(msg, msg.meta.user_id);
});

//*********************************************
// Help handler .wcid
//*********************************************
slapp.message('help|.wcid', ['mention', 'direct_message'], (msg) => {
  var HELP_TEXT = `
  Hello, I'm Pierluigi the Akeneo Baby Foot referee. Here is the information I can provide you:
  \`help\` - to get some help.
  \`.register\` - to enroll in the baby footleague.
  \`.win <LOSING-PLAYER> <WINNER-SCORE>-<LOSER-SCORE>\` - to log a game result you have won.
  \`.leaderboard\` - Show the Baby foot leaderboard.
  \`.challengers\` - Show the next challengers you need to take on!
  \`.last-games\` - See the last 15 games results played in the league.
  \`.my-games\` - See all your games results in the baby foot league.
  `
  msg.say(HELP_TEXT)
})

// attach Slapp to express server
var server = slapp.attachToExpress(express())

// start http server
server.listen(port, (err) => {
  if (err) {
    return console.error(err)
  }

  console.log(`Listening on port ${port}`)
})
<commit_msg>HOTFIX: Fix win saving results and mention last-games<commit_after>'use strict';

const express = require('express');
const Slapp = require('slapp');
const Utils = require('./Utils');
const ConvoStore = require('slapp-convo-beepboop');
const Context = require('slapp-context-beepboop');
const ApiHelper = require('./ApiHelper');

// use `PORT` env var on Beep Boop - default to 3000 locally
var port = process.env.PORT || 3000

var slapp = Slapp({
  // Beep Boop sets the SLACK_VERIFY_TOKEN env var
  verify_token: process.env.SLACK_VERIFY_TOKEN,
  convo_store: ConvoStore(),
  context: Context()
})

// enable debugging
require('beepboop-slapp-presence-polyfill')(slapp, {
  debug: true
})
/***************************************************
//
/**************************************************/
var sendMatchConfirmation = (msg, state) => {
  msg.say({
    channel: state.loserId,
    as_user: true,
    text: '',
    attachments: [{
      fallback: 'Match log confirmation',
      title: `Do you confirm that you lost ${state.winnerScore}-${state.loserScore} against <@${state.winnerId}> ?`,
      callback_id: 'match_confirmation_callback',
      color: '#3AA3E3',
      attachment_type: 'default',
      actions: [{
        name: 'match_confirmation_yes',
        text: 'Yep, good game.',
        style: 'primary',
        type: 'button',
        value: Utils.marshall({ state: state, value: 'yes' })
      },
        {
          name: 'match_confirmation_no',
          text: 'NO WAY ! That\' a lie!',
          type: 'button',
          value: Utils.marshall({ state: state, value: 'no' })
        }]
    }]
  });
};

var sendLeaderboard = (msg, playerId) => {
  msg.say({
      channel: playerId,
      as_user: true,
      text: '*Leaderboard:*'
  });

  ApiHelper.getRankings().then((rankings) => {
    var leaderBoard = rankings.sort((element1, element2) => {
      return element1.rank > element2.rank;
    });

    var leaderBoard = rankings.map((element) => {
      return element.rank + "- <@" + element.playerId + ">"
    });

    msg.say({
      channel: playerId,
      as_user: true,
      text: leaderBoard.join('\n')
    });
  });
};

var sendChallengers = (msg, playerId) => {
  msg.say({
    channel: playerId,
    as_user: true,
    text: '*List of challengers:*'
  });

  ApiHelper.getChallengers(playerId).then((challengers) => {
    var messages = [];

    if (typeof(challengers.toBeat) !== 'undefined') {
      messages.push(':up: if you beat <@' + challengers.toBeat.playerId + '> ('+challengers.toBeat.rank+' th)');
    }

    if (typeof(challengers.notToLose) !== 'undefined') {
      messages.push(':down: if you lose against <@' + challengers.notToLose.playerId + '> ('+challengers.notToLose.rank+' th)');
    }

    msg.say({
      channel: playerId,
      as_user: true,
      text: messages.join('\n')
    });
  });
};

//*********************************************
// Register handler
//*********************************************
slapp.message('^.register', ['direct_message'], (msg) => {
  ApiHelper.isPlayerRegistered(msg.meta.user_id)
  .then((isRegistered) => {
    if (!isRegistered) {
      msg.say({
        text: '',
        attachments: [{
          fallback: 'Do you want to register ?',
          title: 'Do you want to join the Akeneo Baby Foot Star League (ABSL) ?',
          callback_id: 'register_callback',
          color: '#3AA3E3',
          attachment_type: 'default',
          actions: [{
            name: 'register_answer',
            text: 'Hell yeah!',
            style: 'primary',
            type: 'button',
            value: 'yes'
          },
          {
            name: 'register_answer',
            text: 'Nope, not intrested',
            type: 'button',
            value: 'no'
          }]
        }]
      })
    } else {
      msg.say('You are already enrolled into the Akeneo Baby Foot Star League');
      msg.say('type .leaderboard to see who is the better players');
      msg.say('type .challengers to see who you need to beat to advance in the leaderboard');
    }
  })
  .catch(function(error) {
    // TODO: Send message if an error occured
  });
})

// Two != callbacks for this
slapp.action('register_callback', 'register_answer', (msg, value) => {
  var registerAnswer = '';

  if (value === 'yes') {
    registerAnswer = 'Awesome! let me register your account before you can start playing.';
    ApiHelper.registerPlayer(msg.meta.user_id);
  } else {
    registerAnswer = 'Alright, then come back to me when you are ready! :soccer:';
    // TODO: Call the helper route
  }

  var responseAnswer = {
    text: '',
    attachments: [{
      fallback: 'Do you want to register ?',
      title: 'Do you want to join the Akeneo Baby Foot Star League (ABSL) ?',
      text: registerAnswer,
      callback_id: 'register_callback',
      color: '#3AA3E3',
    }]
  };

  msg.respond(msg.body.response_url, responseAnswer);
  sendLeaderboard(msg, msg.meta.user_id);
  sendChallengers(msg, msg.meta.user_id);
});

//*********************************************
// Feature match logging: .win
//*********************************************
// TODO: Protected route
slapp.message('^.win <@([^>]+)> ([^>]+)\s*-\s*([^>]+)$', ['direct_message'], (msg, text, loserId, winnerScore, loserScore) => {
  var winnerId = msg.meta.user_id;
  winnerScore = Math.floor(parseInt(winnerScore, 10));
  loserScore = Math.floor(parseInt(loserScore, 10));

  var isInputError = false;
  var textResponse = [];

  // Parsing score values
  if (isNaN(winnerScore) || isNaN(loserScore)) {
    textResponse.push('The scores are not valid values.');
  } else {
    if (loserScore >= winnerScore) {
      isInputError = true;
      textResponse.push(`- Winner\'s score *(${winnerScore})* cannot be greater than the loser\'s score *(${loserScore})*.`);
    }
    if (winnerScore !== 10) {
      isInputError = true;
      textResponse.push(`- Winner\'s score *(${winnerScore})* should be 10.`);
    }
  }

  // Checking player's registration
  if (winnerId === loserId) {
    isInputError = true;
    textResponse.push('- Winner and loser cannot be the same user.');
  }

  ApiHelper.isPlayerRegistered(loserId)
  .then((isRegistered) => {
    if (isRegistered === false) {
      isInputError = true;
      textResponse.push(`- Losing player (<@${loserId}>) is not registered in the Akeneo Baby Foot League.`);
      // TODO: Show the list of user with the rankings
    }

    if (isInputError) {
      msg.say('Oups, an error occured while saving the game result.\n' + textResponse.join('\n'));
    } else {
      var state = {winnerId: winnerId, loserId: loserId, winnerScore, loserScore};
      msg
      .say(`Congratz for this huge win ! Let me check the result with <@${loserId}>.\n I\'ll come back to you when I\m done.`);
      sendMatchConfirmation(msg, state);
    }
  })
  .catch((error) => {
    console.log('An error occured while checking if the loserId is registered');
    console.log(error);
  });
});

slapp.action('match_confirmation_callback', 'match_confirmation_yes', (msg, args) => {
  args = Utils.unmarshall(args);

  if (typeof(args.state) !== 'undefined') {
    var state = args.state;
    ApiHelper.addMatchResult(state.winnerId, state.loserId, state.winnerScore, state.loserScore)
    .then(() => {
      msg.respond(msg.body.response_url, 'Ok, the match result has been successfully registered.');
      ApiHelper.refreshRank(state.winnerId, state.loserId)
      .then(() => {
        // Show leaderboard and challengers
        sendLeaderboard(msg, state.loserId);
        sendChallengers(msg, state.loserId);

        msg.say({
          channel: state.winnerId,
          as_user: true,
          text: `<@${state.loserId}> confirmed your victory :tada:. Good game.`
        });
        sendLeaderboard(msg, state.winnerId);
        sendChallengers(msg, state.winnerId);
      });
    });
  }
});

slapp.action('match_confirmation_callback', 'match_confirmation_no', (msg, args) => {
  args = Utils.unmarshall(args);

  if (typeof(args.state) !== 'undefined') {
    var state = args.state;
    msg.respond(msg.body.response_url, `Ok, You\'ll have to see this IRL with <@${state.winnerId}>`);
  }
});

//*********************************************
// Feature leaderboard
//*********************************************
slapp.message('^.leaderboard', ['mention', 'direct_message'], (msg, text) => {
  if (msg.isDirectMessage()) {
    sendLeaderboard(msg, msg.meta.user_id);
  } else if (msg.isMention()) {
    sendLeaderboard(msg, msg.meta.channel_id)
  }
})

slapp.message('^.my-games$', ['direct_message'], (msg, text) => {
  var playerId = msg.meta.user_id;
  ApiHelper.getPlayerGames(playerId).then((playerGames) => {
    playerGames = playerGames.map((game) => {
      var matchResult = '';
      var opponentId = '';

      if (game.winnerId === playerId) {
        matchResult += '*win*';
        opponentId = game.loserId;
      } else {
        matchResult += '*lost*';
        opponentId = game.winnerId;
      }

      var date = new Date(game.date);
      var dateDescription = date.getDate() + '/' + (date.getMonth()+1) + '/' + date.getFullYear();

      return `${dateDescription} - ${matchResult} against <@${opponentId}> ${game.winnerScore}-${game.loserScore}`;
    });

    msg.say({
      text: 'Here is the list of all the games you played:\n' + playerGames.join('\n')
    })
  });
});

slapp.message('^.last-games$', ['mention', 'direct_message'], (msg, text) => {
  ApiHelper.getGames().then((latestGames) => {
    latestGames = latestGames.map((game) => {
      var date = new Date(game.date);
      var dateDescription = date.getDate() + '/' + (date.getMonth()+1) + '/' + date.getFullYear();

      return `${dateDescription} - <@${game.winnerId}> won against <@${game.loserId}> ${game.winnerScore}-${game.loserScore}`;
    });

    msg.say({
      as_user: true,
      text: 'Here is the list of the last 15 games:\n' + latestGames.join('\n')
    })
  });
});

//*********************************************
// Feature challengers
//*********************************************
slapp.message('^.challengers', ['direct_message'], (msg, text) => {
  sendChallengers(msg, msg.meta.user_id);
});

//*********************************************
// Help handler .wcid
//*********************************************
slapp.message('help|.wcid', ['mention', 'direct_message'], (msg) => {
  var HELP_TEXT = `
  Hello, I'm Pierluigi the Akeneo Baby Foot referee. Here is the information I can provide you:
  \`help\` - to get some help.
  \`.register\` - to enroll in the baby footleague.
  \`.win <LOSING-PLAYER> <WINNER-SCORE>-<LOSER-SCORE>\` - to log a game result you have won.
  \`.leaderboard\` - Show the Baby foot leaderboard.
  \`.challengers\` - Show the next challengers you need to take on!
  \`.last-games\` - See the last 15 games results played in the league.
  \`.my-games\` - See all your games results in the baby foot league.
  `
  msg.say(HELP_TEXT)
})

// attach Slapp to express server
var server = slapp.attachToExpress(express())

// start http server
server.listen(port, (err) => {
  if (err) {
    return console.error(err)
  }

  console.log(`Listening on port ${port}`)
})
<|endoftext|>


<commit_before>// MIT License
// (c) 2013-2014 Laurent Debacker

// Usage:
// var filter = QM.And(QM.Not(QM.Eq("type",[QM.String("foo"),QM.String("bar")])),QM.Fts("text","belgian chocolate"));
// var sort = QM.Sort(QM.Order("rooms",false),QM.Order("price"));
// window.location.search = "?f=" + filter + "&s=" + sort;

var QM = function() {
	function escapeChar(c) {
		var s = c.charCodeAt(0).toString(16);
		return '%' + (s.length == 1 ? '0' + s : s);
	}

	function escapeString(s) {
		return encodeURIComponent(s).replace(/[()]/g, escapeChar);
	}

	return {
		Sort: function(orders) {
			if(orders.constructor != Array)
				orders = arguments;
			return orders.join();
		},

		Order: function(field, ascending) {
			field = escapeString(field);
			if(ascending === false)
				return '!' + field;
			return field;
		},

		Not: function(predicate) {
			return "not(" + predicate + ")";
		},

		And: function(predicates) {
			if(predicates.constructor != Array)
				predicates = arguments;
			return "and(" + predicates.join() + ")";
		},

		Or: function(predicates) {
			if(predicates.constructor != Array)
				predicates = arguments;
			return "or(" + predicates.join() + ")";
		},

		Eq: function (field, values) {
			if(values.constructor != Array)
				values = Array.prototype.splice.call(arguments, 1);
			var s = "eq(" + escapeString(field);
			if(values.length) s += "," + values.join();
			return s + ")";
		},

		Lt: function(field, value) {
			return "lt(" + escapeString(field) + "," + value + ")";
		},

		Le: function(field, value) {
			return "le(" + escapeString(field) + "," + value + ")";
		},

		Gt: function(field, value) {
			return "gt(" + escapeString(field) + "," + value + ")";
		},

		Ge: function(field, value) {
			return "ge(" + escapeString(field) + "," + value + ")";
		},

		Fts: function(field, string) {
			return "fts(" + escapeString(field) + "," + QM.String(string) + ")";
		},

		Null: "null",

		Boolean: function(bool) {
			if(bool === null) return QM.Null;
			return bool ? "true" : "false";
		},

		Number: function(number) {
			if(number === null) return QM.Null;
			return "" + number;
		},

		String: function(string) {
			if(string === null) return QM.Null;
			return "'" + escapeString(string) + "'";
		},

		Date: function(date) {
			if(date === null) return QM.Null;
			return date.toJSON();
		}
	};
} ();
<commit_msg>Update to new string format in JS library.<commit_after>// MIT License
// (c) 2013-2014 Laurent Debacker

// Usage:
// var filter = QM.And(QM.Not(QM.Eq("type",[QM.String("foo"),QM.String("bar")])),QM.Fts("text","belgian chocolate"));
// var sort = QM.Sort(QM.Order("rooms",false),QM.Order("price"));
// window.location.search = "?f=" + filter + "&s=" + sort;

var QM = function() {
	function escapeChar(c) {
		var s = c.charCodeAt(0).toString(16);
		return '%' + (s.length == 1 ? '0' + s : s);
	}

	function escapeString(s) {
		return encodeURIComponent(s).replace(/[()]/g, escapeChar);
	}

	return {
		Sort: function(orders) {
			if(orders.constructor != Array)
				orders = arguments;
			return orders.join();
		},

		Order: function(field, ascending) {
			field = escapeString(field);
			if(ascending === false)
				return '!' + field;
			return field;
		},

		Not: function(predicate) {
			return "not(" + predicate + ")";
		},

		And: function(predicates) {
			if(predicates.constructor != Array)
				predicates = arguments;
			return "and(" + predicates.join() + ")";
		},

		Or: function(predicates) {
			if(predicates.constructor != Array)
				predicates = arguments;
			return "or(" + predicates.join() + ")";
		},

		Eq: function (field, values) {
			if(values.constructor != Array)
				values = Array.prototype.splice.call(arguments, 1);
			var s = "eq(" + escapeString(field);
			if(values.length) s += "," + values.join();
			return s + ")";
		},

		Lt: function(field, value) {
			return "lt(" + escapeString(field) + "," + value + ")";
		},

		Le: function(field, value) {
			return "le(" + escapeString(field) + "," + value + ")";
		},

		Gt: function(field, value) {
			return "gt(" + escapeString(field) + "," + value + ")";
		},

		Ge: function(field, value) {
			return "ge(" + escapeString(field) + "," + value + ")";
		},

		Fts: function(field, string) {
			return "fts(" + escapeString(field) + "," + QM.String(string) + ")";
		},

		Null: "null",

		Boolean: function(bool) {
			if(bool === null) return QM.Null;
			return bool ? "true" : "false";
		},

		Number: function(number) {
			if(number === null) return QM.Null;
			return "" + number;
		},

		String: function(string) {
			if(string === null) return QM.Null;
			return "$" + escapeString(string);
		},

		Date: function(date) {
			if(date === null) return QM.Null;
			return date.toJSON();
		}
	};
} ();
<|endoftext|>


<commit_before>/**
 * `core/user` data store: Surveys.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import isPlainObject from 'lodash/isPlainObject';
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { CORE_USER } from './constants';
import { createFetchStore } from '../../data/create-fetch-store';
import { createValidatedAction } from '../../data/utils';
import { createCacheKey } from '../../api';
import { getItem, setItem } from '../../api/cache';
const { createRegistrySelector } = Data;

const fetchTriggerSurveyStore = createFetchStore( {
	baseName: 'triggerSurvey',
	controlCallback: ( { triggerID } ) => {
		return API.set( 'core', 'user', 'survey-trigger', { triggerID } );
	},
	argsToParams: ( triggerID ) => {
		return { triggerID };
	},
	reducerCallback: ( state, { survey_payload, session } ) => { // eslint-disable-line camelcase
		// We don't replace survey if we already have one.
		if ( baseSelectors.getCurrentSurvey( state ) ) {
			return state;
		}
		return {
			...state,
			currentSurvey: survey_payload,
			currentSurveySession: session,
		};
	},
	validateParams: ( { triggerID } = {} ) => {
		invariant( 'string' === typeof triggerID && triggerID.length, 'triggerID is required and must be a string' );
	},

} );

const fetchSendSurveyEventStore = createFetchStore( {
	baseName: 'sendSurveyEvent',
	controlCallback: ( { event, session } ) => API.set( 'core', 'user', 'survey-event', { event, session } ),
	argsToParams: ( event, session ) => {
		return { event, session };
	},
} );

const baseInitialState = {
	currentSurvey: null,
	currentSurveySession: null,
};

const baseActions = {
	/**
	 * Triggers a survey.
	 *
	 * @since 1.34.0
	 *
	 * @param {string} triggerID     Trigger ID for the survey.
	 * @param {Object} options       Survey options.
	 * @param {number} [options.ttl] Optional. TTL for survey.
	 * @return {Object} Object with `response` and `error`.
	 */
	triggerSurvey: createValidatedAction(
		( triggerID, options = {} ) => {
			const { ttl = 0 } = options;
			invariant( 'string' === typeof triggerID && triggerID.length, 'triggerID is required and must be a string' );
			invariant( isPlainObject( options ), 'options must be an object' );
			invariant( 'number' === typeof ttl, 'options.ttl must be a number' );
		},
		function* ( triggerID, options = {} ) {
			const { ttl = 0 } = options;
			const { select } = yield Data.commonActions.getRegistry();
			// Bail if there is already a current survey.
			if ( select( CORE_USER ).getCurrentSurvey() ) {
				return {};
			}

			const cacheKey = createCacheKey( 'core', 'user', 'survey-trigger', { triggerID } );
			const { cacheHit } = yield Data.commonActions.await( getItem( cacheKey ) );

			if ( false === cacheHit ) {
				const { response, error } = yield fetchTriggerSurveyStore.actions.fetchTriggerSurvey( triggerID );
				if ( error ) {
					return { response, error };
				}
				if ( ttl > 0 ) {
					setTimeout( () => {
						function* generator() {
							// With a positive ttl we cache an empty object to avoid calling fetchTriggerSurvey() again after 30s.
							yield Data.commonActions.await( setItem( cacheKey, {}, { ttl } ) );
						}

						const gen = generator();
						gen.next();
					}, 3000 );
				}
			}

			return {
				response: {},
				error: false,
			};
		}
	),

	/**
	 * Sends a survey event.
	 *
	 * @since 1.34.0
	 *
	 * @param {string} eventID   Event ID for the survey.
	 * @param {Object} eventData Event Data.
	 * @return {Object} Object with `response` and `error`.
	 */
	sendSurveyEvent: createValidatedAction(
		( eventID, eventData = {} ) => {
			invariant( 'string' === typeof eventID && eventID.length, 'eventID is required and must be a string' );
			invariant( isPlainObject( eventData ), 'eventData must be an object' );
		},
		function* ( eventID, eventData = {} ) {
			const event = { [ eventID ]: eventData };
			const { select } = yield Data.commonActions.getRegistry();
			const session = select( CORE_USER ).getCurrentSurveySession();
			if ( session ) {
				const { response, error } = yield fetchSendSurveyEventStore.actions.fetchSendSurveyEvent( event, session );
				return { response, error };
			}
		}
	),
};

const baseSelectors = {
	/**
	 * Gets the current survey.
	 *
	 * @since 1.34.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {Object} Current survey object.
	 */
	getCurrentSurvey( state ) {
		return state.currentSurvey;
	},

	/**
	 * Gets the current survey session.
	 *
	 * @since 1.34.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {Object} Current survey session object.
	 */
	getCurrentSurveySession( state ) {
		return state.currentSurveySession;
	},

	/**
	 * Gets the completion triggers for the current survey, if one exists.
	 *
	 * @since 1.35.0
	 *
	 * @return {Array|null} Current survey's completion triggers if available; `null` if no questions/survey are found.
	 */
	getCurrentSurveyCompletions: createRegistrySelector( ( select ) => () => {
		const currentSurvey = select( CORE_USER ).getCurrentSurvey();

		return currentSurvey?.completion || null;
	} ),

	/**
	 * Gets the questions from the current survey, if one exists.
	 *
	 * @since 1.35.0
	 *
	 * @return {Array|null} Current survey's questions if available; `null` if no questions/survey are found.
	 */
	getCurrentSurveyQuestions: createRegistrySelector( ( select ) => () => {
		const currentSurvey = select( CORE_USER ).getCurrentSurvey();

		return currentSurvey?.question || null;
	} ),

};

const store = Data.combineStores(
	fetchTriggerSurveyStore,
	fetchSendSurveyEventStore,
	{
		initialState: baseInitialState,
		actions: baseActions,
		selectors: baseSelectors,
	}
);

export const initialState = store.initialState;
export const actions = store.actions;
export const selectors = store.selectors;

export default store;
<commit_msg>Remove yield and simplify code.<commit_after>/**
 * `core/user` data store: Surveys.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import isPlainObject from 'lodash/isPlainObject';
import invariant from 'invariant';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { CORE_USER } from './constants';
import { createFetchStore } from '../../data/create-fetch-store';
import { createValidatedAction } from '../../data/utils';
import { createCacheKey } from '../../api';
import { getItem, setItem } from '../../api/cache';
const { createRegistrySelector } = Data;

const fetchTriggerSurveyStore = createFetchStore( {
	baseName: 'triggerSurvey',
	controlCallback: ( { triggerID } ) => {
		return API.set( 'core', 'user', 'survey-trigger', { triggerID } );
	},
	argsToParams: ( triggerID ) => {
		return { triggerID };
	},
	reducerCallback: ( state, { survey_payload, session } ) => { // eslint-disable-line camelcase
		// We don't replace survey if we already have one.
		if ( baseSelectors.getCurrentSurvey( state ) ) {
			return state;
		}
		return {
			...state,
			currentSurvey: survey_payload,
			currentSurveySession: session,
		};
	},
	validateParams: ( { triggerID } = {} ) => {
		invariant( 'string' === typeof triggerID && triggerID.length, 'triggerID is required and must be a string' );
	},

} );

const fetchSendSurveyEventStore = createFetchStore( {
	baseName: 'sendSurveyEvent',
	controlCallback: ( { event, session } ) => API.set( 'core', 'user', 'survey-event', { event, session } ),
	argsToParams: ( event, session ) => {
		return { event, session };
	},
} );

const baseInitialState = {
	currentSurvey: null,
	currentSurveySession: null,
};

const baseActions = {
	/**
	 * Triggers a survey.
	 *
	 * @since 1.34.0
	 *
	 * @param {string} triggerID     Trigger ID for the survey.
	 * @param {Object} options       Survey options.
	 * @param {number} [options.ttl] Optional. TTL for survey.
	 * @return {Object} Object with `response` and `error`.
	 */
	triggerSurvey: createValidatedAction(
		( triggerID, options = {} ) => {
			const { ttl = 0 } = options;
			invariant( 'string' === typeof triggerID && triggerID.length, 'triggerID is required and must be a string' );
			invariant( isPlainObject( options ), 'options must be an object' );
			invariant( 'number' === typeof ttl, 'options.ttl must be a number' );
		},
		function* ( triggerID, options = {} ) {
			const { ttl = 0 } = options;
			const { select } = yield Data.commonActions.getRegistry();
			// Bail if there is already a current survey.
			if ( select( CORE_USER ).getCurrentSurvey() ) {
				return {};
			}

			const cacheKey = createCacheKey( 'core', 'user', 'survey-trigger', { triggerID } );
			const { cacheHit } = yield Data.commonActions.await( getItem( cacheKey ) );

			if ( false === cacheHit ) {
				const { response, error } = yield fetchTriggerSurveyStore.actions.fetchTriggerSurvey( triggerID );
				if ( error ) {
					return { response, error };
				}
				if ( ttl > 0 ) {
					setTimeout( () => {
						// With a positive ttl we cache an empty object to avoid calling fetchTriggerSurvey() again after 30s.
						setItem( cacheKey, {}, { ttl } );
					}, 3000 );
				}
			}

			return {
				response: {},
				error: false,
			};
		}
	),

	/**
	 * Sends a survey event.
	 *
	 * @since 1.34.0
	 *
	 * @param {string} eventID   Event ID for the survey.
	 * @param {Object} eventData Event Data.
	 * @return {Object} Object with `response` and `error`.
	 */
	sendSurveyEvent: createValidatedAction(
		( eventID, eventData = {} ) => {
			invariant( 'string' === typeof eventID && eventID.length, 'eventID is required and must be a string' );
			invariant( isPlainObject( eventData ), 'eventData must be an object' );
		},
		function* ( eventID, eventData = {} ) {
			const event = { [ eventID ]: eventData };
			const { select } = yield Data.commonActions.getRegistry();
			const session = select( CORE_USER ).getCurrentSurveySession();
			if ( session ) {
				const { response, error } = yield fetchSendSurveyEventStore.actions.fetchSendSurveyEvent( event, session );
				return { response, error };
			}
		}
	),
};

const baseSelectors = {
	/**
	 * Gets the current survey.
	 *
	 * @since 1.34.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {Object} Current survey object.
	 */
	getCurrentSurvey( state ) {
		return state.currentSurvey;
	},

	/**
	 * Gets the current survey session.
	 *
	 * @since 1.34.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {Object} Current survey session object.
	 */
	getCurrentSurveySession( state ) {
		return state.currentSurveySession;
	},

	/**
	 * Gets the completion triggers for the current survey, if one exists.
	 *
	 * @since 1.35.0
	 *
	 * @return {Array|null} Current survey's completion triggers if available; `null` if no questions/survey are found.
	 */
	getCurrentSurveyCompletions: createRegistrySelector( ( select ) => () => {
		const currentSurvey = select( CORE_USER ).getCurrentSurvey();

		return currentSurvey?.completion || null;
	} ),

	/**
	 * Gets the questions from the current survey, if one exists.
	 *
	 * @since 1.35.0
	 *
	 * @return {Array|null} Current survey's questions if available; `null` if no questions/survey are found.
	 */
	getCurrentSurveyQuestions: createRegistrySelector( ( select ) => () => {
		const currentSurvey = select( CORE_USER ).getCurrentSurvey();

		return currentSurvey?.question || null;
	} ),

};

const store = Data.combineStores(
	fetchTriggerSurveyStore,
	fetchSendSurveyEventStore,
	{
		initialState: baseInitialState,
		actions: baseActions,
		selectors: baseSelectors,
	}
);

export const initialState = store.initialState;
export const actions = store.actions;
export const selectors = store.selectors;

export default store;
<|endoftext|>