dockerized_openAger/nodered/rootfs/data/node_modules/node-red-contrib-telegrambot/telegrambot/99-telegrambot.js

1389 lines
68 KiB
JavaScript
Raw Permalink Normal View History

2020-10-17 16:42:50 +00:00
/**
* 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);
}