diff --git a/.travis.yml b/.travis.yml index f33973b..8a9496a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ env: - SCRIPT=platformioSingle EXAMPLE_NAME=ChatAction EXAMPLE_FOLDER=/ BOARDTYPE=ESP32 BOARD=esp32dev - SCRIPT=platformioSingle EXAMPLE_NAME=InlineKeyboardMarkup EXAMPLE_FOLDER=/CustomKeyboard/ BOARDTYPE=ESP32 BOARD=esp32dev - SCRIPT=platformioSingle EXAMPLE_NAME=ReplyKeyboardMarkup EXAMPLE_FOLDER=/CustomKeyboard/ BOARDTYPE=ESP32 BOARD=esp32doit-devkit-v1 + - SCRIPT=platformioSingle EXAMPLE_NAME=UpdateInlineKeyboard EXAMPLE_FOLDER=/CustomKeyboard/ BOARDTYPE=ESP32 BOARD=esp32dev - SCRIPT=platformioSingle EXAMPLE_NAME=EchoBot EXAMPLE_FOLDER=/ BOARDTYPE=ESP32 BOARD=esp32dev - SCRIPT=platformioSingle EXAMPLE_NAME=FlashLED EXAMPLE_FOLDER=/ BOARDTYPE=ESP32 BOARD=esp32doit-devkit-v1 - SCRIPT=platformioSingle EXAMPLE_NAME=Location EXAMPLE_FOLDER=/ BOARDTYPE=ESP32 BOARD=esp32dev diff --git a/examples/ESP32/CustomKeyboard/UpdateInlineKeyboard/README.md b/examples/ESP32/CustomKeyboard/UpdateInlineKeyboard/README.md new file mode 100644 index 0000000..54cff4a --- /dev/null +++ b/examples/ESP32/CustomKeyboard/UpdateInlineKeyboard/README.md @@ -0,0 +1,21 @@ +#ESP32 - LED button update + +This is an example of how one can update inline keyboard messages (buttons). +The message_id of the specific message is sent when a message is received. +This message_id can be used to UPDATE that message. +One can update text inside a message, but also buttons can be updated. +This way one can build menu's, like the menu the botfather uses. + +In this simple example we use a inlinekeyboard button to toggle (and update) the state of a LED. + +NOTE: You will need to enter your SSID, password and bot Token for the example to work. + +Example and update to Universal-Arduino-Telegram-Bot originally written by +[Frits Jan van Kempen] (https://github.com/fritsjan) with inspiration from [RomeHein] (https://github.com/RomeHein) + +Adapted by [Brian Lough](https://github.com/witnessmenow) + +## License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. diff --git a/examples/ESP32/CustomKeyboard/UpdateInlineKeyboard/UpdateInlineKeyboard.ino b/examples/ESP32/CustomKeyboard/UpdateInlineKeyboard/UpdateInlineKeyboard.ino new file mode 100644 index 0000000..80ba5ca --- /dev/null +++ b/examples/ESP32/CustomKeyboard/UpdateInlineKeyboard/UpdateInlineKeyboard.ino @@ -0,0 +1,178 @@ +/******************************************************************* + An example to show how to edit an existing inline keyboard. + + In this example the keyboard is updated with the state of + the LED. + + written by Frits Jan van Kempen + *******************************************************************/ +#include +#include +#include +#include + +// Initialize Wifi connection to the router +const char *ssid = "mySSID"; +const char *password = "myPASSWORD"; + +// Initialize Telegram BOT +#define BOTtoken "xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxx" // your Bot Token (Get from Botfather) +WiFiClientSecure client; +UniversalTelegramBot bot(BOTtoken, client); + +int Bot_mtbs = 1000; //mean time between scan messages +long Bot_lasttime; //last time messages' scan has been done +int last_message_id = 0; + +// LED parameters +const int ledPin = 2; // Internal LED on DevKit ESP32-WROOM (GPIO2) +int ledState = LOW; + +void handleNewMessages(int numNewMessages) +{ + + for (int i = 0; i < numNewMessages; i++) + { + + // Get all the important data from the message + int message_id = bot.messages[i].message_id; + String chat_id = String(bot.messages[i].chat_id); + String text = bot.messages[i].text; + String from_name = bot.messages[i].from_name; + if (from_name == "") + from_name = "Guest"; + String msg = ""; // init a message string to use + + // Output the message_id to give you feeling on how this example works + Serial.print("Message id: "); + Serial.println(message_id); + + // Inline buttons with callbacks when pressed will raise a callback_query message + if (bot.messages[i].type == "callback_query") + { + Serial.print("Call back button pressed by: "); + Serial.println(bot.messages[i].from_id); + Serial.print("Data on the button: "); + Serial.println(bot.messages[i].text); + + if (text == "/toggleLED") + { + + // Toggle the ledState and update the LED itself + ledState = !ledState; + digitalWrite(ledPin, ledState); + + // Now we can UPDATE the message, lets prepare it for sending: + msg = "Hi " + from_name + "!\n"; + msg += "Notice how the LED state has changed!\n\n"; + msg += "Try it again, see the button has updated as well:\n\n"; + + // Prepare the buttons + String keyboardJson = "["; // start Json + keyboardJson += "[{ \"text\" : \"The LED is "; + if (ledState) + { + keyboardJson += "ON"; + } + else + { + keyboardJson += "OFF"; + } + keyboardJson += "\", \"callback_data\" : \"/toggleLED\" }]"; + keyboardJson += ", [{ \"text\" : \"Send text\", \"callback_data\" : \"This text was sent by inline button\" }]"; // add another button + //keyboardJson += ", [{ \"text\" : \"Go to Google\", \"url\" : \"https://www.google.com\" }]"; // add another button, this one appears after first Update + keyboardJson += "]"; // end Json + + // Now send this message including the current message_id as the 5th input to UPDATE that message + bot.sendMessageWithInlineKeyboard(chat_id, msg, "Markdown", keyboardJson, message_id); + } + + else + { + // echo back callback_query which is not handled above + bot.sendMessage(chat_id, text, "Markdown"); + } + } + + // 'Normal' messages are handled here + else + { + if (text == "/start") + { + // lets create a friendly welcome message + msg = "Hi " + from_name + "!\n"; + msg += "I am your Telegram Bot running on ESP32.\n\n"; + msg += "Lets test this updating LED button below:\n\n"; + + // lets create a button depending on the current ledState + String keyboardJson = "["; // start of keyboard json + keyboardJson += "[{ \"text\" : \"The LED is "; + if (ledState) + { + keyboardJson += "ON"; + } + else + { + keyboardJson += "OFF"; + } + keyboardJson += "\", \"callback_data\" : \"/toggleLED\" }]"; //callback is /toggleLED + keyboardJson += ", [{ \"text\" : \"Send text\", \"callback_data\" : \"This text was sent by inline button\" }]"; // add another button + keyboardJson += "]"; // end of keyboard json + + //first time, send this message as a normal inline keyboard message: + bot.sendMessageWithInlineKeyboard(chat_id, msg, "Markdown", keyboardJson); + } + if (text == "/options") + { + String keyboardJson = "[[{ \"text\" : \"Go to Google\", \"url\" : \"https://www.google.com\" }], [{ \"text\" : \"Send\", \"callback_data\" : \"This was sent by inline\" }]]"; + bot.sendMessageWithInlineKeyboard(chat_id, "Choose from one of the following options", "", keyboardJson); + } + } + } +} + +void setup() +{ + Serial.begin(115200); + + // Attempt to connect to Wifi network: + Serial.print("Connecting Wifi: "); + Serial.println(ssid); + + // Set WiFi to station mode and disconnect from an AP if it was Previously + // connected + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + Serial.print("."); + delay(500); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + pinMode(ledPin, OUTPUT); // initialize ledPin as an output. + digitalWrite(ledPin, ledState); // initialize pin as low (LED Off) +} + +void loop() +{ + // run this in loop to poll new messages + if (millis() > Bot_lasttime + Bot_mtbs) + { + int numNewMessages = bot.getUpdates(bot.last_message_received + 1); + + while (numNewMessages) + { + Serial.println("got response"); + handleNewMessages(numNewMessages); + numNewMessages = bot.getUpdates(bot.last_message_received + 1); + } + + Bot_lasttime = millis(); + } +} \ No newline at end of file diff --git a/src/UniversalTelegramBot.cpp b/src/UniversalTelegramBot.cpp index d500dcf..d449098 100644 --- a/src/UniversalTelegramBot.cpp +++ b/src/UniversalTelegramBot.cpp @@ -235,7 +235,7 @@ String UniversalTelegramBot::sendMultipartFormDataToTelegram( client->print(buildCommand(command)); client->println(F(" HTTP/1.1")); // Host header - client->print(F("Host: " TELEGRAM_HOST)); + client->println(F("Host: " TELEGRAM_HOST)); // bugfix - https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot/issues/186 client->println(F("User-Agent: arduino/1.0")); client->println(F("Accept: */*")); @@ -267,7 +267,6 @@ String UniversalTelegramBot::sendMultipartFormDataToTelegram( #endif byte buffer[512]; int count = 0; - while (moreDataAvailableCallback()) { buffer[count] = getNextByteCallback(); count++; @@ -460,6 +459,7 @@ bool UniversalTelegramBot::processResult(JsonObject result, int messageIndex) { messages[messageIndex].chat_id = message["chat"]["id"].as(); messages[messageIndex].chat_title = message["chat"]["title"].as(); messages[messageIndex].hasDocument = false; + messages[messageIndex].message_id = message["message_id"].as(); // added message id if (message.containsKey("text")) { messages[messageIndex].text = message["text"].as(); @@ -479,7 +479,8 @@ bool UniversalTelegramBot::processResult(JsonObject result, int messageIndex) { messages[messageIndex].reply_to_message_id = message["reply_to_message"]["message_id"]; // no need to check if containsKey["text"]. If it doesn't, it default to null messages[messageIndex].reply_to_text = message["reply_to_message"]["text"].as(); - } + } + } else if (result.containsKey("channel_post")) { JsonObject message = result["channel_post"]; messages[messageIndex].type = F("channel_post"); @@ -487,6 +488,7 @@ bool UniversalTelegramBot::processResult(JsonObject result, int messageIndex) { messages[messageIndex].date = message["date"].as(); messages[messageIndex].chat_id = message["chat"]["id"].as(); messages[messageIndex].chat_title = message["chat"]["title"].as(); + messages[messageIndex].message_id = message["message_id"].as(); // added message id } else if (result.containsKey("callback_query")) { JsonObject message = result["callback_query"]; @@ -499,6 +501,8 @@ bool UniversalTelegramBot::processResult(JsonObject result, int messageIndex) { messages[messageIndex].reply_to_text = message["message"]["text"].as(); messages[messageIndex].chat_title = F(""); messages[messageIndex].query_id = message["id"].as(); + messages[messageIndex].message_id = message["message"]["message_id"].as(); // added message id + } else if (result.containsKey("edited_message")) { JsonObject message = result["edited_message"]; messages[messageIndex].type = F("edited_message"); @@ -507,6 +511,7 @@ bool UniversalTelegramBot::processResult(JsonObject result, int messageIndex) { messages[messageIndex].date = message["date"].as(); messages[messageIndex].chat_id = message["chat"]["id"].as(); messages[messageIndex].chat_title = message["chat"]["title"].as(); + messages[messageIndex].message_id = message["message_id"].as(); // added message id if (message.containsKey("text")) { messages[messageIndex].text = message["text"].as(); @@ -555,16 +560,19 @@ bool UniversalTelegramBot::sendSimpleMessage(const String& chat_id, const String } bool UniversalTelegramBot::sendMessage(const String& chat_id, const String& text, - const String& parse_mode) { + const String& parse_mode, int message_id) { // added message_id DynamicJsonDocument payload(maxMessageLength); payload["chat_id"] = chat_id; payload["text"] = text; + if (message_id != 0) + payload["message_id"] = message_id; // added message_id + if (parse_mode != "") payload["parse_mode"] = parse_mode; - return sendPostMessage(payload.as()); + return sendPostMessage(payload.as(), message_id); // if message id == 0 then edit is false, else edit is true } bool UniversalTelegramBot::sendMessageWithReplyKeyboard( @@ -599,25 +607,29 @@ bool UniversalTelegramBot::sendMessageWithReplyKeyboard( bool UniversalTelegramBot::sendMessageWithInlineKeyboard(const String& chat_id, const String& text, const String& parse_mode, - const String& keyboard) { + const String& keyboard, + int message_id) { // added message_id DynamicJsonDocument payload(maxMessageLength); payload["chat_id"] = chat_id; payload["text"] = text; + if (message_id != 0) + payload["message_id"] = message_id; // added message_id + if (parse_mode != "") payload["parse_mode"] = parse_mode; JsonObject replyMarkup = payload.createNestedObject("reply_markup"); replyMarkup["inline_keyboard"] = serialized(keyboard); - return sendPostMessage(payload.as()); + return sendPostMessage(payload.as(), message_id); // if message id == 0 then edit is false, else edit is true } /*********************************************************************** - * SendPostMessage - function to send message to telegram * + * SendPostMessage - function to send message to telegram * * (Arguments to pass: chat_id, text to transmit and markup(optional)) * ***********************************************************************/ -bool UniversalTelegramBot::sendPostMessage(JsonObject payload) { +bool UniversalTelegramBot::sendPostMessage(JsonObject payload, bool edit) { // added message_id bool sent = false; #ifdef TELEGRAM_DEBUG @@ -628,9 +640,9 @@ bool UniversalTelegramBot::sendPostMessage(JsonObject payload) { unsigned long sttime = millis(); if (payload.containsKey("text")) { - while (millis() - sttime < 8000ul) { // loop for a while to send the message - String response = sendPostToTelegram(BOT_CMD("sendMessage"), payload); - #ifdef TELEGRAM_DEBUG + while (millis() < sttime + 8000) { // loop for a while to send the message + String response = sendPostToTelegram((edit ? BOT_CMD("editMessageText") : BOT_CMD("sendMessage")), payload); // if edit is true we send a editMessageText CMD + #ifdef TELEGRAM_DEBUG Serial.println(response); #endif sent = checkForOkResponse(response); diff --git a/src/UniversalTelegramBot.h b/src/UniversalTelegramBot.h index a43233c..5fc97ee 100644 --- a/src/UniversalTelegramBot.h +++ b/src/UniversalTelegramBot.h @@ -58,6 +58,7 @@ struct telegramMessage { float longitude; float latitude; int update_id; + int message_id; int reply_to_message_id; String reply_to_text; @@ -84,17 +85,17 @@ class UniversalTelegramBot { bool getMe(); bool sendSimpleMessage(const String& chat_id, const String& text, const String& parse_mode); - bool sendMessage(const String& chat_id, const String& text, const String& parse_mode = ""); + bool sendMessage(const String& chat_id, const String& text, const String& parse_mode = "", int message_id = 0); bool sendMessageWithReplyKeyboard(const String& chat_id, const String& text, const String& parse_mode, const String& keyboard, bool resize = false, bool oneTime = false, bool selective = false); bool sendMessageWithInlineKeyboard(const String& chat_id, const String& text, - const String& parse_mode, const String& keyboard); + const String& parse_mode, const String& keyboard, int message_id = 0); bool sendChatAction(const String& chat_id, const String& text); - bool sendPostMessage(JsonObject payload); + bool sendPostMessage(JsonObject payload, bool edit = false); String sendPostPhoto(JsonObject payload); String sendPhotoByBinary(const String& chat_id, const String& contentType, int fileSize, MoreDataAvailable moreDataAvailableCallback,