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
1,012 lines (956 loc) • 33 kB
JavaScript
var _ = require('underscore');
var _s = require('underscore.string');
var clc = require('cli-color');
var prettyjson = require('prettyjson');
var utils = require('../helpers/utils');
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var lcd = require('../helpers/lcd');
var Table = require('cli-table');
var request = require('request').defaults({ encoding: null });
var identity = function(obj) { return obj; };
var when = utils.when;
var green = clc.greenBright;
var white = clc.white;
var yellow = clc.yellow;
var red = clc.red;
var orange = clc.xterm(214);
var grey = clc.blackBright;
var _messageTypes = [];
var _events = [];
var _platforms = {};
var ChatExpress = function(options) {
var _this = this;
this.options = _.extend({
contextProvider: null,
connector: null,
inboundMessage: null,
transport: null,
transportDescription: null,
chatIdKey: null,
userIdKey: null,
tsKey: null,
debug: true,
onStart: null,
onStop: null,
RED: null,
routes: null,
routesDescription: null,
events: null,
relaxChatId: false,
bundle: false
}, options);
this.ins = [];
this.outs = [];
this.uses = [];
// init platforms
_platforms[this.options.transport] = {
id: this.options.transport,
name: this.options.transportDescription,
universal: false
};
// configuration warnings
if (_.isEmpty(this.options.chatIdKey) && !_.isFunction(this.options.chatIdKey)) {
// eslint-disable-next-line no-console
console.log(yellow('WARNING: chatIdKey option is empty'));
}
if (_.isEmpty(this.options.userIdKey) && !_.isFunction(this.options.userIdKey)) {
// eslint-disable-next-line no-console
console.log(yellow('WARNING: userIdKey option is empty'));
}
if (_.isEmpty(this.options.transport)) {
// eslint-disable-next-line no-console
console.log(yellow('WARNING: transport option is empty'));
}
function evaluateParam(payload, newKey, optionKey, chatServer) {
var options = _this.options;
if (options[optionKey] != null) {
if (_.isString(options[optionKey]) && newKey != options[optionKey]) {
payload[newKey] = payload[options[optionKey]];
delete payload[options[optionKey]];
} else if (_.isFunction(options[optionKey])) {
payload[newKey] = options[optionKey].call(chatServer, payload);
}
}
}
function parseMessage(payload, options, chatServer) {
var instanceOptions = chatServer.getOptions();
payload = _.clone(payload);
// sets inbound
payload.inbound = true;
// sets the transport
if (!_.isEmpty(_this.options.transport)) {
payload.transport = _this.options.transport;
}if (!_.isEmpty(instanceOptions.transport)) {
// use the new registered transport platform if any
payload.transport = instanceOptions.transport;
}
evaluateParam(payload, 'chatId', 'chatIdKey', chatServer);
evaluateParam(payload, 'userId', 'userIdKey', chatServer);
evaluateParam(payload, 'ts', 'tsKey', chatServer);
evaluateParam(payload, 'type', 'type', chatServer);
evaluateParam(payload, 'language', 'language', chatServer);
// evaluate callbacks
var callbacks = chatServer.getCallbacks();
_(['chatId', 'userId', 'ts', 'type', 'language', 'messageId']).each(function(callbackName) {
if (_.isFunction(callbacks[callbackName])) {
payload[callbackName] = callbacks[callbackName].call(chatServer, payload)
}
});
// at this point should have at least the values chatId and type
if (payload.chatId == null && !options.relaxChatId) {
throw 'Error: inbound message key "chatId" for transport ' + _this.options.transport + ' is empty\n\n'
+ 'See here: https://github.com/guidone/node-red-contrib-chatbot/wiki/Universal-Connector-node for an '
+ 'explanation about this error.';
}
return payload;
}
function warningInboundMiddleware(message) {
// check if message is null, perhaps someone forgot to resolve a promise
if (message == null) {
// eslint-disable-next-line no-console
console.log(yellow('WARNING: a middleware is returning an empty message'));
}
if (message.payload == null) {
// eslint-disable-next-line no-console
console.log(yellow('WARNING: a middleware is returning an empty payload in message'));
}
}
function prepareForConsole(payload) {
var result = _.clone(payload) || {};
if (result.content instanceof Buffer) {
result.content = '<Buffer>';
}
if (result.ts != null) {
result.ts = result.ts.toString();
}
return result;
}
// eslint-disable-next-line max-params
function createMessage(chatId, userId, messageId, inboudMessage, chatServer) {
var options = chatServer.getOptions();
var contextProvider = options.contextProvider;
var onCreateMessage = _.isFunction(options.onCreateMessage) ? options.onCreateMessage : identity;
inboudMessage = inboudMessage || {};
return when(contextProvider.getOrCreate(chatId, {
chatId: chatId,
userId: userId,
messageId: messageId,
transport: options.transport,
authorized: false,
pending: false,
language: null
})
).then(function() {
var message = _.extend({}, inboudMessage, {
originalMessage: {
chatId: chatId,
userId: userId,
messageId: messageId,
transport: options.transport,
language: null
},
chat: function() {
return contextProvider.get(chatId);
},
api: function() {
return chatServer;
},
client: function() {
return options.connector;
}
});
return onCreateMessage.call(chatServer, message);
});
}
function inboundMessage(payload, chatServer) {
var contextProvider = chatServer.getOptions().contextProvider;
if (chatServer.isDebug()) {
// eslint-disable-next-line no-console
console.log(orange('-- INBOUND MESSAGE --'));
try {
// eslint-disable-next-line no-console
console.log(prettyjson.render(payload));
} catch (e) {
// eslint-disable-next-line no-console
console.log('PrettyJSON error');
}
// eslint-disable-next-line no-console
console.log('');
}
// parse the message to extract the minimum payload needed for chat-platform to work properly
// could raise errors, relay to chat server
try {
var parsedMessage = parseMessage(payload, _this.options, chatServer);
} catch(e) {
chatServer.emit('error', e);
return;
}
// create the node red message structure
var message = {
originalMessage: _.extend({}, payload, {
chatId: parsedMessage.chatId,
userId: parsedMessage.userId,
messageId: parsedMessage.messageId,
transport: parsedMessage.transport,
language: parsedMessage.language,
ts: parsedMessage.ts
}),
payload: {
type: parsedMessage.type,
chatId: parsedMessage.chatId,
userId: parsedMessage.userId,
ts: parsedMessage.ts,
transport: parsedMessage.transport,
inbound: true
},
chat: function() {
return contextProvider.get(parsedMessage.chatId);
},
api: function() {
return chatServer;
},
client: function() {
return chatServer.getOptions().connector;
}
};
// create empty promise
var stack = new Promise(function(resolve) {
resolve(message);
});
// if any context provider, then create the context
if (contextProvider != null) {
stack = stack
.then(function() {
return when(contextProvider.getOrCreate(parsedMessage.chatId, {
chatId: parsedMessage.chatId,
userId: parsedMessage.userId,
transport: parsedMessage.transport,
language: parsedMessage.language,
authorized: false
}));
})
.then(function() {
return when(message);
});
} else {
// eslint-disable-next-line no-console
console.log(yellow('WARNING: context provider was not specified'));
}
// run general middleware
_(_this.uses.concat(chatServer.getUseMiddleWares())).each(function(filter) {
stack = stack.then(function(message) {
// encapsulate the promise to catch the error with the source code of the middleware
return new Promise(function(resolve, reject) {
warningInboundMiddleware(message);
when(filter.call(chatServer, message))
.then(resolve)
.catch(function (error) {
error.sourceCode = filter.toString();
reject(error);
});
});
});
});
// run ins middleware without any specific type
_(_this.ins.concat(chatServer.getInMiddleWares())).each(function(filter) {
stack = stack.then(function(message) {
// encapsulate the promise to catch the error with the source code of the middleware
return new Promise(function(resolve, reject) {
warningInboundMiddleware(message);
// if message type is null
if (filter.type == null) {
when(filter.method.call(chatServer, message))
.then(resolve)
.catch(function(error) {
error.sourceCode = filter.method.toString();
reject(error);
});
} else {
resolve(message)
}
});
});
});
// run ins middleware without a specific type
_(_this.ins).each(function(filter) {
stack = stack.then(function(message) {
// encapsulate the promise to catch the error with the source code of the middleware
return new Promise(function (resolve, reject) {
warningInboundMiddleware(message);
// if message type is the same
if (filter.type === message.payload.type || filter.type === '*') {
when(filter.method.call(chatServer, message))
.then(resolve)
.catch(function(error) {
error.sourceCode = filter.method.toString();
reject(error);
});
} else {
resolve(message)
}
});
});
});
// finally
stack
.then(function(message) {
if (message.payload != null && message.payload.type != null) {
if (chatServer.isDebug()) {
// eslint-disable-next-line no-console
console.log(orange('-- RELAY MESSAGE --'));
try {
// eslint-disable-next-line no-console
console.log(prettyjson.render(prepareForConsole(message.payload)));
} catch(e) {
// eslint-disable-next-line no-console
console.log('Unable to render');
}
// eslint-disable-next-line no-console
console.log('');
}
chatServer.emit('message', message);
} else {
// do nothing
if (chatServer.isDebug()) {
// eslint-disable-next-line no-console
console.log(orange('-- DISCARDED MESSAGE (not handled by middlewares) --'));
// eslint-disable-next-line no-console
console.log('');
}
}
})
.catch(function(error) {
lcd.dump(error, 'Error in chat-platform.js');
// dump source code if present
if (error != null && !_.isEmpty(error.sourceCode)) {
// eslint-disable-next-line no-console
console.log(lcd.red(error.sourceCode));
// eslint-disable-next-line no-console
console.log('');
}
if (chatServer != null) {
chatServer.emit('error', error);
}
});
}
function warningOutboundMiddleware(message) {
if (message == null) {
// eslint-disable-next-line no-console
console.log(yellow('WARNING: a middleware is returning an empty value'));
}
}
function outboundMessage(message, chatServer) {
var instanceOptions = chatServer.getOptions();
// check if the message is from the right platform (in static class or instance)
if (message.originalMessage != null && message.originalMessage.transport !== _this.options.transport &&
message.originalMessage.transport !== instanceOptions.transport) {
// exit, it's not from the current platform
if (chatServer.isDebug()) {
// eslint-disable-next-line no-console
console.log(yellow('Skipped incoming message for platform: ' + message.originalMessage.transport));
}
return;
}
if (chatServer.isDebug()) {
// eslint-disable-next-line no-console
console.log(orange('-- OUTBOUND MESSAGE --'));
// eslint-disable-next-line no-console
console.log(prettyjson.render(prepareForConsole(message.payload)));
// eslint-disable-next-line no-console
console.log('');
}
// create empty promise
var stack = new Promise(function(resolve) {
resolve(message);
});
// run general middleware
_(_this.uses.concat(chatServer.getUseMiddleWares())).each(function(filter) {
stack = stack.then(function(message) {
return new Promise(function(resolve, reject) {
warningOutboundMiddleware(message);
when(filter.call(chatServer, message))
.then(resolve)
.catch(function (error) {
error.sourceCode = filter.toString();
reject(error);
});
});
});
});
// run outs middleware without a specific typs
_(_this.outs.concat(chatServer.getOutMiddleWares())).each(function(filter) {
stack = stack.then(function(message) {
return new Promise(function(resolve, reject) {
// check if message is null, perhaps someone forgot to resolve a promise
warningOutboundMiddleware(message);
// if message type is the same
if (filter.type == null) {
when(filter.method.call(chatServer, message))
.then(resolve)
.catch(function (error) {
error.sourceCode = filter.toString();
reject(error);
});
} else {
resolve(message);
}
});
});
});
// run outs middleware with a specific typs
_(_this.outs.concat(chatServer.getOutMiddleWares())).each(function(filter) {
stack = stack.then(function(message) {
return new Promise(function(resolve, reject) {
// check if message is null, perhaps someone forgot to resolve a promise
warningOutboundMiddleware(message);
// if message type is the same
if (message.payload != null && filter.type === message.payload.type) {
when(filter.method.call(chatServer, message))
.then(resolve)
.catch(function (error) {
error.sourceCode = filter.toString();
reject(error);
});
} else {
resolve(message);
}
});
});
});
// finally
return stack
.catch(function(error) {
// eslint-disable-next-line no-console
console.log(red(error));
if (chatServer != null) {
chatServer.emit('error', error);
}
})
.then(function(message) {
return message;
});
}
function unmountEvents(events, chatServer) {
var options = chatServer.getOptions();
var connector = options.connector;
if (connector != null) {
if (options.inboundMessageEvent != null) {
connector.off(options.inboundMessageEvent);
}
_(events).each(function (callback, eventName) {
connector.off(eventName);
});
}
}
function mountEvents(events, chatServer) {
var connector = chatServer.getOptions().connector;
if (connector != null && _.isFunction(connector.on)) {
_(events).each(function (callback, eventName) {
connector.on(eventName, callback.bind(chatServer));
});
}
return when(true);
}
function unmountRoutes(RED, routes, chatServer) {
if (routes != null) {
var endpoints = _(routes).keys();
if (!_.isEmpty(endpoints)) {
var routesCount = RED.httpNode._router.stack.length;
var idx = 0;
var stack = RED.httpNode._router.stack;
for(; idx < stack.length;) {
var route = stack[idx];
if (route != null && route.name != null) {
var routeName = String(route.name).replace('bound ', '');
if (_.contains(endpoints, routeName)) {
stack.splice(idx, 1);
} else {
idx += 1;
}
} else {
idx += 1;
}
}
if (RED.httpNode._router.stack.length >= routesCount) {
// eslint-disable-next-line no-console
chatServer.error('improperly removed some routes, this will cause unexpected results and tricky bugs');
}
}
}
}
// eslint-disable-next-line max-params
function mountRoutes(RED, routes, routesDescription, chatServer) {
if (routes != null && RED == null) {
chatServer.warn('"RED" param is empty, impossible to mount the routes');
}
if (routes != null && RED != null) {
var uiPort = RED.settings.get('uiPort');
var options = chatServer.getOptions();
// eslint-disable-next-line no-console
console.log('');
// eslint-disable-next-line no-console
console.log(grey('------ WebHooks for ' + options.transport.toUpperCase() + '----------------'));
_(routes).map(function (middleware, route) {
RED.httpNode.use(route, middleware.bind(chatServer));
var description = null;
if (routesDescription != null && _.isString(routesDescription[route])) {
description = routesDescription[route];
} else if (routesDescription != null && _.isFunction(routesDescription[route])) {
description = routesDescription[route].call(chatServer);
}
// eslint-disable-next-line no-console
console.log(green('http://localhost' + (uiPort != '80' ? ':' + uiPort : '') + route)
+ (description != null ? grey(' - ') + white(description) : ''));
return null;
});
// eslint-disable-next-line no-console
console.log('');
}
return when(true);
}
var methods = {
'in': function() {
var type = null;
var method = null;
if (arguments.length === 1) {
method = arguments[0];
} else if (arguments.length === 2) {
type = arguments[0];
method = arguments[1];
} else {
throw '.in() wrong number of parameters';
}
_this.ins.push({
type: type,
method: method
});
return methods;
},
out: function() {
var type = null;
var method = null;
if (arguments.length === 1) {
method = arguments[0];
} else if (arguments.length === 2) {
type = arguments[0];
method = arguments[1];
} else {
throw '.out() wrong number of parameters';
}
_this.outs.push({
type: type,
method: method
});
return methods;
},
use: function(method) {
_this.uses.push(method);
return methods;
},
mixin: function(obj) {
_this._mixins = _.extend(_this._mixin || {}, obj);
return methods;
},
registerMessageType: function(type, name, description) {
if (type == null || typeof type !== 'string') {
throw 'Missing type in .registerMessageType()';
}
name = name != null ? name : _s.capitalize(type);
var typeDescriptor = _(_messageTypes).findWhere({ type: type });
if (typeDescriptor == null) {
typeDescriptor = { type: type };
_messageTypes.push(typeDescriptor);
}
if (name != null) {
typeDescriptor.name = name;
}
if (description != null) {
typeDescriptor.description = description;
}
if (typeDescriptor.platforms == null) {
typeDescriptor.platforms = {};
}
typeDescriptor.platforms[options.transport] = true;
return this;
},
registerEvent: function(name, description) {
if (name == null || typeof name !== 'string') {
throw 'Missing name in .registerEvent()';
}
var eventDescriptor = _(_events).findWhere({ name: name });
if (eventDescriptor == null) {
eventDescriptor = { name: name };
_events.push(eventDescriptor);
}
eventDescriptor.name = name;
if (description != null) {
eventDescriptor.description = description;
}
if (eventDescriptor.platforms == null) {
eventDescriptor.platforms = {};
}
eventDescriptor.platforms[options.transport] = true;
return this;
},
createServer: function(options) {
options = _.extend({}, _this.options, options);
var chatServer = null;
var _ins = [];
var _uses = [];
var _outs = [];
var _callbacks = {};
var ChatServer = function(options) {
this.options = options;
this.warn = function(msg) {
var text = '[' + options.transport.toUpperCase() + '] ' + msg;
// eslint-disable-next-line no-console
console.log(yellow(text));
this.emit('warning', text);
};
this.error = function(msg) {
var text = '[' + options.transport.toUpperCase() + '] ' + msg;
// eslint-disable-next-line no-console
console.log(red(text));
this.emit('error', text);
};
this.request = function(options) {
return new Promise(function(resolve, reject) {
request(options, function(error, response, body) {
if (error) {
reject('Error downloading file ' + options.url);
} else {
resolve(body);
}
});
});
};
this.getOptions = function() {
return this.options;
};
this.isDebug = function() {
return this.options != null && this.options.debug;
};
this.getConnector = function() {
return this.options.connector;
};
this.send = function(message) {
var _this = this;
// If more than one message is enqued in the same payload, send one by one through the middlewares if there
// is not bundle option. Bundle option is used in case of multimodal messages (for example audio and video
// at the same time) that need to be sent with a unique call
if (options.bundle || !_.isArray(message.payload)) {
return outboundMessage(message, this);
} else {
var task = when(true);
_(message.payload).each(function(payload) {
task = task.then(function() {
return outboundMessage(_.extend({}, message, { payload: payload }), _this);
});
});
return task;
}
};
this.receive = function(message) {
inboundMessage(message, this);
};
// eslint-disable-next-line max-params
this.createMessage = function(chatId, userId, messageId, inboudMessage) {
return createMessage(chatId, userId, messageId, inboudMessage, this);
};
this.in = function() {
var type = null;
var method = null;
if (arguments.length === 1) {
method = arguments[0];
} else if (arguments.length === 2) {
type = arguments[0];
method = arguments[1];
} else {
throw '.in() wrong number of parameters';
}
_ins.push({
type: type,
method: method
});
return methods;
};
this.getInMiddleWares = function() {
return _ins;
};
this.use = function(method) {
_uses.push(method);
return methods;
};
this.getUseMiddleWares = function() {
return _uses;
};
this.registerMessageType = function(type, name, description) {
if (type == null || typeof type !== 'string') {
throw 'Missing type in .registerMessageType()';
}
name = name != null ? name : _s.capitalize(type);
var typeDescriptor = _(_messageTypes).findWhere({ type: type });
if (typeDescriptor == null) {
typeDescriptor = { type: type };
_messageTypes.push(typeDescriptor);
}
if (name != null) {
typeDescriptor.name = name;
}
if (description != null) {
typeDescriptor.description = description;
}
if (typeDescriptor.platforms == null) {
typeDescriptor.platforms = {};
}
typeDescriptor.platforms[options.transport] = true;
return this;
};
this.registerEvent = function(name, description) {
if (name == null || typeof name !== 'string') {
throw 'Missing name in .registerEvent()';
}
var eventDescriptor = _(_events).findWhere({ name: name });
if (eventDescriptor == null) {
eventDescriptor = { name: name };
_events.push(eventDescriptor);
}
eventDescriptor.name = name;
if (description != null) {
eventDescriptor.description = description;
}
if (eventDescriptor.platforms == null) {
eventDescriptor.platforms = {};
}
eventDescriptor.platforms[options.transport] = true;
return this;
};
this.registerPlatform = function(name, label) {
_platforms[name] = {
id: name,
name: !_.isEmpty(label) ? label : name,
universal: true
};
var options = this.getOptions();
options.transport = name;
options.transportDescription = !_.isEmpty(label) ? label : name;
return this;
};
this.out = function() {
var type = null;
var method = null;
if (arguments.length === 1) {
method = arguments[0];
} else if (arguments.length === 2) {
type = arguments[0];
method = arguments[1];
// automatically register the type
this.registerMessageType(type);
} else {
throw '.out() wrong number of parameters';
}
_outs.push({
type: type,
method: method
});
return methods;
};
this.getOutMiddleWares = function() {
return _outs;
};
this.start = function() {
var _this = this;
var stack = when(true);
var options = this.getOptions();
if (_.isFunction(options.onStart)) {
// execute on start callback, ensure it's a properly chained promise
stack = stack.then(function() {
return when(options.onStart.call(chatServer));
});
}
return stack
.then(function() {
return mountRoutes(options.RED, options.routes, options.routesDescription, _this);
})
.then(function() {
return mountEvents(options.events, _this);
})
.then(function() {
return _.isFunction(options.onStarted) ? when(options.onStarted.call(_this)) : when(true);
})
.then(function() {
if (_this.isDebug()) {
// eslint-disable-next-line no-console
console.log(green('Chat server started, transport: ') + white(options.transport));
}
// listen to inbound event
var connector = options.connector;
if (connector != null && options.inboundMessageEvent != null) {
connector.on(options.inboundMessageEvent, function (message) {
inboundMessage(message, chatServer);
});
}
_this.emit('start');
},
function(error) {
// eslint-disable-next-line no-console
_this.error(error);
});
};
this.onChatId = function(callback) {
_callbacks.chatId = callback;
};
this.onUserId = function(callback) {
_callbacks.userId = callback;
};
this.onTimestamp = function(callback) {
_callbacks.ts = callback;
};
this.onLanguage = function(callback) {
_callbacks.language = callback;
};
this.onMessageId = function(callback) {
_callbacks.messageId = callback;
};
this.getCallbacks = function() {
return _callbacks;
};
this.stop = function() {
this.emit('stop');
var options = this.getOptions();
unmountRoutes(options.RED, options.routes, this);
unmountEvents(options.events, this);
var stack = when(true);
if (_.isFunction(options.onStop)) {
stack = stack.then(function() {
return when(options.onStop.call(chatServer));
});
}
return stack;
};
EventEmitter.call(this);
};
inherits(ChatServer, EventEmitter);
_.extend(ChatServer.prototype, _this._mixins);
// create chat instance
chatServer = new ChatServer(options);
return chatServer;
}
};
return methods;
};
/*
Static methods for ChatExpress
*/
ChatExpress.getMessageTypes = function() {
return _(_messageTypes).map(function(item) {
return {
value: item.type,
label: item.name,
platforms: _(item.platforms).keys()
};
});
};
ChatExpress.getEvents = function() {
return _(_events).map(function(item) {
return {
value: item.name,
label: item.description,
platforms: _(item.platforms).keys()
};
});
};
function compatibilityTable(items, options) {
options = _.extend({ column: 'Column' }, options);
// collect platforms except universal
var platforms = _(ChatExpress.getPlatforms()).chain()
.map(function(platform) {
return platform.id;
})
.reject(function(id) {
return id === 'universal';
})
.sort()
.value();
// build header
var head = [options.column];
_(platforms).each(function(name) {
head.push(_s.capitalize(name));
});
var colAligns = ['left'];
_.times(platforms.length, function() {
colAligns.push('middle');
});
// create table
var table = new Table({
head: head,
colAligns: colAligns,
style: {
head: ['green']
}
});
// message types columns
_(items).chain()
.sortBy(function(type) {
return type.name;
})
.each(function(type) {
var row = [type.name];
_(platforms).each(function(platform) {
if (type.platforms[platform]) {
row.push('✔');
} else {
row.push('');
}
});
table.push(row);
});
// eslint-disable-next-line no-console
console.log(table.toString());
}
/**
* @method showCompatibilityChart
* Print out a compatibility char for message types
*/
ChatExpress.showCompatibilityChart = function() {
compatibilityTable(_messageTypes, { column: 'Message type' });
compatibilityTable(_events, { column: 'Event name' });
};
/**
* @method getPlatforms
* Return an array of available platforms
* @return {Array}
*/
ChatExpress.getPlatforms = function() {
var platforms = _(_platforms).keys();
return _(platforms).chain()
.map(function(platform) {
return {
id: _platforms[platform].id,
name: _platforms[platform].name,
universal: _platforms[platform].universal
};
})
.sortBy(function(platform) {
return platform.name != null ? platform.name : platform.id;
})
.value();
};
/**
* @method isSupported
* Check if a platform is supported (or a message type for a specified platform)
* @param {String} platform Platform id (telegram, facebook, ...)
* @param {String} type Message type (image, document, ...)
* @return {Boolean}
*/
ChatExpress.isSupported = function(platform, type) {
if (type == null) {
var platforms = ChatExpress.getPlatforms();
return _(platforms).findWhere({ id: platform }) != null;
} else {
var messageType = _(_messageTypes).findWhere({ type: type });
return messageType != null && messageType.platforms[platform];
}
};
module.exports = ChatExpress;