<commit_before>/*
TODO:
 [root@engine engine-config]# engine-config -s CORSSupport=true
 [root@engine engine-config]# engine-config -s 'CORSAllowedOrigins=*' # or more particular

  ovirt-engine: CORS filter: use dynamic list of hosts

 replace in manifest.json: "content-security-policy": "default-src * 'unsafe-inline' 'unsafe-eval';connect-src https://engine.local;"
    connect-src 'self';   -->   connect-src [ENGINE_URL];
    implement check here in provider about content of cockpit/machines/manifest.json -- reinstallation
       - maybe script updating it, rpm|user can call it

 encoded url:
 https://engine.local/ovirt-engine/web-ui/authorizedRedirect.jsp?redirectUrl=https://192.168.122.101:9090/machines__hash__token=TOKEN

 check error status code: 401 - remove token and reissue login
 */

/*
 To have this external provider working, the oVirt SSO token must be provided to the cockpit/machines plugin.
 Parameters to cockpit packages can't be provided via '?' in the URL, so the hash '#' sign is used as workaround.
 Example:
    https://[ENGINE_HOST]/ovirt-engine/web-ui/authorizedRedirect.jsp?redirectUrl=https://[COCKPI_HOST]:9090/machines__hash__token=TOKEN
 */

var _ = function (str) { return str; } // TODO: implement localization

function logDebug (msg) {
  if (OVIRT_PROVIDER.CONFIG.debug) {
    console.log('OVIRT_PROVIDER: ' + msg);
  }
}

function logError (msg) {
  console.error('OVIRT_PROVIDER: ' + msg);
}

var OVIRT_PROVIDER = {
  name: 'oVirt',
  token: null,
  CONFIG: {// TODO: read dynamically from config file
    debug: true, // set to false to turn off debug logging
    OVIRT_BASE_URL: 'https://engine.local/ovirt-engine',
  },
  actions: { // this list is for reference only, it's expected to be replaced by init()
    delayRefresh: function () {},
    deleteUnlistedVMs: function (vmNames) {},
    updateOrAddVm: function (vm) {},
  },

  _renderDisclaimer: function (text) { // TODO: if only automatic redirect works ... But CSP
    text = text ? text : _('The oVirt External Provider is installed but default Libvirt is used instead since oVirt' +
      ' login token is missing.
If you want otherwise, please');
    var loc = window.location.href;
    var cockpitHost = loc.substring(0, loc.indexOf('/cockpit/'));
    var url = 'https://[ENGINE_HOST]/ovirt-engine/web-ui/authorizedRedirect.jsp?redirectUrl=' + cockpitHost + '/machines__hash__token=TOKEN';
    var div = document.createElement('div');
    // TODO: if it can't be resolved, then translation will be required
    div.innerHTML = '
' +
      ' ' + text +
      '

either land to cockpit from oVirt User Portal
' +
      '
or specify ENGINE_HOST in following link: ' + url + '

' +
      '
';
    document.body.insertBefore(div, document.body.firstChild);
    window.setTimeout(function () {document.body.removeChild(div);}, 10000);
  },
  _renderUnauthorized: function () { // TODO: if only automatic redirect works ... But CSP
    OVIRT_PROVIDER._renderDisclaimer(_('Authorization expired. Log in again, please'));
  },
  _login: function (baseUrl) {
    var location = window.location;
    var tokenStart = location.hash.indexOf("token=");
    var token = window.sessionStorage.getItem('OVIRT_PROVIDER_TOKEN'); // as default

    if (tokenStart >= 0) { // TOKEN received as a part of URL has precedence
      token = location.hash.substr(tokenStart + "token=".length);
      logDebug("_login(): token found in params: " + token);
      OVIRT_PROVIDER.token = token;
      window.sessionStorage.setItem('OVIRT_PROVIDER_TOKEN', token);
      return true;
    } else if (token) { // search sessionStorrage
      logDebug("_login(): token found in sessionStorrage: " + token);
      OVIRT_PROVIDER.token = token;
      return true;
    } else { // TODO: redirect to SSO is recently not working because of CSP
      // redirect to oVirt's SSO
      var url = baseUrl + '/web-ui/authorizedRedirect.jsp?redirectUrl=' + location + '#token=TOKEN';
      logDebug("_login(): missing oVirt SSO token, redirecting to SSO: " + url);

      //window.cockpit.location.replace(url);
      /*            var a = document.createElement('a');
       a.setAttribute('href', 'http://www.google.com');
       a.innerText = 'My Link';
       document.body.appendChild(a);
       */

//            window.location = url;
      logError('SSO token is not provided!');
      OVIRT_PROVIDER._renderDisclaimer();
    }
    return false;
  },

  _ovirtApiGet: function (resource) {
    return $.ajax({
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/xml',
        'Authorization': 'Bearer ' + OVIRT_PROVIDER.token
      },
      url: OVIRT_PROVIDER.CONFIG.OVIRT_BASE_URL + '/api/' + resource
    }).fail(function (data) {
      logError('HTTP GET failed: ' + JSON.stringify(data));
      if (data.status === 401) { // Unauthorized
        OVIRT_PROVIDER._renderUnauthorized(); // TODO: or better redirect to SSO
      }
    });
  },

  _ovirtApiPost: function (resource, input) {
    return $.ajax({
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/xml',
        'Authorization': 'Bearer ' + OVIRT_PROVIDER.token
      },
      url: OVIRT_PROVIDER.CONFIG.OVIRT_BASE_URL + '/api/' + resource,
      data: input
    }).fail(function (data) {
      logError('HTTP POST failed: ' + JSON.stringify(data));
      if (data.status === 401) { // Unauthorized
        OVIRT_PROVIDER._renderUnauthorized(); // TODO: or better redirect to SSO
      }
    });
  },

  _adaptVm: function (ovirtVm) {
    var vcpus = function (ovirtCpu) {
      var t = ovirtCpu.topology;
      return t.sockets * t.cores * t.threads;
    };
    var currentMemory = function (ovirtMem) {
      return ovirtMem / 1024; // to KiB
    };
    var state = function (ovirtStatus) {
      return ovirtStatus;
    };

    return {
      id: ovirtVm.id,
      name: ovirtVm.name,
      state: state(ovirtVm.status),
      osType: ovirtVm.os.type,
      fqdn: ovirtVm.fqdn,
      uptime: -1, // TODO
      currentMemory: currentMemory(ovirtVm.memory),
      rssMemory: undefined, // TODO
      vcpus: vcpus(ovirtVm.cpu),
      autostart: undefined,
      actualTimeInMs: undefined, // TODO
      cpuTime: undefined // TODO
    };
  },
  /**
   * Initialize the Provider
   */
  init: function (actionCreators) {
    logDebug('init() called');
    OVIRT_PROVIDER.actions = actionCreators;
    return OVIRT_PROVIDER._login(OVIRT_PROVIDER.CONFIG.OVIRT_BASE_URL);
  },
  /*
   UNASSIGNED,
   DOWN,
   UP,
   POWERING_UP,
   PAUSED,
   MIGRATING,
   UNKNOWN,
   NOT_RESPONDING,
   WAIT_FOR_LAUNCH,
   REBOOT_IN_PROGRESS,
   SAVING_STATE,
   RESTORING_STATE,
   SUSPENDED,
   IMAGE_LOCKED,
   POWERING_DOWN */
  canReset: function (state) {
    return state && (state === 'up' || state === 'migrating');
  },
  canShutdown: function (state) {
    return OVIRT_PROVIDER.canReset(state) || (state === 'reboot_in_progress' || state === 'paused' || state === 'powering_up');
  },
  isRunning: function (state) {
    return OVIRT_PROVIDER.canReset(state);
  },
  canRun: function (state) {
    return state && (state === 'down' || state === 'paused' || state === 'suspended');
  },
  vmStateMap: { // TODO: localization needed
    unassigned: undefined,
    down: {className: 'fa fa-arrow-circle-o-down icon-1x-vms', title: 'The VM is down.'},
    up: {className: 'pficon pficon-ok icon-1x-vms', title: _("The VM is running.")},
    powering_up: {className: 'glyphicon glyphicon-wrench icon-1x-vms', title: _('The VM is going up.')},
    paused: {className: 'pficon pficon-pause icon-1x-vms', title: _('The VM is paused.')},
    migrating: {className: 'pficon pficon-route icon-1x-vms', title: _('The VM is migrating.')},
    unknown: undefined,
    not_responding: {className: 'pficon pficon-error-circle-o icon-1x-vms', title: _("The VM is not responding.")},
    wait_for_launch: {className: 'fa fa-clock-o icon-1x-vms', title: _('The VM is scheduled for launch.')},
    reboot_in_progress: undefined, // TODO
    saving_state: undefined,
    restoring_state: undefined,
    suspended: {className: 'pficon pficon-pause icon-1x-vms', title: _('The VM is suspended.')},
    image_locked: {className: 'fa fa-lock icon-1x-vms', title: _("The VM's image is locked.")},
    powering_down: {className: 'glyphicon glyphicon-wrench icon-1x-vms', title: _('The VM is going down.')},
  },

  /**
   * Get single VM
   * @param payload { lookupId: name }
   * @constructor
   */
  GET_VM: function (payload) {
    logDebug('GET_VM() called');
    logError('OVIRT_PROVIDER.GET_VM() is not implemented'); // should not be needed
    return function (dispatch) {
    };
  },

  /**
   * Initiate read of all VMs
   */
  GET_ALL_VMS: function () {
    logDebug('GET_ALL_VMS() called');
    return function (dispatch) {
      OVIRT_PROVIDER._ovirtApiGet('vms')
        .done(function (data) { // data is demarshalled JSON
          logDebug('GET_ALL_VMS successful');

          var vmNames = [];
          data.vm.forEach(function (ovirtVm) {
            var vm = OVIRT_PROVIDER._adaptVm(ovirtVm);
            vmNames.push(vm.name);
            dispatch(OVIRT_PROVIDER.actions.updateOrAddVm(vm));
          });

          // remove undefined domains
          dispatch(OVIRT_PROVIDER.actions.deleteUnlistedVMs(vmNames));

          // keep polling AFTER all VM details have been read (avoid overlap)
          dispatch(OVIRT_PROVIDER.actions.delayRefresh());
        });
    };
  },

  /**
   * Call `shut down` on the VM
   * @param payload { name, id }
   * @constructor
   */
  SHUTDOWN_VM: function (payload) {
    var name = payload.name;
    var id = payload.id;
    logDebug('OVIRT_PROVIDER.SHUTDOWN_VM(name="' + name + '", id="' + id + '")');
    return function (dispatch) {
      return OVIRT_PROVIDER._ovirtApiPost('vms/' + id + '/shutdown', '<action />');
    };
  },

  /**
   * Force shut down on the VM.
   *
   * @param payload { name, id }
   * @constructor
   */
  FORCEOFF_VM: function (payload) {
    var name = payload.name;
    var id = payload.id;
    logDebug('OVIRT_PROVIDER.FORCEOFF_VM(name="' + name + '", id="' + id + '")');
    return function (dispatch) {
      return OVIRT_PROVIDER._ovirtApiPost('vms/' + id + '/stop', '<action />');
    };
  },

  REBOOT_VM: function (payload) {
    var name = payload.name;
    var id = payload.id;
    logDebug('OVIRT_PROVIDER.REBOOT_VM(name="' + name + '", id="' + id + '")');

    return function (dispatch) {
      return OVIRT_PROVIDER._ovirtApiPost('vms/' + id + '/reboot', '<action />');
    };
  },

  FORCEREBOOT_VM: function (payload) {
    return OVIRT_PROVIDER.REBOOT_VM(payload); // TODO: implement 'force'
  },

  START_VM: function (payload) {
    var name = payload.name;
    var id = payload.id;
    logDebug('OVIRT_PROVIDER.START_VM(name="' + name + '", id="' + id + '")');

    return function (dispatch) {
      return OVIRT_PROVIDER._ovirtApiPost('vms/' + id + '/start', '<action />');
    };
  }
};

function init () {
  console.log('Registering oVirt provider');
  window.EXTERNAL_PROVIDER = OVIRT_PROVIDER;
}

init();
<commit_msg>Change in requirements: Libvirt is used for data retrieval<commit_after>/**
 To have this oVirt external provider for Cockpit/machines working,
 the oVirt SSO token must be provided to the cockpit/machines plugin.

 Parameters to cockpit packages can't be provided via '?' in the URL, so the hash '#' sign is used as workaround.

 Example:
    https://[ENGINE_HOST]/ovirt-engine/web-ui/authorizedRedirect.jsp?redirectUrl=https://[COCKPI_HOST]:9090/machines__hash__token=TOKEN

 General notes:
   - data are still retrieved from Libvirt, active operations only are redirected to oVirt

 WIP: there will be redirect to Engine SSO if the token is missing, but recently CSP is blocking it...
 WIP: since the web-ui/authorizedRedirect.jsp, ovirt-web-ui.0.1.1-2 (part of ovirt-engine 4.1) - considering moving similar code to enginess.war
 */

var _ = function (str) { return str; } // TODO: implement localization

function logDebug (msg) {
  if (OVIRT_PROVIDER.CONFIG.debug) {
    console.log('OVIRT_PROVIDER: ' + msg);
  }
}

function logError (msg) {
  console.error('OVIRT_PROVIDER: ' + msg);
}

var OVIRT_PROVIDER = {
  name: 'oVirt',
  token: null,
  CONFIG: {// TODO: read dynamically from config file
    debug: true, // set to false to turn off debug logging
    OVIRT_BASE_URL: 'https://engine.local/ovirt-engine',
  },
  actions: { // this list is for reference only, it's expected to be replaced by init()
    delayRefresh: function () {},
    deleteUnlistedVMs: function (vmNames) {},
    updateOrAddVm: function (vm) {},
  },

  /**
   * Initialize the Provider
   */
  init: function (actionCreators, nextProvider) {
    logDebug('init() called');
    OVIRT_PROVIDER.actions = actionCreators;
    OVIRT_PROVIDER.nextProvider = nextProvider;
    OVIRT_PROVIDER.vmStateMap = nextProvider.vmStateMap; // reuse Libvirt since it is used for data retrieval

    return OVIRT_PROVIDER._login(OVIRT_PROVIDER.CONFIG.OVIRT_BASE_URL);
  },

  vmStateMap: null, // see init()
  canReset: function (state) {
    return OVIRT_PROVIDER.nextProvider.canReset(state);
  },
  canShutdown: function (state) {
    return OVIRT_PROVIDER.nextProvider.canShutdown(state);
  },
  isRunning: function (state) {
    return OVIRT_PROVIDER.nextProvider.isRunning(state);
  },
  canRun: function (state) {
    return OVIRT_PROVIDER.nextProvider.canRun(state);
  },

  /**
   * Get a single VM
   *
   * Redirected to Libvirt provider.
   */
  GET_VM: function (payload) {
    logDebug('GET_VM: redirecting to Libvirt provider');
    return OVIRT_PROVIDER.nextProvider.GET_VM(payload);
  },

  /**
   * Initiate read of all VMs
   *
   * Redirected to Libvirt provider.
   */
  GET_ALL_VMS: function () {
    logDebug('GET_ALL_VMS: redirecting to Libvirt provider');
    return OVIRT_PROVIDER.nextProvider.GET_ALL_VMS();

    /* Not needed, since Libvirt is used for data retrieval
        logDebug('GET_ALL_VMS() called');
        return function (dispatch) {
          OVIRT_PROVIDER._ovirtApiGet('vms')
            .done(function (data) { // data is demarshalled JSON
              logDebug('GET_ALL_VMS successful');

              var vmNames = [];
              data.vm.forEach(function (ovirtVm) {
                var vm = OVIRT_PROVIDER._adaptVm(ovirtVm);
                vmNames.push(vm.name);
                dispatch(OVIRT_PROVIDER.actions.updateOrAddVm(vm));
              });

              // remove undefined domains
              dispatch(OVIRT_PROVIDER.actions.deleteUnlistedVMs(vmNames));

              // keep polling AFTER all VM details have been read (avoid overlap)
              dispatch(OVIRT_PROVIDER.actions.delayRefresh());
            });
        };*/
  },

  /**
   * Call `shut down` on the VM.
   *
   * Redirected to Libvirt provider.
   *
   * @param payload { name, id }
   * @constructor
   */
  SHUTDOWN_VM: function (payload) {
    var name = payload.name;
    var id = payload.id;
    logDebug('OVIRT_PROVIDER.SHUTDOWN_VM(name="' + name + '", id="' + id + '")');
    return function (dispatch) {
      return OVIRT_PROVIDER._ovirtApiPost('vms/' + id + '/shutdown', '<action />');
    };
  },

  /**
   * Force shut down on the VM.
   *
   * @param payload { name, id }
   * @constructor
   */
  FORCEOFF_VM: function (payload) {
    var name = payload.name;
    var id = payload.id;
    logDebug('OVIRT_PROVIDER.FORCEOFF_VM(name="' + name + '", id="' + id + '")');
    return function (dispatch) {
      return OVIRT_PROVIDER._ovirtApiPost('vms/' + id + '/stop', '<action />');
    };
  },

  REBOOT_VM: function (payload) {
    var name = payload.name;
    var id = payload.id;
    logDebug('OVIRT_PROVIDER.REBOOT_VM(name="' + name + '", id="' + id + '")');

    return function (dispatch) {
      return OVIRT_PROVIDER._ovirtApiPost('vms/' + id + '/reboot', '<action />');
    };
  },

  FORCEREBOOT_VM: function (payload) {
    return OVIRT_PROVIDER.REBOOT_VM(payload); // TODO: implement 'force'
  },

  START_VM: function (payload) {
    var name = payload.name;
    var id = payload.id;
    logDebug('OVIRT_PROVIDER.START_VM(name="' + name + '", id="' + id + '")');

    return function (dispatch) {
      return OVIRT_PROVIDER._ovirtApiPost('vms/' + id + '/start', '<action />');
    };
  },

  // --- End of API, private functions follow -------
  _renderDisclaimer: function (text) { // TODO: if only automatic redirect works ... But CSP
    text = text ? text : _('The oVirt External Provider is installed but default Libvirt is used instead since oVirt' +
      ' login token is missing.
If you want otherwise, please');

    var style = "position: fixed; bottom: 0; left: 0; right: 0;"; // always visible footer

    var loc = window.location.href;
    var cockpitHost = loc.substring(0, loc.indexOf('/cockpit/'));
    var url = 'https://[ENGINE_HOST]/ovirt-engine/web-ui/authorizedRedirect.jsp?redirectUrl=' + cockpitHost + '/machines__hash__token=TOKEN';

    var div = document.createElement('div');
    div.className = 'alert alert-danger';
    div.setAttribute('style', style);
    div.innerHTML = '
' +
      ' ' + text +
      '

either land to cockpit from oVirt User Portal
' +
      '
or specify ENGINE_HOST in following link: ' + url + '

' +
      '
';
    document.body.insertBefore(div, document.body.firstChild);
    window.setTimeout(function () {document.body.removeChild(div);}, 10000);
  },

  _renderUnauthorized: function () { // TODO: if only automatic redirect works ... But CSP
    OVIRT_PROVIDER._renderDisclaimer(_('Authorization expired. Log in again, please'));
  },

  _login: function (baseUrl) {
    var location = window.location;
    var tokenStart = location.hash.indexOf("token=");
    var token = window.sessionStorage.getItem('OVIRT_PROVIDER_TOKEN'); // as default

    if (tokenStart >= 0) { // TOKEN received as a part of URL has precedence
      token = location.hash.substr(tokenStart + "token=".length);
      logDebug("_login(): token found in params: " + token);
      OVIRT_PROVIDER.token = token;
      window.sessionStorage.setItem('OVIRT_PROVIDER_TOKEN', token);
      return true;
    } else if (token) { // search sessionStorrage
      logDebug("_login(): token found in sessionStorrage: " + token);
      OVIRT_PROVIDER.token = token;
      return true;
    } else { // TODO: redirect to SSO is recently not working because of CSP
      // redirect to oVirt's SSO
      var url = baseUrl + '/web-ui/authorizedRedirect.jsp?redirectUrl=' + location + '#token=TOKEN';
      logDebug("_login(): missing oVirt SSO token, redirecting to SSO: " + url);

      // window.cockpit.location.replace(url);
      /*            var a = document.createElement('a');
       a.setAttribute('href', 'http://www.google.com');
       a.innerText = 'My Link';
       document.body.appendChild(a);
       */

      // window.location = url;
      logError('SSO token is not provided!');
      OVIRT_PROVIDER._renderDisclaimer();
    }
    return false;
  },

  _ovirtApiGet: function (resource) {
    return $.ajax({
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/xml',
        'Authorization': 'Bearer ' + OVIRT_PROVIDER.token
      },
      url: OVIRT_PROVIDER.CONFIG.OVIRT_BASE_URL + '/api/' + resource
    }).fail(function (data) {
      logError('HTTP GET failed: ' + JSON.stringify(data));
      if (data.status === 401) { // Unauthorized
        OVIRT_PROVIDER._renderUnauthorized(); // TODO: or better redirect to SSO
      }
    });
  },

  _ovirtApiPost: function (resource, input) {
    return $.ajax({
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/xml',
        'Authorization': 'Bearer ' + OVIRT_PROVIDER.token
      },
      url: OVIRT_PROVIDER.CONFIG.OVIRT_BASE_URL + '/api/' + resource,
      data: input
    }).fail(function (data) {
      logError('HTTP POST failed: ' + JSON.stringify(data));
      if (data.status === 401) { // Unauthorized
        OVIRT_PROVIDER._renderUnauthorized(); // TODO: or better redirect to SSO
      }
    });
  },

  // -------------------------------------------------------
/*
  // Not needed anymore, since Libvirt is used for data retrieval.
  canReset: function (state) {
    return state && (state === 'up' || state === 'migrating');
  },
  canShutdown: function (state) {
    return OVIRT_PROVIDER.canReset(state) || (state === 'reboot_in_progress' || state === 'paused' || state === 'powering_up');
  },
  isRunning: function (state) {
    return OVIRT_PROVIDER.canReset(state);
  },
  canRun: function (state) {
    return state && (state === 'down' || state === 'paused' || state === 'suspended');
  },
  vmStateMap: { // TODO: localization needed
    unassigned: undefined,
    down: {className: 'fa fa-arrow-circle-o-down icon-1x-vms', title: 'The VM is down.'},
    up: {className: 'pficon pficon-ok icon-1x-vms', title: _("The VM is running.")},
    powering_up: {className: 'glyphicon glyphicon-wrench icon-1x-vms', title: _('The VM is going up.')},
    paused: {className: 'pficon pficon-pause icon-1x-vms', title: _('The VM is paused.')},
    migrating: {className: 'pficon pficon-route icon-1x-vms', title: _('The VM is migrating.')},
    unknown: undefined,
    not_responding: {className: 'pficon pficon-error-circle-o icon-1x-vms', title: _("The VM is not responding.")},
    wait_for_launch: {className: 'fa fa-clock-o icon-1x-vms', title: _('The VM is scheduled for launch.')},
    reboot_in_progress: undefined, // TODO
    saving_state: undefined,
    restoring_state: undefined,
    suspended: {className: 'pficon pficon-pause icon-1x-vms', title: _('The VM is suspended.')},
    image_locked: {className: 'fa fa-lock icon-1x-vms', title: _("The VM's image is locked.")},
    powering_down: {className: 'glyphicon glyphicon-wrench icon-1x-vms', title: _('The VM is going down.')},
  },
  _adaptVm: function (ovirtVm) {
    var vcpus = function (ovirtCpu) {
      var t = ovirtCpu.topology;
      return t.sockets * t.cores * t.threads;
    };
    var currentMemory = function (ovirtMem) {
      return ovirtMem / 1024; // to KiB
    };
    var state = function (ovirtStatus) {
      return ovirtStatus;
    };

    return {
      id: ovirtVm.id,
      name: ovirtVm.name,
      state: state(ovirtVm.status),
      osType: ovirtVm.os.type,
      fqdn: ovirtVm.fqdn,
      uptime: -1, // TODO
      currentMemory: currentMemory(ovirtVm.memory),
      rssMemory: undefined, // TODO
      vcpus: vcpus(ovirtVm.cpu),
      autostart: undefined,
      actualTimeInMs: undefined, // TODO
      cpuTime: undefined // TODO
    };
  },
*/
};

function init () {
  console.log('Registering oVirt provider');
  window.EXTERNAL_PROVIDER = OVIRT_PROVIDER;
}

init();

// --------------------------------------
/*
 [root@engine engine-config]# engine-config -s CORSSupport=true
 [root@engine engine-config]# engine-config -s 'CORSAllowedOrigins=*' # or more particular

 ovirt-engine: CORS filter: use dynamic list of hosts (https://gerrit.ovirt.org/#/c/68529/ )

 replace in manifest.json: "content-security-policy": "default-src * 'unsafe-inline' 'unsafe-eval';connect-src https://engine.local;"
 connect-src 'self';   -->   connect-src [ENGINE_URL];
 implement check here in provider about content of cockpit/machines/manifest.json -- reinstallation
 - maybe script updating it, rpm|user can call it

 encoded url:
 https://engine.local/ovirt-engine/web-ui/authorizedRedirect.jsp?redirectUrl=https://192.168.122.101:9090/machines__hash__token=TOKEN
 */
<|endoftext|>


<commit_before>import pegjs from 'pegjs';
import loaderUtils from 'loader-utils';

export default function loader(source) {
  if (this.cacheable) {
    this.cacheable();
  }

  const query = loaderUtils.parseQuery(this.query);
  const cacheParserResults = !!query.cache;

  // Description of PEG.js options: https://github.com/pegjs/pegjs#javascript-api
  const pegOptions = { output: 'source', cache: cacheParserResults };

  return `module.exports = ${pegjs.buildParser(source, pegOptions)};`;
}
<commit_msg>Support the 'optimize' option of buildParser() to specify 'speed' or 'size' preference<commit_after>import pegjs from 'pegjs';
import loaderUtils from 'loader-utils';

export default function loader(source) {
  if (this.cacheable) {
    this.cacheable();
  }

  const query = loaderUtils.parseQuery(this.query);
  const cacheParserResults = !!query.cache;
  const optimizeParser = query.optimize || 'speed';

  // Description of PEG.js options: https://github.com/pegjs/pegjs#javascript-api
  const pegOptions = {
    output: 'source',
    cache: cacheParserResults,
    optimize: optimizeParser
  };

  return `module.exports = ${pegjs.buildParser(source, pegOptions)};`;
}
<|endoftext|>


<commit_before>'use strict';

//  ▄▄ •             ▄▄▄ . ▄· ▄▌
// ▐█ ▀ ▪▪     ▪     ▀▄.▀·▐█▪██▌
// ▄█ ▀█▄ ▄█▀▄  ▄█▀▄ ▐▀▀▪▄▐█▌▐█▪
// ▐█▄▪▐█▐█▌.▐▌▐█▌.▐▌▐█▄▄▌ ▐█▀·.
// ·▀▀▀▀  ▀█▄▀▪ ▀█▄▀▪ ▀▀▀   ▀ •

import jsonPath from 'jsonpath'
import _ from 'lodash'

// flat object of all registered services by name
var _services = {}

// default service configuration
const _config = {
  strict: true,

  data: {
    lazy: false,

    matching: {
      queries : true
    },

    publish: {
      ignore: {
        falsy: true
      }
    }
  },

  traversals: {

  }
}

// supported flavors of service tree traversals
const traversals = ['depth', 'breadth', 'async_global', 'async_local']

// a canonical, heiarchical source of data that can delegate updates bi-directionally
export class Service {

  constructor(name: String, model?: Function, parent?: Service, children?: Array = [], config?: Object = _config) {
    if (_.isUndefined(name) || Service.isRegistered(name)) {
      throw `Services must have unique names: ${name}`
    }

    this.name          = name
    this.model         = model
    this.parent        = parent ? parent.relateTo(this) : null
    this.children      = this.relateToAll(children)
    this.state         = {}
    this.subscriptions = []
    this.config        = config

    _services[name] = this

    if (this.model) {
      this.model(this.state)
    }
  }

  // traverses service tree via a conflict-free path and matches subscribers against the published data
  publish(data, success: Function = _.noop, error: Function = _.noop, traversal: String = 'breadth', direction: String = 'down'): Promise {
    // ensure data is pure
    data = _.clone(data, true)

    // subscribers who match the current published data
    const scrips = this.subscriptions.filter(scrip => !!this.matches(data, scrip).size)

    // current service node. proxy data if this service's data update matches any subscriptions
    const result = scrips.length ? scrips.map(scrip => scrip.on(data)) : data

    // traverse service node tree and publish on each "next" node
    return this.traverse(
      result, traversal, direction, success, error,
      child => {
        child.publish(result, success, error, traversal, direction)
      }
    )
  }

  // creates and registers a publish subscription against a jsonpath pattern
  subscribe(path: String = '$', on: Function): Subscription {
    const scrip = new Subscription(this.name, path, on)

    this.subscriptions.push(scrip)

    return scrip
  }

  // ends a publish subscription
  unsubscribe(scrip: Subscription) {
    this.subscriptions.splice(this.subscriptions.indexOf(scrip), 1)
  }

  // updates the Service's canonical data source with new data and publishs the change
  update(data, success?: Function, error?: Function): Promise {
    this.data = data

    return this.publish(data, success, error)
  }

  // merges and updates the Service's canonical date source with a new data object and publishs the change
  merge(data: Object, success?: Function, error?: Function): Promise {
    if (_.isObject(data)) {
      _.merge(this.data, data)
    }

    return this.update(this.data, success, error)
  }

  // alias for subscribe
  on(path: String, on: Function): Subscription {
    return this.subscribe(path, on)
  }

  // alias for update
  use(data, success?: Function, error?: Function): Promise {
    return this.update(data, success, error)
  }

  // alias for upsert
  up(data: Object, success?: Function, error?: Function): Promise {
    return upsert(data, success, error)
  }

  // determines if a subscription path matches data
  matches(data, scrip: Subscription): Set {
    const matchSet = new Set()

    if (this.config.data.matching.queries) { // TODO - determine if jsonpath query
      const jpMatches = jsonPath.query(data, scrip.path)

      if (jpMatches && !!jpMatches.length) {
        matchSet.add(...jpMatches)
      }
    }

    if (data === scrip.pattern && !matchSet.contains(data)) {
      matchSet.add(data)
    }

    return matchSet
  }

  // recursively traverses service tree via provided `next` functgion
  traverse(data, traversal: String, direction: String, success: Function, error: Function, next: Function): Promise {
    if (!traversals.find(t => t === traversal)) {
      throw `Failed to traverse, invalid traversal type: ${traversal}`
    }

    if (direction === 'down' && this.children.length) {
      if (traversal === 'breadth') {
        return Promise.all(
          this.children.map(next)
        )
      }

      if (traversal === 'depth') {
        return this.children.map(next)
      }
    }

    // TODO/WIP
    // if (direction === 'up' && this.parent) {
    //   if (traversal === 'breadth') {
    //     return Promise.all( // delegate to all services sharing parent's depth
    //       this.parent.siblings(this, true).map(next)
    //     )
    //   }

    //   if (traversal === 'depth') {
    //     return this.parent.siblings(this, true).map(next)
    //   }
    // }

    // TODO - async_local traversal

    // end node
    return new Promise((resolve, reject) => {
      try {
        resolve(success(result))
      } catch (err) {
        reject(err)
      }
    })
  }

  //  TODO - validate for cyclic dependencies
  // establishes service as a parent to the provided child service
  relateTo(child: Service): Service {
    this.children.push(child)

    return this
  }

  // establishes service as a parent to each provided child service
  relateToAll(children: Array): Array {
    return children.map(c => {
      c.parent = this

      return c
    })
  }

  // determines a service's depth in the service tree
  depth(node = this): Int {
    let nodeDepth = 0

    while (node.parent) {
      node = node.parent
      nodeDepth += 1
    }

    return nodeDepth
  }

  // determines all siblings of the service
  siblings(node = this, global: Boolean = false) {
    const roots = Service.findRoots()
    const depth = node.depth()

    return Service.findAtDepth(depth, roots).filter(svc => svc !== node)
  }

  // determines if the service is a root node in the service tree
  isRoot(): Boolean {
    return !this.parent
  }

  // determines if the service is a leaf node in the service tree
  isLeaf(): Boolean {
    return !this.children
  }

  // determines all root node Services in the tree
  static findRoots(services: Array = _services): Array {
    return _(services).values().filter(svc =>
      (svc instanceof Service) && svc.isRoot()
    ).value()
  }

  // determines all leaf node Services in the tree
  static findLeafs(services: Array = _services): Array {
    return _(services).values().filter(svc =>
      (svc instanceof Service) && svc.isLeaf()
    ).value()
  }

  static findAtDepth(targetDepth: Int, headNodes: Array): Array {
    const found  = []
    let curDepth = 0

    _.forEach(headNodes, node => {
      _.forEach(node.children, child => {
        curDepth = child.depth()

        if (curDepth < targetDepth) {
          found.push(...this.findAtDepth(targetDepth, [child]))
        } else if (curDepth === targetDepth) {
          found.push(child)
        }
      })
    })

    return found
  }

  // determines if a cyclic relationship exists anywhere in the service tree
  static cycleExists(services = _services): Boolean {
    const roots = Service.findRoots(services)
    const found = !_.isEmpty(roots) ? roots.map(r => r.name) : []

    let curNode = null
    let cyclic  = false

    _.forEach(roots, root => {
      curNode = root

      while (!cyclic && !_.isEmpty(curNode.children)) {
        curNode.children.forEach(child => {
          if (!_.contains(found, child.name)) {
            found.push(child.name)

            curNode = child
          } else {
            cyclic = true
          }
        })
      }
    })

    return cyclic
  }

  // determines if a service name is already registered in the tree
  static isRegistered(name: String): Boolean {
    return _.contains(Array.from(_services).map(s => s.name), name)
  }
}

export var service  = ({name, model, parent, children, config}) => new Service(name, model, parent, children, config)
export var services = _services
export var clear    = () => { _services = new Set() }

// TODO - may as well make this extend Promise
export class Subscription {

  constructor(service: Service, path: String, on: Function) {
    this.service = service
    this.path    = path
    this.on      = on
  }

  end() {
    service.unsubscribe(this)

    Object.freeze(this)
  }

}

// subscribe to all services and react to any data changes within them matching the provided hash
// export function subscribe(hash: String, callback: Function)

// publish a data change across all services (searches for roots, leafs, and orphans)
// traversal: async_global
// export function publish(path: String, data)

// allows a gooey service tree to be defined througha a convenient meta-descriptive POJO
// export function meta(meta: Object)<commit_msg>wip up traversals<commit_after>'use strict';

//  ▄▄ •             ▄▄▄ . ▄· ▄▌
// ▐█ ▀ ▪▪     ▪     ▀▄.▀·▐█▪██▌
// ▄█ ▀█▄ ▄█▀▄  ▄█▀▄ ▐▀▀▪▄▐█▌▐█▪
// ▐█▄▪▐█▐█▌.▐▌▐█▌.▐▌▐█▄▄▌ ▐█▀·.
// ·▀▀▀▀  ▀█▄▀▪ ▀█▄▀▪ ▀▀▀   ▀ •

import jsonPath from 'jsonpath'
import _ from 'lodash'

// flat object of all registered services by name
var _services = {}

// default service configuration
const _config = {
  strict: true,

  data: {
    lazy: false,

    matching: {
      queries : true
    },

    publish: {
      ignore: {
        falsy: true
      }
    }
  },

  traversals: {

  }
}

// supported flavors of service tree traversals
const traversals = ['depth', 'breadth', 'async_global', 'async_local']

// a canonical, heiarchical source of data that can delegate updates bi-directionally
export class Service {

  constructor(name: String, model?: Function, parent?: Service, children?: Array = [], config?: Object = _config) {
    if (_.isUndefined(name) || Service.isRegistered(name)) {
      throw `Services must have unique names: ${name}`
    }

    this.name          = name
    this.model         = model
    this.parent        = parent ? parent.relateTo(this) : null
    this.children      = this.relateToAll(children)
    this.state         = {}
    this.subscriptions = []
    this.config        = config

    _services[name] = this

    if (this.model) {
      this.model(this.state)
    }
  }

  // traverses service tree via a conflict-free path and matches subscribers against the published data
  publish(data, success: Function = _.noop, error: Function = _.noop, traversal: String = 'breadth', direction: String = 'down'): Promise {
    // ensure data is pure
    data = _.clone(data, true)

    // subscribers who match the current published data
    const scrips = this.subscriptions.filter(scrip => !!this.matches(data, scrip).size)

    // current service node. proxy data if this service's data update matches any subscriptions
    const result = scrips.length ? scrips.map(scrip => scrip.on(data)) : data

    // traverse service node tree and publish on each "next" node
    return this.traverse(
      result, traversal, direction, success, error,
      child => {
        child.publish(result, success, error, traversal, direction)
      }
    )
  }

  // creates and registers a publish subscription against a jsonpath pattern
  subscribe(path: String = '$', on: Function): Subscription {
    const scrip = new Subscription(this.name, path, on)

    this.subscriptions.push(scrip)

    return scrip
  }

  // ends a publish subscription
  unsubscribe(scrip: Subscription) {
    this.subscriptions.splice(this.subscriptions.indexOf(scrip), 1)
  }

  // updates the Service's canonical data source with new data and publishs the change
  update(data, success?: Function, error?: Function): Promise {
    this.data = data

    return this.publish(data, success, error)
  }

  // merges and updates the Service's canonical date source with a new data object and publishs the change
  merge(data: Object, success?: Function, error?: Function): Promise {
    if (_.isObject(data)) {
      _.merge(this.data, data)
    }

    return this.update(this.data, success, error)
  }

  // alias for subscribe
  on(path: String, on: Function): Subscription {
    return this.subscribe(path, on)
  }

  // alias for update
  use(data, success?: Function, error?: Function): Promise {
    return this.update(data, success, error)
  }

  // alias for upsert
  up(data: Object, success?: Function, error?: Function): Promise {
    return upsert(data, success, error)
  }

  // determines if a subscription path matches data
  matches(data, scrip: Subscription): Set {
    const matchSet = new Set()

    if (this.config.data.matching.queries) { // TODO - determine if jsonpath query
      const jpMatches = jsonPath.query(data, scrip.path)

      if (jpMatches && !!jpMatches.length) {
        matchSet.add(...jpMatches)
      }
    }

    if (data === scrip.pattern && !matchSet.contains(data)) {
      matchSet.add(data)
    }

    return matchSet
  }

  // recursively traverses service tree via provided `next` functgion
  traverse(data, traversal: String, direction: String, success: Function, error: Function, next: Function): Promise {
    if (!traversals.find(t => t === traversal)) {
      throw `Failed to traverse, invalid traversal type: ${traversal}`
    }

    if (direction === 'down' && this.children.length) {
      if (traversal === 'breadth') {
        return Promise.all(this.children.map(next))
      }

      if (traversal === 'depth') {
        return this.children.map(next)
      }
    }

    if (direction === 'up' && this.parent) { // WIP!
      if (traversal === 'breadth') {
        return Promise.all(
          this.parent.siblings(null, true).map(next)
        )
      }

      if (traversal === 'depth') {
        return this.parent.siblings(null, true).map(next)
      }
    }

    // TODO - async_local traversal

    // end node
    return new Promise((resolve, reject) => {
      try {
        resolve(success(result))
      } catch (err) {
        reject(err)
      }
    })
  }

  //  TODO - validate for cyclic dependencies
  // establishes service as a parent to the provided child service
  relateTo(child: Service): Service {
    this.children.push(child)

    return this
  }

  // establishes service as a parent to each provided child service
  relateToAll(children: Array): Array {
    return children.map(c => {
      c.parent = this

      return c
    })
  }

  // determines a service's depth in the service tree
  depth(node = this): Int {
    let nodeDepth = 0

    while (node.parent) {
      node = node.parent
      nodeDepth += 1
    }

    return nodeDepth
  }

  // determines all siblings of the service
  siblings(node = this, global: Boolean = false) {
    const roots = Service.findRoots()
    const depth = node.depth()

    return Service.findAtDepth(depth, roots).filter(svc => svc !== node)
  }

  // determines if the service is a root node in the service tree
  isRoot(): Boolean {
    return !this.parent
  }

  // determines if the service is a leaf node in the service tree
  isLeaf(): Boolean {
    return !this.children
  }

  // determines all root node Services in the tree
  static findRoots(services: Array = _services): Array {
    return _(services).values().filter(svc =>
      (svc instanceof Service) && svc.isRoot()
    ).value()
  }

  // determines all leaf node Services in the tree
  static findLeafs(services: Array = _services): Array {
    return _(services).values().filter(svc =>
      (svc instanceof Service) && svc.isLeaf()
    ).value()
  }

  static findAtDepth(targetDepth: Int, nodes: Array): Array {
    const found  = []
    let curDepth = 0

    _.forEach(nodes, node => {
      _.forEach(node.children, child => {
        curDepth = child.depth()

        if (curDepth < targetDepth) {
          found.push(...this.findAtDepth(targetDepth, [child]))
        } else if (curDepth === targetDepth) {
          found.push(child)
        }
      })
    })

    return found
  }

  // determines if a cyclic relationship exists anywhere in the service tree
  static cycleExists(services = _services): Boolean {
    const roots = Service.findRoots(services)
    const found = !_.isEmpty(roots) ? roots.map(r => r.name) : []

    let curNode = null
    let cyclic  = false

    _.forEach(roots, root => {
      curNode = root

      while (!cyclic && !_.isEmpty(curNode.children)) {
        curNode.children.forEach(child => {
          if (!_.contains(found, child.name)) {
            found.push(child.name)

            curNode = child
          } else {
            cyclic = true
          }
        })
      }
    })

    return cyclic
  }

  // determines if a service name is already registered in the tree
  static isRegistered(name: String): Boolean {
    return _.contains(Array.from(_services).map(s => s.name), name)
  }
}

export var service  = ({name, model, parent, children, config}) => new Service(name, model, parent, children, config)
export var services = _services
export var clear    = () => { _services = new Set() }

// TODO - may as well make this extend Promise
export class Subscription {

  constructor(service: Service, path: String, on: Function) {
    this.service = service
    this.path    = path
    this.on      = on
  }

  end() {
    service.unsubscribe(this)

    Object.freeze(this)
  }

}

// subscribe to all services and react to any data changes within them matching the provided hash
// export function subscribe(hash: String, callback: Function)

// publish a data change across all services (searches for roots, leafs, and orphans)
// traversal: async_global
// export function publish(path: String, data)

// allows a gooey service tree to be defined througha a convenient meta-descriptive POJO
// export function meta(meta: Object)<|endoftext|>


<commit_before>import assert from 'assert';

import Rx from 'rxjs/Rx';
import Discord from 'discord.js';
import Keyv from 'keyv';
import randomItem from 'random-item';

import { pokemon as pokemonModel } from './pokemon';
import { minLevenshtein } from './util';

const client = new Discord.Client();

const isLanguage = langName => entry => entry.language.name === langName;

const getNameOf = pkmn => pkmn.species.names.find(isLanguage('en')).name;

function fixPokemonName(name) {
    if( ['ho-oh', 'mr-mime'].includes(name.toLowerCase()) ) {
        return name;
    }

    return name.replace(/\-.+/g, '');
}

function quizObservable(question, correctAnswerObs, hintsObs, timeoutObs) {
    const sharedCorrectAnswerObs = correctAnswerObs.share();
    const sharedTimeoutObs = timeoutObs.share();

    const endObs = sharedCorrectAnswerObs.merge(sharedTimeoutObs).delay(10);

    return Rx.Observable.of({ type: 'question', question })
        .concat(Rx.Observable.merge(
            sharedCorrectAnswerObs.map(answer => ({ type: 'correctAnswer', answer })),
            hintsObs.map(hint => ({ type: 'hint', hint })),
            sharedTimeoutObs.map(() => ({ type: 'timeout' }))
        ))
        .takeUntil(endObs);
}

function quizPokemonObservable(cache, guessesObs) {
    return Rx.Observable.defer(() => {
        const randomPokemonObs =
            Rx.Observable.defer(() =>
                pokemonModel.getRandom(cache)
            )
            .share();

        const randomFlavorTextObs =
            randomPokemonObs
            .map(pkmn =>
                randomItem(
                    pkmn.species.flavor_text_entries.filter(isLanguage('en'))
                )
                .flavor_text
            );

        const nameObs =
            randomPokemonObs
            .map(pkmn => pkmn.name)
            .map(fixPokemonName);

        return Rx.Observable.combineLatest(
            randomPokemonObs, randomFlavorTextObs, nameObs
        )
        .mergeMap(([pkmn, flavorText, name]) => {
            const nameRegex = new RegExp(name, 'ig');

            return quizObservable(
                // question
                `**Who's that Pokémon!?** \`\`\`${flavorText.replace(nameRegex, '[REDACTED]')}\`\`\``,

                // observable of right answers
                guessesObs.filter(msg => minLevenshtein(msg.cleanContent, name) <= 2),

                // observable of hints
                Rx.Observable.empty(),

                // timeout observable
                Rx.Observable.timer(25 * 1000)
            )
            .map(ev => {
                ev.pokemon = pkmn;
                return ev;
            });
        })
        .startWith({ type: 'fetchingPokemon' });
    });
}

Promise.resolve()
.then(() => {
    assert.ok(process.env.DISCORD_TOKEN != null, 'DISCORD_TOKEN is not specified, unable to log in.');

    return client.login(process.env.DISCORD_TOKEN);
})
.then(async () => {

    if(process.env.DISCORD_CLIENT_ID != null) {
        const clientId = process.env.DISCORD_CLIENT_ID;
        const inviteUrl = `https://discordapp.com/oauth2/authorize?&client_id=${clientId}&scope=bot&permissions=0`;

        console.log(`Invite URL: ${inviteUrl}`);
    }
    else {
        console.log('*** DISCORD_CLIENT_ID not specified; no invite URL will be generated.');
        console.log('*** The bot will not respond to the `invite` either.');
    }

    const cache = new Keyv({ ttl: 6*60*60*1000 });

    const msgObs = Rx.Observable.fromEvent(client, 'message');

    const msgToMeObs = msgObs.filter(msg => msg.isMentioned(client.user));

    const startGameObs = msgToMeObs
        .filter(msg => /start/.test(msg.cleanContent))
        .map(msg => msg.channel);

    const endGameObs = msgToMeObs
        .filter(msg => /[end|stop]/.test(msg.cleanContent))
        .map(msg => msg.channel);

    const actions = {
        async question(channel, ev) {
            await channel.send(ev.question);
        },

        async correctAnswer(channel, ev) {
            await channel.send(`${ev.answer.author} got it right!! It was **${getNameOf(ev.pokemon)}**!!`);
        },

        async hint(channel, ev) {
            await channel.send(`Here's a hint: ${ev.hint}`);
        },

        async timeout(channel, ev) {
            await channel.send(`No one answered it right!! It was **${getNameOf(ev.pokemon)}**!`);
        },

        async fetchingPokemon(channel) {
            await channel.send(`Getting the next Pokemon...`);
        },

        async end(channel) {
            await channel.send(`That's all, folks!`);
        }
    };

    await Promise.all([
        startGameObs
        .mergeMap(channel =>
            quizPokemonObservable(cache, msgObs)
            .repeat(50)
            .takeUntil(
                endGameObs.filter(endChan => channel == endChan)
            )
            .concat(Rx.Observable.of({ type: 'end' }))
            .mergeMap(ev => actions[ev.type](channel, ev) )
        )
        .toPromise(),

        msgToMeObs
        .filter(() =>
            process.env.DISCORD_CLIENT_ID != null && process.env.DISCORD_OWNER_ID != null
        )
        .filter(msg => msg.author.id == process.env.DISCORD_OWNER_ID)
        .filter(msg => /invite/.test(msg.cleanContent))
        .mergeMap(async msg => {
            const clientId = process.env.DISCORD_CLIENT_ID;
            const inviteUrl = `https://discordapp.com/oauth2/authorize?&client_id=${clientId}&scope=bot&permissions=0`;

            await msg.author.send(`Here's an invite URL: ${inviteUrl}`);
        })
        .toPromise()
    ]);
})
.catch(e => console.error(e.stack));
<commit_msg>Make bot ignore own messages<commit_after>import assert from 'assert';

import Rx from 'rxjs/Rx';
import Discord from 'discord.js';
import Keyv from 'keyv';
import randomItem from 'random-item';

import { pokemon as pokemonModel } from './pokemon';
import { minLevenshtein } from './util';

const client = new Discord.Client();

const isLanguage = langName => entry => entry.language.name === langName;

const getNameOf = pkmn => pkmn.species.names.find(isLanguage('en')).name;

function fixPokemonName(name) {
    if( ['ho-oh', 'mr-mime'].includes(name.toLowerCase()) ) {
        return name;
    }

    return name.replace(/\-.+/g, '');
}

function quizObservable(question, correctAnswerObs, hintsObs, timeoutObs) {
    const sharedCorrectAnswerObs = correctAnswerObs.share();
    const sharedTimeoutObs = timeoutObs.share();

    const endObs = sharedCorrectAnswerObs.merge(sharedTimeoutObs).delay(10);

    return Rx.Observable.of({ type: 'question', question })
        .concat(Rx.Observable.merge(
            sharedCorrectAnswerObs.map(answer => ({ type: 'correctAnswer', answer })),
            hintsObs.map(hint => ({ type: 'hint', hint })),
            sharedTimeoutObs.map(() => ({ type: 'timeout' }))
        ))
        .takeUntil(endObs);
}

function quizPokemonObservable(cache, guessesObs) {
    return Rx.Observable.defer(() => {
        const randomPokemonObs =
            Rx.Observable.defer(() =>
                pokemonModel.getRandom(cache)
            )
            .share();

        const randomFlavorTextObs =
            randomPokemonObs
            .map(pkmn =>
                randomItem(
                    pkmn.species.flavor_text_entries.filter(isLanguage('en'))
                )
                .flavor_text
            );

        const nameObs =
            randomPokemonObs
            .map(pkmn => pkmn.name)
            .map(fixPokemonName);

        return Rx.Observable.combineLatest(
            randomPokemonObs, randomFlavorTextObs, nameObs
        )
        .mergeMap(([pkmn, flavorText, name]) => {
            const nameRegex = new RegExp(name, 'ig');

            return quizObservable(
                // question
                `**Who's that Pokémon!?** \`\`\`${flavorText.replace(nameRegex, '[REDACTED]')}\`\`\``,

                // observable of right answers
                guessesObs.filter(msg => minLevenshtein(msg.cleanContent, name) <= 2),

                // observable of hints
                Rx.Observable.empty(),

                // timeout observable
                Rx.Observable.timer(25 * 1000)
            )
            .map(ev => {
                ev.pokemon = pkmn;
                return ev;
            });
        })
        .startWith({ type: 'fetchingPokemon' });
    });
}

Promise.resolve()
.then(() => {
    assert.ok(process.env.DISCORD_TOKEN != null, 'DISCORD_TOKEN is not specified, unable to log in.');

    return client.login(process.env.DISCORD_TOKEN);
})
.then(async () => {

    if(process.env.DISCORD_CLIENT_ID != null) {
        const clientId = process.env.DISCORD_CLIENT_ID;
        const inviteUrl = `https://discordapp.com/oauth2/authorize?&client_id=${clientId}&scope=bot&permissions=0`;

        console.log(`Invite URL: ${inviteUrl}`);
    }
    else {
        console.log('*** DISCORD_CLIENT_ID not specified; no invite URL will be generated.');
        console.log('*** The bot will not respond to the `invite` either.');
    }

    const cache = new Keyv({ ttl: 6*60*60*1000 });

    const msgObs = Rx.Observable.fromEvent(client, 'message')
        .filter(msg => msg.author != client.user);

    const msgToMeObs = msgObs.filter(msg => msg.isMentioned(client.user));

    const startGameObs = msgToMeObs
        .filter(msg => /start/.test(msg.cleanContent))
        .map(msg => msg.channel);

    const endGameObs = msgToMeObs
        .filter(msg => /[end|stop]/.test(msg.cleanContent))
        .map(msg => msg.channel);

    const actions = {
        async question(channel, ev) {
            await channel.send(ev.question);
        },

        async correctAnswer(channel, ev) {
            await channel.send(`${ev.answer.author} got it right!! It was **${getNameOf(ev.pokemon)}**!!`);
        },

        async hint(channel, ev) {
            await channel.send(`Here's a hint: ${ev.hint}`);
        },

        async timeout(channel, ev) {
            await channel.send(`No one answered it right!! It was **${getNameOf(ev.pokemon)}**!`);
        },

        async fetchingPokemon(channel) {
            await channel.send(`Getting the next Pokemon...`);
        },

        async end(channel) {
            await channel.send(`That's all, folks!`);
        }
    };

    await Promise.all([
        startGameObs
        .mergeMap(channel =>
            quizPokemonObservable(cache, msgObs)
            .repeat(50)
            .takeUntil(
                endGameObs.filter(endChan => channel == endChan)
            )
            .concat(Rx.Observable.of({ type: 'end' }))
            .mergeMap(ev => actions[ev.type](channel, ev) )
        )
        .toPromise(),

        msgToMeObs
        .filter(() =>
            process.env.DISCORD_CLIENT_ID != null && process.env.DISCORD_OWNER_ID != null
        )
        .filter(msg => msg.author.id == process.env.DISCORD_OWNER_ID)
        .filter(msg => /invite/.test(msg.cleanContent))
        .mergeMap(async msg => {
            const clientId = process.env.DISCORD_CLIENT_ID;
            const inviteUrl = `https://discordapp.com/oauth2/authorize?&client_id=${clientId}&scope=bot&permissions=0`;

            await msg.author.send(`Here's an invite URL: ${inviteUrl}`);
        })
        .toPromise()
    ]);
})
.catch(e => console.error(e.stack));
<|endoftext|>


<commit_before>// LICENSE : MIT
"use strict";
export default {
    rules: {
        "1.2.1.句点(。)と読点(、)": require("./1.2.1"),
        "1.2.2.ピリオド(.)とカンマ(,)": require("./1.2.2"),
        "2.2.2.算用数字と漢数字の使い分け": require("./2.2.2"),
        "2.2.3.一部の助数詞の表記": require("./2.2.3"),
        "3.1.1.全角文字と半角文字の間": require("./3.1.1"),
        "3.1.2.全角文字どうし": require("./3.1.2"),
        "3.3.かっこ類と隣接する文字の間のスペースの有無": require("./3.3"),
        "4.1.1.句点(。)": require("./4.1.1"),
        "4.1.3.ピリオド(.)、カンマ(,)": require("./4.1.3"),
        "4.2.1.感嘆符(！)": require("./4.2.1"),
        "4.2.2.疑問符(？)": require("./4.2.2"),
        "4.2.5.波線(〜)": require("./4.2.5"),
        "4.2.6.ハイフン(-)": require("./4.2.6"),
        "4.2.7.コロン(：)": require("./4.2.7"),
        "4.2.8.セミコロン(;)": require("./4.2.8"),
        "4.2.9.ダッシュ(-)": require("./4.2.9"),
        "4.3.1.丸かっこ（）": require("./4.3.1"),
        "4.3.2.大かっこ［］": require("./4.3.2"),
        "4.3.3.かぎかっこ「」": require("./4.3.3"),
        "4.3.4.二重かぎかっこ『』": require("./4.3.4"),
        "4.3.5.二重引用符": require("./4.3.5")
    },
    rulesConfig: {
        "1.2.1.句点(。)と読点(、)": true,
        "1.2.2.ピリオド(.)とカンマ(,)": true,
        "2.2.2.算用数字と漢数字の使い分け": true,
        "2.2.3.一部の助数詞の表記": true,
        "3.1.1.全角文字と半角文字の間": true,
        "3.1.2.全角文字どうし": true,
        "3.3.かっこ類と隣接する文字の間のスペースの有無": true,
        "4.1.1.句点(。)": true,
        "4.1.3.ピリオド(.)、カンマ(,)": true,
        "4.2.1.感嘆符(！)": true,
        "4.2.2.疑問符(？)": true,
        "4.2.5.波線(〜)": true,
        "4.2.6.ハイフン(-)": true,
        "4.2.7.コロン(：)": true,
        "4.2.8.セミコロン(;)": true,
        "4.2.9.ダッシュ(-)": true,
        "4.3.1.丸かっこ（）": true,
        "4.3.2.大かっこ［］": true,
        "4.3.3.かぎかっこ「」": true,
        "4.3.4.二重かぎかっこ『』": true,
        "4.3.5.二重引用符": true
    }
};
<commit_msg>chore(index): add comment<commit_after>// LICENSE : MIT
"use strict";
export default {
    rules: {
        "1.2.1.句点(。)と読点(、)": require("./1.2.1"),
        "1.2.2.ピリオド(.)とカンマ(,)": require("./1.2.2"),
        "2.2.2.算用数字と漢数字の使い分け": require("./2.2.2"),
        "2.2.3.一部の助数詞の表記": require("./2.2.3"),
        "3.1.1.全角文字と半角文字の間": require("./3.1.1"),
        "3.1.2.全角文字どうし": require("./3.1.2"),
        "3.3.かっこ類と隣接する文字の間のスペースの有無": require("./3.3"),
        "4.1.1.句点(。)": require("./4.1.1"),
        "4.1.3.ピリオド(.)、カンマ(,)": require("./4.1.3"),
        "4.2.1.感嘆符(！)": require("./4.2.1"),
        "4.2.2.疑問符(？)": require("./4.2.2"),
        "4.2.5.波線(〜)": require("./4.2.5"),
        "4.2.6.ハイフン(-)": require("./4.2.6"),
        "4.2.7.コロン(：)": require("./4.2.7"),
        "4.2.8.セミコロン(;)": require("./4.2.8"),
        "4.2.9.ダッシュ(-)": require("./4.2.9"),
        "4.3.1.丸かっこ（）": require("./4.3.1"),
        "4.3.2.大かっこ［］": require("./4.3.2"),
        "4.3.3.かぎかっこ「」": require("./4.3.3"),
        "4.3.4.二重かぎかっこ『』": require("./4.3.4"),
        "4.3.5.二重引用符": require("./4.3.5")
    },
    rulesConfig: {
        "1.2.1.句点(。)と読点(、)": true,
        "1.2.2.ピリオド(.)とカンマ(,)": true,
        "2.2.2.算用数字と漢数字の使い分け": true,
        "2.2.3.一部の助数詞の表記": true,
        "3.1.1.全角文字と半角文字の間": true,
        "3.1.2.全角文字どうし": true,
        "3.3.かっこ類と隣接する文字の間のスペースの有無": true,
        "4.1.1.句点(。)": true,
        "4.1.3.ピリオド(.)、カンマ(,)": true,
        "4.2.1.感嘆符(！)": true,
        "4.2.2.疑問符(？)": true,
        "4.2.5.波線(〜)": true,
        "4.2.6.ハイフン(-)": true,// default off?
        "4.2.7.コロン(：)": true,// default off?
        "4.2.8.セミコロン(;)": true, // default off?
        "4.2.9.ダッシュ(-)": true,
        "4.3.1.丸かっこ（）": true,
        "4.3.2.大かっこ［］": true,
        "4.3.3.かぎかっこ「」": true,
        "4.3.4.二重かぎかっこ『』": true,
        "4.3.5.二重引用符": true
    }
};
<|endoftext|>


<commit_before>
/**
 * Hand-written Promisified wrapper around generated ESI code.
 * This module returns a factory function that takes an optional datasource
 * string and base URL. If any parameter is not provided, the defaults are used.
 *
 * The factory function returns an object with properties that expose
 * instantiations of each sub-module for the ESI groups.
 *
 * @param {String} opts.accessToken The default access token to provide on all
 *   requests; methods that require authentication provide an optional access
 *   token argument so that it can be overridden per request. Defaults to `null`
 * @param {String} opts.baseURL The base URL of the ESI endpoint, which defaults
 *   to `'https://esi.tech.ccp.is/latest'`
 * @param {String} opts.datasource The datasource for the ESI endpoint, defaults
 *   to `'tranquility'`
 * @param {String} opts.language The default language locale code, defaults to
 *   `en`, but can be set to one of {@link eve_swagger_interface:languages languages}.
 *   Requests that support localization provide a language parameter so it can
 *   be overridden per request.
 *
 * @module eve_swagger_interface
 */
module.exports = function(opts) {
    var internal = require('./internal')(opts);

    var exports = {};

    /**
     * The enumeration of language codes that functions that provide
     * localization support. Supported language codes are:
     *
     * 1. `'de'`
     * 2. `'en'`
     * 3. `'fr'`
     * 4. `'ja'`
     * 5. `'ru'`
     * 6. `'zh'`
     *
     * @constant {Array.<String>}
     */
    exports.languages = [ 'de', 'en', 'fr', 'ja', 'ru', 'zh' ];

    /**
     * This is an instance of `alliance` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:alliance}
     */
    exports.alliance = require('./alliance')(internal);

    /**
     * This is an instance of `corporation` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:corporation}
     */
    exports.corporation = require('./corporation')(internal);

    /**
     * This is an instance of `fleets` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:fleets}
     */
    exports.fleets = require('./fleets')(internal);

    /**
     * This is an instance of `incursions` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:incursions}
     */
    exports.incursions = require('./incursions')(internal);

    /**
     * This is an instance of `industry` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:industry}
     */
    exports.industry = require('./industry')(internal);

    /**
     * This is an instance of `insurance` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:insurance}
     */
    exports.insurance = require('./insurance')(internal);

    /**
     * This is an instance of `killmails` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:killmails}
     */
    exports.killmails = require('./killmails')(internal);

    /**
     * This is an instance of `market` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:market}
     */
    exports.market = require('./market')(internal);

    /**
     * This is an instance of `sovereignty` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:sovereignty}
     */
    exports.sovereignty = require('./sovereignty')(internal);

    /**
     * This is an instance of `universe` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:universe}
     */
    exports.universe = require('./universe')(internal);

    /**
     * This is an instance of `wars` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:wars}
     */
    exports.wars = require('./wars')(internal);

    return exports;
};
<commit_msg>Properly export search module<commit_after>
/**
 * Hand-written Promisified wrapper around generated ESI code.
 * This module returns a factory function that takes an optional datasource
 * string and base URL. If any parameter is not provided, the defaults are used.
 *
 * The factory function returns an object with properties that expose
 * instantiations of each sub-module for the ESI groups.
 *
 * @param {String} opts.accessToken The default access token to provide on all
 *   requests; methods that require authentication provide an optional access
 *   token argument so that it can be overridden per request. Defaults to `null`
 * @param {String} opts.baseURL The base URL of the ESI endpoint, which defaults
 *   to `'https://esi.tech.ccp.is/latest'`
 * @param {String} opts.datasource The datasource for the ESI endpoint, defaults
 *   to `'tranquility'`
 * @param {String} opts.language The default language locale code, defaults to
 *   `en`, but can be set to one of {@link eve_swagger_interface:languages languages}.
 *   Requests that support localization provide a language parameter so it can
 *   be overridden per request.
 *
 * @module eve_swagger_interface
 */
module.exports = function(opts) {
    var internal = require('./internal')(opts);

    var exports = {};

    /**
     * The enumeration of language codes that functions that provide
     * localization support. Supported language codes are:
     *
     * 1. `'de'`
     * 2. `'en'`
     * 3. `'fr'`
     * 4. `'ja'`
     * 5. `'ru'`
     * 6. `'zh'`
     *
     * @constant {Array.<String>}
     */
    exports.languages = [ 'de', 'en', 'fr', 'ja', 'ru', 'zh' ];

    /**
     * This is an instance of `alliance` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:alliance}
     */
    exports.alliance = require('./alliance')(internal);

    /**
     * This is an instance of `corporation` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:corporation}
     */
    exports.corporation = require('./corporation')(internal);

    /**
     * This is an instance of `fleets` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:fleets}
     */
    exports.fleets = require('./fleets')(internal);

    /**
     * This is an instance of `incursions` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:incursions}
     */
    exports.incursions = require('./incursions')(internal);

    /**
     * This is an instance of `industry` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:industry}
     */
    exports.industry = require('./industry')(internal);

    /**
     * This is an instance of `insurance` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:insurance}
     */
    exports.insurance = require('./insurance')(internal);

    /**
     * This is an instance of `killmails` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:killmails}
     */
    exports.killmails = require('./killmails')(internal);

    /**
     * This is an instance of `market` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:market}
     */
    exports.market = require('./market')(internal);

    /**
     * This is an instance of `search` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:search}
     */
    exports.search = require('./search')(internal);

    /**
     * This is an instance of `sovereignty` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:sovereignty}
     */
    exports.sovereignty = require('./sovereignty')(internal);

    /**
     * This is an instance of `universe` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:universe}
     */
    exports.universe = require('./universe')(internal);

    /**
     * This is an instance of `wars` module configured to use the
     * options provided to the factory. This instance uses a cache shared by the
     * other exposed APIs members.
     *
     * @constant {module:wars}
     */
    exports.wars = require('./wars')(internal);

    return exports;
};
<|endoftext|>


<commit_before>'use strict';
const alexa = require('alexa-sdk');
const util = require('util');
const databaseHelper = require('./helpers/database');
const scrapper = require('./helpers/scrapper');
const skillName = 'Petrol Price';
const resultCountLimit = 2;

var messages = {
    'welcomeMessage': 'Welcome to ' + skillName + '. You can get the latest petrol price for major cities in India. ',
    'helpMessage' : skillName + ' skill gives you the latest petrol price for major cities in India. ',
    'promptForCity': 'For which city do you want to know the price? ',
    'rePromptForCity': 'Please say the city name. ',
    'priceMessage': 'It is %s rupees per litre in %s. ',
    'priceUpMessage': 'The price has gone up by %s. ',
    'priceDownMessage': 'The price has gone down by %s. ',
    'cityNotSupportedMessage': 'Sorry. Currently we do not have the petrol price updates for %s. ',
    'citySlotMissingMessage': 'Sorry. I did not get the city name. ',
    'exitMessage' : 'godspeed'
}

exports.handler = function (event, context, callback) {
    var alexaHandler = alexa.handler(event, context);
    alexaHandler.registerHandlers(handlers);
    alexaHandler.execute();
}

const handlers = {
    'LaunchRequest': function () {
        this.response.speak(messages.welcomeMessage + messages.promptForCity).listen(messages.rePromptForCity);
        this.emit(':responseReady');
    },

    'PriceIntent': function () {
        var emitter = this;
        var citySlot = this.event.request.intent.slots.city;
        console.log('input : ' + JSON.stringify(citySlot));
        if (!citySlot || !citySlot.value) {
            console.error('city slot missing');
            this.response.speak(messages.citySlotMissingMessage + messages.promptForCity).listen(messages.rePromptForCity);
            this.emit(':responseReady');
        }
        else {
            var city = transformInput(citySlot.value).toLowerCase();
            databaseHelper.getLatestPetrolPrices(city, resultCountLimit, true, function (data) {
                if (!data) {
                    scrapper.scrapPetrolPrice(city, function (scrappedData) {
                        if (!scrappedData) {
                            console.error('city not supported - empty scrapped data');
                            emitter.response.speak(util.format(messages.cityNotSupportedMessage, citySlot.value));
                            emitter.emit(':responseReady');
                        }
                        else {
                            databaseHelper.insertPetrolPrice(scrappedData, function () {
                                emitter.response.speak(util.format(messages.priceMessage, scrappedData.price, citySlot.value));
                                emitter.emit(':responseReady');
                            });
                        }
                    })

                }
                else {
                    if (data.length > 1) {
                        var response;
                        var dateDifference = dateDiffInDays(new Date(data[1].date), new Date(data[0].date));
                        var dateDifferenceMsg = dateDifference > 1 ? ' over the last ' + dateDifference + ' days' : ' since yesterday';
                        if (data[0].price > data[1].price) {
                            response = util.format(messages.priceMessage, data[0].price, citySlot.value) + util.format(messages.priceUpMessage, Number(data[0].price - data[1].price).toFixed(2) + dateDifferenceMsg);
                        }
                        else if (data[0].price < data[1].price) {
                            response = util.format(messages.priceMessage, data[0].price, citySlot.value) + util.format(messages.priceDownMessage, Number(data[1].price - data[0].price).toFixed(2) + dateDifferenceMsg);
                        }
                        else {
                            response = util.format(messages.priceMessage, data[0].price, citySlot.value);
                        }
                        emitter.response.speak(response);
                        emitter.emit(':responseReady');

                    }
                    else {
                        emitter.response.speak(util.format(messages.priceMessage, data[0].price, citySlot.value));
                        emitter.emit(':responseReady');
                    }
                }
            });
        }
    },

    'AMAZON.HelpIntent': function () {
        this.response.speak(messages.helpMessage + messages.promptForCity).listen(messages.rePromptForCity);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent': function () {
        this.response.speak(messages.exitMessage);
        this.emit(':responseReady');
    },
    'AMAZON.StopIntent': function () {
        this.response.speak(messages.exitMessage);
        this.emit(':responseReady');
    }
};

//transform synonymous city names to the name stored in db
//eg : new delhi -> delhi
function transformInput(city) {
    switch (city.toLowerCase()) {
        case 'new delhi': {
            return 'delhi';
        }
        case 'bangalore': {
            return 'bengaluru';
        }
        case 'bombay': {
            return 'mumbai';
        }
        case 'madras': {
            return 'chennai';
        }
        case 'calcutta': {
            return 'kolkata';
        }
        default: {
            return city;
        }
    }
}

function dateDiffInDays(a, b) {
    var utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
    var utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
    return Math.floor((utc2 - utc1) / (1000 * 60 * 60 * 24));
}
<commit_msg>Added function to transform state names to cities<commit_after>'use strict';
const alexa = require('alexa-sdk');
const util = require('util');
const databaseHelper = require('./helpers/database');
const scrapper = require('./helpers/scrapper');
const skillName = 'Petrol Price';
const resultCountLimit = 2;

var messages = {
    'welcomeMessage': 'Welcome to ' + skillName + '. You can get the latest petrol price for major cities in India. ',
    'helpMessage': skillName + ' skill gives you the latest petrol price for major cities in India. ',
    'promptForCity': 'For which city do you want to know the price? ',
    'rePromptForCity': 'Please say the city name. ',
    'priceMessage': 'It is %s rupees per litre in %s. ',
    'priceUpMessage': 'The price has gone up by %s. ',
    'priceDownMessage': 'The price has gone down by %s. ',
    'cityNotSupportedMessage': 'Sorry. Currently we do not have the petrol price updates for %s. ',
    'citySlotMissingMessage': 'Sorry. I did not get the city name. ',
    'exitMessage': 'godspeed'
}

exports.handler = function (event, context, callback) {
    var alexaHandler = alexa.handler(event, context);
    alexaHandler.registerHandlers(handlers);
    alexaHandler.execute();
}

const handlers = {
    'LaunchRequest': function () {
        this.response.speak(messages.welcomeMessage + messages.promptForCity).listen(messages.rePromptForCity);
        this.emit(':responseReady');
    },

    'PriceIntent': function () {
        var emitter = this;
        var citySlot = this.event.request.intent.slots.city;
        console.log('input : ' + JSON.stringify(citySlot));
        if (!citySlot || !citySlot.value) {
            console.error('city slot missing');
            this.response.speak(messages.citySlotMissingMessage + messages.promptForCity).listen(messages.rePromptForCity);
            this.emit(':responseReady');
        }
        else {
            var city = transformState(citySlot.value).transformCity(citySlot.value).toLowerCase();
            databaseHelper.getLatestPetrolPrices(city, resultCountLimit, true, function (data) {
                if (!data) {
                    scrapper.scrapPetrolPrice(city, function (scrappedData) {
                        if (!scrappedData) {
                            console.error('city not supported - empty scrapped data');
                            emitter.response.speak(util.format(messages.cityNotSupportedMessage, citySlot.value));
                            emitter.emit(':responseReady');
                        }
                        else {
                            databaseHelper.insertPetrolPrice(scrappedData, function () {
                                emitter.response.speak(util.format(messages.priceMessage, scrappedData.price, citySlot.value));
                                emitter.emit(':responseReady');
                            });
                        }
                    })

                }
                else {
                    if (data.length > 1) {
                        var response;
                        var dateDifference = dateDiffInDays(new Date(data[1].date), new Date(data[0].date));
                        var dateDifferenceMsg = dateDifference > 1 ? ' over the last ' + dateDifference + ' days' : ' since yesterday';
                        if (data[0].price > data[1].price) {
                            response = util.format(messages.priceMessage, data[0].price, citySlot.value) + util.format(messages.priceUpMessage, Number(data[0].price - data[1].price).toFixed(2) + dateDifferenceMsg);
                        }
                        else if (data[0].price < data[1].price) {
                            response = util.format(messages.priceMessage, data[0].price, citySlot.value) + util.format(messages.priceDownMessage, Number(data[1].price - data[0].price).toFixed(2) + dateDifferenceMsg);
                        }
                        else {
                            response = util.format(messages.priceMessage, data[0].price, citySlot.value);
                        }
                        emitter.response.speak(response);
                        emitter.emit(':responseReady');

                    }
                    else {
                        emitter.response.speak(util.format(messages.priceMessage, data[0].price, citySlot.value));
                        emitter.emit(':responseReady');
                    }
                }
            });
        }
    },

    'AMAZON.HelpIntent': function () {
        this.response.speak(messages.helpMessage + messages.promptForCity).listen(messages.rePromptForCity);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent': function () {
        this.response.speak(messages.exitMessage);
        this.emit(':responseReady');
    },
    'AMAZON.StopIntent': function () {
        this.response.speak(messages.exitMessage);
        this.emit(':responseReady');
    }
};

//transform synonymous city names to the name stored in db
//eg : new delhi -> delhi
function transformCity(city) {
    switch (city.toLowerCase()) {
        case 'new delhi': {
            return 'delhi';
        }
        case 'bangalore': {
            return 'bengaluru';
        }
        case 'bombay': {
            return 'mumbai';
        }
        case 'madras': {
            return 'chennai';
        }
        case 'calcutta': {
            return 'kolkata';
        }
        default: {
            return city;
        }
    }
}

//transform state names to the city name stored in db
//eg : kerala -> trivandrum
function transformState(state) {
    switch (state.toLowerCase()) {
        case 'andaman': { }
        case 'nicobar': { }
        case 'andaman and nicobar': { }
        case 'andaman and nicobar islands': {
            return 'port blair';
        }
        case 'andhra': { }
        case 'andhra pradesh': {
            return 'hyderabad';
        }
        case 'arunachal pradesh': {
            return 'itanagar';
        }
        case 'assam': {
            return 'guwahati';
        }
        case 'bihar': {
            return 'patna';
        }
        case 'chhattisgarh': {
            return 'raipur';
        }
        case 'dadra': { }
        case 'dadra and nagar haveli': {
            return 'silvassa';
        }
        case 'gujarat': {
            return 'ahmedabad';
        }
        case 'haryana': {
            return 'faridabad';
        }
        case 'himachal': { }
        case 'himachal pradesh': {
            return 'shimla';
        }
        case 'kashmir': { }
        case 'jammu and kashmir': {
            return 'srinagar';
        }
        case 'jharkhand': {
            return 'ranchi';
        }
        case 'karnataka': {
            return 'bengaluru';
        }
        case 'kerala': {
            return 'trivandrum';
        }
        case 'madhya pradesh': {
            return 'bhopal';
        }
        case 'maharashtra': {
            return 'mumbai';
        }
        case 'manipur': {
            return 'imphal';
        }
        case 'meghalaya': {
            return 'shillong';
        }
        case 'mizoram': {
            return 'aizwal';
        }
        case 'nagaland': {
            return 'kohima';
        }
        case 'orissa': {
            return 'bhubhaneswar';
        }
        case 'odisha': {
            return 'cuttack';
        }
        case 'puducherry': {
            return 'pondicherry';
        }
        case 'punjab': {
            return 'amritsar';
        }
        case 'rajasthan': {
            return 'jaipur';
        }
        case 'sikkim': {
            return 'gangtok';
        }
        case 'tamilnadu': { }
        case 'tamil nadu': {
            return 'chennai';
        }
        case 'telangana': {
            return 'warangal';
        }
        case 'tripura': {
            return 'agartala';
        }
        case 'uttar pradesh': {
            return 'lucknow';
        }
        case 'uttarakhand': {
            return 'nainital';
        }
        case 'west bengal': {
            return 'kolkata';
        }
        default: {
            return state;
        }
    }
}

function dateDiffInDays(a, b) {
    var utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
    var utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
    return Math.floor((utc2 - utc1) / (1000 * 60 * 60 * 24));
}
<|endoftext|>


<commit_before>var ItemPreviewCtrl = function($element, $state, $scope, itemTypeMap, mjs, notification, $window, $document) {

    var self = this;
    this.$document = $document;
    this.$state = $state;
    this.$window = $window;
    this.mjs = mjs;
    this.$scope = $scope;
    this.notification = notification;
    this.item_string = itemTypeMap.get_item_string($scope.previewData);
    this.item_type = itemTypeMap.get_type($scope.previewData.UnitType);
    this.branches = $scope.previewData.branches;
    this.url = $scope.previewData.url;
    this.collection_name = itemTypeMap.get_collection_name($scope.previewData);
    this.element = $element.find('div')[0];
    this.rmdialog_is_open = false;
    this.deleted = false;

};

ItemPreviewCtrl.prototype = {

    get_item_url: function() {
        if (this.url !== undefined) {
            return this.url;
        }
        if (this.collection_name === 'genTreeIndividuals') {
            return this.$state.href('ftree-item', {individual_id: this.$scope.previewData.II, tree_number: this.$scope.previewData.GTN});
        }
        else {
			var parts = this.item_string.split('.')
			return this.$state.href('item-view', {collection: parts[0], id: parts[1]});
        }
    },

    remove_from_story: function() {
        var self = this;
        this.mjs.remove_from_story(this.item_string).success(function() {
            self.deleted = true;
        });
    },

    update_branch: function($event, branch) {
        if (this.branches[branch]) {

            this.mjs.add_to_branch(this.item_string, branch);
        }
        else {
            $event.stopPropagation();
       }
    },

    stopPropagation: function($event) {
        $event.stopPropagation();
    },

};

angular.module('main').controller('ItemPreviewCtrl', ['$element', '$state', '$scope', 'itemTypeMap', 'mjs', 'notification', '$window', '$document', ItemPreviewCtrl]);
<commit_msg>fixing adding/removing to/from branch<commit_after>var ItemPreviewCtrl = function($element, $state, $scope, itemTypeMap, mjs, notification, $window, $document) {

    var self = this;
    this.$document = $document;
    this.$state = $state;
    this.$window = $window;
    this.mjs = mjs;
    this.$scope = $scope;
    this.notification = notification;
    this.item_string = itemTypeMap.get_item_string($scope.previewData);
    this.item_type = itemTypeMap.get_type($scope.previewData.UnitType);
    this.branches = $scope.previewData.branches;
    this.url = $scope.previewData.url;
    this.collection_name = itemTypeMap.get_collection_name($scope.previewData);
    this.element = $element.find('div')[0];
    this.rmdialog_is_open = false;
    this.deleted = false;

};

ItemPreviewCtrl.prototype = {

    get_item_url: function() {
        if (this.url !== undefined) {
            return this.url;
        }
        if (this.collection_name === 'genTreeIndividuals') {
            return this.$state.href('ftree-item', {individual_id: this.$scope.previewData.II, tree_number: this.$scope.previewData.GTN});
        }
        else {
			var parts = this.item_string.split('.')
			return this.$state.href('item-view', {collection: parts[0], id: parts[1]});
        }
    },

    remove_from_story: function() {
        var self = this;
        this.mjs.remove_from_story(this.item_string).success(function() {
            self.deleted = true;
        });
    },

    update_branch: function($event, branch) {
        if (this.branches[branch]) {

            this.mjs.add_to_branch(this.item_string, branch);
        }
        else {
            this.mjs.remove_from_branch(this.item_string, branch);
       }
    },

    stopPropagation: function($event) {
        $event.stopPropagation();
    },

};

angular.module('main').controller('ItemPreviewCtrl', ['$element', '$state', '$scope', 'itemTypeMap', 'mjs', 'notification', '$window', '$document', ItemPreviewCtrl]);
<|endoftext|>


<commit_before>(function () {
  'use strict';
  var extend = require('extend'),
      defaults = require('./protractor.conf');

  exports.config = extend(defaults.config, {
    // --- uncomment to use mac mini's ---
    // seleniumAddress: 'http://clas-mac-mini.local:4444/wd/hub',
    // baseUrl: 'http://Marcos-MacBook-Pro-2.local:8001/',

    multiCapabilities: [
      // {
      //   browserName: 'chrome',
      //   'chromeOptions': {
      //     args: ['--test-type']
      //   }
      // },
      {
        browserName: 'firefox'
      }
    ]
  });
})();
<commit_msg>Add chrome to local e2e tests<commit_after>(function () {
  'use strict';
  var extend = require('extend'),
      defaults = require('./protractor.conf');

  exports.config = extend(defaults.config, {
    // --- uncomment to use mac mini's ---
    // seleniumAddress: 'http://clas-mac-mini.local:4444/wd/hub',
    // baseUrl: 'http://Marcos-MacBook-Pro-2.local:8001/',

    multiCapabilities: [
      {
        browserName: 'chrome',
        'chromeOptions': {
          args: ['--test-type']
        }
      },
      {
        browserName: 'firefox'
      }
    ]
  });
})();
<|endoftext|>


<commit_before>var keys = require('./keys');

/**
 * Input wrapper
 * @constructor
 * @param {Game} game - Game object
 */
var Input = function(game) {
  /**
   * Pressed keys object
   * @type {object}
   */
  this.keys = {};

  /**
   * Controls if you can press keys
   * @type {boolean}
   */
  this.canControlKeys = true;

  /**
   * Mouse object with positions and if is mouse button pressed
   * @type {object}
   */
  this.mouse = {
    isDown: false,
    x: null,
    y: null
  };

  this._addEvents(game);
};

/**
 * Clears the pressed keys object
 */
Input.prototype.resetKeys = function() {
  this.keys = {};
};

/**
 * Return true or false if key is pressed
 * @param {string} key
 * @return {boolean}
 */
Input.prototype.isKeyDown = function(key) {
  if (this.canControlKeys) {
    return this.keys[keys[key.toUpperCase()]];
  }
};

/**
 * Add canvas event listener
 * @private
 */
Input.prototype._addEvents = function(game) {
  var self = this;
  var canvas = game.canvas;

  canvas.addEventListener('mousemove', function(e) {
    var x = e.offsetX === undefined ? e.layerX - canvas.offsetLeft : e.offsetX;
    var y = e.offsetY === undefined ? e.layerY - canvas.offsetTop : e.offsetY;

    game.mousemove(x, y);
    self.mouse.x = x;
    self.mouse.y = y;
  });

  canvas.addEventListener('mouseup', function(e) {
    e.preventDefault();

    var x = e.offsetX === undefined ? e.layerX - canvas.offsetLeft : e.offsetX;
    var y = e.offsetY === undefined ? e.layerY - canvas.offsetTop : e.offsetY;

    self.mouse.isDown = false;

    game.mouseup(x, y, e.button);
  }, false);

  canvas.addEventListener('mousedown', function(e) {
    e.preventDefault();

    var x = e.offsetX === undefined ? e.layerX - canvas.offsetLeft : e.offsetX;
    var y = e.offsetY === undefined ? e.layerY - canvas.offsetTop : e.offsetY;

    self.mouse.x = x;
    self.mouse.y = y;
    self.mouse.isDown = true;

    game.click(x, y, e.button);
  }, false);

  var touchX = null;
  var touchY = null;

  canvas.addEventListener('touchstart', function(e) {
    var x = e.layerX;
    var y = e.layerY;

    touchX = x;
    touchY = y;

    self.mouse.x = x;
    self.mouse.y = y;
    self.mouse.isDown = true;
  });

  canvas.addEventListener('touchmove', function(e) {
    e.preventDefault();

    var x = e.layerX;
    var y = e.layerY;

    game.mousemove(x, y);

    self.mouse.x = x;
    self.mouse.y = y;
  });

  canvas.addEventListener('touchend', function(e) {
    e.preventDefault();

    self.mouse.isDown = false;

    for (var i=0, len=e.changedTouches.length; i<len; i++) {
      var touch = e.changedTouches[i];

      var x = touch.pageX - canvas.offsetLeft;
      var y = touch.pageY - canvas.offsetTop;
      var button = 0;

      var dx = Math.abs(touchX - x);
      var dy = Math.abs(touchY - y);

      var threshold = 5;

      if (dx < threshold && dy < threshold) {
        self.mouse.x = x;
        self.mouse.y = y;

        game.click(x, y, button);
      }
    }

  });

  canvas.addEventListener('contextmenu', function(e) {
    e.preventDefault();
  });

  document.addEventListener('keydown', function(e) {
    game.input.keys[e.keyCode] = true;
  });

  document.addEventListener('keyup', function(e) {
    game.input.keys[e.keyCode] = false;
  });

  if (game.keypress) {
    document.addEventListener('keypress', function(e) {
      game.keypress(e.which);
    });
  }
};

module.exports = Input;
<commit_msg>store which mouse button is pressed<commit_after>var keys = require('./keys');

/**
 * Input wrapper
 * @constructor
 * @param {Game} game - Game object
 */
var Input = function(game) {
  /**
   * Pressed keys object
   * @type {object}
   */
  this.keys = {};

  /**
   * Controls if you can press keys
   * @type {boolean}
   */
  this.canControlKeys = true;

  /**
   * Mouse object with positions and if is mouse button pressed
   * @type {object}
   */
  this.mouse = {
    isDown: false,
    button: null,
    x: null,
    y: null
  };

  this._addEvents(game);
};

/**
 * Clears the pressed keys object
 */
Input.prototype.resetKeys = function() {
  this.keys = {};
};

/**
 * Return true or false if key is pressed
 * @param {string} key
 * @return {boolean}
 */
Input.prototype.isKeyDown = function(key) {
  if (this.canControlKeys) {
    return this.keys[keys[key.toUpperCase()]];
  }
};

/**
 * Add canvas event listener
 * @private
 */
Input.prototype._addEvents = function(game) {
  var self = this;
  var canvas = game.canvas;

  canvas.addEventListener('mousemove', function(e) {
    var x = e.offsetX === undefined ? e.layerX - canvas.offsetLeft : e.offsetX;
    var y = e.offsetY === undefined ? e.layerY - canvas.offsetTop : e.offsetY;

    game.mousemove(x, y);
    self.mouse.x = x;
    self.mouse.y = y;
  });

  canvas.addEventListener('mouseup', function(e) {
    e.preventDefault();

    var x = e.offsetX === undefined ? e.layerX - canvas.offsetLeft : e.offsetX;
    var y = e.offsetY === undefined ? e.layerY - canvas.offsetTop : e.offsetY;

    self.mouse.button = e.button;
    self.mouse.isDown = false;

    game.mouseup(x, y, e.button);
  }, false);

  canvas.addEventListener('mousedown', function(e) {
    e.preventDefault();

    var x = e.offsetX === undefined ? e.layerX - canvas.offsetLeft : e.offsetX;
    var y = e.offsetY === undefined ? e.layerY - canvas.offsetTop : e.offsetY;

    self.mouse.x = x;
    self.mouse.y = y;
    self.mouse.button = e.button;
    self.mouse.isDown = true;

    game.click(x, y, e.button);
  }, false);

  var touchX = null;
  var touchY = null;

  canvas.addEventListener('touchstart', function(e) {
    var x = e.layerX;
    var y = e.layerY;

    touchX = x;
    touchY = y;

    self.mouse.x = x;
    self.mouse.y = y;
    self.mouse.isDown = true;
  });

  canvas.addEventListener('touchmove', function(e) {
    e.preventDefault();

    var x = e.layerX;
    var y = e.layerY;

    game.mousemove(x, y);

    self.mouse.x = x;
    self.mouse.y = y;
  });

  canvas.addEventListener('touchend', function(e) {
    e.preventDefault();

    self.mouse.isDown = false;

    for (var i=0, len=e.changedTouches.length; i<len; i++) {
      var touch = e.changedTouches[i];

      var x = touch.pageX - canvas.offsetLeft;
      var y = touch.pageY - canvas.offsetTop;
      var button = 0;

      var dx = Math.abs(touchX - x);
      var dy = Math.abs(touchY - y);

      var threshold = 5;

      if (dx < threshold && dy < threshold) {
        self.mouse.x = x;
        self.mouse.y = y;

        game.click(x, y, button);
      }
    }

  });

  canvas.addEventListener('contextmenu', function(e) {
    e.preventDefault();
  });

  document.addEventListener('keydown', function(e) {
    game.input.keys[e.keyCode] = true;
  });

  document.addEventListener('keyup', function(e) {
    game.input.keys[e.keyCode] = false;
  });

  if (game.keypress) {
    document.addEventListener('keypress', function(e) {
      game.keypress(e.which);
    });
  }
};

module.exports = Input;
<|endoftext|>


<commit_before>export default class Input {
    constructor() {
    }

    update() {
        console.log('Input Updating!');
    }
}
<commit_msg>Added getter methods<commit_after>export default class Input {

    constructor() {
        this.keys = [];
    }

    update() {
        console.log('Input Updating!');
    }

    getKey(key){
        return this.keys[key];
    }

    setKey(key){
        this.keys[key] = true;
    }
}
<|endoftext|>