controllers/bot.js

var promise 	= require('bluebird');
var path        = require('path');
var rivescript  = require("rivescript");

var botconfig   = require('../config/botconfig').botconfig;

var botutil 	= require('../utils/botutil');
var stringutil 	= require('../utils/stringutil');
var debugutil 	= require('../utils/debugutil');

exports = module.exports = function(instances)
{
	return new Botctl(instances);
}

/**
 * @constructs Bot_Controller
 * @public
 * @param {Bot[]} instances - An array with bot instances config
 */
function Botctl(instances)
{
    this.bot = {};

    if(instances && instances.length!=undefined)
    {
        for (var i = 0; i < instances.length; i++)
        {
            var bot = instances[i];
            bot.language = bot.language || 'en';
            bot.config = bot.config || {};
            bot.path = bot.path || '';
            bot.variables = bot.variables || {};

            this.bot[bot.language] = {};

            this.bot[bot.language].loaded = false;
            this.bot[bot.language].entities = [];
            this.bot[bot.language].variables = bot.variables;
            this.bot[bot.language].brain = new rivescript(bot.config);

			if(bot.unicodePunctuation)
				this.bot[bot.language].brain.unicodePunctuation = bot.unicodePunctuation;

            this.bot[bot.language].brain.loadDirectory(bot.path, success_handler.bind(null, bot.language, this), error_handler.bind(null, bot.language));
        }
    }
}

/**
 * Sets a rivescript array to be used by the brain
 * @param {String} name - The name of the key of array
 * @param {Array} value - The array of values
 * @param {String} lang - The language of the bot
 * @return {Array} The array
 */
Botctl.prototype.setArray = function setArray(name, value, lang)
{
    if (value === void 0)
      return delete this.bot[lang].brain._array[name];
    else
      return this.bot[lang].brain._array[name] = value;
}

/**
 * Sets a rivescript global to be used by the brain
 * @param {String} name - The name of the key of global
 * @param {Array} value - The array of values
 * @param {String} lang - The language of the bot
 * @return {Object} The global
 */
Botctl.prototype.setGlobal = function setGlobal(name, value, lang)
{
    return this.bot[lang].brain.setGlobal(name, value);
}

/**
 * Sets a rivescript variable to be used by the brain
 * @param {String} name - The name of the key of variable
 * @param {Array} value - The array of values
 * @param {String} lang - The language of the bot
 * @return {Object} The variable
 */
Botctl.prototype.setVariable = function setVariable(name, value, lang)
{
    return this.bot[lang].brain.setVariable(name, value);
}

/**
 * Sets a rivescript user variable to be used by the brain
 * @param {String} user - The id of the user
 * @param {String} name - The name of the key of variable
 * @param {Array} value - The array of values
 * @param {String} lang - The language of the bot
 * @return {Object} The user variable
 */
Botctl.prototype.setUservar = function setUservar(user, name, value, lang)
{
    return this.bot[lang].brain.setUservar(user, name, value);
}

/**
 * Sets a object with rivescript user variables to be used by the brain
 * @param {String} user - The id of the user
 * @param {Object} data - A data object with pairs of key/values
 * @param {String} lang - The language of the bot
 * @return {Object} The user variables object
 */
Botctl.prototype.setUservars = function setUservars(user, data, lang)
{
    return this.bot[lang].brain.setUservars(user, data);
}

/**
 * Sets a rivescript subroutine to be used by the brain
 * @param {String} user - The id of the user
 * @param {Object} code - The subroutine code
 * @param {String} lang - The language of the bot
 * @return {Object} The subroutine
 */
Botctl.prototype.setSubroutine = function setSubroutine(name, code, lang)
{
    return this.bot[lang].brain.setSubroutine(name, code);
}

/**
 * Sets a rivescript handler to be used by the brain
 * @param {String} language_name - The name of language used
 * @param {Object} obj - A object to be used
 * @param {String} lang - The language of the bot
 * @return {Object} The handler
 */
Botctl.prototype.setHandler = function setHandler(language_name, obj, lang)
{
    return this.bot[lang].brain.setHandler(language_name, obj);
}

/**
 * Sets a rivescript substitution to be used by the brain
 * @param {String} name - The name of the substitution
 * @param {Object} value - The value of the substitution
 * @param {String} lang - The language of the bot
 * @return {Object} The substitution
 */
Botctl.prototype.setSubstitution = function setSubstitution(name, value, lang)
{
    return this.bot[lang].brain.setSubstitution(name, value);
}

/**
 * Sets a rivescript person to be used by the brain
 * @param {String} name - The name of the person
 * @param {Object} value - The value of the person
 * @param {String} lang - The language of the bot
 * @return {Object} The person
 */
Botctl.prototype.setPerson = function setPerson(name, value, lang)
{
    return this.bot[lang].brain.setPerson(name, value);
}

/**
 * Gets a rivescript array object
 * @param {String} lang - The language of the bot
 * @return {Array} The array object
 */
Botctl.prototype.getArray = function getArray(lang)
{
    return this.bot[lang].brain._array;
}

/**
 * Gets a rivescript subtitution object
 * @param {String} lang - The language of the bot
 * @return {Array} The subtitution object
 */
Botctl.prototype.getSubstitution = function getSubstitution(lang)
{
    return this.bot[lang].brain._sub;
}

/**
 * Gets a rivescript user last match
 * @param {String} user - The id of the user
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript user last match
 */
Botctl.prototype.lastMatch = function lastMatch(user, lang)
{
    return this.bot[lang].brain.lastMatch(user);
}

/**
 * Gets a rivescript user initial match
 * @param {String} user - The id of the user
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript user initial match
 */
Botctl.prototype.initialMatch = function initialMatch(user, lang)
{
    return this.bot[lang].brain.initialMatch(user);
}

/**
 * Gets a rivescript user topic triggers
 * @param {String} user - The id of the user
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript user initial match
 */
Botctl.prototype.getUserTopicTriggers = function getUserTopicTriggers(user, lang)
{
    return this.bot[lang].brain.getUserTopicTriggers(user);
}

/**
 * Gets a rivescript variable by a given name
 * @param {String} user - The id of the user
 * @param {String} name - The name of the substitution
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript variable
 */
Botctl.prototype.getVariable = function getVariable(user, name, lang)
{
    return this.bot[lang].brain.getVariable(user, name);
}

/**
 * Gets a rivescript user variable by a given name
 * @param {String} user - The id of the user
 * @param {String} name - The name of the substitution
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript user variable
 */
Botctl.prototype.getUservar = function getUservar(user, name, lang)
{
    return this.bot[lang].brain.getUservar(user, name);
}

/**
 * Gets a rivescript user variables object
 * @param {String} user - The id of the user
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript user variables object
 */
Botctl.prototype.getUservars = function getUservars(user, lang)
{
    return this.bot[lang].brain.getUservars(user);
}

/**
 * Clears the rivescript user variables object
 * @param {String} user - The id of the user
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript user variables object
 */
Botctl.prototype.clearUservars = function clearUservars(user, lang)
{
    return this.bot[lang].brain.clearUservars(user);
}

/**
 * Freezes the rivescript user variables object
 * @param {String} user - The id of the user
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript user variables object
 */
Botctl.prototype.freezeUservars = function freezeUservars(user, lang)
{
    return this.bot[lang].brain.freezeUservars(user);
}

/**
 * Thaws the rivescript user variables object
 * @param {String} user - The id of the user
 * @param {String} lang - The language of the bot
 * @return {Array} The rivescript user variables object
 */
Botctl.prototype.thawUservars = function thawUservars(user, lang)
{
    return this.bot[lang].brain.thawUservars(user);
}

/**
 * Process bot event and sends to rivescript
 * @param {Event} event - A NGINB event object
 * @return {Object} A bluebird promise bot response object
 */
Botctl.prototype.processEvent = function processEvent(event)
{
    var self = this;
	return new promise(function(resolve, reject)
	{
		if(!stringutil.isEmail(event.text))
			event.text = stringutil.replaceAll(event.text, '.', '');

		self.bot[event.lang].brain.replyAsync(event.sender, event.text)
        .then(function(response)
        {
            var reply = botutil.getVariablesObjectFromString(response, event.userdata);
            resolve ({reply:reply, event:event});
        })
        .catch(function(error)
        {
            console.log(error);
        });
	});
}

/**
 * Process entities and configure to use in rivescript
 * @param {EntityConfig[]} entities - An array of entities objects
 * @param {String} lang - The language of the bot to set entities to
 * @return {Boolean}  A bluebird promise with true or false
 */
Botctl.prototype.setEntities = function setEntities(entities, lang)
{
    var self = this;
    return new promise(function(resolve, reject)
	{
        entities = entities || [];
        lang = lang || 'en';

		if(self.bot[lang])
		{
	        if(self.bot[lang].loaded)
	        {
	            processEntities(entities, lang)
	            .then(function(response)
	            {
	                resolve(response);
	            });
	        }
	        else
	            self.bot[lang].entities = entities;
		}
    });
}

/**
 * Success handler for load rivescript brain instances
 * @private
 * @param {String} lang - The language of the bot
 * @param {Bot_Controller} self - This instance of bot controller
 */
function success_handler (lang, self)
{
    self.bot[lang].brain.sortReplies();
    self.bot[lang].loaded = true;

    if(self.bot[lang].variables && self.bot[lang].variables.entities)
        processEntities(self,self.bot[lang].variables.entities, lang);

    if(self.bot[lang].entities.length>0)
    {
        processEntities(self, bot[lang].entities, lang);
        self.bot[lang].entities = [];
    }
}

/**
 * Error handler for load rivescript brain instances
 * @private
 * @param {String} lang - The language of the bot
 * @param {String} err - The error sent by rivescript loader
 */
function error_handler (lang, err)
{
	console.log("Error loading brains " + lang + ": " + err + "\n");
}

/**
 * A function to process the entities
 * @private
 * @param {Bot_Controller} self - This instance of bot controller
 * @param {EntityConfig[]} entities - The entities array object
 * @param {String} lang - The language of the bot
 */
function processEntities(self, entities, lang)
{
    return new promise(function(resolve, reject)
	{
        var flatentities = JSON.stringify(entities);
        var flatbotarray = JSON.stringify(self.getArray(lang));
        var flatbotsubs = JSON.stringify(self.getSubstitution(lang));

        flatentities += flatbotarray + flatbotsubs;

        for (var i = 0; i < entities.length; i++)
        {
            var entity = entities[i];
            if(entity.hasOwnProperty('name') && entity.hasOwnProperty('entries'))
            {
                var array = [];
                for (var j = 0; j < entity.entries.length; j++)
                {
                    var entry = entity.entries[j];
                    var prefix = entry.hasOwnProperty('prefix') ? entry.prefix + ' ' : '';
                    var vlr = prefix + entry.value;
                    array.push(vlr);

                    if(entry.hasOwnProperty('synonyms'))
                    {
                        for (var s = 0; s < entry.synonyms.length; s++)
                        {
                            var sub = entry.synonyms[s];

                            if(sub!=vlr)
                            {
                                self.setSubstitution(sub, vlr, lang);

                                if(botconfig.humanize_subs)
                                {
                                    for (var c = 0; c < sub.length; c++)
                                    {
                                        var newword = botutil.dropChar(sub, c);

                                        if(flatentities.indexOf('"'+newword+'"')==-1)
                                            self.setSubstitution(newword, vlr, lang);

                                        newword = botutil.switchChar(sub, c);
                                        if(flatentities.indexOf('"'+newword+'"')==-1)
                                            self.setSubstitution(newword, vlr, lang);

                                        //botutil.addTypo(sub, c); will add too much subs
                                    }
                                }
                            }
                        }
                    }
                }

                self.setArray(entity.name, array, lang);
            }
        }
        resolve(true);
    });
}