Intial Commit

This commit is contained in:
valki
2020-10-17 18:42:50 +02:00
commit 664c6d8ca3
5892 changed files with 759183 additions and 0 deletions

View File

@@ -0,0 +1,550 @@
<!-- Created by Karl-Heinz Wind -->
<!-- ------------------------------------------------------------------------------------------ -->
<script type="text/javascript">
RED.nodes.registerType('telegram bot', {
category: 'config',
defaults: {
botname: { value: "", required: true },
usernames: { value: "", required: false },
chatids: { value: "", required: false },
baseapiurl: { value: "", required: false },
updatemode: { value: "polling", required: true },
// only for polling mode
pollinterval: { value: 300, required: false, validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
// only for socks5 proxy support
usesocks: { value: false, required: false },
sockshost: { value: "", required: false },
socksport: { value: 6667, required: false, validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
socksusername: { value: "anonymous", required: false },
sockspassword: { value: "", required: false },
// only for webhook mode
bothost: { value: "", required: false },
localbotport: { value: 8443, required: false, validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
publicbotport: { value: 8443, required: false, validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
// only for webhook and certificate
privatekey: { value: "", required: false },
certificate: { value: "", required: false },
useselfsignedcertificate: { value: false, required: false },
sslterminated: { value: false, required: false },
verboselogging: { value: false, required: false }
},
credentials: {
token: { type: "text" }
},
label: function () {
return this.botname;
},
oneditprepare: function() {
// polling or webhook
var updateOptions = function() {
var mode = $("#node-config-input-updatemode").val();
if (mode == "webhook") {
$("#webhook").show();
$("#polling").hide();
} else if (mode == "polling"){
$("#webhook").hide();
$("#polling").show();
} else {
// option not supported
}
};
updateOptions();
$("#node-config-input-updatemode").change(updateOptions);
// sslTerminated on / off
var sslTerminated = function() {
var mode = $('#node-config-input-sslterminated').prop('checked');
if (mode === false) {
$('#node-config-input-privatekey').parent().show();
$('#node-config-input-certificate').parent().show();
} else {
$('#node-config-input-privatekey').parent().hide();
$('#node-config-input-certificate').parent().hide();
}
};
sslTerminated();
$('#node-config-input-sslterminated').change(sslTerminated);
// socks5 on / off
var useSocks5 = function() {
var mode = $("#node-config-input-usesocks").prop('checked');
if (mode === false) {
$("#socks").hide();
} else {
$("#socks").show();
}
};
useSocks5();
$("#node-config-input-usesocks").change(useSocks5);
}
});
</script>
<script type="text/x-red" data-template-name="telegram bot">
<div class="form-row" style="min-width: 700px">
<div class="form-row">
<label for="node-config-input-botname"><i class="fa fa-telegram"></i> Bot-Name</label>
<input type="text" id="node-config-input-botname" placeholder="(Name of bot to connect to)">
</div>
<div class="form-row">
<label for="node-config-input-token"><i class="fa fa-key"></i> Token</label>
<input type="text" id="node-config-input-token" placeholder="(Enter the bot token from botfather here)">
</div>
<hr align="middle"/>
<div class="form-row">
<label for="node-config-input-usernames"><i class="fa fa-user"></i> Users</label>
<input type="text" id="node-config-input-usernames" placeholder="(Optional list of authorized user names e.g.: hugo,sepp,egon)">
</div>
<div class="form-row">
<label for="node-config-input-chatids"><i class="fa fa-comment"></i> ChatIds</label>
<input type="text" id="node-config-input-chatids" placeholder="(Optional list of authorized chat-ids e.g.: -1234567,2345678,-3456789)">
</div>
<div class="form-row">
<label for="node-config-input-baseapiurl"><i class="fa fa-server"></i> Server URL</label>
<input type="text" id="node-config-input-baseapiurl" placeholder="(Optional URL for proxying and testing e.g.: https://api.telegram.org)">
</div>
<div class="form-row">
<label for="node-config-input-updatemode"><i class="fa fa-link"></i> Update Mode</label>
<select id="node-config-input-updatemode">
<option value="polling">Polling</option>
<option value="webhook">Webhook</option>
</select>
</div>
<hr align="middle"/>
<div class="form-row hidden" id="polling" style="background: #fbfbfb">
<label style="width: auto"><i class="fa fa-cogs"></i> Polling Options:</label>
<div class="form-row" style="margin-left: 20px">
<div class="form-row">
<label for="node-config-input-pollinterval"><i class="fa fa-clock-o"></i> Poll Interval</label>
<input type="text" id="node-config-input-pollinterval" placeholder="(Optional poll interval in milliseconds. The default is 300.)">
</div>
</div>
<div class="form-tips" style="width: auto"><b>Tip:</b> Polling mode is very robust and easy to set up. Nevertheless it creates more traffic on the network over time.</div>
</div>
<div class="form-row hidden" id="webhook" style="background: #fbfbfb">
<label style="width: auto"><i class="fa fa-cogs"></i> Webhook Options:</label>
<div class="form-row" style="margin-left: 20px">
<div class="form-row">
<label for="node-config-input-bothost"><i class="fa fa-desktop"></i> Bot Host</label>
<input type="text" id="node-config-input-bothost" placeholder="(Optional public IP or hostname of your bot when using webhook. e.g.: mybot.domain.com)">
</div>
<div class="form-row">
<label for="node-config-input-publicbotport"><i class="fa fa-random"></i> Public Bot Port</label>
<input type="text" id="node-config-input-publicbotport" placeholder="(Optional public port for your bot when using webhook. The default is 8443.)">
</div>
<div class="form-row">
<label for="node-config-input-localbotport"><i class="fa fa-random"></i> Local Bot Port</label>
<input type="text" id="node-config-input-localbotport" placeholder="(Optional local port for your bot when using webhook. The default is 8443.)">
</div>
<div class="form-row">
<label for="node-config-input-privatekey"><i class="fa fa-id-card"></i> Private Key</label>
<input type="text" id="node-config-input-privatekey" placeholder="(Optional local path to your private key file. e.g.: C:\temp\\key.pem)">
</div>
<div class="form-row">
<label for="node-config-input-certificate"><i class="fa fa-id-card-o"></i> Certificate</label>
<input type="text" id="node-config-input-certificate" placeholder="(Optional local path to your certificate file. e.g.: C:\temp\\crt.pem)">
</div>
<div class="form-row">
<label for="node-config-input-useselfsignedcertificate"><i class="fa fa-pencil"></i> Certificate is self-signed</label>
<input type="checkbox" id="node-config-input-useselfsignedcertificate" style="display: inline-block; width: auto; vertical-align: top;">
</div>
<div class="form-row">
<label for="node-config-input-sslterminated"><i class="fa fa-pencil"></i> SSL terminated by reverse proxy</label>
<input type="checkbox" id="node-config-input-sslterminated" style="display: inline-block; width: auto; vertical-align: top;">
</div>
<div class="form-tips" style="width: auto"><b>Tip:</b> Webhook mode requires a HTTPS certificate. The certificate can also be a self-signed (=custom) one. If any of the host, key, certificate properties is left blank the bot will default to polling mode. If SSL termination is already handled by reverse proxy, key and certificate is not required.</div>
</div>
</div>
<hr align="middle"/>
<div class="form-row">
<label for="node-config-input-usesocks"><i class="fa fa-lock"></i> Use SOCKS5</label>
<input type="checkbox" id="node-config-input-usesocks" style="display: inline-block; width: auto; vertical-align: top;">
</div>
<div class="form-row hidden" id="socks" style="background: #fbfbfb">
<label style="width: auto"><i class="fa fa-cogs"></i> SOCKS5 Options:</label>
<div class="form-row" style="background: #fff; margin-left: 20px">
<div class="form-row">
<label for="node-config-input-sockshost"><i class="fa fa-desktop"></i> Host</label>
<input type="text" id="node-config-input-sockshost" placeholder="(Optional IP or hostname of the socks proxy when using polling mode.)">
</div>
<div class="form-row">
<label for="node-config-input-socksport"><i class="fa fa-random"></i> Port</label>
<input type="text" id="node-config-input-socksport" placeholder="(Optional port of the socks proxy when using polling mode.)">
</div>
<div class="form-row">
<label for="node-config-input-socksusername"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-config-input-socksusername" placeholder="(Optional username of the socks proxy when using polling mode.)">
</div>
<div class="form-row">
<label for="node-config-input-sockspassword"><i class="fa fa-key"></i> Password</label>
<input type="text" id="node-config-input-sockspassword" placeholder="(Optional password of the socks proxy when using polling mode.)">
</div>
</div>
<div class="form-tips" style="width: auto"><b>Tip:</b> SOCKS5 support is optional can can only be used with a valid proxy server, port, username and password.</div>
</div>
<hr align="middle"/>
<div class="form-row">
<label for="node-config-input-verboselogging"><i class="fa fa-search"></i> Verbose Logging</label>
<input type="checkbox" id="node-config-input-verboselogging" style="display: inline-block; width: auto; vertical-align: top;">
</div>
<div class="form-tips" style="width: auto"><b>Tip:</b> When verbose logging is turned on additional log messages will be printed to the node-red console.</div>
<hr align="middle"/>
</div>
</script>
<script type="text/x-red" data-help-name="telegram bot">
<p>A configuration node that holds the token of the telegram bot.</p>
<h3>Details</h3>
<p>It communicates with the telegram server. Do not create several configurations nodes with the same token!</p>
<p>The usernames and chat ids properties can be used to limit authorization to the bot. Enter values in comma separated format e.g. a,b,c</p>
<p>Usernames and chat ids are optional. Leave field blank if you do not want to use this feature.</p>
<p>The API Base URL can be changed for testing or when using a proxy.</p>
<p>You can enable verbose logging to get more details when debugging, but keep in mind that whis could fill your log file very rapidly.</p>
<p></p>
<h3>Operation Modes: Polling vs Webhook</h3>
<p>By default the bot polls the telegram api server every 300ms. But you can also create a webhook so that the telegram server sends updates via HTTPS POST directly to your host machine.</p>
<p>You can enable the webhook by providing the required data:</p>
<p>- private key</p>
<p>- public key</p>
<p>- the publicly reachable IP or hostname of your bot</p>
<p>- the public port (e.g.: 8443, 443, ...)</p>
<p>- the local port (e.g.: 8443. This value is only different to the public port when you are behind a router that maps ports from public to private ones) </p>
<p></p>
<h3>References</h3>
<p>See also </p>
<p>https://core.telegram.org/bots/webhooks</p>
<p>https://stackoverflow.com/questions/42713926/what-is-easy-way-to-create-and-use-a-self-signed-certification-for-a-telegram-we</p>
<p>One common pitfall when creating certificates that don't work is that the value CN you provided to openssl must match the bots domain name: see "bot host" above. </p>
</script>
<!-- ------------------------------------------------------------------------------------------ -->
<script type="text/javascript">
RED.nodes.registerType('telegram receiver', {
category: 'telegram',
color: '#3BABDD',
defaults: {
name: { value: "" },
bot: { value:"", type: "telegram bot", required: true },
saveDataDir: { value: "" }
},
inputs: 0,
outputs: 2,
icon: "telegram.png",
paletteLabel: "receiver",
label: function () {
return this.name || "Telegram receiver";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="telegram receiver">
<div class="form-row">
<label for="node-input-bot"><i class="fa fa-telegram"></i> Bot</label>
<input type="text" id="node-input-bot" placeholder="Bot">
</div>
<div class="form-row">
<label for="node-input-saveDataDir"><i class="fa fa-hdd-o"></i> Download Directory</label>
<input type="text" id="node-input-saveDataDir" placeholder="Download directory">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="telegram receiver">
<p>A telegram node that triggers the output when some message is received from the chat.</p>
<p>The node receives all messages polled from/sent by the telegram server.</p>
<h3>Outputs</h3>
<p>1. Standard Ouput: Message is sent to output 1 if it is from an authorized user.</p>
<p>2. Unauthorized Output: Message is sent to output 2 if it is from a non-authorized user.</p>
<h3>Details</h3>
<p><code>msg.payload</code> typically contains the parsed data as follows:</p>
<ul>
<li><code>content</code> the message contents</li>
<li><code>type</code> the type of message contents</li>
<li><code>messageId</code> the messageId number</li>
<li><code>chatId</code> the chatId number</li>
</ul>
<p>Other properties may be present depending on the type of message.</p>
<code>msg.originalMessage</code> contains the raw data object from the underlying library,
and contains many useful properties.</p>
</script>
<!-- ------------------------------------------------------------------------------------------ -->
<script type="text/javascript">
RED.nodes.registerType('telegram command', {
category: 'telegram',
color: '#3BABDD',
defaults: {
name: { value: "" },
command: { value: ""},
bot: { value: "", type: "telegram bot", required: true },
strict : { value: false }
},
inputs: 0,
outputs: 2,
icon: "telegramc.png",
paletteLabel: "command",
label: function () {
return this.name || this.command || "Telegram command";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="telegram command">
<div class="form-row">
<label for="node-input-command"><i class="fa fa-envelope"></i> Command</label>
<input type="text" id="node-input-command" placeholder="Command">
</div>
<div class="form-row">
<label for="node-input-bot"><i class="fa fa-telegram"></i> Bot</label>
<input type="text" id="node-input-bot" placeholder="Bot">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-strict"><i class="fa fa-tag"></i> Strict in group chats</label>
<input type="checkbox" id="node-input-strict" style="display: inline-block; width: auto; vertical-align: top;">
</div>
</script>
<script type="text/x-red" data-help-name="telegram command">
<p>A telegram node that triggers the output when a specified command is received from the chat.</p>
<h3>Outputs</h3>
<p>1. Standard Ouput: Message is sent to output 1 if it is from an authorized user and it contains the command at the beginning of the message.</p>
<p>2. Error Ouput: Message is sent to output 2 if it is from an authorized user but does not contain the specified command.</p>
</script>
<!-- ------------------------------------------------------------------------------------------ -->
<script type="text/javascript">
RED.nodes.registerType('telegram event', {
category: 'telegram',
color: '#3BABDD',
defaults: {
name: { value: "" },
bot: { value: "", type: "telegram bot", required: true },
event: { value: "callback_query", required: true },
autoanswer: { value: false, required: false }
},
inputs: 0,
outputs: 1,
icon: "telegram.png",
paletteLabel: "event",
label: function () {
return this.name || this.event;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="telegram event">
<div class="form-row">
<label for="node-input-bot"><i class="fa fa-telegram"></i> Bot</label>
<input type="text" id="node-input-bot" placeholder="Bot">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-event"><i class="fa fa-mail-reply-all"></i> Event</label>
<div style="display: inline-block; position: relative; width: 70%; height: 19.1333px;">
<div style="position: absolute; left: 0; right: 0;">
<select id="node-input-event" style="width:100%">
<option value="callback_query">Callback Query</option>
<option value="inline_query">Inline Query</option>
<option value="edited_message">Edited Message</option>
<option value="edited_message_text">Edited Message Text</option>
<option value="edited_message_caption">Edited Message Caption</option>
<option value="channel_post">Channel Post</option>
<option value="edited_channel_post">Edited Channel Post</option>
<option value="edited_channel_post_text">Edited Channel Post Text</option>
<option value="edited_channel_post_caption">Edited Channel Post Caption</option>
</select>
</div>
</div>
</div>
<div class="form-row">
<label for="node-input-autoanswer"><i class="fa fa-retweet"></i> Auto-Answer</label>
<input type="checkbox" id="node-input-autoanswer" style="display:inline-block; width:auto; vertical-align:top;">
</div>
</script>
<script type="text/x-red" data-help-name="telegram event">
<p>A telegram node that triggers the output when a event is received from a chat.</p>
<h3>Outputs</h3>
<p>1. Standard Output: The output is triggered when the configured event was received.</p>
<h3>Details</h3>
<p>The event type can be selected.</p>
<p><code>msg.payload</code> typically contains the parsed data as follows:</p>
<ul>
<li><code>chatId</code>Unique identifier for this chat.</li>
<li><code>messageId</code>Message identifier.</li>
<li><code>type</code>event type</li>
<li><code>date</code>timestamp</li>
<li><code>content</code>the actual UTF-8 text of the message</li>
</ul>
<p>Other properties may be present depending on the type of message.</p>
<code>msg.originalMessage</code> contains the raw data object from the underlying library,
and contains many useful properties.</p>
<p>The autoanswer checkbox can be set for callback_query.</p>
<p>Then you won't have to send an explicit answer to the bot on your own.</p>
</script>
<!-- ------------------------------------------------------------------------------------------ -->
<script type="text/javascript">
RED.nodes.registerType('telegram sender', {
category: 'telegram',
color: '#3BABDD',
defaults: {
name: { value: "" },
bot: { value: "", type: "telegram bot", required: true }
},
inputs: 1,
outputs: 1,
icon: "telegram.png",
align: "right",
paletteLabel: "sender",
label: function () {
return this.name || "Telegram sender";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="telegram sender">
<div class="form-row">
<label for="node-input-bot"><i class="fa fa-telegram"></i> Bot</label>
<input type="text" id="node-input-bot" placeholder="Bot">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="telegram sender">
<p>A telegram node that sends the <code>msg.payload</code> to the chat.</p>
<h3>Inputs</h3>
<p>1. Standard Input: receives a message object containing the chatId. It can be directly connected to the receiver node's output 1.</p>
<h3>Details</h3>
<p>The <code>msg.payload</code> must be an object that contains a complete set of telegram message properties,
at a minimum these should contain:
<ul>
<li><code>content</code> the message contents</li>
<li><code>type</code> the type of message contents</li>
<li><code>chatId</code> the chatId number</li>
</ul>
<p>The output message contains an error property if an exception occured.</p>
</script>
<!-- ------------------------------------------------------------------------------------------ -->
<!-- ------------------------------------------------------------------------------------------ -->
<script type="text/javascript">
RED.nodes.registerType('telegram reply', {
category: 'telegram',
color: '#3BABDD',
defaults: {
name: { value: "" },
bot: { value: "", type: "telegram bot", required: true }
},
inputs: 1,
outputs: 1,
icon: "telegram.png",
align: "right",
paletteLabel: "reply",
label: function () {
return this.name || "Telegram reply";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="telegram reply">
<div class="form-row">
<label for="node-input-bot"><i class="fa fa-telegram"></i> Bot</label>
<input type="text" id="node-input-bot" placeholder="Bot">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="telegram reply">
<p>A telegram node that is triggered when someone answered to a specified message.</p>
<h3>Inputs</h3>
<p>1. Standard Input: calls the onReplyToMessage of the bot.</p>
<h3>Outputs</h3>
<p>1. Standard Output: contains the result from the onReplyToMessage call.</p>
<h3>Details</h3>
<p>This can be useful, when the bot for example sent a message and you want to take some action</p>
<p>when someone responded to this specified message.</p>
<p>Responding to messages is done by clicking on the message in your client and choose "answer" from the popup.</p>
<p>The <code>msg.payload</code> should be an object that contains:</p>
<ul><li><code>chatId</code> destination chatId.</li>
<li><code>sentMessageId</code> the id of the previously sent message in the chat.</li>
<li><code>content</code> the message content.</li></ul>
</script>
<!-- ------------------------------------------------------------------------------------------ -->

View File

@@ -0,0 +1,1388 @@
/**
* 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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB