<commit_before>/* This Source Code Form is subject to the terms of the MIT license
 * If a copy of the MIT license was not distributed with this file, you can
 * obtain one at https://raw.github.com/mozilla/butter/master/LICENSE */

/**
 * Module: EditorModule
 *
 * Butter Module for Editors
 */
define( [ "core/eventmanager", "core/trackevent", "./editor",
          "ui/toggler", "util/lang", "text!layouts/editor-area.html",
          "./default", "core/logger", "./header",
          "./media-editor", "./share-editor" ],
  function( EventManager, TrackEvent, Editor,
            Toggler, LangUtils, EDITOR_AREA_LAYOUT,
            DefaultEditor, Logger, Header,

            //included here to register themselves
            MediaEditor, ShareEditor ){

  var DEFAULT_EDITOR_NAME = "plugin-list";

  // Expose DefaultEditor to external editors
  Editor.DefaultEditor = DefaultEditor;

  /**
   * Class: EventEditor
   *
   * Module which provides Editor functionality to Butter
   */
  function EventEditor( butter, moduleOptions, ButterNamespace ){

    moduleOptions = moduleOptions || {};

    var _currentEditor,
        _editorAreaDOMRoot = LangUtils.domFragment( EDITOR_AREA_LAYOUT, ".butter-editor-area" ),
        _editorContentArea = _editorAreaDOMRoot.querySelector( ".butter-editor-content" ),
        _header,
        _toggler,
        _this = this,
        _createdEditors = {},
        _logger = new Logger( butter.id );

    EventManager.extend( _this );

    ButterNamespace.Editor = Editor;

    _header = new Header( _editorAreaDOMRoot, _this );

    function setupHeader() {
      if ( butter.project.isSaved ) {
        _header.views.saved();
      } else {
        _header.views.unSaved();
      }
    }

    /**
     * Member: openEditor
     *
     * Opens an editor corresponding to the given editor name if it exists
     *
     * @param {String} editorName: Name of editor to open
     * @param {Object} options: An object storing various optional parameters that are used when opening an editor. These options are:
     * * @param {Booelean} persist: Indicate whether or not the editor should be recreated each time it wants to be opened
     * * @param {Object} openData: TrackEvent data used within an editors `open` method
     */
    _this.openEditor = function( editorName, options ) {
      options = options || {};

      var persist = options.persist,
          onTransitionEnd,
          onEditorOpened;

      onEditorOpened = function() {
        butter.dispatch( "editoropened", editorName );
      };

      onTransitionEnd = function() {
        LangUtils.removeTransitionEndListener( _editorAreaDOMRoot, onTransitionEnd );
        onEditorOpened();
      };

      persist = persist === true || persist === false ? persist : Editor.isPersistent( editorName );

      _toggler.state = false;

      if ( _currentEditor ) {
        _currentEditor.close();
      }

      // Some editors may not need to be created again. If so, store them in an object and open and close them as needed.
      if ( persist && _createdEditors[ editorName ] ) {
        _currentEditor = _createdEditors[ editorName ];
      } else {
        _currentEditor = _createdEditors[ editorName ] = Editor.create( editorName, butter );
      }

      _currentEditor.open( _editorContentArea, options.openData );
      _currentEditor.listen( "back", function( e ) {
        _this.openEditor( DEFAULT_EDITOR_NAME );
      });

      // Check if this is a top-level editor
      if ( _header.focusMap[ editorName ] ) {
        _header.setFocus( editorName );
      } else {
        // Otherwise, it is an event editor, so focus the default editor
        _header.setFocus( DEFAULT_EDITOR_NAME );
      }

      // If the editor was closed when this was called, remove classes keeping it hidden
      if ( _editorAreaDOMRoot.classList.contains( "minimized" ) ) {
        LangUtils.applyTransitionEndListener( _editorAreaDOMRoot, onTransitionEnd );
        _editorAreaDOMRoot.classList.remove( "minimized" );
        document.body.classList.add( "editor-open" );
      } else {
        onEditorOpened();
      }

      return _currentEditor;
    };

    /**
     * Member: closeTrackEventEditor
     *
     * Closes the currently opened editor and opens the default one in it's place
     *
     */
    _this.closeEditor = function() {
      _currentEditor.close();
      _currentEditor = null;
      _this.openEditor( DEFAULT_EDITOR_NAME );
    };

    /**
     * Member: closeEditor
     *
     * A safer means of closing an editor. This ensures that the current open editor's trackevent matches the
     * passed in trackevent before calling its close method.
     *
     * @param {TrackEvent} trackEvent: The trackevent being used to compare against the current open editor.
     */
    _this.closeTrackEventEditor = function( trackEvent ) {
      var isTrackEventEditor = _currentEditor.getTrackEvent;

      if ( trackEvent && isTrackEventEditor &&
          isTrackEventEditor().id === trackEvent.id ) {

        _this.closeEditor();
      }
    };

    /**
     * Member: editTrackEvent
     *
     * Open the editor corresponding to the type of the given TrackEvent
     *
     * @param {TrackEvent} trackEvent: TrackEvent to edit
     */
    _this.editTrackEvent = function( trackEvent ) {
      if ( !trackEvent || !( trackEvent instanceof TrackEvent ) ) {
        throw new Error( "trackEvent must be valid to start an editor." );
      }
      var editorType = Editor.isRegistered( trackEvent.type ) ? trackEvent.type : "default";
      return _this.openEditor( editorType, {
        openData: trackEvent
      });
    };

    /**
     * Member: _start
     *
     * Prepares this module for Butter startup
     *
     * @param {Function} onModuleReady: Callback to signify that module is ready
     */
    this._start = function( onModuleReady ){
      _toggler = new Toggler( _editorAreaDOMRoot.querySelector( ".butter-editor-close-btn" ),
        function( e ) {
          var newState = !_editorAreaDOMRoot.classList.contains( "minimized" );

          var onTransitionEnd = function(){
            LangUtils.removeTransitionEndListener( _editorAreaDOMRoot, onTransitionEnd );
            _this.dispatch( "editortoggled", newState );
          };

          _toggler.state = newState;
          if ( newState ) {
            document.body.classList.remove( "editor-open" );
            _editorAreaDOMRoot.classList.add( "minimized" );
          }
          else {
            document.body.classList.add( "editor-open" );
            _editorAreaDOMRoot.classList.remove( "minimized" );
          }

          LangUtils.applyTransitionEndListener( _editorAreaDOMRoot, onTransitionEnd );

        }, "Show/Hide Editor", true );

      var editorsToLoad = [],
          editorsLoaded = 0;

      if ( butter.config.value( "ui" ).enabled !== false ) {

        // Set up views for share editor
        butter.listen( "ready", setupHeader );
        butter.listen( "autologinsucceeded", setupHeader );
        butter.listen( "authenticated", setupHeader );

        butter.listen( "projectsaved", _header.views.saved );
        butter.listen( "logout", _header.views.unSaved );

        // Set up views for plugin list editor
        butter.listen( "mediacontentchanged", _header.views.disablePlugins );
        butter.listen( "mediaready", _header.views.enablePlugins );
        document.body.classList.add( "butter-editor-spacing" );

        // Start minimized
        _editorAreaDOMRoot.classList.add( "minimized" );
        document.body.classList.remove( "editor-open" );

        butter.ui.setEditor( _editorAreaDOMRoot );

        var config = butter.config.value( "editor" );
        for ( var editorName in config ) {
          if ( config.hasOwnProperty( editorName ) ) {
            editorsToLoad.push({
              url: config[ editorName ],
              type: "js"
            });
          }
        }

        if ( editorsToLoad.length > 0 ){
          butter.loader.load( editorsToLoad, function() {
            Editor.initialize( onModuleReady, butter.config.value( "baseDir" ) );
          }, function( e ) {
            _logger.log( "Couldn't load editor " + e.target.src );

            if ( ++editorsLoaded === editorsToLoad.length ) {
              onModuleReady();
            }
          });
        }
        else {
          onModuleReady();
        }

      }
      else {
        onModuleReady();
      }
    };

    Object.defineProperties( _this, {
      currentEditor: {
        enumerable: true,
        get: function() {
          return _currentEditor;
        }
      }
    });
  }

  this.register = Editor.register;

  EventEditor.__moduleName = "editor";

  return EventEditor;

}); //define
<commit_msg>[#2992] Prevent editor from being closed by dragging/resizing/droppable/contenteditable if already opened for current track event<commit_after>/* This Source Code Form is subject to the terms of the MIT license
 * If a copy of the MIT license was not distributed with this file, you can
 * obtain one at https://raw.github.com/mozilla/butter/master/LICENSE */

/**
 * Module: EditorModule
 *
 * Butter Module for Editors
 */
define( [ "core/eventmanager", "core/trackevent", "./editor",
          "ui/toggler", "util/lang", "text!layouts/editor-area.html",
          "./default", "core/logger", "./header",
          "./media-editor", "./share-editor" ],
  function( EventManager, TrackEvent, Editor,
            Toggler, LangUtils, EDITOR_AREA_LAYOUT,
            DefaultEditor, Logger, Header,

            //included here to register themselves
            MediaEditor, ShareEditor ){

  var DEFAULT_EDITOR_NAME = "plugin-list";

  // Expose DefaultEditor to external editors
  Editor.DefaultEditor = DefaultEditor;

  /**
   * Class: EventEditor
   *
   * Module which provides Editor functionality to Butter
   */
  function EventEditor( butter, moduleOptions, ButterNamespace ){

    moduleOptions = moduleOptions || {};

    var _currentEditor,
        _editorAreaDOMRoot = LangUtils.domFragment( EDITOR_AREA_LAYOUT, ".butter-editor-area" ),
        _editorContentArea = _editorAreaDOMRoot.querySelector( ".butter-editor-content" ),
        _header,
        _toggler,
        _this = this,
        _createdEditors = {},
        _logger = new Logger( butter.id );

    EventManager.extend( _this );

    ButterNamespace.Editor = Editor;

    _header = new Header( _editorAreaDOMRoot, _this );

    function setupHeader() {
      if ( butter.project.isSaved ) {
        _header.views.saved();
      } else {
        _header.views.unSaved();
      }
    }

    /**
     * Member: openEditor
     *
     * Opens an editor corresponding to the given editor name if it exists
     *
     * @param {String} editorName: Name of editor to open
     * @param {Object} options: An object storing various optional parameters that are used when opening an editor. These options are:
     * * @param {Booelean} persist: Indicate whether or not the editor should be recreated each time it wants to be opened
     * * @param {Object} openData: TrackEvent data used within an editors `open` method
     */
    _this.openEditor = function( editorName, options ) {
      options = options || {};

      var persist = options.persist,
          onTransitionEnd,
          onEditorOpened;

      onEditorOpened = function() {
        butter.dispatch( "editoropened", editorName );
      };

      onTransitionEnd = function() {
        LangUtils.removeTransitionEndListener( _editorAreaDOMRoot, onTransitionEnd );
        onEditorOpened();
      };

      persist = persist === true || persist === false ? persist : Editor.isPersistent( editorName );

      _toggler.state = false;

      if ( _currentEditor ) {
        _currentEditor.close();
      }

      // Some editors may not need to be created again. If so, store them in an object and open and close them as needed.
      if ( persist && _createdEditors[ editorName ] ) {
        _currentEditor = _createdEditors[ editorName ];
      } else {
        _currentEditor = _createdEditors[ editorName ] = Editor.create( editorName, butter );
      }

      _currentEditor.open( _editorContentArea, options.openData );
      _currentEditor.listen( "back", function( e ) {
        _this.openEditor( DEFAULT_EDITOR_NAME );
      });

      // Check if this is a top-level editor
      if ( _header.focusMap[ editorName ] ) {
        _header.setFocus( editorName );
      } else {
        // Otherwise, it is an event editor, so focus the default editor
        _header.setFocus( DEFAULT_EDITOR_NAME );
      }

      // If the editor was closed when this was called, remove classes keeping it hidden
      if ( _editorAreaDOMRoot.classList.contains( "minimized" ) ) {
        LangUtils.applyTransitionEndListener( _editorAreaDOMRoot, onTransitionEnd );
        _editorAreaDOMRoot.classList.remove( "minimized" );
        document.body.classList.add( "editor-open" );
      } else {
        onEditorOpened();
      }

      return _currentEditor;
    };

    /**
     * Member: closeTrackEventEditor
     *
     * Closes the currently opened editor and opens the default one in it's place
     *
     */
    _this.closeEditor = function() {
      _currentEditor.close();
      _currentEditor = null;
      _this.openEditor( DEFAULT_EDITOR_NAME );
    };

    /**
     * Member: closeEditor
     *
     * A safer means of closing an editor. This ensures that the current open editor's trackevent matches the
     * passed in trackevent before calling its close method.
     *
     * @param {TrackEvent} trackEvent: The trackevent being used to compare against the current open editor.
     */
    _this.closeTrackEventEditor = function( trackEvent ) {
      var isTrackEventEditor = _currentEditor.getTrackEvent;

      if ( trackEvent && isTrackEventEditor &&
          isTrackEventEditor().id === trackEvent.id ) {

        _this.closeEditor();
      }
    };

    /**
     * Member: editTrackEvent
     *
     * Open the editor corresponding to the type of the given TrackEvent
     *
     * @param {TrackEvent} trackEvent: TrackEvent to edit
     */
    _this.editTrackEvent = function( trackEvent ) {
      var editorType = Editor.isRegistered( trackEvent.type ) ? trackEvent.type : "default";

      if ( !trackEvent || !( trackEvent instanceof TrackEvent ) ) {
        throw new Error( "trackEvent must be valid to start an editor." );
      }

      if ( _currentEditor && _currentEditor.getTrackEvent ) {
        if ( trackEvent.id === _currentEditor.getTrackEvent().id ) {
          return _currentEditor;
        }
      }

      return _this.openEditor( editorType, { openData: trackEvent } );
    };

    /**
     * Member: _start
     *
     * Prepares this module for Butter startup
     *
     * @param {Function} onModuleReady: Callback to signify that module is ready
     */
    this._start = function( onModuleReady ){
      _toggler = new Toggler( _editorAreaDOMRoot.querySelector( ".butter-editor-close-btn" ),
        function( e ) {
          var newState = !_editorAreaDOMRoot.classList.contains( "minimized" );

          var onTransitionEnd = function(){
            LangUtils.removeTransitionEndListener( _editorAreaDOMRoot, onTransitionEnd );
            _this.dispatch( "editortoggled", newState );
          };

          _toggler.state = newState;
          if ( newState ) {
            document.body.classList.remove( "editor-open" );
            _editorAreaDOMRoot.classList.add( "minimized" );
          }
          else {
            document.body.classList.add( "editor-open" );
            _editorAreaDOMRoot.classList.remove( "minimized" );
          }

          LangUtils.applyTransitionEndListener( _editorAreaDOMRoot, onTransitionEnd );

        }, "Show/Hide Editor", true );

      var editorsToLoad = [],
          editorsLoaded = 0;

      if ( butter.config.value( "ui" ).enabled !== false ) {

        // Set up views for share editor
        butter.listen( "ready", setupHeader );
        butter.listen( "autologinsucceeded", setupHeader );
        butter.listen( "authenticated", setupHeader );

        butter.listen( "projectsaved", _header.views.saved );
        butter.listen( "logout", _header.views.unSaved );

        // Set up views for plugin list editor
        butter.listen( "mediacontentchanged", _header.views.disablePlugins );
        butter.listen( "mediaready", _header.views.enablePlugins );
        document.body.classList.add( "butter-editor-spacing" );

        // Start minimized
        _editorAreaDOMRoot.classList.add( "minimized" );
        document.body.classList.remove( "editor-open" );

        butter.ui.setEditor( _editorAreaDOMRoot );

        var config = butter.config.value( "editor" );
        for ( var editorName in config ) {
          if ( config.hasOwnProperty( editorName ) ) {
            editorsToLoad.push({
              url: config[ editorName ],
              type: "js"
            });
          }
        }

        if ( editorsToLoad.length > 0 ){
          butter.loader.load( editorsToLoad, function() {
            Editor.initialize( onModuleReady, butter.config.value( "baseDir" ) );
          }, function( e ) {
            _logger.log( "Couldn't load editor " + e.target.src );

            if ( ++editorsLoaded === editorsToLoad.length ) {
              onModuleReady();
            }
          });
        }
        else {
          onModuleReady();
        }

      }
      else {
        onModuleReady();
      }
    };

    Object.defineProperties( _this, {
      currentEditor: {
        enumerable: true,
        get: function() {
          return _currentEditor;
        }
      }
    });
  }

  this.register = Editor.register;

  EventEditor.__moduleName = "editor";

  return EventEditor;

}); //define
<|endoftext|>


<commit_before>
/**
 * Created by lundfall on 01/06/2017.
 */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Owner: mark@famo.us
 * @license MPL 2.0
 * @copyright Famous Industries, Inc. 2015
 */

define(function (require, exports, module) {

  var DOMEventHandler = {};
  var EventEmitter = require('./EventEmitter.js');
  var DOMBuffer = require('./DOMBuffer');

  //TODO Add more to complete list
  var singleElementEvents = [
    'submit', 'focus', 'blur', 'load', 'unload', 'change', 'reset', 'scroll', 'mousewheel', 'wheel'
  ];

  var initializedListeners = {};

  DOMEventHandler.isNativeEvent = function(eventName) {
    return typeof document.body["on" + eventName] !== "undefined";
  };

  DOMEventHandler.addEventListener = function(id, element, type, callback){
    if(!DOMEventHandler.isNativeEvent(type)){
      return;
    }

    if(singleElementEvents.includes(type)){
      return element.addEventListener(type, callback);
    }
    DOMBuffer.setAttribute(element, 'data-arvaid', id);
    var eventEmitter = initializedListeners[type];
    if(!eventEmitter){
      eventEmitter = initializedListeners[type] = new EventEmitter();
      window.addEventListener(type, function (event) {
        var recievedID = event.target && event.target.getAttribute && event.target.getAttribute('data-arvaid');
        if(recievedID){
          eventEmitter.emit(recievedID, event);
        }
      });
    }
    eventEmitter.on(id, callback);

  };

  DOMEventHandler.removeEventListener = function(element, id, type, callback) {
    if(singleElementEvents.includes(type)){
      return element.addEventListener(type, callback);
    }
    if(initializedListeners[type]){
      initializedListeners[type].removeListener(id, callback);
    }
  };

  module.exports = DOMEventHandler;
});
<commit_msg>Made DOMEventHandler look at the relatedTarget as well<commit_after>
/**
 * Created by lundfall on 01/06/2017.
 */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Owner: mark@famo.us
 * @license MPL 2.0
 * @copyright Famous Industries, Inc. 2015
 */

define(function (require, exports, module) {

  var DOMEventHandler = {};
  var EventEmitter = require('./EventEmitter.js');
  var DOMBuffer = require('./DOMBuffer');

  //TODO Add more to complete list
  var singleElementEvents = [
    'submit', 'focus', 'blur', 'load', 'unload', 'change', 'reset', 'scroll', 'mousewheel', 'wheel'
  ];

  var initializedListeners = {};

  DOMEventHandler.isNativeEvent = function(eventName) {
    return typeof document.body["on" + eventName] !== "undefined";
  };

  DOMEventHandler.addEventListener = function(id, element, type, callback){
    if(!DOMEventHandler.isNativeEvent(type)){
      return;
    }

    if(singleElementEvents.includes(type)){
      return element.addEventListener(type, callback);
    }
    DOMBuffer.setAttribute(element, 'data-arvaid', id);
    var eventEmitter = initializedListeners[type];
    if(!eventEmitter){
      eventEmitter = initializedListeners[type] = new EventEmitter();
      window.addEventListener(type, function (event) {
        var target = event.relatedTarget || event.target;
        var recievedID = target && target.getAttribute && target.getAttribute('data-arvaid');
        if(recievedID){
          eventEmitter.emit(recievedID, event);
        }
      });
    }
    eventEmitter.on(id, callback);

  };

  DOMEventHandler.removeEventListener = function(element, id, type, callback) {
    if(singleElementEvents.includes(type)){
      return element.addEventListener(type, callback);
    }
    if(initializedListeners[type]){
      initializedListeners[type].removeListener(id, callback);
    }
  };

  module.exports = DOMEventHandler;
});
<|endoftext|>


<commit_before>// export default function tween(spec) {
//   _validate(spec)

//   if (!spec.target) throws(`error`)
//   if (typeof spec.duration !== 'number') throws(`error`)
//   if (typeof spec.delay !== 'undefined' && typeof spec.delay !== 'number') throws(`error`)
//   if (typeof to !== 'object') throws(`error`)

// }

import Tween from 'sk/utils/tweens/Tween'
import * as utils from 'sk/utils'

/**
 * Creates and return a new tween.
 *
 * @param {Object} spec
 *
 * @param {Object} spec.target - The target objects (which will receive the
 *        modifications).
 * @param {Number} spec.duration - The duration of the tween, in milliseconds.
 * @param {Number} spec.delay - The delay of the tween. If set, the total
 *        execution time of the tween will be `delay + duration`.
 * @param {Object} spec.to - The map of properties to be changed, the key
 *        is the name of the property and the value is the target value of
 *        that property at time `1`.
 * @param {Boolean} spec.loop - If the tween should repeat or not. If true,
 *        the tween `hasFinished` method will only return true when the tween
 *        is manually stop. Notice that, the tween will pause for the delay
 *        time every iteration.
 * @param {Function} spec.ease - The easing function. Defaults to linear.
 * @param {Function} spec.update - The update callback, called every tick if
 *        the tween is updating the target values (i.e., it is not called
 *        while on delay).
 * @param {Function} spec.stop - The stop callback, called when the tween is
 *        finished.
 */
export default function tween(spec) {
  // Spec validation
  _validate(spec)

  // Processing values
  return _create(spec)
}

// Shortcut for throwing an error
function throws(message, error) {
  error = error || Error
  throw new error(message)
}

// Validates the spec
function _validate(spec) {
  // Empty spec
  if (!spec)
    throws(`Empty tween specification. Please provide an object with the tween
            declaration.`)

  // Empty target
  if (!spec.target)
    throws(`Empty tween target. Please provide an object.`)

  // Empty to
  if (!spec.to)
    throws(`Empty tween to specification. Please provide an object with the
            tween properties.`)

  // Duration is not a number
  if (typeof spec.duration !== 'undefined' && typeof spec.duration !== 'number')
    throws(`Tween duration must be a number.`)

  // Duration is equal (or less than zero)
  if (spec.duration <= 0)
    throws(`Tween duration must be bigger than 0.`)

  // Delay is not a number
  if (typeof spec.delay !== 'undefined' && typeof spec.delay !== 'number')
    throws(`Tween delay must be a number.`)

  // Delay is equal (or less than zero)
  if (spec.delay < 0)
    throws(`Tween delay must be bigger or equal to 0.`)

  // Ease is not a function
  if (spec.ease && !utils.isFunction(spec.ease))
    throws(`Tween ease must be an easing function.`)

  // Update callback is not a function
  if (spec.update && !utils.isFunction(spec.update))
    throws(`Tween update callback must be a function.`)

  // Stop callback is not a function
  if (spec.stop && !utils.isFunction(spec.stop))
    throws(`Tween stop callback must be a function.`)

  // Validate each property value (must be a number)
  for (let k in spec.to) {
    if (typeof spec.to[k] !== 'number')
      throws(`Value for the target property "${k}" must be a number.`)
  }
}

// Process values
function _create(spec) {
  let properties = {}
  for (let k in spec.to) {
    properties[k] = [spec.target[k], spec.to[k]]
  }

  let tween = new Tween(
    spec.target,
    spec.duration,
    properties,
    spec.delay,
    spec.loop,
    spec.ease
  )
  tween._updateFn = spec.update
  tween._stopFn = spec.stop

  return tween
}<commit_msg>Tween declarator<commit_after>// export default function tween(spec) {
//   _validate(spec)

//   if (!spec.target) throws(`error`)
//   if (typeof spec.duration !== 'number') throws(`error`)
//   if (typeof spec.delay !== 'undefined' && typeof spec.delay !== 'number') throws(`error`)
//   if (typeof to !== 'object') throws(`error`)

// }

import Tween from 'sk/utils/tweens/Tween'
import * as utils from 'sk/utils'

/**
 * Creates and return a new tween.
 *
 * @param {Object} spec
 *
 * @param {Object} spec.target - The target objects (which will receive the
 *        modifications).
 * @param {Number} spec.duration - The duration of the tween, in milliseconds.
 * @param {Number} spec.delay - The delay of the tween. If set, the total
 *        execution time of the tween will be `delay + duration`.
 * @param {Object} spec.to - The map of properties to be changed, the key
 *        is the name of the property and the value is the target value of
 *        that property at time `1`.
 * @param {Boolean} spec.loop - If the tween should repeat or not. If true,
 *        the tween `hasFinished` method will only return true when the tween
 *        is manually stop. Notice that, the tween will pause for the delay
 *        time every iteration.
 * @param {Function} spec.ease - The easing function. Defaults to linear.
 * @param {Function} spec.update - The update callback, called every tick if
 *        the tween is updating the target values (i.e., it is not called
 *        while on delay).
 * @param {Function} spec.stop - The stop callback, called when the tween is
 *        finished.
 */
export default function tween(spec) {
  // Spec validation
  _validate(spec)

  // Processing values
  return _create(spec)
}

// Shortcut for throwing an error
function throws(message, error) {
  error = error || Error
  throw new error(message)
}

// Validates the spec
function _validate(spec) {
  // Empty spec
  if (!spec)
    throws(`Empty tween specification. Please provide an object with the tween
            declaration.`)

  // Empty target
  if (!spec.target)
    throws(`Empty tween target. Please provide an object.`)

  // Empty to
  if (!spec.to && !spec.properties)
    throws(`Empty tween to or properties specification. Please provide an
            object with the tween properties.`)

  // Duration is not a number
  if (typeof spec.duration !== 'undefined' && typeof spec.duration !== 'number')
    throws(`Tween duration must be a number.`)

  // Duration is equal (or less than zero)
  if (spec.duration <= 0)
    throws(`Tween duration must be bigger than 0.`)

  // Delay is not a number
  if (typeof spec.delay !== 'undefined' && typeof spec.delay !== 'number')
    throws(`Tween delay must be a number.`)

  // Delay is equal (or less than zero)
  if (spec.delay < 0)
    throws(`Tween delay must be bigger or equal to 0.`)

  // Ease is not a function
  if (spec.ease && !utils.isFunction(spec.ease))
    throws(`Tween ease must be an easing function.`)

  // Update callback is not a function
  if (spec.update && !utils.isFunction(spec.update))
    throws(`Tween update callback must be a function.`)

  // Stop callback is not a function
  if (spec.stop && !utils.isFunction(spec.stop))
    throws(`Tween stop callback must be a function.`)

  // Validate each property value (must be a number)
  for (let k in spec.to) {
    if (typeof spec.to[k] !== 'number')
      throws(`Value for the target property "${k}" must be a number.`)
  }
  for (let k in spec.properties) {
    let p = spec.properties
    if (typeof p[k][0] !== 'number' || typeof p[k][1] !== 'number')
      throws(`Value for the target property "${k}" must be a number.`)
  }
}

// Process values
function _create(spec) {
  let properties = spec.properties || {}
  for (let k in spec.to) {
    properties[k] = [spec.target[k], spec.to[k]]
  }

  let tween = new Tween(
    spec.target,
    properties,
    spec.duration,
    spec.delay,
    spec.loop,
    spec.ease,
    spec.update,
    spec.stop
  )

  return tween
}<|endoftext|>


<commit_before>console.log('Loading restify server...');

var restify = require('restify');

var api = restify.createServer({name: 'fitbit-restify'});
api.listen(3000, function () {
    console.log('%s listening at %s', api.name, api.url)
});

// api.pre(restify.CORS({
//     origins: ['*'],
//     credentials: false,
//     headers: ['X-Requested-With', 'Authorization']
// }));
api.pre(restify.fullResponse());

api.use(restify.bodyParser());

api.get('/ping', function (req, res, next) {
    console.log("ping called");
    res.send(200, {response: true});
});

// api.get('/validators/username/:username', function (req, res, next)
// {
//     console.log("validators/username called");
//     var list = ['jesse', 'james', 'cow'];
//     var attemptedName = req.params.username;
//     // [jwarden 2.23.2015] TODO: Really need some string / number validation here.
//     if(_.find(list, function(item)
//         {
//             return attemptedName == item;
//         }) != undefined)
//     {
//         res.send(200, {response: {
//                             available: false,
//                             error: 'Username is taken.',
//                         }});
//     }
//     else
//     {
//         res.send(200, {response: {
//                             available: true
//                         }});
//     }
// });

// api.get('/validators/creditcard/:creditcard', function (req, res, next)
// {
//     console.log("validators/creditcard called");
//     if(wdprValidators.isCreditCard(req.params.creditcard) === true)
//     {
//         res.send(200, {response: {
//                             valid: true
//                         }});
//     }
//     else
//     {
//         res.send(200, {response: {
//                             valid: false,
//                             error: 'This is not a valid credit card number.'
//                         }});
//     }
// });

// api.post('/validators/remember', function(req, res, next)
// {
//     console.log("validators::remember");

//     var validArray = wdprValidators.checkBoolean(req.body.remember);
//     var response;
//     if(validArray.length <= 0)
//     {
//         response = "Valid boolean.";
//     }
//     else
//     {
//         response = validArray[0];
//     }
//    res.send(200, {response: {
//                                 valid: response
//                             }});
// });
<commit_msg>remove kruft<commit_after>console.log('Loading restify server...');

var restify = require('restify');

var api = restify.createServer({name: 'fitbit-restify'});
api.listen(3000, function () {
    console.log('%s listening at %s', api.name, api.url)
});

// api.pre(restify.CORS({
//     origins: ['*'],
//     credentials: false,
//     headers: ['X-Requested-With', 'Authorization']
// }));
api.pre(restify.fullResponse());

api.use(restify.bodyParser());

api.get('/ping', function (req, res, next) {
    console.log("ping called");
    res.send(200, {response: true});
});

<|endoftext|>


<commit_before>import Metadata from "../metadata/Metadata";
import WebAPI from "../webapi/WebAPI";

const create = superclass => class extends superclass {
    static async create(logicalName, entityData) {
        const entityMetadata = await Metadata.getEntityDefinitions(logicalName);
        console.log(`Create ${logicalName}`);
        entityData[entityMetadata.PrimaryIdAttribute] = await WebAPI.createEntity(logicalName, entityData);
        return this.parseResult([entityData], logicalName)[0];
    }

    async create() {
        return this.getClass().create(this.logicalName, this.data);
    }
};
export default create;
<commit_msg>Fix entity create doesn't return entity<commit_after>import Metadata from "../metadata/Metadata";
import WebAPI from "../webapi/WebAPI";

const create = superclass => class extends superclass {
    static async create(logicalName, entityData) {
        const entityMetadata = await Metadata.getEntityDefinitions(logicalName);
        console.log(`Create ${logicalName}`);
        entityData[entityMetadata.PrimaryIdAttribute] = await WebAPI.createEntity(logicalName, entityData);
        return this.parseResult([entityData], logicalName).then(entities => entities[0]);
    }

    async create() {
        return this.getClass().create(this.logicalName, this.data);
    }
};
export default create;
<|endoftext|>


<commit_before>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;
const FB_TEXT_LIMIT = 640;

const FACEBOOK_LOCATION = "FACEBOOK_LOCATION";
const FACEBOOK_WELCOME = "FACEBOOK_WELCOME";

class FacebookBot {
    constructor() {
        this.apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
        this.sessionIds = new Map();
        this.messagesDelay = 200;
    }

    doDataResponse(sender, facebookResponseData) {
        if (!Array.isArray(facebookResponseData)) {
            console.log('Response as formatted message');
            this.sendFBMessage(sender, facebookResponseData)
                .catch(err => console.error(err));
        } else {
            async.eachSeries(facebookResponseData, (facebookMessage, callback) => {
                if (facebookMessage.sender_action) {
                    console.log('Response as sender action');
                    this.sendFBSenderAction(sender, facebookMessage.sender_action)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
                else {
                    console.log('Response as formatted message');
                    this.sendFBMessage(sender, facebookMessage)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
            }, (err) => {
                if (err) {
                    console.error(err);
                } else {
                    console.log('Data response completed');
                }
            });
        }
    }

    doRichContentResponse(sender, messages) {
        let facebookMessages = []; // array with result messages

        for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
            let message = messages[messageIndex];

            switch (message.type) {
                //message.type 0 means text message
                case 0:
                    // speech: ["hi"]
                    // we have to get value from fulfillment.speech, because of here is raw speech
                    if (message.speech) {

                        let splittedText = this.splitResponse(message.speech);

                        splittedText.forEach(s => {
                            facebookMessages.push({text: s});
                        });
                    }

                    break;
                //message.type 1 means card message
                case 1: {
                    let carousel = [message];

                    for (messageIndex++; messageIndex < messages.length; messageIndex++) {
                        if (messages[messageIndex].type == 1) {
                            carousel.push(messages[messageIndex]);
                        } else {
                            messageIndex--;
                            break;
                        }
                    }

                    let facebookMessage = {};
                    carousel.forEach((c) => {
                        // buttons: [ {text: "hi", postback: "postback"} ], imageUrl: "", title: "", subtitle: ""

                        let card = {};

                        card.title = c.title;
                        card.image_url = c.imageUrl;
                        if (this.isDefined(c.subtitle)) {
                            card.subtitle = c.subtitle;
                        }
                        //If button is involved in.
                        if (c.buttons.length > 0) {
                            let buttons = [];
                            for (let buttonIndex = 0; buttonIndex < c.buttons.length; buttonIndex++) {
                                let button = c.buttons[buttonIndex];

                                if (button.text) {
                                    let postback = button.postback;
                                    if (!postback) {
                                        postback = button.text;
                                    }

                                    let buttonDescription = {
                                        title: button.text
                                    };

                                    if (postback.startsWith("http")) {
                                        buttonDescription.type = "web_url";
                                        buttonDescription.url = postback;
                                    } else {
                                        buttonDescription.type = "postback";
                                        buttonDescription.payload = postback;
                                    }

                                    buttons.push(buttonDescription);
                                }
                            }

                            if (buttons.length > 0) {
                                card.buttons = buttons;
                            }
                        }

                        if (!facebookMessage.attachment) {
                            facebookMessage.attachment = {type: "template"};
                        }

                        if (!facebookMessage.attachment.payload) {
                            facebookMessage.attachment.payload = {template_type: "generic", elements: []};
                        }

                        facebookMessage.attachment.payload.elements.push(card);
                    });

                    facebookMessages.push(facebookMessage);
                }

                    break;
                //message.type 2 means quick replies message
                case 2: {
                    if (message.replies && message.replies.length > 0) {
                        let facebookMessage = {};

                        facebookMessage.text = message.title ? message.title : 'Choose an item';
                        facebookMessage.quick_replies = [];

                        message.replies.forEach((r) => {
                            facebookMessage.quick_replies.push({
                                content_type: "text",
                                title: r,
                                payload: r
                            });
                        });

                        facebookMessages.push(facebookMessage);
                    }
                }

                    break;
                //message.type 3 means image message
                case 3:

                    if (message.imageUrl) {
                        let facebookMessage = {};

                        // "imageUrl": "http://example.com/image.jpg"
                        facebookMessage.attachment = {type: "image"};
                        facebookMessage.attachment.payload = {url: message.imageUrl};

                        facebookMessages.push(facebookMessage);
                    }

                    break;
                //message.type 4 means custom payload message
                case 4:
                    if (message.payload && message.payload.facebook) {
                        facebookMessages.push(message.payload.facebook);
                    }
                    break;

                default:
                    break;
            }
        }

        return new Promise((resolve, reject) => {
            async.eachSeries(facebookMessages, (msg, callback) => {
                    this.sendFBSenderAction(sender, "typing_on")
                        .then(() => this.sleep(this.messagesDelay))
                        .then(() => this.sendFBMessage(sender, msg))
                        .then(() => callback())
                        .catch(callback);
                },
                (err) => {
                    if (err) {
                        console.error(err);
                        reject(err);
                    } else {
                        console.log('Messages sent');
                        resolve();
                    }
                });
        });

    }

    doTextResponse(sender, responseText) {
        console.log('Response as text message');
        // facebook API limit for text length is 640,
        // so we must split message if needed
        let splittedText = this.splitResponse(responseText);

        async.eachSeries(splittedText, (textPart, callback) => {
            this.sendFBMessage(sender, {text: textPart})
                .then(() => callback())
                .catch(err => callback(err));
        });
    }

    //which webhook event
    getEventText(event) {
        if (event.message) {
            if (event.message.quick_reply && event.message.quick_reply.payload) {
                return event.message.quick_reply.payload;
            }

            if (event.message.text) {
                return event.message.text;
            }
        }

        if (event.postback && event.postback.payload) {
            return event.postback.payload;
        }

        return null;

    }

    getFacebookEvent(event) {
        if (event.postback && event.postback.payload) {

            let payload = event.postback.payload;

            switch (payload) {
                case FACEBOOK_WELCOME:
                    return {name: FACEBOOK_WELCOME};

                case FACEBOOK_LOCATION:
                    return {name: FACEBOOK_LOCATION, data: event.postback.data}
            }
        }

        return null;
    }

    processFacebookEvent(event) {
        const sender = event.sender.id.toString();
        const eventObject = this.getFacebookEvent(event);

        if (eventObject) {

            // Handle a text message from this sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            let apiaiRequest = this.apiAiService.eventRequest(eventObject,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });
            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    processMessageEvent(event) {
        const sender = event.sender.id.toString();
        const text = this.getEventText(event);

        if (text) {

            // Handle a text message from this sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            console.log("Text", text);
            //send user's text to api.ai service
            // let apiaiRequest = this.apiAiService.textRequest(text,
            //     {
            //         sessionId: this.sessionIds.get(sender),
            //         originalRequest: {
            //             data: event,
            //             source: "facebook"
            //         }
            //     });

            let apiaiRequest = apiAiService.textRequest(text,
            {
                sessionId: sessionIds.get(sender),
                contexts: [
                    {
                        name: "generic",
                        parameters: {
                            facebook_user: userName
                        }
                    }
                ]
            });

            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    doApiAiRequest(apiaiRequest, sender) {
        apiaiRequest = apiAiService.textRequest(text,
        {
            sessionId: sessionIds.get(sender),
            contexts: [
            {
                name: "generic",
                parameters: {
                    facebook_user: userName
                }
            }
        });
        apiaiRequest.on('response', (response) => {
            if (this.isDefined(response.result) && this.isDefined(response.result.fulfillment)) {
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let responseMessages = response.result.fulfillment.messages;

                if (this.isDefined(responseData) && this.isDefined(responseData.facebook)) {
                    let facebookResponseData = responseData.facebook;
                    this.doDataResponse(sender, facebookResponseData);
                } else if (this.isDefined(responseMessages) && responseMessages.length > 0) {
                    this.doRichContentResponse(sender, responseMessages);
                }
                else if (this.isDefined(responseText)) {
                    this.doTextResponse(sender, responseText);
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }

    splitResponse(str) {
        if (str.length <= FB_TEXT_LIMIT) {
            return [str];
        }

        return this.chunkString(str, FB_TEXT_LIMIT);
    }

    chunkString(s, len) {
        let curr = len, prev = 0;

        let output = [];

        while (s[curr]) {
            if (s[curr++] == ' ') {
                output.push(s.substring(prev, curr));
                prev = curr;
                curr += len;
            }
            else {
                let currReverse = curr;
                do {
                    if (s.substring(currReverse - 1, currReverse) == ' ') {
                        output.push(s.substring(prev, currReverse));
                        prev = currReverse;
                        curr = currReverse + len;
                        break;
                    }
                    currReverse--;
                } while (currReverse > prev)
            }
        }
        output.push(s.substr(prev));
        return output;
    }

    sendFBMessage(sender, messageData) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    message: messageData
                }
            }, (error, response) => {
                if (error) {
                    console.log('Error sending message: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.log('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    sendFBSenderAction(sender, action) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    sender_action: action
                }
            }, (error, response) => {
                if (error) {
                    console.error('Error sending action: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.error('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    doSubscribeRequest() {
        request({
                method: 'POST',
                uri: `https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=${FB_PAGE_ACCESS_TOKEN}`
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription: ', error);
                } else {
                    console.log('Subscription result: ', response.body);
                }
            });
    }

    configureGetStartedEvent() {
        request({
                method: 'POST',
                uri: `https://graph.facebook.com/v2.6/me/thread_settings?access_token=${FB_PAGE_ACCESS_TOKEN}`,
                json: {
                    setting_type: "call_to_actions",
                    thread_state: "new_thread",
                    call_to_actions: [
                        {
                            payload: FACEBOOK_WELCOME
                        }
                    ]
                }
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription', error);
                } else {
                    console.log('Subscription result', response.body);
                }
            });
    }

    isDefined(obj) {
        if (typeof obj == 'undefined') {
            return false;
        }

        if (!obj) {
            return false;
        }

        return obj != null;
    }

    sleep(delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(), delay);
        });
    }

}

let facebookBot = new FacebookBot();

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] === FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            facebookBot.doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        const data = JSONbig.parse(req.body);

        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        if (event.message && !event.message.is_echo) {

                            if (event.message.attachments) {
                                let locations = event.message.attachments.filter(a => a.type === "location");

                                // delete all locations from original message
                                event.message.attachments = event.message.attachments.filter(a => a.type !== "location");

                                if (locations.length > 0) {
                                    locations.forEach(l => {
                                        let locationEvent = {
                                            sender: event.sender,
                                            postback: {
                                                payload: "FACEBOOK_LOCATION",
                                                data: l.payload.coordinates
                                            }
                                        };

                                        facebookBot.processFacebookEvent(locationEvent);
                                    });
                                }
                            }

                            facebookBot.processMessageEvent(event);
                        } else if (event.postback && event.postback.payload) {
                            if (event.postback.payload === "FACEBOOK_WELCOME") {
                                facebookBot.processFacebookEvent(event);
                            } else {
                                facebookBot.processMessageEvent(event);
                            }
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

facebookBot.doSubscribeRequest();
<commit_msg>CAMBIOS 2<commit_after>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;
const FB_TEXT_LIMIT = 640;

const FACEBOOK_LOCATION = "FACEBOOK_LOCATION";
const FACEBOOK_WELCOME = "FACEBOOK_WELCOME";

class FacebookBot {
    constructor() {
        this.apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
        this.sessionIds = new Map();
        this.messagesDelay = 200;
    }

    doDataResponse(sender, facebookResponseData) {
        if (!Array.isArray(facebookResponseData)) {
            console.log('Response as formatted message');
            this.sendFBMessage(sender, facebookResponseData)
                .catch(err => console.error(err));
        } else {
            async.eachSeries(facebookResponseData, (facebookMessage, callback) => {
                if (facebookMessage.sender_action) {
                    console.log('Response as sender action');
                    this.sendFBSenderAction(sender, facebookMessage.sender_action)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
                else {
                    console.log('Response as formatted message');
                    this.sendFBMessage(sender, facebookMessage)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
            }, (err) => {
                if (err) {
                    console.error(err);
                } else {
                    console.log('Data response completed');
                }
            });
        }
    }

    doRichContentResponse(sender, messages) {
        let facebookMessages = []; // array with result messages

        for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
            let message = messages[messageIndex];

            switch (message.type) {
                //message.type 0 means text message
                case 0:
                    // speech: ["hi"]
                    // we have to get value from fulfillment.speech, because of here is raw speech
                    if (message.speech) {

                        let splittedText = this.splitResponse(message.speech);

                        splittedText.forEach(s => {
                            facebookMessages.push({text: s});
                        });
                    }

                    break;
                //message.type 1 means card message
                case 1: {
                    let carousel = [message];

                    for (messageIndex++; messageIndex < messages.length; messageIndex++) {
                        if (messages[messageIndex].type == 1) {
                            carousel.push(messages[messageIndex]);
                        } else {
                            messageIndex--;
                            break;
                        }
                    }

                    let facebookMessage = {};
                    carousel.forEach((c) => {
                        // buttons: [ {text: "hi", postback: "postback"} ], imageUrl: "", title: "", subtitle: ""

                        let card = {};

                        card.title = c.title;
                        card.image_url = c.imageUrl;
                        if (this.isDefined(c.subtitle)) {
                            card.subtitle = c.subtitle;
                        }
                        //If button is involved in.
                        if (c.buttons.length > 0) {
                            let buttons = [];
                            for (let buttonIndex = 0; buttonIndex < c.buttons.length; buttonIndex++) {
                                let button = c.buttons[buttonIndex];

                                if (button.text) {
                                    let postback = button.postback;
                                    if (!postback) {
                                        postback = button.text;
                                    }

                                    let buttonDescription = {
                                        title: button.text
                                    };

                                    if (postback.startsWith("http")) {
                                        buttonDescription.type = "web_url";
                                        buttonDescription.url = postback;
                                    } else {
                                        buttonDescription.type = "postback";
                                        buttonDescription.payload = postback;
                                    }

                                    buttons.push(buttonDescription);
                                }
                            }

                            if (buttons.length > 0) {
                                card.buttons = buttons;
                            }
                        }

                        if (!facebookMessage.attachment) {
                            facebookMessage.attachment = {type: "template"};
                        }

                        if (!facebookMessage.attachment.payload) {
                            facebookMessage.attachment.payload = {template_type: "generic", elements: []};
                        }

                        facebookMessage.attachment.payload.elements.push(card);
                    });

                    facebookMessages.push(facebookMessage);
                }

                    break;
                //message.type 2 means quick replies message
                case 2: {
                    if (message.replies && message.replies.length > 0) {
                        let facebookMessage = {};

                        facebookMessage.text = message.title ? message.title : 'Choose an item';
                        facebookMessage.quick_replies = [];

                        message.replies.forEach((r) => {
                            facebookMessage.quick_replies.push({
                                content_type: "text",
                                title: r,
                                payload: r
                            });
                        });

                        facebookMessages.push(facebookMessage);
                    }
                }

                    break;
                //message.type 3 means image message
                case 3:

                    if (message.imageUrl) {
                        let facebookMessage = {};

                        // "imageUrl": "http://example.com/image.jpg"
                        facebookMessage.attachment = {type: "image"};
                        facebookMessage.attachment.payload = {url: message.imageUrl};

                        facebookMessages.push(facebookMessage);
                    }

                    break;
                //message.type 4 means custom payload message
                case 4:
                    if (message.payload && message.payload.facebook) {
                        facebookMessages.push(message.payload.facebook);
                    }
                    break;

                default:
                    break;
            }
        }

        return new Promise((resolve, reject) => {
            async.eachSeries(facebookMessages, (msg, callback) => {
                    this.sendFBSenderAction(sender, "typing_on")
                        .then(() => this.sleep(this.messagesDelay))
                        .then(() => this.sendFBMessage(sender, msg))
                        .then(() => callback())
                        .catch(callback);
                },
                (err) => {
                    if (err) {
                        console.error(err);
                        reject(err);
                    } else {
                        console.log('Messages sent');
                        resolve();
                    }
                });
        });

    }

    doTextResponse(sender, responseText) {
        console.log('Response as text message');
        // facebook API limit for text length is 640,
        // so we must split message if needed
        let splittedText = this.splitResponse(responseText);

        async.eachSeries(splittedText, (textPart, callback) => {
            this.sendFBMessage(sender, {text: textPart})
                .then(() => callback())
                .catch(err => callback(err));
        });
    }

    //which webhook event
    getEventText(event) {
        if (event.message) {
            if (event.message.quick_reply && event.message.quick_reply.payload) {
                return event.message.quick_reply.payload;
            }

            if (event.message.text) {
                return event.message.text;
            }
        }

        if (event.postback && event.postback.payload) {
            return event.postback.payload;
        }

        return null;

    }

    getFacebookEvent(event) {
        if (event.postback && event.postback.payload) {

            let payload = event.postback.payload;

            switch (payload) {
                case FACEBOOK_WELCOME:
                    return {name: FACEBOOK_WELCOME};

                case FACEBOOK_LOCATION:
                    return {name: FACEBOOK_LOCATION, data: event.postback.data}
            }
        }

        return null;
    }

    processFacebookEvent(event) {
        const sender = event.sender.id.toString();
        const eventObject = this.getFacebookEvent(event);

        if (eventObject) {

            // Handle a text message from this sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            let apiaiRequest = this.apiAiService.eventRequest(eventObject,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });
            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    processMessageEvent(event) {
        const sender = event.sender.id.toString();
        const text = this.getEventText(event);

        if (text) {

            // Handle a text message from this sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            console.log("Text", text);
            send user's text to api.ai service
            let apiaiRequest = this.apiAiService.textRequest(text,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });

            let apiaiRequest = apiAiService.textRequest(text,
            {
                sessionId: sessionIds.get(sender),
                contexts: [
                    {
                        name: "generic",
                        parameters: {
                            facebook_user: userName
                        }
                    }
                ]
            });

            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    doApiAiRequest(apiaiRequest, sender) {
        apiaiRequest = apiAiService.textRequest(text,
        {
            sessionId: sessionIds.get(sender),
            contexts: [
            {
                name: "generic",
                parameters: {
                    facebook_user: userName
                }
            }
        });
        apiaiRequest.on('response', (response) => {
            if (this.isDefined(response.result) && this.isDefined(response.result.fulfillment)) {
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let responseMessages = response.result.fulfillment.messages;

                if (this.isDefined(responseData) && this.isDefined(responseData.facebook)) {
                    let facebookResponseData = responseData.facebook;
                    this.doDataResponse(sender, facebookResponseData);
                } else if (this.isDefined(responseMessages) && responseMessages.length > 0) {
                    this.doRichContentResponse(sender, responseMessages);
                }
                else if (this.isDefined(responseText)) {
                    this.doTextResponse(sender, responseText);
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }

    splitResponse(str) {
        if (str.length <= FB_TEXT_LIMIT) {
            return [str];
        }

        return this.chunkString(str, FB_TEXT_LIMIT);
    }

    chunkString(s, len) {
        let curr = len, prev = 0;

        let output = [];

        while (s[curr]) {
            if (s[curr++] == ' ') {
                output.push(s.substring(prev, curr));
                prev = curr;
                curr += len;
            }
            else {
                let currReverse = curr;
                do {
                    if (s.substring(currReverse - 1, currReverse) == ' ') {
                        output.push(s.substring(prev, currReverse));
                        prev = currReverse;
                        curr = currReverse + len;
                        break;
                    }
                    currReverse--;
                } while (currReverse > prev)
            }
        }
        output.push(s.substr(prev));
        return output;
    }

    sendFBMessage(sender, messageData) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    message: messageData
                }
            }, (error, response) => {
                if (error) {
                    console.log('Error sending message: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.log('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    sendFBSenderAction(sender, action) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    sender_action: action
                }
            }, (error, response) => {
                if (error) {
                    console.error('Error sending action: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.error('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    doSubscribeRequest() {
        request({
                method: 'POST',
                uri: `https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=${FB_PAGE_ACCESS_TOKEN}`
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription: ', error);
                } else {
                    console.log('Subscription result: ', response.body);
                }
            });
    }

    configureGetStartedEvent() {
        request({
                method: 'POST',
                uri: `https://graph.facebook.com/v2.6/me/thread_settings?access_token=${FB_PAGE_ACCESS_TOKEN}`,
                json: {
                    setting_type: "call_to_actions",
                    thread_state: "new_thread",
                    call_to_actions: [
                        {
                            payload: FACEBOOK_WELCOME
                        }
                    ]
                }
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription', error);
                } else {
                    console.log('Subscription result', response.body);
                }
            });
    }

    isDefined(obj) {
        if (typeof obj == 'undefined') {
            return false;
        }

        if (!obj) {
            return false;
        }

        return obj != null;
    }

    sleep(delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(), delay);
        });
    }

}

let facebookBot = new FacebookBot();

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] === FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            facebookBot.doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        const data = JSONbig.parse(req.body);

        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        if (event.message && !event.message.is_echo) {

                            if (event.message.attachments) {
                                let locations = event.message.attachments.filter(a => a.type === "location");

                                // delete all locations from original message
                                event.message.attachments = event.message.attachments.filter(a => a.type !== "location");

                                if (locations.length > 0) {
                                    locations.forEach(l => {
                                        let locationEvent = {
                                            sender: event.sender,
                                            postback: {
                                                payload: "FACEBOOK_LOCATION",
                                                data: l.payload.coordinates
                                            }
                                        };

                                        facebookBot.processFacebookEvent(locationEvent);
                                    });
                                }
                            }

                            facebookBot.processMessageEvent(event);
                        } else if (event.postback && event.postback.payload) {
                            if (event.postback.payload === "FACEBOOK_WELCOME") {
                                facebookBot.processFacebookEvent(event);
                            } else {
                                facebookBot.processMessageEvent(event);
                            }
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

facebookBot.doSubscribeRequest();
<|endoftext|>


<commit_before>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;

const apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
const sessionIds = new Map();

function processEvent(event) {
    var sender = event.sender.id.toString();

    if ((event.message && event.message.text) || (event.postback && event.postback.payload)) {
        var text = event.message ? event.message.text : event.postback.payload;
        // Handle a text message from this sender

        if (!sessionIds.has(sender)) {
            sessionIds.set(sender, uuid.v1());
        }

        console.log("Text", text);

        let apiaiRequest = apiAiService.textRequest(text,
            {
                sessionId: sessionIds.get(sender)
            });

        apiaiRequest.on('response', (response) => {
            if (isDefined(response.result)) {
                //asd
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let action = response.result.action;

                if (isDefined(responseData) && isDefined(responseData.facebook)) {
                    if (!Array.isArray(responseData.facebook)) {
                        try {
                            console.log('Response as formatted message');
                            sendFBMessage(sender, responseData.facebook);
                        } catch (err) {
                            sendFBMessage(sender, {text: err.message});
                        }
                    } else {
                        responseData.facebook.forEach((facebookMessage) => {
                            try {
                                if (facebookMessage.sender_action) {
                                    console.log('Response as sender action');
                                    sendFBSenderAction(sender, facebookMessage.sender_action);
                                }
                                else {
                                    console.log('Response as formatted message');
                                    sendFBMessage(sender, facebookMessage);
                                }
                            } catch (err) {
                                sendFBMessage(sender, {text: err.message});
                            }
                        });
                    }
                //} else if (isDefined(responseText)) {
                } else if (isDefined(action) && isDefined(responseText)) {
                    console.log('Response as text message');
                    // facebook API limit for text length is 320,
                    // so we must split message if needed
                    var splittedText = splitResponse(responseText);
                    switch (action){
                        case 'show_prod':
                            console.log('generic');
                            async.eachSeries(splittedText, (textPart, callback) => {
                                sendFBMessage(sender, {text: textPart}, callback);
                            });
                            const generic_message = {
                                attachment: {
                                    type: "template",
                                    payload: {
                                        template_type: "generic",
                                        elements: [{
                                            title: "Margarita",
                                            subtitle: "Be carefull and don't eat your fingers!!!",
                                            item_url: "https://en.wikipedia.org/wiki/Pizza",
                                            image_url: "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg",
                                            buttons: [{
                                                type: "web_url",
                                                url: "https://en.wikipedia.org/wiki/Pizza",
                                                title: "Open Web URL"
                                            }/*, {
                                             type: "postback",
                                             title: "Call Postback",
                                             payload: "Payload for first bubble"
                                             }*/]
                                        }]
                                    }
                                }
                            };
                            sendFBMessage(sender, generic_message, null);
                            break;
                        default:
                            console.log('default');
                            async.eachSeries(splittedText, (textPart, callback) => {
                                sendFBMessage(sender, {text: textPart}, callback);
                            });
                    }
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }
}

function splitResponse(str) {
    if (str.length <= 320) {
        return [str];
    }

    return chunkString(str, 300);
}

function chunkString(s, len) {
    var curr = len, prev = 0;

    var output = [];

    while (s[curr]) {
        if (s[curr++] == ' ') {
            output.push(s.substring(prev, curr));
            prev = curr;
            curr += len;
        }
        else {
            var currReverse = curr;
            do {
                if (s.substring(currReverse - 1, currReverse) == ' ') {
                    output.push(s.substring(prev, currReverse));
                    prev = currReverse;
                    curr = currReverse + len;
                    break;
                }
                currReverse--;
            } while (currReverse > prev)
        }
    }
    output.push(s.substr(prev));
    //console.log(output);
    //output = ['Length of param message[text] must be less than or equal to 320'];
    return output;
}

const generic_message = {
    attachment: {
        type: "template",
        payload: {
            template_type: "generic",
            elements: [{
                title: "Margarita",
                subtitle: "Be carefull and don't eat your fingers!!!",
                item_url: "https://en.wikipedia.org/wiki/Pizza",
                image_url: "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg",
                buttons: [{
                    type: "web_url",
                    url: "https://en.wikipedia.org/wiki/Pizza",
                    title: "Open Web URL"
                }/*, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for first bubble"
                }*/]
            }
    /*attachment: {
        type: "template",
        payload: {
            template_type: "generic",
            elements: [{
                title: "rift",
                subtitle: "Next-generation virtual reality",
                item_url: "https://www.oculus.com/en-us/rift/",
                image_url: "http://messengerdemo.parseapp.com/img/rift.png",
                buttons: [{
                    type: "web_url",
                    url: "https://www.oculus.com/en-us/rift/",
                    title: "Open Web URL"
                }, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for first bubble"
                }]
            }, {
                title: "touch",
                subtitle: "Your Hands, Now in VR",
                item_url: "https://www.oculus.com/en-us/touch/",
                image_url: "http://messengerdemo.parseapp.com/img/touch.png",
                buttons: [{
                    type: "web_url",
                    url: "https://www.oculus.com/en-us/touch/",
                    title: "Open Web URL"
                }, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for second bubble"
                }]
            }*/]
        }
    }
};

function sendFBMessage(sender, messageData, callback) {
    //console.log(sender);
    request({
        url: 'https://graph.facebook.com/v2.6/me/messages',
        qs: {access_token: FB_PAGE_ACCESS_TOKEN},
        method: 'POST',
        json: {
            recipient: {id: sender},
            message: messageData
            //message: generic_message
        }
    }, (error, response, body) => {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }

        if (callback) {
            callback();
        }
    });
}

function sendFBSenderAction(sender, action, callback) {
    setTimeout(() => {
        request({
            url: 'https://graph.facebook.com/v2.6/me/messages',
            qs: {access_token: FB_PAGE_ACCESS_TOKEN},
            method: 'POST',
            json: {
                recipient: {id: sender},
                sender_action: action
            }
        }, (error, response, body) => {
            if (error) {
                console.log('Error sending action: ', error);
            } else if (response.body.error) {
                console.log('Error: ', response.body.error);
            }
            if (callback) {
                callback();
            }
        });
    }, 1000);
}

function doSubscribeRequest() {
    request({
            method: 'POST',
            uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
        },
        (error, response, body) => {
            if (error) {
                console.error('Error while subscription: ', error);
            } else {
                console.log('Subscription result: ', response.body);
            }
        });
}

function isDefined(obj) {
    if (typeof obj == 'undefined') {
        return false;
    }

    if (!obj) {
        return false;
    }

    return obj != null;
}

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        var data = JSONbig.parse(req.body);

        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        if (event.message && !event.message.is_echo ||
                            event.postback && event.postback.payload) {
                            processEvent(event);
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

doSubscribeRequest();
<commit_msg>'aaa'<commit_after>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;

const apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
const sessionIds = new Map();

function processEvent(event) {
    var sender = event.sender.id.toString();

    if ((event.message && event.message.text) || (event.postback && event.postback.payload)) {
        var text = event.message ? event.message.text : event.postback.payload;
        // Handle a text message from this sender

        if (!sessionIds.has(sender)) {
            sessionIds.set(sender, uuid.v1());
        }

        console.log("Text", text);

        let apiaiRequest = apiAiService.textRequest(text,
            {
                sessionId: sessionIds.get(sender)
            });

        apiaiRequest.on('response', (response) => {
            if (isDefined(response.result)) {
                //asd
                let responseText = response.result.fulfillment.speech;
                let responseData = response.result.fulfillment.data;
                let action = response.result.action;

                if (isDefined(responseData) && isDefined(responseData.facebook)) {
                    if (!Array.isArray(responseData.facebook)) {
                        try {
                            console.log('Response as formatted message');
                            sendFBMessage(sender, responseData.facebook);
                        } catch (err) {
                            sendFBMessage(sender, {text: err.message});
                        }
                    } else {
                        responseData.facebook.forEach((facebookMessage) => {
                            try {
                                if (facebookMessage.sender_action) {
                                    console.log('Response as sender action');
                                    sendFBSenderAction(sender, facebookMessage.sender_action);
                                }
                                else {
                                    console.log('Response as formatted message');
                                    sendFBMessage(sender, facebookMessage);
                                }
                            } catch (err) {
                                sendFBMessage(sender, {text: err.message});
                            }
                        });
                    }
                //} else if (isDefined(responseText)) {
                } else if (isDefined(action) && isDefined(responseText)) {
                    console.log('Response as text message');
                    // facebook API limit for text length is 320,
                    // so we must split message if needed
                    var splittedText = splitResponse(responseText);
                    switch (action){
                        case 'show_prod':
                            console.log('generic');
                            async.eachSeries(splittedText, (textPart, callback) => {
                                sendFBMessage(sender, {text: textPart}, callback);
                            });
                            const generic_message = {
                                attachment: {
                                    type: "template",
                                    payload: {
                                        template_type: "generic",
                                        elements: [{
                                            title: "Margarita",
                                            subtitle: "Be carefull and don't eat your fingers!!!",
                                            item_url: "https://en.wikipedia.org/wiki/Pizza",
                                            image_url: "http://www.cbc.ca/inthekitchen/assets_c/2012/11/MargheritaPizza21-thumb-596x350-247022.jpg",
                                            buttons: [{
                                                type: "web_url",
                                                url: "https://en.wikipedia.org/wiki/Pizza",
                                                title: "Open Web URL"
                                            }/*, {
                                             type: "postback",
                                             title: "Call Postback",
                                             payload: "Payload for first bubble"
                                             }*/]
                                        }]
                                    }
                                }
                            };
                            sendFBMessage(sender, generic_message, null);
                            break;
                        default:
                            console.log('default');
                            async.eachSeries(splittedText, (textPart, callback) => {
                                sendFBMessage(sender, {text: textPart}, callback);
                            });
                    }
                }

            }
        });

        apiaiRequest.on('error', (error) => console.error(error));
        apiaiRequest.end();
    }
}

function splitResponse(str) {
    if (str.length <= 320) {
        return [str];
    }

    return chunkString(str, 300);
}

function chunkString(s, len) {
    var curr = len, prev = 0;

    var output = [];

    while (s[curr]) {
        if (s[curr++] == ' ') {
            output.push(s.substring(prev, curr));
            prev = curr;
            curr += len;
        }
        else {
            var currReverse = curr;
            do {
                if (s.substring(currReverse - 1, currReverse) == ' ') {
                    output.push(s.substring(prev, currReverse));
                    prev = currReverse;
                    curr = currReverse + len;
                    break;
                }
                currReverse--;
            } while (currReverse > prev)
        }
    }
    output.push(s.substr(prev));
    //console.log(output);
    //output = ['Length of param message[text] must be less than or equal to 320'];
    return output;
}

/*const generic_message = {
    attachment: {
        type: "template",
        payload: {
            template_type: "generic",
            elements: [{
                title: "rift",
                subtitle: "Next-generation virtual reality",
                item_url: "https://www.oculus.com/en-us/rift/",
                image_url: "http://messengerdemo.parseapp.com/img/rift.png",
                buttons: [{
                    type: "web_url",
                    url: "https://www.oculus.com/en-us/rift/",
                    title: "Open Web URL"
                }, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for first bubble"
                }]
            }, {
                title: "touch",
                subtitle: "Your Hands, Now in VR",
                item_url: "https://www.oculus.com/en-us/touch/",
                image_url: "http://messengerdemo.parseapp.com/img/touch.png",
                buttons: [{
                    type: "web_url",
                    url: "https://www.oculus.com/en-us/touch/",
                    title: "Open Web URL"
                }, {
                    type: "postback",
                    title: "Call Postback",
                    payload: "Payload for second bubble"
                }]
            }]
        }
    }
};*/

function sendFBMessage(sender, messageData, callback) {
    //console.log(sender);
    request({
        url: 'https://graph.facebook.com/v2.6/me/messages',
        qs: {access_token: FB_PAGE_ACCESS_TOKEN},
        method: 'POST',
        json: {
            recipient: {id: sender},
            message: messageData
            //message: generic_message
        }
    }, (error, response, body) => {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }

        if (callback) {
            callback();
        }
    });
}

function sendFBSenderAction(sender, action, callback) {
    setTimeout(() => {
        request({
            url: 'https://graph.facebook.com/v2.6/me/messages',
            qs: {access_token: FB_PAGE_ACCESS_TOKEN},
            method: 'POST',
            json: {
                recipient: {id: sender},
                sender_action: action
            }
        }, (error, response, body) => {
            if (error) {
                console.log('Error sending action: ', error);
            } else if (response.body.error) {
                console.log('Error: ', response.body.error);
            }
            if (callback) {
                callback();
            }
        });
    }, 1000);
}

function doSubscribeRequest() {
    request({
            method: 'POST',
            uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
        },
        (error, response, body) => {
            if (error) {
                console.error('Error while subscription: ', error);
            } else {
                console.log('Subscription result: ', response.body);
            }
        });
}

function isDefined(obj) {
    if (typeof obj == 'undefined') {
        return false;
    }

    if (!obj) {
        return false;
    }

    return obj != null;
}

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        var data = JSONbig.parse(req.body);

        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        if (event.message && !event.message.is_echo ||
                            event.postback && event.postback.payload) {
                            processEvent(event);
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

doSubscribeRequest();
<|endoftext|>