From 48fed9347a08404fa96a8f923b89c592a8c6b1e3 Mon Sep 17 00:00:00 2001 From: wulan17 Date: Sat, 6 Apr 2024 11:15:19 +0700 Subject: [PATCH] Pyrofork: Add UpdateBotNewBusinessMessage updates handler Signed-off-by: wulan17 --- docs/source/api/decorators.rst | 2 + docs/source/api/handlers.rst | 2 + pyrogram/dispatcher.py | 32 +++- pyrogram/handlers/__init__.py | 1 + .../handlers/bot_business_message_handler.py | 151 ++++++++++++++++++ pyrogram/methods/decorators/__init__.py | 2 + .../decorators/on_bot_business_message.py | 62 +++++++ pyrogram/types/messages_and_media/message.py | 7 + pyrogram/utils.py | 5 +- 9 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 pyrogram/handlers/bot_business_message_handler.py create mode 100644 pyrogram/methods/decorators/on_bot_business_message.py diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index 90a01cbe..c518891d 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -36,6 +36,7 @@ Index :columns: 3 - :meth:`~Client.on_message` + - :meth:`~Client.on_bot_business_message` - :meth:`~Client.on_edited_message` - :meth:`~Client.on_callback_query` - :meth:`~Client.on_message_reaction_updated` @@ -58,6 +59,7 @@ Details .. Decorators .. autodecorator:: pyrogram.Client.on_message() +.. autodecorator:: pyrogram.Client.on_bot_business_message() .. autodecorator:: pyrogram.Client.on_edited_message() .. autodecorator:: pyrogram.Client.on_callback_query() .. autodecorator:: pyrogram.Client.on_message_reaction_updated() diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index cfe99bbe..6b39a3e6 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -36,6 +36,7 @@ Index :columns: 3 - :class:`MessageHandler` + - :class:`BotBusinessMessageHandler` - :class:`EditedMessageHandler` - :class:`DeletedMessagesHandler` - :class:`CallbackQueryHandler` @@ -57,6 +58,7 @@ Details .. Handlers .. autoclass:: MessageHandler() +.. autoclass:: BotBusinessMessageHandler() .. autoclass:: EditedMessageHandler() .. autoclass:: DeletedMessagesHandler() .. autoclass:: CallbackQueryHandler() diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index 696e52b1..5e0e4416 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -25,10 +25,26 @@ from collections import OrderedDict import pyrogram from pyrogram import utils from pyrogram.handlers import ( - CallbackQueryHandler, MessageHandler, EditedMessageHandler, DeletedMessagesHandler, MessageReactionUpdatedHandler, MessageReactionCountUpdatedHandler, UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler, ConversationHandler, ChosenInlineResultHandler, ChatMemberUpdatedHandler, ChatJoinRequestHandler, StoryHandler + BotBusinessMessageHandler, + CallbackQueryHandler, + MessageHandler, + EditedMessageHandler, + DeletedMessagesHandler, + MessageReactionUpdatedHandler, + MessageReactionCountUpdatedHandler, + UserStatusHandler, + RawUpdateHandler, + InlineQueryHandler, + PollHandler, + ConversationHandler, + ChosenInlineResultHandler, + ChatMemberUpdatedHandler, + ChatJoinRequestHandler, + StoryHandler ) from pyrogram.raw.types import ( UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage, + UpdateBotNewBusinessMessage, UpdateEditMessage, UpdateEditChannelMessage, UpdateDeleteMessages, UpdateDeleteChannelMessages, UpdateBotCallbackQuery, UpdateInlineBotCallbackQuery, @@ -44,6 +60,7 @@ log = logging.getLogger(__name__) class Dispatcher: NEW_MESSAGE_UPDATES = (UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage) + NEW_BOT_BUSINESS_MESSAGE_UPDATES = (UpdateBotNewBusinessMessage,) EDIT_MESSAGE_UPDATES = (UpdateEditMessage, UpdateEditChannelMessage) DELETE_MESSAGES_UPDATES = (UpdateDeleteMessages, UpdateDeleteChannelMessages) CALLBACK_QUERY_UPDATES = (UpdateBotCallbackQuery, UpdateInlineBotCallbackQuery) @@ -77,6 +94,18 @@ class Dispatcher: MessageHandler ) + async def bot_business_message_parser(update, users, chats): + return ( + await pyrogram.types.Message._parse( + self.client, + update.message, + users, + chats, + business_connection_id=update.connection_id + ), + BotBusinessMessageHandler + ) + async def edited_message_parser(update, users, chats): # Edited messages are parsed the same way as new messages, but the handler is different parsed, _ = await message_parser(update, users, chats) @@ -154,6 +183,7 @@ class Dispatcher: self.update_parsers = { Dispatcher.NEW_MESSAGE_UPDATES: message_parser, + Dispatcher.NEW_BOT_BUSINESS_MESSAGE_UPDATES: bot_business_message_parser, Dispatcher.EDIT_MESSAGE_UPDATES: edited_message_parser, Dispatcher.DELETE_MESSAGES_UPDATES: deleted_messages_parser, Dispatcher.CALLBACK_QUERY_UPDATES: callback_query_parser, diff --git a/pyrogram/handlers/__init__.py b/pyrogram/handlers/__init__.py index ab918668..ae118ad6 100644 --- a/pyrogram/handlers/__init__.py +++ b/pyrogram/handlers/__init__.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrofork. If not, see . +from .bot_business_message_handler import BotBusinessMessageHandler from .callback_query_handler import CallbackQueryHandler from .chat_join_request_handler import ChatJoinRequestHandler from .chat_member_updated_handler import ChatMemberUpdatedHandler diff --git a/pyrogram/handlers/bot_business_message_handler.py b/pyrogram/handlers/bot_business_message_handler.py new file mode 100644 index 00000000..1c1b4bb3 --- /dev/null +++ b/pyrogram/handlers/bot_business_message_handler.py @@ -0,0 +1,151 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . +from inspect import iscoroutinefunction +from typing import Callable +import pyrogram + +from pyrogram.types import Message, Identifier + +from .handler import Handler + + +class BotBusinessMessageHandler(Handler): + """The Bot Business Message handler class. Used to handle new bot business messages. + It is intended to be used with :meth:`~pyrogram.Client.add_handler` + + For a nicer way to register this handler, have a look at the + :meth:`~pyrogram.Client.on_bot_business_message` decorator. + + Parameters: + callback (``Callable``): + Pass a function that will be called when a new Message arrives. It takes *(client, message)* + as positional arguments (look at the section below for a detailed description). + + filters (:obj:`Filters`): + Pass one or more filters to allow only a subset of messages to be passed + in your callback function. + + Other parameters: + client (:obj:`~pyrogram.Client`): + The Client itself, useful when you want to call other API methods inside the message handler. + + message (:obj:`~pyrogram.types.Message`): + The received message. + """ + + def __init__(self, callback: Callable, filters=None): + self.original_callback = callback + super().__init__(self.resolve_future_or_callback, filters) + + async def check_if_has_matching_listener(self, client: "pyrogram.Client", message: Message): + """ + Checks if the message has a matching listener. + + :param client: The Client object to check with. + :param message: The Message object to check with. + :return: A tuple of whether the message has a matching listener and its filters does match with the Message + and the matching listener; + """ + from_user = message.from_user + from_user_id = from_user.id if from_user else None + from_user_username = from_user.username if from_user else None + + message_id = getattr(message, "id", getattr(message, "message_id", None)) + + data = Identifier( + message_id=message_id, + chat_id=[message.chat.id, message.chat.username], + from_user_id=[from_user_id, from_user_username], + ) + + listener = client.get_listener_matching_with_data(data, pyrogram.enums.ListenerTypes.MESSAGE) + + listener_does_match = False + + if listener: + filters = listener.filters + if callable(filters): + if iscoroutinefunction(filters.__call__): + listener_does_match = await filters(client, message) + else: + listener_does_match = await client.loop.run_in_executor( + None, filters, client, message + ) + else: + listener_does_match = True + + return listener_does_match, listener + + async def check(self, client: "pyrogram.Client", message: Message): + """ + Checks if the message has a matching listener or handler and its filters does match with the Message. + + :param client: Client object to check with. + :param message: Message object to check with. + :return: Whether the message has a matching listener or handler and its filters does match with the Message. + """ + listener_does_match = ( + await self.check_if_has_matching_listener(client, message) + )[0] + + if callable(self.filters): + if iscoroutinefunction(self.filters.__call__): + handler_does_match = await self.filters(client, message) + else: + handler_does_match = await client.loop.run_in_executor( + None, self.filters, client, message + ) + else: + handler_does_match = True + + # let handler get the chance to handle if listener + # exists but its filters doesn't match + return listener_does_match or handler_does_match + + async def resolve_future_or_callback(self, client: "pyrogram.Client", message: Message, *args): + """ + Resolves the future or calls the callback of the listener if the message has a matching listener. + + :param client: Client object to resolve or call with. + :param message: Message object to resolve or call with. + :param args: Arguments to call the callback with. + :return: None + """ + listener_does_match, listener = await self.check_if_has_matching_listener( + client, message + ) + + if listener and listener_does_match: + client.remove_listener(listener) + + if listener.future and not listener.future.done(): + listener.future.set_result(message) + + raise pyrogram.StopPropagation + elif listener.callback: + if iscoroutinefunction(listener.callback): + await listener.callback(client, message, *args) + else: + listener.callback(client, message, *args) + + raise pyrogram.StopPropagation + else: + raise ValueError("Listener must have either a future or a callback") + else: + await self.original_callback(client, message, *args) \ No newline at end of file diff --git a/pyrogram/methods/decorators/__init__.py b/pyrogram/methods/decorators/__init__.py index 169b1ec3..b3b1dea5 100644 --- a/pyrogram/methods/decorators/__init__.py +++ b/pyrogram/methods/decorators/__init__.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrofork. If not, see . +from .on_bot_business_message import OnBotBusinessMessage from .on_callback_query import OnCallbackQuery from .on_chat_join_request import OnChatJoinRequest from .on_chat_member_updated import OnChatMemberUpdated @@ -36,6 +37,7 @@ from .on_message_reaction_count_updated import OnMessageReactionCountUpdated class Decorators( OnMessage, + OnBotBusinessMessage, OnEditedMessage, OnDeletedMessages, OnCallbackQuery, diff --git a/pyrogram/methods/decorators/on_bot_business_message.py b/pyrogram/methods/decorators/on_bot_business_message.py new file mode 100644 index 00000000..ba499484 --- /dev/null +++ b/pyrogram/methods/decorators/on_bot_business_message.py @@ -0,0 +1,62 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +from typing import Callable + +import pyrogram +from pyrogram.filters import Filter + + +class OnBotBusinessMessage: + def on_bot_business_message( + self=None, + filters=None, + group: int = 0 + ) -> Callable: + """Decorator for handling new bot business messages. + + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.BotBusinessMessageHandler`. + + Parameters: + filters (:obj:`~pyrogram.filters`, *optional*): + Pass one or more filters to allow only a subset of messages to be passed + in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.handlers.BotBusinessMessageHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.BotBusinessMessageHandler(func, self), + group if filters is None else filters + ) + ) + + return func + + return decorator diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 5eafa14b..a7612041 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -133,6 +133,9 @@ class Message(Object, Update): reply_to_story (:obj:`~pyrogram.types.Story`, *optional*): For replies, the original story. + business_connection_id (``str``, *optional*): + The business connection identifier. + mentioned (``bool``, *optional*): The message contains a mention. @@ -401,6 +404,7 @@ class Message(Object, Update): client: "pyrogram.Client" = None, id: int, message_thread_id: int = None, + business_connection_id: str = None, from_user: "types.User" = None, sender_chat: "types.Chat" = None, sender_business_bot: "types.User" = None, @@ -504,6 +508,7 @@ class Message(Object, Update): self.id = id self.message_thread_id = message_thread_id + self.business_connection_id = business_connection_id self.from_user = from_user self.sender_chat = sender_chat self.sender_business_bot = sender_business_bot @@ -643,6 +648,7 @@ class Message(Object, Update): chats: dict, topics: dict = None, is_scheduled: bool = False, + business_connection_id: str = None, replies: int = 1 ): if isinstance(message, raw.types.MessageEmpty): @@ -1039,6 +1045,7 @@ class Message(Object, Update): parsed_message = Message( id=message.id, message_thread_id=message_thread_id, + business_connection_id=business_connection_id, date=utils.timestamp_to_datetime(message.date), chat=types.Chat._parse(client, message, users, chats, is_chat=True), topics=None, diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 4ed7fb20..007e3381 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -98,7 +98,8 @@ def get_input_media_from_file_id( async def parse_messages( client, messages: "raw.types.messages.Messages", - replies: int = 1 + replies: int = 1, + business_connection_id: str = None ) -> List["types.Message"]: users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} @@ -112,7 +113,7 @@ async def parse_messages( parsed_messages = [] for message in messages.messages: - parsed_messages.append(await types.Message._parse(client, message, users, chats, topics, replies=0)) + parsed_messages.append(await types.Message._parse(client, message, users, chats, topics, replies=0, business_connection_id=business_connection_id)) if replies: messages_with_replies = {