From 6dc93c0da75dcd9ad04d5e720b273981637d97dd Mon Sep 17 00:00:00 2001 From: "Alexey D. Filimonov" Date: Sat, 8 Mar 2025 19:23:19 +0300 Subject: [PATCH] NotificationsService: add telegram (#1440) Add support for notifications using telegram bot. ``` $Notification/Type=TG $Notification/T1=bot_token $Notification/T2=chat_id $Notification/Send=Hello, World! ``` --- FluidNC/src/WebUI/NotificationsService.cpp | 75 ++++++++++++++++++++++ FluidNC/src/WebUI/NotificationsService.h | 1 + 2 files changed, 76 insertions(+) diff --git a/FluidNC/src/WebUI/NotificationsService.cpp b/FluidNC/src/WebUI/NotificationsService.cpp index 43a3d9877..32e39e766 100644 --- a/FluidNC/src/WebUI/NotificationsService.cpp +++ b/FluidNC/src/WebUI/NotificationsService.cpp @@ -24,6 +24,7 @@ namespace WebUI { static const int PUSHOVER_NOTIFICATION = 1; static const int EMAIL_NOTIFICATION = 2; static const int LINE_NOTIFICATION = 3; + static const int TELEGRAM_NOTIFICATION = 4; static const int DEFAULT_NOTIFICATION_TYPE = 0; static const int MIN_NOTIFICATION_TOKEN_LENGTH = 0; @@ -40,6 +41,10 @@ namespace WebUI { static const char* LINESERVER = "notify-api.line.me"; static const int LINEPORT = 443; + static const int TELEGRAMTIMEOUT = 5000; + static const char* TELEGRAMSERVER = "api.telegram.org"; + static const int TELEGRAMPORT = 443; + static const int EMAILTIMEOUT = 5000; bool NotificationsService::_started = false; @@ -55,6 +60,7 @@ namespace WebUI { { "LINE", 3 }, { "PUSHOVER", 1 }, { "EMAIL", 2 }, + { "TG", 4 }, }; EnumSetting* notification_type; StringSetting* notification_t1; @@ -156,6 +162,8 @@ namespace WebUI { return "Email"; case LINE_NOTIFICATION: return "Line"; + case TELEGRAM_NOTIFICATION: + return "TG"; default: return "None"; } @@ -176,6 +184,9 @@ namespace WebUI { case LINE_NOTIFICATION: return sendLineMSG(title, message); break; + case TELEGRAM_NOTIFICATION: + return sendTelegramMSG(title, message); + break; default: break; } @@ -320,6 +331,64 @@ namespace WebUI { Notificationclient.stop(); return res; } + /// @brief Send notification message to telegram chat. + /// @param title message title + /// @param message message content + /// @return true on success + /// @attention `$Notification/Type` should be "TG". + /// @attention `$Notification/T1` should be Telegram bot token. See notes. + /// @attention `$Notification/T2` should be Telegram chat id, e.g. `1234567890` or `-1234567890`. See notes. + /// @note * Obtaining bot token: Register bot with [@BotFather](https://t.me/BotFather), + /// and get bot token, e.g. `1234567890:ABCdefGHi-JKLmNOpQR-stuvw-xyz012345`. + /// @note * Obtaining chat id for personal account: If you want to get notifications to your personal chat, + /// text `/start` to the bot, + /// open `https://api.telegram.org/bot{bot-token}/getUpdates` in browser and get `..."chat":{"id":1234567890` here. + /// @note * Obtaining chat id for group chat: If you want to get notifications to group chat, invite bot to group, + /// text to group chat `/nameOfYour_bot hello`, + /// open `https://api.telegram.org/bot{bot-token}/getUpdates` in browser and get `..."chat":{"id":-1234567890` here. + /// Note, group chat id is negative. + bool NotificationsService::sendTelegramMSG(const char* title, const char* message) { + WiFiClientSecure Notificationclient; + Notificationclient.setInsecure(); //! Can not verify TLS certificates, as we don't load them into FW. + if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { + return false; + } + /** POST data explained + * POST /bot{T1}/sendMessage HTTP/1.1{CRLF} + * Host: {_serveraddress}{CRLF} + * Content-Type: application/json{CRLF} + * Content-Length: {len(payload)}{CRLF} + * {CRLF} + * {payload}{CRLF} + * + * Payload explained: + * { + * "parse_mode":"HTML", + * "chat_id":"{T2}", + * "text":"{title}{LF}{LF}{message}" + * } + */ + + //! WARNING: + // To save few bytes in flash memory, this code is so ugly. + // Length of line marked as TG_PAYLOAD_LINE is precalculated as if all + // placeholders were zero-length and added to overall length on line + // marked as TG_PAYLOAD_LINE_LEN + Notificationclient.printf( + "POST /bot%s/sendMessage HTTP/1.1\r\n" + "Host:%s\r\n" + "Content-Type:application/json\r\n" + "Content-Length:%d\r\n" + "\r\n" + "{\"parse_mode\":\"HTML\",\"chat_id\":\"%s\",\"text\":\"%s\n\n%s\"}" // TG_PAYLOAD_LINE + "\r\n", + _token1.c_str(), + _serveraddress.c_str(), + _token2.length() + strlen(title) + strlen(message) + + 55, // TG_PAYLOAD_LINE_LEN + _token2.c_str(), title, message); + return Wait4Answer(Notificationclient, "{", "\"ok\":true", TELEGRAMTIMEOUT); + } //Email#serveraddress:port bool NotificationsService::getPortFromSettings() { std::string tmp(notification_ts->get()); @@ -400,6 +469,12 @@ namespace WebUI { _port = LINEPORT; _serveraddress = LINESERVER; break; + case TELEGRAM_NOTIFICATION: + _token1 = notification_t1->get(); + _token2 = notification_t2->get(); + _port = TELEGRAMPORT; + _serveraddress = TELEGRAMSERVER; + break; case EMAIL_NOTIFICATION: _token1 = base64::encode(notification_t1->get()).c_str(); _token2 = base64::encode(notification_t2->get()).c_str(); diff --git a/FluidNC/src/WebUI/NotificationsService.h b/FluidNC/src/WebUI/NotificationsService.h index cfe64a536..4de7c473a 100644 --- a/FluidNC/src/WebUI/NotificationsService.h +++ b/FluidNC/src/WebUI/NotificationsService.h @@ -41,6 +41,7 @@ namespace WebUI { static bool sendPushoverMSG(const char* title, const char* message); static bool sendEmailMSG(const char* title, const char* message); static bool sendLineMSG(const char* title, const char* message); + static bool sendTelegramMSG(const char* title, const char* message); static bool getPortFromSettings(); static bool getServerAddressFromSettings(); static bool getEmailFromSettings();