universal_bot
Version:
Taking the principles of aiml - but allowing a dynamic and extendable *.js environment
743 lines (509 loc) • 20.6 kB
JavaScript
"use strict";
var meta = require('debug')('aiml:markov');
var meta2 = require('debug')('aiml:topic');
var reg = require('debug')('reg:topic');
var reg2 = require('debug')('match');
var reg3 = require('debug')('cond');
var fs = require('fs');
var meta = require('debug')('worker:meta');
var debugs = require('debug')('check');
var resolve = require('path').resolve
/*
<category>
<pattern>THANK YOU *</pattern>
<template>
<random>
<li>You are welcome!</li>
<li>I am at your service</li>
<li>That was nothing.</li>
</random>
</template>
*/
// a single file or an array of files
var Aiml = function(file) {
var self = this;
this.topics = [];
this.topics.push("*");
meta( this.topics );
this.nodes = [];
this.vars = null;
this.writeFile = function (name) {
var filename = "lib/" + name + ".json";
var s = JSON.stringify( self.nodes, null, 4 );
fs.writeFile(filename, s);
}
this.check_star = function(str) {
if (str.search("[*]") !== -1) return true;
else return false;
}
self.wildCardArray = [];
this.checkIfMessageMatchesPattern = function(userInput, patternText){
//convert wildcards in of the pattern node into a regex that matches every char
var regexPattern = self.convertWildcardToRegex(patternText);
//add one with the text in function 'convertWildcardToRegex' here a space is added before and after the user input
//to prevent false matching
if(userInput.charAt(0) != " "){
userInput = " " + userInput;
}
var lastCharacterPosition = userInput.length - 1;
var lastCharacter = userInput.charAt(lastCharacterPosition);
if(lastCharacter != " "){
userInput = userInput + " ";
}
//match userInput with the regex pattern
//if it matches, matchedString is defined
// var matchedString = userInput.toUpperCase().match(regexPattern);
var matchedString = userInput.match(regexPattern);
if(matchedString){
reg2( matchedString );
reg2 (regexPattern );
// [a-zA-Z\x7f-\xff]
// var x = regexPattern.indexOf('[a-zx7f-\xff|0-9|\\s]*[a-Z|0-9|-]*[a-zäöüß|A-Zx7f-\xff|0-9]*[!|.|?|\\s]*') ;
var x = regexPattern.indexOf('[a-zäöüß|0-9|\\s]*[a-Z|0-9|-]*[a-zäöüß|A-ZÄÖÜ|0-9]*[!|.|?|\\s]*') ;
//the matched pattern must be at least as long as the user input or must contain the regex
if(matchedString[0].length >= userInput.length || regexPattern.indexOf('[a-zäöüß|A-ZÄÖÜ|0-9|\\s]*[a-zäöüß|A-ZÄÖÜ|0-9|-]*[a-zäöüß|A-ZÄÖÜ|0-9]*[!|.|?|\\s]*') > -1){
//if patternText contained a wild card, get the user input that were put into this wild card
//use original patternText (* is not replaced by regex!)
// reg2("Das könnte klappen");
self.information = self.getWildCardValue(userInput, patternText);
reg2( self.information);
return true;
}
}
else{
return false;
}
}
this.getWildCardValue = function(userInput, patternText){
var wildCardArray = [];
var lastWildCardValue = '';
//get all strings of the pattern that are divided by a *
//e.g. WHAT IS THE RELATION BETWEEN * AND * -> [WHAT IS THE RELATION BETWEEN , AND ]
var replaceArray = patternText.split('*');
var wildCardInput = userInput;
reg2("so weit so gut " + replaceArray);
if(replaceArray.length > 1){
//replace the string of the userInput which is fixed by the pattern
for(var i = 0; i < replaceArray.length; i++){
wildCardInput = wildCardInput.replace(new RegExp(replaceArray[i], 'i'), '|');
}
//split the wildCardInput string by | to differentiate multiple * inputs
//e.g. userInput = WHAT IS THE RELATION BETWEEN TIM AND STRUPPI?
//-> | TIM | STRUPPI
//-> [TIM, STRUPPI]
wildCardInput = wildCardInput.split('|');
//split function can create an array which also includes spaces etc. -> e.g. [TIM, " ", "", STRUPPI, " "]
//we just want the information
var wildCardArrayIndex = 0;
for(var i = 0; i < wildCardInput.length; i++){
if(wildCardInput[i] != '' && wildCardInput[i] != ' ' && wildCardInput != undefined){
var wildCard = wildCardInput[i];
var wildCardLastCharIndex = wildCard.length - 1;
var firstCharOfWildCard = wildCard.charAt(0);
var lastCharOfWildCard = wildCard.charAt(wildCardLastCharIndex);
try{
//harmonize the wildcard string
//remove first char if it is a space.
//calculate the last index again since the length of the string changed
if(firstCharOfWildCard === ' '){
wildCard = wildCard.splice(0);
wildCardLastCharIndex = wildCard.length - 1;
lastCharOfWildCard = wildCard.charAt(wildCardLastCharIndex);
}
//if the last char is a space, remove it
//calculate the last index again since the length of the string changed
if(lastCharOfWildCard === ' '){
wildCard = wildCard.substr(0, wildCardLastCharIndex);
wildCardLastCharIndex = wildCard.length - 1;
lastCharOfWildCard = wildCard.charAt(wildCardLastCharIndex);
}
if(lastCharOfWildCard === '?'){
wildCard = wildCard.substr(0, wildCardLastCharIndex);
}
}
catch(e){
}
wildCardArray[wildCardArrayIndex] = wildCard;
wildCardArrayIndex++;
}
}
}
if(wildCardArray.length - 1 >= 0){
lastWildCardValue = wildCardArray[wildCardArray.length - 1];
}
return wildCardArray;
}
this.convertWildcardToRegex = function ( text ) {
var firstCharacter = text.charAt(0);
if(firstCharacter != "*" && firstCharacter != "_"){
var text = " " + text;
}
var lastCharacterPosition = text.length - 1;
var lastCharacter = text.charAt(lastCharacterPosition);
var modifiedText = text.replace(' _', '*').replace(' *', '*');
modifiedText = modifiedText.replace(/\*/g, '[a-zäöüß|A-ZÄÖÜ|0-9|\\s]*[a-z»A-Z|0-9|\*|-]*[a-zäöüß|A-ZÄÖÜ|0-9]*[!|.|?|\\s]*');
if(lastCharacter != "*"){
// text = text + " ";
//pattern should also match when user inputs ends with a space, ?, ! or .
modifiedText = modifiedText + '[\\s|?|!|.]*';
}
reg("REGEX: " + modifiedText);
return modifiedText;
}
this.compare = function(str1, str2) {
self.information = null;
meta2("Vergleiche " + str1 + " mit " + str2);
if ( self.check_star(str1) === true || self.check_star(str2) === true) {
// self.convertWildcardToRegex( str1 );
var matchedString = self.checkIfMessageMatchesPattern(str1, str2);
reg2( matchedString );
if (matchedString) {
reg2("matched String erfolgreich");
return matchedString;
}
else return false;
}
else
{
if (str1 === str2) return true;
else return false;
}
}
this.check_condition_type = function( state, template) {
var keys = Object.keys(state);
reg2(keys)
}
this.check_condition = function( params, node) {
reg3("CHECK CONDITION");
if ( params.condition ) {
if (Array.isArray( params.condition) ) reg2("Das ist ein Array");
if( typeof( params.condition ) === "object") {
var states = Object.keys(params.condition);
reg2( states );
for (var i = 0; i < node.template.length; i++) {
reg2("Template " + i);
var temp = node.template[i];
var tempkeys = Object.keys( temp) ;
for (var n = 0; n < tempkeys.length; n++) {
var k = tempkeys[n];
if (states.includes(k) === true) {
// reg2( "Übereinstimmung: " + params.condition[k] );
if (params.condition[k] === temp[k]) return temp.template;
}
}
}
}
}
else {
console.log("Hat das Session-Objekt eine Variable?" + node.condition );
var condition = self.session.eingabe;
if (condition) debugs("Es gibt eine solche Variable");
}
return null;
}
this.node_match = function(params, node) {
var item = null;
var res = self.compare( params.pattern, node.pattern);
reg2( res );
if (res && params.that) {
if (params.that !== node.that) res = false;
}
if (res) {
reg2("MATCH");
reg2( node );
if (!node.srai) return node;
else
{
reg2("kein srai");
var item = self.find({
pattern: node.srai,
topic: params.topic
});
if (item ) return item;
}
}
}
this.find = function( params ) {
if (self.check_star( params.pattern ) === true) meta2 ("STAR");
for (var i = 0; i < self.nodes.length; i++) {
var node = self.nodes[i];
if (params.topic) {
if (node.topic === params.topic) {
reg2("Hier gibt es ein Topic");
var item = self.node_match ( params, node );
// if ( node.condition) item.template = self.check_condition( params, node );
reg2 ( item );
if ( item && node.condition) item.template.template = self.check_condition( params, node );
if (item) return item;
}
}
else
{
var item = self.node_match ( params, node );
reg2 ( "ITEM ");
reg2( item );
if ( item && node.condition) item.template.template = self.check_condition( params, node );
if (item ) return item;
}
}
}
this.process_substitute_stars = function(ndx, str, subst ) {
debugs( subst );
switch(ndx) {
case 0:
var r = new RegExp(/\*/g);
var x = str.search(r);
str = str.replace(r, subst);
break;
case 1:
var r = new RegExp(/\*\*/g);
var x = str.search(r);
str = str.replace(r, subst);
break;
}
return str;
}
this.substitute_stars = function( str ) {
debugs( str );
var n = self.information.length;
while(n--) {
var inf = self.information[n];
str = self.process_substitute_stars(n, str, inf);
}
/*
for (var i = 0; i <self.information.length; i++) {
var inf = self.information[i];
str = self.process_substitute_stars(i, str, inf);
}
*/
return str;
}
this.get_vars = function( item ){
var index = 0;
var list = [];
var checking = true;
while(checking) {
index = item.indexOf("{", index);
var open = index;
var close = item.indexOf("}", index);
var bracket = item.slice(open+1, close);
list.push(bracket);
index = parseInt( close );
var test = item.indexOf("{", index);
if (test === -1) checking = false;
}
return list;
}
this.substitute_vars= function ( item) {
self.vars = null;
if ( item.search("{") !== -1) {
self.vars = self.get_vars( item );
reg2( self.vars);
}
}
this.condition_respond = function( item ){
self.topic_change = item.topic_change;
// console.log ( item );
if (self.information) {
var o = item.template;
console.log( self.parent );
}
}
this.get_var_type = function( item ) {
var x = Object.keys( item );
return x[0];
}
this.starvalues = ['*', '**', '***', '****', '*****'];
this.get_joker = function( item, value) {
var val;
switch(value) {
case '*':
val = self.information[0];
break;
case '**':
val = self.information[1];
break;
case '***':
val = self.information[2];
break;
case '****':
val = self.information[3];
break;
}
return val;
}
this.set_vars = function( item ) {
if ( Array.isArray( item.think ) === true) {
var t = item.think;
for (var i = 0; i < t.length; i++) {
var it = t[i];
var type = self.get_var_type( it );
var value = it[type];
var x = null;
if (self.starvalues.includes( value) === true) {
// console.log("EIN JOKER");
var x = self.get_joker( it, value );
}
else x = value;
self.session[type] = x;
}
}
else {
console.log("THINK ist kein Array");
var it = item.think;
console.log( it );
var type = self.get_var_type( it );
var value = it[type];
console.log( "TYP " + type );
var x = null;
if (self.starvalues.includes( value) === true) {
x = self.get_joker( it, value );
console.log( " RETURN " + x );
}
else x = value;
self.session[type] = x;
console.log( self.session[type] );
}
}
// the new version of variable replacement - with the session object integrated
this.getWordsBetweenCurlies = function (str) {
var results = [], re = /{([^}]+)}/g, text;
while(text = re.exec(str)) {
results.push(text[1]);
}
return results;
}
this.insert_vars = function( template ) {
var r = self.getWordsBetweenCurlies( template );
for (var i = 0; i < r.length; i++) {
var v = r[i];
v = v.replace(/ /g, "");
var s = self.session[v];
var c = '{' + r[i] + '}';
template = template.replace(c, s);
}
return template;
}
this.respond = function( item ) {
self.topic_change = item.topic_change;
self.action = item.action;
if (item.think) self.set_vars( item );
if (self.information ) {
if (Array.isArray(item.template) === true) {
var list = [];
for (var i = 0; i < item.template.length; i++) {
var it = item.template[i];
// self.substitute_vars( it ); // I don't know if this is necessary here
var s = self.insert_vars( it );
var xs = self.substitute_stars( s );
list.push( self.substitute_stars( xs ) );
}
debugs ( "RESPOND ARRAY" );
var x = parseInt( Math.random() * list.length );
reg2( "Eine Liste muss zurückgegeben werden " + list);
return list[x];
}
else {
console.log("STARS ERSETZUNG");
var s = self.insert_vars( item.template );
reg2("nach insertVars " + s);
return self.substitute_stars( s );
}
}
else
{
var s = self.insert_vars( item.template );
return s;
}
}
this.set_state = function( state) {
if (! self.state) self.state = {};
for(var index in state) {
var attr = state[index];
self.state[index] = attr;
}
reg2( self.state );
}
this.input = function( params, session, callback ) {
debugs ("AIML Input");
self.session = session;
if (params.state) self.set_state( params.state );
var res = self.find ( params );
var answer = '';
if ( res ) {
if (res.condition) {
answer = self.condition_respond ( res );
answer = 'Hmm.. hier gibt es keine Antwort';
}
else answer = self.respond( res );
// console.log(res);
// console.log("Anzahl der Patterns " + res.length );
debugs ("Die Antwort ist: " + answer );
// debugs( answer.length );
var params = {
answer: answer,
information: self.information,
vars: self.vars,
topic_change: self.topic_change,
session_id: session.id,
action: self.action
}
if (callback) callback( params );
// if (callback) callback( answer );
}
else {
console.log("Nicht identifiziert");
self.unidentifed_input( callback, params, session );
}
}
this.paradoxical = function( callback, params, session) {
reg2("paradoxical");
params.pattern = "PARDOXICAL_INTERVENTION";
params.topic = "intervention";
self.input( params, session, callback );
}
this.unidentifed_input = function( callback, params, session ) {
debugs ("not identified");
console.log( params );
params.pattern
if (session) {
params.session_id = session.id;
}
reg2("Der Bot hat kein eindeutiges Resultat bekommen");
// var x = self.input( { pattern: "UNCLEAR" });
callback( null, params);
}
this.init = function() {
if (file) {
if (Array.isArray(file)) {
meta("INIT DES AIML");
var x = resolve( file[0] );
meta( x );
for (var i = 0; i < file.length; i++ ) {
var f = resolve( file[i] );
var x = require( f );
self.nodes = self.nodes.concat( x );
}
// self.input( { pattern: "No", that: "Do you like movies?" } );
} else {
self.nodes = require( file );
reg2 ("Anzahl der Knoten " + self.nodes.length);
self.input( { pattern: "Who are you" } );
}
}
else
{
meta("Ich sollte Knoten erzeugen");
var x = {
pattern: "Hello *",
srai: null,
template: ["Hi", "How is it going"]
};
self.nodes.push(x);
self.input( { pattern: "What is the relation between Tim and Struppi", mood: "happy" } );
self.writeFile("greeting");
}
}
self.init();
}
module.exports = Aiml;