Telegram Inline Keyboard

What this flow does

This flow allows you to create and modifiy multiple inline keyboards. Unlike the official example's of inline keybaords, this can be easily used multiple times throughout your flows with little modification.

Contact

Alex Trostle - GitHub - Email - LinkedIn - Instagram - My Website

Buy Me A Coffee

Pre-Reqs

You will need Telegram installed on your phone and a BOT Created using the BOT Father. There is lots of instructions on how ot create a bot on the Telegram website or the Telegram nodes on Node-RED You will also need to enable inline messages by sending the BOT Father /setinline

What Needs Modified for you to run

  1. Configure your Telegram Bot and add the Bot to the Nodes listed
    • Telegram Command Node labeled /lights
    • Telegram Event Node labeled callback_query
    • Telegram Sender Node labled show inline keyboard
    • Telegram Sender Node labled answer callback query
  2. MQTT Node needs configured

Usage

The flow will start anytime the /lights command is sent to your Telegram Bot

Debugging

When debugging connect a Debug Node to all of the outputs of the evaluate callback query

How it works

The flow starts when /lights is sent to your Telegram Bot. This causes the initial inline keyboard message to be built in the function called initial inline keyboard message and sent throught the Telegram Sender Node labeled show inline keyboard. Once this first message has been sent the following Function Node labeled save messageId saves the message Id of the first message in a global variable as well as setting the callback number in a global variable. To only use this one callback query the callback number global variable is not needed, but when you have multiple callback queries it is used to separate the responses from each one. The Telegram Event Node labeled callback_query recieves the callback query that a button press on the inline keyboard will trigger. Note that the Event node when configured to callback queries will recieve all call back queries. This is why the callback query number was set earlier. From here the two function nodes will be activated. One will edit the inline keyboard and the other will evaluate what the first button click means. The one labeled edit inline keyboard message is similar to the telegram example but with one major change being that it only evaluates if the correct global callback number is set. This means if the callback query was activated by another command this code will not run at all. The evaluate callback query operates in a similar manner but rather than replying to the message looks at what button you pressed and based on it outputs to the Data->information node which does just that. The Join both selections node is used to join the user input of the first inline keyboard and the user input from the second inline keyboard together. The following function determines an action and sends it over mqtt. It would be very easy to rather than implement mqtt implement other actions here.

[{"id":"9cff10ff.07ea8","type":"group","z":"47cca1f5.5a7a5","name":"Telegram Inline Keyboard","style":{"stroke":"#ff0000","fill":"#000000","label":true,"label-position":"n","color":"#ffffff"},"nodes":["f77ff55c.626e28","14687b00.bcecc5","2dd8279e.71db38","33d79a15.9bdfa6","bb7ba34b.2ca7f","c13d4a93.613218","d554a95e.4fa678","e3a405ef.b83438","6acf1ab9.148b74","74e1c7fb.a05638","c72a49b2.420da8","bdb17e1d.37a18","42886470.e4550c","cecb9664.171b68","40595b98.f585d4"],"x":34,"y":1619,"w":1012,"h":304.5},{"id":"f77ff55c.626e28","type":"debug","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":330,"y":1660,"wires":[]},{"id":"14687b00.bcecc5","type":"catch","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"","scope":null,"uncaught":false,"x":160,"y":1660,"wires":[["f77ff55c.626e28"]]},{"id":"2dd8279e.71db38","type":"comment","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"Inline Telegram Keyboard Light Control","info":"Created by Alex Trostle","x":630,"y":1660,"wires":[]},{"id":"33d79a15.9bdfa6","type":"telegram sender","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"show inline keyboard","bot":"","haserroroutput":false,"outputs":1,"x":560,"y":1720,"wires":[["d554a95e.4fa678"]]},{"id":"bb7ba34b.2ca7f","type":"function","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"initial inline keyboard message","func":"context.global.keyboard = { messageId : msg.payload.messageId };\n\nvar opts = {\n  reply_to_message_id: msg.payload.messageId,\n  reply_markup: JSON.stringify({\n    \"inline_keyboard\": [[\n                {\n                    \"text\": \"Bedside Lamp\",\n                    \"callback_data\": \"Bedside Lamp\"            \n                }, \n                {\n                    \"text\": \"Door Lamp\",\n                    \"callback_data\": \"Door Lamp\"            \n                }]\n            ]\n  })\n};\n\nmsg.payload.content = 'Divide the light from the darkness!';\nmsg.payload.options = opts;\n\nreturn [ msg ];\n","outputs":"1","noerr":0,"initialize":"","finalize":"","x":310,"y":1720,"wires":[["33d79a15.9bdfa6"]]},{"id":"c13d4a93.613218","type":"telegram command","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"/lights","command":"/lights","bot":"","strict":false,"hasresponse":false,"useregex":false,"removeregexcommand":false,"outputs":1,"x":110,"y":1720,"wires":[["bb7ba34b.2ca7f"]]},{"id":"d554a95e.4fa678","type":"function","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"save messageId","func":"// We store the messageId to be able to edit this reply in the callback query. \ncontext.global.keyboard.messageId = msg.payload.sentMessageId;\ncontext.global.callbackNumber = 1;\nreturn [ msg ];\n","outputs":"1","noerr":0,"initialize":"","finalize":"","x":760,"y":1720,"wires":[[]]},{"id":"e3a405ef.b83438","type":"telegram event","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"callback_query","bot":"","event":"callback_query","autoanswer":true,"x":140,"y":1820,"wires":[["c72a49b2.420da8","6acf1ab9.148b74"]],"info":"ALL CALL BACK QUERIES ARE READ BY\nALL CALL BACK QUERRY NODES. This means\nwhether you only need one callback_query\nfor all of NodeRED. You just have to sort them."},{"id":"6acf1ab9.148b74","type":"function","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"evaluate callback query","func":"// This is a sample switch to demonstrate the handling of the user input.\nif(context.global.callbackNumber != 1){\n    return;\n}\nif(msg.payload.content === \"Turn on\")\n{\n    // Hide the keyboard and forget the messageId\n    msg.payload.type = 'deleteMessage';\n    msg.payload.content = context.global.keyboard.messageId\n    context.global.keyboard.messageId = null;\n    context.global.callbackNumber = null;\n    msg.topic = 'turn on';\n    // You could also send a editMessageReplyMarkup with an empty reply_markup here\n    return [ null, null, msg, null, null ];\n}\nelse if(msg.payload.content === \"Turn off\")\n{\n    // Hide the keyboard and forget the messageId\n    msg.payload.type = 'deleteMessage';\n    msg.payload.content = context.global.keyboard.messageId\n    context.global.keyboard.messageId = null;\n    context.global.callbackNumber = null;\n    msg.topic = 'turn off';\n    // You could also send a editMessageReplyMarkup with an empty reply_markup here\n    return [ null, msg, null, null, null ];\n}\nelse if(msg.payload.content === \"Bedside Lamp\")\n{\n    // we don't want to clear the message yet, we need another answer\n    msg.topic = 'bedside lamp';\n    return [ null, null, null, null, msg ];\n}\nelse if(msg.payload.content === \"Door Lamp\")\n{\n    // we don't want to clear the message yet, we need another answer\n    msg.topic = 'door lamp';\n    return [ null, null, null, msg, null ];\n}\nelse\n{\n    var show_alert = false; // you can set this to true to open a dialog with the answer in the client.\n    \n    // msg.payload.content contains the callback data from the keyboard.\n    // You may change this value here.\n    msg.payload.options = show_alert;\n    \n    return [ msg, null, null, null, null ];\n}","outputs":5,"noerr":0,"initialize":"","finalize":"","x":350,"y":1860,"wires":[["74e1c7fb.a05638","bdb17e1d.37a18"],["74e1c7fb.a05638","bdb17e1d.37a18"],["74e1c7fb.a05638","bdb17e1d.37a18"],["bdb17e1d.37a18"],["bdb17e1d.37a18"]]},{"id":"74e1c7fb.a05638","type":"telegram sender","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"answer callback query","bot":"","haserroroutput":false,"outputs":1,"x":620,"y":1780,"wires":[[]]},{"id":"c72a49b2.420da8","type":"function","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"edit inline keyboard message","func":"if(context.global.callbackNumber != 1){\n    return;\n}\n// This is the message id of the initial keyboard that is simply exchanged by a new one.\nvar messageId = context.global.keyboard.messageId;\n\n// This is a sample of how to send a second inline keyboard with modified buttons\nvar reply_markup = JSON.stringify({\n    \"inline_keyboard\": [[\n                {\n                    \"text\": \"On\",\n                    \"callback_data\": \"Turn on\"            \n                }, \n                {\n                    \"text\": \"Off\",\n                    \"callback_data\": \"Turn off\"            \n                }]\n            ]\n  });\n\n\nvar options = {\n    chat_id : msg.payload.chatId,\n    reply_markup : reply_markup,\n    message_id : messageId\n};\n\nmsg.payload.type = 'editMessageReplyMarkup';\nmsg.payload.content = reply_markup;\nmsg.payload.options = options;\n\nreturn [ msg ];\n","outputs":"1","noerr":0,"initialize":"","finalize":"","x":360,"y":1780,"wires":[["74e1c7fb.a05638"]]},{"id":"bdb17e1d.37a18","type":"function","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"Data -> information","func":"//[bed, door, on, off]\nif (msg.topic === 'bedside lamp'){\n    msg = {payload: 'bed'};\n    return msg;\n}\nelse if (msg.topic === 'door lamp'){\n    msg = {payload: 'door'};\n    return msg;\n}\nelse if (msg.topic === 'turn on'){\n    msg = {payload: 'on'};\n    return msg;\n}\nelse if (msg.topic === 'turn off'){\n    msg = {payload: 'off'};\n    return msg;\n}\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":610,"y":1860,"wires":[["42886470.e4550c"]]},{"id":"42886470.e4550c","type":"join","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"Join both selections","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"","joinerType":"str","accumulate":false,"timeout":"20","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":810,"y":1880,"wires":[["cecb9664.171b68"]]},{"id":"cecb9664.171b68","type":"function","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"determine action","func":"if(msg.payload[0] === 'bed' && msg.payload[1] === 'off'){\n    msg.topic = 'esp/to/esp13';\n    msg.payload = '1';//a 1 turns off ESP32 #13 relay\n    return [msg, null];\n}\nelse if(msg.payload[0] === 'bed' && msg.payload[1] === 'on'){\n    msg.topic = 'esp/to/esp13';\n    msg.payload = '0';//a 0 turns on ESP32 #13 relay\n    return [msg, null];\n}\nelse if(msg.payload[0] === 'door' && msg.payload[1] === 'off'){\n    msg.topic = 'esp/to/esp10';\n    msg.payload = '1';//a 1 turns off ESP32 #10 relay\n    return [null, msg];\n}\nelse if(msg.payload[0] === 'door' && msg.payload[1] === 'on'){\n    msg.topic = 'esp/to/esp10';\n    msg.payload = '0';//a 0 turns on ESP32 #10 relay\n    return [null, msg];\n}\nreturn;","outputs":2,"noerr":0,"initialize":"","finalize":"","x":820,"y":1820,"wires":[["40595b98.f585d4"],["40595b98.f585d4"]]},{"id":"40595b98.f585d4","type":"mqtt out","z":"47cca1f5.5a7a5","g":"9cff10ff.07ea8","name":"","topic":"","qos":"","retain":"","broker":"","x":970,"y":1820,"wires":[]}]

Flow Info

Created 3 years, 8 months ago
Rating: 5 2

Owner

Actions

Rate:

Node Types

Core
  • catch (x1)
  • comment (x1)
  • debug (x1)
  • function (x6)
  • join (x1)
  • mqtt out (x1)
Other

Tags

  • Telgram
  • MQTT
  • Inline-Keyboard
  • Home-Automation
  • ESP32
  • Raspberry-Pi
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option