Hide navigation sidebar
Hide table of contents sidebar
PTB has undergone significant changes in v20. Please read the documentation carefully and also check out the transition guide in the wiki.
Back to top
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 List, Tuple, 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()