node-red-contrib-chatbot
Version:
REDBot a Chat bot for a full featured chat bot for Telegram, Facebook Messenger and Slack. Almost no coding skills required
300 lines (277 loc) • 11 kB
JavaScript
const _ = require('underscore');
const moment = require('moment');
const { ContextProviders, ChatExpress } = require('chat-platform');
const clc = require('cli-color');
const prettyjson = require('prettyjson');
const MicrosoftTeamsServer = require('../lib/platforms/microsoft-teams/index');
const utils = require('../lib/helpers/utils');
const lcd = require('../lib/helpers/lcd');
const validators = require('../lib/helpers/validators');
const RegisterType = require('../lib/node-installer');
const GlobalContextHelper = require('../lib/helpers/global-context-helper');
const GetEnvironment = require('../lib/helpers/get-environment');
const when = utils.when;
const warn = clc.yellow;
const green = clc.green;
module.exports = function(RED) {
const registerType = RegisterType(RED);
const globalContextHelper = GlobalContextHelper(RED);
const getEnvironment = GetEnvironment(RED);
var contextProviders = ContextProviders(RED);
function MicrosoftTeamsBotNode(n) {
RED.nodes.createNode(this, n);
globalContextHelper.init(this.context().global);
var node = this;
var environment = getEnvironment();
var isUsed = utils.isUsed(RED, node.id);
var startNode = utils.isUsedInEnvironment(RED, node.id, environment);
var msteamsConfigs = globalContextHelper.get('msteams') || {};
this.botname = n.botname;
this.store = n.store;
this.log = n.log;
this.usernames = n.usernames != null ? n.usernames.split(',') : [];
this.accountSid = n.accountSid;
this.fromNumber = n.fromNumber;
this.debug = n.debug;
if (!isUsed) {
// silently exit, this node is not used
return;
}
// exit if the node is not meant to be started in this environment
if (!startNode) {
// eslint-disable-next-line no-console
console.log(warn('Microsoft Teams Bot ' + this.botname + ' will NOT be launched, environment is ' + environment));
return;
}
// eslint-disable-next-line no-console
console.log(green('Microsoft Teams Bot ' + this.botname + ' will be launched, environment is ' + environment));
// get the context storage node
var contextStorageNode = RED.nodes.getNode(this.store);
// build the configuration object
var botConfiguration = {
authorizedUsernames: node.usernames,
appId: node.credentials != null && node.credentials.appId != null ? node.credentials.appId.trim() : null,
appPassword: node.credentials != null && node.credentials.appPassword != null ? node.credentials.appPassword.trim() : null,
logfile: node.log,
contextProvider: contextStorageNode != null ? contextStorageNode.contextStorage : null,
contextParams: contextStorageNode != null ? contextStorageNode.contextParams : null,
debug: node.debug
};
// check if there's a valid configuration in global settings
if (msteamsConfigs[node.botname] != null) {
var validation = validators.platform.msteams(msteamsConfigs[node.botname]);
if (validation != null) {
/* eslint-disable no-console */
console.log('');
console.log(lcd.error('Found a Microsoft Teams configuration in settings.js "' + node.botname + '", but it\'s invalid.'));
console.log(lcd.grey('Errors:'));
console.log(prettyjson.render(validation));
console.log('');
return;
} else {
console.log('');
console.log(lcd.grey('Found a valid Microsoft Teams configuration in settings.js: "' + node.botname + '":'));
console.log(prettyjson.render(msteamsConfigs[node.botname]));
console.log('');
/* eslint-enable no-console */
botConfiguration = msteamsConfigs[node.botname];
}
}
// check if context node
if (botConfiguration.contextProvider == null) {
// eslint-disable-next-line no-console
console.log(lcd.warn('No context provider specified for chatbot ' + node.botname + '. Defaulting to "memory"'));
botConfiguration.contextProvider = 'memory';
botConfiguration.contextParams = {};
}
// if chat is not already there and there's a token
if (node.chat == null && botConfiguration.appId != null) {
// check if provider exisst
if (!contextProviders.hasProvider(botConfiguration.contextProvider)) {
node.error('Error creating chatbot ' + this.botname + '. The context provider '
+ botConfiguration.contextProvider + ' doesn\'t exist.');
return;
}
// create a factory for the context provider
node.contextProvider = contextProviders.getProvider(
botConfiguration.contextProvider,
{ ...botConfiguration.contextParams, id: this.store }
);
// try to start the servers
try {
node.contextProvider.start();
node.chat = MicrosoftTeamsServer.createServer({
authorizedUsernames: botConfiguration.authorizedUsernames,
appId: botConfiguration.appId,
appPassword: botConfiguration.appPassword,
contextProvider: node.contextProvider,
logfile: botConfiguration.logfile,
debug: botConfiguration.debug,
RED: RED
});
// add extensions
RED.nodes.eachNode(function(currentNode) {
if (currentNode.type === 'chatbot-extend' && !_.isEmpty(currentNode.codeJs)
&& currentNode.platform === 'msteams') {
try {
eval(currentNode.codeJs);
} catch (e) {
lcd.node(currentNode.codeJs, {
color: lcd.red,
node: currentNode,
title: 'Syntax error in Extend Chat Server node'
});
}
}
});
// finally launch it
node.chat.start();
// handle error on sl6teack chat server
node.chat.on('error', function(error) {
node.error(error);
});
node.chat.on('warning', function(warning) {
node.warn(warning);
});
} catch(e) {
node.error(e);
}
}
this.on('close', function (done) {
node.chat.stop()
.then(function() {
return node.contextProvider.stop();
})
.then(function() {
node.chat = null;
node.contextProvider = null;
ChatExpress.reset();
ContextProviders.reset();
done();
});
});
}
registerType('chatbot-msteams-node', MicrosoftTeamsBotNode, {
credentials: {
appId: {
type: 'text'
},
appPassword: {
type: 'text'
}
}
});
function MicrosoftTeamsInNode(config) {
RED.nodes.createNode(this, config);
globalContextHelper.init(this.context().global);
var node = this;
var environment = getEnvironment();
var nodeGlobalKey = null;
this.bot = config.bot;
this.botProduction = config.botProduction;
//console.log('--- prod', this.botProduction , )
//console.log('--- bot', this.bot)
this.config = RED.nodes.getNode(environment === 'production' ? this.botProduction : this.bot);
//console.log('--- config', this.config)
if (this.config) {
this.status({fill: 'red', shape: 'ring', text: 'disconnected'});
node.chat = this.config.chat;
if (node.chat) {
this.status({fill: 'green', shape: 'ring', text: 'connected'});
nodeGlobalKey = 'msteams_master_' + this.config.id.replace('.','_');
var isMaster = false;
if (globalContextHelper.get(nodeGlobalKey) == null) {
isMaster = true;
globalContextHelper.set(nodeGlobalKey, node.id);
}
node.chat.on('message', function (message) {
var context = message.chat();
// check if there is a conversation is going on
when(context.get('currentConversationNode'))
.then(function(currentConversationNode) {
// if there's a current converation, then the message must be forwarded
if (currentConversationNode != null) {
// if the current node is master, then redirect, if not master do nothing
if (isMaster) {
when(context.remove('currentConversationNode'))
.then(function () {
// emit message directly the node where the conversation stopped
RED.events.emit('node:' + currentConversationNode, message);
});
}
} else {
node.send(message);
}
});
});
} else {
node.warn('Missing or incomplete configuration in Microsoft Teams Receiver');
}
} else {
node.warn('Missing configuration in Microsoft Teams Receiver');
}
this.on('close', function (done) {
globalContextHelper.set(nodeGlobalKey, null);
if (node.chat != null) {
node.chat.off('message');
}
done();
});
}
registerType('chatbot-msteams-receive', MicrosoftTeamsInNode);
function MicrosoftTeamsOutNode(config) {
RED.nodes.createNode(this, config);
globalContextHelper.init(this.context().global);
var node = this;
var environment = getEnvironment();
this.bot = config.bot;
this.botProduction = config.botProduction;
this.track = config.track;
this.passThrough = config.passThrough;
this.config = RED.nodes.getNode(environment === 'production' ? this.botProduction : this.bot);
if (this.config) {
this.status({fill: 'red', shape: 'ring', text: 'disconnected'});
node.chat = this.config.chat;
if (node.chat) {
this.status({fill: 'green', shape: 'ring', text: 'connected'});
} else {
node.warn('Missing or incomplete configuration in Microsoft Teams Receiver');
}
} else {
node.warn('Missing configuration in Microsoft Teams Receiver');
}
// relay message
var handler = function(msg) {
node.send(msg);
};
RED.events.on('node:' + config.id, handler);
// cleanup on close
this.on('close',function() {
RED.events.removeListener('node:' + config.id, handler);
});
this.on('input', function (message) {
var context = message.chat();
var stack = when(true);
// check if this node has some wirings in the follow up pin, in that case
// the next message should be redirected here
if (context != null && node.track && !_.isEmpty(node.wires[0])) {
stack = stack.then(function() {
return when(context.set({
currentConversationNode: node.id,
currentConversationNode_at: moment()
}));
});
}
// finally send out
stack.then(function() {
return node.chat.send(message);
}).then(function() {
// forward if not tracking
if (node.passThrough) {
node.send(message);
}
});
});
}
registerType('chatbot-msteams-send', MicrosoftTeamsOutNode);
};