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