alexia
Version:
A Framework for creating Amazon Echo (Alexa) skills using Node.js
98 lines (78 loc) • 3.4 kB
JavaScript
;
const _ = require('lodash');
const bases = require('bases');
const builtInSlotsMap = require('./built-in-slots-map');
const builtInIntentsMap = require('./built-in-intents-map');
const validator = require('./validator');
/**
* Creates intent
* @param {Object[]} intents - Array of intents. Required for determining taken intent names
* @param {string} name - Name of the intent. If null or undefined, automatically generated intent name is used
* @param {(string|string[])} richUtterances - Utterance or array of rich utterances
* @param {function} handler - Function to be called when intent is invoked
*/
module.exports = (intents, name, richUtterances, handler) => {
// Convert utterances to array
richUtterances = _.isArray(richUtterances) ? richUtterances : [richUtterances];
// If intent name is not specified, try to generate unique one
if(!name) {
name = generateIntentName(intents);
} else if(!validator.isNameValid(name)) {
throw Error(`Intent name ${name} is invalid. Only lowercase and uppercase letters are allowed`);
} else if(builtInIntentsMap[name]) {
// If built-in intent name was used map intent name to it
name = builtInIntentsMap[name];
}
// Transformed slots and utterances from richUtterances
let slots = [];
let utterances = [];
parseRichUtterances(richUtterances, slots, utterances)
return {
name: name,
slots: slots,
utterances: utterances,
handler: handler
};
}
const generateIntentName = (intents) => {
let position = 0;
let generatedName;
// While generated name is not already used and generatedName is not built-in intent (just in case)
while(intents[(generatedName = bases.toBase52(position++))] && !builtInIntentsMap[generatedName]);
return generatedName;
};
const parseRichUtterances = (richUtterances, slots, utterances) => {
// Iterate over each rich utterance and transform it by removing slots description
_.each(richUtterances, function(utterance) {
var matches = findUtteranceMatches(utterance);
_.each(matches, function(match) {
// Remember slot type
slots.push({
name: match[1],
type: transformSlotType(match[2])
});
// Replace utterance slot type (there could be multiple slots in utterance)
utterance = utterance.replace(match[0], '{' + match[1] + '}');
});
if(validator.isUtteranceValid(utterance)) {
// Remember utterance
utterances.push(utterance);
} else {
throw new Error(`Error: Sample utterance: '${utterance}' is not valid. Each sample utterance must consist only of alphabet characters, spaces, dots, hyphens, brackets and single quotes`);
}
});
};
const transformSlotType = (type) => {
const transformedType = builtInSlotsMap[type];
return transformedType ? transformedType : type;
}
const findUtteranceMatches = (utterance) => {
// Example: for 'move forward by {value:Number}' we get:
// [[ '{value:Number}', 'value', 'Number', index: 16, input: 'move forward by {value:Number}' ]]
var myregex = /{(.*?):(.*?)\}/gmi;
var result, allMatches = [];
while((result = myregex.exec(utterance)) != null) {
allMatches.push(result);
}
return allMatches;
}