#!/usr/bin/env python # pylint: disable=C0116,W0613 # This program is dedicated to the public domain under the CC0 license. """ Simple Bot to showcase `telegram.ext.ContextTypes`. Usage: Press Ctrl-C on the command line or send a signal to the process to stop the bot. """ from collections import defaultdict from typing import DefaultDict, Optional, Set from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode from telegram.ext import ( Updater, CommandHandler, CallbackContext, ContextTypes, CallbackQueryHandler, TypeHandler, Dispatcher, ) class ChatData: """Custom class for chat_data. Here we store data per message.""" def __init__(self) -> None: self.clicks_per_message: DefaultDict[int, int] = defaultdict(int) # The [dict, ChatData, dict] is for type checkers like mypy class CustomContext(CallbackContext[dict, ChatData, dict]): """Custom class for context.""" def __init__(self, dispatcher: Dispatcher): super().__init__(dispatcher=dispatcher) self._message_id: Optional[int] = None @property def bot_user_ids(self) -> Set[int]: """Custom shortcut to access a value stored in the bot_data dict""" return self.bot_data.setdefault('user_ids', set()) @property def message_clicks(self) -> Optional[int]: """Access the number of clicks for the message this context object was built for.""" if self._message_id: return self.chat_data.clicks_per_message[self._message_id] return None @message_clicks.setter def message_clicks(self, value: int) -> None: """Allow to change the count""" if not self._message_id: raise RuntimeError('There is no message associated with this context obejct.') self.chat_data.clicks_per_message[self._message_id] = value @classmethod def from_update(cls, update: object, dispatcher: 'Dispatcher') -> 'CustomContext': """Override from_update to set _message_id.""" # Make sure to call super() context = super().from_update(update, dispatcher) if context.chat_data and isinstance(update, Update) and update.effective_message: context._message_id = update.effective_message.message_id # pylint: disable=W0212 # Remember to return the object return context def start(update: Update, context: CustomContext) -> None: """Display a message with a button.""" update.message.reply_html( 'This button was clicked 0 times.', reply_markup=InlineKeyboardMarkup.from_button( InlineKeyboardButton(text='Click me!', callback_data='button') ), ) def count_click(update: Update, context: CustomContext) -> None: """Update the click count for the message.""" context.message_clicks += 1 update.callback_query.answer() update.effective_message.edit_text( f'This button was clicked {context.message_clicks} times.', reply_markup=InlineKeyboardMarkup.from_button( InlineKeyboardButton(text='Click me!', callback_data='button') ), parse_mode=ParseMode.HTML, ) def print_users(update: Update, context: CustomContext) -> None: """Show which users have been using this bot.""" update.message.reply_text( 'The following user IDs have used this bot: ' f'{", ".join(map(str, context.bot_user_ids))}' ) def track_users(update: Update, context: CustomContext) -> None: """Store the user id of the incoming update, if any.""" if update.effective_user: context.bot_user_ids.add(update.effective_user.id) def main() -> None: """Run the bot.""" context_types = ContextTypes(context=CustomContext, chat_data=ChatData) updater = Updater("TOKEN", context_types=context_types) dispatcher = updater.dispatcher # run track_users in its own group to not interfere with the user handlers dispatcher.add_handler(TypeHandler(Update, track_users), group=-1) dispatcher.add_handler(CommandHandler("start", start)) dispatcher.add_handler(CallbackQueryHandler(count_click)) dispatcher.add_handler(CommandHandler("print_users", print_users)) updater.start_polling() updater.idle() if __name__ == '__main__': main()