Satria
Satria

Reputation: 131

how to make Telegram bot dynamic keyboardbutton in python every button on one row

I want to make a keyboard button(replykeyboardmarkup) that reads data from the database and every button on every row of the keyboard to show.

I want to create a custom keyboard in telegram bot. For example: We have an array of strings that we get from the database. How we can push data from array to InlineKeyboardMarkup? Dynamic, responsive button.

What can I do?

Upvotes: 13

Views: 91342

Answers (8)

mostafa-hajian
mostafa-hajian

Reputation: 53

use python telegram bot

dictionary = {"Name": "John", "Language": "Python", "API": "pyTelegramBotAPI"}

buttons = []

for key, value in dictionary.items():
    buttons.append(
    [InlineKeyboardButton(text = key, url = 'google.com')]
    )
keyboard = InlineKeyboardMarkup(buttons)
bot.sendMessage(chat_id,text = 'f', reply_markup = keyboard)

Upvotes: 3

Dmitry Artamonov
Dmitry Artamonov

Reputation: 11

I had a text file, each line of which was a button. You can just use any array like so:

markup_reply = types.ReplyKeyboardMarkup(resize_keyboard = True)
for line in open('comments.txt', encoding='utf-8').readlines():
    markup_reply.row(types.KeyboardButton(line))
bot.send_message(message.chat.id, 'message', reply_markup=markup_reply)

Upvotes: 1

Luca Motta
Luca Motta

Reputation: 331

lib: python-telegram-bot

Considering the other answers provide info about how to create and delete InlineKeyboardButton, following how to edit them in a "dynamic, responsive" manner using edit_message_reply_markup method:

from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
from random import seed
from random import random

...

def start(bot, update):
    keyboard = [
                [InlineKeyboardButton("Click button 1", callback_data='callback_1')],
                [InlineKeyboardButton("Click button 1", callback_data='callback_2')]
            ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    message_reply_text = 'Click one of these buttons'
    update.message.reply_text(message_reply_text, reply_markup=reply_markup)

def press_button_callback(bot, update):
    seed(1)
    random_number = str(random())
    keyboard = [
                [InlineKeyboardButton('Click button 1' , callback_data='callback_1')],
                [InlineKeyboardButton('Click button 1 ' + random_number, callback_data='callback_2')]
            ]

    reply_markup = InlineKeyboardMarkup(keyboard)
    update.callback_query.edit_message_reply_markup(reply_markup)

def create_updater():
   updater = Updater('XXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
   updater.dispatcher.add_handler(CommandHandler('start', start))
   updater.dispatcher.add_handler(CallbackQueryHandler(press_button_callback))
   updater.dispatcher.add_error_handler(error)
return updater

...

Upvotes: 2

Amit Ghosh
Amit Ghosh

Reputation: 1606

For just one button, try this -

import requests

headers = {
    'Content-Type': 'application/json',
}

data = '{"chat_id":"@entropytrades", "text":"Testing", "reply_markup": {"inline_keyboard": [[{"text":"Visit Unofficed", "url": "http://unofficed.com"}]]} }'

response = requests.post('https://api.telegram.org/bot$apiToken/sendMessage', headers=headers, data=data)

Upvotes: 1

Saravanan Manoharan
Saravanan Manoharan

Reputation: 371

This is using python-telegram-bot:

If you are having a list of cities and you want to display like this:

Telegram bot displaying the list of cities in a row

def city(update,context):
  list_of_cities = ['Erode','Coimbatore','London', 'Thunder Bay', 'California']
  button_list = []
  for each in list_of_cities:
     button_list.append(InlineKeyboardButton(each, callback_data = each))
  reply_markup=InlineKeyboardMarkup(build_menu(button_list,n_cols=1)) #n_cols = 1 is for single column and mutliple rows
  bot.send_message(chat_id=update.message.chat_id, text='Choose from the following',reply_markup=reply_markup)


def build_menu(buttons,n_cols,header_buttons=None,footer_buttons=None):
  menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)]
  if header_buttons:
    menu.insert(0, header_buttons)
  if footer_buttons:
    menu.append(footer_buttons)
  return menu

If you need any clarification comment below!

Upvotes: 10

Kushan Gunasekera
Kushan Gunasekera

Reputation: 8556

In the Telegram Bot API they didn't mention about how to deal with dynamic InlineKeyboardButton. But there is a way to do this kind of things using CallbackQuery. In the following example assume stringList variable will be hold the database query results, to do this I am using stringList as a Pyton Dictionary with Python 3.7 and pyTelegramBotAPI which is implement for the Telegram Bot API.

stringList = {"Name": "John", "Language": "Python", "API": "pyTelegramBotAPI"}

Let's create buttons according to stringList by importing types from telebot. In here callback_data fulfill with a List which helps us to pass the data from button to query handlers. There is a limitation, if your callback_data is more than 64 bytes Telegram Bot API will generate BUTTON_DATA_INVALID respond. Please refer more about Markup Errors to avoid those kind of exceptions.

markup = types.InlineKeyboardMarkup()

for key, value in stringList.items():
    markup.add(types.InlineKeyboardButton(text=value,
                                          callback_data="['value', '" + value + "', '" + key + "']"),
               types.InlineKeyboardButton(text=crossIcon,
                                          callback_data="['key', '" + key + "']"))

Below image is the result of above code.

Result of the InlineKeyboardButton

I am using this cross icon to show you how to delete dynamic button and how to edit the InlineKeyboardButton with removing that dynamic button. As you can see (above code) there are two CallbackQuery which handle the first button and the cross button.

You can capture first button's callback_data in following code lines.

if (call.data.startswith("['value'")):
    print(f"call.data : {call.data} , type : {type(call.data)}")
    print(f"ast.literal_eval(call.data) : {ast.literal_eval(call.data)} , type : {type(ast.literal_eval(call.data))}")
    valueFromCallBack = ast.literal_eval(call.data)[1]
    keyFromCallBack = ast.literal_eval(call.data)[2]
    bot.answer_callback_query(callback_query_id=call.id, show_alert=True,
                              text="You Clicked " + valueFromCallBack + " and key is " + keyFromCallBack)

There are two print statements to see exactly which type of data that we are going to handle. First print statement shows us the string type List when we click the first button of John.

call.data : ['value', 'John', 'Name'] , type : <class 'str'>

We are almost closer to the solution, but with the string type List. I found a solution to convert string type List into the normal List by referring answers of this question. Python ast(Abstract Syntax Trees) is the module which representation of the abstract syntactic structure of source code. Using ast.literal_eval() we can get the following output from the second print statement.

ast.literal_eval(call.data) : ['value', 'John', 'Name'] , type : <class 'list'>

Meantime display this alert as well becasue of answerCallbackQuery which we are using in the above code.

Result of the answerCallbackQuery

We can get output like this because we fill the button with List of values. Likewise you can pass the List inside callback_data and handle that List in callback_query_handler. Let's take a look at what will happens when click the cross icon.

if (call.data.startswith("['key'")):
    keyFromCallBack = ast.literal_eval(call.data)[1]
    del stringList[keyFromCallBack]
    bot.edit_message_text(chat_id=call.message.chat.id,
                          text="Here are the values of stringList", message_id=call.message.message_id,
                          reply_markup=makeKeyboard(), parse_mode='HTML')

It will go to answerCallbackQuery which start with " '[key' " and delete the Pyton Dictionary by using the given key (del stringList[keyFromCallBack]). Let's click first cross icon see what will happen.

Regenerate dynamic buttons according to the List

First button will be disappeared because editMessageText regenerate with available Dictionary details. Instead of deleting Pyton Dictionary you can call some database query.

Here is the full code of the above examples.

import telebot
import ast
import time
from telebot import types

bot = telebot.TeleBot("YOUR_BOT_API_KEY_HERE")

stringList = {"Name": "John", "Language": "Python", "API": "pyTelegramBotAPI"}
crossIcon = u"\u274C"

def makeKeyboard():
    markup = types.InlineKeyboardMarkup()

    for key, value in stringList.items():
        markup.add(types.InlineKeyboardButton(text=value,
                                              callback_data="['value', '" + value + "', '" + key + "']"),
        types.InlineKeyboardButton(text=crossIcon,
                                   callback_data="['key', '" + key + "']"))

    return markup

@bot.message_handler(commands=['test'])
def handle_command_adminwindow(message):
    bot.send_message(chat_id=message.chat.id,
                     text="Here are the values of stringList",
                     reply_markup=makeKeyboard(),
                     parse_mode='HTML')

@bot.callback_query_handler(func=lambda call: True)
def handle_query(call):

    if (call.data.startswith("['value'")):
        print(f"call.data : {call.data} , type : {type(call.data)}")
        print(f"ast.literal_eval(call.data) : {ast.literal_eval(call.data)} , type : {type(ast.literal_eval(call.data))}")
        valueFromCallBack = ast.literal_eval(call.data)[1]
        keyFromCallBack = ast.literal_eval(call.data)[2]
        bot.answer_callback_query(callback_query_id=call.id,
                              show_alert=True,
                              text="You Clicked " + valueFromCallBack + " and key is " + keyFromCallBack)

    if (call.data.startswith("['key'")):
        keyFromCallBack = ast.literal_eval(call.data)[1]
        del stringList[keyFromCallBack]
        bot.edit_message_text(chat_id=call.message.chat.id,
                              text="Here are the values of stringList",
                              message_id=call.message.message_id,
                              reply_markup=makeKeyboard(),
                              parse_mode='HTML')
while True:
    try:
        bot.polling(none_stop=True, interval=0, timeout=0)
    except:
        time.sleep(10)

To test this code type /test command in your bot window.

Upvotes: 24

neboduus
neboduus

Reputation: 418

If you are using python-telegram-bot, according to this snippets, it should be something like this

>>> custom_keyboard = [['top-left', 'top-right'], 
...                    ['bottom-left', 'bottom-right']]
>>> reply_markup = telegram.ReplyKeyboardMarkup(custom_keyboard)
>>> bot.send_message(chat_id=chat_id, 
...                  text="Custom Keyboard Test", 
...                  reply_markup=reply_markup)

Note that I did not check the code. Let me Know if is what you want.

Upvotes: 2

91DarioDev
91DarioDev

Reputation: 1690

If you are using python-telegram-bot as library you can take a look at the his documentation here

Also if you are not using that library, you could adapt that function on your code

Upvotes: 2

Related Questions