Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
View this page
Toggle table of contents sidebar

# `arbitrarycallbackdatabot.py`

      1#!/usr/bin/env python
      2# pylint: disable=unused-argument
      3# This program is dedicated to the public domain under the CC0 license.
      4
      5"""This example showcases how PTBs "arbitrary callback data" feature can be used.
      6
      7For detailed info on arbitrary callback data, see the wiki page at
      8https://github.com/python-telegram-bot/python-telegram-bot/wiki/Arbitrary-callback_data
      9
     10Note:
     11To use arbitrary callback data, you must install PTB via
     12`pip install "python-telegram-bot[callback-data]"`
     13"""
     14import logging
     15from typing import cast
     16
     17from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
     18from telegram.ext import (
     19    Application,
     20    CallbackQueryHandler,
     21    CommandHandler,
     22    ContextTypes,
     23    InvalidCallbackData,
     24    PicklePersistence,
     25)
     26
     27# Enable logging
     28logging.basicConfig(
     29    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
     30)
     31# set higher logging level for httpx to avoid all GET and POST requests being logged
     32logging.getLogger("httpx").setLevel(logging.WARNING)
     33
     34logger = logging.getLogger(__name__)
     35
     36
     37async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     38    """Sends a message with 5 inline buttons attached."""
     39    number_list: list[int] = []
     40    await update.message.reply_text("Please choose:", reply_markup=build_keyboard(number_list))
     41
     42
     43async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     44    """Displays info on how to use the bot."""
     45    await update.message.reply_text(
     46        "Use /start to test this bot. Use /clear to clear the stored data so that you can see "
     47        "what happens, if the button data is not available. "
     48    )
     49
     50
     51async def clear(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     52    """Clears the callback data cache"""
     53    context.bot.callback_data_cache.clear_callback_data()
     54    context.bot.callback_data_cache.clear_callback_queries()
     55    await update.effective_message.reply_text("All clear!")
     56
     57
     58def build_keyboard(current_list: list[int]) -> InlineKeyboardMarkup:
     59    """Helper function to build the next inline keyboard."""
     60    return InlineKeyboardMarkup.from_column(
     61        [InlineKeyboardButton(str(i), callback_data=(i, current_list)) for i in range(1, 6)]
     62    )
     63
     64
     65async def list_button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     66    """Parses the CallbackQuery and updates the message text."""
     67    query = update.callback_query
     68    await query.answer()
     69    # Get the data from the callback_data.
     70    # If you're using a type checker like MyPy, you'll have to use typing.cast
     71    # to make the checker get the expected type of the callback_data
     72    number, number_list = cast(tuple[int, list[int]], query.data)
     73    # append the number to the list
     74    number_list.append(number)
     75
     76    await query.edit_message_text(
     77        text=f"So far you've selected {number_list}. Choose the next item:",
     78        reply_markup=build_keyboard(number_list),
     79    )
     80
     81    # we can delete the data stored for the query, because we've replaced the buttons
     82    context.drop_callback_data(query)
     83
     84
     85async def handle_invalid_button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     86    """Informs the user that the button is no longer available."""
     87    await update.callback_query.answer()
     88    await update.effective_message.edit_text(
     89        "Sorry, I could not process this button click 😕 Please send /start to get a new keyboard."
     90    )
     91
     92
     93def main() -> None:
     94    """Run the bot."""
     95    # We use persistence to demonstrate how buttons can still work after the bot was restarted
     96    persistence = PicklePersistence(filepath="arbitrarycallbackdatabot")
     97    # Create the Application and pass it your bot's token.
     98    application = (
     99        Application.builder()
    100        .token("TOKEN")
    101        .persistence(persistence)
    102        .arbitrary_callback_data(True)
    103        .build()
    104    )
    105
    106    application.add_handler(CommandHandler("start", start))
    107    application.add_handler(CommandHandler("help", help_command))
    108    application.add_handler(CommandHandler("clear", clear))
    109    application.add_handler(
    110        CallbackQueryHandler(handle_invalid_button, pattern=InvalidCallbackData)
    111    )
    112    application.add_handler(CallbackQueryHandler(list_button))
    113
    114    # Run the bot until the user presses Ctrl-C
    115    application.run_polling(allowed_updates=Update.ALL_TYPES)
    116
    117
    118if __name__ == "__main__":
    119    main()