/** * Created by Karl-Heinz Wind **/ // Temporary fix for 0.30.0 api process.env["NTBA_FIX_319"] = 1; module.exports = function (RED) { "use strict"; var telegramBot = require('node-telegram-bot-api'); const Agent = require('socks5-https-client/lib/Agent') // -------------------------------------------------------------------------------------------- // The configuration node // holds the token // and establishes the connection to the telegram bot // you can either select between polling mode and webhook mode. function TelegramBotNode(n) { RED.nodes.createNode(this, n); var self = this; // contains all the nodes that make use of the bot connection maintained by this configuration node. this.users = []; this.status = "disconnected"; // Reading configuration properties... this.botname = n.botname; this.verbose = n.verboselogging; this.usernames = []; if (n.usernames) { this.usernames = n.usernames.split(','); } this.chatids = []; if (n.chatids) { this.chatids = n.chatids.split(',').map(function (item) { return parseInt(item, 10); }); } this.baseApiUrl = n.baseapiurl; this.updateMode = n.updatemode; if(!this.updateMode){ this.updateMode = "polling"; } // 1. optional when polling mode is used this.pollInterval = parseInt(n.pollinterval); if (isNaN(this.pollInterval)) { this.pollInterval = 300; } // 2. optional when webhook is used. this.botHost = n.bothost; this.publicBotPort = parseInt(n.publicbotport); if (isNaN(this.publicBotPort)) { this.publicBotPort = 8443; } this.localBotPort = parseInt(n.localbotport); if (isNaN(this.localBotPort)) { this.localBotPort = this.publicBotPort; } // 3. optional when webhook and self signed certificate is used this.privateKey = n.privatekey; this.certificate = n.certificate; this.useSelfSignedCertificate = n.useselfsignedcertificate; this.sslTerminated = n.sslterminated; // 4. optional when request via SOCKS5 is used. this.useSocks = n.usesocks; if(this.useSocks){ this.socksRequest = { agentClass: Agent, agentOptions: { socksHost: n.sockshost, socksPort: n.socksport, socksUsername: n.socksusername, socksPassword: n.sockspassword, }, }; } this.useWebhook = false; if (this.updateMode == "webhook") { if (this.botHost && (this.sslTerminated || (this.privateKey && this.certificate))) { this.useWebhook = true; } else{ self.error("Configuration data for webhook is not complete. Defaulting to polling mode."); } } // Activates the bot or returns the already activated bot. this.getTelegramBot = function () { if (!this.telegramBot) { if (this.credentials) { this.token = this.credentials.token; if (this.token) { this.token = this.token.trim(); if (!this.telegramBot) { if (this.useWebhook){ var webHook = { autoOpen: true, port : this.localBotPort, }; if (!this.sslTerminated) { webHook.key = this.privateKey; webHook.cert = this.certificate; } var options = { webHook: webHook, baseApiUrl: this.baseApiUrl, request: this.socksRequest, }; this.telegramBot = new telegramBot(this.token, options); this.telegramBot.on('webhook_error', function (error) { self.setUsersStatus({ fill: "red", shape: "ring", text: "webhook error" }) if(self.verbose) { self.warn("Webhook error: " + error.message); } // TODO: check if we should abort in future when this happens // self.abortBot(error.message, function () { // self.warn("Bot stopped: Webhook error."); // }); }); var botUrl = "https://" + this.botHost + ":" + this.publicBotPort + "/" + this.token; var setWebHookOptions; if (!this.sslTerminated && this.useSelfSignedCertificate){ setWebHookOptions = { certificate: options.webHook.cert, }; } this.telegramBot.setWebHook(botUrl, setWebHookOptions).then(function (success) { if(self.verbose) { self.telegramBot.getWebHookInfo().then(function (result) { self.log("Webhook enabled: " + JSON.stringify(result)); }); } if(success) { self.status = "connected"; } else { self.abortBot("Failed to set webhook " + botUrl, function () { self.warn("Bot stopped: Webhook not set."); }); } }); } else { var polling = { autoStart: true, interval: this.pollInterval } var options = { polling: polling, baseApiUrl: this.baseApiUrl, request: this.socksRequest, }; this.telegramBot = new telegramBot(this.token, options); self.status = "connected"; this.telegramBot.on('polling_error', function (error) { self.setUsersStatus({ fill: "red", shape: "ring", text: "polling error" }) if(self.verbose) { self.warn(error.message); var stopPolling = false; var hint; if (error.message === "ETELEGRAM: 401 Unauthorized") { hint = "Please check if the bot token is valid: " + self.credentials.token; stopPolling = true; } else if (error.message.startsWith("EFATAL: Error: connect ETIMEDOUT")) { hint = "Timeout connecting to server. Trying again."; } else if (error.message.startsWith("EFATAL: Error: read ECONNRESET")) { hint = "Network connection may be down. Trying again."; } else if (error.message.startsWith("EFATAL: Error: getaddrinfo EAI_AGAIN")) { hint = "Network connection may be down. Trying again."; } else if (error.message.startsWith("EFATAL: Error: getaddrinfo ENOTFOUND")) { hint = "Network connection may be down. Trying again."; } else if (error.message.startsWith("EFATAL: Error: SOCKS connection failed. Connection refused.")) { hint = "Username or password may be be wrong. Aborting."; stopPolling = true; } else if (error.message.startsWith("EFATAL: Error: connect ETIMEDOUT")) { hint = "Server did not respond. Maybe proxy blocked polling. Trying again."; } else { // unknown error occured... we simply ignore it. hint = "Unknown error. Trying again."; } if (stopPolling) { self.abortBot(error.message, function () { self.warn("Bot stopped: " + hint); }); } else { // here we simply ignore the bug and continue polling. // The following line is removed as this would create endless log files if(self.verbose){ self.warn(hint); } } } }); } this.telegramBot.on('error', function (error) { self.warn("Bot error: " + error.message); self.abortBot(error.message, function () { self.warn("Bot stopped: Fatal Error."); }); }); } } } } return this.telegramBot; } this.on('close', function (done) { self.abortBot("closing", done); }); this.abortBot = function (hint, done) { if (self.telegramBot !== null) { if (self.telegramBot._polling){ self.telegramBot.stopPolling() .then(function () { self.telegramBot = null; self.status = "disconnected"; self.setUsersStatus({ fill: "red", shape: "ring", text: "bot stopped. " + hint }) done(); }); } if (self.telegramBot._webHook){ self.telegramBot.deleteWebHook(); self.telegramBot.closeWebHook() .then(function () { self.telegramBot = null; self.status = "disconnected"; self.setUsersStatus({ fill: "red", shape: "ring", text: "bot stopped. " + hint }) done(); }); } } else { self.status = "disconnected"; self.setUsersStatus({ fill: "red", shape: "ring", text: "bot stopped. " + hint }) done(); } } this.isAuthorizedUser = function (user) { var isAuthorized = false; if (self.usernames.length > 0) { if (self.usernames.indexOf(user) >= 0) { isAuthorized = true; } } return isAuthorized; } this.isAuthorizedChat = function (chatid) { var isAuthorized = false; var length = self.chatids.length; if (length > 0) { for (var i = 0; i < length; i++) { var id = self.chatids[i]; if (id === chatid) { isAuthorized = true; break; } } } return isAuthorized; } this.isAuthorized = function (chatid, user) { var isAuthorizedUser = self.isAuthorizedUser(user); var isAuthorizedChatId = self.isAuthorizedChat(chatid); var isAuthorized = false; if (isAuthorizedUser || isAuthorizedChatId) { isAuthorized = true; } else { if (self.chatids.length === 0 && self.usernames.length === 0) { isAuthorized = true; } } return isAuthorized; } this.register = function (node) { var id = node.id; if (self.users.indexOf(id) === -1) { self.users.push(id); } else { if(self.verbose) { // This warnin was removed as it caused confusion: see https://github.com/windkh/node-red-contrib-telegrambot/issues/87 // self.warn("Node " + id + " registered twice at the configuration node: ignoring."); } } } this.setUsersStatus = function (status) { self.users.forEach(function (nodeId) { var userNode = RED.nodes.getNode(nodeId); if(userNode) { userNode.status(status); } }); } } RED.nodes.registerType("telegram bot", TelegramBotNode, { credentials: { token: { type: "text" } } }); // adds the caption of the message into the options. function addCaptionToMessageOptions(msg) { var options = msg.payload.options; if (options === undefined) { options = {}; } if (msg.payload.caption !== undefined) { options.caption = msg.payload.caption; } msg.payload.options = options; return msg; } function getPhotoIndexWithHighestResolution(photoArray) { var photoIndex = 0; var highestResolution = 0; photoArray.forEach(function (photo, index, array) { var resolution = photo.width * photo.height; if (resolution > highestResolution) { highestResolution = resolution; photoIndex = index; } }); return photoIndex; } // creates the message details object from the original message function getMessageDetails(botMsg) { // Note that photos and videos can be sent as media group. The bot will receive the contents as single messages. // Therefore the photo and video messages can contain a mediaGroupId if so.... var messageDetails; if (botMsg.text) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'message', content: botMsg.text }; } else if (botMsg.photo) { // photos are sent using several resolutions. Therefore photo is an array. We choose the one with the highest resolution in the array. var index = getPhotoIndexWithHighestResolution(botMsg.photo); messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'photo', content: botMsg.photo[index].file_id, caption: botMsg.caption, date: botMsg.date, blob: true, photos: botMsg.photo, mediaGroupId: botMsg.media_group_id }; } else if (botMsg.audio) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'audio', content: botMsg.audio.file_id, caption: botMsg.caption, date: botMsg.date, blob: true }; } else if (botMsg.document) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'document', content: botMsg.document.file_id, caption: botMsg.caption, date: botMsg.date, blob: true }; } else if (botMsg.sticker) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'sticker', content: botMsg.sticker.file_id, date: botMsg.date, blob: true }; } else if (botMsg.video) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'video', content: botMsg.video.file_id, caption: botMsg.caption, date: botMsg.date, blob: true, mediaGroupId: botMsg.media_group_id }; } else if (botMsg.video_note) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'video_note', content: botMsg.video_note.file_id, caption: botMsg.caption, date: botMsg.date, blob: true }; } else if (botMsg.voice) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'voice', content: botMsg.voice.file_id, caption: botMsg.caption, date: botMsg.date, blob: true }; } else if (botMsg.location) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'location', content: botMsg.location, date: botMsg.date }; } else if (botMsg.venue) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'venue', content: botMsg.venue, date: botMsg.date }; } else if (botMsg.contact) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'contact', content: botMsg.contact, date: botMsg.date }; } else if (botMsg.new_chat_title) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'new_chat_title', content: botMsg.new_chat_title, date: botMsg.date }; } else if (botMsg.new_chat_photo) { // photos are sent using several resolutions. Therefore photo is an array. We choose the one with the highest resolution in the array. var index = getPhotoIndexWithHighestResolution(botMsg.new_chat_photo); messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'new_chat_photo', content: botMsg.new_chat_photo[index].file_id, date: botMsg.date, blob: true, photos: botMsg.new_chat_photo }; } else if (botMsg.new_chat_members) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'new_chat_members', content: botMsg.new_chat_members, date: botMsg.date, }; } else if (botMsg.left_chat_member) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'left_chat_member', content: botMsg.left_chat_member, date: botMsg.date, }; } else if (botMsg.delete_chat_photo) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'delete_chat_photo', content: botMsg.delete_chat_photo, date: botMsg.date }; } else if (botMsg.pinned_message) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'pinned_message', content: botMsg.pinned_message, date: botMsg.date }; } else if (botMsg.channel_chat_created) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'channel_chat_created', content: botMsg.channel_chat_created, date: botMsg.date }; } else if (botMsg.group_chat_created) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'group_chat_created', content: botMsg.group_chat_created, chat: botMsg.chat, date: botMsg.date }; } else if (botMsg.supergroup_chat_created) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'supergroup_chat_created', content: botMsg.supergroup_chat_created, chat: botMsg.chat, date: botMsg.date }; } else if (botMsg.migrate_from_chat_id) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'migrate_from_chat_id', content: botMsg.migrate_from_chat_id, chat: botMsg.chat, date: botMsg.date }; } else if (botMsg.migrate_to_chat_id) { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'migrate_to_chat_id', content: botMsg.migrate_to_chat_id, chat: botMsg.chat, date: botMsg.date }; } else { // unknown type --> no output // TODO: // 'successful_payment', // 'invoice', // 'game', } return messageDetails; } // -------------------------------------------------------------------------------------------- // The input node receives messages from the chat. // the message details are stored in the payload // chatId // type // content // depending on type caption and date is part of the output, too. // The original message is stored next to payload. // // The message ist send to output 1 if the message is from an authorized user // and to output2 if the message is not from an authorized user. // // message : content string // photo : content file_id of first image in array // audio : content file_id // document: content file_id of document // sticker : content file_id // video : content file_id // voice : content file_id // location: content is object with latitude and longitude // contact : content is full contact object function TelegramInNode(config) { RED.nodes.createNode(this, config); var node = this; this.bot = config.bot; this.config = RED.nodes.getNode(this.bot); if (this.config) { this.config.register(node); node.status({ fill: "red", shape: "ring", text: "not connected" }); node.telegramBot = this.config.getTelegramBot(); if (node.telegramBot) { node.status({ fill: "green", shape: "ring", text: "connected" }); node.telegramBot.on('message', function (botMsg) { node.status({ fill: "green", shape: "ring", text: "connected" }); var username = botMsg.from.username; var chatid = botMsg.chat.id; var messageDetails = getMessageDetails(botMsg); if (messageDetails) { var msg = { payload: messageDetails, originalMessage: botMsg }; if (node.config.isAuthorized(chatid, username)) { // downloadable "blob" message? if (messageDetails.blob) { var fileId = msg.payload.content; node.telegramBot.getFileLink(fileId).then(function (weblink) { msg.payload.weblink = weblink; // download and provide with path if (config.saveDataDir) { node.telegramBot.downloadFile(fileId, config.saveDataDir).then(function (path) { msg.payload.path = path; node.send([msg, null]); }); } else { node.send([msg, null]); } }); // vanilla message } else { node.send([msg, null]); } } else { if(node.config.verbose){ node.warn("Unauthorized incoming call from " + username); } node.send([null, msg]); } } }); } else { node.warn("bot not initialized"); node.status({ fill: "red", shape: "ring", text: "bot not initialized" }); } } else { node.warn("config node failed to initialize."); node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" }); } this.on("close", function () { node.telegramBot.off('message'); node.status({}); }); } RED.nodes.registerType("telegram receiver", TelegramInNode); // -------------------------------------------------------------------------------------------- // The input node receives a command from the chat. // The message details are stored in the payload // chatId // type // content // depending on type caption and date is part of the output, too. // The original message is stored next to payload. // // message : content string function TelegramCommandNode(config) { RED.nodes.createNode(this, config); var node = this; var command = config.command; var strict = config.strict; this.bot = config.bot; this.config = RED.nodes.getNode(this.bot); if (this.config) { this.config.register(node); node.status({ fill: "red", shape: "ring", text: "not connected" }); node.telegramBot = this.config.getTelegramBot(); node.botname = this.config.botname; if (node.telegramBot) { node.status({ fill: "green", shape: "ring", text: "connected" }); node.telegramBot.on('message', function (botMsg) { node.status({ fill: "green", shape: "ring", text: "connected" }); var username = botMsg.from.username; var chatid = botMsg.chat.id; if (node.config.isAuthorized(chatid, username)) { var msg; var messageDetails; if (botMsg.text) { var message = botMsg.text; var tokens = message.split(" "); var isChatCommand = tokens[0] === command; var command2 = command + "@" + node.botname; var isDirectCommand = tokens[0] === command2; var isGroupChat = chatid < 0; if (isDirectCommand || (isChatCommand && !isGroupChat) || (isChatCommand && isGroupChat && !strict)) { var remainingText; if(isDirectCommand) { remainingText = message.replace(command2, ""); } else { remainingText = message.replace(command, ""); } messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'message', content: remainingText }; msg = { payload: messageDetails, originalMessage: botMsg }; node.send([msg, null]); } else { messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'message', content: botMsg.text }; msg = { payload: messageDetails, originalMessage: botMsg }; node.send([null, msg]); } } else { // unknown type --> no output } } else { // ignoring unauthorized calls if(node.config.verbose){ node.warn("Unauthorized incoming call from " + username); } } }); } else { node.warn("bot not initialized."); node.status({ fill: "red", shape: "ring", text: "no bot token found in config" }); } } else { node.warn("config node failed to initialize."); node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" }); } this.on("close", function () { node.telegramBot.off('message'); node.status({}); }); } RED.nodes.registerType("telegram command", TelegramCommandNode); // -------------------------------------------------------------------------------------------- // The input node receives an event from the chat. // The type of event can be configured: // - callback_query // - inline_query // - edited_message // - channel_post // - edited_channel_post // - edited_message_text // The message details are stored in the payload // chatId // messageId // type // content // depending on type from and date is part of the output, too. // The original message is stored next to payload. // callback_query : content string function TelegramEventNode(config) { RED.nodes.createNode(this, config); var node = this; this.bot = config.bot; this.event = config.event; this.autoAnswerCallback = config.autoanswer; this.config = RED.nodes.getNode(this.bot); if (this.config) { this.config.register(node); node.status({ fill: "red", shape: "ring", text: "not connected" }); node.telegramBot = this.config.getTelegramBot(); node.botname = this.config.botname; if (node.telegramBot) { node.status({ fill: "green", shape: "ring", text: "connected" }); node.telegramBot.on(this.event, (botMsg) => { node.status({ fill: "green", shape: "ring", text: "connected" }); var username; var chatid; if (botMsg.chat) { //channel username = botMsg.chat.username; chatid = botMsg.chat.id; } else if (botMsg.from) { //private, group, supergroup username = botMsg.from.username; chatid = botMsg.from.id; } else { node.error("username or chatid undefined"); } if (node.config.isAuthorized(chatid, username)) { var msg; var messageDetails; switch (this.event) { case 'callback_query': var callbackQueryId = botMsg.id; messageDetails = { chatId: chatid, messageId: botMsg.message.message_id, type: 'callback_query', content: botMsg.data, callbackQueryId: callbackQueryId, from: botMsg.from }; if (node.autoAnswer) { node.telegramBot.answerCallbackQuery(callbackQueryId).then(function (result) { // Nothing to do here ; }); } break; // /setinline must be set before in botfather see https://core.telegram.org/bots/inline case 'inline_query': var inlineQueryId = botMsg.id; messageDetails = { chatId: chatid, type: 'inline_query', content: botMsg.query, inlineQueryId: inlineQueryId, offset: botMsg.offset, from: botMsg.from, location: botMsg.location // location is only available when /setinlinegeo is set in botfather }; // Right now this is not supported as a result is required! //if (node.autoAnswer) { // // result = https://core.telegram.org/bots/api#inlinequeryresult // node.telegramBot.answerInlineQuery(inlineQueryId, results).then(function (result) { // // Nothing to do here // ; // }); //} break; case 'edited_message': messageDetails = { chatId: chatid, messageId: botMsg.message_id, type: "edited_message", content: botMsg.text, editDate: botMsg.edit_date, date: botMsg.date, from: botMsg.from, chat: botMsg.chat, location: botMsg.location // for live location updates }; break; // the text of an already sent message. case 'edited_message_text': messageDetails = { chatId: chatid, type: "edited_message_text", messageId: botMsg.message_id, content: botMsg.text, editDate: botMsg.edit_date, date: botMsg.date, chat: botMsg.chat }; break; // the caption of a document or an image ... case 'edited_message_caption': messageDetails = { chatId: chatid, type: "edited_message_caption", messageId: botMsg.message_id, content: botMsg.caption, editDate: botMsg.edit_date, date: botMsg.date, chat: botMsg.chat }; break; case 'channel_post': messageDetails = { chatId: chatid, messageId: botMsg.message_id, type: "channel_post", content: botMsg.text, date: botMsg.date, chat: botMsg.chat }; break; case 'edited_channel_post': messageDetails = { chatId: chatid, type: "edited_channel_post", messageId: botMsg.message_id, content: botMsg.text, editDate: botMsg.edit_date, date: botMsg.date, chat: botMsg.chat }; break; case 'edited_channel_post_text': messageDetails = { chatId: chatid, type: "edited_channel_post_text", messageId: botMsg.message_id, content: botMsg.text, editDate: botMsg.edit_date, date: botMsg.date, chat: botMsg.chat }; break; case 'edited_channel_post_caption': messageDetails = { chatId: chatid, type: "edited_channel_post_caption", messageId: botMsg.message_id, content: botMsg.caption, editDate: botMsg.edit_date, date: botMsg.date, chat: botMsg.chat }; break; // TODO: implement those // chosen_inline_result, // shippingQuery, preCheckoutQuery default: } if (messageDetails != null) { msg = { payload: messageDetails, originalMessage: botMsg }; node.send(msg); } } else { // ignoring unauthorized calls if(node.config.verbose){ node.warn("Unauthorized incoming call from " + username); } } }); } else { node.warn("bot not initialized."); node.status({ fill: "red", shape: "ring", text: "no bot token found in config" }); } } else { node.warn("config node failed to initialize."); node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" }); } this.on("close", function () { node.telegramBot.off(this.event); node.status({}); }); } RED.nodes.registerType("telegram event", TelegramEventNode); // -------------------------------------------------------------------------------------------- // The output node sends to the chat and passes the msg through. // The payload needs three fields // chatId : string destination chat // type : string type of message to send // content : message content // The type is a string can be any of the following: // message content is String // photo content is String|stream.Stream|Buffer // audio content is String|stream.Stream|Buffer // document content is String|stream.Stream|Buffer // sticker content is String|stream.Stream|Buffer // video content is String|stream.Stream|Buffer // voice content is String|stream.Stream|Buffer // location content is an object that contains latitude and logitude // contact content is full contact object // mediaGroup content is array of mediaObject // action content is one of the following: // typing, upload_photo, record_video, upload_video, record_audio, upload_audio, // upload_document, find_location, record_video_note, upload_video_note function TelegramOutNode(config) { RED.nodes.createNode(this, config); var node = this; this.bot = config.bot; this.config = RED.nodes.getNode(this.bot); if (this.config) { this.config.register(node); node.status({ fill: "red", shape: "ring", text: "not connected" }); node.telegramBot = this.config.getTelegramBot(); if (node.telegramBot) { node.status({ fill: "green", shape: "ring", text: "connected" }); } else { node.warn("bot not initialized."); node.status({ fill: "red", shape: "ring", text: "bot not initialized" }); } } else { node.warn("config node failed to initialize."); node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" }); } this.hasContent = function (msg) { var hasContent; if (msg.payload.content) { hasContent = true; } else { node.warn("msg.payload.content is empty"); hasContent = false; } return hasContent; } this.on('input', function (msg, nodeSend, nodeDone) { nodeSend = nodeSend || function() { node.send.apply(node,arguments) }; node.status({ fill: "green", shape: "ring", text: "connected" }); if (msg.payload) { if (msg.payload.chatId) { if (msg.payload.type) { var chatId = msg.payload.chatId; var type = msg.payload.type; addCaptionToMessageOptions(msg); switch (type) { // -------------------------------------------------------------------- case 'message': if (this.hasContent(msg)) { // the maximum message size is 4096 so we must split the message into smaller chunks. var chunkSize = 4000; var message = msg.payload.content; var done = false; do { var messageToSend; if (message.length > chunkSize) { messageToSend = message.substr(0, chunkSize); message = message.substr(chunkSize); } else { messageToSend = message; done = true; } node.telegramBot.sendMessage(chatId, messageToSend, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }).catch(function (err) { // markdown error? try plain mode if ( String(err).includes("can't parse entities in message text:") && msg.payload.options && msg.payload.options.parse_mode === 'Markdown' ) { delete msg.payload.options.parse_mode; node.telegramBot.sendMessage(chatId, messageToSend, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); return; } if (nodeDone) { nodeDone(err); } else { throw err; } }); } while (!done) } break; case 'photo': if (this.hasContent(msg)) { node.telegramBot.sendPhoto(chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'mediaGroup': if(this.hasContent(msg)) { if(Array.isArray(msg.payload.content)) { for (var i = 0; i < msg.payload.content.length; i++) { var mediaItem = msg.payload.content[i]; if(typeof mediaItem.type !== "string") { node.warn("msg.payload.content[" + i + "].type is not a string it is " + typeof mediaItem.type); break; } if(mediaItem.media === undefined) { node.warn("msg.payload.content[" + i + "].media is not defined"); break; } } node.telegramBot.sendMediaGroup(chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } else { node.warn("msg.payload.content for mediaGroup is not an array of mediaItem"); } } break; case 'audio': if (this.hasContent(msg)) { node.telegramBot.sendAudio(chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'document': if (this.hasContent(msg)) { node.telegramBot.sendDocument(chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'sticker': if (this.hasContent(msg)) { node.telegramBot.sendSticker(chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'video': if (this.hasContent(msg)) { node.telegramBot.sendVideo(chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'video_note': if (this.hasContent(msg)) { node.telegramBot.sendVideoNote(chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'voice': if (this.hasContent(msg)) { node.telegramBot.sendVoice(chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'location': if (this.hasContent(msg)) { node.telegramBot.sendLocation(chatId, msg.payload.content.latitude, msg.payload.content.longitude, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'venue': if (this.hasContent(msg)) { node.telegramBot.sendVenue(chatId, msg.payload.content.latitude, msg.payload.content.longitude, msg.payload.content.title, msg.payload.content.address, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'contact': if (this.hasContent(msg)) { if (msg.payload.content.last_name) { if (!msg.payload.options) { msg.payload.options = {}; } msg.payload.options.last_name = msg.payload.content.last_name; } node.telegramBot.sendContact(chatId, msg.payload.content.phone_number, msg.payload.content.first_name, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; // -------------------------------------------------------------------- case 'editMessageLiveLocation': if (this.hasContent(msg)) { node.telegramBot.editMessageLiveLocation(msg.payload.content.latitude, msg.payload.content.longitude, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'stopMessageLiveLocation': // This message requires the options to be set! //if (this.hasContent(msg)) { node.telegramBot.stopMessageLiveLocation(msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); //} break; case 'editMessageCaption': case 'editMessageText': case 'editMessageReplyMarkup': if (this.hasContent(msg)) { node.telegramBot[type](msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; msg.payload.sentMessageId = result.message_id; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'callback_query': if (this.hasContent(msg)) { // The new signature expects one object instead of three arguments. var callbackQueryId = msg.payload.callbackQueryId; var options = { callback_query_id: callbackQueryId, text: msg.payload.content, show_alert: msg.payload.options }; node.telegramBot.answerCallbackQuery(callbackQueryId, options).then(function (result) { msg.payload.content = result; // true if succeeded nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'inline_query': if (this.hasContent(msg)) { var inlineQueryId = msg.payload.inlineQueryId; var results = msg.payload.results; // this type requires results to be set: see https://core.telegram.org/bots/api#inlinequeryresult node.telegramBot.answerInlineQuery(inlineQueryId, results).then(function (result) { msg.payload.content = result; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'action': if (this.hasContent(msg)) { node.telegramBot.sendChatAction(chatId, msg.payload.content).then(function (result) { msg.payload.content = result; // true if succeeded nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; // -------------------------------------------------------------------- // Some of the following functions require the bot to be administrator of the chat/channel case 'getChatAdministrators': case 'getChatMembersCount': case 'getChat': case 'leaveChat': case 'exportChatInviteLink': case 'unpinChatMessage': case 'deleteChatPhoto': node.telegramBot[type](chatId).then(function (result) { msg.payload.content = result; nodeSend(msg); if (nodeDone) { nodeDone(); } }); break; case 'kickChatMember': case 'unbanChatMember': case 'restrictChatMember': case 'promoteChatMember': case 'getChatMember': // The userId must be passed in msg.payload.content: note that this is is a number not the username. // Right now there is no way for resolving the user_id by username in the official API. if (this.hasContent(msg)) { node.telegramBot[type](chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; // -------------------------------------------------------------------- case 'setChatTitle': case 'setChatDescription': case 'pinChatMessage': case 'deleteMessage': if (this.hasContent(msg)) { node.telegramBot[type](chatId, msg.payload.content).then(function (result) { msg.payload.content = result; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; case 'setChatPhoto': if (this.hasContent(msg)) { node.telegramBot[type](chatId, msg.payload.content, msg.payload.options).then(function (result) { msg.payload.content = result; nodeSend(msg); if (nodeDone) { nodeDone(); } }); } break; // TODO: // getUserProfilePhotos, getFile, // setChatStickerSet, deleteChatStickerSet // sendGame, setGameScore, getGameHighScores // sendInvoice, answerShippingQuery, answerPreCheckoutQuery // getStickerSet, uploadStickerFile, createNewStickerSet, addStickerToSet, setStickerPositionInSet, deleteStickerFromSet default: // unknown type nothing to send. } } else { node.warn("msg.payload.type is empty"); } } else { node.warn("msg.payload.chatId is empty"); } } else { node.warn("msg.payload is empty"); } }); } RED.nodes.registerType("telegram sender", TelegramOutNode); // -------------------------------------------------------------------------------------------- // The output node receices the reply for a specified message and passes the msg through. // The payload needs three fields // chatId : string destination chat // sentMessageId : string the id of the message the reply coresponds to. // content : message content function TelegramReplyNode(config) { RED.nodes.createNode(this, config); var node = this; this.bot = config.bot; this.config = RED.nodes.getNode(this.bot); if (this.config) { this.config.register(node); node.status({ fill: "red", shape: "ring", text: "not connected" }); node.telegramBot = this.config.getTelegramBot(); if (node.telegramBot) { node.status({ fill: "green", shape: "ring", text: "connected" }); } else { node.warn("bot not initialized."); node.status({ fill: "red", shape: "ring", text: "bot not initialized" }); } } else { node.warn("config node failed to initialize."); node.status({ fill: "red", shape: "ring", text: "config node failed to initialize" }); } this.on('input', function (msg, nodeSend, nodeDone) { node.status({ fill: "green", shape: "ring", text: "connected" }); if (msg.payload) { if (msg.payload.chatId) { if (msg.payload.sentMessageId) { var chatId = msg.payload.chatId; var messageId = msg.payload.sentMessageId; node.telegramBot.onReplyToMessage(chatId, messageId, function (botMsg) { var messageDetails = getMessageDetails(botMsg); if (messageDetails) { var newMsg = { payload: messageDetails, originalMessage: botMsg }; nodeSend(newMsg); if (nodeDone) { nodeDone(); } } }); } else { node.warn("msg.payload.sentMessageId is empty"); } } else { node.warn("msg.payload.chatId is empty"); } } else { node.warn("msg.payload is empty"); } }); } RED.nodes.registerType("telegram reply", TelegramReplyNode); }