lib/annyang-core.js
//! Adapted for use in Sonus by Evan Cohen @_evnc
//! annyang
//! version : 2.5.0
//! author : Tal Ater @TalAter
//! license : MIT
//! https://www.TalAter.com/annyang/
"use strict";
let annyang;
let commandsList = [];
const callbacks = { start: [], error: [], end: [], result: [], resultMatch: [], resultNoMatch: [], errorNetwork: [], errorPermissionBlocked: [], errorPermissionDenied: [] };
let recognition;
let debugState = false;
// The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license.
const optionalParam = /\s*\((.*?)\)\s*/g;
const optionalRegex = /(\(\?:[^)]+\))\?/g;
const namedParam = /(\(\?)?:\w+/g;
const splatParam = /\*\w+/g;
const escapeRegExp = /[\-{}\[\]+?.,\\\^$|#]/g;
const commandToRegExp = function (command) {
command = command.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, function (match, optional) {
return optional ? match : '([^\\s]+)';
})
.replace(splatParam, '(.*?)')
.replace(optionalRegex, '\\s*$1?\\s*');
return new RegExp('^' + command + '$', 'i');
};
// This method receives an array of callbacks to iterate over, and invokes each of them
const invokeCallbacks = callbacks => {
let args = Array.prototype.slice.call(arguments, 1);
callbacks.forEach(callback =>
callback.callback.apply(callback.context, args)
);
};
const isInitialized = () => {
return recognition !== undefined;
};
// method for logging in developer console when debug mode is on
const logMessage = (text, extraParameters) => {
if (text.indexOf('%c') === -1 && !extraParameters) {
console.log(text);
} else {
extraParameters = extraParameters || '';
console.log(text, extraParameters);
}
};
const initIfNeeded = () => {
if (!isInitialized()) {
annyang.init({}, false);
}
};
const registerCommand = (command, cb, phrase) => {
commandsList.push({ command: command, callback: cb, originalPhrase: phrase });
if (debugState) {
logMessage('Command successfully loaded: %c' + phrase);
}
};
const parseResults = function (results) {
invokeCallbacks(callbacks.result, results);
let commandText;
// go over each of the 5 results and alternative results received (we've set maxAlternatives to 5 above)
for (let i = 0; i < results.length; i++) {
// the text recognized
commandText = results[i].trim();
if (debugState) {
logMessage('Speech recognized: %c' + commandText);
}
// try and match recognized text to one of the commands on the list
for (let j = 0, l = commandsList.length; j < l; j++) {
const currentCommand = commandsList[j];
const result = currentCommand.command.exec(commandText);
if (result) {
const parameters = result.slice(1);
if (debugState) {
logMessage('command matched: %c' + currentCommand.originalPhrase);
if (parameters.length) {
logMessage('with parameters', parameters);
}
}
// execute the matched command
currentCommand.callback.apply(this, parameters);
invokeCallbacks(callbacks.resultMatch, commandText, currentCommand.originalPhrase, results);
return;
}
}
}
invokeCallbacks(callbacks.resultNoMatch, results);
};
annyang = {
init: (commands, resetCommands) => {
if (resetCommands === undefined) {
resetCommands = true;
} else {
resetCommands = !!resetCommands;
}
if (resetCommands) {
commandsList = [];
}
if (commands.length) {
this.addCommands(commands);
}
},
debug: newState => {
if (arguments.length > 0) {
debugState = !!newState;
} else {
debugState = true;
}
},
addCommands: commands => {
let cb;
initIfNeeded();
for (const phrase in commands) {
if (commands.hasOwnProperty(phrase)) {
cb = global[commands[phrase]] || commands[phrase];
if (typeof cb === 'function') {
// convert command to regex then register the command
registerCommand(commandToRegExp(phrase), cb, phrase);
} else if (typeof cb === 'object' && cb.regexp instanceof RegExp) {
// register the command
registerCommand(new RegExp(cb.regexp.source, 'i'), cb.callback, phrase);
} else {
if (debugState) {
logMessage('Can not register command: %c' + phrase);
}
continue;
}
}
}
},
removeCommands: commandsToRemove => {
if (commandsToRemove === undefined) {
commandsList = [];
return;
}
commandsToRemove = Array.isArray(commandsToRemove) ? commandsToRemove : [commandsToRemove];
commandsList = commandsList.filter(command => {
for (let i = 0; i < commandsToRemove.length; i++) {
if (commandsToRemove[i] === command.originalPhrase) {
return false;
}
}
return true;
});
},
addCallback: (type, callback, context) => {
if (callbacks[type] === undefined) {
return;
}
var cb = global[callback] || callback;
if (typeof cb !== 'function') {
return;
}
callbacks[type].push({ callback: cb, context: context || this });
},
removeCallback: (type, callback) => {
const compareWithCallbackParameter = function (cb) {
return cb.callback !== callback;
};
// Go over each callback type in callbacks store object
for (const callbackType in callbacks) {
if (callbacks.hasOwnProperty(callbackType)) {
// if this is the type user asked to delete, or he asked to delete all, go ahead.
if (type === undefined || type === callbackType) {
// If user asked to delete all callbacks in this type or all types
if (callback === undefined) {
callbacks[callbackType] = [];
} else {
// Remove all matching callbacks
callbacks[callbackType] = callbacks[callbackType].filter(compareWithCallbackParameter);
}
}
}
}
},
trigger: sentences => {
if (!Array.isArray(sentences)) {
sentences = [sentences];
}
parseResults(sentences);
}
};
module.exports = annyang