<commit_before>/*
Copyright (c) 2014, Geomatics and Cartographic Research Centre, Carleton
University
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 - Neither the name of the Geomatics and Cartographic Research Centre,
   Carleton University nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

;(function($n2) {
"use strict";

var
 _loc = function(str,args){ return $n2.loc(str,'nunaliit2',args); }
 ,DH = 'n2.svg'
 ,XMLNS = "http://www.w3.org/2000/svg"
 ;

//--------------------------------------------------------------------------
// Thanks to OpenLayers 2
var PredefinedGraphicsByName = {
     "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
             303,215, 231,161, 321,161, 350,75],
     "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
             4,0],
     "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
     "square": [0,0, 0,1, 1,1, 1,0, 0,0],
     "triangle": [0,10, 10,10, 5,0, 0,10]
 };

//--------------------------------------------------------------------------
// List of SVG presentation attributes served in a map for fast access
var presentationAttributeMap = {
	'alignment-baseline': true
	,'baseline-shift': true
	,'clip': true
	,'clip-path': true
	,'clip-rule': true
	,'color': true
	,'color-interpolation': true
	,'color-interpolation-filters': true
	,'color-profile': true
	,'color-rendering': true
	,'cursor': true
	,'direction': true
	,'display': true
	,'dominant-baseline': true
	,'enable-background': true
	,'fill': true
	,'fill-opacity': true
	,'fill-rule': true
	,'filter': true
	,'flood-color': true
	,'flood-opacity': true
	,'font-family': true
	,'font-size': true
	,'font-size-adjust': true
	,'font-stretch': true
	,'font-style': true
	,'font-variant': true
	,'font-weight': true
	,'glyph-orientation-horizontal': true
	,'glyph-orientation-vertical': true
	,'image-rendering': true
	,'kerning': true
	,'letter-spacing': true
	,'lighting-color': true
	,'marker-end': true
	,'marker-mid': true
	,'marker-start': true
	,'mask': true
	,'opacity': true
	,'overflow': true
	,'pointer-events': true
	,'shape-rendering': true
	,'stop-color': true
	,'stop-opacity': true
	,'stroke': true
	,'stroke-dasharray': true
	,'stroke-dashoffset': true
	,'stroke-linecap': true
	,'stroke-linejoin': true
	,'stroke-miterlimit': true
	,'stroke-opacity': true
	,'stroke-width': true
	,'text-anchor': true
	,'text-decoration': true
	,'text-rendering': true
	,'unicode-bidi': true
	,'visibility': true
	,'word-spacing': true
	,'writing-mode': true
};

//--------------------------------------------------------------------------
var Renderer = $n2.Class({

	svgElemId: null,

	initialize: function(opts_){
		var opts = $n2.extend({
			svgElem: null
			,svgElemId: null
		},opts_);

		this.svgElemId = opts.svgElemId;
		if( !this.svgElemId && opts.svgElem ){
			this.svgElemId = $n2.utils.getElementIdentifier(opts.svgElem);
		};
		if( !this.svgElemId ){
			throw 'SVG element must be provided';
		};
	},

	_getSvgElem: function(){
		return document.getElementById(this.svgElemId);
	},

	_getDefsElem: function(){
		var defsElem = null;

		var svgElem = this._getSvgElem();
		var childNodeList = svgElem.childNodes;
		for(var i=0,e=childNodeList.length; i<e; ++i){
			var child = childNodeList.item(i);
			if( 'defs' === child.localName.toLowerCase() ){
				defsElem = child;
			};
		};

		if( null === defsElem ){
			defsElem = this._createNode('defs');
			svgElem.appendChild(defsElem);
		};

		return defsElem;
	},

    _importGraphic: function (graphicName)  {
    	var defsElem = this._getDefsElem();

        var graphicId = this.svgElemId + "-grpahic-" + graphicName;

        // Try to get by id
        var graphicNode = document.getElementById(graphicId);
        if( !graphicNode ) {
            var graphic = PredefinedGraphicsByName[graphicName];
            if (!graphic) {
                throw ('' + graphicName + ' is not a valid graphic name');
            }

            var graphicNode = this._createNode('symbol',graphicId);
            var node = this._createNode('polygon');
            graphicNode.appendChild(node);
            var maxx = null
             ,minx = null
             ,maxy = null
             ,miny = null
             ,points = []
             ,x,y;

            for (var i=0; i<graphic.length; i=i+2) {
                x = graphic[i];
                y = graphic[i+1];

                minx = (minx == null) ? x : Math.min(minx, x);
                maxx = (maxx == null) ? x : Math.max(maxx, x);
                miny = (miny == null) ? y : Math.min(miny, y);
                maxy = (maxy == null) ? y : Math.max(maxy, y);
            }

            var width = maxx - minx;
            var height = maxy - miny;
            var longestDim = width > height ? width : height;
            var factor = 2 / longestDim;
            var midX = (minx + maxx) / 2;
            var midY = (miny + maxy) / 2;

            for (var i=0; i<graphic.length; i=i+2) {
                x = (graphic[i] - midX) * factor;
                y = (graphic[i+1] - midY) * factor;

                if( i != 0 ){
                	points.push(' ');
                };
                points.push(x, ',', y);
            }

            node.setAttributeNS(null, 'points', points.join(''));

            graphicNode.setAttributeNS(null, 'viewBox', '-3 -3 6 6');

            defsElem.appendChild(graphicNode);
        };

        return graphicNode;
    },

	_createNode: function(name, id){
        var node = document.createElementNS(XMLNS, name);
        if (id) {
            node.setAttributeNS(null, "id", id);
        }
        return node;
	}
});

var svgNs = 'http://www.w3.org/2000/svg';
var xlinkNs = 'http://www.w3.org/1999/xlink';

function createSVGNode(type, id) {
    var node = null;
    if( document.createElementNS ) {
        node = document.createElementNS(svgNs, type);
        if (id) {
            node.setAttributeNS(null, 'id', id);
        };
    };
    return node;
};

function setAttr(node, name, value) {
	node.setAttributeNS(null, name, value);
};

function setAttrNS(node, ns, name, value) {
	node.setAttributeNS(ns, name, value);
};

function addClass(elem, className) {
	var classNames = [];

	if( elem.className ){
		var currentClasses = '' + elem.className;
		classNames = currentClasses.split(' ');
	};

	classNames.push(className);

	elem.className = classNames.join(' ');
};

//--------------------------------------------------------------------------
$n2.svg = {
	Renderer: Renderer
	,svgNs: svgNs
	,xlinkNs: xlinkNs
	,createSVGNode: createSVGNode
	,setAttr: setAttr
	,setAttrNS: setAttrNS
	,addClass: addClass
	,presentationAttributeMap: presentationAttributeMap
};

})(nunaliit2);
<commit_msg>nunaliit2-js: Start support for SVG merkers<commit_after>/*
Copyright (c) 2014, Geomatics and Cartographic Research Centre, Carleton
University
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 - Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 - Neither the name of the Geomatics and Cartographic Research Centre,
   Carleton University nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

;(function($n2) {
"use strict";

var
 _loc = function(str,args){ return $n2.loc(str,'nunaliit2',args); }
 ,DH = 'n2.svg'
 ,XMLNS = "http://www.w3.org/2000/svg"
 ;

//--------------------------------------------------------------------------
// Thanks to OpenLayers 2
var PredefinedGraphicsByName = {
     "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
             303,215, 231,161, 321,161, 350,75],
     "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
             4,0],
     "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
     "square": [0,0, 0,1, 1,1, 1,0, 0,0],
     "triangle": [0,10, 10,10, 5,0, 0,10]
 };

//--------------------------------------------------------------------------
var PredefinedMarkersByName = {
	'endArrow': {
		attributes: {
			viewBox: '0 0 10 10'
			,refX: '1'
			,refY: '5'
			,markerWidth: '6'
			,markerHeight: '6'
			,orient: 'auto'
		}
		,path: 'M 0 0 L 10 5 L 0 10 z'
	}
};

//--------------------------------------------------------------------------
// List of SVG presentation attributes served in a map for fast access
var presentationAttributeMap = {
	'alignment-baseline': true
	,'baseline-shift': true
	,'clip': true
	,'clip-path': true
	,'clip-rule': true
	,'color': true
	,'color-interpolation': true
	,'color-interpolation-filters': true
	,'color-profile': true
	,'color-rendering': true
	,'cursor': true
	,'direction': true
	,'display': true
	,'dominant-baseline': true
	,'enable-background': true
	,'fill': true
	,'fill-opacity': true
	,'fill-rule': true
	,'filter': true
	,'flood-color': true
	,'flood-opacity': true
	,'font-family': true
	,'font-size': true
	,'font-size-adjust': true
	,'font-stretch': true
	,'font-style': true
	,'font-variant': true
	,'font-weight': true
	,'glyph-orientation-horizontal': true
	,'glyph-orientation-vertical': true
	,'image-rendering': true
	,'kerning': true
	,'letter-spacing': true
	,'lighting-color': true
	,'marker-end': true
	,'marker-mid': true
	,'marker-start': true
	,'mask': true
	,'opacity': true
	,'overflow': true
	,'pointer-events': true
	,'shape-rendering': true
	,'stop-color': true
	,'stop-opacity': true
	,'stroke': true
	,'stroke-dasharray': true
	,'stroke-dashoffset': true
	,'stroke-linecap': true
	,'stroke-linejoin': true
	,'stroke-miterlimit': true
	,'stroke-opacity': true
	,'stroke-width': true
	,'text-anchor': true
	,'text-decoration': true
	,'text-rendering': true
	,'unicode-bidi': true
	,'visibility': true
	,'word-spacing': true
	,'writing-mode': true
};

//--------------------------------------------------------------------------
var Renderer = $n2.Class({

	svgElemId: null,

	initialize: function(opts_){
		var opts = $n2.extend({
			svgElem: null
			,svgElemId: null
		},opts_);

		this.svgElemId = opts.svgElemId;
		if( !this.svgElemId && opts.svgElem ){
			this.svgElemId = $n2.utils.getElementIdentifier(opts.svgElem);
		};
		if( !this.svgElemId ){
			throw 'SVG element must be provided';
		};
	},

	_getSvgElem: function(){
		return document.getElementById(this.svgElemId);
	},

	_getDefsElem: function(){
		var defsElem = null;

		var svgElem = this._getSvgElem();
		var childNodeList = svgElem.childNodes;
		for(var i=0,e=childNodeList.length; i<e; ++i){
			var child = childNodeList.item(i);
			if( 'defs' === child.localName.toLowerCase() ){
				defsElem = child;
			};
		};

		if( null === defsElem ){
			defsElem = this._createNode('defs');
			svgElem.appendChild(defsElem);
		};

		return defsElem;
	},

    _importGraphic: function (graphicName)  {
    	var defsElem = this._getDefsElem();

        var graphicId = this.svgElemId + "-graphic-" + graphicName;

        // Try to get by id
        var graphicNode = document.getElementById(graphicId);
        if( !graphicNode ) {
            var graphic = PredefinedGraphicsByName[graphicName];
            if (!graphic) {
                throw ('' + graphicName + ' is not a valid graphic name');
            }

            var graphicNode = this._createNode('symbol',graphicId);
            var node = this._createNode('polygon');
            graphicNode.appendChild(node);
            var maxx = null
             ,minx = null
             ,maxy = null
             ,miny = null
             ,points = []
             ,x,y;

            for (var i=0; i<graphic.length; i=i+2) {
                x = graphic[i];
                y = graphic[i+1];

                minx = (minx == null) ? x : Math.min(minx, x);
                maxx = (maxx == null) ? x : Math.max(maxx, x);
                miny = (miny == null) ? y : Math.min(miny, y);
                maxy = (maxy == null) ? y : Math.max(maxy, y);
            }

            var width = maxx - minx;
            var height = maxy - miny;
            var longestDim = width > height ? width : height;
            var factor = 2 / longestDim;
            var midX = (minx + maxx) / 2;
            var midY = (miny + maxy) / 2;

            for (var i=0; i<graphic.length; i=i+2) {
                x = (graphic[i] - midX) * factor;
                y = (graphic[i+1] - midY) * factor;

                if( i != 0 ){
                	points.push(' ');
                };
                points.push(x, ',', y);
            }

            node.setAttributeNS(null, 'points', points.join(''));

            graphicNode.setAttributeNS(null, 'viewBox', '-3 -3 6 6');

            defsElem.appendChild(graphicNode);
        };

        return graphicNode;
    },

    _importMarker: function (markerName)  {
    	var defsElem = this._getDefsElem();

        var markerId = this.svgElemId + "-marker-" + markerName;

        // Try to get by id
        var markerNode = document.getElementById(markerId);
        if( !markerNode ) {
            var marker = PredefinedMarkersByName[markerName];
            if (!marker) {
                throw ('' + markerName + ' is not a valid marker name');
            }

            var markerNode = this._createNode('marker',markerId);
			if( marker.attributes ){
				for(var markerAttributeName in marker.attributes){
					var markerAttributeValue = marker.attributes[markerAttributeName];
					markerNode.setAttributeNS(null, markerAttributeName, markerAttributeValue);
				};
			};

            if( marker.path ){
                var path = this._createNode('path');
    			path.setAttributeNS(null, 'd', marker.path);
                markerNode.appendChild(path);
            };

            defsElem.appendChild(markerNode);
        };

        return markerNode;
    },

	_createNode: function(name, id){
        var node = document.createElementNS(XMLNS, name);
        if (id) {
            node.setAttributeNS(null, "id", id);
        }
        return node;
	}
});

var svgNs = 'http://www.w3.org/2000/svg';
var xlinkNs = 'http://www.w3.org/1999/xlink';

function createSVGNode(type, id) {
    var node = null;
    if( document.createElementNS ) {
        node = document.createElementNS(svgNs, type);
        if (id) {
            node.setAttributeNS(null, 'id', id);
        };
    };
    return node;
};

function setAttr(node, name, value) {
	node.setAttributeNS(null, name, value);
};

function setAttrNS(node, ns, name, value) {
	node.setAttributeNS(ns, name, value);
};

function addClass(elem, className) {
	var classNames = [];

	if( elem.className ){
		var currentClasses = '' + elem.className;
		classNames = currentClasses.split(' ');
	};

	classNames.push(className);

	elem.className = classNames.join(' ');
};

//--------------------------------------------------------------------------
$n2.svg = {
	Renderer: Renderer
	,svgNs: svgNs
	,xlinkNs: xlinkNs
	,createSVGNode: createSVGNode
	,setAttr: setAttr
	,setAttrNS: setAttrNS
	,addClass: addClass
	,presentationAttributeMap: presentationAttributeMap
};

})(nunaliit2);
<|endoftext|>


<commit_before>var UI = require('ui');
var Settings = require('settings');
var Vibe = require('ui/vibe');
var ajax = require('ajax');

var CONFIGURATION_URL = "http://kz.github.io/lifx-pebble-config/index.html";
var API_BASE_URL = "https://api.lifx.com/v1";
var API_KEY;

var groups = [];
var scenes = [];
var lights = [];

/*
 * Initial Loading Screen
 */
var loadingCard = new UI.Card({
    title: 'Please Wait',
    subtitle: 'Loading... (Internet connection required.)'
});
loadingCard.show();

/*
 * Configuration
 */

// Listen for configuration start
Settings.config(
        {
            url: CONFIGURATION_URL,
            autoSave: true
        },
        function (e) {
        },
        function (e) {
            checkConfiguration();
        }
);

// Listen for configuration complete
Settings.config(
        {
            url: CONFIGURATION_URL,
            autoSave: true
        },
        function (e) {
            checkConfiguration();
        }
);

/*
 * Views
 */

var setupCard = new UI.Card({
    title: 'Setup Required',
    body: 'Open this app\'s settings page in the Pebble app and enter your LIFX cloud access token.'
});

var errorCard = new UI.Card({
    title: 'Error',
    body: 'An unknown error has occurred. Please try again.'
});

var mainMenu = new UI.Menu({
    sections: [
        {
            title: 'Quick Actions',
            items: [
                { title: 'Toggle all lights' }
            ]
        },
        {
            title: 'Scenes',
            items: []
        },
        {
            title: 'Groups',
            items: []
        },
        {
            title: 'Lights',
            items: []
        }
    ]
});

/*
 * App Functions
 */

function checkConfiguration() {
    if (typeof Settings.option('apiKey') === 'undefined') {
        setupCard.show();
        mainMenu.hide();
        return false;
    } else {
        API_KEY = Settings.option('apiKey');
        setupCard.hide();
        mainMenu.show();
        return true;
    }
}

function handleError(statusCode) {
    if (statusCode === 401) {
        Settings.option('apiKey', null);
        checkConfiguration();
    } else {
        errorCard.show();
    }
}

/*
 * Internet Enabled Functions
 */
function fetchScenes() {
    loadingCard.show();

    scenes = [];

    ajax(
        {
            url: API_BASE_URL + '/scenes',
            type: 'json',
            method: 'GET',
            headers: {
                Authorization: 'Bearer ' + API_KEY
            }
        },
        function(data) {
            data.forEach(function(scene) {
                if (!(scene.uuid in scenes)) {
                    scenes.push({uuid: scene.uuid, name: scene.name});
                    mainMenu.item(1, scenes.length - 1, {title: scene.name});
                }
            });

            loadingCard.hide();
        },
        function(error, status) {
            loadingCard.hide();
            handleError(status);
        }
    );
}

function fetchLights() {
    loadingCard.show();

    groups = [];
    lights = [];

    ajax(
        {
            url: API_BASE_URL + '/lights/all',
            type: 'json',
            method: 'GET',
            headers: {
                Authorization: 'Bearer ' + API_KEY
            }
        },
        function(data) {
            data.forEach(function(light) {
                if (!(light.group.id in groups)) {
                    groups.push({id: light.group.id, name: light.group.name});
                    mainMenu.item(2, groups.length - 1, {title: light.group.name});
                }
                if (!(light.id in lights)) {
                    lights.push({id: light.id, name: light.label});
                    mainMenu.item(3, lights.length - 1, {title: light.label});
                }
            });

            loadingCard.hide();
        },
        function(error, status) {
            loadingCard.hide();
            handleError(status);
        }
    );
}

function toggle(id) {
    ajax(
        {
            url: API_BASE_URL + '/lights/' + id + '/toggle',
            type: 'json',
            method: 'POST',
            headers: {
                Authorization: 'Bearer ' + API_KEY
            }
        },
        function(data) {
            Vibe.vibrate('short');
        },
        function(error, status) {
            console.log(JSON.stringify(error));
            console.log(JSON.stringify(status));
            handleError(status);
        }
    );
}

function activateScene(uuid) {
    ajax(
        {
            url: API_BASE_URL + '/scenes/scene_id:' + uuid + '/activate',
            type: 'json',
            method: 'PUT',
            headers: {
                Authorization: 'Bearer ' + API_KEY
            }
        },
        function(data) {
            Vibe.vibrate('short');
        },
        function(error, status) {
            console.log(JSON.stringify(error));
            console.log(JSON.stringify(status));
            handleError(status);
        }
    );
}

/*
 * App Ready
 */

mainMenu.show();
loadingCard.hide();

if (checkConfiguration()) {
    fetchScenes();
    fetchLights();
}

/*
 * Event Listeners
 */
mainMenu.on('select', function(e) {
    console.log('Selected item #' + e.itemIndex + ' of section #' + e.sectionIndex);
    console.log('The item is titled "' + e.item.title + '"');

    if (e.item.title === 'Toggle all lights') {
        toggle('all');
    } else if (e.sectionIndex === 1) {
        // Scenes
        activateScene(scenes[e.ItemIndex].uuid);
    } else if (e.sectionIndex === 2) {
        // Groups

    } else if (e.sectionIndex === 3) {
        // Lights
    }
});<commit_msg>Add better loading functionality<commit_after>var UI = require('ui');
var Settings = require('settings');
var Vibe = require('ui/vibe');
var ajax = require('ajax');

var CONFIGURATION_URL = "http://kz.github.io/lifx-pebble-config/index.html";
var API_BASE_URL = "https://api.lifx.com/v1";
var API_KEY;

var groups = [];
var scenes = [];
var lights = [];

/*
 * Configuration
 */

// Listen for configuration start
Settings.config(
        {
            url: CONFIGURATION_URL,
            autoSave: true
        },
        function (e) {
        },
        function (e) {
            checkConfiguration();
        }
);

// Listen for configuration complete
Settings.config(
        {
            url: CONFIGURATION_URL,
            autoSave: true
        },
        function (e) {
            checkConfiguration();
        }
);

/*
 * Views
 */

var setupCard = new UI.Card({
    title: 'Setup Required',
    body: 'Open this app\'s settings page in the Pebble app and enter your LIFX cloud access token.'
});

var errorCard = new UI.Card({
    title: 'Error',
    body: 'An unknown error has occurred. Please try again.'
});

var mainMenu = new UI.Menu({
    sections: [
        {
            title: 'Quick Actions',
            items: [
                { title: 'Toggle all lights' }
            ]
        },
        {
            title: 'Scenes',
            items: [
                { title: 'Loading...' }
            ]
        },
        {
            title: 'Groups',
            items: [
                { title: 'Loading...' }
            ]
        },
        {
            title: 'Lights',
            items: [
                { title: 'Loading...' }
            ]
        }
    ]
});

/*
 * App Functions
 */

function checkConfiguration() {
    if (typeof Settings.option('apiKey') === 'undefined') {
        setupCard.show();
        mainMenu.hide();
        return false;
    } else {
        API_KEY = Settings.option('apiKey');
        setupCard.hide();
        mainMenu.show();
        return true;
    }
}

function handleError(statusCode) {
    if (statusCode === 401) {
        Settings.option('apiKey', null);
        checkConfiguration();
    } else {
        errorCard.show();
    }
}

/*
 * Internet Enabled Functions
 */
function fetchScenes() {

    scenes = [];

    ajax(
        {
            url: API_BASE_URL + '/scenes',
            type: 'json',
            method: 'GET',
            headers: {
                Authorization: 'Bearer ' + API_KEY
            }
        },
        function(data) {
            data.forEach(function(scene) {
                if (!(scene.uuid in scenes)) {
                    scenes.push({uuid: scene.uuid, name: scene.name});
                    mainMenu.item(1, scenes.length - 1, {title: scene.name});
                }
            });
        },
        function(error, status) {
            handleError(status);
        }
    );
}

function fetchLights() {

    groups = [];
    lights = [];

    ajax(
        {
            url: API_BASE_URL + '/lights/all',
            type: 'json',
            method: 'GET',
            headers: {
                Authorization: 'Bearer ' + API_KEY
            }
        },
        function(data) {
            data.forEach(function(light) {
                if (!(light.group.id in groups)) {
                    groups.push({id: light.group.id, name: light.group.name});
                    mainMenu.item(2, groups.length - 1, {title: light.group.name});
                }
                if (!(light.id in lights)) {
                    lights.push({id: light.id, name: light.label});
                    mainMenu.item(3, lights.length - 1, {title: light.label});
                }
            });
        },
        function(error, status) {
            handleError(status);
        }
    );
}

function toggle(id) {
    ajax(
        {
            url: API_BASE_URL + '/lights/' + id + '/toggle',
            type: 'json',
            method: 'POST',
            headers: {
                Authorization: 'Bearer ' + API_KEY
            }
        },
        function(data) {
            Vibe.vibrate('short');
        },
        function(error, status) {
            console.log(JSON.stringify(error));
            console.log(JSON.stringify(status));
            handleError(status);
        }
    );
}

function activateScene(uuid) {
    ajax(
        {
            url: API_BASE_URL + '/scenes/scene_id:' + uuid + '/activate',
            type: 'json',
            method: 'PUT',
            headers: {
                Authorization: 'Bearer ' + API_KEY
            }
        },
        function(data) {
            Vibe.vibrate('short');
        },
        function(error, status) {
            console.log(JSON.stringify(error));
            console.log(JSON.stringify(status));
            handleError(status);
        }
    );
}

/*
 * App Ready
 */

mainMenu.show();

if (checkConfiguration()) {
    fetchScenes();
    fetchLights();
}

/*
 * Event Listeners
 */
mainMenu.on('select', function(e) {
    console.log('Selected item #' + e.itemIndex + ' of section #' + e.sectionIndex);
    console.log('The item is titled "' + e.item.title + '"');

    if (e.item.title === 'Toggle all lights') {
        toggle('all');
    } else if (e.sectionIndex === 1) {
        // Scenes
        activateScene(scenes[e.itemIndex].uuid);
    } else if (e.sectionIndex === 2) {
        // Groups
        toggle('group_id:' + groups[e.itemIndex].id);
    } else if (e.sectionIndex === 3) {
        // Lights
        toggle('id:' + lights[e.itemIndex].id);
    }
});<|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"
                    }
                });

            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    doApiAiRequest(apiaiRequest, sender) {
        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>fdvfv<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"
                    }
                });

            this.doApiAiRequest(apiaiRequest, sender);
        }
    }

    doApiAiRequest(apiaiRequest, sender) {
        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>var firstNamesMale, firstNamesFemale, lastNames;
var filesToLoad = 3;
getFile('data/first-names-male.txt', function(output) {
  firstNamesMale = output.split('\n');;
  filesToLoad--;
});
getFile('data/first-names-female.txt', function(output) {
  firstNamesFemale = output.split('\n');;
  filesToLoad--;
});
getFile('data/last-names.txt', function(output) {
  lastNames = output.split('\n');;
  filesToLoad--;
});

var NameList = React.createClass({
  render: function() {
    var nameNodes = this.props.names.map(function(name) {
      return (

          {name}

      );
    });
    return (

          {nameNodes}

    );
  }
});

var InputForm = React.createClass({
  getInitialState: function() {
    return {slk: '', invalid: false};
  },
  handleSlkChange: function(e) {
    this.setState({slk: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var slk = this.state.slk.trim().toUpperCase();

    if (!slk || !SlkDecoder.isValid(slk)) {
      this.setState({invalid: true});
      return false;
    }

    this.setState({invalid: false});
    this.props.slkCallback(slk);
  },
  render: function() {
    return (
      <form className="inputForm" onSubmit={this.handleSubmit}>
        Enter a SLK851 key
        <input className="slk" type="text" placeholder="URBAL241019541"
          value={this.state.slk}
          onChange={this.handleSlkChange} />
        <input className="submit" type="submit" value="Decode" />

{this.state.invalid ? "Oops! That doesn't look like an SLK." : ' '}

      </form>
    );
  }
});

var Results = React.createClass({
  render: function() {
    return (

Date of Birth

{this.props.results.dob}

Gender

{this.props.results.gender}

First Names

          <NameList names={this.props.results.firstNames}/>

Last Name

          <NameList names={this.props.results.lastNames}/>

    );
  }
});

var SlkDecoderPage = React.createClass({
  getInitialState: function() {
    return {details: {firstNames: [], lastNames: []}};
  },
  decodeSlk: function(slkString) {
    var decoder = new SlkDecoder(firstNamesMale, firstNamesFemale, lastNames);
    var details = decoder.decode(slkString);
    this.setState({results: details});
  },
  render: function() {
    return (

        <InputForm slkCallback={this.decodeSlk}/>
        {this.state.results ? <Results results={this.state.results} /> : null}

    );
  }
});

ReactDOM.render(
  <SlkDecoderPage />,
  document.getElementById('content')
);

function getFile(filename, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', filename);
  xhr.onload = function(e) {
    callback(this.response);
  }
  xhr.send();
}
<commit_msg>grammar is fun<commit_after>var firstNamesMale, firstNamesFemale, lastNames;
var filesToLoad = 3;
getFile('data/first-names-male.txt', function(output) {
  firstNamesMale = output.split('\n');;
  filesToLoad--;
});
getFile('data/first-names-female.txt', function(output) {
  firstNamesFemale = output.split('\n');;
  filesToLoad--;
});
getFile('data/last-names.txt', function(output) {
  lastNames = output.split('\n');;
  filesToLoad--;
});

var NameList = React.createClass({
  render: function() {
    var nameNodes = this.props.names.map(function(name) {
      return (

          {name}

      );
    });
    return (

          {nameNodes}

    );
  }
});

var InputForm = React.createClass({
  getInitialState: function() {
    return {slk: '', invalid: false};
  },
  handleSlkChange: function(e) {
    this.setState({slk: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var slk = this.state.slk.trim().toUpperCase();

    if (!slk || !SlkDecoder.isValid(slk)) {
      this.setState({invalid: true});
      return false;
    }

    this.setState({invalid: false});
    this.props.slkCallback(slk);
  },
  render: function() {
    return (
      <form className="inputForm" onSubmit={this.handleSubmit}>
        Enter an SLK851 key
        <input className="slk" type="text" placeholder="URBAL241019541"
          value={this.state.slk}
          onChange={this.handleSlkChange} />
        <input className="submit" type="submit" value="Decode" />

{this.state.invalid ? "Oops! That doesn't look like an SLK." : ' '}

      </form>
    );
  }
});

var Results = React.createClass({
  render: function() {
    return (

Date of Birth

{this.props.results.dob}

Gender

{this.props.results.gender}

First Names

          <NameList names={this.props.results.firstNames}/>

Last Name

          <NameList names={this.props.results.lastNames}/>

    );
  }
});

var SlkDecoderPage = React.createClass({
  getInitialState: function() {
    return {details: {firstNames: [], lastNames: []}};
  },
  decodeSlk: function(slkString) {
    var decoder = new SlkDecoder(firstNamesMale, firstNamesFemale, lastNames);
    var details = decoder.decode(slkString);
    this.setState({results: details});
  },
  render: function() {
    return (

        <InputForm slkCallback={this.decodeSlk}/>
        {this.state.results ? <Results results={this.state.results} /> : null}

    );
  }
});

ReactDOM.render(
  <SlkDecoderPage />,
  document.getElementById('content')
);

function getFile(filename, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', filename);
  xhr.onload = function(e) {
    callback(this.response);
  }
  xhr.send();
}
<|endoftext|>


<commit_before>import {Rx} from '@cycle/core';
import {h} from '@cycle/dom';

import TimeTravel from 'cycle-time-travel';

function todoView ({todo, complete}) {
  return (
    h('.todo', [
      h('span.title', todo),
      h('input.complete', {type: 'checkbox', checked: complete})
    ])
  );
}

function todosView (todos) {
  return (
    h('.todos', todos.map(todoView))
  );
}

function todoAppView (todos) {
  return (
    h('.todo-app', [
      h('input.new-todo'),
      h('button.add-todo', 'Add todo'),
      todosView(todos)
    ])
  );
}

export default function main ({DOM}) {
  const todos$ = Rx.Observable.just([
    {todo: 'Display todos', complete: true},
    {todo: 'Add todos', complete: false}
  ]);

  const timeTravel = TimeTravel(DOM, [
    {stream: todos$, label: 'todos$', feature: true}
  ]);

  const view$ = todos$.map(todoAppView);

  return {
    DOM: Rx.Observable.combineLatest(view$, timeTravel.DOM,
      (...vtrees) => h('div', vtrees)
    )
  };
}

<commit_msg>Add todo creation<commit_after>import {Rx} from '@cycle/core';
import {h} from '@cycle/dom';

import TimeTravel from 'cycle-time-travel';

function todoView ({todo, complete}) {
  return (
    h('.todo', [
      h('span.title', todo),
      h('input.complete', {type: 'checkbox', checked: complete})
    ])
  );
}

function todosView (todos) {
  return (
    h('.todos', todos.map(todoView))
  );
}

function todoAppView (todos) {
  return (
    h('.todo-app', [
      h('input.new-todo'),
      h('button.add-todo', 'Add todo'),
      todosView(todos)
    ])
  );
}

function addTodo (todo) {
  return (todos) => {
    return todos.concat([{todo, complete: false}]);
  };
}

export default function main ({DOM}) {
  const newTodoText$ = DOM
    .select('.new-todo')
    .events('input')
    .map(event => event.target.value);

  const addTodo$ = DOM
    .select('.add-todo')
    .events('click')
    .withLatestFrom(newTodoText$, (_, todoText) => addTodo(todoText));

  const startingTodos = [
    {todo: 'Display todos', complete: true},
    {todo: 'Add todos', complete: false}
  ];

  const action$ = addTodo$;

  const todos$ = action$
    .startWith(startingTodos)
    .scan((todos, action) => action(todos));

  const timeTravel = TimeTravel(DOM, [
    {stream: todos$, label: 'todos$', feature: true},
    {stream: addTodo$, label: 'addTodo$'},
    {stream: newTodoText$, label: 'newTodoText$'}
  ]);

  const view$ = timeTravel.timeTravel.todos$.map(todoAppView);

  return {
    DOM: Rx.Observable.combineLatest(view$, timeTravel.DOM,
      (...vtrees) => h('div', vtrees)
    )
  };
}

<|endoftext|>


<commit_before>const util = require('util');
const CommandUtil = require('./command_util').CommandUtil;
const Effects     = require('./effects').Effects;

const penalize = (player, item, attr, callback) => {
  util.log('factor for ' + attr + ' is:');
  const factor = player.getAttribute(attr) / item.getPrerequisite(attr);
  util.log(factor);
  callback(factor);
}

const getPenaltyDesc = (item, location, status) => status + '_by_' + item.getShortDesc() + '_' + location;

const useDefaultPenalties = (item, player, location, missedPrerequisites, verb) => {
  const gerund = verb ? verb + 'ing' : 'using';
  verb = verb || 'use';

  missedPrerequisites.forEach(prereq => {
    switch (prereq) {

      case 'stamina':
        return penalize(player, item, 'stamina', factor => {
          const name = getPenaltyDesc(item, location, 'encumbered');
          player.warn('You are not strong enough to ' + verb + ' this properly.');

          player.addEffect(name , Effects.encumbered({ player, factor }));
          player.combat.addSpeedMod({ name, effect: speed => speed / factor });
        });

      case 'quickness':
        return penalize(player, item, 'quickness', factor => {
          const name = getPenaltyDesc(item, location, 'slowed');

          player.warn('You are not quick enough to move about deftly while ' + gerund + ' the ' + item.getShortDesc() + '.');
          player.combat.addDodgeMod({ name, effect: dodge => dodge * factor });
        });

      case 'cleverness':
        return penalize(player, item, 'cleverness', factor => {
          const name = getPenaltyDesc(item, location, 'confused');

          player.warn('You are not sure how to handle this piece of gear...');
          player.combat.addToHitMod({ name, effect: toHit => toHit * factor });
        });

      case 'willpower':
        return penalize(player, item, 'willpower', factor => {
          const name = getPenaltyDesc(item, location, 'distracted');

          player.warn('You find yourself easily distracted as you ' + verb + ' the ' + item.getShortDesc());
          player.combat.addDefenseMod({ name, effect: defense => defense * factor });
        });

      default:
        player.warn('You have some trouble ' + gerund + ' it...');
        util.log('ITEM ' + item.getShortDesc() + ' has unsupported prerequisites.');
    }
  });

};

const removeDefaultPenaltes = (player, item, location) => {
  const itemDesc = item.getShortDesc();

  const encumbered = getPenaltyDesc(item, location, 'encumbered');
  const confused   = getPenaltyDesc(item, location, 'confused');
  const distracted = getPenaltyDesc(item, location, 'distracted');
  const slowed     = getPenaltyDesc(item, location, 'slowed');

  player.removeEffect(encumbered);
  player.removeEffect(confused);

  player.combat.deleteAllMods(distracted);
  player.combat.deleteAllMods(encumbered);
  player.combat.deleteAllMods(slowed);
};

const checkForCrit = (attacker, defender, damageDealt) => {
  const defenderHealth = defender.getAttribute('health');
  const defenderMaxHealth = defender.getAttribute('max_health');

  //TODO: Improve... if the damage is over the weapon's normal max damage it should be considered a crit...
  const massiveDamage = damageDealt > defenderMaxHealth * .5;
  const almostDead    = defenderHealth <= defenderMaxHealth * .2;
  const coupDeGrace   = almostDead && damageDealt >= defenderHealth;

  if (massiveDamage || coupDeGrace) {
    //TODO: Add some kind of bonus.
    attacker.say('<bold><cyan>You have dealt a critical blow!</cyan></bold>');
  }
}

function checkInventory(player, item, items) {
  return [ tooLarge(player, item, items) , tooHeavy(player, item, items) ];
}

function tooLarge(player, item, items) {
  const itemSize = item.getAttribute('size');
  if (itemSize === Infinity) { return true; }

  const containerWithCapacity = player.getContainerWithCapacity(items, itemSize);
  return !containerWithCapacity;
}

function tooHeavy(player, item, items) {
  const itemWeight = item.getWeight(items);
  if (itemWeight === Infinity) { return true; }

  const carriedWeight  = player.getCarriedWeight(items);
  const maxCarryWeight = player.getMaxCarryWeight();

  return (carriedWeight + itemWeight) > maxCarryWeight;
}

function hold({ player, room, item }, callback) {
  const equipment = player.getEquipped();
  const location  = player.findHoldingLocation();

  player.addItem(item);
  if (room) { room.removeItem(item) };
  item.setRoom(null);
  item.setHolder(player.getName());
  player.equip(location, item);
  item.setEquipped(true);

  callback(location);
}

function pickUp({ player, room, item }, callback) {
  item.setRoom(null);
  item.setHolder(player.getName());

  const container = player.getContainerWithCapacity(item.getAttribute('size'));
  player.addItem(item);
  container.addItem(item);
  if (room) { room.removeItem(item); }

  callback(container);
}

/* Used for deleting an item from equipment, including moving from one slot to another.
 * @param entity player or anything with equipment
 * @param item object
 * @param location new location to equip item at.
 * @return string new location where item is equipped.
 */
function deleteFromEquipment(entity, item, location) {
  for (const slot in entity.equipment) {
    if (slot === location) { continue; }
    if (entity.equipment[slot] === item.getUuid()) {
      delete entity.equipment[slot];
      return slot;
    }
  }
}

function getFailureMessage(tooLarge, tooHeavy, item) {
  const itemName = item.getShortDesc();

  if (tooLarge) { return `The ${itemName} will not fit in your inventory, it is too large.`; }
  if (tooHeavy) { return `The ${itemName} is too heavy for you to carry at the moment.`; }
  return `You cannot pick up ${itemName} right now.`;
}

function isHeld(player, item) {
  const equipment = player.getEquipped();
  return ['held', 'wield', 'offhand', 'offhand held'].filter(slot => equipment[slot] === item.getUuid())[0];
}

function putItemInContainer(item, container, player, players) {
    const containerName = container.getShortDesc();
    const itemName      = item.getShortDesc();
    container.addItem(item);

    if (item.isEquipped()) {
      util.log('was equipped, gon remove');
      item.setEquipped(false);
      deleteFromEquipment(player, item);
    }

    util.log('CONTAINERING');

    player.say(`You remove the ${itemName} and place it in your ${containerName}.`);
    players.eachIf(
      p => CommandUtil.inSameRoom(p, player),
      p => p.say(`${player.getName()} places their ${itemName} in their ${containerName}.`)
    );
    return true;
  }

  function holdOntoItem(item, holdingLocation, player, players) {
    util.log('HOLDIN');
    const itemName = item.getShortDesc();
    player.equip(holdingLocation, item);
    return true;
  }

exports.ItemUtil = {
  isHeld, deleteFromEquipment,
  checkInventory, putItemInContainer,
  hold, pickUp,
  getFailureMessage, holdOntoItem,
  tooLarge, tooHeavy,
  penalize, getPenaltyDesc,
  useDefaultPenalties, checkForCrit,
  removeDefaultPenaltes
};
<commit_msg>Remove goofy af encumbrance effects.<commit_after>const util = require('util');
const CommandUtil = require('./command_util').CommandUtil;
const Effects     = require('./effects').Effects;

const penalize = (player, item, attr, callback) => {
  util.log('factor for ' + attr + ' is:');
  const factor = player.getAttribute(attr) / item.getPrerequisite(attr);
  util.log(factor);
  callback(factor);
}

const getPenaltyDesc = (item, location, status) => status + '_by_' + item.getShortDesc() + '_' + location;

const useDefaultPenalties = (item, player, location, missedPrerequisites, verb) => {
  const gerund = verb ? verb + 'ing' : 'using';
  verb = verb || 'use';

  missedPrerequisites.forEach(prereq => {
    switch (prereq) {

      case 'stamina':
        return penalize(player, item, 'stamina', factor => {
          const name = getPenaltyDesc(item, location, 'encumbered');
          player.warn('You are not strong enough to ' + verb + ' this properly.');

          player.combat.addSpeedMod({ name, effect: speed => speed / factor });
        });

      case 'quickness':
        return penalize(player, item, 'quickness', factor => {
          const name = getPenaltyDesc(item, location, 'slowed');

          player.warn('You are not quick enough to move about deftly while ' + gerund + ' the ' + item.getShortDesc() + '.');
          player.combat.addDodgeMod({ name, effect: dodge => dodge * factor });
        });

      case 'cleverness':
        return penalize(player, item, 'cleverness', factor => {
          const name = getPenaltyDesc(item, location, 'confused');

          player.warn('You are not sure how to handle this piece of gear...');
          player.combat.addToHitMod({ name, effect: toHit => toHit * factor });
        });

      case 'willpower':
        return penalize(player, item, 'willpower', factor => {
          const name = getPenaltyDesc(item, location, 'distracted');

          player.warn('You find yourself easily distracted as you ' + verb + ' the ' + item.getShortDesc());
          player.combat.addDefenseMod({ name, effect: defense => defense * factor });
        });

      default:
        player.warn('You have some trouble ' + gerund + ' it...');
        util.log('ITEM ' + item.getShortDesc() + ' has unsupported prerequisites.');
    }
  });

};

const removeDefaultPenaltes = (player, item, location) => {
  const itemDesc = item.getShortDesc();

  const encumbered = getPenaltyDesc(item, location, 'encumbered');
  const confused   = getPenaltyDesc(item, location, 'confused');
  const distracted = getPenaltyDesc(item, location, 'distracted');
  const slowed     = getPenaltyDesc(item, location, 'slowed');

  player.removeEffect(encumbered);
  player.removeEffect(confused);

  player.combat.deleteAllMods(distracted);
  player.combat.deleteAllMods(encumbered);
  player.combat.deleteAllMods(slowed);
};

const checkForCrit = (attacker, defender, damageDealt) => {
  const defenderHealth = defender.getAttribute('health');
  const defenderMaxHealth = defender.getAttribute('max_health');

  //TODO: Improve... if the damage is over the weapon's normal max damage it should be considered a crit...
  const massiveDamage = damageDealt > defenderMaxHealth * .5;
  const almostDead    = defenderHealth <= defenderMaxHealth * .2;
  const coupDeGrace   = almostDead && damageDealt >= defenderHealth;

  if (massiveDamage || coupDeGrace) {
    //TODO: Add some kind of bonus.
    attacker.say('<bold><cyan>You have dealt a critical blow!</cyan></bold>');
  }
}

function checkInventory(player, item, items) {
  return [ tooLarge(player, item, items) , tooHeavy(player, item, items) ];
}

function tooLarge(player, item, items) {
  const itemSize = item.getAttribute('size');
  if (itemSize === Infinity) { return true; }

  const containerWithCapacity = player.getContainerWithCapacity(items, itemSize);
  return !containerWithCapacity;
}

function tooHeavy(player, item, items) {
  const itemWeight = item.getWeight(items);
  if (itemWeight === Infinity) { return true; }

  const carriedWeight  = player.getCarriedWeight(items);
  const maxCarryWeight = player.getMaxCarryWeight();

  return (carriedWeight + itemWeight) > maxCarryWeight;
}

function hold({ player, room, item }, callback) {
  const equipment = player.getEquipped();
  const location  = player.findHoldingLocation();

  player.addItem(item);
  if (room) { room.removeItem(item) };
  item.setRoom(null);
  item.setHolder(player.getName());
  player.equip(location, item);
  item.setEquipped(true);

  callback(location);
}

function pickUp({ player, room, item }, callback) {
  item.setRoom(null);
  item.setHolder(player.getName());

  const container = player.getContainerWithCapacity(item.getAttribute('size'));
  player.addItem(item);
  container.addItem(item);
  if (room) { room.removeItem(item); }

  callback(container);
}

/* Used for deleting an item from equipment, including moving from one slot to another.
 * @param entity player or anything with equipment
 * @param item object
 * @param location new location to equip item at.
 * @return string new location where item is equipped.
 */
function deleteFromEquipment(entity, item, location) {
  for (const slot in entity.equipment) {
    if (slot === location) { continue; }
    if (entity.equipment[slot] === item.getUuid()) {
      delete entity.equipment[slot];
      return slot;
    }
  }
}

function getFailureMessage(tooLarge, tooHeavy, item) {
  const itemName = item.getShortDesc();

  if (tooLarge) { return `The ${itemName} will not fit in your inventory, it is too large.`; }
  if (tooHeavy) { return `The ${itemName} is too heavy for you to carry at the moment.`; }
  return `You cannot pick up ${itemName} right now.`;
}

function isHeld(player, item) {
  const equipment = player.getEquipped();
  return ['held', 'wield', 'offhand', 'offhand held'].filter(slot => equipment[slot] === item.getUuid())[0];
}

function putItemInContainer(item, container, player, players) {
    const containerName = container.getShortDesc();
    const itemName      = item.getShortDesc();
    container.addItem(item);

    if (item.isEquipped()) {
      util.log('was equipped, gon remove');
      item.setEquipped(false);
      deleteFromEquipment(player, item);
    }

    util.log('CONTAINERING');

    player.say(`You remove the ${itemName} and place it in your ${containerName}.`);
    players.eachIf(
      p => CommandUtil.inSameRoom(p, player),
      p => p.say(`${player.getName()} places their ${itemName} in their ${containerName}.`)
    );
    return true;
  }

  function holdOntoItem(item, holdingLocation, player, players) {
    util.log('HOLDIN');
    const itemName = item.getShortDesc();
    player.equip(holdingLocation, item);
    return true;
  }

exports.ItemUtil = {
  isHeld, deleteFromEquipment,
  checkInventory, putItemInContainer,
  hold, pickUp,
  getFailureMessage, holdOntoItem,
  tooLarge, tooHeavy,
  penalize, getPenaltyDesc,
  useDefaultPenalties, checkForCrit,
  removeDefaultPenaltes
};
<|endoftext|>