diff --git a/README.md b/README.md index d036ead..17d8ad6 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,11 @@ You also need to install the [ESP8266 Arduino Core and Library](https://github.c Don't you know Telegram bots and how to setup one? Check [this](https://core.telegram.org/bots#6-botfather). + **_ESP8266 don't use anymore the fingerprint for Telegram certificate validation_** -+ **_Now ArduinoJson version 5 and 6 are supported!_** ++ **_Now ArduinoJson version 5, 6 and 7 are supported!_** + **_getNewMessage method now is more responsive!!_** -### _IMPORTANT - There are some incompatibilities with the ArduinoJson v 6.20.0. The last supported version is the 6.19.4_ - ### News ++ Arduino JSON v7.* support + ESP32 supported + ArduinoJson version 5 and 6 supported @@ -45,13 +44,9 @@ A special thanks go to these people who helped me making this library + [michaelzs85](https://github.com/michaelzs85) + [Di-Strix](https://github.com/Di-Strix) + [ElVasquito](https://github.com/ElVasquito) - -### Future work -+ [x] Add Telegram inline keyboards -+ [x] Add ESP32 support & testing -+ [x] ArduinoJSON 6 support - + ### Changelog +Fork (Drovosekov) for Arduino JSON v7 support testing + 2.1.13 ESP32 compile error + 2.1.12 No more fingerprint certificate validation for ESP8266 + 2.1.11 Fixed an issue on group/chat ID in callback queries diff --git a/examples/chatGroupEchoBot/chatGroupEchoBot.ino b/examples/chatGroupEchoBot/chatGroupEchoBot.ino index aa7d59d..94b2f12 100644 --- a/examples/chatGroupEchoBot/chatGroupEchoBot.ino +++ b/examples/chatGroupEchoBot/chatGroupEchoBot.ino @@ -2,38 +2,48 @@ Name: chatGroupEchoBot.ino Created: 14/06/2020 Author: Stefano Ledda + Ported Arduino JSON v7: Alexander Drovosekov Description: an example that check for incoming messages 1) send a message to the sender some "message related" infos 2) if the message came from a group chat, reply the group chat with the same message (like the echoBot example) */ +#include #include "CTBot.h" #include "Utilities.h" // for int64ToAscii() helper function -String ssid = "mySSID" ; // REPLACE mySSID WITH YOUR WIFI SSID -String pass = "myPassword"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY -String token = "myToken" ; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN +String ssid = "YOUR_SSID"; // REPLACE mySSID WITH YOUR WIFI SSID +String pass = "YOUR_WIFI_PASSWORD"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY +String token = "TELEGRAM_TOKEN" ; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN CTBot myBot; - - - + void setup() { // initialize the Serial Serial.begin(115200); Serial.println("Starting TelegramBot..."); - // connect the ESP8266 to the desired access point - myBot.wifiConnect(ssid, pass); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); // set the telegram bot token myBot.setTelegramToken(token); // check if all things are ok if (myBot.testConnection()) - Serial.println("\ntestConnection OK"); + Serial.println("testConnection OK"); else - Serial.println("\ntestConnection NOK"); + Serial.println("testConnection NOK"); } void loop() { @@ -63,7 +73,6 @@ void loop() { } } } - - // wait 500 milliseconds + delay(500); } diff --git a/examples/echoBot/echoBot.ino b/examples/echoBot/echoBot.ino index 754a77e..2fdf394 100644 --- a/examples/echoBot/echoBot.ino +++ b/examples/echoBot/echoBot.ino @@ -1,33 +1,46 @@ /* Name: echoBot.ino - Created: 12/21/2017 + Created: 20/09/2024 Author: Stefano Ledda + Ported Arduino JSON v7: Alexander Drovosekov Description: a simple example that check for incoming messages and reply the sender with the received message */ -#include "CTBot.h" -CTBot myBot; +#include +#include "CTBot.h" -String ssid = "mySSID" ; // REPLACE mySSID WITH YOUR WIFI SSID -String pass = "myPassword"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY -String token = "myToken" ; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN +String ssid = "YOUR_SSID"; // REPLACE mySSID WITH YOUR WIFI SSID +String pass = "YOUR_WIFI_PASSWORD"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY +String token = "TELEGRAM_TOKEN" ; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN +CTBot myBot; + void setup() { - // initialize the Serial - Serial.begin(115200); - Serial.println("Starting TelegramBot..."); - - // connect the ESP8266 to the desired access point - myBot.wifiConnect(ssid, pass); - - // set the telegram bot token - myBot.setTelegramToken(token); - - // check if all things are ok - if (myBot.testConnection()) - Serial.println("\ntestConnection OK"); - else - Serial.println("\ntestConnection NOK"); + // initialize the Serial + Serial.begin(115200); + Serial.println("Starting TelegramBot..."); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + // set the telegram bot token + myBot.setTelegramToken(token); + + // check if all things are ok + if (myBot.testConnection()) + Serial.println("testConnection OK"); + else + Serial.println("testConnection NOK"); } void loop() { diff --git a/examples/inlineKeyboard/inlineKeyboard.ino b/examples/inlineKeyboard/inlineKeyboard.ino index 5879334..7973fe9 100644 --- a/examples/inlineKeyboard/inlineKeyboard.ino +++ b/examples/inlineKeyboard/inlineKeyboard.ino @@ -1,7 +1,8 @@ /* Name: inlineKeyboard.ino -Created: 29/05/2018 +Created: 20/09/2024 Author: Stefano Ledda +Ported Arduino JSON v7: Alexander Drovosekov Description: a simple example that do: 1) if a "show keyboard" text message is received, show the inline custom keyboard, otherwise reply the sender with "Try 'show keyboard'" message @@ -10,18 +11,20 @@ Description: a simple example that do: 4) if "see docs" inline keyboard button is pressed, open a browser window with URL "https://github.com/shurillu/CTBot" */ -#include "CTBot.h" +#include +#include "CTBot.h" + +String ssid = "YOUR_SSID"; // REPLACE mySSID WITH YOUR WIFI SSID +String pass = "YOUR_WIFI_PASSWORD"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY +String token = "TELEGRAM_TOKEN" ; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN #define LIGHT_ON_CALLBACK "lightON" // callback data sent when "LIGHT ON" button is pressed #define LIGHT_OFF_CALLBACK "lightOFF" // callback data sent when "LIGHT OFF" button is pressed CTBot myBot; CTBotInlineKeyboard myKbd; // custom inline keyboard object helper - -String ssid = "mySSID"; // REPLACE mySSID WITH YOUR WIFI SSID -String pass = "myPassword"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY -String token = "myToken"; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN -uint8_t led = 2; // the onboard ESP8266 LED. + +#define LED_PIN 2 // the onboard ESP8266 LED. // If you have a NodeMCU you can use the BUILTIN_LED pin // (replace 2 with BUILTIN_LED) // ATTENTION: this led use inverted logic @@ -31,21 +34,31 @@ void setup() { Serial.begin(115200); Serial.println("Starting TelegramBot..."); - // connect the ESP8266 to the desired access point - myBot.wifiConnect(ssid, pass); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); // set the telegram bot token myBot.setTelegramToken(token); // check if all things are ok if (myBot.testConnection()) - Serial.println("\ntestConnection OK"); + Serial.println("testConnection OK"); else - Serial.println("\ntestConnection NOK"); + Serial.println("testConnection NOK"); // set the pin connected to the LED to act as output pin - pinMode(led, OUTPUT); - digitalWrite(led, HIGH); // turn off the led (inverted logic!) + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, HIGH); // turn off the led (inverted logic!) // inline keyboard customization // add a query button to the first row of the inline keyboard @@ -79,12 +92,12 @@ void loop() { // received a callback query message if (msg.callbackQueryData.equals(LIGHT_ON_CALLBACK)) { // pushed "LIGHT ON" button... - digitalWrite(led, LOW); // ...turn on the LED (inverted logic!) + digitalWrite(LED_PIN, LOW); // ...turn on the LED (inverted logic!) // terminate the callback with an alert message myBot.endQuery(msg.callbackQueryID, "Light on", true); } else if (msg.callbackQueryData.equals(LIGHT_OFF_CALLBACK)) { // pushed "LIGHT OFF" button... - digitalWrite(led, HIGH); // ...turn off the LED (inverted logic!) + digitalWrite(LED_PIN, HIGH); // ...turn off the LED (inverted logic!) // terminate the callback with a popup message myBot.endQuery(msg.callbackQueryID, "Light off"); } diff --git a/examples/lightBot/lightBot.ino b/examples/lightBot/lightBot.ino index 65b1a01..fefe408 100644 --- a/examples/lightBot/lightBot.ino +++ b/examples/lightBot/lightBot.ino @@ -1,21 +1,23 @@ /* Name: lightBot.ino -Created: 17/01/2018 +Created: 20/09/2024 Author: Stefano Ledda +Ported Arduino JSON v7: Alexander Drovosekov Description: a simple example that do: 1) parse incoming messages 2) if "LIGHT ON" message is received, turn on the onboard LED 3) if "LIGHT OFF" message is received, turn off the onboard LED 4) otherwise, reply to sender with a welcome message - */ +#include #include "CTBot.h" CTBot myBot; -String ssid = "mySSID"; // REPLACE mySSID WITH YOUR WIFI SSID -String pass = "myPassword"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY -String token = "myToken"; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN -uint8_t led = 2; // the onboard ESP8266 LED. +String ssid = "YOUR_SSID"; // REPLACE mySSID WITH YOUR WIFI SSID +String pass = "YOUR_WIFI_PASSWORD"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY +String token = "TELEGRAM_TOKEN" ; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN + +#define LED_PIN 2 // the onboard ESP8266 LED. // If you have a NodeMCU you can use the BUILTIN_LED pin // (replace 2 with BUILTIN_LED) @@ -24,21 +26,31 @@ void setup() { Serial.begin(115200); Serial.println("Starting TelegramBot..."); - // connect the ESP8266 to the desired access point - myBot.wifiConnect(ssid, pass); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); // set the telegram bot token myBot.setTelegramToken(token); // check if all things are ok if (myBot.testConnection()) - Serial.println("\ntestConnection OK"); + Serial.println("testConnection OK"); else - Serial.println("\ntestConnection NOK"); + Serial.println("testConnection NOK"); // set the pin connected to the LED to act as output pin - pinMode(led, OUTPUT); - digitalWrite(led, HIGH); // turn off the led (inverted logic!) + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, HIGH); // turn off the led (inverted logic!) } @@ -50,17 +62,17 @@ void loop() { if (CTBotMessageText == myBot.getNewMessage(msg)) { if (msg.text.equalsIgnoreCase("LIGHT ON")) { // if the received message is "LIGHT ON"... - digitalWrite(led, LOW); // turn on the LED (inverted logic!) + digitalWrite(LED_PIN, LOW); // turn on the LED (inverted logic!) myBot.sendMessage(msg.sender.id, "Light is now ON"); // notify the sender } else if (msg.text.equalsIgnoreCase("LIGHT OFF")) { // if the received message is "LIGHT OFF"... - digitalWrite(led, HIGH); // turn off the led (inverted logic!) + digitalWrite(LED_PIN, HIGH); // turn off the led (inverted logic!) myBot.sendMessage(msg.sender.id, "Light is now OFF"); // notify the sender } else { // otherwise... // generate the message for the sender String reply; - reply = (String)"Welcome " + msg.sender.username + (String)". Try LIGHT ON or LIGHT OFF."; + reply = "Welcome " + msg.sender.username + ". Try LIGHT ON or LIGHT OFF."; myBot.sendMessage(msg.sender.id, reply); // and send it } } diff --git a/examples/replyKeyboard/replyKeyboard.ino b/examples/replyKeyboard/replyKeyboard.ino index 8b3fbbb..5b69ea9 100644 --- a/examples/replyKeyboard/replyKeyboard.ino +++ b/examples/replyKeyboard/replyKeyboard.ino @@ -1,7 +1,8 @@ /* Name: replyKeyboard.ino -Created: 07/10/2019 +Created: 20/09/2024 Author: Stefano Ledda +Ported Arduino JSON v7: Alexander Drovosekov Description: a simple example that do: 1) if a "show keyboard" text message is received, show the reply keyboard, otherwise reply the sender with "Try 'show keyboard'" message @@ -17,26 +18,36 @@ CTBot myBot; CTBotReplyKeyboard myKbd; // reply keyboard object helper bool isKeyboardActive; // store if the reply keyboard is shown -String ssid = "mySSID"; // REPLACE mySSID WITH YOUR WIFI SSID -String pass = "myPassword"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY -String token = "myToken"; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN +String ssid = "YOUR_SSID"; // REPLACE mySSID WITH YOUR WIFI SSID +String pass = "YOUR_WIFI_PASSWORD"; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY +String token = "TELEGRAM_TOKEN" ; // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN void setup() { // initialize the Serial Serial.begin(115200); Serial.println("Starting TelegramBot..."); - // connect the ESP8266 to the desired access point - myBot.wifiConnect(ssid, pass); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); // set the telegram bot token myBot.setTelegramToken(token); // check if all things are ok if (myBot.testConnection()) - Serial.println("\ntestConnection OK"); + Serial.println("testConnection OK"); else - Serial.println("\ntestConnection NOK"); + Serial.println("testConnection NOK"); // reply keyboard customization // add a button that send a message with "Simple button" text diff --git a/library.json b/library.json index 2555ebf..38cc591 100644 --- a/library.json +++ b/library.json @@ -2,12 +2,12 @@ "name": "CTBot", "keywords": "telegram, bot", "description": "Simple Arduino Telegram BOT library for ESP8266/ESP32", - "homepage": "https://github.com/shurillu/CTBot", + "homepage": "https://github.com/drovosekov/CTBot", "repository": { "type": "git", - "url": "https://github.com/shurillu/CTBot.git" + "url": "https://github.com/drovosekov/CTBot.git" }, - "version": "2.1.13", + "version": "3.2.0", "authors": { "name": "Stefano Ledda", "email": "shurillu@tiscalinet.it" @@ -16,6 +16,6 @@ "platforms": "esp8266,esp32", "dependencies": { "name": "ArduinoJson", - "version": "6.19.4" + "version": "7.2.0" } } diff --git a/library.properties b/library.properties index c01aba9..44f1bed 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=CTBot -version=2.1.13 +version=2.2.0 author=Stefano Ledda maintainer=Stefano Ledda sentence=Simple Arduino Telegram BOT library for ESP8266/ESP32 -paragraph=A simple, easy to use and strightforward Arduino library for using Telegram bots on ESP8266/ESP32 chips. In order to use this library you need the ArduinoJson library (release 5.13.5 or greater) installed. Inline and Reply keyboard supported. Localization messages supported. Fingerprint authentication and 2.5.0+ ESP8266 Toolchain/Library supported. NEW: ArduinoJson version 6 supperted! +paragraph=A simple, easy to use and strightforward Arduino library for using Telegram bots on ESP8266/ESP32 chips. In order to use this library you need the ArduinoJson library (release 5.13.5 or greater) installed. Inline and Reply keyboard supported. Localization messages supported. Fingerprint authentication and 2.5.0+ ESP8266 Toolchain/Library supported. NEW: ArduinoJson version 7 supperted! category=Communication -url=https://github.com/shurillu/CTBot +url=https://github.com/drovosekov/CTBot architectures=esp8266,esp32 diff --git a/src/CTBot.cpp b/src/CTBot.cpp index 060e102..d61fafe 100644 --- a/src/CTBot.cpp +++ b/src/CTBot.cpp @@ -1,7 +1,12 @@ +// This is an independent project of an individual developer. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: https://pvs-studio.com + // for using int_64 data -#define ARDUINOJSON_USE_LONG_LONG 1 +#define ARDUINOJSON_USE_LONG_LONG 1 // for decoding UTF8/UNICODE -#define ARDUINOJSON_DECODE_UNICODE 1 +#define ARDUINOJSON_DECODE_UNICODE 1 + +#define ARDUINOJSON_ENABLE_STD_STRING 1 #if defined(ARDUINO_ARCH_ESP8266) // ESP8266 // for strings stored in FLASH - only for ESP8266 @@ -11,177 +16,312 @@ #include "CTBot.h" #include "Utilities.h" - -CTBot::CTBot() { - m_wifiConnectionTries = 0; // wait until connection to the AP is established (locking!) - m_token = ""; // no token - m_lastUpdate = 0; // not updated yet - m_useDNS = false; // use static IP for Telegram Server - m_UTF8Encoding = false; // no UTF8 encoded string conversion +// header string for standard (no binary) messages +// parameters: +// 1) token +// 2) Telegram API command +// 3) payload length/size +// 4) content type +// #define CTBOT_HEADER_STRING CFSTR("POST /bot%s/%s HTTP/1.1\r\nHost: api.telegram.org\r\nContent-Length: %d\r\nContent-Type: %s\r\n\r\n") +#define CTBOT_HEADER_STRING CFSTR("POST https://api.telegram.org/bot%s/%s HTTP/1.1\r\nHost: api.telegram.org\r\nContent-Length: %d\r\nContent-Type: %s\r\n\r\n") + +// payload header for binary messages +// parameters: +// 1) form boundary +// 2) chat ID +// 3) form boundary +// 4) Telegram type data (photo - audio - document) +// 5) Filename +// 6) Content type +// #define CTBOT_PAYLOAD_HEADER_STRING CFSTR("--%s\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\n%" PRId64 "\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n") +#define CTBOT_PAYLOAD_HEADER_STRING CFSTR("--%s\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\n%lld\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n") +// payload footer for binary messages +// parameters: +// 1) form boundary +// #define CTBOT_PAYLOAD_FOOTER_STRING CFSTR("\r\n--%s--\r\n\r\n") +#define CTBOT_PAYLOAD_FOOTER_STRING CFSTR("\r\n--%s--\r\n") + +// content types +#define CTBOT_CONTENT_TYPE_JSON CFSTR("application/json") +#define CTBOT_CONTENT_TYPE_JPEG CFSTR("image/jpeg") +#define CTBOT_CONTENT_TYPE_TEXT CFSTR("text/plain") +#define CTBOT_CONTENT_TYPE_RAW CFSTR("application/octet-stream") +#define CTBOT_CONTENT_TYPE_MULTIPART CFSTR("multipart/form-data; boundary=%s") + +// form boundary +#define CTBOT_FORM_BOUNDARY FSTR("----CTBotFormBoundary1357924680") + +// telegram commands +#define CTBOT_COMMAND_GETUPDATES CFSTR("getUpdates") +#define CTBOT_COMMAND_SENDMESSAGE CFSTR("sendMessage") +#define CTBOT_COMMAND_ENDQUERY CFSTR("answerCallbackQuery") +#define CTBOT_COMMAND_GETME CFSTR("getMe") +#define CTBOT_COMMAND_EDITMESSAGETEXT CFSTR("editMessageText") +#define CTBOT_COMMAND_DELETEMESSAGE CFSTR("deleteMessage") +#define CTBOT_COMMAND_SENDPHOTO CFSTR("sendPhoto") +#define CTBOT_COMMAND_SENDDOCUMENT CFSTR("sendDocument") + +CTBot::CTBot() +{ + m_token = NULL; // no token + m_lastUpdate = 0; // not updated yet m_lastUpdateTimeStamp = millis(); + m_keepAlive = true; + m_parseMode = CTBotParseModeDisabled; + m_silentNotification = false; } -CTBot::~CTBot() { -} - -String CTBot::sendCommand(const String& command, const String& parameters) -{ - // must filter command + parameters from escape sequences and spaces - const String URL = (String)"GET https://api.telegram.org/bot" + m_token + (String)"/" + command + parameters; - - // send the HTTP request - return(m_connection.send(URL)); -} - -String CTBot::toUTF8(String message) -{ - String converted = ""; - uint16_t i = 0; - String subMessage; - while (i < message.length()) { - subMessage = (String)message[i]; - if (message[i] != '\\') { - converted += subMessage; - i++; - } else { - // found "\" - i++; - if (i == message.length()) { - // no more characters - converted += subMessage; - } else { - subMessage += (String)message[i]; - if (message[i] != 'u') { - converted += subMessage; - i++; - } else { - //found \u escape code - i++; - if (i == message.length()) { - // no more characters - converted += subMessage; - } else { - uint8_t j = 0; - while ((j < 4) && ((j + i) < message.length())) { - subMessage += (String)message[i + j]; - j++; - } - i += j; - String utf8; - if (unicodeToUTF8(subMessage, utf8)) - converted += utf8; - else - converted += subMessage; - } - } - } - } - } - return(converted); +CTBot::~CTBot() +{ + free(m_token); } -void CTBot::enableUTF8Encoding(bool value) -{ m_UTF8Encoding = value;} +bool CTBot::setTelegramToken(const String &token) +{ + return setTelegramToken(token.c_str()); +} +bool CTBot::setTelegramToken(const char *token) +{ + int tokenSize; + free(m_token); + m_token = NULL; -void CTBot::setStatusPin(int8_t pin) -{ -/* - // if there is a valid status pin -> put it in high impedance - if (m_statusPin != CTBOT_DISABLE_STATUS_PIN) - pinMode(m_statusPin, INPUT); - m_statusPin = pin; - pinMode(m_statusPin, OUTPUT); -*/ - m_wifi.setStatusPin(pin); - m_connection.setStatusPin(pin); + if (NULL == token) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->setTelegramToken: token can't be NULL\n")); + return false; + } -} + tokenSize = strlen_P(token); + if (0 == tokenSize) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->setTelegramToken: token too short (%d)\n"), tokenSize); + return false; + } -void CTBot::setTelegramToken(const String& token) -{ m_token = token;} + m_token = (char *)malloc(tokenSize + 1); + if (NULL == m_token) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->setTelegramToken: unable to allocate memory\n")); + return false; + } -bool CTBot::testConnection(void){ - TBUser user; - return(getMe(user)); -} + memccpy_P(m_token, token, 1, tokenSize + 1); -bool CTBot::getMe(TBUser &user) { + return true; +} #if ARDUINOJSON_VERSION_MAJOR == 5 -#if CTBOT_BUFFER_SIZE > 0 - StaticJsonBuffer jsonBuffer; -#else - DynamicJsonBuffer jsonBuffer; -#endif - JsonObject& root = jsonBuffer.parse(sendCommand(FSTR("getMe"))); +bool CTBot::sendCommand(const char *command, const JsonObject &jsonData) +{ #elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); - DeserializationError error = deserializeJson(root, sendCommand("getMe")); - if (error) { - serialLog(FSTR("getNewMessage error: ArduinoJson deserialization error code: "), CTBOT_DEBUG_JSON); - serialLog(error.c_str(), CTBOT_DEBUG_JSON); - serialLog("\n", CTBOT_DEBUG_JSON); - return CTBotMessageNoData; - } +bool CTBot::sendCommand(const char *command, const DynamicJsonDocument &jsonData) +{ +#elif ARDUINOJSON_VERSION_MAJOR == 7 +bool CTBot::sendCommand(const char *command, const JsonDocument &jsonData) +{ #endif + bool response; + uint16_t headerSize, payloadSize; + char *pheader, *ppayload; + + if (NULL == m_token) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->sendCommand: no Telegram token defined\n")); + return false; + } - if (!root[FSTR("ok")]) { -#if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 - serialLog(FSTR("getMe error:\n"), CTBOT_DEBUG_JSON); #if ARDUINOJSON_VERSION_MAJOR == 5 - root.prettyPrintTo(Serial); -#elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJsonPretty(root, Serial); -#endif - serialLog("\n", CTBOT_DEBUG_JSON); + payloadSize = jsonData.measureLength() + 1; +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + payloadSize = measureJson(jsonData) + 1; #endif + + ppayload = (char *)malloc(payloadSize); + if (NULL == ppayload) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->sendCommand: unable to allocate memory\n")); return false; } -#if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 #if ARDUINOJSON_VERSION_MAJOR == 5 - root.prettyPrintTo(Serial); -#elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJsonPretty(root, Serial); -#endif - serialLog("\n", CTBOT_DEBUG_JSON); + jsonData.printTo(ppayload, payloadSize); +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + serializeJson(jsonData, ppayload, payloadSize); #endif - user.id = root[FSTR("result")][FSTR("id")].as(); - user.isBot = root[FSTR("result")][FSTR("is_bot")].as(); - user.firstName = root[FSTR("result")][FSTR("first_name")].as(); - user.lastName = root[FSTR("result")][FSTR("last_name")].as(); - user.username = root[FSTR("result")][FSTR("username")].as(); - user.languageCode = root[FSTR("result")][FSTR("language_code")].as(); - return true; + + // header + headerSize = snprintf_P(NULL, 0, CTBOT_HEADER_STRING, m_token, command, payloadSize, CTBOT_CONTENT_TYPE_JSON) + 1; + pheader = (char *)malloc(headerSize); + if (NULL == pheader) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->sendCommand: unable to allocate memory\n")); + free(ppayload); + return false; + } + snprintf_P(pheader, headerSize, CTBOT_HEADER_STRING, m_token, command, payloadSize, CTBOT_CONTENT_TYPE_JSON); + response = m_connection.POST(pheader, (uint8_t *)ppayload, payloadSize); + serialLog(CTBOT_DEBUG_CONNECTION, "--->sendCommand: Header\n%s\n", pheader); + serialLog(CTBOT_DEBUG_CONNECTION, "--->sendCommand: Payload\n%s\n", ppayload); + + free(ppayload); + free(pheader); + + return response; +} + +bool CTBot::sendMessageEx(int64_t id, const char *message, const char *keyboard) +{ + return editMessageTextEx(id, 0, message, keyboard); + /* + bool response; + + #if ARDUINOJSON_VERSION_MAJOR == 5 + #if CTBOT_BUFFER_SIZE > 0 + StaticJsonBuffer jsonBuffer; + #else + DynamicJsonBuffer jsonBuffer; + #endif + JsonObject& root = jsonBuffer.createObject(); + #elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); + #endif + + // payload + root[FSTR("chat_id")] = id; + root[FSTR("text")] = message; + + #if ARDUINOJSON_VERSION_MAJOR == 5 + #if CTBOT_BUFFER_SIZE > 0 + StaticJsonBuffer buf; + #else + DynamicJsonBuffer buf; + #endif + JsonObject& myKbd = buf.parse(keyboard); + + #elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument doc(CTBOT_JSON6_BUFFER_SIZE); + deserializeJson(doc, keyboard, strlen(keyboard)); + JsonObject myKbd = doc.as(); + #endif + + if (keyboard != NULL) + if (strlen(keyboard) != 0) + root[FSTR("reply_markup")] = myKbd; + + response = sendCommand(CTBOT_COMMAND_SENDMESSAGE, root); + + return response; + */ +} +bool CTBot::sendMessageEx(int64_t id, const char *message, CTBotInlineKeyboard &keyboard) +{ + return editMessageTextEx(id, 0, message, keyboard.getJSON()); } +bool CTBot::sendMessageEx(int64_t id, const char *message, CTBotReplyKeyboard &keyboard) +{ + return editMessageTextEx(id, 0, message, keyboard.getJSON()); +} + +int32_t CTBot::sendMessage(int64_t id, const char *message, const char *keyboard) +{ + CTBotMessageType result = CTBotMessageNoData; + TBMessage msg; + uint8_t i = 0; + + if (!editMessageTextEx(id, 0, message, keyboard)) + { + flushTelegramResponses(); + if (!m_keepAlive) + m_connection.disconnect(); + return 0; + } + + while ((CTBotMessageNoData == result) && (i < CTBOT_MAX_PARSERESPONSE)) + { + result = parseResponse(msg); + if (CTBotMessageNoData == result) + delay(CTBOT_DELAY_PARSERESPONSE); + i++; + } + if (!m_keepAlive) + m_connection.disconnect(); -CTBotMessageType CTBot::getNewMessage(TBMessage& message, bool blocking) { + if (result != CTBotMessageACK) + return 0; + return msg.messageID; +} +int32_t CTBot::sendMessage(int64_t id, const char *message, CTBotInlineKeyboard &keyboard) +{ + return sendMessage(id, message, keyboard.getJSON()); +} +int32_t CTBot::sendMessage(int64_t id, const char *message, CTBotReplyKeyboard &keyboard) +{ + return sendMessage(id, message, keyboard.getJSON()); +} - if (!blocking) { - // check if is passed CTBOT_GET_UPDATE_TIMEOUT ms from the last update - uint32_t currentTime = millis(); - if (m_lastUpdateTimeStamp > currentTime) { - // millis has done an overflow and start over - if (((UINT32_MAX - m_lastUpdateTimeStamp) + currentTime) < CTBOT_GET_UPDATE_TIMEOUT) - return CTBotMessageNoData; +bool CTBot::getUpdates() +{ + bool response; + + // check if is passed CTBOT_GET_UPDATE_TIMEOUT ms from the last update + // Telegram server accept updates only every 3 seconds + uint32_t currentTime = millis(); + if (m_lastUpdateTimeStamp > currentTime) + { + // millis has done an overflow and start over + if (((UINT32_MAX - m_lastUpdateTimeStamp) + currentTime) < CTBOT_GET_UPDATE_TIMEOUT) + { + // serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->getUpdates: too much updates in %ums\n"), CTBOT_GET_UPDATE_TIMEOUT); + return false; } - else { - if ((currentTime - m_lastUpdateTimeStamp) < CTBOT_GET_UPDATE_TIMEOUT) - return CTBotMessageNoData; + } + else + { + if ((currentTime - m_lastUpdateTimeStamp) < CTBOT_GET_UPDATE_TIMEOUT) + { + // serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->getUpdates: too much updates in %ums\n"), CTBOT_GET_UPDATE_TIMEOUT); + return false; } } - String parameters; - char buf[21]; +#if ARDUINOJSON_VERSION_MAJOR == 5 +#if CTBOT_BUFFER_SIZE > 0 + StaticJsonBuffer jsonBuffer; +#else + DynamicJsonBuffer jsonBuffer; +#endif + JsonObject &root = jsonBuffer.createObject(); +#elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument root; +#endif + + // payload + root[FSTR("limit")] = 1; + root[FSTR("allowed_updates")] = FSTR("[\"message\",\"callback_query\"]"); + root[FSTR("offset")] = m_lastUpdate; - message.messageType = CTBotMessageNoData; + response = sendCommand(CTBOT_COMMAND_GETUPDATES, root); + m_lastUpdateTimeStamp = millis(); + return response; +} + +CTBotMessageType CTBot::parseResponse(TBMessage &message, bool destructive) +{ + if (!m_connection.isConnected()) + return CTBotMessageNoData; + + const char *response = m_connection.receive(); + + if (NULL == response) + { + message.messageType = CTBotMessageNoData; + return CTBotMessageNoData; + } - ltoa(m_lastUpdate, buf, 10); - // polling timeout: add &timeout= - // default is zero (short polling). - parameters = FSTR("?limit=1&allowed_updates=[\"message\",\"callback_query\"]"); - if (m_lastUpdate != 0) - parameters += (String)FSTR("&offset=") + (String)buf; + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->parseResponse: response received\n")); #if ARDUINOJSON_VERSION_MAJOR == 5 #if CTBOT_BUFFER_SIZE > 0 @@ -189,147 +329,168 @@ CTBotMessageType CTBot::getNewMessage(TBMessage& message, bool blocking) { #else DynamicJsonBuffer jsonBuffer; #endif - JsonObject& root = jsonBuffer.parse(m_UTF8Encoding ? - toUTF8(sendCommand(FSTR("getUpdates"), parameters)) : - sendCommand(FSTR("getUpdates"), parameters)); + JsonObject &root = jsonBuffer.parse(response); #elif ARDUINOJSON_VERSION_MAJOR == 6 DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); - DeserializationError error = deserializeJson(root, m_UTF8Encoding ? - toUTF8(sendCommand(FSTR("getUpdates"), parameters)) : - sendCommand(FSTR("getUpdates"), parameters)); - - if (error) { - serialLog(FSTR("getNewMessage error: ArduinoJson deserialization error code: "), CTBOT_DEBUG_JSON); - serialLog(error.c_str(), CTBOT_DEBUG_JSON); - serialLog("\n", CTBOT_DEBUG_JSON); - m_lastUpdateTimeStamp = millis(); + DeserializationError error = deserializeJson(root, response); + + if (error) + { + serialLog(CTBOT_DEBUG_JSON, CFSTR("--->parseResponse: ArduinoJson deserialization error code: %s\n"), error.c_str()); + m_connection.freeMemory(); + message.messageType = CTBotMessageNoData; + return CTBotMessageNoData; + } +#else + JsonDocument root; + DeserializationError error = deserializeJson(root, response); + + if (error) + { + serialLog(CTBOT_DEBUG_JSON, CFSTR("--->parseResponse: ArduinoJson deserialization error code: %s\n"), error.c_str()); + m_connection.freeMemory(); + message.messageType = CTBotMessageNoData; return CTBotMessageNoData; } #endif - m_lastUpdateTimeStamp = millis(); + // free memory - all the data is now stored in the ArduinoJSON object + m_connection.freeMemory(); - if (!root[FSTR("ok")]) { + if (!root[FSTR("ok")]) + { #if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 - serialLog(FSTR("getNewMessage error: "), CTBOT_DEBUG_JSON); + serialLog(CTBOT_DEBUG_JSON, CFSTR("--->parseResponse: JSON error ")); #if ARDUINOJSON_VERSION_MAJOR == 5 root.prettyPrintTo(Serial); -#elif ARDUINOJSON_VERSION_MAJOR == 6 +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 serializeJsonPretty(root, Serial); #endif - serialLog("\n", CTBOT_DEBUG_JSON); + serialLog(CTBOT_DEBUG_JSON, "\n"); #endif + message.messageType = CTBotMessageNoData; return CTBotMessageNoData; } #if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 - serialLog(FSTR("getNewMessage JSON: "), CTBOT_DEBUG_JSON); + serialLog(CTBOT_DEBUG_JSON, CFSTR("--->parseResponse: JSON ")); #if ARDUINOJSON_VERSION_MAJOR == 5 root.prettyPrintTo(Serial); -#elif ARDUINOJSON_VERSION_MAJOR == 6 +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 serializeJsonPretty(root, Serial); #endif - serialLog("\n", CTBOT_DEBUG_JSON); + serialLog(CTBOT_DEBUG_JSON, "\n"); #endif uint32_t updateID = root[FSTR("result")][0][FSTR("update_id")].as(); if (0 == updateID) - return CTBotMessageNoData; - m_lastUpdate = updateID + 1; + { + if (root[FSTR("result")].size() > 0) + { + // no updateID but result not empty -> ack (sendMessage/editMessage, endQuery, deleteMessage, etc) + message.messageID = root[FSTR("result")][FSTR("message_id")].as(); + message.sender.id = root[FSTR("result")][FSTR("from")][FSTR("id")].as(); + message.sender.username = root[FSTR("result")][FSTR("from")][FSTR("username")].as(); + message.sender.firstName = root[FSTR("result")][FSTR("from")][FSTR("first_name")].as(); + message.date = root[FSTR("result")][FSTR("date")].as(); + message.text = root[FSTR("result")][FSTR("text")].as(); + message.group.id = root[FSTR("result")][FSTR("chat")][FSTR("id")].as(); + message.messageType = CTBotMessageACK; + return CTBotMessageACK; + } + // the field is not present -> an empty getUpdates (no new messages) + message.messageType = CTBotMessageOK; + return CTBotMessageOK; + } + if (destructive) + m_lastUpdate = updateID + 1; - if (root[FSTR("result")][0][FSTR("callback_query")][FSTR("id")]) { + if (root[FSTR("result")][0][FSTR("callback_query")][FSTR("id")]) + { // this is a callback query - message.messageID = root[FSTR("result")][0][FSTR("callback_query")][FSTR("message")][FSTR("message_id")].as(); - message.text = root[FSTR("result")][0][FSTR("callback_query")][FSTR("message")][FSTR("text")].as(); - message.date = root[FSTR("result")][0][FSTR("callback_query")][FSTR("message")][FSTR("date")].as(); - message.group.id = root[FSTR("result")][0][FSTR("callback_query")][FSTR("message")][FSTR("chat")][FSTR("id")].as(); - message.sender.id = root[FSTR("result")][0][FSTR("callback_query")][FSTR("from")][FSTR("id")].as(); - message.sender.username = root[FSTR("result")][0][FSTR("callback_query")][FSTR("from")][FSTR("username")].as(); - message.sender.firstName = root[FSTR("result")][0][FSTR("callback_query")][FSTR("from")][FSTR("first_name")].as(); - message.sender.lastName = root[FSTR("result")][0][FSTR("callback_query")][FSTR("from")][FSTR("last_name")].as(); - message.callbackQueryID = root[FSTR("result")][0][FSTR("callback_query")][FSTR("id")].as(); + message.messageID = root[FSTR("result")][0][FSTR("callback_query")][FSTR("message")][FSTR("message_id")].as(); + message.text = root[FSTR("result")][0][FSTR("callback_query")][FSTR("message")][FSTR("text")].as(); + message.date = root[FSTR("result")][0][FSTR("callback_query")][FSTR("message")][FSTR("date")].as(); + message.group.id = root[FSTR("result")][0][FSTR("callback_query")][FSTR("message")][FSTR("chat")][FSTR("id")].as(); + message.sender.id = root[FSTR("result")][0][FSTR("callback_query")][FSTR("from")][FSTR("id")].as(); + message.sender.username = root[FSTR("result")][0][FSTR("callback_query")][FSTR("from")][FSTR("username")].as(); + message.sender.firstName = root[FSTR("result")][0][FSTR("callback_query")][FSTR("from")][FSTR("first_name")].as(); + message.sender.lastName = root[FSTR("result")][0][FSTR("callback_query")][FSTR("from")][FSTR("last_name")].as(); + message.callbackQueryID = root[FSTR("result")][0][FSTR("callback_query")][FSTR("id")].as(); message.callbackQueryData = root[FSTR("result")][0][FSTR("callback_query")][FSTR("data")].as(); - message.chatInstance = root[FSTR("result")][0][FSTR("callback_query")][FSTR("chat_instance")].as(); - message.messageType = CTBotMessageQuery; - - serialLog(FSTR("--->getNewMessage: Free heap memory : "), CTBOT_DEBUG_MEMORY); - serialLog(ESP.getFreeHeap(), CTBOT_DEBUG_MEMORY); - serialLog("\n", CTBOT_DEBUG_MEMORY); - + message.chatInstance = root[FSTR("result")][0][FSTR("callback_query")][FSTR("chat_instance")].as(); + message.messageType = CTBotMessageQuery; + // serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->parseResponse: Free heap memory : %u\n"), ESP.getFreeHeap()); return CTBotMessageQuery; } - else if (root[FSTR("result")][0][FSTR("message")][FSTR("message_id")]) { + else if (root[FSTR("result")][0][FSTR("message")][FSTR("message_id")]) + { // this is a message - message.messageID = root[FSTR("result")][0][FSTR("message")][FSTR("message_id")].as(); - message.sender.id = root[FSTR("result")][0][FSTR("message")][FSTR("from")][FSTR("id")].as(); - message.sender.username = root[FSTR("result")][0][FSTR("message")][FSTR("from")][FSTR("username")].as(); + message.messageID = root[FSTR("result")][0][FSTR("message")][FSTR("message_id")].as(); + message.sender.id = root[FSTR("result")][0][FSTR("message")][FSTR("from")][FSTR("id")].as(); + message.sender.username = root[FSTR("result")][0][FSTR("message")][FSTR("from")][FSTR("username")].as(); message.sender.firstName = root[FSTR("result")][0][FSTR("message")][FSTR("from")][FSTR("first_name")].as(); - message.sender.lastName = root[FSTR("result")][0][FSTR("message")][FSTR("from")][FSTR("last_name")].as(); - message.group.id = root[FSTR("result")][0][FSTR("message")][FSTR("chat")][FSTR("id")].as(); - message.group.title = root[FSTR("result")][0][FSTR("message")][FSTR("chat")][FSTR("title")].as(); - message.date = root[FSTR("result")][0][FSTR("message")][FSTR("date")].as(); + message.sender.lastName = root[FSTR("result")][0][FSTR("message")][FSTR("from")][FSTR("last_name")].as(); + message.group.id = root[FSTR("result")][0][FSTR("message")][FSTR("chat")][FSTR("id")].as(); + message.group.title = root[FSTR("result")][0][FSTR("message")][FSTR("chat")][FSTR("title")].as(); + message.date = root[FSTR("result")][0][FSTR("message")][FSTR("date")].as(); #if ARDUINOJSON_VERSION_MAJOR == 5 - if (root[FSTR("result")][0][FSTR("message")][FSTR("text")].as().length() != 0) { -#elif ARDUINOJSON_VERSION_MAJOR == 6 - if (root[FSTR("result")][0][FSTR("message")][FSTR("text")]) { + if (root[FSTR("result")][0][FSTR("message")][FSTR("text")].as().length() != 0) + { +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + if (root[FSTR("result")][0][FSTR("message")][FSTR("text")]) + { #endif // this is a text message message.text = root[FSTR("result")][0][FSTR("message")][FSTR("text")].as(); message.messageType = CTBotMessageText; - - serialLog(FSTR("--->getNewMessage: Free heap memory : "), CTBOT_DEBUG_MEMORY); - serialLog(ESP.getFreeHeap(), CTBOT_DEBUG_MEMORY); - serialLog("\n", CTBOT_DEBUG_MEMORY); - + // serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->parseResponse: Free heap memory : %u\n"), ESP.getFreeHeap()); return CTBotMessageText; } - else if (root[FSTR("result")][0][FSTR("message")][FSTR("location")]) { + else if (root[FSTR("result")][0][FSTR("message")][FSTR("location")]) + { // this is a location message message.location.longitude = root[FSTR("result")][0][FSTR("message")][FSTR("location")][FSTR("longitude")].as(); - message.location.latitude = root[FSTR("result")][0][FSTR("message")][FSTR("location")][FSTR("latitude")].as(); + message.location.latitude = root[FSTR("result")][0][FSTR("message")][FSTR("location")][FSTR("latitude")].as(); message.messageType = CTBotMessageLocation; - - serialLog(FSTR("--->getNewMessage: Free heap memory : "), CTBOT_DEBUG_MEMORY); - serialLog(ESP.getFreeHeap(), CTBOT_DEBUG_MEMORY); - serialLog("\n", CTBOT_DEBUG_MEMORY); - + // serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->parseResponse: Free heap memory : %u\n"), ESP.getFreeHeap()); return CTBotMessageLocation; } - else if (root[FSTR("result")][0][FSTR("message")][FSTR("contact")]) { + else if (root[FSTR("result")][0][FSTR("message")][FSTR("contact")]) + { // this is a contact message - message.contact.id = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("user_id")].as(); - message.contact.firstName = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("first_name")].as(); - message.contact.lastName = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("last_name")].as(); + message.contact.id = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("user_id")].as(); + message.contact.firstName = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("first_name")].as(); + message.contact.lastName = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("last_name")].as(); message.contact.phoneNumber = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("phone_number")].as(); - message.contact.vCard = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("vcard")].as(); + message.contact.vCard = root[FSTR("result")][0][FSTR("message")][FSTR("contact")][FSTR("vcard")].as(); message.messageType = CTBotMessageContact; - - serialLog(FSTR("--->getNewMessage: Free heap memory : "), CTBOT_DEBUG_MEMORY); - serialLog(ESP.getFreeHeap(), CTBOT_DEBUG_MEMORY); - serialLog("\n", CTBOT_DEBUG_MEMORY); - + // serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->parseResponse: Free heap memory : %u\n"), ESP.getFreeHeap()); return CTBotMessageContact; } } + // no valid/handled message + message.messageType = CTBotMessageNoData; return CTBotMessageNoData; } - -int32_t CTBot::sendMessage(int64_t id, const String& message, const String& keyboard) +CTBotMessageType CTBot::parseResponse(TBUser &user) { - String parameters; - String strID; - - if (0 == message.length()) - return 0; + if (!m_connection.isConnected()) + { + // no active connection -> reset the waiting response variable; + return CTBotMessageNoData; + } - strID = int64ToAscii(id); + const char *response = m_connection.receive(); - parameters = (String)FSTR("?chat_id=") + strID + (String)FSTR("&text=") + URLEncodeMessage(message); + if (NULL == response) + { + return CTBotMessageNoData; + } - if (keyboard.length() != 0) - parameters += (String)FSTR("&reply_markup=") + keyboard; + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->parseResponse: response received\n")); #if ARDUINOJSON_VERSION_MAJOR == 5 #if CTBOT_BUFFER_SIZE > 0 @@ -337,67 +498,111 @@ int32_t CTBot::sendMessage(int64_t id, const String& message, const String& keyb #else DynamicJsonBuffer jsonBuffer; #endif - JsonObject& root = jsonBuffer.parse(sendCommand(FSTR("sendMessage"), parameters)); + JsonObject &root = jsonBuffer.parse(response); #elif ARDUINOJSON_VERSION_MAJOR == 6 DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); - DeserializationError error = deserializeJson(root, sendCommand(FSTR("sendMessage"), parameters)); - if (error) { - serialLog(FSTR("getNewMessage error: ArduinoJson deserialization error code: "), CTBOT_DEBUG_JSON); - serialLog(error.c_str(), CTBOT_DEBUG_JSON); - serialLog("\n", CTBOT_DEBUG_JSON); - return 0; + DeserializationError error = deserializeJson(root, response); + + if (error) + { + serialLog(CTBOT_DEBUG_JSON, CFSTR("--->parseResponse: ArduinoJson deserialization error code: %s\n"), error.c_str()); + m_connection.freeMemory(); + return CTBotMessageNoData; + } +#else + JsonDocument root; + DeserializationError error = deserializeJson(root, response); + + if (error) + { + serialLog(CTBOT_DEBUG_JSON, CFSTR("--->parseResponse: ArduinoJson deserialization error code: %s\n"), error.c_str()); + m_connection.freeMemory(); + return CTBotMessageNoData; } #endif - if (!root[FSTR("ok")]) { + // free memory - all the data is now stored in the ArduinoJSON object + m_connection.freeMemory(); + + if (!root[FSTR("ok")]) + { #if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 - serialLog(FSTR("SendMessage error: "), CTBOT_DEBUG_JSON); + serialLog(CTBOT_DEBUG_JSON, CFSTR("--->parseResponse: JSON error ")); #if ARDUINOJSON_VERSION_MAJOR == 5 root.prettyPrintTo(Serial); -#elif ARDUINOJSON_VERSION_MAJOR == 6 +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 serializeJsonPretty(root, Serial); #endif - serialLog("\n", CTBOT_DEBUG_JSON); + serialLog(CTBOT_DEBUG_JSON, "\n"); #endif - return 0; + return CTBotMessageNoData; } #if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 - serialLog(FSTR("SendMessage JSON: "), CTBOT_DEBUG_JSON); + serialLog(CTBOT_DEBUG_JSON, CFSTR("--->parseResponse: JSON ")); #if ARDUINOJSON_VERSION_MAJOR == 5 root.prettyPrintTo(Serial); -#elif ARDUINOJSON_VERSION_MAJOR == 6 +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 serializeJsonPretty(root, Serial); #endif - serialLog("\n", CTBOT_DEBUG_JSON); + serialLog(CTBOT_DEBUG_JSON, "\n"); #endif - return root[FSTR("result")][FSTR("message_id")].as(); - ; -} - -int32_t CTBot::sendMessage(int64_t id, const String& message, CTBotInlineKeyboard &keyboard) { - return(sendMessage(id, message, keyboard.getJSON())); -} + user.firstName = root[FSTR("result")][FSTR("first_name")].as(); + user.id = root[FSTR("result")][FSTR("id")].as(); + user.isBot = root[FSTR("result")][FSTR("is_bot")].as(); + user.username = root[FSTR("result")][FSTR("username")].as(); -int32_t CTBot::sendMessage(int64_t id, const String& message, CTBotReplyKeyboard &keyboard) { - return(sendMessage(id, message, keyboard.getJSON())); + return CTBotMessageContact; } -bool CTBot::editMessageText(int64_t id, int32_t messageID, const String& message, const String& keyboard) +CTBotMessageType CTBot::getNewMessage(TBMessage &message, CTBotGetMessageMode mode) { - String parameters; - String strID; + CTBotMessageType result = CTBotMessageNoData; + uint8_t i = 0; - if (0 == message.length()) - return false; + message.messageType = CTBotMessageNoData; + do + { + if (!getUpdates()) + { + flushTelegramResponses(); + + // if (!m_keepAlive) + // m_connection.disconnect(); + // return CTBotMessageNoData; + } + else + while ((CTBotMessageNoData == result) && (i < CTBOT_MAX_PARSERESPONSE)) + { + if ((mode & CTBotGetMessageDestructive) > 0) + { + result = parseResponse(message, true); + } + else + { + result = parseResponse(message, false); + } + + if (CTBotMessageNoData == result) + delay(CTBOT_DELAY_PARSERESPONSE); + i++; + } + } while ((CTBotMessageNoData == result) && ((mode & CTBotGetMessageBlocking) > 0)); - strID = int64ToAscii(id); + if (!m_keepAlive) + m_connection.disconnect(); + return result; +} - parameters = (String)FSTR("?chat_id=") + strID + (String)FSTR("&message_id=") + (String)messageID + (String)FSTR("&text=") + URLEncodeMessage(message); +bool CTBot::endQueryEx(const char *queryID, const char *message, bool alertMode) +{ + bool response; - if (keyboard.length() != 0) - parameters += (String)FSTR("&reply_markup=") + keyboard; + if (NULL == queryID) + return false; + if (queryID[0] == '\0') + return false; #if ARDUINOJSON_VERSION_MAJOR == 5 #if CTBOT_BUFFER_SIZE > 0 @@ -405,63 +610,182 @@ bool CTBot::editMessageText(int64_t id, int32_t messageID, const String& message #else DynamicJsonBuffer jsonBuffer; #endif - JsonObject& root = jsonBuffer.parse(sendCommand(FSTR("editMessageText"), parameters)); + JsonObject &root = jsonBuffer.createObject(); #elif ARDUINOJSON_VERSION_MAJOR == 6 DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); - DeserializationError error = deserializeJson(root, sendCommand(FSTR("editMessageText"), parameters)); - if (error) { - serialLog(FSTR("getNewMessage error: ArduinoJson deserialization error code: "), CTBOT_DEBUG_JSON); - serialLog(error.c_str(), CTBOT_DEBUG_JSON); - serialLog("\n", CTBOT_DEBUG_JSON); - return CTBotMessageNoData; - } +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument root; #endif - if (!root[FSTR("ok")]) { -#if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 - serialLog(FSTR("SendMessage error: "), CTBOT_DEBUG_JSON); + // payload + root[FSTR("callback_query_id")] = queryID; + + if (message != NULL) + { + if (message[0] != '\0') + { + root[FSTR("text")] = message; + root[FSTR("show_alert")] = alertMode; + } + } + response = sendCommand(CTBOT_COMMAND_ENDQUERY, root); + return response; +} +bool CTBot::endQuery(const char *queryID, const char *message, bool alertMode) +{ + CTBotMessageType result = CTBotMessageNoData; + TBMessage msg; + uint8_t i = 0; + + if (!endQueryEx(queryID, message, alertMode)) + { + flushTelegramResponses(); + if (!m_keepAlive) + m_connection.disconnect(); + return false; + } + while ((CTBotMessageNoData == result) && (i < CTBOT_MAX_PARSERESPONSE)) + { + result = parseResponse(msg); + if (CTBotMessageNoData == result) + delay(CTBOT_DELAY_PARSERESPONSE); + i++; + } + if (!m_keepAlive) + m_connection.disconnect(); + + if (result != CTBotMessageACK) + return false; + return true; +} + +bool CTBot::removeReplyKeyboardEx(int64_t id, const char *message, bool selective) +{ + uint16_t kbdSize; + char *pkbd; + bool result; + #if ARDUINOJSON_VERSION_MAJOR == 5 - root.prettyPrintTo(Serial); + DynamicJsonBuffer jsonBuffer; + JsonObject &root = jsonBuffer.createObject(); #elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJsonPretty(root, Serial); + DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument root; #endif - serialLog("\n", CTBOT_DEBUG_JSON); + + root[FSTR("remove_keyboard")] = true; + root[FSTR("selective")] = selective; + +#if ARDUINOJSON_VERSION_MAJOR == 5 + kbdSize = root.measureLength() + 1; +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + kbdSize = measureJson(root) + 1; #endif + + pkbd = (char *)malloc(kbdSize); + if (NULL == pkbd) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->removeReplyKeyboard: unable to allocate memory\n")); return false; } -#if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 - serialLog(FSTR("SendMessage JSON: "), CTBOT_DEBUG_JSON); #if ARDUINOJSON_VERSION_MAJOR == 5 - root.prettyPrintTo(Serial); + root.printTo(pkbd, kbdSize); #elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJsonPretty(root, Serial); -#endif - serialLog("\n", CTBOT_DEBUG_JSON); + serializeJson(root, pkbd, kbdSize); #endif - return true; + result = sendMessageEx(id, message, pkbd); + free(pkbd); + return result; } +bool CTBot::removeReplyKeyboard(int64_t id, const char *message, bool selective) +{ + CTBotMessageType result = CTBotMessageNoData; + TBMessage msg; + uint8_t i = 0; + + if (!removeReplyKeyboardEx(id, message, selective)) + { + flushTelegramResponses(); + if (!m_keepAlive) + m_connection.disconnect(); + return false; + } + + while ((CTBotMessageNoData == result) && (i < CTBOT_MAX_PARSERESPONSE)) + { + result = parseResponse(msg); + if (CTBotMessageNoData == result) + delay(CTBOT_DELAY_PARSERESPONSE); + i++; + } + if (!m_keepAlive) + m_connection.disconnect(); -bool CTBot::editMessageText(int64_t id, int32_t messageID, const String& message, CTBotInlineKeyboard &keyboard) { - return(editMessageText(id, messageID, message, keyboard.getJSON())); + if (result != CTBotMessageACK) + return false; + return true; } -bool CTBot::endQuery(const String& queryID, const String& message, bool alertMode) +bool CTBot::getMeEx() { - String parameters; + bool response; - if (0 == queryID.length()) +#if ARDUINOJSON_VERSION_MAJOR == 5 +#if CTBOT_BUFFER_SIZE > 0 + StaticJsonBuffer jsonBuffer; +#else + DynamicJsonBuffer jsonBuffer; +#endif + JsonObject &root = jsonBuffer.createObject(); +#elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument root; +#endif + response = sendCommand(CTBOT_COMMAND_GETME, root); + return response; +} +bool CTBot::getMe(TBUser &user) +{ + CTBotMessageType result = CTBotMessageNoData; + uint8_t i = 0; + + if (!getMeEx()) + { + flushTelegramResponses(); + if (!m_keepAlive) + m_connection.disconnect(); return false; + } - parameters = (String)FSTR("?callback_query_id=") + queryID; - - if (message.length() != 0) { - if (alertMode) - parameters += (String)FSTR("&text=") + URLEncodeMessage(message) + (String)FSTR("&show_alert=true"); - else - parameters += (String)FSTR("&text=") + URLEncodeMessage(message) + (String)FSTR("&show_alert=false"); + while ((CTBotMessageNoData == result) && (i < CTBOT_MAX_PARSERESPONSE)) + { + result = parseResponse(user); + if (CTBotMessageNoData == result) + delay(CTBOT_DELAY_PARSERESPONSE); + i++; } + if (!m_keepAlive) + m_connection.disconnect(); + + if (CTBotMessageContact == result) + return true; + + return false; +} + +bool CTBot::testConnection(void) +{ + TBUser user; + return getMe(user); +} + +bool CTBot::editMessageTextEx(int64_t id, int32_t messageID, const char *message, const char *keyboard) +{ + bool response; #if ARDUINOJSON_VERSION_MAJOR == 5 #if CTBOT_BUFFER_SIZE > 0 @@ -469,92 +793,441 @@ bool CTBot::endQuery(const String& queryID, const String& message, bool alertMod #else DynamicJsonBuffer jsonBuffer; #endif - JsonObject& root = jsonBuffer.parse(sendCommand(FSTR("answerCallbackQuery"), parameters)); + JsonObject &root = jsonBuffer.createObject(); #elif ARDUINOJSON_VERSION_MAJOR == 6 DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); - DeserializationError error = deserializeJson(root, sendCommand(FSTR("answerCallbackQuery"), parameters)); - if (error) { - serialLog(FSTR("getNewMessage error: ArduinoJson deserialization error code: "), CTBOT_DEBUG_JSON); - serialLog(error.c_str(), CTBOT_DEBUG_JSON); - serialLog("\n", CTBOT_DEBUG_JSON); - return CTBotMessageNoData; - } +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument root; #endif - if (!root[FSTR("ok")]) { -#if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 - serialLog(FSTR("answerCallbackQuery error: "), CTBOT_DEBUG_JSON); + // payload + root[FSTR("chat_id")] = id; + root[FSTR("text")] = message; + root[FSTR("disable_notification")] = m_silentNotification; + + if (CTBotParseModeHTML == m_parseMode) + { + root[FSTR("parse_mode")] = FSTR("HTML"); + } + else if (CTBotParseModeMarkdown == m_parseMode) + { + root[FSTR("parse_mode")] = FSTR("Markdown"); + } + else if (CTBotParseModeMarkdownV2 == m_parseMode) + { + root[FSTR("parse_mode")] = FSTR("MarkdownV2"); + } + if (messageID != 0) + root[FSTR("message_id")] = messageID; + #if ARDUINOJSON_VERSION_MAJOR == 5 - root.prettyPrintTo(Serial); -#elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJsonPretty(root, Serial); +#if CTBOT_BUFFER_SIZE > 0 + StaticJsonBuffer buf; +#else + DynamicJsonBuffer buf; #endif - serialLog("\n", CTBOT_DEBUG_JSON); + JsonObject &myKbd = buf.parse(keyboard); + +#elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument doc(CTBOT_JSON6_BUFFER_SIZE); + deserializeJson(doc, keyboard, strlen(keyboard)); + JsonObject myKbd = doc.as(); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument doc; + deserializeJson(doc, keyboard, strlen(keyboard)); + JsonObject myKbd = doc.as(); #endif + + if (keyboard != NULL) + if (keyboard[0] != '\0') + root[FSTR("reply_markup")] = myKbd; + + if (messageID != 0) + response = sendCommand(CTBOT_COMMAND_EDITMESSAGETEXT, root); + else + response = sendCommand(CTBOT_COMMAND_SENDMESSAGE, root); + + return response; +} +bool CTBot::editMessageTextEx(int64_t id, int32_t messageID, const char *message, CTBotInlineKeyboard &keyboard) +{ + return editMessageTextEx(id, messageID, message, keyboard.getJSON()); +} +bool CTBot::editMessageTextEx(int64_t id, int32_t messageID, const char *message, CTBotReplyKeyboard &keyboard) +{ + return editMessageTextEx(id, messageID, message, keyboard.getJSON()); +} + +bool CTBot::editMessageText(int64_t id, int32_t messageID, const char *message, const char *keyboard) +{ + CTBotMessageType result = CTBotMessageNoData; + TBMessage msg; + uint8_t i = 0; + + if (!editMessageTextEx(id, messageID, message, keyboard)) + { + flushTelegramResponses(); + if (!m_keepAlive) + m_connection.disconnect(); return false; } -#if (CTBOT_DEBUG_MODE & CTBOT_DEBUG_JSON) > 0 -#if ARDUINOJSON_VERSION_MAJOR == 5 - root.prettyPrintTo(Serial); -#elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJsonPretty(root, Serial); -#endif - serialLog("\n", CTBOT_DEBUG_JSON); -#endif + while ((CTBotMessageNoData == result) && (i < CTBOT_MAX_PARSERESPONSE)) + { + result = parseResponse(msg); + if (CTBotMessageNoData == result) + delay(CTBOT_DELAY_PARSERESPONSE); + i++; + } + if (!m_keepAlive) + m_connection.disconnect(); + if (result != CTBotMessageACK) + return false; return true; } +bool CTBot::editMessageText(int64_t id, int32_t messageID, const char *message, CTBotInlineKeyboard &keyboard) +{ + return editMessageText(id, messageID, message, keyboard.getJSON()); +} +bool CTBot::editMessageText(int64_t id, int32_t messageID, const char *message, CTBotReplyKeyboard &keyboard) +{ + return editMessageText(id, messageID, message, keyboard.getJSON()); +} + +bool CTBot::editMessageText(int64_t id, int32_t messageID, const String &message, const String &keyboard) +{ + return editMessageText(id, messageID, message.c_str(), keyboard.c_str()); +} +bool CTBot::editMessageText(int64_t id, int32_t messageID, const String &message, CTBotInlineKeyboard &keyboard) +{ + return editMessageText(id, messageID, message.c_str(), keyboard.getJSON()); +} +bool CTBot::editMessageText(int64_t id, int32_t messageID, const String &message, CTBotReplyKeyboard &keyboard) +{ + return editMessageText(id, messageID, message.c_str(), keyboard.getJSON()); +} -bool CTBot::removeReplyKeyboard(int64_t id, const String& message, bool selective) +bool CTBot::deleteMessageEx(int64_t id, int32_t messageID) { - String command; + bool response; #if ARDUINOJSON_VERSION_MAJOR == 5 +#if CTBOT_BUFFER_SIZE > 0 + StaticJsonBuffer jsonBuffer; +#else DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); +#endif + JsonObject &root = jsonBuffer.createObject(); #elif ARDUINOJSON_VERSION_MAJOR == 6 DynamicJsonDocument root(CTBOT_JSON6_BUFFER_SIZE); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument root; #endif - root[FSTR("remove_keyboard")] = true; - if (selective) { - root[FSTR("selective")] = true; + // payload + root[FSTR("chat_id")] = id; + root[FSTR("message_id")] = messageID; + + response = sendCommand(CTBOT_COMMAND_DELETEMESSAGE, root); + + return response; +} +bool CTBot::deleteMessage(int64_t id, int32_t messageID) +{ + CTBotMessageType result = CTBotMessageNoData; + TBMessage msg; + uint8_t i = 0; + + if (!deleteMessageEx(id, messageID)) + { + flushTelegramResponses(); + if (!m_keepAlive) + m_connection.disconnect(); + return false; } -#if ARDUINOJSON_VERSION_MAJOR == 5 - root.printTo(command); -#elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJson(root, command); -#endif + while ((CTBotMessageNoData == result) && (i < CTBOT_MAX_PARSERESPONSE)) + { + result = parseResponse(msg); + if (CTBotMessageNoData == result) + delay(CTBOT_DELAY_PARSERESPONSE); + i++; + } + if (!m_keepAlive) + m_connection.disconnect(); + + if (result != CTBotMessageACK) + return false; + return true; +} + +void CTBot::flushTelegramResponses() +{ + m_connection.flush(); +} - return sendMessage(id, message, command); +void CTBot::keepAlive(bool value) +{ + m_keepAlive = value; +} + +void CTBot::silentNotification(bool mode) +{ + m_silentNotification = mode; } -// ----------------------------| STUBS - FOR BACKWARD VERSION COMPATIBILITY +void CTBot::setParseMode(CTBotParseModeType parseMode) +{ + m_parseMode = parseMode; +} + +CTBotParseModeType CTBot::getParseMode(void) +{ + return m_parseMode; +} + +bool CTBot::sendImageEx(int64_t id, uint8_t *data, uint32_t dataSize) +{ + return sendBinaryDataEx(id, CTBotDataTypeJPEG, data, dataSize, CFSTR("pic.jpg")); +} +bool CTBot::sendImageEx(int64_t id, const File &fhandle, uint32_t dataSize) +{ + return sendBinaryDataEx(id, CTBotDataTypeJPEG, fhandle, dataSize, CFSTR("pic.jpg")); +} +bool CTBot::sendImage(int64_t id, uint8_t *data, uint32_t dataSize) +{ + return sendBinaryData(id, CTBotDataTypeJPEG, data, dataSize, CFSTR("pic.jpg")); +} +bool CTBot::sendImage(int64_t id, const File &fhandle, uint32_t dataSize) +{ + return sendBinaryData(id, CTBotDataTypeJPEG, fhandle, dataSize, CFSTR("pic.jpg")); +} -void CTBot::setMaxConnectionRetries(uint8_t retries) +bool CTBot::sendRawDataEx(int64_t id, uint8_t *data, uint32_t dataSize, const char *filename) +{ + return sendBinaryDataEx(id, CTBotDataTypeRAW, data, dataSize, filename); +} +bool CTBot::sendRawDataEx(int64_t id, const File &fhandle, uint32_t dataSize, const char *filename) +{ + return sendBinaryDataEx(id, CTBotDataTypeRAW, fhandle, dataSize, filename); +} +bool CTBot::sendRawData(int64_t id, uint8_t *data, uint32_t dataSize, const char *filename) { - m_wifi.setMaxConnectionRetries(retries); + return sendBinaryData(id, CTBotDataTypeRAW, data, dataSize, filename); } +bool CTBot::sendRawData(int64_t id, const File &fhandle, uint32_t dataSize, const char *filename) +{ + return sendBinaryData(id, CTBotDataTypeRAW, fhandle, dataSize, filename); +} + +bool CTBot::sendBinaryDataEx(int64_t id, CTBotDataType dataType, uint8_t *data, const File &fhandle, uint32_t dataSize, const char *filename) +{ + bool response; + uint32_t headerSize, payloadHeaderSize, payloadFooterSize, contentTypeSize, payloadSize; + char *pheader, *ppayloadHeader, *ppayloadFooter, *pcontentType; + const char *telegramDataType, *command, *dataContentType; + + if (NULL == m_token) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->sendBinaryData: no Telegram token defined\n")); + return false; + } + + switch (dataType) + { + case CTBotDataTypeJPEG: + command = CTBOT_COMMAND_SENDPHOTO; + telegramDataType = CFSTR("photo"); + dataContentType = CTBOT_CONTENT_TYPE_JPEG; + break; + case CTBotDataTypeText: + command = CTBOT_COMMAND_SENDDOCUMENT; + telegramDataType = CFSTR("document"); + dataContentType = CTBOT_CONTENT_TYPE_TEXT; + break; + case CTBotDataTypeRAW: + command = CTBOT_COMMAND_SENDDOCUMENT; + telegramDataType = CFSTR("document"); + dataContentType = CTBOT_CONTENT_TYPE_RAW; + break; + default: + return false; + } + + // payload header + payloadHeaderSize = snprintf_P(NULL, 0, CTBOT_PAYLOAD_HEADER_STRING, CTBOT_FORM_BOUNDARY, id, CTBOT_FORM_BOUNDARY, + telegramDataType, filename, dataContentType) + + 1; + ppayloadHeader = (char *)malloc(payloadHeaderSize); + if (NULL == ppayloadHeader) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->sendBinaryData: unable to allocate memory\n")); + return false; + } + // snprintf_P(ppayloadHeader, payloadHeaderSize, (char*)CTBOT_PAYLOAD_HEADER_STRING, CTBOT_FORM_BOUNDARY, id, CTBOT_FORM_BOUNDARY, + snprintf_P(ppayloadHeader, payloadHeaderSize, CTBOT_PAYLOAD_HEADER_STRING, CTBOT_FORM_BOUNDARY, id, CTBOT_FORM_BOUNDARY, + telegramDataType, filename, dataContentType); + + // payload footer + payloadFooterSize = snprintf_P(NULL, 0, CTBOT_PAYLOAD_FOOTER_STRING, CTBOT_FORM_BOUNDARY) + 1; + ppayloadFooter = (char *)malloc(payloadFooterSize); + if (NULL == ppayloadFooter) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->sendBinaryData: unable to allocate memory\n")); + free(ppayloadHeader); + return false; + } + snprintf_P(ppayloadFooter, payloadFooterSize, CTBOT_PAYLOAD_FOOTER_STRING, CTBOT_FORM_BOUNDARY); + + payloadSize = payloadHeaderSize + dataSize + payloadFooterSize; + + // content type + contentTypeSize = snprintf_P(NULL, 0, CTBOT_CONTENT_TYPE_MULTIPART, CTBOT_FORM_BOUNDARY) + 1; + pcontentType = (char *)malloc(contentTypeSize); + if (NULL == pcontentType) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->sendBinaryData: unable to allocate memory\n")); + free(ppayloadHeader); + free(ppayloadFooter); + return false; + } + snprintf_P(pcontentType, contentTypeSize, CTBOT_CONTENT_TYPE_MULTIPART, CTBOT_FORM_BOUNDARY); + + // header + headerSize = snprintf_P(NULL, 0, CTBOT_HEADER_STRING, m_token, command, payloadSize, pcontentType) + 1; + pheader = (char *)malloc(headerSize); + if (NULL == pheader) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->sendBinaryData: unable to allocate memory\n")); + free(ppayloadHeader); + free(ppayloadFooter); + free(pcontentType); + return false; + } + snprintf_P(pheader, headerSize, CTBOT_HEADER_STRING, m_token, command, payloadSize, pcontentType); + + if (fhandle) + response = m_connection.POST(pheader, fhandle, dataSize, ppayloadHeader, ppayloadFooter); + else if (data != NULL) + response = m_connection.POST(pheader, data, dataSize, ppayloadHeader, ppayloadFooter); + else + response = false; + + serialLog(CTBOT_DEBUG_CONNECTION, "--->sendBinaryData: Header\n%s\n", pheader); + serialLog(CTBOT_DEBUG_CONNECTION, "--->sendBinaryData: Payload header\n%s\n", ppayloadHeader); + serialLog(CTBOT_DEBUG_CONNECTION, "--->sendBinaryData: Payload footer\n%s\n", ppayloadFooter); -bool CTBot::setIP(const String& ip, const String& gateway, const String& subnetMask, const String& dns1, const String& dns2) + free(ppayloadHeader); + free(ppayloadFooter); + free(pcontentType); + free(pheader); + + return response; +} +bool CTBot::sendBinaryDataEx(int64_t id, CTBotDataType dataType, uint8_t *data, uint32_t dataSize, const char *filename) { - return(m_wifi.setIP(ip, gateway, subnetMask, dns1, dns2)); + return sendBinaryDataEx(id, dataType, data, File(), dataSize, filename); } +bool CTBot::sendBinaryDataEx(int64_t id, CTBotDataType dataType, const File &fhandle, uint32_t dataSize, const char *filename) +{ + return sendBinaryDataEx(id, dataType, NULL, fhandle, dataSize, filename); +} + +bool CTBot::sendBinaryData(int64_t id, CTBotDataType dataType, uint8_t *data, const File &fhandle, uint32_t dataSize, const char *filename) +{ + CTBotMessageType result = CTBotMessageNoData; + TBMessage msg; + uint8_t i = 0; + + if (!sendBinaryDataEx(id, dataType, data, fhandle, dataSize, filename)) + { + flushTelegramResponses(); + if (!m_keepAlive) + m_connection.disconnect(); + return false; + } -bool CTBot::wifiConnect(const String& ssid, const String& password) + while ((CTBotMessageNoData == result) && (i < CTBOT_MAX_PARSERESPONSE)) + { + result = parseResponse(msg); + if (CTBotMessageNoData == result) + delay(CTBOT_DELAY_PARSERESPONSE); + i++; + } + if (!m_keepAlive) + m_connection.disconnect(); + + if (result != CTBotMessageACK) + return false; + return true; +} +bool CTBot::sendBinaryData(int64_t id, CTBotDataType dataType, uint8_t *data, uint32_t dataSize, const char *filename) { - return(m_wifi.wifiConnect(ssid, password)); + return sendBinaryData(id, dataType, data, File(), dataSize, filename); } +bool CTBot::sendBinaryData(int64_t id, CTBotDataType dataType, const File &fhandle, uint32_t dataSize, const char *filename) +{ + return sendBinaryData(id, dataType, NULL, fhandle, dataSize, filename); +} + +// -----------------------STUBS - for backward compatibility bool CTBot::useDNS(bool value) { - return(m_connection.useDNS(value)); + return m_connection.useDNS(value); +} + +// void CTBot::setFingerprint(const uint8_t* newFingerprint) { +// m_connection.setFingerprint(newFingerprint); +// } + +void CTBot::disconnect() +{ + if (m_connection.isConnected()) + m_connection.disconnect(); +} + +int32_t CTBot::sendMessage(int64_t id, const String &message, const String &keyboard) +{ + return sendMessage(id, message.c_str(), keyboard.c_str()); +} +int32_t CTBot::sendMessage(int64_t id, const String &message, CTBotInlineKeyboard &keyboard) +{ + return sendMessage(id, message.c_str(), keyboard.getJSON()); +} +int32_t CTBot::sendMessage(int64_t id, const String &message, CTBotReplyKeyboard &keyboard) +{ + return sendMessage(id, message.c_str(), keyboard.getJSON()); } -void CTBot::setFingerprint(const uint8_t* newFingerprint) +CTBotMessageType CTBot::getNewMessage(TBMessage &message, bool blocking) { - m_connection.setFingerprint(newFingerprint); + /* + CTBotMessageType result = CTBotMessageNoData; + if (blocking) { + // delay(CTBOT_GET_UPDATE_TIMEOUT); + do { + result = getNewMessage(message); + } while (CTBotMessageNoData == result); + } + return result; + */ + + if (blocking) + return getNewMessage(message, CTBotGetMessageBlockingDestructive); + else + return getNewMessage(message); } +bool CTBot::removeReplyKeyboard(int64_t id, const String &message, bool selective) +{ + return removeReplyKeyboard(id, message.c_str(), selective); +} + +void CTBot::enableUTF8Encoding(bool) {} + +bool CTBot::endQuery(const String &queryID, const String &message, bool alertMode) +{ + return endQuery(queryID.c_str(), message.c_str(), alertMode); +} diff --git a/src/CTBot.h b/src/CTBot.h index 4ba2500..404853e 100644 --- a/src/CTBot.h +++ b/src/CTBot.h @@ -3,23 +3,22 @@ #define CTBOT #include +#include +#include "CTBotSecureConnection.h" +#include "CTBotDefines.h" #include "CTBotDataStructures.h" #include "CTBotInlineKeyboard.h" #include "CTBotReplyKeyboard.h" -#include "CTBotWifiSetup.h" -#include "CTBotSecureConnection.h" -#include "CTBotDefines.h" - class CTBot { - public: // default constructor CTBot(); // default destructor ~CTBot(); - // set a static ip. If not set, use the DHCP. + // Wifi stuff ------------------------------------------------------------------------------------------------------------------- + // set a static ip. If not set, use the DHCP. // params // ip : the ip address // gateway : the gateway address @@ -28,15 +27,7 @@ class CTBot // dns2 : the optional second DNS // returns // true if no error occurred - bool setIP(const String& ip, const String& gateway, const String& subnetMask, const String& dns1 = "", const String& dns2 = ""); - - // connect to a wifi network - // params - // ssid : the SSID network identifier - // password: the optional password - // returns - // true if no error occurred - bool wifiConnect(const String& ssid, const String& password = ""); + bool setIP(const char *ip, const char *gateway, const char *subnetMask, const char *dns1 = "", const char *dns2 = "") const; // set how many times the wifiConnect method have to try to connect to the specified SSID. // A value of zero mean infinite retries. @@ -45,11 +36,7 @@ class CTBot // retries: how many times wifiConnect have to try to connect void setMaxConnectionRetries(uint8_t retries); - // set the telegram token - // params - // token: the telegram token - void setTelegramToken(const String& token); - + // Connection stuff ------------------------------------------------------------------------------------------------------------- // use the URL style address "api.telegram.org" or the fixed IP address "149.154.167.198" // for all communication with the telegram server // Default value is true @@ -58,124 +45,250 @@ class CTBot // false -> use fixed IP addres bool useDNS(bool value); - // enable/disable the UTF8 encoding for the received message. - // Default value is false (disabled) - // param - // value: true -> encode the received message with UTF8 encoding rules - // false -> leave the received message as-is - void enableUTF8Encoding(bool value); + // set the new Telegram API server fingerprint overwriting the default one. + // It can be obtained by this service: https://www.grc.com/fingerprints.htm + // quering api.telegram.org + // params: + // newFingerprint: the array of 20 bytes that contains the new fingerprint + void setFingerprint(const uint8_t *newFingerprint); - // set the status pin used to connect a LED for visual notification - // CTBOT_DISABLE_STATUS_PIN will disable the notification - // default value is CTBOT_DISABLE_STATUS_PIN + // close the connection to the Telegram server + void disconnect(); + + // Telegram stuff --------------------------------------------------------------------------------------------------------------- + // set the telegram token // params - // pin: the pin used for visual notification - void setStatusPin(int8_t pin); + // token: the telegram token + // returns + // true if no error occurred + bool setTelegramToken(const char *token); - // test the connection between ESP8266 and the telegram server + // send a message to the specified telegram user ID + // params + // id : the telegram recipient user ID + // message : the message to send + // keyboard: the inline/reply keyboard (optional) + // (in json format or using the CTBotInlineKeyboard/CTBotReplyKeyboard class helper) // returns - // true if no error occurred - bool testConnection(void); + // the messageID of the sent message if no errors occurred. Zero otherwise + int32_t sendMessage(int64_t id, const char *message, const char *keyboard = ""); + int32_t sendMessage(int64_t id, const char *message, CTBotInlineKeyboard &keyboard); + int32_t sendMessage(int64_t id, const char *message, CTBotReplyKeyboard &keyboard); - // get the first unread message from the queue (text and query from inline keyboard). + // get the first unread message from the queue (text and query from inline keyboard). // This is a destructive operation: once read, the message will be marked as read // so a new getMessage will read the next message (if any). // params // message : the data structure that will contains the data retrieved - // blocking: false -> execute the member function only every CTBOT_GET_UPDATE_TIMEOUT milliseconds - // with this trick the Telegram Server responds very quickly - // true -> the old method, blocking the execution for aroun 3-4 second + // mode : change the behavior of the getNewMessage + // CTBotGetMessageNoOption - no blocking, no destructive + // CTBotGetMessageDestructuve - once read, the message is no more retrieved + // CTBotGetMessageBlocking - wait until a Telegram Server response + // CTBotGetMEssageBlockingDestructive - the sum of the two effects // returns - // CTBotMessageNoData: an error has occurred - // CTBotMessageText : the received message is a text - // CTBotMessageQuery : the received message is a query (from inline keyboards) - CTBotMessageType getNewMessage(TBMessage &message, bool blocking = false); + // CTBotMessageNoData : an error has occurred + // CTBotMessageText : the received message is a text + // CTBotMessageQuery : the received message is a query (from inline keyboards) + // CTBotMessageLocation: the received message is a location + // CTBotMessageContact : the received message is a contact + CTBotMessageType getNewMessage(TBMessage &message, CTBotGetMessageMode mode = CTBotGetMessageDestructive); // send a message to the specified telegram user ID // params - // id : the telegram recipient user ID + // id : the telegram recipient user ID // message : the message to send // keyboard: the inline/reply keyboard (optional) // (in json format or using the CTBotInlineKeyboard/CTBotReplyKeyboard class helper) // returns - // the messageID if no errors occurred, otherwise 0 - int32_t sendMessage(int64_t id, const String& message, const String& keyboard = ""); - int32_t sendMessage(int64_t id, const String& message, CTBotInlineKeyboard &keyboard); - int32_t sendMessage(int64_t id, const String& message, CTBotReplyKeyboard &keyboard); + // true if the data is sent correctly to the telegram server. To read + // the Telegram response, call the parseResponse() member function + bool sendMessageEx(int64_t id, const char *message, const char *keyboard = ""); + bool sendMessageEx(int64_t id, const char *message, CTBotInlineKeyboard &keyboard); + bool sendMessageEx(int64_t id, const char *, CTBotReplyKeyboard &keyboard); - // edits text or inline keyboard of a previous message for the specified telegram user ID - // params - // id : the telegram recipient user ID - // messageID : the ID of the message to be edited - // message : the new text - // keyboard : the inline/reply keyboard (optional) - // (in json format or using the CTBotInlineKeyboard/CTBotReplyKeyboard class helper) + // request an update (incoming messages) from Telegram server. To read the updates, call the + // parseResponse() member function. // returns // true if no error occurred - bool editMessageText(int64_t id, int32_t messageID, const String& message, const String& keyboard = ""); - bool editMessageText(int64_t id, int32_t messageID, const String& message, CTBotInlineKeyboard &keyboard); + bool getUpdates(); + + // parse a getUpdates/sendMessageEx result. While getNewMessage and sendMessage sends data + // to the Telegram server and read the response, getUpdates and sendMessageEx only send data + // to the Telegram server. To read the response, call this member function + // params + // message/user : the data structure that will contains the data retrieved + // destructive: set to read the retireved message (if any) + // returns + // CTBotMessageNoData : no data/an error has occurred + // CTBotMessageText : the received message is a text + // CTBotMessageQuery : the received message is a query (from inline/reply keyboards) + // CTBotMessageLocation: the received message is a location + // CTBotMessageContact : the received message is a contact + // CTBotMessageACK : the received message is an acknowledge (send/edit/delete message, qndQuery, etc) + // CTBotMessageOK : the received message is an acknowledge (getUpdates/getNewMessage with no new message) + CTBotMessageType parseResponse(TBMessage &message, bool destructive = true); + CTBotMessageType parseResponse(TBUser &user); // terminate a query started by pressing an inlineKeyboard button. The steps are: // 1) send a message with an inline keyboard // 2) wait for a (getNewMessage) of type CTBotMessageQuery - // 3) handle the query and then call endQuery with .callbackQueryID + // 3) handle the query and then call endQuery with .callbackQueryID // params // queryID : the unique query ID (retrieved with getNewMessage method) // message : an optional message // alertMode: false -> a simply popup message // true --> an alert message with ok button - bool endQuery(const String& queryID, const String& message = "", bool alertMode = false); + bool endQueryEx(const char *queryID, const char *message = "", bool alertMode = false); + bool endQuery(const char *queryID, const char *message = "", bool alertMode = false); // remove an active reply keyboard for a selected user, sending a message // params: - // id : the telegram user ID + // id : the telegram user ID // message : the message to be show to the selected user ID // selective: enable selective mode (hide the keyboard for specific users only) - // Targets: 1) users that are @mentioned in the text of the Message object; + // Targets: 1) users that are @mentioned in the text of the Message object; // 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message // return: // true if no error occurred - bool removeReplyKeyboard(int64_t id, const String& message, bool selective = false); + bool removeReplyKeyboardEx(int64_t id, const char *message, bool selective = false); + bool removeReplyKeyboard(int64_t id, const char *message, bool selective = false); - // set the new Telegram API server fingerprint overwriting the default one. - // It can be obtained by this service: https://www.grc.com/fingerprints.htm - // quering api.telegram.org - // params: - // newFingerprint: the array of 20 bytes that contains the new fingerprint - void setFingerprint(const uint8_t *newFingerprint); + // get some information about the bot + // params + // user: the data structure that will contains the data retreived + // returns + // true if no error occurred + bool getMeEx(); + bool getMe(TBUser &user); -private: - CTBotSecureConnection m_connection; - CTBotWifiSetup m_wifi; - uint8_t m_wifiConnectionTries; - String m_token; - int32_t m_lastUpdate; - bool m_useDNS; - bool m_UTF8Encoding; - uint32_t m_lastUpdateTimeStamp; + // test the connection between ESP8266/ESP32 and the telegram server + // returns + // true if no error occurred + bool testConnection(void); - // send commands to the telegram server. For info about commands, check the telegram api https://core.telegram.org/bots/api + // edits text or inline keyboard of a previous message for the specified telegram user ID // params - // command : the command to send, i.e. getMe - // parameters: optional parameters + // id : the telegram recipient user ID + // messageID : the ID of the message to be edited + // message : the new text + // keyboard : the inline/reply keyboard (optional) + // (in json format or using the CTBotInlineKeyboard/CTBotReplyKeyboard class helper) // returns - // an empty string if error - // a string containing the Telegram JSON response - String sendCommand(const String& command, const String& parameters = ""); + // true if no errors occurred + bool editMessageTextEx(int64_t id, int32_t messageID, const char *message, const char *keyboard = ""); + bool editMessageTextEx(int64_t id, int32_t messageID, const char *message, CTBotInlineKeyboard &keyboard); + bool editMessageTextEx(int64_t id, int32_t messageID, const char *message, CTBotReplyKeyboard &keyboard); + + bool editMessageText(int64_t id, int32_t messageID, const char *message, const char *keyboard); + bool editMessageText(int64_t id, int32_t messageID, const char *message, CTBotInlineKeyboard &keyboard); + bool editMessageText(int64_t id, int32_t messageID, const char *message, CTBotReplyKeyboard &keyboard); - // convert an UNICODE string to UTF8 encoded string + bool editMessageText(int64_t id, int32_t messageID, const String &message, const String &keyboard = ""); + bool editMessageText(int64_t id, int32_t messageID, const String &message, CTBotInlineKeyboard &keyboard); + bool editMessageText(int64_t id, int32_t messageID, const String &message, CTBotReplyKeyboard &keyboard); + + // delete a previously sent message // params - // message: the UNICODE message + // id : the telegram recipient/chat ID + // messageID : the message ID to be deleted // returns - // a string with the converted message in UTF8 - String toUTF8(String message); + // true if no errors occurred + bool deleteMessageEx(int64_t id, int32_t messageID); + bool deleteMessage(int64_t id, int32_t messageID); - // get some information about the bot + // drop (flush) all Telegram responses from the receive buffer + // useful in conjunction with all "*Ex" member functions when the + // Telegram response is not important (so it can be dropped/flushed) + void flushTelegramResponses(); + + // keep the connection alive after calling a non "*Ex" member (like getNewMesage, editMessageText etc) + // it is useful when the program needs to connect to other services after a member function calls + // param + // value: true -> keep the connection alive + // false -> close connection after a member function call + void keepAlive(bool value); + + // Enable/disable the silent notification + // param + // mode: true -> enable the silent notification. No sound when an incoming message is received + // false -> disable the silent notification + void silentNotification(bool mode); + + // set the message parse mode, like markdown or html (or disable the functionality) + // VERY IMPORTANT!!!! IN MARKDOWN MODE SPECIAL CHARACTER (like "!") MUST BE ESCAPED!!! + // READ THIS https://core.telegram.org/bots/api#markdownv2-style + // EXAMPLE: to escape the "!" character, use double backslash -> "\\!" + // param + // parseMode: new message parse mode + void setParseMode(CTBotParseModeType parseMode); + + // set the message parse mode, like markdown or html (or disable the functionality) + // param + // none + // returns + // current parse mode + CTBotParseModeType getParseMode(void); + + bool sendImageEx(int64_t id, uint8_t *data, uint32_t dataSize); + bool sendImageEx(int64_t id, const File &fhandle, uint32_t dataSize); + bool sendImage(int64_t id, uint8_t *data, uint32_t dataSize); + bool sendImage(int64_t id, const File &fhandle, uint32_t dataSize); + + bool sendRawDataEx(int64_t id, uint8_t *data, uint32_t dataSize, const char *filename); + bool sendRawDataEx(int64_t id, const File &fhandle, uint32_t dataSize, const char *filename); + bool sendRawData(int64_t id, uint8_t *data, uint32_t dataSize, const char *filename); + bool sendRawData(int64_t id, const File &fhandle, uint32_t dataSize, const char *filename); + + bool sendBinaryDataEx(int64_t id, CTBotDataType dataType, uint8_t *data, uint32_t dataSize, const char *filename); + bool sendBinaryDataEx(int64_t id, CTBotDataType dataType, const File &fhandle, uint32_t dataSize, const char *filename); + bool sendBinaryData(int64_t id, CTBotDataType dataType, uint8_t *data, uint32_t dataSize, const char *filename); + bool sendBinaryData(int64_t id, CTBotDataType dataType, const File &fhandle, uint32_t dataSize, const char *filename); + +private: + CTBotSecureConnection m_connection; + CTBotParseModeType m_parseMode; + char *m_token; + int32_t m_lastUpdate; + uint32_t m_lastUpdateTimeStamp; + bool m_keepAlive; + bool m_silentNotification; + + // send commands to the telegram server. For info about commands, check the telegram api https://core.telegram.org/bots/api // params - // user: the data structure that will contains the data retreived + // command : the command to send, i.e. getMe + // jsonData : a JSON that contains the parameters(as the destination user ID) // returns - // true if no error occurred - bool getMe(TBUser &user); + // true if no errors occurred +#if ARDUINOJSON_VERSION_MAJOR == 5 + bool sendCommand(const char *command, const JsonObject &jsonData); +#elif ARDUINOJSON_VERSION_MAJOR == 6 + bool sendCommand(const char *command, const DynamicJsonDocument &jsonData); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + bool sendCommand(const char *command, const JsonDocument &jsonData); +#endif + + bool sendBinaryDataEx(int64_t id, CTBotDataType dataType, uint8_t *data, const File &fhandle, uint32_t dataSize, const char *filename); + bool sendBinaryData(int64_t id, CTBotDataType dataType, uint8_t *data, const File &fhandle, uint32_t dataSize, const char *filename); + + // -----------------------STUBS - for backward compatibility -------------------------------------------------------------------- +public: + CTBotMessageType getNewMessage(TBMessage &message, bool blocking); + + int32_t sendMessage(int64_t id, const String &message, const String &keyboard = ""); + int32_t sendMessage(int64_t id, const String &message, CTBotInlineKeyboard &keyboard); + int32_t sendMessage(int64_t id, const String &message, CTBotReplyKeyboard &keyboard); + + bool setTelegramToken(const String &token); // STUB + + bool setIP(const String &ip, const String &gateway, const String &subnetMask, const String &dns1 = "", const String &dns2 = "") const; + + bool wifiConnect(const String &ssid, const String &password = ""); + + void enableUTF8Encoding(bool value); + + bool endQuery(const String &queryID, const String &message = "", bool alertMode = false); + + bool removeReplyKeyboard(int64_t id, const String &message, bool selective = false); }; #endif \ No newline at end of file diff --git a/src/CTBotDataStructures.h b/src/CTBotDataStructures.h index 8fa437f..1469b95 100644 --- a/src/CTBotDataStructures.h +++ b/src/CTBotDataStructures.h @@ -4,12 +4,33 @@ #include +enum CTBotGetMessageMode { + CTBotGetMessageNoOption = 0, + CTBotGetMessageBlocking = 1 << 0, + CTBotGetMessageDestructive = 1 << 1, + CTBotGetMessageBlockingDestructive = CTBotGetMessageBlocking | CTBotGetMessageDestructive +}; +enum CTBotParseModeType { + CTBotParseModeDisabled = 0, + CTBotParseModeMarkdown = 1, + CTBotParseModeHTML = 2, + CTBotParseModeMarkdownV2 = 3 +}; + +enum CTBotDataType { + CTBotDataTypeJPEG = 0, + CTBotDataTypeText = 1, + CTBotDataTypeRAW = 2 +}; + enum CTBotMessageType { - CTBotMessageNoData = 0, - CTBotMessageText = 1, - CTBotMessageQuery = 2, - CTBotMessageLocation = 3, - CTBotMessageContact = 4 + CTBotMessageNoData = 0, + CTBotMessageText = 1, + CTBotMessageQuery = 2, + CTBotMessageLocation = 3, + CTBotMessageContact = 4, + CTBotMessageACK = 5, // for all methods like send/edit/deleteMessage, endQuery, etc) + CTBotMessageOK = 6 // for getUpdates with no new message }; struct TBUser { @@ -32,19 +53,19 @@ struct TBLocation{ }; struct TBContact { + int64_t id; String phoneNumber; String firstName; String lastName; - int64_t id; String vCard; }; struct TBMessage { int32_t messageID; + int32_t date; TBUser sender; TBGroup group; - int32_t date; String text; String chatInstance; String callbackQueryData; diff --git a/src/CTBotDefines.h b/src/CTBotDefines.h index feb59a8..25f67f5 100644 --- a/src/CTBotDefines.h +++ b/src/CTBotDefines.h @@ -2,6 +2,10 @@ #ifndef CTBOTDEFINES #define CTBOTDEFINES +#define CTBOT_VERSION_MAJOR 3 +#define CTBOT_VERSION_MINOR 2 +#define CTBOT_VERSION_PATCH 0 + #define CTBOT_DEBUG_DISABLED 0 // no debug message on the serial console #define CTBOT_DEBUG_WIFI 1 << 0 // WiFi debug messages #define CTBOT_DEBUG_JSON 1 << 1 // JSON debug messages @@ -11,15 +15,13 @@ #define CTBOT_DEBUG_ALL CTBOT_DEBUG_WIFI | CTBOT_DEBUG_JSON | CTBOT_DEBUG_MEMORY | CTBOT_DEBUG_CONNECTION // enable debugmode -> print debug data on the Serial; Zero -> debug disabled -//#define CTBOT_DEBUG_MODE CTBOT_DEBUG_ALL -#define CTBOT_DEBUG_MODE CTBOT_DEBUG_DISABLED +// #define CTBOT_DEBUG_MODE CTBOT_DEBUG_ALL +#define CTBOT_DEBUG_MODE CTBOT_DEBUG_DISABLED -#define CTBOT_STATION_MODE 1 // Station mode -> Set the mode to WIFI_STA (no access point) - // Zero -> WIFI_AP_STA -#define CTBOT_USE_FINGERPRINT 1 // use Telegram fingerprint server validation +#define CTBOT_USE_FINGERPRINT 0 // use Telegram fingerprint/SSL certificate server validation // MUST be enabled for ESP8266 Core library > 2.4.2 (no more mandatory) // Zero -> disabled -#define CTBOT_CHECK_JSON 1 // Check every JSON received from Telegram Server. Speedup the bot. +#define CTBOT_CHECK_JSON 0 // Check every JSON received from Telegram Server. Speedup the bot. // Zero -> Set it to zero if the bot doesn't receive messages anymore // slow down the bot #define CTBOT_GET_UPDATE_TIMEOUT 3500 // minimum time between two updates (getNewMessage) in milliseconds @@ -27,6 +29,21 @@ // value for disabling the status pin. It is utilized for led notification on the board #define CTBOT_DISABLE_STATUS_PIN -1 +// timeout used when try to connect to the telegram server. +// set to 0 to disabling it +#define CTBOT_CONNECTION_TIMEOUT 2000 // ms + +// size of the packet of binary data sent to the telegram server +//#define CTBOT_PACKET_SIZE 8192 // bytes (8k) +#define CTBOT_PACKET_SIZE 4096 // bytes + +// for non "Ex" functions (the ones that inside calls an "Ex" member function and the parseResponse member function), +// it define how many times it need to call "parseResponse" and wait CTBOT_DELAY_PARSERESPONSE ms waiting a Telegram response +// set it to 0 to execute once +// set it to 256 (or more) to wait until a Telegram response is sent (LOCKING!!!!) +#define CTBOT_MAX_PARSERESPONSE 100 +#define CTBOT_DELAY_PARSERESPONSE 10 // ms + // Library specific defines: ArduinoJson5 ------------------------------------------------------------------------ #define CTBOT_JSON5_BUFFER_SIZE 0 // json parser buffer size (only for ArduinoJson 5) // Zero -> dynamic allocation @@ -37,15 +54,14 @@ #define CTBOT_ESP8266_TCP_BUFFER_SIZE 512 // tx/rx wifiClientSecure buffer size for Telegram server connections // only for ESP8266 -// timeout used when try to connect to the telegram server -#define CTBOT_CONNECTION_TIMEOUT 2000 // ms - // strings on FLASH macro #if defined(ARDUINO_ARCH_ESP8266) // ESP8266 -#define FSTR(x) F(x) +#define FSTR(x) F(x) +#define CFSTR(x) (char*)FSTR(x) #elif defined(ARDUINO_ARCH_ESP32) // ESP32 // ESP32 does not support strings on FLASH -#define FSTR(x) (x) +#define FSTR(x) (x) +#define CFSTR(x) (x) #endif diff --git a/src/CTBotInlineKeyboard.cpp b/src/CTBotInlineKeyboard.cpp index 15fb77e..46dffa6 100644 --- a/src/CTBotInlineKeyboard.cpp +++ b/src/CTBotInlineKeyboard.cpp @@ -1,97 +1,144 @@ +// This is an independent project of an individual developer. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: https://pvs-studio.com + #include "CTBotInlineKeyboard.h" #include "Utilities.h" void CTBotInlineKeyboard::initialize(void) { #if ARDUINOJSON_VERSION_MAJOR == 5 - JsonObject& root = m_jsonBuffer.createObject(); - JsonArray& rows = root.createNestedArray((String)"inline_keyboard"); - JsonArray& buttons = rows.createNestedArray(); + JsonObject &root = m_jsonBuffer.createObject(); + JsonArray &rows = root.createNestedArray((String) "inline_keyboard"); + JsonArray &buttons = rows.createNestedArray(); m_root = &root; m_rows = &rows; m_buttons = &buttons; #elif ARDUINOJSON_VERSION_MAJOR == 6 - m_rows = m_root->createNestedArray("inline_keyboard"); - m_buttons = m_rows.createNestedArray(); + if (m_root != NULL) + { + m_rows = m_root->createNestedArray("inline_keyboard"); + m_buttons = m_rows.createNestedArray(); + } +#elif ARDUINOJSON_VERSION_MAJOR == 7 + if (m_root != NULL) + { + m_rows = m_root["inline_keyboard"].to(); + m_buttons = m_rows.add(); + } #endif m_isRowEmpty = true; + m_pkeyboard = NULL; } CTBotInlineKeyboard::CTBotInlineKeyboard() { #if ARDUINOJSON_VERSION_MAJOR == 6 m_root = new DynamicJsonDocument(CTBOT_JSON6_BUFFER_SIZE); - if (!m_root) - serialLog("CTBotInlineKeyboard: Unable to allocate JsonDocument memory.\n", CTBOT_DEBUG_MEMORY); + if (NULL == m_root) + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("CTBotInlineKeyboard: Unable to allocate JsonDocument memory.\n")); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + m_root.clear();// = new JsonDocument(); + if (NULL == m_root) + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("CTBotInlineKeyboard: Unable to allocate JsonDocument memory.\n")); #endif initialize(); } CTBotInlineKeyboard::~CTBotInlineKeyboard() { -#if ARDUINOJSON_VERSION_MAJOR == 6 - delete m_root; +#if ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + if (m_root != NULL) + free(&m_root); #endif + free(m_pkeyboard); } void CTBotInlineKeyboard::flushData(void) { #if ARDUINOJSON_VERSION_MAJOR == 5 m_jsonBuffer.clear(); -#elif ARDUINOJSON_VERSION_MAJOR == 6 - m_root->clear(); +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + if (m_root != NULL) + m_root.clear(); #endif + free(m_pkeyboard); + initialize(); } bool CTBotInlineKeyboard::addRow(void) { if (m_isRowEmpty) - return(false); + return (false); #if ARDUINOJSON_VERSION_MAJOR == 5 - JsonArray& buttons = m_rows->createNestedArray(); + JsonArray &buttons = m_rows->createNestedArray(); m_buttons = &buttons; #elif ARDUINOJSON_VERSION_MAJOR == 6 - m_buttons = m_rows.createNestedArray(); + if (m_root != NULL) + m_buttons = m_rows.createNestedArray(); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + if (m_root != NULL) + m_buttons = m_rows.add(); #endif m_isRowEmpty = true; return true; } -bool CTBotInlineKeyboard::addButton(const String& text, const String& command, CTBotInlineKeyboardButtonType buttonType) +bool CTBotInlineKeyboard::addButton(const String &text, const String &command, CTBotInlineKeyboardButtonType buttonType) +{ + return addButton(text.c_str(), command.c_str(), buttonType); +} +bool CTBotInlineKeyboard::addButton(const char *text, const char *command, CTBotInlineKeyboardButtonType buttonType) { if ((buttonType != CTBotKeyboardButtonURL) && (buttonType != CTBotKeyboardButtonQuery)) return false; #if ARDUINOJSON_VERSION_MAJOR == 5 - JsonObject& button = m_buttons->createNestedObject(); + JsonObject &button = m_buttons->createNestedObject(); #elif ARDUINOJSON_VERSION_MAJOR == 6 JsonObject button = m_buttons.createNestedObject(); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonObject button = m_buttons.add(); #endif - button["text"] = URLEncodeMessage(text); + button[FSTR("text")] = text; if (CTBotKeyboardButtonURL == buttonType) - button["url"] = command; + button[FSTR("url")] = command; else if (CTBotKeyboardButtonQuery == buttonType) - button["callback_data"] = command; + button[FSTR("callback_data")] = command; if (m_isRowEmpty) m_isRowEmpty = false; return true; } -String CTBotInlineKeyboard::getJSON(void) +// String CTBotInlineKeyboard::getJSON(void) +const char *CTBotInlineKeyboard::getJSON(void) { - String serialized; + uint16_t keyboardSize; #if ARDUINOJSON_VERSION_MAJOR == 5 - m_root->printTo(serialized); -#elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJson(*m_root, serialized); + keyboardSize = m_root->measureLength() + 1; +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + keyboardSize = measureJson(m_root) + 1; #endif - return serialized; -} + free(m_pkeyboard); + + m_pkeyboard = (char *)malloc(keyboardSize); + if (NULL == m_pkeyboard) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->getJSON: unable to allocate memory\n")); + return ""; + } +#if ARDUINOJSON_VERSION_MAJOR == 5 + m_root->printTo(m_pkeyboard, keyboardSize); +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + serializeJson(m_root, m_pkeyboard, keyboardSize); +#endif + m_pkeyboard[keyboardSize - 1] = 0x00; + return m_pkeyboard; +} diff --git a/src/CTBotInlineKeyboard.h b/src/CTBotInlineKeyboard.h index 4afa0ea..fa720ba 100644 --- a/src/CTBotInlineKeyboard.h +++ b/src/CTBotInlineKeyboard.h @@ -3,9 +3,9 @@ #define CTBOT_INLINE_KEYBOARD // for using int_64 data -#define ARDUINOJSON_USE_LONG_LONG 1 +#define ARDUINOJSON_USE_LONG_LONG 1 // for decoding UTF8/UNICODE -#define ARDUINOJSON_DECODE_UNICODE 1 +#define ARDUINOJSON_DECODE_UNICODE 1 #if defined(ARDUINO_ARCH_ESP8266) // ESP8266 // for strings stored in FLASH - only for ESP8266 @@ -15,9 +15,10 @@ #include #include -enum CTBotInlineKeyboardButtonType { - CTBotKeyboardButtonURL = 1, - CTBotKeyboardButtonQuery = 2 +enum CTBotInlineKeyboardButtonType +{ + CTBotKeyboardButtonURL = 1, + CTBotKeyboardButtonQuery = 2 }; class CTBotInlineKeyboard @@ -25,16 +26,20 @@ class CTBotInlineKeyboard private: #if ARDUINOJSON_VERSION_MAJOR == 5 DynamicJsonBuffer m_jsonBuffer; - JsonObject* m_root; - JsonArray* m_rows; - JsonArray* m_buttons; + JsonObject *m_root; + JsonArray *m_rows; + JsonArray *m_buttons; #elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument* m_root; + DynamicJsonDocument *m_root; + JsonArray m_rows; + JsonArray m_buttons; +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument m_root; JsonArray m_rows; JsonArray m_buttons; #endif bool m_isRowEmpty; - + char *m_pkeyboard; void initialize(void); public: @@ -56,15 +61,15 @@ class CTBotInlineKeyboard // callback query data (if buttonType is CTBotKeyboardButtonQuery) // return: // true if no error occurred - bool addButton(const String& text, const String& command, CTBotInlineKeyboardButtonType buttonType); + bool addButton(const String &text, const String &command, CTBotInlineKeyboardButtonType buttonType); + bool addButton(const char *text, const char *command, CTBotInlineKeyboardButtonType buttonType); - // generate a string that contains the inline keyboard formatted in a JSON structure. + // generate a string that contains the inline keyboard formatted in a JSON structure. // Useful for CTBot::sendMessage() // returns: - // the JSON of the inline keyboard - String getJSON(void); + // the JSON of the inline keyboard + // String getJSON(void); + const char *getJSON(void); }; - - #endif diff --git a/src/CTBotReplyKeyboard.cpp b/src/CTBotReplyKeyboard.cpp index 28928d2..9b8c00a 100644 --- a/src/CTBotReplyKeyboard.cpp +++ b/src/CTBotReplyKeyboard.cpp @@ -1,48 +1,67 @@ +// This is an independent project of an individual developer. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: https://pvs-studio.com + #include "CTBotReplyKeyboard.h" #include "Utilities.h" void CTBotReplyKeyboard::initialize(void) { #if ARDUINOJSON_VERSION_MAJOR == 5 - JsonObject& root = m_jsonBuffer.createObject(); - JsonArray& rows = root.createNestedArray((String)"keyboard"); - JsonArray& buttons = rows.createNestedArray(); + JsonObject &root = m_jsonBuffer.createObject(); + JsonArray &rows = root.createNestedArray((String) "keyboard"); + JsonArray &buttons = rows.createNestedArray(); m_root = &root; m_rows = &rows; m_buttons = &buttons; #elif ARDUINOJSON_VERSION_MAJOR == 6 - m_rows = m_root->createNestedArray("keyboard"); - m_buttons = m_rows.createNestedArray(); + if (m_root != NULL) + { + m_rows = m_root->createNestedArray("keyboard"); + m_buttons = m_rows.createNestedArray(); + } +#elif ARDUINOJSON_VERSION_MAJOR == 7 + if (m_root != NULL) + { + m_rows = m_root["keyboard"].to(); + m_buttons = m_rows.add(); + } #endif - m_isRowEmpty = true; + m_pkeyboard = NULL; } CTBotReplyKeyboard::CTBotReplyKeyboard() { #if ARDUINOJSON_VERSION_MAJOR == 6 m_root = new DynamicJsonDocument(CTBOT_JSON6_BUFFER_SIZE); - if (!m_root) - serialLog("CTBotInlineKeyboard: Unable to allocate JsonDocument memory.\n", CTBOT_DEBUG_MEMORY); + if (NULL == m_root) + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("CTBotInlineKeyboard: Unable to allocate JsonDocument memory.\n")); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + m_root.clear(); // = new JsonDocument; + if (NULL == m_root) + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("CTBotInlineKeyboard: Unable to allocate JsonDocument memory.\n")); #endif - initialize(); } CTBotReplyKeyboard::~CTBotReplyKeyboard() { #if ARDUINOJSON_VERSION_MAJOR == 6 - delete m_root; + if (m_root != NULL) + free(m_root); #endif + free(m_pkeyboard); } void CTBotReplyKeyboard::flushData(void) { #if ARDUINOJSON_VERSION_MAJOR == 5 m_jsonBuffer.clear(); -#elif ARDUINOJSON_VERSION_MAJOR == 6 - m_root->clear(); +#elif ARDUINOJSON_VERSION_MAJOR == 6 || ARDUINOJSON_VERSION_MAJOR == 7 + if (m_root != NULL) + m_root.clear(); #endif + free(m_pkeyboard); initialize(); } @@ -53,17 +72,25 @@ bool CTBotReplyKeyboard::addRow(void) return false; #if ARDUINOJSON_VERSION_MAJOR == 5 - JsonArray& buttons = m_rows->createNestedArray(); + JsonArray &buttons = m_rows->createNestedArray(); m_buttons = &buttons; #elif ARDUINOJSON_VERSION_MAJOR == 6 - m_buttons = m_rows.createNestedArray(); + if (m_root != NULL) + m_buttons = m_rows.createNestedArray(); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + if (m_root != NULL) + m_buttons = m_rows.add(); #endif m_isRowEmpty = true; return true; } -bool CTBotReplyKeyboard::addButton(const String& text, CTBotReplyKeyboardButtonType buttonType) +bool CTBotReplyKeyboard::addButton(const String &text, CTBotReplyKeyboardButtonType buttonType) +{ + return addButton(text.c_str(), buttonType); +} +bool CTBotReplyKeyboard::addButton(const char *text, CTBotReplyKeyboardButtonType buttonType) { if ((buttonType != CTBotKeyboardButtonSimple) && (buttonType != CTBotKeyboardButtonContact) && @@ -71,13 +98,14 @@ bool CTBotReplyKeyboard::addButton(const String& text, CTBotReplyKeyboardButtonT return false; #if ARDUINOJSON_VERSION_MAJOR == 5 - JsonObject& button = m_buttons->createNestedObject(); -#endif -#if ARDUINOJSON_VERSION_MAJOR == 6 + JsonObject &button = m_buttons->createNestedObject(); +#elif ARDUINOJSON_VERSION_MAJOR == 6 JsonObject button = m_buttons.createNestedObject(); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonObject button = m_buttons.add(); #endif - button["text"] = URLEncodeMessage(text); + button["text"] = text; if (CTBotKeyboardButtonContact == buttonType) button["request_contact"] = true; @@ -89,28 +117,49 @@ bool CTBotReplyKeyboard::addButton(const String& text, CTBotReplyKeyboardButtonT return true; } -void CTBotReplyKeyboard::enableResize(void) { - (*m_root)["resize_keyboard"] = true; +void CTBotReplyKeyboard::enableResize(void) +{ + m_root["resize_keyboard"] = true; } -void CTBotReplyKeyboard::enableOneTime(void) { - (*m_root)["one_time_keyboard"] = true; +void CTBotReplyKeyboard::enableOneTime(void) +{ + m_root["one_time_keyboard"] = true; } -void CTBotReplyKeyboard::enableSelective(void) { - (*m_root)["selective"] = true; +void CTBotReplyKeyboard::enableSelective(void) +{ + m_root["selective"] = true; } -String CTBotReplyKeyboard::getJSON(void) +const char *CTBotReplyKeyboard::getJSON(void) { - String serialized; + uint16_t keyboardSize; #if ARDUINOJSON_VERSION_MAJOR == 5 - m_root->printTo(serialized); + keyboardSize = m_root->measureLength() + 1; #elif ARDUINOJSON_VERSION_MAJOR == 6 - serializeJson(*m_root, serialized); + keyboardSize = measureJson(*m_root) + 1; +#elif ARDUINOJSON_VERSION_MAJOR == 7 + keyboardSize = measureJson(m_root) + 1; #endif - return serialized; -} + free(m_pkeyboard); + + m_pkeyboard = (char *)malloc(keyboardSize); + if (NULL == m_pkeyboard) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->getJSON: unable to allocate memory\n")); + return ""; + } +#if ARDUINOJSON_VERSION_MAJOR == 5 + m_root->printTo(m_pkeyboard, keyboardSize); +#elif ARDUINOJSON_VERSION_MAJOR == 6 + serializeJson(*m_root, m_pkeyboard, keyboardSize); +#elif ARDUINOJSON_VERSION_MAJOR == 7 + serializeJson(m_root, m_pkeyboard, keyboardSize); +#endif + m_pkeyboard[keyboardSize - 1] = 0x00; + return m_pkeyboard; +} diff --git a/src/CTBotReplyKeyboard.h b/src/CTBotReplyKeyboard.h index 8b12fe1..7b76a6d 100644 --- a/src/CTBotReplyKeyboard.h +++ b/src/CTBotReplyKeyboard.h @@ -3,9 +3,9 @@ #define CTBOT_REPLY_KEYBOARD // for using int_64 data -#define ARDUINOJSON_USE_LONG_LONG 1 +#define ARDUINOJSON_USE_LONG_LONG 1 // for decoding UTF8/UNICODE -#define ARDUINOJSON_DECODE_UNICODE 1 +#define ARDUINOJSON_DECODE_UNICODE 1 #if defined(ARDUINO_ARCH_ESP8266) // ESP8266 // for strings stored in FLASH - only for ESP8266 @@ -15,9 +15,10 @@ #include #include -enum CTBotReplyKeyboardButtonType { - CTBotKeyboardButtonSimple = 1, - CTBotKeyboardButtonContact = 2, +enum CTBotReplyKeyboardButtonType +{ + CTBotKeyboardButtonSimple = 1, + CTBotKeyboardButtonContact = 2, CTBotKeyboardButtonLocation = 3 }; @@ -26,16 +27,20 @@ class CTBotReplyKeyboard private: #if ARDUINOJSON_VERSION_MAJOR == 5 DynamicJsonBuffer m_jsonBuffer; - JsonObject* m_root; - JsonArray* m_rows; - JsonArray* m_buttons; + JsonObject *m_root; + JsonArray *m_rows; + JsonArray *m_buttons; #elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument* m_root; + DynamicJsonDocument *m_root; + JsonArray m_rows; + JsonArray m_buttons; +#elif ARDUINOJSON_VERSION_MAJOR == 7 + JsonDocument m_root; JsonArray m_rows; JsonArray m_buttons; #endif - bool m_isRowEmpty; + char *m_pkeyboard; void initialize(void); public: @@ -56,24 +61,25 @@ class CTBotReplyKeyboard // buttonType: the type of the button (simple text, contact request, location request) // return: // true if no error occurred - bool addButton(const String& text, CTBotReplyKeyboardButtonType buttonType = CTBotKeyboardButtonSimple); + bool addButton(const String &text, CTBotReplyKeyboardButtonType buttonType = CTBotKeyboardButtonSimple); + bool addButton(const char *text, CTBotReplyKeyboardButtonType buttonType = CTBotKeyboardButtonSimple); // enable reply keyboard autoresizing (default: the same size of the standard keyboard) void enableResize(void); - + // hide the reply keyboard as soon as it's been used void enableOneTime(void); - // Use this parameter if you want to show the keyboard for specific users only. - // Targets: 1) users that are @mentioned in the text of the Message object; + // Use this parameter if you want to show the keyboard for specific users only. + // Targets: 1) users that are @mentioned in the text of the Message object; // 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message void enableSelective(void); - // generate a string that contains the inline keyboard formatted in a JSON structure. + // generate a string that contains the inline keyboard formatted in a JSON structure. // Useful for CTBot::sendMessage() // returns: - // the JSON of the inline keyboard - String getJSON(void); + // the JSON of the inline keyboard + const char *getJSON(void); }; #endif diff --git a/src/CTBotSecureConnection.cpp b/src/CTBotSecureConnection.cpp index d191c13..9ba79fc 100644 --- a/src/CTBotSecureConnection.cpp +++ b/src/CTBotSecureConnection.cpp @@ -1,182 +1,463 @@ -#include +// This is an independent project of an individual developer. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: https://pvs-studio.com + +#include +#if defined(ARDUINO_ARCH_ESP8266) +#include +#elif defined(ARDUINO_ARCH_ESP32) +#endif #include "CTBotSecureConnection.h" #include "Utilities.h" -#define TELEGRAM_URL FSTR("api.telegram.org") -#define TELEGRAM_IP FSTR("149.154.167.220") // "149.154.167.198" <-- Old IP +#define TELEGRAM_URL FSTR("api.telegram.org") +#define TELEGRAM_IP FSTR("149.154.167.220") // "149.154.167.198" <-- Old IP #define TELEGRAM_PORT 443 -CTBotSecureConnection::CTBotSecureConnection() { - m_useDNS = true; -#if defined(ARDUINO_ARCH_ESP8266) +#define HTTP_RESPONSE_OK CFSTR("HTTP/1.1 200 OK") +#define HTTP_CONTENT_LENGTH CFSTR("Content-Length: ") + +// -------------------------------------------------------------------------------------------------- + +CTBotSecureConnection::CTBotSecureConnection() +{ +#if defined(ARDUINO_ARCH_ESP8266) +#if CTBOT_USE_FINGERPRINT == 0 // ESP8266 no HTTPS verification + m_telegramServer.setInsecure(); + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->CTBotSecureConnection: ESP8266 no https verification\n")); +#else // ESP8266 with HTTPS verification + // m_telegramServer.setFingerprint(m_fingerprint); m_cert.append(m_CAcert); + m_telegramServer.setTrustAnchors(&m_cert); + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->CTBotSecureConnection: ESP8266 with https verification\n")); #endif -} - -bool CTBotSecureConnection::useDNS(bool value) { -#if (CTBOT_USE_FINGERPRINT == 1) - serialLog(FSTR("useDNS must be true for Telegram SSL certificate check.\n"), CTBOT_DEBUG_CONNECTION); - return false; + m_telegramServer.setBufferSizes(CTBOT_ESP8266_TCP_BUFFER_SIZE, CTBOT_ESP8266_TCP_BUFFER_SIZE); +#elif defined(ARDUINO_ARCH_ESP32) // ESP32 +#if CTBOT_USE_FINGERPRINT == 0 // ESP32 no HTTPS verification + m_telegramServer.setInsecure(); + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->CTBotSecureConnection: ESP32 no https verification\n")); #else - m_useDNS = value; - - // seems that it doesn't work with ESP32 - comment out for now - // the check is present (and work) on send() member function - //if (m_useDNS) { - // WiFiClientSecure telegramServer; - // if (!telegramServer.connect(TELEGRAM_URL, TELEGRAM_PORT)) { - // telegramServer.stop(); - // m_useDNS = false; - // return false; - // } - //} - return true; + m_telegramServer.setCACert(m_CAcert); + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->CTBotSecureConnection: ESP32 with https verification\n")); +#endif #endif -} -void CTBotSecureConnection::setFingerprint(const uint8_t* newFingerprint) -{ - for (int i = 0; i < 20; i++) - m_fingerprint[i] = newFingerprint[i]; + if (CTBOT_CONNECTION_TIMEOUT > 0) + m_telegramServer.setTimeout(CTBOT_CONNECTION_TIMEOUT); + + m_useDNS = true; + m_receivedData = NULL; } -void CTBotSecureConnection::setStatusPin(int8_t pin) { - m_statusPin.setPin(pin); +CTBotSecureConnection::~CTBotSecureConnection() +{ + disconnect(); } -String CTBotSecureConnection::send(const String& message) { -#if defined(ARDUINO_ARCH_ESP8266) && CTBOT_USE_FINGERPRINT == 0 // ESP8266 no HTTPS verification - WiFiClientSecure telegramServer; - telegramServer.setInsecure(); - serialLog(FSTR("ESP8266 no https verification"), CTBOT_DEBUG_CONNECTION); -#elif defined(ARDUINO_ARCH_ESP8266) && CTBOT_USE_FINGERPRINT == 1 // ESP8266 with HTTPS verification - BearSSL::WiFiClientSecure telegramServer; -// telegramServer.setFingerprint(m_fingerprint); - telegramServer.setTrustAnchors(&m_cert); - serialLog(FSTR("ESP8266 with https verification"), CTBOT_DEBUG_CONNECTION); -#elif defined(ARDUINO_ARCH_ESP32) // ESP32 - WiFiClientSecure telegramServer; -#if CTBOT_USE_FINGERPRINT == 0 - telegramServer.setInsecure(); - serialLog(FSTR("ESP32 no https verification"), CTBOT_DEBUG_CONNECTION); -#else - telegramServer.setCACert(m_CAcert); - serialLog(FSTR("ESP32 with https verification"), CTBOT_DEBUG_CONNECTION); -#endif -#endif +bool CTBotSecureConnection::connect() +{ + if (!WiFi.isConnected()) + return false; -#if defined(ARDUINO_ARCH_ESP8266) // only for ESP8266 reduce drastically the heap usage (~15K more) - telegramServer.setBufferSizes(CTBOT_ESP8266_TCP_BUFFER_SIZE, CTBOT_ESP8266_TCP_BUFFER_SIZE); -#endif + if (isConnected()) + return true; - telegramServer.setTimeout(CTBOT_CONNECTION_TIMEOUT); + freeMemory(); // check for using symbolic URLs - if (m_useDNS) { + if (m_useDNS) + { // try to connect with URL - if (!telegramServer.connect(TELEGRAM_URL, TELEGRAM_PORT)) { + if (!m_telegramServer.connect(TELEGRAM_URL, TELEGRAM_PORT)) + { // no way, try to connect with fixed IP IPAddress telegramServerIP; telegramServerIP.fromString(TELEGRAM_IP); - if (!telegramServer.connect(telegramServerIP, TELEGRAM_PORT)) { - serialLog(FSTR("\nUnable to connect to Telegram server\n"), CTBOT_DEBUG_CONNECTION); - return(""); + if (!m_telegramServer.connect(telegramServerIP, TELEGRAM_PORT)) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->connect: Unable to connect to Telegram server\n")); + return false; } - else { - serialLog(FSTR("\nConnected using fixed IP\n"), CTBOT_DEBUG_CONNECTION); + else + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->connect: Connected using fixed IP\n")); useDNS(false); + return true; } } - else { - serialLog(FSTR("\nConnected using DNS\n"), CTBOT_DEBUG_CONNECTION); + else + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->connect: Connected using DNS\n")); + return true; } } - else { + else + { // try to connect with fixed IP IPAddress telegramServerIP; // (149, 154, 167, 220); telegramServerIP.fromString(TELEGRAM_IP); - if (!telegramServer.connect(telegramServerIP, TELEGRAM_PORT)) { - serialLog(FSTR("\nUnable to connect to Telegram server\n"), CTBOT_DEBUG_CONNECTION); - return(""); + if (!m_telegramServer.connect(telegramServerIP, TELEGRAM_PORT)) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->connect: Unable to connect to Telegram server\n")); + return false; } else - serialLog(FSTR("\nConnected using fixed IP\n"), CTBOT_DEBUG_CONNECTION); + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->connect: Connected using fixed IP\n")); + return true; + } } + return false; +} - m_statusPin.toggle(); +bool CTBotSecureConnection::isConnected() +{ + if (!WiFi.isConnected()) + return false; + return m_telegramServer.connected(); +} - // must filter command + parameters from escape sequences and spaces - // String URL = "GET /bot" + m_token + (String)"/" + toURL(command + parameters); -// String URL = (String)FSTR("GET /bot") + m_token + (String)"/" + command + parameters; +void CTBotSecureConnection::disconnect() +{ + freeMemory(); - unsigned long elapsed = millis(); + if (!isConnected()) + return; - // send the HTTP request - telegramServer.println(message); + flush(); + m_telegramServer.stop(); +} - m_statusPin.toggle(); +bool CTBotSecureConnection::POST(const char *header, const uint8_t *payload, uint32_t payloadSize, const char *payloadHeader, const char *payloadFooter) +{ + return POST(header, payload, File(), payloadSize, payloadHeader, payloadFooter); +} +bool CTBotSecureConnection::POST(const char *header, File fhandle, uint32_t payloadSize, const char *payloadHeader, const char *payloadFooter) +{ + return POST(header, NULL, fhandle, payloadSize, payloadHeader, payloadFooter); +} +bool CTBotSecureConnection::POST(const char *header, const uint8_t *payload, File fhandle, uint32_t payloadSize, const char *payloadHeader, const char *payloadFooter) +{ + uint16_t dataSent; + char *buffer = NULL; - serialLog(FSTR("--->sendCommand : Free heap memory: "), CTBOT_DEBUG_MEMORY); - serialLog(ESP.getFreeHeap(), CTBOT_DEBUG_MEMORY); + freeMemory(); + if (!isConnected()) + { + if (!connect()) + return false; + } -#if CTBOT_CHECK_JSON == 0 - serialLog("\n", CTBOT_DEBUG_MEMORY); - return(telegramServer.readString()); -#else + flush(); - String response; - int curlyCounter; // count the open/closed curly bracket for identify the json - bool skipCounter = false; // for filtering curly bracket inside a text message - int c; - curlyCounter = -1; - response = ""; - - while (telegramServer.connected()) { - while (telegramServer.available()) { - c = telegramServer.read(); - response += (char)c; - if (c == '\\') { - // escape character -> read next and skip - c = telegramServer.read(); - response += (char)c; - continue; - } - if (c == '"') - skipCounter = !skipCounter; - if (!skipCounter) { - if (c == '{') { - if (curlyCounter == -1) - curlyCounter = 1; - else - curlyCounter++; - } - else if (c == '}') - curlyCounter--; - if (curlyCounter == 0) { + if (NULL == header) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->POST: header can't be NULL\n")); + return false; + } + + if (header[0] == '\0') + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->POST: header can't be an empty string\n")); + return false; + } + + if (0 == payloadSize) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->POST: payload can't zero size\n")); + return false; + } + + if ((NULL == payload) && (!fhandle)) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->POST: NULL payload or invalid file handle\n")); + return false; + } + + // allocate memory buffer for file reading + if (fhandle) + { + buffer = (char *)malloc(CTBOT_PACKET_SIZE); + if (NULL == buffer) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->POST: unable to allocate memory buffer for file reading\n")); + return false; + } + } + + dataSent = m_telegramServer.print(header); + if (dataSent != strlen(header)) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->POST: error sending HTTP header (%u/%u)\n"), dataSent, strlen(header)); + disconnect(); + free(buffer); + return false; + } + + if ((payloadHeader != NULL) && (payloadFooter != NULL)) + { + dataSent = m_telegramServer.print(payloadHeader); + if (dataSent != strlen(payloadHeader)) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->POST: error sending payload header (%u/%u)\n"), dataSent, strlen(payloadHeader)); + disconnect(); + free(buffer); + return false; + } + } + + // divide the payload in packets of CTBOT_PACKET_SIZE dimension + while (payloadSize > 0) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->POST: Remaining data to send: %u\n"), payloadSize); + uint16_t packetSize; + if (payloadSize > CTBOT_PACKET_SIZE) + packetSize = CTBOT_PACKET_SIZE; + else + packetSize = payloadSize; + + if (fhandle) + { + fhandle.readBytes(buffer, packetSize); + dataSent = m_telegramServer.write((uint8_t *)buffer, packetSize); + } + else if (payload != NULL) + { + dataSent = m_telegramServer.write(payload, packetSize); + payload += packetSize; + } + + if (dataSent != packetSize) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->POST: error sending HTTP payload (%u/%u)\n"), dataSent, packetSize); + disconnect(); + free(buffer); + return false; + } + payloadSize -= packetSize; + } + + free(buffer); + + if ((payloadHeader != NULL) && (payloadFooter != NULL)) + { + dataSent = m_telegramServer.print(payloadFooter); + if (dataSent != strlen(payloadFooter)) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->POST: error sending payload footer (%u/%u)\n"), dataSent, strlen(payloadFooter)); + disconnect(); + return false; + } + } + + dataSent = m_telegramServer.print("\r\n"); + if (dataSent != 2) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->POST: error sending CR/LF (%u/2)\n"), dataSent); + disconnect(); + return false; + } + return true; +} - // JSON ended, close connection and return JSON +const char *CTBotSecureConnection::receive() +{ + + char buffer[32]; + char singleChar; + int result, size, found, payloadSize; + + freeMemory(); + + if (!isConnected()) + { + if (!connect()) + return NULL; + } + + if (!m_telegramServer.available()) + return NULL; - elapsed = millis() - elapsed; + // check for HTTP response status + size = strlen_P(HTTP_RESPONSE_OK); + result = m_telegramServer.readBytes((uint8_t *)buffer, size); + buffer[result] = 0x00; - serialLog(FSTR(" / "), CTBOT_DEBUG_MEMORY); - serialLog(ESP.getFreeHeap(), CTBOT_DEBUG_MEMORY); - serialLog(FSTR(" - "), CTBOT_DEBUG_MEMORY); - serialLog(elapsed, CTBOT_DEBUG_MEMORY); - serialLog(FSTR(" ms\n"), CTBOT_DEBUG_MEMORY); + result = memcmp_P(buffer, HTTP_RESPONSE_OK, size); + if (result != 0) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->receive: HTTPS response error:\n")); - telegramServer.flush(); - telegramServer.stop(); - return(response); + /* + #if CTBOT_DEBUG_MODE == CTBOT_DEBUG_CONNECTION + // drop the header and print the payload + found = -1; + while ((-1 == found) && m_telegramServer.available()) { + while (m_telegramServer.read() != '\n'); + singleChar = m_telegramServer.read(); + if ('\r' == singleChar) { + singleChar = m_telegramServer.read(); + if ('\n' == singleChar) + found = 0; + } } - } + if (found != 0) { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->receive: end of header not found\n")); + disconnect(); + return NULL; + } + while (m_telegramServer.available()) + Serial.write(m_telegramServer.read()); + Serial.println(); + #endif + disconnect(); + return NULL; + */ + } + else + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->receive: HTTP Response OK: %s\n"), buffer); + + // go to the next line + while (m_telegramServer.read() != '\n') + ; + + // find the Content-Length + found = -1; + size = strlen_P((const char *)HTTP_CONTENT_LENGTH); + payloadSize = 0; + + while ((found != 0) && m_telegramServer.available()) + { + result = m_telegramServer.readBytes((uint8_t *)buffer, size); + buffer[result] = 0x00; + found = memcmp_P(buffer, HTTP_CONTENT_LENGTH, size); + if (found != 0) + { + // not Content-Length field -> drop the entire line + while (m_telegramServer.read() != '\n') + ; + } + else + { + // ok: Content-Length found -> read payload size + int byteRead; + byteRead = m_telegramServer.readBytesUntil(0x0D, buffer, 19); + buffer[byteRead] = 0x00; + payloadSize = atoi(buffer); + while (m_telegramServer.read() != '\n') + ; + + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->receive: Content-Length size: %d\n"), payloadSize); } } - serialLog("\n", CTBOT_DEBUG_MEMORY); + if (found != 0) + { + // Content-length not found; + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->receive: Content-Length not found\n")); + // disconnect(); + // m_telegramServer.flush(); + flush(); + return NULL; + } + + // we have the payload -> drop all other header fields + found = -1; + while ((-1 == found) && m_telegramServer.available()) + { + while (m_telegramServer.read() != '\n') + ; + singleChar = m_telegramServer.read(); + if ('\r' == singleChar) + { + singleChar = m_telegramServer.read(); + if ('\n' == singleChar) + found = 0; + } + } + if (found != 0) + { + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->receive: end of header not found\n")); + // disconnect(); + // m_telegramServer.flush(); + flush(); + return NULL; + } + + m_receivedData = (char *)malloc(payloadSize + 1); + if (NULL == m_receivedData) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->receive: unable to allocate memory\n")); + // disconnect(); + // m_telegramServer.flush(); + flush(); + return NULL; + } - // timeout, no JSON to parse - telegramServer.flush(); - telegramServer.stop(); - return(""); + result = m_telegramServer.readBytes(m_receivedData, payloadSize); + if (result != payloadSize) + { + serialLog(CTBOT_DEBUG_MEMORY, CFSTR("--->receive: unable read the payload. Byte read: %u/%u\n"), result, payloadSize); + // disconnect(); + // m_telegramServer.flush(); + freeMemory(); + flush(); + return NULL; + } + m_receivedData[payloadSize] = 0x00; + + // only for ArduinoJson v. 5 that doesn't support the unicode->UTF8 decoding +#if ARDUINOJSON_VERSION_MAJOR == 5 + toUTF8(m_receivedData); #endif -} \ No newline at end of file + + // drop the CR/LF characters + m_telegramServer.readBytesUntil('\n', buffer, 0); + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->receive: start payload:\n%s\n--->receive: end payload\n"), m_receivedData); + return m_receivedData; +} + +void CTBotSecureConnection::freeMemory() +{ + if (m_receivedData != NULL) + { + free(m_receivedData); + m_receivedData = NULL; + } +} + +void CTBotSecureConnection::flush(void) +{ + if (isConnected()) + while (m_telegramServer.available()) + m_telegramServer.read(); +} + +bool CTBotSecureConnection::useDNS(bool value) +{ +#if (CTBOT_USE_FINGERPRINT == 1) + serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->useDNS: useDNS must be true for Telegram SSL certificate/fingerprint check.\n")); + return false; +#else + m_useDNS = value; + // check if there is an established connection.. + if (isConnected()) + { + // ..yes -> reconnect with the new settings + disconnect(); + return connect(); + } + return true; +#endif +} + +// bool CTBotSecureConnection::setFingerprint(const uint8_t *newFingerprint) +// { +// if (NULL == newFingerprint) +// { +// serialLog(CTBOT_DEBUG_CONNECTION, CFSTR("--->setFingerprint: fingerprint can't be NULL\n")); +// return false; +// } +// for (int i = 0; i < 20; i++) +// m_fingerprint[i] = newFingerprint[i]; +// return true; +// } diff --git a/src/CTBotSecureConnection.h b/src/CTBotSecureConnection.h index dcfd86f..a57ef17 100644 --- a/src/CTBotSecureConnection.h +++ b/src/CTBotSecureConnection.h @@ -2,19 +2,31 @@ #ifndef CTBOTSECURECONNECTION #define CTBOTSECURECONNECTION +// for using int_64 data +#define ARDUINOJSON_USE_LONG_LONG 1 +// for decoding UTF8/UNICODE +#define ARDUINOJSON_DECODE_UNICODE 1 + +#if defined(ARDUINO_ARCH_ESP8266) // ESP8266 +// for strings stored in FLASH - only for ESP8266 +#define ARDUINOJSON_ENABLE_PROGMEM 1 +#endif +#include + #include +#include #include -#include "CTBotStatusPin.h" #include "CTBotDefines.h" class CTBotSecureConnection { public: CTBotSecureConnection(); + ~CTBotSecureConnection(); // use the URL style address "api.telegram.org" or the fixed IP address "149.154.167.198" - // for all communication with the telegram server. When changing to true a test - // connection is made using the URL. If no connection is made useDNS falls back to false. + // for all communication with the telegram server. When changing to true a test + // connection is made using the URL. If no connection is made useDNS falls back to false. // Default value is false // params // value: true -> use URL style address @@ -28,68 +40,104 @@ class CTBotSecureConnection // quering api.telegram.org // params: // newFingerprint: the array of 20 bytes that contains the new fingerprint - void setFingerprint(const uint8_t* newFingerprint); + // bool setFingerprint(const uint8_t *newFingerprint); + + // establish an HTTPS connection to the Telegram server + // returns + // returns true if no errors occurred + bool connect(); - // set the status pin used to connect a LED for visual notification - // CTBOT_DISABLE_STATUS_PIN will disable the notification - // default value is CTBOT_DISABLE_STATUS_PIN (visual notification disabled) - // - ESP8266 onboard LED: 2 - // - ESP32 onboard LED : 4 + // check if there is an established connection to the Telegram server + // returns + // returns true if there is a valid connection to the Telegram server + bool isConnected(); + + // close the connection with the Telegram Server + void disconnect(); + + // execute an HTTP POST on the Telegram server // params - // pin: the pin used for visual notification - void setStatusPin(int8_t pin); + // header : the header of the POST, including the URI. The caller must + // compute/calculate it + // payload : the payload + // fhandle : handle to a file + // payloadSize: the size of the payload (in bytes) + // returns + // true if no errors occurred + bool POST(const char *header, const uint8_t *payload, uint32_t payloadSize, const char *payloadHeader = NULL, const char *payloadFooter = NULL); + bool POST(const char *header, File fhandle, uint32_t payloadSize, const char *payloadHeader = NULL, const char *payloadFooter = NULL); - String send(const String& message); + // receive Telegram server POST response + // returns + // - NULL if no valid data/errors occurred + // - a pointer to the allocated memory that cointains the response (JSON). + // The data is stored internally in the CTBotSecureConnection object and is overwritten when another + // receive call is done. + const char *receive(); -private: - bool m_useDNS; - CTBotStatusPin m_statusPin; + // free memory allocated by receive + void freeMemory(); -#if defined(ARDUINO_ARCH_ESP8266) - X509List m_cert; -#endif + // flush (drop) all data not already read and stored in the receive buffer + void flush(); +private: +#if defined(ARDUINO_ARCH_ESP8266) && CTBOT_USE_FINGERPRINT == 0 // ESP8266 no HTTPS verification + WiFiClientSecure m_telegramServer; +#elif defined(ARDUINO_ARCH_ESP8266) && CTBOT_USE_FINGERPRINT == 1 // ESP8266 with HTTPS verification + // BearSSL::WiFiClientSecure m_telegramServer; + WiFiClientSecure m_telegramServer; +#elif defined(ARDUINO_ARCH_ESP32) // ESP32 + WiFiClientSecure m_telegramServer; +#endif + bool m_useDNS; + char *m_receivedData; +#if CTBOT_USE_FINGERPRINT == 1 + // this should be useless now - CACert even for ESP8266 // get fingerprints from https://www.grc.com/fingerprints.htm -// uint8_t m_fingerprint[20]{ 0x07, 0x36, 0x89, 0x3D, 0x0F, 0xCC, 0x8C, 0xF7, 0xD0, 0x19, 0xB7, 0x83, 0x39, 0xC4, 0xD5, 0x15, 0x70, 0x9A, 0xC6, 0x5D }; // use this preconfigured fingerprrint by default (2022/04/29) - uint8_t m_fingerprint[20]{ 0x8A, 0x10, 0xB5, 0xB9, 0xB1, 0x57, 0xAB, 0xDA, 0x19, 0x74, 0x5B, 0xAB, 0x62, 0x1F, 0x38, 0x03, 0x72, 0xFE, 0x8E, 0x47 }; + // uint8_t m_fingerprint[20]{ 0x07, 0x36, 0x89, 0x3D, 0x0F, 0xCC, 0x8C, 0xF7, 0xD0, 0x19, 0xB7, 0x83, 0x39, 0xC4, 0xD5, 0x15, 0x70, 0x9A, 0xC6, 0x5D }; // use this preconfigured fingerprrint by default (2022/04/29) + // uint8_t m_fingerprint[20]{0x8A, 0x10, 0xB5, 0xB9, 0xB1, 0x57, 0xAB, 0xDA, 0x19, 0x74, 0x5B, 0xAB, 0x62, 0x1F, 0x38, 0x03, 0x72, 0xFE, 0x8E, 0x47}; + const uint8_t m_fingerprint[20]{0x1F, 0x77, 0x5F, 0x20, 0xC5, 0xD3, 0xBD, 0x67, 0xDE, 0xE8, 0x07, 0x9B, 0x59, 0x1D, 0x22, 0xE9, 0xC0, 0xE4, 0x52, 0x4B}; // 07.06.2024 - // SSL Certificate validation - // get the certificate by running + // Get the certificate by running // openssl s_client -showcerts -connect api.telegram.org:443 // and copy the Root Certificate -//#if defined(ARDUINO_ARCH_ESP32) - const char* m_CAcert = \ - "-----BEGIN CERTIFICATE-----\n" \ - "MIIE0DCCA7igAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx\n" \ - "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT\n" \ - "EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp\n" \ - "ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTExMDUwMzA3MDAwMFoXDTMxMDUwMzA3\n" \ - "MDAwMFowgbQxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH\n" \ - "EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjEtMCsGA1UE\n" \ - "CxMkaHR0cDovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMTMwMQYDVQQD\n" \ - "EypHbyBEYWRkeSBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi\n" \ - "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC54MsQ1K92vdSTYuswZLiBCGzD\n" \ - "BNliF44v/z5lz4/OYuY8UhzaFkVLVat4a2ODYpDOD2lsmcgaFItMzEUz6ojcnqOv\n" \ - "K/6AYZ15V8TPLvQ/MDxdR/yaFrzDN5ZBUY4RS1T4KL7QjL7wMDge87Am+GZHY23e\n" \ - "cSZHjzhHU9FGHbTj3ADqRay9vHHZqm8A29vNMDp5T19MR/gd71vCxJ1gO7GyQ5HY\n" \ - "pDNO6rPWJ0+tJYqlxvTV0KaudAVkV4i1RFXULSo6Pvi4vekyCgKUZMQWOlDxSq7n\n" \ - "eTOvDCAHf+jfBDnCaQJsY1L6d8EbyHSHyLmTGFBUNUtpTrw700kuH9zB0lL7AgMB\n" \ - "AAGjggEaMIIBFjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV\n" \ - "HQ4EFgQUQMK9J47MNIMwojPX+2yz8LQsgM4wHwYDVR0jBBgwFoAUOpqFBxBnKLbv\n" \ - "9r0FQW4gwZTaD94wNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v\n" \ - "b2NzcC5nb2RhZGR5LmNvbS8wNQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2NybC5n\n" \ - "b2RhZGR5LmNvbS9nZHJvb3QtZzIuY3JsMEYGA1UdIAQ/MD0wOwYEVR0gADAzMDEG\n" \ - "CCsGAQUFBwIBFiVodHRwczovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkv\n" \ - "MA0GCSqGSIb3DQEBCwUAA4IBAQAIfmyTEMg4uJapkEv/oV9PBO9sPpyIBslQj6Zz\n" \ - "91cxG7685C/b+LrTW+C05+Z5Yg4MotdqY3MxtfWoSKQ7CC2iXZDXtHwlTxFWMMS2\n" \ - "RJ17LJ3lXubvDGGqv+QqG+6EnriDfcFDzkSnE3ANkR/0yBOtg2DZ2HKocyQetawi\n" \ - "DsoXiWJYRBuriSUBAA/NxBti21G00w9RKpv0vHP8ds42pM3Z2Czqrpv1KrKQ0U11\n" \ - "GIo/ikGQI31bS/6kA1ibRrLDYGCD+H1QQc7CoZDDu+8CL9IVVO5EFdkKrqeKM+2x\n" \ - "LXY2JtwE65/3YR8V3Idv7kaWKK2hJn0KCacuBKONvPi8BDAB\n" \ +// #if defined(ARDUINO_ARCH_ESP32) +#if defined(ARDUINO_ARCH_ESP8266) + X509List m_cert; + const char *m_CAcert = + "-----BEGIN CERTIFICATE-----\n" + "MIIE0DCCA7igAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx\n" + "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT\n" + "EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp\n" + "ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTExMDUwMzA3MDAwMFoXDTMxMDUwMzA3\n" + "MDAwMFowgbQxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH\n" + "EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjEtMCsGA1UE\n" + "CxMkaHR0cDovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMTMwMQYDVQQD\n" + "EypHbyBEYWRkeSBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi\n" + "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC54MsQ1K92vdSTYuswZLiBCGzD\n" + "BNliF44v/z5lz4/OYuY8UhzaFkVLVat4a2ODYpDOD2lsmcgaFItMzEUz6ojcnqOv\n" + "K/6AYZ15V8TPLvQ/MDxdR/yaFrzDN5ZBUY4RS1T4KL7QjL7wMDge87Am+GZHY23e\n" + "cSZHjzhHU9FGHbTj3ADqRay9vHHZqm8A29vNMDp5T19MR/gd71vCxJ1gO7GyQ5HY\n" + "pDNO6rPWJ0+tJYqlxvTV0KaudAVkV4i1RFXULSo6Pvi4vekyCgKUZMQWOlDxSq7n\n" + "eTOvDCAHf+jfBDnCaQJsY1L6d8EbyHSHyLmTGFBUNUtpTrw700kuH9zB0lL7AgMB\n" + "AAGjggEaMIIBFjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV\n" + "HQ4EFgQUQMK9J47MNIMwojPX+2yz8LQsgM4wHwYDVR0jBBgwFoAUOpqFBxBnKLbv\n" + "9r0FQW4gwZTaD94wNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v\n" + "b2NzcC5nb2RhZGR5LmNvbS8wNQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2NybC5n\n" + "b2RhZGR5LmNvbS9nZHJvb3QtZzIuY3JsMEYGA1UdIAQ/MD0wOwYEVR0gADAzMDEG\n" + "CCsGAQUFBwIBFiVodHRwczovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkv\n" + "MA0GCSqGSIb3DQEBCwUAA4IBAQAIfmyTEMg4uJapkEv/oV9PBO9sPpyIBslQj6Zz\n" + "91cxG7685C/b+LrTW+C05+Z5Yg4MotdqY3MxtfWoSKQ7CC2iXZDXtHwlTxFWMMS2\n" + "RJ17LJ3lXubvDGGqv+QqG+6EnriDfcFDzkSnE3ANkR/0yBOtg2DZ2HKocyQetawi\n" + "DsoXiWJYRBuriSUBAA/NxBti21G00w9RKpv0vHP8ds42pM3Z2Czqrpv1KrKQ0U11\n" + "GIo/ikGQI31bS/6kA1ibRrLDYGCD+H1QQc7CoZDDu+8CL9IVVO5EFdkKrqeKM+2x\n" + "LXY2JtwE65/3YR8V3Idv7kaWKK2hJn0KCacuBKONvPi8BDAB\n" "-----END CERTIFICATE-----\n"; -//#endif - +#endif +#endif + bool POST(const char *header, const uint8_t *payload, File fhandle, uint32_t payloadSize, const char *payloadHeader, const char *payloadFooter); }; #endif \ No newline at end of file diff --git a/src/CTBotStatusPin.cpp b/src/CTBotStatusPin.cpp deleted file mode 100644 index 0b2babc..0000000 --- a/src/CTBotStatusPin.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "CTBotStatusPin.h" - -CTBotStatusPin::CTBotStatusPin() { - m_pin = CTBOT_DISABLE_STATUS_PIN; - m_pinValue = 0; -} - -CTBotStatusPin::~CTBotStatusPin() { -// if (m_pin != CTBOT_DISABLE_STATUS_PIN) -// pinMode(m_pin, INPUT); -} - -void CTBotStatusPin::setPin(int8_t newPin) -{ - if (m_pin != CTBOT_DISABLE_STATUS_PIN) { - // disable the previous pin - pinMode(m_pin, INPUT); - } - m_pin = newPin; - if (m_pin != CTBOT_DISABLE_STATUS_PIN) { - pinMode(m_pin, OUTPUT); - digitalWrite(m_pin, LOW); - } - m_pinValue = 0; -} - -void CTBotStatusPin::toggle() -{ - if (CTBOT_DISABLE_STATUS_PIN == m_pin) - return; - - if (m_pinValue > 0) - m_pinValue = 0; - else - m_pinValue = 1; - digitalWrite(m_pin, m_pinValue); - -} - -void CTBotStatusPin::setValue(bool newValue) -{ - if (CTBOT_DISABLE_STATUS_PIN == m_pin) - return; - - m_pinValue = newValue; - digitalWrite(m_pin, m_pinValue); -} - -uint8_t CTBotStatusPin::getValue() -{ - return m_pinValue; -} - -int8_t CTBotStatusPin::getPin() -{ - return m_pin; -} diff --git a/src/CTBotStatusPin.h b/src/CTBotStatusPin.h deleted file mode 100644 index f9404fd..0000000 --- a/src/CTBotStatusPin.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#ifndef CTBOTSTATUSPIN -#define CTBOTSTATUSPIN - -#include -#include "CTBotDefines.h" - -class CTBotStatusPin -{ -public: - // default constructor - CTBotStatusPin(); - // default destructor - ~CTBotStatusPin(); - - // set the status pin used to connect a LED for visual notification - // CTBOT_DISABLE_STATUS_PIN will disable the notification - // default value is CTBOT_DISABLE_STATUS_PIN (visual notification disabled) - // - ESP8266 onboard LED: 2 - // - ESP32 onboard LED : 4 - // params - // pin: the pin used for visual notification - void setPin(int8_t newPin); - - // invert the status LED value - void toggle(); - - // set the status LED value - // only on/off value (HIGH/LOW) - // params - // newValue: the new LED value - void setValue(bool newValue); - - // return the current value for the status LED - // returns - // the current value (HIGH/LOW) - uint8_t getValue(); - - // return the current pin used for visual notification - // returns - // the current pin - // CTBOT_DISABLE_STATUS_PIN if no pin is used (notification disabled) - int8_t getPin(); - -private: - uint8_t m_pinValue; - int8_t m_pin; -}; - -#endif diff --git a/src/CTBotWifiSetup.cpp b/src/CTBotWifiSetup.cpp deleted file mode 100644 index 804e1a2..0000000 --- a/src/CTBotWifiSetup.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "CTBotWifiSetup.h" -#include "Utilities.h" - -#if defined(ARDUINO_ARCH_ESP8266) // ESP8266 -#include -#elif defined(ARDUINO_ARCH_ESP32) // ESP32 -#include -#endif - -CTBotWifiSetup::CTBotWifiSetup() { - m_wifiConnectionTries = 0; - m_SSID = ""; - m_password = ""; -} - -CTBotWifiSetup::~CTBotWifiSetup() { -} - -void CTBotWifiSetup::setMaxConnectionRetries(uint8_t retries) { - m_wifiConnectionTries = retries; -} - -bool CTBotWifiSetup::isConnected() { - if (WL_CONNECTED == WiFi.status()) - return true; - return false; -} - -bool CTBotWifiSetup::reconnect() { - if (isConnected()) - return true; - - if (0 == m_SSID.length()) - return false; - - return wifiConnect(m_SSID, m_password); -} - -void CTBotWifiSetup::setStatusPin(int8_t pin) { - m_statusPin.setPin(pin); -} - -bool CTBotWifiSetup::setIP(const String& ip, const String& gateway, const String& subnetMask, const String& dns1, const String& dns2) { - IPAddress IP, SN, GW, DNS1, DNS2; - - if (!IP.fromString(ip)) { - serialLog(FSTR("--- setIP: error on IP address\n"), CTBOT_DEBUG_WIFI); - return false; - } - if (!SN.fromString(subnetMask)) { - serialLog(FSTR("--- setIP: error on subnet mask\n"), CTBOT_DEBUG_WIFI); - return false; - } - if (!GW.fromString(gateway)) { - serialLog(FSTR("--- setIP: error on gateway address\n"), CTBOT_DEBUG_WIFI); - return false; - } - if (dns1.length() != 0) { - if (!DNS1.fromString(dns1)) { - serialLog(FSTR("--- setIP: error on DNS1 address\n"), CTBOT_DEBUG_WIFI); - return false; - } - } - if (dns2.length() != 0) { - if (!DNS2.fromString(dns2)) { - serialLog(FSTR("--- setIP: error on DNS1 address\n"), CTBOT_DEBUG_WIFI); - return false; - } - } - if (WiFi.config(IP, GW, SN, DNS1, DNS2)) { - IPAddress ip = WiFi.localIP(); - String message = (String)FSTR("New IP address: ") + ip.toString() + (String)"\n"; - serialLog(message, CTBOT_DEBUG_WIFI); - return true; - } - else { - serialLog(FSTR("--- setIP: error on setting the static ip address (WiFi.config)\n"), CTBOT_DEBUG_WIFI); - return false; - } -} - -bool CTBotWifiSetup::wifiConnect(const String& ssid, const String& password) { - if (ssid.length() == 0) - return false; - - // attempt to connect to Wifi network: - int tries = 0; - String message; - message = (String)FSTR("\n\nConnecting Wifi: ") + ssid + (String)"\n"; - serialLog(message, CTBOT_DEBUG_WIFI); - -#if CTBOT_STATION_MODE > 0 - WiFi.mode(WIFI_STA); -#else - WiFi.mode(WIFI_AP_STA); -#endif - delay(500); - - WiFi.begin(ssid.c_str(), password.c_str()); - delay(500); - - if (0 == m_wifiConnectionTries) - tries = -1; - - while ((WiFi.status() != WL_CONNECTED) && (tries < m_wifiConnectionTries)) { - serialLog(".", CTBOT_DEBUG_WIFI); - - m_statusPin.toggle(); - - delay(500); - if (m_wifiConnectionTries != 0) tries++; - } - serialLog("\n", CTBOT_DEBUG_WIFI); - - if (WiFi.status() == WL_CONNECTED) { - -#if defined(ARDUINO_ARCH_ESP8266) - // Set time via NTP, as required for x.509 validation - configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov"); - serialLog("--->connect: Waiting for NTP time sync: ", CTBOT_DEBUG_WIFI); - - if (0 == m_wifiConnectionTries) - tries = -1; - else - tries = 0; - - time_t now = time(nullptr); - while ((now < 8 * 3600 * 2) && (tries < m_wifiConnectionTries)) { - delay(500); - serialLog(".", CTBOT_DEBUG_WIFI); - now = time(nullptr); - m_statusPin.toggle(); - if (m_wifiConnectionTries != 0) tries++; - } - - if (now < 8 * 3600 * 2) { - serialLog("\n--->connect: Unable to sync time data.\n", CTBOT_DEBUG_WIFI); - WiFi.disconnect(); - return false; - } - struct tm timeinfo; - gmtime_r(&now, &timeinfo); - serialLog("\nCurrent time: ", CTBOT_DEBUG_WIFI); - serialLog(asctime(&timeinfo), CTBOT_DEBUG_WIFI); - serialLog("\n", CTBOT_DEBUG_WIFI); -#endif - - IPAddress ip = WiFi.localIP(); - message = (String)FSTR("\nWiFi connected\nIP address: ") + ip.toString() + (String)"\n"; - serialLog(message, CTBOT_DEBUG_WIFI); - -#if defined(ARDUINO_ARCH_ESP8266) // ESP8266 - m_statusPin.setValue(LOW); -#elif defined(ARDUINO_ARCH_ESP32) // ESP32 - m_statusPin.setValue(HIGH); -#endif - - m_SSID = ssid; - m_password = password; - - return true; - } - else { - message = (String)FSTR("\nUnable to connect to ") + ssid + (String)FSTR(" network.\n"); - serialLog(message, CTBOT_DEBUG_WIFI); - -#if defined(ARDUINO_ARCH_ESP8266) // ESP8266 - m_statusPin.setValue(HIGH); -#elif defined(ARDUINO_ARCH_ESP32) // ESP32 - m_statusPin.setValue(LOW); -#endif - m_SSID = ""; - m_password = ""; - return false; - } -} \ No newline at end of file diff --git a/src/CTBotWifiSetup.h b/src/CTBotWifiSetup.h deleted file mode 100644 index e8c20a8..0000000 --- a/src/CTBotWifiSetup.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once -#ifndef CTBOTWIFISETUP -#define CTBOTWIFISETUP - -#include -#include "CTBotStatusPin.h" -#include "CTBotDefines.h" - -class CTBotWifiSetup -{ -public: - // default constructor - CTBotWifiSetup(); - // default destructor - ~CTBotWifiSetup(); - - // set the status pin used to connect a LED for visual notification - // CTBOT_DISABLE_STATUS_PIN will disable the notification - // default value is CTBOT_DISABLE_STATUS_PIN (visual notification disabled) - // - ESP8266 onboard LED: 2 - // - ESP32 onboard LED : 4 - // params - // pin: the pin used for visual notification - void setStatusPin(int8_t pin); - - // set a static ip. If not set, use the DHCP. - // params - // ip : the ip address - // gateway : the gateway address - // subnetMask: the subnet mask - // dns1 : the optional first DNS - // dns2 : the optional second DNS - // returns - // true if no error occurred - bool setIP(const String& ip, const String& gateway, const String& subnetMask, const String& dns1 = "", const String& dns2 = ""); - - // connect to a wifi network - // params - // ssid : the SSID network identifier - // password: the optional password - // returns - // true if no error occurred - bool wifiConnect(const String& ssid, const String& password = ""); - - // set how many times the wifiConnect method have to try to connect to the specified SSID. - // A value of zero means infinite retries. - // Default value is zero (infinite retries) - // params - // retries: how many times wifiConnect have to try to connect - void setMaxConnectionRetries(uint8_t retries); - - // check if a WiFi connection is established - // returns - // true if there is an established WiFi connection - bool isConnected(); - - // try to reconnect to a previuos connected WiFi network - // returns - // true if no error occurred - bool reconnect(); - -private: - uint8_t m_wifiConnectionTries; - CTBotStatusPin m_statusPin; - String m_SSID; - String m_password; -}; - -#endif \ No newline at end of file diff --git a/src/Utilities.cpp b/src/Utilities.cpp index c3be09b..f280350 100644 --- a/src/Utilities.cpp +++ b/src/Utilities.cpp @@ -1,6 +1,7 @@ #include "Utilities.h" -bool unicodeToUTF8(String unicode, String &utf8) { +bool unicodeToUTF8(String unicode, String &utf8) +{ uint32_t value = 0; unicode.toUpperCase(); @@ -10,7 +11,8 @@ bool unicodeToUTF8(String unicode, String &utf8) { if ((unicode[0] != '\\') || (unicode[1] != 'U')) return false; - for (uint16_t i = 2; i < unicode.length(); i++) { + for (uint16_t i = 2; i < unicode.length(); i++) + { uint8_t digit = unicode[i]; if ((digit >= '0') && (digit <= '9')) digit -= '0'; @@ -25,7 +27,8 @@ bool unicodeToUTF8(String unicode, String &utf8) { buffer[1] = 0x00; utf8 = ""; - if (value < 0x80) { + if (value < 0x80) + { buffer[0] = value & 0x7F; utf8 = (String)buffer; return true; @@ -34,11 +37,13 @@ bool unicodeToUTF8(String unicode, String &utf8) { byte maxValue = 0x20; byte mask = 0xC0; - while (maxValue > 0x01) { + while (maxValue > 0x01) + { buffer[0] = (value & 0x3F) | 0x80; utf8 = (String)buffer + utf8; value = value >> 6; - if (value < maxValue) { + if (value < maxValue) + { buffer[0] = (value & (maxValue - 1)) | mask; utf8 = (String)buffer + utf8; return true; @@ -49,7 +54,8 @@ bool unicodeToUTF8(String unicode, String &utf8) { return false; } -String int64ToAscii(int64_t value) { +String int64ToAscii(int64_t value) +{ String buffer = ""; int64_t temp; uint8_t rest; @@ -59,7 +65,8 @@ String int64ToAscii(int64_t value) { else temp = value; - while (temp != 0) { + while (temp != 0) + { rest = temp % 10; temp = (temp - rest) / 10; ascii = 0x30 + rest; @@ -70,18 +77,21 @@ String int64ToAscii(int64_t value) { return buffer; } -String URLEncodeMessage(String message) { +String URLEncodeMessage(String message) +{ String encodedMessage = ""; char buffer[4]; buffer[0] = '%'; buffer[3] = 0x00; uint16_t i; - for (i = 0; i < message.length(); i++) { + for (i = 0; i < message.length(); i++) + { if (((message[i] >= 0x30) && (message[i] <= 0x39)) || // numbers ((message[i] >= 0x41) && (message[i] <= 0x5A)) || // caps letters - ((message[i] >= 0x61) && (message[i] <= 0x7A))) // letters + ((message[i] >= 0x61) && (message[i] <= 0x7A))) // letters encodedMessage += (String)message[i]; - else { + else + { buffer[1] = message[i] >> 4; if (buffer[1] <= 0x09) buffer[1] += 0x30; @@ -96,6 +106,224 @@ String URLEncodeMessage(String message) { encodedMessage += (String)buffer; } } - return encodedMessage ; + return encodedMessage; } +String toUTF8(String message) +{ + String converted = ""; + uint16_t i = 0; + String subMessage; + while (i < message.length()) + { + subMessage = (String)message[i]; + if (message[i] != '\\') + { + converted += subMessage; + i++; + } + else + { + // found "\" + i++; + if (i == message.length()) + { + // no more characters + converted += subMessage; + } + else + { + subMessage += (String)message[i]; + if (message[i] != 'u') + { + converted += subMessage; + i++; + } + else + { + // found \u escape code + i++; + if (i == message.length()) + { + // no more characters + converted += subMessage; + } + else + { + uint8_t j = 0; + while ((j < 4) && ((j + i) < message.length())) + { + subMessage += (String)message[i + j]; + j++; + } + i += j; + String utf8; + if (unicodeToUTF8(subMessage, utf8)) + converted += utf8; + else + converted += subMessage; + } + } + } + } + } + return (converted); +} + +// C-style string version +void toUTF8(char *message) +{ + uint16_t i = 0; // + uint16_t encoded = 0; + uint16_t messageLength = strlen(message); + + while (message[i] != 0x00) + { + if (message[i] != '\\') + { + // no start escape character -> this is a standard char + message[encoded] = message[i]; + i++; + encoded++; + } + else + { + // found '\' + i++; + if (0x00 == message[i]) + { + // found the end of the string + message[encoded] = '\\'; + message[encoded + 1] = message[i]; + return; + } + // if ((message[i] != 'u') || (message[i] != 'U')) { + if (message[i] != 'u') + { + // no unicode escape character -> leave it + message[encoded] = '\\'; + message[encoded + 1] = message[i]; + i++; + encoded += 2; + } + else + { + // found escape character for unicode "\u" + if ((messageLength - i) < 5) + { + // there are no four digit "unicode" code + message[encoded] = '\\'; + message[encoded + 1] = message[i]; + encoded += 2; + while (message[i] != 0x00) + { + message[encoded] = message[i]; + i++; + encoded++; + } + message[encoded] = 0x00; + return; + } + i++; + char decoded[7]; + decoded[0] = '\\'; + decoded[1] = 'u'; + for (uint8_t j = 0; j < 4; j++) + { + decoded[2 + j] = message[i + j]; + } + decoded[6] = 0x00; + + bool result = unicodeToUTF8(decoded); + if (result) + { + // unicode -> UTF8 ok! + i += 4; + uint8_t j = 0; + while (decoded[j] != 0x00) + { + message[encoded] = decoded[j]; + j++; + encoded++; + } + } + else + { + // unicode -> UTF8 nok! + message[encoded] = '\\'; + message[encoded + 1] = message[i]; + encoded += 2; + for (uint8_t j = 0; j < 4; j++) + { + message[encoded] = message[i]; + i++; + encoded++; + } + } + } + } + } + message[encoded] = 0x00; +} + +// C-style string version +bool unicodeToUTF8(char *str) +{ + uint32_t value = 0; + uint16_t i; + + if (strlen(str) < 3) + return false; + + strupr(str); + + if ((str[0] != '\\') || (str[1] != 'U')) + return false; + + for (i = 2; i < strlen(str); i++) + { + uint8_t digit = str[i]; + if ((digit >= '0') && (digit <= '9')) + digit -= '0'; + else if ((digit >= 'A') && (digit <= 'F')) + digit = (digit - 'A') + 10; + else + return false; + value += digit << (4 * (strlen(str) - (i + 1))); + } + + char buffer[5]; // UTF8 code max length is four char + buffer[1] = 0x00; + str[0] = 0x00; + i = 0; + + if (value < 0x80) + { + str[i] = (value & 0x7F); + str[i + 1] = 0x00; + return true; + } + + byte maxValue = 0x20; + byte mask = 0xC0; + + while (maxValue > 0x01) + { + buffer[0] = (value & 0x3F) | 0x80; + buffer[1] = 0x00; + strcat(buffer, str); + strcpy(str, buffer); + value = value >> 6; + if (value < maxValue) + { + buffer[0] = (value & (maxValue - 1)) | mask; + buffer[1] = 0x00; + strcat(buffer, str); + strcpy(str, buffer); + return true; + } + mask = mask + maxValue; + maxValue = maxValue >> 1; + } + return false; +} \ No newline at end of file diff --git a/src/Utilities.h b/src/Utilities.h index d2b96a8..f71a533 100644 --- a/src/Utilities.h +++ b/src/Utilities.h @@ -4,13 +4,14 @@ #include #include "CTBotDefines.h" + // convert an UNICODE coded string to a UTF8 coded string // params // unicode: the UNICODE string to convert // utf8 : the string result of UNICODE to UTF8 conversion // returns // true if no error occurred -bool unicodeToUTF8(String unicode, String &utf8); +bool unicodeToUTF8(String unicode, String& utf8); // convert an int64 value to an ASCII string // params @@ -26,22 +27,41 @@ String int64ToAscii(int64_t value); // the encoded string String URLEncodeMessage(String message); +// convert the message string from unicode to UTF8 +void toUTF8(char* message); + +// convert a unicode charachter into the UTF8 equivalent +bool unicodeToUTF8(char* str); + // send data to the serial port. It work only if the CTBOT_DEBUG_MODE is enabled. // params -// message : the message to send +// format : the message to send, formatted like printf // debugLevel: debug level. Useful to filter debug messages wanted +// ... : depending on the format string, the list of the additional parameters +void inline serialLog(uint8_t debugLevel, const char* format, ...) __attribute__((format(printf, 2, 3))); + #if CTBOT_DEBUG_MODE > 0 -inline void serialLog(String message, uint8_t debugLevel) { - if ((debugLevel & CTBOT_DEBUG_MODE) != 0) - Serial.print(message); -} -inline void serialLog(int32_t value, uint8_t debugLevel) { - if ((debugLevel & CTBOT_DEBUG_MODE) != 0) - Serial.print(value); +void inline serialLog(uint8_t debugLevel, const char* format, ...) { + if (((debugLevel) & (CTBOT_DEBUG_MODE)) != 0) { + va_list arg; + char* buf; + uint32_t size; + va_start(arg, format); + size = vsnprintf(NULL, 0, format, (__VALIST)arg) + 1; + buf = (char*)malloc(size); + if (NULL == buf) { + Serial.println(FSTR("--->serialLog: unable to allocate memory.")); + return; + } + vsnprintf(buf, size, format, (__VALIST)arg); + Serial.printf(CFSTR("%s"), buf); + free(buf); + va_end(arg); + } } #else -inline void serialLog(String, uint8_t) {} -inline void serialLog(int64_t, uint8_t) {} +void inline serialLog(uint8_t , const char* , ...) {} #endif + #endif