meshmessaging.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. /**
  2. * @description MeshCentral user messaging communication module
  3. * @author Ylian Saint-Hilaire
  4. * @copyright Intel Corporation 2022
  5. * @license Apache-2.0
  6. * @version v0.0.1
  7. */
  8. /*xjslint node: true */
  9. /*xjslint plusplus: true */
  10. /*xjslint maxlen: 256 */
  11. /*jshint node: true */
  12. /*jshint strict: false */
  13. /*jshint esversion: 6 */
  14. "use strict";
  15. /*
  16. // For Telegram user login, add this in config.json
  17. "messaging": {
  18. "telegram": {
  19. "apiid": 00000000,
  20. "apihash": "00000000000000000000000",
  21. "session": "aaaaaaaaaaaaaaaaaaaaaaa"
  22. }
  23. }
  24. // For Telegram bot login, add this in config.json
  25. "messaging": {
  26. "telegram": {
  27. "apiid": 00000000,
  28. "apihash": "00000000000000000000000",
  29. "bottoken": "00000000:aaaaaaaaaaaaaaaaaaaaaaaa"
  30. }
  31. }
  32. // For Telegram login with proxy settings, add this in config.json
  33. {
  34. "messaging": {
  35. "telegram": {
  36. "apiid": 0,
  37. "apihash": "00000000000000000000000",
  38. "session": "aaaaaaaaaaaaaaaaaaaaaaa",
  39. "useWSS": false, // Important. Most proxies cannot use SSL.
  40. "proxy": {
  41. "ip": "123.123.123.123", // Proxy host (IP or hostname)
  42. "port": 123, // Proxy port
  43. "MTProxy": false, // Whether it's an MTProxy or a normal Socks one
  44. "secret": "00000000000000000000000000000000", // If used MTProxy then you need to provide a secret (or zeros).
  45. "socksType": 5, // If used Socks you can choose 4 or 5.
  46. "timeout": 2 // Timeout (in seconds) for connection,
  47. }
  48. }
  49. }
  50. }
  51. // For Discord login, add this in config.json
  52. "messaging": {
  53. "discord": {
  54. "inviteurl": "https://discord.gg/xxxxxxxxx",
  55. "token": "xxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxx"
  56. }
  57. }
  58. // For XMPP login, add this in config.json
  59. {
  60. "messaging": {
  61. "xmpp": {
  62. "service": "xmppserver.com",
  63. "credentials": {
  64. "username": "username",
  65. "password": "password"
  66. }
  67. }
  68. }
  69. }
  70. // For CallMeBot
  71. // For Signal Messenger: https://www.callmebot.com/blog/free-api-signal-send-messages/
  72. {
  73. "messaging": {
  74. "callmebot": true
  75. }
  76. }
  77. // For Pushover
  78. {
  79. "messaging": {
  80. "pushover": {
  81. "token": "xxxxxxx"
  82. }
  83. }
  84. }
  85. // For ntfy
  86. {
  87. "messaging": {
  88. "ntfy": true
  89. }
  90. }
  91. // For zulip
  92. {
  93. "messaging": {
  94. "site": "https://api.zulip.com",
  95. "email": "your-bot@zulip.com",
  96. "api_key": "your_32_character_api_key"
  97. }
  98. }
  99. // For Slack Webhook
  100. {
  101. "messaging": {
  102. "slack": true
  103. }
  104. }
  105. */
  106. // Construct a messaging server object
  107. module.exports.CreateServer = function (parent) {
  108. var obj = {};
  109. obj.parent = parent;
  110. obj.providers = 0; // 1 = Telegram, 2 = Signal, 4 = Discord, 8 = XMPP, 16 = CallMeBot, 32 = Pushover, 64 = ntfy, 128 = Zulip, 256 = Slack
  111. obj.telegramClient = null;
  112. obj.discordClient = null;
  113. obj.discordUrl = null;
  114. obj.xmppClient = null;
  115. var xmppXml = null;
  116. obj.callMeBotClient = null;
  117. obj.pushoverClient = null;
  118. obj.zulipClient = null;
  119. obj.slackClient = null;
  120. const sortCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
  121. // Telegram client setup
  122. if (parent.config.messaging.telegram) {
  123. // Validate Telegram configuration values
  124. var telegramOK = true;
  125. if (typeof parent.config.messaging.telegram.apiid != 'number') { console.log('Invalid or missing Telegram apiid.'); telegramOK = false; }
  126. if (typeof parent.config.messaging.telegram.apihash != 'string') { console.log('Invalid or missing Telegram apihash.'); telegramOK = false; }
  127. if ((typeof parent.config.messaging.telegram.session != 'string') && (typeof parent.config.messaging.telegram.bottoken != 'string')) { console.log('Invalid or missing Telegram session or bottoken.'); telegramOK = false; }
  128. if (telegramOK) {
  129. // Setup Telegram
  130. async function setupTelegram() {
  131. const { TelegramClient } = require('telegram');
  132. const { StringSession } = require('telegram/sessions');
  133. const { Logger } = require('telegram/extensions/Logger');
  134. const logger = new Logger({ LogLevel : 'none' });
  135. const input = require('input');
  136. var client;
  137. var options = { connectionRetries: 5, baseLogger: logger };
  138. if (parent.config.messaging.telegram.usewss == false) { options.useWSS = false; }
  139. if (typeof parent.config.messaging.telegram.connectionretries == 'number') { options.connectionRetries = parent.config.messaging.telegram.connectionretries; }
  140. if (typeof parent.config.messaging.telegram.proxy == 'object') { options.proxy = parent.config.messaging.telegram.proxy; }
  141. if (parent.config.messaging.telegram.bottoken == null) {
  142. // User login
  143. var stringSession = new StringSession(parent.config.messaging.telegram.session);
  144. const client = new TelegramClient(stringSession, parent.config.messaging.telegram.apiid, parent.config.messaging.telegram.apihash, options);
  145. await client.start({ onError: function (err) { console.log('Telegram error', err); } });
  146. obj.telegramClient = client;
  147. obj.providers += 1; // Enable Telegram messaging
  148. console.log("MeshCentral Telegram client is user connected.");
  149. } else {
  150. // Bot login
  151. var stringSession = new StringSession('');
  152. const client = new TelegramClient(stringSession, parent.config.messaging.telegram.apiid, parent.config.messaging.telegram.apihash, options);
  153. await client.start({ botAuthToken: parent.config.messaging.telegram.bottoken, onError: function (err) { console.log('Telegram error', err); } });
  154. obj.telegramClient = client;
  155. obj.providers += 1; // Enable Telegram messaging
  156. console.log("MeshCentral Telegram client is bot connected.");
  157. }
  158. }
  159. setupTelegram();
  160. }
  161. }
  162. // Discord client setup
  163. if (parent.config.messaging.discord) {
  164. // Validate Discord configuration values
  165. var discordOK = true;
  166. if (typeof parent.config.messaging.discord.token != 'string') { console.log('Invalid or missing Discord token.'); discordOK = false; }
  167. if (discordOK) {
  168. // Setup Discord
  169. const { Client, GatewayIntentBits } = require('discord.js');
  170. var discordClient = new Client({
  171. intents: [
  172. GatewayIntentBits.Guilds,
  173. GatewayIntentBits.GuildMessages,
  174. GatewayIntentBits.MessageContent,
  175. GatewayIntentBits.GuildMembers,
  176. GatewayIntentBits.DirectMessages
  177. ]
  178. });
  179. // Called when Discord client is connected
  180. discordClient.on('ready', function() {
  181. console.log(`MeshCentral Discord client is connected as ${discordClient.user.tag}!`);
  182. obj.discordClient = discordClient;
  183. obj.discordUrl = parent.config.messaging.discord.serverurl;
  184. obj.providers += 4; // Enable Discord messaging
  185. });
  186. // Receives incoming messages, ignore for now
  187. discordClient.on('messageCreate', function(message) {
  188. if (message.author.bot) return false;
  189. console.log(`Discord message from ${message.author.username}: ${message.content}`, message.channel.type);
  190. //message.channel.send("Channel Hello");
  191. //message.author.send('Private Hello');
  192. });
  193. // Called when Discord client received an interaction
  194. discordClient.on('interactionCreate', async function(interaction) {
  195. console.log('Discord interaction', interaction);
  196. if (!interaction.isChatInputCommand()) return;
  197. if (interaction.commandName === 'ping') { await interaction.reply('Pong!'); }
  198. });
  199. // Connect Discord client
  200. discordClient.login(parent.config.messaging.discord.token);
  201. }
  202. }
  203. // XMPP client setup
  204. if (parent.config.messaging.xmpp) {
  205. // Validate XMPP configuration values
  206. var xmppOK = true;
  207. if (typeof parent.config.messaging.xmpp.service != 'string') { console.log('Invalid or missing XMPP service.'); xmppOK = false; }
  208. if (xmppOK) {
  209. // Setup XMPP
  210. const { client, xml } = require('@xmpp/client');
  211. const xmpp = client(parent.config.messaging.xmpp);
  212. xmpp.on('error', function (err) { parent.debug('email', 'XMPP error: ' + err); console.error('XMPP error', err); });
  213. xmpp.on('offline', function () { parent.debug('email', 'XMPP client is offline.'); console.log('XMPP offline'); });
  214. //xmpp.on('stanza', async function (stanza) { if (stanza.is("message")) { await xmpp.send(xml('presence', { type: 'unavailable' })); await xmpp.stop(); } });
  215. xmpp.on('online', async function (address) {
  216. // await xmpp.send(xml("presence")); const message = xml("message", { type: "chat", to: "username@server.com" }, xml("body", {}, "hello world")); await xmpp.send(message);
  217. xmppXml = xml;
  218. obj.xmppClient = xmpp;
  219. obj.providers += 8; // Enable XMPP messaging
  220. console.log("MeshCentral XMPP client is connected.");
  221. });
  222. xmpp.start().catch(console.error);
  223. }
  224. }
  225. // CallMeBot client setup (https://www.callmebot.com/)
  226. if (parent.config.messaging.callmebot) {
  227. obj.callMeBotClient = true;
  228. obj.providers += 16; // Enable CallMeBot messaging
  229. }
  230. // Pushover client setup (https://pushover.net)
  231. if (typeof parent.config.messaging.pushover == 'object') {
  232. // Validate Pushover configuration values
  233. var pushoverOK = true;
  234. if (typeof parent.config.messaging.pushover.token != 'string') { console.log('Invalid or missing Pushover token.'); pushoverOK = false; }
  235. if (pushoverOK) {
  236. // Setup PushOver
  237. obj.pushoverClient = true;
  238. obj.providers += 32; // Enable Pushover messaging
  239. }
  240. }
  241. // ntfy client setup (https://ntfy.sh/)
  242. if (parent.config.messaging.ntfy) {
  243. obj.ntfyClient = true;
  244. obj.providers += 64; // Enable ntfy messaging
  245. }
  246. // Zulip client setup (https://zulip.com/)
  247. if (typeof parent.config.messaging.zulip == 'object') {
  248. var zulip = require('zulip');
  249. obj.zulipClient = new zulip.Client(parent.config.messaging.zulip);
  250. obj.providers += 128; // Enable zulip messaging
  251. }
  252. // Slack Webhook setup (https://slack.com)
  253. if (parent.config.messaging.slack) {
  254. obj.slackClient = true;
  255. obj.providers += 256; // Enable slack messaging
  256. }
  257. // Send a direct message to a specific userid
  258. async function discordSendMsg(userId, message) {
  259. const user = await obj.discordClient.users.fetch(userId).catch(function () { return null; });
  260. if (!user) return;
  261. await user.send(message).catch(function (ex) { console.log('Discord Error', ex); });
  262. }
  263. // Convert a userTag to a userId. We need to query the Discord server to find this information.
  264. // Example: findUserByTab('aaaa#0000', function (userid) { sendMsg(userid, 'message'); });
  265. async function discordFindUserByTag(userTag, func) {
  266. var username = userTag.split('#')[0];
  267. const guilds = await obj.discordClient.guilds.fetch();
  268. guilds.forEach(async function (value, key) {
  269. var guild = await value.fetch();
  270. const guildMembers = await guild.members.search({ query: username });
  271. guildMembers.forEach(async function (value, key) {
  272. if ((value.user.username + (value.user.discriminator != '0' ? '#' + value.user.discriminator : ''))== userTag) { func(key); return; }
  273. });
  274. });
  275. }
  276. // Send an XMPP message
  277. async function sendXmppMessage(to, msg, func) {
  278. const message = xmppXml('message', { type: 'chat', to: to.substring(5) }, xmppXml('body', {}, msg));
  279. await obj.xmppClient.send(message);
  280. if (func != null) { func(true); }
  281. }
  282. // Send an user message
  283. obj.sendMessage = function(to, msg, domain, func) {
  284. if ((to.startsWith('telegram:')) && (obj.telegramClient != null)) { // Telegram
  285. async function sendTelegramMessage(to, msg, func) {
  286. if (obj.telegramClient == null) return;
  287. parent.debug('email', 'Sending Telegram message to: ' + to.substring(9) + ': ' + msg);
  288. try { await obj.telegramClient.sendMessage(to.substring(9), { message: msg }); if (func != null) { func(true); } } catch (ex) { if (func != null) { func(false, ex); } }
  289. }
  290. sendTelegramMessage(to, msg, func);
  291. } else if ((to.startsWith('discord:')) && (obj.discordClient != null)) { // Discord
  292. discordFindUserByTag(to.substring(8), function (userid) {
  293. parent.debug('email', 'Sending Discord message to: ' + to.substring(9) + ', ' + userid + ': ' + msg);
  294. discordSendMsg(userid, msg); if (func != null) { func(true); }
  295. });
  296. } else if ((to.startsWith('xmpp:')) && (obj.xmppClient != null)) { // XMPP
  297. parent.debug('email', 'Sending XMPP message to: ' + to.substring(5) + ': ' + msg);
  298. sendXmppMessage(to, msg, func);
  299. } else if ((to.startsWith('callmebot:')) && (obj.callMeBotClient != null)) { // CallMeBot
  300. parent.debug('email', 'Sending CallMeBot message to: ' + to.substring(10) + ': ' + msg);
  301. console.log('Sending CallMeBot message to: ' + to.substring(10) + ': ' + msg);
  302. var toData = to.substring(10).split('|');
  303. if ((toData[0] == 'signal') && (toData.length == 3)) {
  304. var url = 'https://api.callmebot.com/signal/send.php?phone=' + encodeURIComponent(toData[1]) + '&apikey=' + encodeURIComponent(toData[2]) + '&text=' + encodeURIComponent(msg);
  305. require('https').get(url, function (r) { if (func != null) { func(r.statusCode == 200); } });
  306. } else if ((toData[0] == 'whatsapp') && (toData.length == 3)) {
  307. var url = 'https://api.callmebot.com/whatsapp.php?phone=' + encodeURIComponent(toData[1]) + '&apikey=' + encodeURIComponent(toData[2]) + '&text=' + encodeURIComponent(msg);
  308. require('https').get(url, function (r) { if (func != null) { func(r.statusCode == 200); } });
  309. } else if ((toData[0] == 'facebook') && (toData.length == 2)) {
  310. var url = 'https://api.callmebot.com/facebook/send.php?apikey=' + encodeURIComponent(toData[1]) + '&text=' + encodeURIComponent(msg);
  311. require('https').get(url, function (r) { if (func != null) { func(r.statusCode == 200); } });
  312. } else if ((toData[0] == 'telegram') && (toData.length == 2)) {
  313. var url = 'https://api.callmebot.com/text.php?user=' + encodeURIComponent(toData[1]) + '&text=' + encodeURIComponent(msg);
  314. require('https').get(url, function (r) { if (func != null) { func(r.statusCode == 200); } });
  315. }
  316. } else if ((to.startsWith('pushover:')) && (obj.pushoverClient != null)) { // Pushover
  317. const Pushover = require('node-pushover');
  318. const push = new Pushover({ token: parent.config.messaging.pushover.token, user: to.substring(9) });
  319. push.send(domain.title ? domain.title : 'MeshCentral', msg, function (err, res) { if (func != null) { func(err == null); } });
  320. } else if ((to.startsWith('ntfy:')) && (obj.ntfyClient != null)) { // ntfy
  321. const url = 'https://' + (((typeof parent.config.messaging.ntfy == 'object') && (typeof parent.config.messaging.ntfy.host == 'string')) ? parent.config.messaging.ntfy.host : 'ntfy.sh') + '/' + encodeURIComponent(to.substring(5));
  322. const headers = (typeof parent.config.messaging.ntfy.authorization == 'string') ? { 'Authorization': parent.config.messaging.ntfy.authorization } : {};
  323. const req = require('https').request(new URL(url), { method: 'POST', headers: headers }, function (res) { if (func != null) { func(true); } });
  324. req.on('error', function (err) { if (func != null) { func(false); } });
  325. req.end(msg);
  326. } else if ((to.startsWith('zulip:')) && (obj.zulipClient != null)) { // zulip
  327. obj.zulipClient.sendMessage({
  328. type: 'private',
  329. content: msg,
  330. to: [ to.substring(6) ],
  331. subject: domain.title ? domain.title : 'MeshCentral'
  332. });
  333. if (func != null) { func(true); }
  334. }else if ((to.startsWith('slack:')) && (obj.slackClient != null)) { //slack
  335. const req = require('https').request(new URL(to.substring(6)), { method: 'POST' }, function (res) { if (func != null) { func(true); } });
  336. req.on('error', function (err) { if (func != null) { func(false); } });
  337. req.write(JSON.stringify({"text": msg }));
  338. req.end();
  339. } else {
  340. // No providers found
  341. if (func != null) { func(false, "No messaging providers found for this message."); }
  342. }
  343. }
  344. // Convert a CallMeBot URL into a handle
  345. obj.callmebotUrlToHandle = function (xurl) {
  346. var url = null;
  347. try { url = require('url').parse(xurl); } catch (ex) { return; }
  348. if ((url == null) || (url.host != 'api.callmebot.com') || (url.query == null)) return;
  349. var urlArgs = {}, urlArgs2 = url.query.split('&');
  350. for (var i in urlArgs2) { var j = urlArgs2[i].indexOf('='); if (j > 0) { urlArgs[urlArgs2[i].substring(0, j)] = urlArgs2[i].substring(j + 1); } }
  351. if ((urlArgs['phone'] != null) && (urlArgs['phone'].indexOf('|') >= 0)) return;
  352. if ((urlArgs['apikey'] != null) && (urlArgs['apikey'].indexOf('|') >= 0)) return;
  353. if ((urlArgs['user'] != null) && (urlArgs['user'].indexOf('|') >= 0)) return;
  354. // Signal Messenger, Whatapp, Facebook and Telegram
  355. if (url.path.startsWith('/signal') && (urlArgs['phone'] != null) && (urlArgs['apikey'] != null)) { return 'callmebot:signal|' + urlArgs['phone'] + '|' + urlArgs['apikey']; }
  356. if (url.path.startsWith('/whatsapp') && (urlArgs['phone'] != null) && (urlArgs['apikey'] != null)) { return 'callmebot:whatsapp|' + urlArgs['phone'] + '|' + urlArgs['apikey']; }
  357. if (url.path.startsWith('/facebook') && (urlArgs['apikey'] != null)) { return 'callmebot:facebook|' + urlArgs['apikey']; }
  358. if (url.path.startsWith('/text') && (urlArgs['user'] != null)) { return 'callmebot:telegram|' + urlArgs['user']; }
  359. return null;
  360. }
  361. // Get the correct SMS template
  362. function getTemplate(templateNumber, domain, lang) {
  363. parent.debug('email', 'Getting SMS template #' + templateNumber + ', lang: ' + lang);
  364. if (Array.isArray(lang)) { lang = lang[0]; } // TODO: For now, we only use the first language given.
  365. var r = {}, emailsPath = null;
  366. if ((domain != null) && (domain.webemailspath != null)) { emailsPath = domain.webemailspath; }
  367. else if (obj.parent.webEmailsOverridePath != null) { emailsPath = obj.parent.webEmailsOverridePath; }
  368. else if (obj.parent.webEmailsPath != null) { emailsPath = obj.parent.webEmailsPath; }
  369. if ((emailsPath == null) || (obj.parent.fs.existsSync(emailsPath) == false)) { return null }
  370. // Get the non-english email if needed
  371. var txtfile = null;
  372. if ((lang != null) && (lang != 'en')) {
  373. var translationsPath = obj.parent.path.join(emailsPath, 'translations');
  374. var translationsPathTxt = obj.parent.path.join(emailsPath, 'translations', 'sms-messages_' + lang + '.txt');
  375. if (obj.parent.fs.existsSync(translationsPath) && obj.parent.fs.existsSync(translationsPathTxt)) {
  376. txtfile = obj.parent.fs.readFileSync(translationsPathTxt).toString();
  377. }
  378. }
  379. // Get the english email
  380. if (txtfile == null) {
  381. var pathTxt = obj.parent.path.join(emailsPath, 'sms-messages.txt');
  382. if (obj.parent.fs.existsSync(pathTxt)) {
  383. txtfile = obj.parent.fs.readFileSync(pathTxt).toString();
  384. }
  385. }
  386. // No email templates
  387. if (txtfile == null) { return null; }
  388. // Decode the TXT file
  389. var lines = txtfile.split('\r\n').join('\n').split('\n')
  390. if (lines.length <= templateNumber) return null;
  391. return lines[templateNumber];
  392. }
  393. // Send messaging account verification
  394. obj.sendMessagingCheck = function (domain, to, verificationCode, language, func) {
  395. parent.debug('email', "Sending verification message to " + to);
  396. var sms = getTemplate(0, domain, language);
  397. if (sms == null) { parent.debug('email', "Error: Failed to get SMS template"); return; } // No SMS template found
  398. // Setup the template
  399. sms = sms.split('[[0]]').join(domain.title ? domain.title : 'MeshCentral');
  400. sms = sms.split('[[1]]').join(verificationCode);
  401. // Send the message
  402. obj.sendMessage(to, sms, domain, func);
  403. };
  404. // Send 2FA verification
  405. obj.sendToken = function (domain, to, verificationCode, language, func) {
  406. parent.debug('email', "Sending login token message to " + to);
  407. var sms = getTemplate(1, domain, language);
  408. if (sms == null) { parent.debug('email', "Error: Failed to get SMS template"); return; } // No SMS template found
  409. // Setup the template
  410. sms = sms.split('[[0]]').join(domain.title ? domain.title : 'MeshCentral');
  411. sms = sms.split('[[1]]').join(verificationCode);
  412. // Send the message
  413. obj.sendMessage(to, sms, domain, func);
  414. };
  415. // Send device state change notification
  416. obj.sendDeviceNotify = function (domain, username, to, connections, disconnections, lang) {
  417. if (to == null) return;
  418. parent.debug('email', "Sending device state change message to " + to);
  419. // Format the message
  420. var sms = [];
  421. if (connections.length > 0) { sms.push('Connections: ' + connections.join(', ')); } // TODO: Translate 'Connections: '
  422. if (disconnections.length > 0) { sms.push('Disconnections: ' + disconnections.join(', ')); } // TODO: Translate 'Disconnections: '
  423. if (sms.length == 0) return;
  424. sms = sms.join(' - ');
  425. if (sms.length > 1000) { sms = sms.substring(0, 997) + '...'; } // Limit messages to 1000 characters
  426. // Send the message
  427. obj.sendMessage(to, sms, domain, null);
  428. };
  429. // Send help request notification
  430. obj.sendDeviceHelpRequest = function (domain, username, to, devicename, nodeid, helpusername, helprequest, lang) {
  431. if (to == null) return;
  432. parent.debug('email', "Sending device help request message to " + to);
  433. // Format the message
  434. var sms = "Help Request from " + devicename + ': ' + helprequest; // TODO: Translate 'Help Request from {0}:'
  435. if (sms.length > 1000) { sms = sms.substring(0, 997) + '...'; } // Limit messages to 1000 characters
  436. // Send the message
  437. obj.sendMessage(to, sms, domain, null);
  438. }
  439. //
  440. // Device connection and disconnection notifications
  441. //
  442. obj.deviceNotifications = {}; // UserId --> { timer, nodes: nodeid --> connectType }
  443. // A device connected and a user needs to be notified about it.
  444. obj.notifyDeviceConnect = function (user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
  445. const mesh = parent.webserver.meshes[meshid];
  446. if (mesh == null) return;
  447. // Add the user and start a timer
  448. if (obj.deviceNotifications[user._id] == null) {
  449. obj.deviceNotifications[user._id] = { nodes: {} };
  450. obj.deviceNotifications[user._id].timer = setTimeout(function () { sendDeviceNotifications(user._id); }, 1 * 60 * 1000); // 1 minute before message is sent
  451. }
  452. // Add the device
  453. if (obj.deviceNotifications[user._id].nodes[nodeid] == null) {
  454. obj.deviceNotifications[user._id].nodes[nodeid] = { c: connectType }; // This device connection need to be added
  455. } else {
  456. const info = obj.deviceNotifications[user._id].nodes[nodeid];
  457. if ((info.d != null) && ((info.d & connectType) != 0)) {
  458. info.d -= connectType; // This device disconnect cancels out a device connection
  459. if (((info.c == null) || (info.c == 0)) && ((info.d == null) || (info.d == 0))) {
  460. // This device no longer needs a notification
  461. delete obj.deviceNotifications[user._id].nodes[nodeid];
  462. if (Object.keys(obj.deviceNotifications[user._id].nodes).length == 0) {
  463. // This user no longer needs a notification
  464. clearTimeout(obj.deviceNotifications[user._id].timer);
  465. delete obj.deviceNotifications[user._id];
  466. }
  467. return;
  468. }
  469. } else {
  470. if (info.c != null) {
  471. info.c |= connectType; // This device disconnect needs to be added
  472. } else {
  473. info.c = connectType; // This device disconnect needs to be added
  474. }
  475. }
  476. }
  477. // Set the device group name
  478. if ((extraInfo != null) && (extraInfo.name != null)) { obj.deviceNotifications[user._id].nodes[nodeid].nn = extraInfo.name; }
  479. obj.deviceNotifications[user._id].nodes[nodeid].mn = mesh.name;
  480. }
  481. // Cancel a device disconnect notification
  482. obj.cancelNotifyDeviceDisconnect = function (user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
  483. const mesh = parent.webserver.meshes[meshid];
  484. if (mesh == null) return;
  485. if ((obj.deviceNotifications[user._id] != null) && (obj.deviceNotifications[user._id].nodes[nodeid] != null)) {
  486. const info = obj.deviceNotifications[user._id].nodes[nodeid];
  487. if ((info.d != null) && ((info.d & connectType) != 0)) {
  488. info.d -= connectType; // This device disconnect cancels out a device connection
  489. if (((info.c == null) || (info.c == 0)) && ((info.d == null) || (info.d == 0))) {
  490. // This device no longer needs a notification
  491. delete obj.deviceNotifications[user._id].nodes[nodeid];
  492. if (Object.keys(obj.deviceNotifications[user._id].nodes).length == 0) {
  493. // This user no longer needs a notification
  494. clearTimeout(obj.deviceNotifications[user._id].timer);
  495. delete obj.deviceNotifications[user._id];
  496. }
  497. }
  498. }
  499. }
  500. }
  501. // A device disconnected and a user needs to be notified about it.
  502. obj.notifyDeviceDisconnect = function (user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
  503. const mesh = parent.webserver.meshes[meshid];
  504. if (mesh == null) return;
  505. // Add the user and start a timer
  506. if (obj.deviceNotifications[user._id] == null) {
  507. obj.deviceNotifications[user._id] = { nodes: {} };
  508. obj.deviceNotifications[user._id].timer = setTimeout(function () { sendDeviceNotifications(user._id); }, 1 * 60 * 1000); // 1 minute before message is sent
  509. }
  510. // Add the device
  511. if (obj.deviceNotifications[user._id].nodes[nodeid] == null) {
  512. obj.deviceNotifications[user._id].nodes[nodeid] = { d: connectType }; // This device disconnect need to be added
  513. } else {
  514. const info = obj.deviceNotifications[user._id].nodes[nodeid];
  515. if ((info.c != null) && ((info.c & connectType) != 0)) {
  516. info.c -= connectType; // This device disconnect cancels out a device connection
  517. if (((info.d == null) || (info.d == 0)) && ((info.c == null) || (info.c == 0))) {
  518. // This device no longer needs a notification
  519. delete obj.deviceNotifications[user._id].nodes[nodeid];
  520. if (Object.keys(obj.deviceNotifications[user._id].nodes).length == 0) {
  521. // This user no longer needs a notification
  522. clearTimeout(obj.deviceNotifications[user._id].timer);
  523. delete obj.deviceNotifications[user._id];
  524. }
  525. return;
  526. }
  527. } else {
  528. if (info.d != null) {
  529. info.d |= connectType; // This device disconnect needs to be added
  530. } else {
  531. info.d = connectType; // This device disconnect needs to be added
  532. }
  533. }
  534. }
  535. // Set the device group name
  536. if ((extraInfo != null) && (extraInfo.name != null)) { obj.deviceNotifications[user._id].nodes[nodeid].nn = extraInfo.name; }
  537. obj.deviceNotifications[user._id].nodes[nodeid].mn = mesh.name;
  538. }
  539. // Cancel a device connect notification
  540. obj.cancelNotifyDeviceConnect = function (user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
  541. const mesh = parent.webserver.meshes[meshid];
  542. if (mesh == null) return;
  543. if ((obj.deviceNotifications[user._id] != null) && (obj.deviceNotifications[user._id].nodes[nodeid] != null)) {
  544. const info = obj.deviceNotifications[user._id].nodes[nodeid];
  545. if ((info.c != null) && ((info.c & connectType) != 0)) {
  546. info.c -= connectType; // This device disconnect cancels out a device connection
  547. if (((info.d == null) || (info.d == 0)) && ((info.c == null) || (info.c == 0))) {
  548. // This device no longer needs a notification
  549. delete obj.deviceNotifications[user._id].nodes[nodeid];
  550. if (Object.keys(obj.deviceNotifications[user._id].nodes).length == 0) {
  551. // This user no longer needs a notification
  552. clearTimeout(obj.deviceNotifications[user._id].timer);
  553. delete obj.deviceNotifications[user._id];
  554. }
  555. }
  556. }
  557. }
  558. }
  559. // Send a notification about device connections and disconnections to a user
  560. function sendDeviceNotifications(userid) {
  561. if (obj.deviceNotifications[userid] == null) return;
  562. clearTimeout(obj.deviceNotifications[userid].timer);
  563. var connections = [];
  564. var disconnections = [];
  565. for (var nodeid in obj.deviceNotifications[userid].nodes) {
  566. var info = obj.deviceNotifications[userid].nodes[nodeid];
  567. if ((info.c != null) && (info.c > 0) && (info.nn != null) && (info.mn != null)) {
  568. /*
  569. var c = [];
  570. if (info.c & 1) { c.push("Agent"); }
  571. if (info.c & 2) { c.push("CIRA"); }
  572. if (info.c & 4) { c.push("AMT"); }
  573. if (info.c & 8) { c.push("AMT-Relay"); }
  574. if (info.c & 16) { c.push("MQTT"); }
  575. connections.push(info.mn + ', ' + info.nn + ': ' + c.join(', '));
  576. */
  577. if (info.c & 1) { connections.push(info.nn); }
  578. }
  579. if ((info.d != null) && (info.d > 0) && (info.nn != null) && (info.mn != null)) {
  580. /*
  581. var d = [];
  582. if (info.d & 1) { d.push("Agent"); }
  583. if (info.d & 2) { d.push("CIRA"); }
  584. if (info.d & 4) { d.push("AMT"); }
  585. if (info.d & 8) { d.push("AMT-Relay"); }
  586. if (info.d & 16) { d.push("MQTT"); }
  587. disconnections.push(info.mn + ', ' + info.nn + ': ' + d.join(', '));
  588. */
  589. if (info.d & 1) { disconnections.push(info.nn); }
  590. }
  591. }
  592. // Sort the notifications
  593. connections.sort(sortCollator.compare);
  594. disconnections.sort(sortCollator.compare);
  595. // Get the user and domain
  596. const user = parent.webserver.users[userid];
  597. if ((user == null) || (user.msghandle == null)) return;
  598. const domain = obj.parent.config.domains[user.domain];
  599. if (domain == null) return;
  600. // Send the message
  601. obj.sendDeviceNotify(domain, user.name, user.msghandle, connections, disconnections, user.llang);
  602. // Clean up
  603. delete obj.deviceNotifications[userid];
  604. }
  605. return obj;
  606. };
  607. // Called to setup the Telegram session key
  608. module.exports.SetupTelegram = async function (parent) {
  609. // If basic telegram values are not setup, instruct the user on how to get them.
  610. if ((typeof parent.config.messaging != 'object') || (typeof parent.config.messaging.telegram != 'object') || (typeof parent.config.messaging.telegram.apiid != 'number') || (typeof parent.config.messaging.telegram.apihash != 'string')) {
  611. console.log('Login to your Telegram account at this URL: https://my.telegram.org/.');
  612. console.log('Click "API development tools" and fill your application details (only app title and short name required).');
  613. console.log('Click "Create application"');
  614. console.log('Set this apiid and apihash values in the messaging section of the config.json like this:');
  615. console.log('{');
  616. console.log(' "messaging": {');
  617. console.log(' "telegram": {');
  618. console.log(' "apiid": 123456,');
  619. console.log(' "apihash": "123456abcdfg"');
  620. console.log(' }');
  621. console.log(' }');
  622. console.log('}');
  623. console.log('Then, run --setuptelegram again to continue.');
  624. process.exit();
  625. return;
  626. }
  627. // If the session value is missing, perform the process to get it
  628. if (((parent.config.messaging.telegram.session == null) || (parent.config.messaging.telegram.session == '') || (typeof parent.config.messaging.telegram.session != 'string')) && ((parent.config.messaging.telegram.bottoken == null) || (parent.config.messaging.telegram.bottoken == '') || (typeof parent.config.messaging.telegram.bottoken != 'string'))) {
  629. if (parent.args.setuptelegram == 'user') {
  630. const { TelegramClient } = require('telegram');
  631. const { StringSession } = require('telegram/sessions');
  632. const { Logger } = require('telegram/extensions/Logger');
  633. const logger = new Logger({ LogLevel: 'none' });
  634. const input = require('input');
  635. const stringSession = new StringSession('');
  636. const client = new TelegramClient(stringSession, parent.config.messaging.telegram.apiid, parent.config.messaging.telegram.apihash, { connectionRetries: 5, baseLogger: logger });
  637. await client.start({
  638. phoneNumber: async function () { return await input.text("Please enter your number (+1-111-222-3333): "); },
  639. password: async function () { return await input.text("Please enter your password: "); },
  640. phoneCode: async function () { return await input.text("Please enter the code you received: "); },
  641. onError: function (err) { console.log('Telegram error', err); }
  642. });
  643. console.log('Set this session value in the messaging section of the config.json like this:');
  644. console.log('{');
  645. console.log(' "messaging": {');
  646. console.log(' "telegram": {');
  647. console.log(' "apiid": ' + parent.config.messaging.telegram.apiid + ',');
  648. console.log(' "apihash": "' + parent.config.messaging.telegram.apihash + '",');
  649. console.log(' "session": "' + client.session.save() + '"');
  650. console.log(' }');
  651. console.log(' }');
  652. console.log('}');
  653. process.exit();
  654. } else if (parent.args.setuptelegram == 'bot') {
  655. console.log('Login to your Telegram account, search for "BotFather", message him and create a bot.');
  656. console.log('Once you get the HTTP API token, add it in the config.json as "bottoken" like so:');
  657. console.log('{');
  658. console.log(' "messaging": {');
  659. console.log(' "telegram": {');
  660. console.log(' "apiid": ' + parent.config.messaging.telegram.apiid + ',');
  661. console.log(' "apihash": "' + parent.config.messaging.telegram.apihash + '",');
  662. console.log(' "bottoken": "00000000:aaaaaaaaaaaaaaaaaaaaaaaa"');
  663. console.log(' }');
  664. console.log(' }');
  665. console.log('}');
  666. process.exit();
  667. } else {
  668. console.log('run "--setuptelegram bot" to setup Telegram login as a bot (typical).');
  669. console.log('run "--setuptelegram user" to setup Telegram login as a user.');
  670. process.exit();
  671. }
  672. }
  673. // All Telegram values seem ok
  674. console.log('Telegram seems to be configured correctly in the config.json, no need to run --setuptelegram.');
  675. process.exit();
  676. };