UNPKG

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

545 lines (502 loc) 16 kB
var _ = require('underscore'); var validators = require('../helpers/validators'); var moment = require('moment'); var ChatExpress = require('../chat-platform/chat-platform'); var request = require('request').defaults({ encoding: null }); var utils = require('../../lib/helpers/utils'); var when = utils.when; var ChatLog = require('../chat-log'); var FileCache = require('../file-cache'); var ViberBot = require('viber-bot').Bot; var path = require('path'); var TextMessage = require('viber-bot').Message.Text; var UrlMessage = require('viber-bot').Message.Url; //var ContactMessage = require('viber-bot').Message.Contact; var PictureMessage = require('viber-bot').Message.Picture; //var VideoMessage = require('viber-bot').Message.Video; var LocationMessage = require('viber-bot').Message.Location; //var StickerMessage = require('viber-bot').Message.Sticker; var RichMediaMessage = require('viber-bot').Message.RichMedia; //var KeyboardMessage = require('viber-bot').Message.Keyboard; var FileMessage = require('viber-bot').Message.File; /* API DOCS: https://developers.viber.com/docs/api/nodejs-bot-api/#sendMessage */ var Viber = new ChatExpress({ inboundMessageEvent: 'message', transport: 'viber', transportDescription: 'Viber', relaxChatId: true, // sometimes chatId is not necessary (for example inline_query_id) chatIdKey: function(payload) { //return payload.message_token; return payload.sender != null ? payload.sender.id : null; }, userIdKey: function(payload) { return payload.sender != null ? payload.sender.id : null; }, tsKey: function(payload) { return moment.unix(payload.timestamp); }, type: function() { // todo remove this }, language: function(payload) { return payload != null && payload.sender != null ? payload.sender.language : null; }, onStop: function() { var cache = this.cache; return new Promise(function(resolve) { cache.destroy(); resolve(); }); }, onStart: function() { var options = this.getOptions(); options.connector = new ViberBot({ authToken: options.token, name: options.botname, avatar: null }); this.cache = FileCache(); return true; }, onStarted: function() { var options = this.getOptions(); var bot = options.connector; return new Promise(function(resolve, reject) { bot.setWebhook(options.webHook) .then( resolve, function() { reject( 'Viber server is unable to reach web hook: ' + options.webHook + '\n' + 'Check you internet connection, this address must be reachable outside your local network' ); } ) }); }, routes: { '/redbot/viber/files/:id': function(req, res) { var server = this; var cache = server.cache; if (cache.exists(req.params.id)) { if (req.method === 'GET') { cache.get(req.params.id) .then(function(file) { cache.stats(); //cache.cleanup(); res.contentType(file.contentType); res.end(file.buffer, 'binary'); }); } else if (req.method === 'HEAD') { res.sendStatus(200); } } else { res.sendStatus(404); } }, '/redbot/viber': function(req, res) { if (req.body.event === 'message') { this.receive(req.body); } // ack res.sendStatus(200); } } }); Viber.mixin({ downloadFile: function (url) { return new Promise(function (resolve, reject) { var options = { url: url }; request(options, function (error, response, body) { if (error) { reject(error); } else { resolve(body); } }); }); } }); // get plain text messages Viber.in(function(message) { return new Promise(function(resolve, reject) { var chatContext = message.chat(); var payload = message.originalMessage; if (payload.message != null && payload.message.type === 'text') { message.payload.content = payload.message.text; message.payload.type = 'message'; when(chatContext.set('message', message.payload.content)) .then(function () { resolve(message); }, function (error) { reject(error); }); } else { resolve(message); } }); }); // send text message Viber.out('message', function(message) { var connector = message.client(); var context = message.chat(); var recipient = { id: message.payload.chatId }; return new Promise(function (resolve, reject) { if (validators.url(message.payload.content)) { message = new UrlMessage(message.payload.content); } else { message = new TextMessage(message.payload.content); } connector .sendMessage(recipient, message) .then(function(messageId) { return when(context.set('messageId', messageId)); }) .then(function() { resolve(message); }, function(error) { reject(error); }); }); }); // get picture Viber.in(function(message) { var server = this; return new Promise(function(resolve, reject) { var payload = message.originalMessage; if (payload.message != null && payload.message.type === 'picture') { server.downloadFile(payload.message.media) .then( function(buffer) { message.payload.content = buffer; message.payload.type = 'photo'; resolve(message); }, function(error) { reject(error); }); } else { resolve(message); } }); }); // location Viber.in(function(message) { return new Promise(function(resolve) { var payload = message.originalMessage; if (payload.message != null && payload.message.type === 'location') { message.payload.content = { latitude: payload.message.location.lat, longitude: payload.message.location.lon }; message.payload.type = 'location'; resolve(message); } else { resolve(message); } }); }); // get document Viber.in(function(message) { var server = this; return new Promise(function(resolve, reject) { var payload = message.originalMessage; if (payload.message != null && payload.message.type === 'file') { server.downloadFile(payload.message.media) .then( function(buffer) { message.payload.content = buffer; message.payload.type = 'document'; resolve(message); }, function(error) { reject(error); }); } else { resolve(message); } }); }); // send document Viber.out('document', function(message) { var cache = this.cache; var connector = message.client(); var context = message.chat(); var options = this.getOptions(); return new Promise(function (resolve, reject) { var webHook = options.webHook; var recipient = { id: message.payload.chatId }; // create a unique id and store in the cache to be retrieved via URL using ndoe-red var fileId = _.uniqueId('document_'); var fileBackUrl = webHook + '/files/' + fileId; var file = new FileMessage(fileBackUrl, message.payload.content.byteLength, message.payload.filename); // viber doesn't dowload files, only when the user clicks on the link, so retain in memory for a while // then remove after use cache .store(fileId, message.payload.content, { contentType: message.payload.mimeType, expiresIn: 10, removeAfterUse: true }) .then(function() { return connector.sendMessage(recipient, file); }) .then(function(messageId) { return when(context.set('messageId', messageId)); }) .then(function() { resolve(message); }, function(error) { reject(error); }); }); }); // send photo Viber.out('photo', function(message) { var cache = this.cache; var connector = message.client(); var context = message.chat(); var options = this.getOptions(); return new Promise(function (resolve, reject) { var webHook = options.webHook; var recipient = { id: message.payload.chatId }; // check if buffer if (!(message.payload.content instanceof Buffer)) { reject('Content is not a Buffer'); return; } // check if size is less than 1Mb if (message.payload.content.byteLength > 1024*1024) { reject('Viber only supports .JPG images less than 1 Mb'); return; } // get extension var fileExtension = null; if (message.payload.filename != null) { fileExtension = path.extname(message.payload.filename).toLowerCase(); } // check extention if (fileExtension != null && fileExtension !== '.jpg' && fileExtension !== '.jpeg') { reject('Viber only supports images in JPG format'); return; } // create a unique id and store in the cache to be retrieved via URL using ndoe-red var fileId = _.uniqueId('image_'); var pictureBackUrl = webHook + '/files/' + fileId; // build viber message var image = new PictureMessage(pictureBackUrl, message.payload.caption); cache .store(fileId, message.payload.content, { contentType: 'image/jpeg' }) .then(function() { connector.sendMessage(recipient, image); }) .then(function(messageId) { return when(context.set('messageId', messageId)); }) .then(function() { resolve(message); }, function(error) { reject(error); }); }); }); /* Viber.out('request', function(message) { var options = this.getOptions(); var connector = options.connector; var context = message.chat(); var translatedButton = null; return new Promise(function(resolve, reject) { var keyboard = null; if (message.payload.requestType === 'location') { translatedButton = { ActionType: 'share-phone', BgColor: '#ffffff', Text: !_.isEmpty(message.payload.label) ? message.payload.label : 'Send your position', Rows: 1, Columns: 3 }; } else if (message.payload.requestType === 'phone-number') { } // create message type var richMedia = new RichMediaMessage({ ButtonsGroupColumns: 6, ButtonsGroupRows: 1, BgColor: '#FFFFFF', Buttons: [translatedButton] }); var recipient = {id: message.payload.chatId}; connector .sendMessage(recipient, richMedia) .then(function (messageId) { return when(context.set('messageId', messageId)); }) .then(function () { resolve(message); }, function (error) { console.log('errrr', error); reject(error); }); }); }); */ Viber.out('inline-buttons', function(message) { var options = this.getOptions(); var connector = options.connector; var context = message.chat(); var buttons = message.payload.buttons; // API docs: https://developers.viber.com/docs/all/#buttons-parameters // get the size of the buttons for each row, maximum 3 buttons for row // the viber grid is 6 colums, per RedBot design a button has a minimum of 2 columns // so maximum 3 buttons per row, buttons that exceeds are wrapped in a new row var columnSizePerRow = []; var currentButtons = 0; buttons.forEach(function(button, idx) { var isLast = idx === (buttons.length - 1); if (button.type === 'newline' || currentButtons === 3) { columnSizePerRow.push(6 / currentButtons); currentButtons = 0; } else if (isLast) { currentButtons += 1; columnSizePerRow.push(6 / currentButtons); currentButtons = 0; } else { currentButtons += 1; } }); var rowsNumber = columnSizePerRow.length; // build the list fo translated buttons var rowIndex = 0; var currentRowSize = 0; var translatedButtons = []; buttons.forEach(function(button) { var obj = null; switch (button.type) { case 'url': obj = { ActionBody: button.url, ActionType: 'open-url', BgColor: '#ffffff', Text: button.label, Rows: 1, Columns: columnSizePerRow[rowIndex] }; currentRowSize += columnSizePerRow[rowIndex]; break; case 'postback': obj = { ActionBody: button.value, ActionType: 'reply', BgColor: '#ffffff', Text: button.label, Rows: 1, Columns: columnSizePerRow[rowIndex] }; currentRowSize += columnSizePerRow[rowIndex]; break; case 'newline': currentRowSize = 0; rowIndex += 1; break; } translatedButtons.push(obj); if (currentRowSize >= 6) { currentRowSize = 0; rowIndex += 1; } }); // create message type var richMedia = new RichMediaMessage({ ButtonsGroupColumns: 6, ButtonsGroupRows: rowsNumber, BgColor: '#FFFFFF', Buttons: translatedButtons }); var recipient = { id: message.payload.chatId }; var task = new Promise(function(resolve) { resolve(); }); return new Promise(function (resolve, reject) { task .then(function() { // send enclosed message separately, some platform like Telegram doesn't allow sending buttons without a msg if (!_.isEmpty(message.payload.content)) { return connector.sendMessage(recipient, new TextMessage(message.payload.content)) } return true; }) .then(function() { return connector.sendMessage(recipient, richMedia) }) .then(function (messageId) { return when(context.set('messageId', messageId)); }) .then(function () { resolve(message); }, function (error) { reject(error); }); }); }); Viber.out('location', function(message) { var options = this.getOptions(); var connector = options.connector; var context = message.chat(); var recipient = { id: message.payload.chatId }; var location = new LocationMessage(message.payload.content.latitude, message.payload.content.longitude); return new Promise(function (resolve, reject) { connector .sendMessage(recipient, location) .then(function(messageId) { return when(context.set('messageId', messageId)); }) .then(function() { resolve(message); }, function(error) { reject(error); }); }); }); // log messages, these should be the last Viber.out(function(message) { var options = this.getOptions(); var logfile = options.logfile; var chatContext = message.chat(); if (!_.isEmpty(logfile)) { return when(chatContext.all()) .then(function(variables) { var chatLog = new ChatLog(variables); return chatLog.log(message, logfile); }); } return message; }); Viber.in('*', function(message) { var options = this.getOptions(); var logfile = options.logfile; var chatContext = message.chat(); if (!_.isEmpty(logfile)) { return when(chatContext.all()) .then(function(variables) { var chatLog = new ChatLog(variables); return chatLog.log(message, logfile); }); } return message; }); Viber.registerMessageType('audio', 'Audio', 'Send an audio message'); Viber.registerMessageType('buttons', 'Buttons', 'Open keyboard buttons in the client'); Viber.registerMessageType('command', 'Command', 'Detect command-like messages'); Viber.registerMessageType('document', 'Document', 'Send a document or generic file'); Viber.registerMessageType('inline-buttons', 'Inline buttons', 'Send a message with inline buttons'); Viber.registerMessageType('location', 'Location', 'Send a map location message'); Viber.registerMessageType('message', 'Message', 'Send a plain text message'); Viber.registerMessageType('photo', 'Photo', 'Send a photo message'); Viber.registerMessageType('video', 'Video', 'Send video message'); module.exports = Viber;