prisme-flow
Version:
prisme platform flow engine
571 lines (496 loc) • 25.1 kB
JavaScript
/**
* Created by prisme.io on 09/06/2017.
*/
module.exports = function (RED) {
"use strict";
var telegramBot = require('node-telegram-bot-api');
// --------------------------------------------------------------------------------------------
// The configuration node
// holds the token
// and establishes the connection to the telegram bot
function TelegramBotNode(n) {
RED.nodes.createNode(this, n);
var self = this;
this.botname = n.botname;
this.status = "disconnected";
this.nodes = [];
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);
});
}
if (this.credentials) {
this.token = this.credentials.token;
if (this.token) {
this.token = this.token.trim();
if (!this.telegramBot) {
this.telegramBot = new telegramBot(this.token, { polling: true });
this.telegramBot.setMaxListeners(0);
self.status = "connected";
this.telegramBot.on('polling_error', function(error) {
if (error.message === "ETELEGRAM: 401 Unauthorized") {
self.warn(error.message);
self.abortBot(function () {
self.warn("Bot stopped. Please check if the bot token is valid: " + self.credentials.token);
});
}
});
this.telegramBot.on('webhook_error', function(error) {
self.warn(error.message);
});
}
}
}
this.on('close', function (done) {
self.abortBot(done);
});
this.abortBot = function(done) {
if (self.telegramBot !== null && self.telegramBot._polling) {
self.telegramBot.stopPolling()
.then(function () {
self.telegramBot = null;
self.status = "disconnected";
self.setNodesStatus({ fill: "red", shape: "ring", text: "bot stopped." })
done();
});
}
else {
self.status = "disconnected";
self.setNodesStatus({ fill: "red", shape: "ring", text: "bot stopped." })
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) {
if (self.nodes.indexOf(node) === -1){
self.nodes.push(node);
}
else {
self.warn("Node " + node.id + " registered twice at the configuration node: ignoring.");
}
}
this.setNodesStatus = function (status) {
self.nodes.forEach(function (node) {
node.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;
}
// creates the message details object from the original message
function getMessageDetails(botMsg) {
var messageDetails;
if (botMsg.text) {
messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'message', content: botMsg.text };
} else if (botMsg.photo) {
messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'photo', content: botMsg.photo[0].file_id, caption: botMsg.caption, date: botMsg.date, blob: true };
} 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, 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 };
} 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 };
} else if (botMsg.venue) {
messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'venue', content: botMsg.venue };
} else if (botMsg.contact) {
messageDetails = { chatId: botMsg.chat.id, messageId: botMsg.message_id, type: 'contact', content: botMsg.contact };
} else {
// unknown type --> no output
}
return messageDetails;
}
// --------------------------------------------------------------------------------------------
// The input node receives messages from the chat.
// the message details are stored in the playload
// 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
// docuemnt: 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);
this.status({ fill: "red", shape: "ring", text: "disconnected" });
node.telegramBot = this.config.telegramBot;
if (node.telegramBot) {
this.status({ fill: "green", shape: "ring", text: "connected" });
node.telegramBot.on('message', function (botMsg) {
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? download and provide with path
if (config.saveDataDir && messageDetails.blob) {
node.telegramBot.downloadFile(messageDetails.content, config.saveDataDir).then(function(path) {
msg.payload.path = path;
node.send([msg, null]);
});
// vanilla message
} else {
node.send([msg, null]);
}
} else {
// node.warn("Unauthorized incoming call from " + username);
node.send([null, msg]);
}
}
});
} else {
node.warn("bot not initialized");
this.status({ fill: "red", shape: "ring", text: "bot not initialized" });
}
} else {
node.warn("config node failed to initialize.");
this.status({ fill: "red", shape: "ring", text: "config node failed to initialize." });
}
}
RED.nodes.registerType("telegram receiver", TelegramInNode);
// --------------------------------------------------------------------------------------------
// The input node receives a command from the chat.
// The message details are stored in the playload
// 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;
this.bot = config.bot;
this.config = RED.nodes.getNode(this.bot);
if (this.config) {
this.config.register(node);
this.status({ fill: "red", shape: "ring", text: "disconnected" });
node.telegramBot = this.config.telegramBot;
node.botname = this.config.botname;
if (node.telegramBot) {
this.status({ fill: "green", shape: "ring", text: "connected" });
node.telegramBot.on('message', function (botMsg) {
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 command2 = command + "@" + node.botname;
if (tokens[0] === command || tokens[0] === command2) {
var 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
// node.warn("Unauthorized incoming call from " + username);
}
});
} else {
node.warn("bot not initialized.");
this.status({ fill: "red", shape: "ring", text: "no bot token found in config" });
}
} else {
node.warn("config node failed to initialize.");
this.status({ fill: "red", shape: "ring", text: "config node failed to initialize." });
}
}
RED.nodes.registerType("telegram command", TelegramCommandNode);
// --------------------------------------------------------------------------------------------
// 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
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);
this.status({ fill: "red", shape: "ring", text: "disconnected" });
node.telegramBot = this.config.telegramBot;
if (node.telegramBot) {
this.status({ fill: "green", shape: "ring", text: "connected" });
} else {
node.warn("bot not initialized.");
this.status({ fill: "red", shape: "ring", text: "bot not initialized" });
}
} else {
node.warn("config node failed to initialize.");
this.status({ fill: "red", shape: "ring", text: "config node failed to initialize." });
}
this.on('input', function (msg) {
if (msg.payload) {
if (msg.payload.content) {
if (msg.payload.chatId) {
if (msg.payload.type) {
var chatId = msg.payload.chatId;
var type = msg.payload.type;
addCaptionToMessageOptions(msg);
switch (type) {
case 'message':
// 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 (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
} while (!done)
break;
case 'photo':
node.telegramBot.sendPhoto(chatId, msg.payload.content, msg.payload.options).then(function (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
break;
case 'audio':
node.telegramBot.sendAudio(chatId, msg.payload.content, msg.payload.options).then(function (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
break;
case 'document':
node.telegramBot.sendDocument(chatId, msg.payload.content, msg.payload.options).then(function (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
break;
case 'sticker':
node.telegramBot.sendSticker(chatId, msg.payload.content, msg.payload.options).then(function (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
break;
case 'video':
node.telegramBot.sendVideo(chatId, msg.payload.content, msg.payload.options).then(function (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
break;
case 'voice':
node.telegramBot.sendVoice(chatId, msg.payload.content, msg.payload.options).then(function (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
break;
case 'location':
node.telegramBot.sendLocation(chatId, msg.payload.content.latitude, msg.payload.content.longitude, msg.payload.options).then(function (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
break;
case 'venue':
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 (sent) {
msg.payload.sentMessageId = sent.message_id;
node.send(msg);
}).catch(function (err) {
msg.error = err;
node.send(msg);
});
break;
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.content 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);
this.status({ fill: "red", shape: "ring", text: "disconnected" });
node.telegramBot = this.config.telegramBot;
if (node.telegramBot) {
this.status({ fill: "green", shape: "ring", text: "connected" });
} else {
node.warn("bot not initialized.");
this.status({ fill: "red", shape: "ring", text: "bot not initialized" });
}
} else {
node.warn("config node failed to initialize.");
this.status({ fill: "red", shape: "ring", text: "config node failed to initialize." });
}
this.on('input', function (msg) {
if (msg.payload) {
if (msg.payload.chatId) {
if (msg.payload.messageId) {
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 };
node.send(newMsg);
}
});
} else {
node.warn("msg.payload.messageId is empty");
}
} else {
node.warn("msg.payload.chatId is empty");
}
} else {
node.warn("msg.payload is empty");
}
});
}
RED.nodes.registerType("telegram reply", TelegramReplyNode);
}