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

862 lines (812 loc) 26.2 kB
var _ = require('underscore'); var validators = require('../helpers/validators'); var moment = require('moment'); var ChatExpress = require('../chat-platform/chat-platform'); var TelegramBot = require('node-telegram-bot-api'); var request = require('request').defaults({ encoding: null }); var utils = require('../../lib/helpers/utils'); var helpers = require('../../lib/helpers/regexps'); var when = utils.when; var ChatLog = require('../chat-log'); var MESSAGE_MAX_SIZE = 4096; var Telegram = new ChatExpress({ inboundMessageEvent: 'message', transport: 'telegram', transportDescription: 'Telegram', relaxChatId: true, // sometimes chatId is not necessary (for example inline_query_id) chatIdKey: function(payload) { return payload.chat != null ? payload.chat.id : null; }, userIdKey: function(payload) { return payload.from.username; }, tsKey: function(payload) { return moment.unix(payload.date); }, type: function() { // todo remove this }, language: function(payload) { return payload != null && payload.from != null ? payload.from.language_code : null; }, onStop: function() { var options = this.getOptions(); return new Promise(function(resolve) { if (options.connector != null) { if (options.connectMode === 'webHook') { // cancel the web hook on stop options.connector.deleteWebHook() .then(function() { options.connector = null; resolve(); }, function() { resolve(); }); } else if (options.connectMode === 'polling') { // stop polling options.connector.stopPolling() .then(function() { options.connector = null; resolve(); }, function() { resolve(); }); } } else { resolve(); } }); }, onStart: function() { var options = this.getOptions(); if (options.connectMode === 'webHook') { options.connector = new TelegramBot(options.token); return options.connector.setWebHook(options.webHook); } else if (options.connectMode === 'polling') { options.connector = new TelegramBot(options.token, { polling: { params: { timeout: 10 }, interval: !isNaN(parseInt(options.polling, 10)) ? parseInt(options.polling, 10) : 1000 } }); return true; } else { throw 'Unkown connection mode'; } }, routes: { '/redbot/telegram': function(req, res) { var chatServer = this; var json = req.body; if (json.message != null) { chatServer.receive(json.message); } else if (json.edited_message != null) { chatServer.receive(json.edited_message); } res.send({ status: 'ok' }); } }, events: { inline_query: function(botMsg) { botMsg.inlineQueryId = botMsg.id; delete botMsg.id; this.receive(botMsg); }, callback_query: function(botMsg) { var chatServer = this; var options = this.getOptions(); var connector = options.connector; var chatId = botMsg.message.chat.id; var alert = false; var answer = null; if (connector.lastInlineButtons != null && connector.lastInlineButtons[chatId] != null) { // find the button with the right value, takes the answer and alert if any var button = _(connector.lastInlineButtons[chatId]).findWhere({value: botMsg.data}); if (button != null) { answer = button.answer; alert = button.alert; } // do not remove from hash, the user could click again } // copy the "from" of the message containing user information, not chatbot detail botMsg.message.from = botMsg.from; // send answer back to client connector.answerCallbackQuery(botMsg.id, { text: answer, show_alert: alert }) .then(function() { // send through the message as usual botMsg.message.text = botMsg.data; chatServer.receive(botMsg.message); }); }, pre_checkout_query: function(botMsg) { var chatServer = this; var options = this.getOptions(); var connector = options.connector; // send confirmation connector.answerPreCheckoutQuery(botMsg.id, true) .then( function() { }, function() { chatServer.error('Error on .answerPreCheckoutQuery(), checkout: ' + botMsg.id); }); }, shipping_query: function(botMsg) { var chatServer = this; // bounce the message into the flow chatServer.receive({ from: botMsg.from, shipping_query_id: botMsg.id, invoice_payload: botMsg.invoice_payload, shipping_address: botMsg.shipping_address }); } } }); // detect new user in group Telegram.in(function(message) { var botMsg = message.originalMessage; return new Promise(function(resolve) { if (_.isArray(botMsg.new_chat_members) && !_.isEmpty(botMsg.new_chat_members)) { message.payload.newUsers = botMsg.new_chat_members; message.payload.type = 'event'; message.payload.eventType = 'new-user'; resolve(message); } else { resolve(message); } }); }); // detect inline query Telegram.in(function(message) { return new Promise(function(resolve) { if (message.originalMessage.query != null) { message.payload.content = message.originalMessage.query; message.payload.type = 'inline-query'; resolve(message); } else { resolve(message); } }); }); Telegram.in(function(message) { var chatServer = this; return new Promise(function(resolve, reject) { var connector = message.client(); if (message.originalMessage.sticker != null) { connector.getFileLink(message.originalMessage.sticker.file_id) .then(function(imageUrl) { return chatServer.request({ url: imageUrl }); }) .then( function(image) { message.payload.type = 'photo'; message.payload.content = image; resolve(message); }, function(err) { reject(err); } ); } else { resolve(message); } }); }); Telegram.in(function(message) { return new Promise(function(resolve, reject) { var chatContext = message.chat(); if (_.isString(message.originalMessage.text) && !_.isEmpty(message.originalMessage.text)) { message.payload.content = message.originalMessage.text; message.payload.type = 'message'; if (helpers.isCommand(message.originalMessage.text)) { message.payload.arguments = message.originalMessage.text.split(' ').slice(1); } when(chatContext.set('message', message.payload.content)) .then(function () { resolve(message); }, function (error) { reject(error); }); } else { resolve(message); } }); }); Telegram.in(function(message) { var botMsg = message.originalMessage; return new Promise(function(resolve) { if (botMsg.location != null) { message.payload.content = botMsg.location; message.payload.type = 'location'; resolve(message); } else { resolve(message); } }); }); Telegram.in(function(message) { var botMsg = message.originalMessage; return new Promise(function(resolve) { if (botMsg.contact != null) { message.payload.content = botMsg.contact; message.payload.type = 'contact'; resolve(message); } else { resolve(message); } }); }); Telegram.in(function(message) { var botMsg = message.originalMessage; return new Promise(function(resolve) { if (botMsg.successful_payment != null) { message.payload.content = botMsg.successful_payment; message.payload.type = 'payment'; resolve(message); } else { resolve(message); } }); }); Telegram.in(function(message) { var botMsg = message.originalMessage; return new Promise(function(resolve) { if (botMsg.shipping_query_id != null) { message.payload.shippingQueryId = botMsg.shipping_query_id; message.payload.payload = botMsg.payload; message.payload.shippingAddress = botMsg.shipping_address; message.payload.type = 'invoice-shipping'; resolve(message); } else { resolve(message); } }); }); Telegram.in(function(message) { var vars = {}; var context = message.chat(); var options = this.getOptions(); var authorizedUsernames = options.authorizedUsernames; var userId = String(message.originalMessage.from.id); var username = String(message.originalMessage.from.username); return new Promise(function(resolve, reject) { if (!_.isEmpty(message.originalMessage.from.first_name)) { vars.firstName = message.originalMessage.from.first_name; } if (!_.isEmpty(message.originalMessage.from.last_name)) { vars.lastName = message.originalMessage.from.last_name; } vars.authorized = false; if (_.isArray(authorizedUsernames) && !_.isEmpty(authorizedUsernames)) { if (_.contains(authorizedUsernames, userId) || _.contains(authorizedUsernames, username)) { vars.authorized = true; } } return when(context.set(vars)) .then(function() { resolve(message); }, reject); }); }); Telegram.in(function(message) { var connector = this.getOptions().connector; var botMsg = message.originalMessage; var chatServer = this; return new Promise(function(resolve, reject) { var type = null; var fileId = null; // download one of these binary types if (botMsg.photo != null) { type = 'photo'; fileId = _(botMsg.photo).last().file_id; } else if (botMsg.video != null) { type = 'video'; fileId = botMsg.video.file_id; } else if (botMsg.voice != null) { type = 'audio'; fileId = botMsg.voice.file_id; } else if (botMsg.document != null) { type = 'document'; fileId = botMsg.document.file_id; } // if not one of these pass thru if (type != null) { message.payload.type = type; connector.getFileLink(fileId) .then(function(path) { return chatServer.downloadFile(path); }) .then( function(buffer) { message.payload.content = buffer; message.payload.caption = botMsg.caption; resolve(message); }, function() { reject('Error downloading photo'); }); } else { resolve(message); } }); }); Telegram.out('photo', function(message) { var connector = this.getOptions().connector; return new Promise(function(resolve, reject) { connector.sendPhoto(message.payload.chatId, message.payload.content, { caption: message.payload.caption }).then(function() { resolve(message); }, function(error) { reject(error); }); }); }); Telegram.out('video', function(message) { var connector = this.getOptions().connector; return new Promise(function(resolve, reject) { connector.sendVideo(message.payload.chatId, message.payload.content, { caption: message.payload.caption }).then(function() { resolve(message); }, function(error) { reject(error); }); }); }); Telegram.out('document', function(message) { var connector = this.getOptions().connector; return new Promise(function(resolve, reject) { connector.sendDocument(message.payload.chatId, message.payload.content, {}, { caption: message.payload.caption, filename: !_.isEmpty(message.payload.filename) ? message.payload.filename.replace(/\.[^.]+$/, '') : null }).then(function() { resolve(message); }, function(error) { reject(error); }); }); }); Telegram.out('audio', function(message) { var connector = this.getOptions().connector; return new Promise(function(resolve, reject) { connector.sendVoice(message.payload.chatId, message.payload.content, { caption: message.payload.caption }).then(function() { resolve(message); }, function(error) { reject(error); }); }); }); Telegram.out('reset-buttons', function(message) { var options = this.getOptions(); var connector = options.connector; var parseMode = options.parseMode != null ? options.parseMode : null; return new Promise(function(resolve, reject) { // finally send connector.sendMessage( message.payload.chatId, message.payload.content, { reply_markup: { remove_keyboard: true }, parse_mode: parseMode } ).then(function() { resolve(message); }, function(error) { reject(error); }); }); }); Telegram.out('buttons', function(message) { var options = this.getOptions(); var connector = options.connector; var parseMode = options.parseMode != null ? options.parseMode : null; var chatServer = this; return new Promise(function(resolve, reject) { if (_.isEmpty(message.payload.content)) { reject('Buttons node needs a non-empty message'); return; } var keyboard = [[]]; _(message.payload.buttons).each(function(button) { var json = null; switch(button.type) { case 'keyboardButton': json = button.label; break; case 'newline': keyboard.push([]); break; default: chatServer.warn('Telegram is not able to handle this button type "' + button.type + '"'); } if (json != null) { // add the button to the last row, if any keyboard[keyboard.length - 1].push(json); } }); var buttons = { reply_markup: JSON.stringify({ keyboard: keyboard, resize_keyboard: true, one_time_keyboard: true }), parse_mode: parseMode }; // finally send connector.sendMessage( message.payload.chatId, message.payload.content, buttons ).then(function() { resolve(message); }, function(error) { reject(error); }); }); }); Telegram.out('request', function(message) { var options = this.getOptions(); var connector = options.connector; return new Promise(function(resolve, reject) { var keyboard = null; if (message.payload.requestType === 'location') { keyboard = [ [{ text: !_.isEmpty(message.payload.label) ? message.payload.label : 'Send your position', request_location: true }] ]; } else if (message.payload.requestType === 'phone-number') { keyboard = [ [{ text: !_.isEmpty(message.payload.label) ? message.payload.label : 'Send your phone number', request_contact: true }] ]; } if (keyboard != null) { connector .sendMessage(message.payload.chatId, message.payload.content, { reply_markup: JSON.stringify({ keyboard: keyboard, 'resize_keyboard': true, 'one_time_keyboard': true }) }) .then(function() { resolve(message); }, function(error) { reject(error); }); } else { reject('Request type not supported'); } }); }); Telegram.out('inline-buttons', function(message) { var options = this.getOptions(); var connector = options.connector; var parseMode = options.parseMode != null ? options.parseMode : null; var chatServer = this; var context = message.chat(); return new Promise(function (resolve, reject) { // create inline buttons, docs for this is https://core.telegram.org/bots/api#inlinekeyboardmarkup // create the first array of array var inlineKeyboard = [[]]; var chatId = message.payload.chatId; // cycle through buttons, add new line at the end if flag _(message.payload.buttons).each(function(button) { var json = null; switch(button.type) { case 'url': json = { text: button.label, url: button.url }; break; case 'postback': json = { text: button.label, callback_data: !_.isEmpty(button.value) ? button.value : button.label }; break; case 'newline': inlineKeyboard.push([]); break; default: chatServer.warn('Telegram is not able to handle this button type "' + button.type + '"'); } if (json != null) { // add the button to the last row, if any inlineKeyboard[inlineKeyboard.length - 1].push(json); } }); // store the last buttons, this will be handled by the receiver if (connector.lastInlineButtons == null) { connector.lastInlineButtons = {}; } connector.lastInlineButtons[chatId] = message.payload.buttons; // send buttons or edit var task = null; if (message.originalMessage.modifyMessageId != null) { task = connector.editMessageReplyMarkup(JSON.stringify({ inline_keyboard: inlineKeyboard }), { chat_id: chatId, message_id: message.originalMessage.modifyMessageId }); } else { // finally send task = connector.sendMessage(chatId, message.payload.content, { reply_markup: JSON.stringify({ inline_keyboard: inlineKeyboard }), parse_mode: parseMode }); } // finally task .then(function(result) { return when(context.set('messageId', result.message_id)) }) .then(function() { resolve(message) }, function(error) { reject(error); }); }); }); Telegram.out('message', function(message) { var options = this.getOptions(); var connector = message.client(); var context = message.chat(); var parseMode = options.parseMode != null ? options.parseMode : null; return new Promise(function (resolve, reject) { var task = null; if (message.originalMessage.modifyMessageId != null) { task = connector.editMessageText(message.payload.content, { chat_id: message.payload.chatId, message_id: message.originalMessage.modifyMessageId }); } else { task = when(true); // split message into chucks of max size _(utils.split(message.payload.content, MESSAGE_MAX_SIZE)) .each(function(partial) { task = task.then(function() { return connector.sendMessage(message.payload.chatId, partial, { parse_mode: parseMode, disable_notification: message.payload.silent }); }); }); } task .then(function(result) { return when(context.set('messageId', result.message_id)) }) .then(function() { resolve(message); }, function(error) { reject(error); }); }); }); Telegram.out('action', function(message) { var options = this.getOptions(); var connector = options.connector; return new Promise(function (resolve, reject) { connector.sendChatAction(message.payload.chatId, message.payload.waitingType != null ? message.payload.waitingType : 'typing') .then(function() { resolve(message); }, function(error) { reject(error); }); }) }); Telegram.out('location', function(message) { var options = this.getOptions(); var connector = options.connector; return new Promise(function (resolve, reject) { connector.sendLocation( message.payload.chatId, message.payload.content.latitude, message.payload.content.longitude, message.payload.options ).then( function() { resolve(message); }, function(error) { reject(error); }); }) }); Telegram.out('inline-query-answer', function(message) { var options = this.getOptions(); var connector = options.connector; return new Promise(function (resolve, reject) { var inlineQueryId = message.originalMessage.inlineQueryId; // do some checks if (_.isEmpty(inlineQueryId)) { reject('Empty inlineQueryId'); return; } if (!_.isArray(message.payload.content)) { reject('Invalid inline query answer'); return; } var options = {}; if (validators.integer(message.payload.caching)) { options.cache_time = message.payload.caching; } if (validators.boolean(message.payload.personal)) { options.is_personal = message.payload.personal; } connector.answerInlineQuery(inlineQueryId, message.payload.content, options) .then( function() { resolve(message); }, function(error) { reject(error); }); }); }); Telegram.out('invoice', function(message) { var options = this.getOptions(); var connector = options.connector; return new Promise(function (resolve, reject) { if (_.isEmpty(options.providerToken)) { reject('Missing providerToken in Telegram chatbot configuration, needed to send an invoice.'); return; } var invoiceOptions = { need_name: message.payload.needName, need_phone_number: message.payload.needPhoneNumber, need_email: message.payload.needEmail, need_shipping_address: message.payload.needShippingAddress, is_flexible: message.payload.isFlexible }; var prices = _(message.payload.prices).map(function(price) { return { label: price.label, amount: Math.floor(parseFloat(price.amount) * 100) } }); if (!_.isEmpty(message.payload.photoUrl)) { invoiceOptions.photo_url = message.payload.photoUrl; invoiceOptions.photo_width = message.payload.photoWidth; invoiceOptions.photo_height = message.payload.photoHeight; } connector.sendInvoice( message.payload.chatId, message.payload.title, message.payload.description, message.payload.payload, options.providerToken, message.payload.startParameter, message.payload.currency, prices, invoiceOptions) .then( function() { // do nothing }, function(error) { reject(error); }); }); }); Telegram.out('invoice-shipping', function(message) { var options = this.getOptions(); var connector = options.connector; return new Promise(function (resolve, reject) { var shippingOptions = _(message.payload.shippingOptions).map(function(shippingOption) { return { id: shippingOption.id, title: shippingOption.label, prices: [{ label: shippingOption.id, amount: Math.floor(shippingOption.amount * 100) }] }; }); var canShip = !_.isEmpty(shippingOptions); connector.answerShippingQuery(message.payload.shippingQueryId, canShip, { shipping_options: shippingOptions }) .then( function() { // do nothing }, function(error) { reject(error); } ); }); }); Telegram.out('sticker', function(message) { var options = this.getOptions(); var connector = options.connector; return new Promise(function(resolve, reject) { connector.sendSticker(message.payload.chatId, message.payload.content) .then( function() { // do nothing }, function(error) { reject(error); } ); }); }); // log messages, these should be the last Telegram.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; }); Telegram.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; }); Telegram.mixin({ downloadFile: function (url, token) { return new Promise(function (resolve, reject) { var options = { url: url, headers: { 'Authorization': 'Bearer ' + token } }; request(options, function (error, response, body) { if (error) { reject(error); } else { resolve(body); } }); }); } }); Telegram.registerMessageType('action', 'Action', 'Send an action message (like typing, ...)'); Telegram.registerMessageType('audio', 'Audio', 'Send an audio message'); Telegram.registerMessageType('buttons', 'Buttons', 'Open keyboard buttons in the client'); Telegram.registerMessageType('command', 'Command', 'Detect command-like messages'); Telegram.registerMessageType('contact', 'Contact', 'Send a contact'); Telegram.registerMessageType('document', 'Document', 'Send a document or generic file'); Telegram.registerMessageType('inline-buttons', 'Inline buttons', 'Send a message with inline buttons'); Telegram.registerMessageType('inline-query', 'Inline Query', 'Receives inline queries'); Telegram.registerMessageType('invoice', 'Invoice', 'Send a payment invoice'); Telegram.registerMessageType('invoice-shipping', 'Invoice Shipping Query', 'Define an invoice shipping option'); Telegram.registerMessageType('location', 'Location', 'Send a map location message'); Telegram.registerMessageType('message', 'Message', 'Send a plain text message'); Telegram.registerMessageType('photo', 'Photo', 'Send a photo message'); Telegram.registerMessageType('payment', 'Payment', 'Payment received message'); Telegram.registerMessageType('request', 'Request', 'Trigger a request of location or contact'); Telegram.registerMessageType('video', 'Video', 'Send video message'); Telegram.registerMessageType('sticker', 'Sticker', 'Send a sticker'); Telegram.registerMessageType('event', 'Event', 'Event from platform'); Telegram.registerEvent('new-user', 'New user'); module.exports = Telegram;