Telegram Inline keyboard

Hi all!

Actually i have been using a custom Telegram "menu" in a simple way. I mean, a command node, and some functions to work together.

An example in the following screen shot.

image

The code:

[
    {
        "id": "a006ca3c.bf57f8",
        "type": "function",
        "z": "31d2354e.c7d47a",
        "name": "confirmation message",
        "func": "var opts = {\n  reply_markup: JSON.stringify({\n    keyboard: [\n      ['Bajar peliculas'],\n      ['Iluminacion'],\n      ['Resetear Sonoffs'],\n      ['Versiones Sonoffs'],\n      ['Listar dispositivos en red local'],\n      ['Ip publica'],\n      ['Control de VMs'],\n      ['Salir'],\n      ],\n      'resize_keyboard' : true, \n      'remove_keyboard' : true\n  })\n};\n\nmsg.payload.content = 'Selecciona una opción:';\nmsg.payload.options = opts;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 400,
        "y": 100,
        "wires": [
            [
                "3737123ab3432afb"
            ]
        ]
    },
    {
        "id": "b9a949ef.104a98",
        "type": "function",
        "z": "31d2354e.c7d47a",
        "name": "create response",
        "func": "if(msg.payload.content === 'Bajar peliculas')\n{\n    msg.payload.content = '/torrents';\n    return [msg, null];   \n}\nif(msg.payload.content === 'Iluminacion')\n{\n    msg.payload.content = '/iluminacion';\n    return [msg, null];\n}\nif(msg.payload.content === 'Resetear Sonoffs')\n{\n    msg.payload.content = '/resetsonoff';\n    return [msg, null];\n}\nif(msg.payload.content === 'Versiones Sonoffs')\n{\n    msg.payload.content = 'Chequeando versiones Tasmota en los sonoffs...';\n    return [msg, null];\n}\nif(msg.payload.content === 'Listar dispositivos en red local')\n{\n    msg.payload.content = 'Listar dispositivos en red local';\n    return [msg, null];\n}\nif(msg.payload.content === 'Ip publica')\n{\n    msg.payload.content = 'Procesando ip publica...';\n    return [msg, null];\n}\nif(msg.payload.content === 'Control de VMs')\n{\n    msg.payload.content = '/vmhosts';\n    return [msg, null];\n}\nif(msg.payload.content === 'Salir')\n{\n    msg.payload.content = 'Adios!';\n    return [msg, null];\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 380,
        "y": 140,
        "wires": [
            [
                "b0de20d8.017bf",
                "6cf94e31.1f1bf"
            ]
        ]
    },
    {
        "id": "b0de20d8.017bf",
        "type": "delay",
        "z": "31d2354e.c7d47a",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "outputs": 1,
        "x": 360,
        "y": 180,
        "wires": [
            [
                "e9f67f2c.ee1b9"
            ]
        ]
    },
    {
        "id": "e9f67f2c.ee1b9",
        "type": "switch",
        "z": "31d2354e.c7d47a",
        "name": "",
        "property": "payload.content",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "/torrents",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "Listar dispositivos en red local",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "Procesando ip publica...",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "/vmhosts",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "Chequeando versiones Tasmota en los sonoffs...",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "Adios!",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 6,
        "x": 350,
        "y": 260,
        "wires": [
            [],
            [
                "51de7222f6db2ec3"
            ],
            [
                "0a99c7f2e11239e4"
            ],
            [
                "ad18cef9d91611dc"
            ],
            [
                "dd4a98e61e40e2b9"
            ],
            [
                "e7e65dd3fcb43324"
            ]
        ]
    }
]

How can i improve this?.
Is there any way to make a Menu with inline keyboard?.
suggestions?.

Thanks a lot!.

Regards.

Anyone?.

Thanks!

Have you looked at the two examples in the node-red examples library. Click import > examples > node-red-contrib-telegrambot.

Yes i did. The thing is that i cannot make ir work with a Telegram command, those two examples are without a telegram command, so the Telegram node reply is quite different from each other.

Regards!

Then show the flow you can not get to work, as no one can help if you do not show any issues. I would of thought that you would just attach your command node to the example, where the inject is.
Their differences are one is inline keyboard the other is keyboard.

I've shared the flow from the first post, but i can upload fully if you need it.

Regards!

You shared 2 function nodes and a switch with a 1 second delay, I saw no telegram nodes.

The function shows you are using keyboard style, if you want inline keyboards use example 2 (inline keyboard)from library examples. With Callback query node you can create a response to the selection or update the inline keyboard.

Yeah ok. The problem with that comes out when you have to call a message from Telegram. For example, you have a /menu message and then several options, you select one and then continue the flow to do whatever you need. That chain is hard to work (i don't know how to do it).

Maybe a node example will be helpful.

Tks!

It shows you in example 2, the message property msg.payload.content outputted from the callback node holds the value of the callback_data selected, in th example 2 either "1" or "2". The following function or switch node you can be use that value to decide which option you selected.
Here is that example with the command on front and a switch to select between options, so all I added was a command node and a switch node, I also remover the hard coded chatid etc from first function node, as the command node passes those values on.

[{"id":"aa2e75d.cbd4488","type":"telegram command","z":"b779de97.b1b46","name":"","command":"/menu","description":"","registercommand":false,"language":"","scope":"default","bot":"125415a2.b4e0e2","strict":false,"hasresponse":true,"useregex":false,"removeregexcommand":false,"outputs":2,"x":760,"y":1400,"wires":[["f97878f7.de1bf8"],[]]},{"id":"f97878f7.de1bf8","type":"function","z":"b779de97.b1b46","name":"build keyboard","func":"var opts = {\n  reply_markup: JSON.stringify({\n    \"inline_keyboard\": [[\n                {\n                    \"text\": \"A1\",\n                    \"callback_data\": \"1\"            \n                }, \n                {\n                    \"text\": \"A2\",\n                    \"callback_data\": \"2\"            \n                }]\n            ]\n  })\n};\n\nmsg.payload.content = \"Selection?\";\nmsg.payload.options = opts;\nmsg.payload.type = \"message\";\n\nreturn [ msg ];\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":1400,"wires":[["f7c18f49.063c4","67925ff5.4c7c9"]]},{"id":"f7c18f49.063c4","type":"telegram sender","z":"b779de97.b1b46","name":"","bot":"125415a2.b4e0e2","haserroroutput":false,"outputs":1,"x":1260,"y":1400,"wires":[[]]},{"id":"67925ff5.4c7c9","type":"debug","z":"b779de97.b1b46","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1220,"y":1360,"wires":[]},{"id":"385615779f4b3364","type":"function","z":"b779de97.b1b46","name":"answerCallbackQuery","func":"var text = \"Win - You clicked \" + msg.payload.content;\nvar options = {\n    // text : text, <-- you can set the text here or use msg.payload.content \n    show_alert : true,\n    cache_time : 10\n};\n\nmsg.payload.content = text;\nmsg.payload.options = options;\nmsg.payload.type = \"answerCallbackQuery\";\n\nreturn [ msg ];\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1200,"y":1460,"wires":[["f7c18f49.063c4"]]},{"id":"652dce9e.9bede","type":"function","z":"b779de97.b1b46","name":"answerCallbackQuery","func":"var text = \"Lose - You clicked \" + msg.payload.content;\nmsg.payload.content = text;\n\nmsg.payload.type = \"message\";\n\nreturn [ msg ];\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1200,"y":1500,"wires":[["f7c18f49.063c4"]]},{"id":"3b460e58.b701ba","type":"switch","z":"b779de97.b1b46","name":"","property":"payload.content","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"2","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":980,"y":1480,"wires":[["385615779f4b3364"],["652dce9e.9bede"]]},{"id":"981d4537.1ced18","type":"telegram event","z":"b779de97.b1b46","name":"","bot":"125415a2.b4e0e2","event":"callback_query","autoanswer":false,"x":770,"y":1482,"wires":[["3b460e58.b701ba"]]},{"id":"125415a2.b4e0e2","type":"telegram bot","botname":"mybot","usernames":"","chatids":"-1001361034082","baseapiurl":"","updatemode":"polling","pollinterval":"300","usesocks":false,"sockshost":"","socksport":"6667","socksusername":"anonymous","sockspassword":"","bothost":"","botpath":"","localbotport":"8443","publicbotport":"8443","privatekey":"","certificate":"","useselfsignedcertificate":false,"sslterminated":false,"verboselogging":false}]

The API with more info, all methods and types can be actioned using http request nodes to, using the rest API.

Ok, great approach there and i've tested that before so thanks a lot!.

But....what about to have a secondary menu?, for example, Seleccion A, then Selection Ab, then Selection Ac and so on. That's the part i've been fooling around...

The main idea is to have multiple menu selections.

Thanks a lot!

I have implemented something similar in my home-automation system.
Here's part of the UI - to turn lights On/Off using two-level in-line keyboard.
telegram_AA
telegram_B


Here's the Javascript in the 'Turn lights' function block.


// Set the required access for this function here
var checklevel = 6;
var opts = {};

// Handle the help request
if (msg.originalMessage.help) {
    // only add the help command if the user has access to execute it
    if (msg.originalMessage.from.accesslevel >= checklevel) {
        msg.payload.content = msg.payload.content + "\r\n\r\nturn:\r\nSwitch lights on and off";  
    }
    return [null,null,msg];
}

// Check if this is a keyboard message
if (context.get("keyboard_turn")) {
    
    // Is this is a second keyboard request for the On/Off state ??
    if ((msg.payload.content==="On")||(msg.payload.content==="Off")) {
        context.set("keyboard_turn",false);
        msg.originalMessage.thing = context.get("thing");
        if (msg.payload.content==="On") {
            msg.originalMessage.state = "1";
        }
        if (msg.payload.content==="Off") {
            msg.originalMessage.state = "0";
        }
        msg.payload.content = "Turning light "+ msg.payload.content.toLowerCase();
    } else {
    
        // The response was NOT on or off, so this is only the first keyboard return
        
        // Parse the lights
        switch (msg.payload.content) {
            case "Home security Telegram reports":
                context.set("thing","telegram");
                break;
            case "IKEA light":
                context.set("thing","ikea");
                break;
            case "Herb Garden":
                context.set("thing","herb");
                break;
            case "House Front":
                context.set("thing","house");
                break;
            case "Print Server":
                context.set("thing","printer");
                break;
            default:
                // Not a recognized lamp type
                msg.payload.content="This option is not allowed";
                context.set("keyboard_turn",false);
                context.set("thing","");
        }
        if (context.get("thing")!=="") {
            opts = {
                reply_to_message_id: msg.payload.messageId,
                reply_markup: JSON.stringify({
                    keyboard: [
                        ['On'],
                        ['Off']],
                        'resize_keyboard': true,
                        'one_time_keyboard': true
                })
            };
            msg.payload.options = opts; 
            msg.payload.content = "Select the new state:";
        }
    }
    return [msg,null,null];  
}

// Check if the current message contains this function
if (msg.originalMessage.command==="switch") {
    // check if the user has access to execute this function
    if (msg.originalMessage.from.accesslevel >= checklevel) {
        if (msg.originalMessage.state===undefined) {
            // The state could not be detected, showing options
            opts = {
                reply_to_message_id: msg.payload.messageId,
                reply_markup: JSON.stringify({
                    keyboard: [
                        ['Home security Telegram reports'],
                        ['IKEA light'],
                        ['Herb Garden'],
                        ['House Front'],
                        ['Print Server']],
                        'resize_keyboard': true,
                        'one_time_keyboard': true
                })
            };
            msg.payload.content = "Select a light:";
            msg.payload.options = opts;  
            context.set("keyboard_turn",true);
        } else {
            msg.payload.content = "Turning "+msg.originalMessage.thing +" (light) "+msg.originalMessage.state;
        }
        return [msg,null,null];  
    } else {
        return [null,msg,null];
    }
} else {
    // pass the message to the next node
    return [null,null,msg];
}

The above piece of JavaScript was originally taken from Csongor Varga's Youtube video about Telegram - Chat Integration and then heavily modified to match my home situation.

Then create a secondary menu after the callback node and send it to sender node, or if you want the different commands to create different menus, pass though switch after the command node and create the menu for each command, or do it in the function.

Thanks a lot I got it working with some tweaks.

Regards!

Would you care to publish your flow, so other people can see a possible solution?

Oh yes! Of course, my bad.

This is a basic example with a menu and a sub menu.

[
    {
        "id": "146caad9ed676760",
        "type": "telegram command",
        "z": "59dc13f283450f9c",
        "name": "",
        "command": "/menu",
        "description": "",
        "registercommand": false,
        "language": "",
        "scope": "default",
        "bot": "",
        "strict": false,
        "hasresponse": true,
        "useregex": false,
        "removeregexcommand": false,
        "outputs": 2,
        "x": 90,
        "y": 120,
        "wires": [
            [
                "aa6745504cf68296"
            ],
            []
        ]
    },
    {
        "id": "aa6745504cf68296",
        "type": "function",
        "z": "59dc13f283450f9c",
        "name": "build keyboard",
        "func": "var opts = {\n  reply_markup: JSON.stringify({\n    \"inline_keyboard\": [[\n                {\n                    \"text\": \"option\",\n                    \"callback_data\": \"option\"            \n                }\n                ]\n            ]\n  })\n};\n\nmsg.payload.content = \"Elija una opción: \";\nmsg.payload.options = opts;\nmsg.payload.type = \"message\";\n\nreturn [ msg ];\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 400,
        "y": 120,
        "wires": [
            [
                "18638dee03ff7b2e"
            ]
        ]
    },
    {
        "id": "18638dee03ff7b2e",
        "type": "telegram sender",
        "z": "59dc13f283450f9c",
        "name": "",
        "bot": "",
        "haserroroutput": false,
        "outputs": 1,
        "x": 990,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "648a46aa05186572",
        "type": "switch",
        "z": "59dc13f283450f9c",
        "name": "menu options from \"build keyboard call_data\"",
        "property": "payload.content",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "option",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 490,
        "y": 200,
        "wires": [
            [
                "d9ebd8cef2fe7354"
            ]
        ]
    },
    {
        "id": "e3d1473eefad76bf",
        "type": "telegram event",
        "z": "59dc13f283450f9c",
        "name": "callback menu principal",
        "bot": "",
        "event": "callback_query",
        "autoanswer": false,
        "x": 140,
        "y": 200,
        "wires": [
            [
                "648a46aa05186572"
            ]
        ]
    },
    {
        "id": "d9ebd8cef2fe7354",
        "type": "function",
        "z": "59dc13f283450f9c",
        "name": "menu options",
        "func": "var opts = {\n  reply_markup: JSON.stringify({\n    \"inline_keyboard\": [[\n                {\n                    \"text\": \"sub option\",\n                    \"callback_data\": \"sub option\"            \n                }]\n            ]\n  })\n};\n\nmsg.payload.content = \"Elija una opción: \";\nmsg.payload.options = opts;\nmsg.payload.type = \"message\";\n\nreturn [ msg ];\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 400,
        "y": 240,
        "wires": [
            [
                "18638dee03ff7b2e"
            ]
        ]
    },
    {
        "id": "2734baede7850e6c",
        "type": "telegram event",
        "z": "59dc13f283450f9c",
        "name": "callback",
        "bot": "",
        "event": "callback_query",
        "autoanswer": false,
        "x": 100,
        "y": 320,
        "wires": [
            [
                "bf9b0dc95dd68e8e"
            ]
        ]
    },
    {
        "id": "bf9b0dc95dd68e8e",
        "type": "switch",
        "z": "59dc13f283450f9c",
        "name": "sub option",
        "property": "payload.content",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "sub option",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 390,
        "y": 320,
        "wires": [
            []
        ]
    }
]

One last question.

is it possible to auto adjust the buttons that Telegram shows?. In my mobile phone they show a liitle tight.

Regards!

The arrays in side initial array are rows, so.
[[{},{}]] is one row of two buttons
So it think, not tested
[[{},{}],[{},{}]]
Would be two rows of two buttons.
Not 100% sure though.

mmm that doesn't work i think. Maybe you have an example?.

In fact, i've broken the Json file, luckly i had a backup.

Regards!