2021-06-06 10:37:53 +02:00
|
|
|
#!/usr/bin/env python
|
2023-09-09 23:12:30 +02:00
|
|
|
# pylint: disable=unused-argument
|
2021-06-06 10:37:53 +02:00
|
|
|
# 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.
|
|
|
|
"""
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
import logging
|
2021-06-06 10:37:53 +02:00
|
|
|
from collections import defaultdict
|
2024-10-24 20:48:49 +02:00
|
|
|
from typing import Optional
|
2021-06-06 10:37:53 +02:00
|
|
|
|
2022-05-05 09:27:54 +02:00
|
|
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
2021-10-19 18:28:19 +02:00
|
|
|
from telegram.constants import ParseMode
|
2021-06-06 10:37:53 +02:00
|
|
|
from telegram.ext import (
|
2022-05-05 09:27:54 +02:00
|
|
|
Application,
|
2021-06-06 10:37:53 +02:00
|
|
|
CallbackContext,
|
|
|
|
CallbackQueryHandler,
|
2022-05-05 09:27:54 +02:00
|
|
|
CommandHandler,
|
|
|
|
ContextTypes,
|
2021-10-09 13:56:50 +02:00
|
|
|
ExtBot,
|
2022-05-05 09:27:54 +02:00
|
|
|
TypeHandler,
|
2021-06-06 10:37:53 +02:00
|
|
|
)
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
# Enable logging
|
|
|
|
logging.basicConfig(
|
|
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
|
|
|
)
|
2023-06-07 22:32:04 +02:00
|
|
|
# set higher logging level for httpx to avoid all GET and POST requests being logged
|
|
|
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2021-06-06 10:37:53 +02:00
|
|
|
|
|
|
|
class ChatData:
|
|
|
|
"""Custom class for chat_data. Here we store data per message."""
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
2024-10-24 20:48:49 +02:00
|
|
|
self.clicks_per_message: defaultdict[int, int] = defaultdict(int)
|
2021-06-06 10:37:53 +02:00
|
|
|
|
|
|
|
|
2021-10-09 13:56:50 +02:00
|
|
|
# The [ExtBot, dict, ChatData, dict] is for type checkers like mypy
|
|
|
|
class CustomContext(CallbackContext[ExtBot, dict, ChatData, dict]):
|
2021-06-06 10:37:53 +02:00
|
|
|
"""Custom class for context."""
|
|
|
|
|
2023-05-18 07:57:59 +02:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
application: Application,
|
|
|
|
chat_id: Optional[int] = None,
|
|
|
|
user_id: Optional[int] = None,
|
|
|
|
):
|
2022-06-03 16:55:36 +02:00
|
|
|
super().__init__(application=application, chat_id=chat_id, user_id=user_id)
|
2021-06-06 10:37:53 +02:00
|
|
|
self._message_id: Optional[int] = None
|
|
|
|
|
|
|
|
@property
|
2024-10-24 20:48:49 +02:00
|
|
|
def bot_user_ids(self) -> set[int]:
|
2021-06-06 10:37:53 +02:00
|
|
|
"""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:
|
2021-10-19 18:28:19 +02:00
|
|
|
raise RuntimeError("There is no message associated with this context object.")
|
2021-06-06 10:37:53 +02:00
|
|
|
self.chat_data.clicks_per_message[self._message_id] = value
|
|
|
|
|
|
|
|
@classmethod
|
2022-04-24 12:38:09 +02:00
|
|
|
def from_update(cls, update: object, application: "Application") -> "CustomContext":
|
2021-06-06 10:37:53 +02:00
|
|
|
"""Override from_update to set _message_id."""
|
|
|
|
# Make sure to call super()
|
2022-04-24 12:38:09 +02:00
|
|
|
context = super().from_update(update, application)
|
2021-06-06 10:37:53 +02:00
|
|
|
|
|
|
|
if context.chat_data and isinstance(update, Update) and update.effective_message:
|
2021-10-08 08:17:00 +02:00
|
|
|
# pylint: disable=protected-access
|
|
|
|
context._message_id = update.effective_message.message_id
|
2021-06-06 10:37:53 +02:00
|
|
|
|
|
|
|
# Remember to return the object
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
async def start(update: Update, context: CustomContext) -> None:
|
2021-06-06 10:37:53 +02:00
|
|
|
"""Display a message with a button."""
|
2022-04-24 12:38:09 +02:00
|
|
|
await update.message.reply_html(
|
2021-06-06 10:37:53 +02:00
|
|
|
"This button was clicked <i>0</i> times.",
|
|
|
|
reply_markup=InlineKeyboardMarkup.from_button(
|
|
|
|
InlineKeyboardButton(text="Click me!", callback_data="button")
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
async def count_click(update: Update, context: CustomContext) -> None:
|
2021-06-06 10:37:53 +02:00
|
|
|
"""Update the click count for the message."""
|
|
|
|
context.message_clicks += 1
|
2022-04-24 12:38:09 +02:00
|
|
|
await update.callback_query.answer()
|
|
|
|
await update.effective_message.edit_text(
|
2021-06-06 10:37:53 +02:00
|
|
|
f"This button was clicked <i>{context.message_clicks}</i> times.",
|
|
|
|
reply_markup=InlineKeyboardMarkup.from_button(
|
|
|
|
InlineKeyboardButton(text="Click me!", callback_data="button")
|
|
|
|
),
|
|
|
|
parse_mode=ParseMode.HTML,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
async def print_users(update: Update, context: CustomContext) -> None:
|
2021-06-06 10:37:53 +02:00
|
|
|
"""Show which users have been using this bot."""
|
2022-04-24 12:38:09 +02:00
|
|
|
await update.message.reply_text(
|
2023-09-22 18:19:21 +02:00
|
|
|
f"The following user IDs have used this bot: {', '.join(map(str, context.bot_user_ids))}"
|
2021-06-06 10:37:53 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-04-24 12:38:09 +02:00
|
|
|
async def track_users(update: Update, context: CustomContext) -> None:
|
2021-06-06 10:37:53 +02:00
|
|
|
"""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)
|
2022-04-24 12:38:09 +02:00
|
|
|
application = Application.builder().token("TOKEN").context_types(context_types).build()
|
2021-06-06 10:37:53 +02:00
|
|
|
|
|
|
|
# run track_users in its own group to not interfere with the user handlers
|
2022-04-24 12:38:09 +02:00
|
|
|
application.add_handler(TypeHandler(Update, track_users), group=-1)
|
|
|
|
application.add_handler(CommandHandler("start", start))
|
|
|
|
application.add_handler(CallbackQueryHandler(count_click))
|
|
|
|
application.add_handler(CommandHandler("print_users", print_users))
|
2021-06-06 10:37:53 +02:00
|
|
|
|
2023-06-04 17:11:58 +02:00
|
|
|
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
2021-06-06 10:37:53 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|