From a548ac1118c4503ddb9d7ab0ba0c65fcea097862 Mon Sep 17 00:00:00 2001 From: Cezar H Date: Wed, 8 Nov 2023 17:35:11 -0300 Subject: [PATCH 01/10] feat: integrate pyromod patches --- hydrogram/client.py | 307 +++++++++++++++++- hydrogram/errors/__init__.py | 3 + hydrogram/errors/pyromod/__init__.py | 4 + hydrogram/errors/pyromod/listener_stopped.py | 2 + hydrogram/errors/pyromod/listener_timeout.py | 2 + hydrogram/handlers/callback_query_handler.py | 151 ++++++++- hydrogram/handlers/message_handler.py | 103 +++++- hydrogram/helpers/__init__.py | 21 ++ hydrogram/helpers/helpers.py | 138 ++++++++ hydrogram/nav/__init__.py | 21 ++ hydrogram/nav/pagination.py | 91 ++++++ hydrogram/types/__init__.py | 4 + hydrogram/types/messages_and_media/message.py | 28 ++ hydrogram/types/pyromod/__init__.py | 5 + hydrogram/types/pyromod/identifier.py | 41 +++ hydrogram/types/pyromod/listener.py | 19 ++ hydrogram/types/pyromod/listener_types.py | 6 + hydrogram/types/user_and_chats/chat.py | 30 ++ hydrogram/types/user_and_chats/user.py | 31 ++ hydrogram/utils.py | 11 +- 20 files changed, 1011 insertions(+), 7 deletions(-) create mode 100644 hydrogram/errors/pyromod/__init__.py create mode 100644 hydrogram/errors/pyromod/listener_stopped.py create mode 100644 hydrogram/errors/pyromod/listener_timeout.py create mode 100644 hydrogram/helpers/__init__.py create mode 100644 hydrogram/helpers/helpers.py create mode 100644 hydrogram/nav/__init__.py create mode 100644 hydrogram/nav/pagination.py create mode 100644 hydrogram/types/pyromod/__init__.py create mode 100644 hydrogram/types/pyromod/identifier.py create mode 100644 hydrogram/types/pyromod/listener.py create mode 100644 hydrogram/types/pyromod/listener_types.py diff --git a/hydrogram/client.py b/hydrogram/client.py index 080ff756b..a040b083a 100644 --- a/hydrogram/client.py +++ b/hydrogram/client.py @@ -44,6 +44,8 @@ BadRequest, CDNFileHashMismatch, ChannelPrivate, + ListenerStopped, + ListenerTimeout, SessionPasswordNeeded, VolumeLocNotFound, ) @@ -51,11 +53,12 @@ from hydrogram.methods import Methods from hydrogram.session import Auth, Session from hydrogram.storage import BaseStorage, SQLiteStorage -from hydrogram.types import TermsOfService, User -from hydrogram.utils import ainput +from hydrogram.types import Identifier, Listener, ListenerTypes, TermsOfService, User +from hydrogram.utils import PyromodConfig, ainput from .dispatcher import Dispatcher from .file_id import FileId, FileType, ThumbnailSource +from .filters import Filter from .mime_types import mime_types from .parser import Parser from .session.internals import MsgId @@ -310,6 +313,7 @@ def __init__( self.last_update_time = datetime.now() self.loop = asyncio.get_event_loop() + self.listeners = {listener_type: [] for listener_type in ListenerTypes} def __enter__(self): return self.start() @@ -341,6 +345,305 @@ async def updates_watchdog(self): ): await self.invoke(raw.functions.updates.GetState()) + async def listen( + self, + filters: Optional[Filter] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + chat_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + message_id: Optional[Union[int, List[int]]] = None, + inline_message_id: Optional[Union[str, List[str]]] = None, + ): + """ + Creates a listener and waits for it to be fulfilled. + + :param filters: A filter to check if the listener should be fulfilled. + :param listener_type: The type of listener to create. Defaults to :attr:`pyromod.types.ListenerTypes.MESSAGE`. + :param timeout: The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``. + :param unallowed_click_alert: Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. + :param chat_id: The chat ID(s) to listen for. Defaults to ``None``. + :param user_id: The user ID(s) to listen for. Defaults to ``None``. + :param message_id: The message ID(s) to listen for. Defaults to ``None``. + :param inline_message_id: The inline message ID(s) to listen for. Defaults to ``None``. + :return: The Message or CallbackQuery that fulfilled the listener. + """ + pattern = Identifier( + from_user_id=user_id, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + + loop = asyncio.get_event_loop() + future = loop.create_future() + + listener = Listener( + future=future, + filters=filters, + unallowed_click_alert=unallowed_click_alert, + identifier=pattern, + listener_type=listener_type, + ) + + future.add_done_callback(lambda _future: self.remove_listener(listener)) + + self.listeners[listener_type].append(listener) + + try: + return await asyncio.wait_for(future, timeout) + except asyncio.exceptions.TimeoutError: + if callable(PyromodConfig.timeout_handler): + if inspect.iscoroutinefunction(PyromodConfig.timeout_handler.__call__): + await PyromodConfig.timeout_handler(pattern, listener, timeout) + else: + await self.loop.run_in_executor( + None, PyromodConfig.timeout_handler, pattern, listener, timeout + ) + elif PyromodConfig.throw_exceptions: + raise ListenerTimeout(timeout) + + async def ask( + self, + chat_id: Union[Union[int, str], List[Union[int, str]]], + text: str, + filters: Optional[Filter] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + message_id: Optional[Union[int, List[int]]] = None, + inline_message_id: Optional[Union[str, List[str]]] = None, + *args, + **kwargs, + ): + """ + Sends a message and waits for a response. + + :param chat_id: The chat ID(s) to wait for a message from. The first chat ID will be used to send the message. + :param text: The text to send. + :param filters: Same as :meth:`pyromod.types.Client.listen`. + :param listener_type: Same as :meth:`pyromod.types.Client.listen`. + :param timeout: Same as :meth:`pyromod.types.Client.listen`. + :param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`. + :param user_id: Same as :meth:`pyromod.types.Client.listen`. + :param message_id: Same as :meth:`pyromod.types.Client.listen`. + :param inline_message_id: Same as :meth:`pyromod.types.Client.listen`. + :param args: Additional arguments to pass to :meth:`hydrogram.Client.send_message`. + :param kwargs: Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`. + :return: + Same as :meth:`pyromod.types.Client.listen`. The sent message is returned as the attribute ``sent_message``. + """ + sent_message = None + if text.strip() != "": + chat_to_ask = chat_id[0] if isinstance(chat_id, list) else chat_id + sent_message = await self.send_message(chat_to_ask, text, *args, **kwargs) + + response = await self.listen( + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + chat_id=chat_id, + user_id=user_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + if response: + response.sent_message = sent_message + + return response + + def remove_listener(self, listener: Listener): + """ + Removes a listener from the :meth:`pyromod.types.Client.listeners` dictionary. + + :param listener: The listener to remove. + :return: ``void`` + """ + with contextlib.suppress(ValueError): + self.listeners[listener.listener_type].remove(listener) + + def get_listener_matching_with_data( + self, data: Identifier, listener_type: ListenerTypes + ) -> Optional[Listener]: + """ + Gets a listener that matches the given data. + + :param data: A :class:`pyromod.types.Identifier` to match against. + :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. + :return: The listener that matches the given data or ``None`` if no listener matches. + """ + matching = [] + for listener in self.listeners[listener_type]: + if listener.identifier.matches(data): + matching.append(listener) + + # in case of multiple matching listeners, the most specific should be returned + def count_populated_attributes(listener_item: Listener): + return listener_item.identifier.count_populated() + + return max(matching, key=count_populated_attributes, default=None) + + def get_listener_matching_with_identifier_pattern( + self, pattern: Identifier, listener_type: ListenerTypes + ) -> Optional[Listener]: + """ + Gets a listener that matches the given identifier pattern. + + The difference from :meth:`pyromod.types.Client.get_listener_matching_with_data` is that this method + intends to get a listener by passing partial info of the listener identifier, while the other method + intends to get a listener by passing the full info of the update data, which the listener should match with. + + :param pattern: A :class:`pyromod.types.Identifier` to match against. + :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. + :return: The listener that matches the given identifier pattern or ``None`` if no listener matches. + """ + matching = [] + for listener in self.listeners[listener_type]: + if pattern.matches(listener.identifier): + matching.append(listener) + + # in case of multiple matching listeners, the most specific should be returned + + def count_populated_attributes(listener_item: Listener): + return listener_item.identifier.count_populated() + + return max(matching, key=count_populated_attributes, default=None) + + def get_many_listeners_matching_with_data( + self, + data: Identifier, + listener_type: ListenerTypes, + ) -> List[Listener]: + """ + Same of :meth:`pyromod.types.Client.get_listener_matching_with_data` but returns a list of listeners instead of one. + + :param data: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. + :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. + :return: A list of listeners that match the given data. + """ + listeners = [] + for listener in self.listeners[listener_type]: + if listener.identifier.matches(data): + listeners.append(listener) + return listeners + + def get_many_listeners_matching_with_identifier_pattern( + self, + pattern: Identifier, + listener_type: ListenerTypes, + ) -> List[Listener]: + """ + Same of :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one. + + :param pattern: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. + :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. + :return: A list of listeners that match the given identifier pattern. + """ + listeners = [] + for listener in self.listeners[listener_type]: + if pattern.matches(listener.identifier): + listeners.append(listener) + return listeners + + async def stop_listening( + self, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + chat_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + message_id: Optional[Union[int, List[int]]] = None, + inline_message_id: Optional[Union[str, List[str]]] = None, + ): + """ + Stops all listeners that match the given identifier pattern. + Uses :meth:`pyromod.types.Client.get_many_listeners_matching_with_identifier_pattern`. + + :param listener_type: The type of listener to stop. Must be a value from :class:`pyromod.types.ListenerTypes`. + :param chat_id: The chat_id to match against. + :param user_id: The user_id to match against. + :param message_id: The message_id to match against. + :param inline_message_id: The inline_message_id to match against. + :return: ``void`` + """ + pattern = Identifier( + from_user_id=user_id, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + listeners = self.get_many_listeners_matching_with_identifier_pattern( + pattern, listener_type + ) + + for listener in listeners: + await self.stop_listener(listener) + + async def stop_listener(self, listener: Listener): + """ + Stops a listener, calling stopped_handler if applicable or raising ListenerStopped if throw_exceptions is True. + + :param listener: The :class:`pyromod.types.Listener` to stop. + :return: ``void`` + :raises ListenerStopped: If throw_exceptions is True. + """ + self.remove_listener(listener) + + if listener.future.done(): + return + + if callable(PyromodConfig.stopped_handler): + if inspect.iscoroutinefunction(PyromodConfig.stopped_handler.__call__): + await PyromodConfig.stopped_handler(None, listener) + else: + await self.loop.run_in_executor( + None, PyromodConfig.stopped_handler, None, listener + ) + elif PyromodConfig.throw_exceptions: + listener.future.set_exception(ListenerStopped()) + + def register_next_step_handler( + self, + callback: Callable, + filters: Optional[Filter] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + unallowed_click_alert: bool = True, + chat_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + message_id: Optional[Union[int, List[int]]] = None, + inline_message_id: Optional[Union[str, List[str]]] = None, + ): + """ + Registers a listener with a callback to be called when the listener is fulfilled. + + :param callback: The callback to call when the listener is fulfilled. + :param filters: Same as :meth:`pyromod.types.Client.listen`. + :param listener_type: Same as :meth:`pyromod.types.Client.listen`. + :param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`. + :param chat_id: Same as :meth:`pyromod.types.Client.listen`. + :param user_id: Same as :meth:`pyromod.types.Client.listen`. + :param message_id: Same as :meth:`pyromod.types.Client.listen`. + :param inline_message_id: Same as :meth:`pyromod.types.Client.listen`. + :return: ``void`` + """ + pattern = Identifier( + from_user_id=user_id, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + + listener = Listener( + callback=callback, + filters=filters, + unallowed_click_alert=unallowed_click_alert, + identifier=pattern, + listener_type=listener_type, + ) + + self.listeners[listener_type].append(listener) + async def authorize(self) -> User: if self.bot_token: return await self.sign_in_bot(self.bot_token) diff --git a/hydrogram/errors/__init__.py b/hydrogram/errors/__init__.py index 7bf62fa8e..b083606f6 100644 --- a/hydrogram/errors/__init__.py +++ b/hydrogram/errors/__init__.py @@ -493,6 +493,7 @@ service_unavailable_503, unauthorized_401, ) +from .pyromod import ListenerStopped, ListenerTimeout from .rpc_error import UnknownError @@ -955,6 +956,8 @@ def __init__(self, msg: Optional[str] = None): "TypesEmpty", "Unauthorized", "UnknownError", + "ListenerStopped", + "ListenerTimeout", "UnknownMethod", "UntilDateInvalid", "UploadNoVolume", diff --git a/hydrogram/errors/pyromod/__init__.py b/hydrogram/errors/pyromod/__init__.py new file mode 100644 index 000000000..00921aa28 --- /dev/null +++ b/hydrogram/errors/pyromod/__init__.py @@ -0,0 +1,4 @@ +from .listener_stopped import ListenerStopped +from .listener_timeout import ListenerTimeout + +__all__ = ["ListenerStopped", "ListenerTimeout"] diff --git a/hydrogram/errors/pyromod/listener_stopped.py b/hydrogram/errors/pyromod/listener_stopped.py new file mode 100644 index 000000000..05a1016e2 --- /dev/null +++ b/hydrogram/errors/pyromod/listener_stopped.py @@ -0,0 +1,2 @@ +class ListenerStopped(Exception): + pass diff --git a/hydrogram/errors/pyromod/listener_timeout.py b/hydrogram/errors/pyromod/listener_timeout.py new file mode 100644 index 000000000..9c2accd11 --- /dev/null +++ b/hydrogram/errors/pyromod/listener_timeout.py @@ -0,0 +1,2 @@ +class ListenerTimeout(Exception): + pass diff --git a/hydrogram/handlers/callback_query_handler.py b/hydrogram/handlers/callback_query_handler.py index 97984128d..07c67d753 100644 --- a/hydrogram/handlers/callback_query_handler.py +++ b/hydrogram/handlers/callback_query_handler.py @@ -16,8 +16,12 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . +from asyncio import iscoroutinefunction +from typing import Callable, Tuple -from typing import Callable +import hydrogram +from hydrogram.types import CallbackQuery, Identifier, Listener, ListenerTypes +from hydrogram.utils import PyromodConfig from .handler import Handler @@ -47,4 +51,147 @@ class CallbackQueryHandler(Handler): """ def __init__(self, callback: Callable, filters=None): - super().__init__(callback, filters) + self.original_callback = callback + super().__init__(self.resolve_future_or_callback, filters) + + def compose_data_identifier(self, query: CallbackQuery): + """ + Composes an Identifier object from a CallbackQuery object. + + :param query: The CallbackQuery object to compose of. + :return: An Identifier object. + """ + from_user = query.from_user + from_user_id = from_user.id if from_user else None + from_user_username = from_user.username if from_user else None + + chat_id = None + message_id = None + + if query.message: + message_id = getattr(query.message, "id", getattr(query.message, "message_id", None)) + + if query.message.chat: + chat_id = [query.message.chat.id, query.message.chat.username] + + return Identifier( + message_id=message_id, + chat_id=chat_id, + from_user_id=[from_user_id, from_user_username], + inline_message_id=query.inline_message_id, + ) + + async def check_if_has_matching_listener( + self, client: "hydrogram.Client", query: CallbackQuery + ) -> Tuple[bool, Listener]: + """ + Checks if the CallbackQuery object has a matching listener. + + :param client: The Client object to check with. + :param query: The CallbackQuery object to check with. + :return: A tuple of a boolean and a Listener object. The boolean indicates whether + the found listener has filters and its filters matches with the CallbackQuery object. + The Listener object is the matching listener. + """ + data = self.compose_data_identifier(query) + + listener = client.get_listener_matching_with_data(data, ListenerTypes.CALLBACK_QUERY) + + listener_does_match = False + + if listener: + filters = listener.filters + if callable(filters): + if iscoroutinefunction(filters.__call__): + listener_does_match = await filters(client, query) + else: + listener_does_match = await client.loop.run_in_executor( + None, filters, client, query + ) + else: + listener_does_match = True + + return listener_does_match, listener + + async def check(self, client: "hydrogram.Client", query: CallbackQuery): + """ + Checks if the CallbackQuery object has a matching listener or handler. + + :param client: The Client object to check with. + :param query: The CallbackQuery object to check with. + :return: A boolean indicating whether the CallbackQuery object has a matching listener or the handler + filter matches. + """ + listener_does_match, listener = await self.check_if_has_matching_listener(client, query) + + if callable(self.filters): + if iscoroutinefunction(self.filters.__call__): + handler_does_match = await self.filters(client, query) + else: + handler_does_match = await client.loop.run_in_executor( + None, self.filters, client, query + ) + else: + handler_does_match = True + + data = self.compose_data_identifier(query) + + if PyromodConfig.unallowed_click_alert: + # matches with the current query but from any user + permissive_identifier = Identifier( + chat_id=data.chat_id, + message_id=data.message_id, + inline_message_id=data.inline_message_id, + from_user_id=None, + ) + + matches = permissive_identifier.matches(data) + + if ( + listener + and (matches and not listener_does_match) + and listener.unallowed_click_alert + ): + alert = ( + listener.unallowed_click_alert + if isinstance(listener.unallowed_click_alert, str) + else PyromodConfig.unallowed_click_alert_text + ) + await query.answer(alert) + return False + + # 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: "hydrogram.Client", query: CallbackQuery, *args + ): + """ + Resolves the future or calls the callback of the listener. Will call the original handler if no listener. + + :param client: The Client object to resolve or call with. + :param query: The CallbackQuery object to resolve or call with. + :param args: The arguments to call the callback with. + :return: None + """ + listener_does_match, listener = await self.check_if_has_matching_listener(client, query) + + if listener and listener_does_match: + client.remove_listener(listener) + + if listener.future and not listener.future.done(): + listener.future.set_result(query) + + raise hydrogram.StopPropagation + elif listener.callback: + if iscoroutinefunction(listener.callback): + await listener.callback(client, query, *args) + else: + listener.callback(client, query, *args) + + raise hydrogram.StopPropagation + else: + raise ValueError("Listener must have either a future or a callback") + else: + await self.original_callback(client, query, *args) diff --git a/hydrogram/handlers/message_handler.py b/hydrogram/handlers/message_handler.py index a0d2c3992..1376f5423 100644 --- a/hydrogram/handlers/message_handler.py +++ b/hydrogram/handlers/message_handler.py @@ -16,9 +16,12 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . - +from inspect import iscoroutinefunction from typing import Callable +import hydrogram +from hydrogram.types import Identifier, ListenerTypes, Message + from .handler import Handler @@ -47,4 +50,100 @@ class MessageHandler(Handler): """ def __init__(self, callback: Callable, filters=None): - super().__init__(callback, filters) + self.original_callback = callback + super().__init__(self.resolve_future_or_callback, filters) + + async def check_if_has_matching_listener(self, client: "hydrogram.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, 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: "hydrogram.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: "hydrogram.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 hydrogram.StopPropagation + elif listener.callback: + if iscoroutinefunction(listener.callback): + await listener.callback(client, message, *args) + else: + listener.callback(client, message, *args) + + raise hydrogram.StopPropagation + else: + raise ValueError("Listener must have either a future or a callback") + else: + await self.original_callback(client, message, *args) diff --git a/hydrogram/helpers/__init__.py b/hydrogram/helpers/__init__.py new file mode 100644 index 000000000..46bbb897f --- /dev/null +++ b/hydrogram/helpers/__init__.py @@ -0,0 +1,21 @@ +""" +pyromod - A monkeypatcher add-on for Pyrogram +Copyright (C) 2020 Cezar H. + +This file is part of pyromod. + +pyromod is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +pyromod 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with pyromod. If not, see . +""" + +from .helpers import array_chunk, bki, btn, force_reply, ikb, kb, kbtn, ntb diff --git a/hydrogram/helpers/helpers.py b/hydrogram/helpers/helpers.py new file mode 100644 index 000000000..c14a26264 --- /dev/null +++ b/hydrogram/helpers/helpers.py @@ -0,0 +1,138 @@ +from hydrogram.types import ( + ForceReply, + InlineKeyboardButton, + InlineKeyboardMarkup, + KeyboardButton, + ReplyKeyboardMarkup, +) + + +def ikb(rows=None): + """ + Create an InlineKeyboardMarkup from a list of lists of buttons. + :param rows: List of lists of buttons. Defaults to empty list. + :return: InlineKeyboardMarkup + """ + if rows is None: + rows = [] + + lines = [] + for row in rows: + line = [] + for button in row: + button = ( + btn(button, button) if isinstance(button, str) else btn(*button) + ) # InlineKeyboardButton + line.append(button) + lines.append(line) + return InlineKeyboardMarkup(inline_keyboard=lines) + # return {'inline_keyboard': lines} + + +def btn(text, value, type="callback_data"): + """ + Create an InlineKeyboardButton. + + :param text: Text of the button. + :param value: Value of the button. + :param type: Type of the button. Defaults to "callback_data". + :return: InlineKeyboardButton + """ + return InlineKeyboardButton(text, **{type: value}) + # return {'text': text, type: value} + + +# The inverse of above +def bki(keyboard): + """ + Create a list of lists of buttons from an InlineKeyboardMarkup. + + :param keyboard: InlineKeyboardMarkup + :return: List of lists of buttons + """ + lines = [] + for row in keyboard.inline_keyboard: + line = [] + for button in row: + button = ntb(button) # btn() format + line.append(button) + lines.append(line) + return lines + # return ikb() format + + +def ntb(button): + """ + Create a button list from an InlineKeyboardButton. + + :param button: InlineKeyboardButton + :return: Button as a list to be used in btn() + """ + for btn_type in [ + "callback_data", + "url", + "switch_inline_query", + "switch_inline_query_current_chat", + "callback_game", + ]: + value = getattr(button, btn_type) + if value: + break + button = [button.text, value] + if btn_type != "callback_data": + button.append(btn_type) + return button + # return {'text': text, type: value} + + +def kb(rows=None, **kwargs): + """ + Create a ReplyKeyboardMarkup from a list of lists of buttons. + + :param rows: List of lists of buttons. Defaults to empty list. + :param kwargs: Other arguments to pass to ReplyKeyboardMarkup. + :return: ReplyKeyboardMarkup + """ + if rows is None: + rows = [] + + lines = [] + for row in rows: + line = [] + for button in row: + button_type = type(button) + if button_type == str: + button = KeyboardButton(button) + elif button_type == dict: + button = KeyboardButton(**button) + + line.append(button) + lines.append(line) + return ReplyKeyboardMarkup(keyboard=lines, **kwargs) + + +kbtn = KeyboardButton +""" +Create a KeyboardButton. +""" + + +def force_reply(selective=True): + """ + Create a ForceReply. + + :param selective: Whether the reply should be selective. Defaults to True. + :return: ForceReply + """ + return ForceReply(selective=selective) + + +def array_chunk(input_array, size): + """ + Split an array into chunks. + + :param input_array: The array to split. + :param size: The size of each chunk. + :return: List of chunks. + """ + return [input_array[i : i + size] for i in range(0, len(input_array), size)] diff --git a/hydrogram/nav/__init__.py b/hydrogram/nav/__init__.py new file mode 100644 index 000000000..be822dedf --- /dev/null +++ b/hydrogram/nav/__init__.py @@ -0,0 +1,21 @@ +""" +pyromod - A monkeypatcher add-on for Pyrogram +Copyright (C) 2020 Cezar H. + +This file is part of pyromod. + +pyromod is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +pyromod 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with pyromod. If not, see . +""" + +from .pagination import Pagination diff --git a/hydrogram/nav/pagination.py b/hydrogram/nav/pagination.py new file mode 100644 index 000000000..a68839d74 --- /dev/null +++ b/hydrogram/nav/pagination.py @@ -0,0 +1,91 @@ +""" +pyromod - A monkeypatcher add-on for Hydrogram +Copyright (C) 2020 Cezar H. + +This file is part of pyromod. + +pyromod is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +pyromod 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with pyromod. If not, see . +""" + +import math + +from hydrogram.helpers import array_chunk + + +class Pagination: + def __init__(self, objects, page_data=None, item_data=None, item_title=None): + def default_page_callback(x): + return str(x) + + def default_item_callback(i, pg): + return f"[{pg}] {i}" + + self.objects = objects + self.page_data = page_data or default_page_callback + self.item_data = item_data or default_item_callback + self.item_title = item_title or default_item_callback + + def create(self, page, lines=5, columns=1): + quant_per_page = lines * columns + page = 1 if page <= 0 else page + offset = (page - 1) * quant_per_page + stop = offset + quant_per_page + cutted = self.objects[offset:stop] + + total = len(self.objects) + pages_range = [*range(1, math.ceil(total / quant_per_page) + 1)] # each item is a page + last_page = len(pages_range) + + nav = [] + if page <= 3: + for n in [1, 2, 3]: + if n not in pages_range: + continue + text = f"· {n} ·" if n == page else n + nav.append((text, self.page_data(n))) + if last_page >= 4: + nav.append(("4 ›" if last_page > 5 else 4, self.page_data(4))) + if last_page > 4: + nav.append(( + f"{last_page} »" if last_page > 5 else last_page, + self.page_data(last_page), + )) + elif page >= last_page - 2: + nav.extend([ + ("« 1" if last_page - 4 > 1 else 1, self.page_data(1)), + ( + f"‹ {last_page - 3}" if last_page - 4 > 1 else last_page - 3, + self.page_data(last_page - 3), + ), + ]) + for n in range(last_page - 2, last_page + 1): + text = f"· {n} ·" if n == page else n + nav.append((text, self.page_data(n))) + else: + nav = [ + ("« 1", self.page_data(1)), + (f"‹ {page - 1}", self.page_data(page - 1)), + (f"· {page} ·", "noop"), + (f"{page + 1} ›", self.page_data(page + 1)), + (f"{last_page} »", self.page_data(last_page)), + ] + + buttons = [] + for item in cutted: + buttons.append((self.item_title(item, page), self.item_data(item, page))) + kb_lines = array_chunk(buttons, columns) + if last_page > 1: + kb_lines.append(nav) + + return kb_lines diff --git a/hydrogram/types/__init__.py b/hydrogram/types/__init__.py index 73599bcb3..8b13b20f2 100644 --- a/hydrogram/types/__init__.py +++ b/hydrogram/types/__init__.py @@ -189,6 +189,7 @@ web_page, ) from .object import Object +from .pyromod import Identifier, Listener, ListenerTypes from .update import Update from .user_and_chats import ( Chat, @@ -275,6 +276,9 @@ "WebAppData", "WebPage", "Object", + "Identifier", + "Listener", + "ListenerTypes", "Update", "Chat", "ChatAdminWithInviteLinks", diff --git a/hydrogram/types/messages_and_media/message.py b/hydrogram/types/messages_and_media/message.py index 3f5fbec24..f3314852b 100644 --- a/hydrogram/types/messages_and_media/message.py +++ b/hydrogram/types/messages_and_media/message.py @@ -505,6 +505,34 @@ def __init__( self.web_app_data = web_app_data self.reactions = reactions + async def wait_for_click( + self, + from_user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + timeout: Optional[int] = None, + filters=None, + alert: Union[str, bool] = True, + ): + """ + Waits for a callback query to be clicked on the message. + + :param from_user_id: The user ID(s) to wait for. If None, waits for any user. + :param timeout: The timeout in seconds. If None, waits forever. + :param filters: The filters to pass to Client.listen(). + :param alert: The alert to show when the button is clicked by users that are not allowed in from_user_id. + :return: The callback query that was clicked. + """ + message_id = getattr(self, "id", getattr(self, "message_id", None)) + + return await self._client.listen( + listener_type=types.ListenerTypes.CALLBACK_QUERY, + timeout=timeout, + filters=filters, + unallowed_click_alert=alert, + chat_id=self.chat.id, + user_id=from_user_id, + message_id=message_id, + ) + @staticmethod async def _parse( client: "hydrogram.Client", diff --git a/hydrogram/types/pyromod/__init__.py b/hydrogram/types/pyromod/__init__.py new file mode 100644 index 000000000..d6303133a --- /dev/null +++ b/hydrogram/types/pyromod/__init__.py @@ -0,0 +1,5 @@ +from .identifier import Identifier +from .listener import Listener +from .listener_types import ListenerTypes + +__all__ = ["Identifier", "Listener", "ListenerTypes"] diff --git a/hydrogram/types/pyromod/identifier.py b/hydrogram/types/pyromod/identifier.py new file mode 100644 index 000000000..20895f201 --- /dev/null +++ b/hydrogram/types/pyromod/identifier.py @@ -0,0 +1,41 @@ +from dataclasses import dataclass +from typing import List, Optional, Union + + +@dataclass +class Identifier: + inline_message_id: Optional[Union[str, List[str]]] = None + chat_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None + message_id: Optional[Union[int, List[int]]] = None + from_user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None + + def matches(self, update: "Identifier") -> bool: + # Compare each property of other with the corresponding property in self + # If the property in self is None, the property in other can be anything + # If the property in self is not None, the property in other must be the same + for field in self.__annotations__: + pattern_value = getattr(self, field) + update_value = getattr(update, field) + + if pattern_value is not None: + if isinstance(update_value, list): + if isinstance(pattern_value, list): + if not set(update_value).intersection(set(pattern_value)): + return False + elif pattern_value not in update_value: + return False + elif isinstance(pattern_value, list): + if update_value not in pattern_value: + return False + elif update_value != pattern_value: + return False + return True + + def count_populated(self): + non_null_count = 0 + + for attr in self.__annotations__: + if getattr(self, attr) is not None: + non_null_count += 1 + + return non_null_count diff --git a/hydrogram/types/pyromod/listener.py b/hydrogram/types/pyromod/listener.py new file mode 100644 index 000000000..4e771ee5d --- /dev/null +++ b/hydrogram/types/pyromod/listener.py @@ -0,0 +1,19 @@ +from asyncio import Future +from dataclasses import dataclass +from typing import Callable + +import hydrogram +from hydrogram import filters + +from .identifier import Identifier +from .listener_types import ListenerTypes + + +@dataclass +class Listener: + listener_type: ListenerTypes + filters: "hydrogram.filters.Filter" + unallowed_click_alert: bool + identifier: Identifier + future: Future = None + callback: Callable = None diff --git a/hydrogram/types/pyromod/listener_types.py b/hydrogram/types/pyromod/listener_types.py new file mode 100644 index 000000000..5b7c41dfc --- /dev/null +++ b/hydrogram/types/pyromod/listener_types.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class ListenerTypes(Enum): + MESSAGE = "message" + CALLBACK_QUERY = "callback_query" diff --git a/hydrogram/types/user_and_chats/chat.py b/hydrogram/types/user_and_chats/chat.py index cc4c90862..5f9beb29c 100644 --- a/hydrogram/types/user_and_chats/chat.py +++ b/hydrogram/types/user_and_chats/chat.py @@ -405,6 +405,36 @@ def _parse_chat( return Chat._parse_user_chat(client, chat) return Chat._parse_channel_chat(client, chat) + def listen(self, *args, **kwargs): + """ + Listens for messages in the chat. Calls Client.listen() with the chat_id set to the chat's id. + + :param args: Arguments to pass to Client.listen(). + :param kwargs: Keyword arguments to pass to Client.listen(). + :return: The return value of Client.listen(). + """ + return self._client.listen(*args, chat_id=self.id, **kwargs) + + def ask(self, text, *args, **kwargs): + """ + Asks a question in the chat. Calls Client.ask() with the chat_id set to the chat's id. + :param text: The text to send. + :param args: Arguments to pass to Client.ask(). + :param kwargs: Keyword arguments to pass to Client.ask(). + :return: The return value of Client.ask(). + """ + return self._client.ask(self.id, text, *args, **kwargs) + + def stop_listening(self, *args, **kwargs): + """ + Stops listening for messages in the chat. Calls Client.stop_listening() with the chat_id set to the chat's id. + + :param args: Arguments to pass to Client.stop_listening(). + :param kwargs: Keyword arguments to pass to Client.stop_listening(). + :return: The return value of Client.stop_listening(). + """ + return self._client.stop_listening(*args, chat_id=self.id, **kwargs) + async def archive(self): """Bound method *archive* of :obj:`~hydrogram.types.Chat`. diff --git a/hydrogram/types/user_and_chats/user.py b/hydrogram/types/user_and_chats/user.py index e93062024..5de772389 100644 --- a/hydrogram/types/user_and_chats/user.py +++ b/hydrogram/types/user_and_chats/user.py @@ -301,6 +301,37 @@ def _parse_user_status(client, user_status: "raw.types.UpdateUserStatus"): client=client, ) + def listen(self, *args, **kwargs): + """ + Listens for messages from the user. Calls Client.listen() with the user_id set to the user's id. + + :param args: Arguments to pass to Client.listen(). + :param kwargs: Keyword arguments to pass to Client.listen(). + :return: The return value of Client.listen(). + """ + return self._client.listen(*args, user_id=self.id, **kwargs) + + def ask(self, text, *args, **kwargs): + """ + Asks a question to the user. Calls Client.ask() with both chat_id and user_id set to the user's id. + + :param text: The text to send. + :param args: Arguments to pass to Client.ask(). + :param kwargs: Keyword arguments to pass to Client.ask(). + :return: The return value of Client.ask(). + """ + return self._client.ask(self.id, text, *args, user_id=self.id, **kwargs) + + def stop_listening(self, *args, **kwargs): + """ + Stops listening for messages from the user. Calls Client.stop_listening() with the user_id set to the user's id. + + :param args: Arguments to pass to Client.stop_listening(). + :param kwargs: Keyword arguments to pass to Client.stop_listening(). + :return: The return value of Client.stop_listening(). + """ + return self._client.stop_listening(*args, user_id=self.id, **kwargs) + async def archive(self): """Bound method *archive* of :obj:`~hydrogram.types.User`. diff --git a/hydrogram/utils.py b/hydrogram/utils.py index c03271415..b01df0eca 100644 --- a/hydrogram/utils.py +++ b/hydrogram/utils.py @@ -26,12 +26,21 @@ from concurrent.futures.thread import ThreadPoolExecutor from datetime import datetime, timezone from getpass import getpass -from typing import Optional, Union +from types import SimpleNamespace +from typing import Dict, List, Optional, Union import hydrogram from hydrogram import enums, raw, types from hydrogram.file_id import DOCUMENT_TYPES, PHOTO_TYPES, FileId, FileType +PyromodConfig = SimpleNamespace( + timeout_handler=None, + stopped_handler=None, + throw_exceptions=True, + unallowed_click_alert=True, + unallowed_click_alert_text=("[pyromod] You're not expected to click this button."), +) + async def ainput(prompt: str = "", *, hide: bool = False): """Just like the built-in input, but async""" From 93f21d1bdd81a91e7a064878d825d7bb71749223 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sat, 2 Dec 2023 11:47:54 -0300 Subject: [PATCH 02/10] refactor: fix ruff reported issues --- hydrogram/client.py | 74 ++++++++++--------- hydrogram/errors/pyromod/listener_stopped.py | 2 +- hydrogram/errors/pyromod/listener_timeout.py | 2 +- hydrogram/handlers/callback_query_handler.py | 14 ++-- hydrogram/handlers/message_handler.py | 10 +-- hydrogram/helpers/__init__.py | 2 + hydrogram/helpers/helpers.py | 4 +- hydrogram/nav/__init__.py | 2 + hydrogram/nav/pagination.py | 5 +- hydrogram/types/messages_and_media/message.py | 2 +- hydrogram/types/pyromod/identifier.py | 10 +-- hydrogram/types/pyromod/listener.py | 1 - hydrogram/utils.py | 2 +- 13 files changed, 67 insertions(+), 63 deletions(-) diff --git a/hydrogram/client.py b/hydrogram/client.py index a040b083a..878641851 100644 --- a/hydrogram/client.py +++ b/hydrogram/client.py @@ -351,10 +351,10 @@ async def listen( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, - chat_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, - message_id: Optional[Union[int, List[int]]] = None, - inline_message_id: Optional[Union[str, List[str]]] = None, + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, ): """ Creates a listener and waits for it to be fulfilled. @@ -406,15 +406,15 @@ async def listen( async def ask( self, - chat_id: Union[Union[int, str], List[Union[int, str]]], + chat_id: Union[Union[int, str], list[Union[int, str]]], text: str, filters: Optional[Filter] = None, listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, - user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, - message_id: Optional[Union[int, List[int]]] = None, - inline_message_id: Optional[Union[str, List[str]]] = None, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, *args, **kwargs, ): @@ -475,10 +475,11 @@ def get_listener_matching_with_data( :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. :return: The listener that matches the given data or ``None`` if no listener matches. """ - matching = [] - for listener in self.listeners[listener_type]: - if listener.identifier.matches(data): - matching.append(listener) + matching = [ + listener + for listener in self.listeners[listener_type] + if listener.identifier.matches(data) + ] # in case of multiple matching listeners, the most specific should be returned def count_populated_attributes(listener_item: Listener): @@ -500,10 +501,11 @@ def get_listener_matching_with_identifier_pattern( :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. :return: The listener that matches the given identifier pattern or ``None`` if no listener matches. """ - matching = [] - for listener in self.listeners[listener_type]: - if pattern.matches(listener.identifier): - matching.append(listener) + matching = [ + listener + for listener in self.listeners[listener_type] + if pattern.matches(listener.identifier) + ] # in case of multiple matching listeners, the most specific should be returned @@ -516,7 +518,7 @@ def get_many_listeners_matching_with_data( self, data: Identifier, listener_type: ListenerTypes, - ) -> List[Listener]: + ) -> list[Listener]: """ Same of :meth:`pyromod.types.Client.get_listener_matching_with_data` but returns a list of listeners instead of one. @@ -524,17 +526,17 @@ def get_many_listeners_matching_with_data( :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. :return: A list of listeners that match the given data. """ - listeners = [] - for listener in self.listeners[listener_type]: - if listener.identifier.matches(data): - listeners.append(listener) - return listeners + return [ + listener + for listener in self.listeners[listener_type] + if listener.identifier.matches(data) + ] def get_many_listeners_matching_with_identifier_pattern( self, pattern: Identifier, listener_type: ListenerTypes, - ) -> List[Listener]: + ) -> list[Listener]: """ Same of :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one. @@ -542,19 +544,19 @@ def get_many_listeners_matching_with_identifier_pattern( :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. :return: A list of listeners that match the given identifier pattern. """ - listeners = [] - for listener in self.listeners[listener_type]: - if pattern.matches(listener.identifier): - listeners.append(listener) - return listeners + return [ + listener + for listener in self.listeners[listener_type] + if pattern.matches(listener.identifier) + ] async def stop_listening( self, listener_type: ListenerTypes = ListenerTypes.MESSAGE, - chat_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, - message_id: Optional[Union[int, List[int]]] = None, - inline_message_id: Optional[Union[str, List[str]]] = None, + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, ): """ Stops all listeners that match the given identifier pattern. @@ -609,10 +611,10 @@ def register_next_step_handler( filters: Optional[Filter] = None, listener_type: ListenerTypes = ListenerTypes.MESSAGE, unallowed_click_alert: bool = True, - chat_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, - message_id: Optional[Union[int, List[int]]] = None, - inline_message_id: Optional[Union[str, List[str]]] = None, + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, ): """ Registers a listener with a callback to be called when the listener is fulfilled. diff --git a/hydrogram/errors/pyromod/listener_stopped.py b/hydrogram/errors/pyromod/listener_stopped.py index 05a1016e2..7c63c7658 100644 --- a/hydrogram/errors/pyromod/listener_stopped.py +++ b/hydrogram/errors/pyromod/listener_stopped.py @@ -1,2 +1,2 @@ -class ListenerStopped(Exception): +class ListenerStopped(Exception): # noqa: N818 pass diff --git a/hydrogram/errors/pyromod/listener_timeout.py b/hydrogram/errors/pyromod/listener_timeout.py index 9c2accd11..b7d8ca0fb 100644 --- a/hydrogram/errors/pyromod/listener_timeout.py +++ b/hydrogram/errors/pyromod/listener_timeout.py @@ -1,2 +1,2 @@ -class ListenerTimeout(Exception): +class ListenerTimeout(Exception): # noqa: N818 pass diff --git a/hydrogram/handlers/callback_query_handler.py b/hydrogram/handlers/callback_query_handler.py index 07c67d753..2b93becc4 100644 --- a/hydrogram/handlers/callback_query_handler.py +++ b/hydrogram/handlers/callback_query_handler.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . from asyncio import iscoroutinefunction -from typing import Callable, Tuple +from typing import Callable import hydrogram from hydrogram.types import CallbackQuery, Identifier, Listener, ListenerTypes @@ -83,7 +83,7 @@ def compose_data_identifier(self, query: CallbackQuery): async def check_if_has_matching_listener( self, client: "hydrogram.Client", query: CallbackQuery - ) -> Tuple[bool, Listener]: + ) -> tuple[bool, Listener]: """ Checks if the CallbackQuery object has a matching listener. @@ -184,14 +184,14 @@ async def resolve_future_or_callback( listener.future.set_result(query) raise hydrogram.StopPropagation - elif listener.callback: + if listener.callback: if iscoroutinefunction(listener.callback): await listener.callback(client, query, *args) else: listener.callback(client, query, *args) raise hydrogram.StopPropagation - else: - raise ValueError("Listener must have either a future or a callback") - else: - await self.original_callback(client, query, *args) + + raise ValueError("Listener must have either a future or a callback") + + await self.original_callback(client, query, *args) diff --git a/hydrogram/handlers/message_handler.py b/hydrogram/handlers/message_handler.py index 1376f5423..1647f66db 100644 --- a/hydrogram/handlers/message_handler.py +++ b/hydrogram/handlers/message_handler.py @@ -136,14 +136,14 @@ async def resolve_future_or_callback( listener.future.set_result(message) raise hydrogram.StopPropagation - elif listener.callback: + if listener.callback: if iscoroutinefunction(listener.callback): await listener.callback(client, message, *args) else: listener.callback(client, message, *args) raise hydrogram.StopPropagation - else: - raise ValueError("Listener must have either a future or a callback") - else: - await self.original_callback(client, message, *args) + + raise ValueError("Listener must have either a future or a callback") + + await self.original_callback(client, message, *args) diff --git a/hydrogram/helpers/__init__.py b/hydrogram/helpers/__init__.py index 46bbb897f..ffcd7dbbc 100644 --- a/hydrogram/helpers/__init__.py +++ b/hydrogram/helpers/__init__.py @@ -19,3 +19,5 @@ """ from .helpers import array_chunk, bki, btn, force_reply, ikb, kb, kbtn, ntb + +__all__ = ["array_chunk", "bki", "btn", "force_reply", "ikb", "kb", "kbtn", "ntb"] diff --git a/hydrogram/helpers/helpers.py b/hydrogram/helpers/helpers.py index c14a26264..05a900a57 100644 --- a/hydrogram/helpers/helpers.py +++ b/hydrogram/helpers/helpers.py @@ -101,9 +101,9 @@ def kb(rows=None, **kwargs): line = [] for button in row: button_type = type(button) - if button_type == str: + if isinstance(button_type, str): button = KeyboardButton(button) - elif button_type == dict: + elif isinstance(button_type, dict): button = KeyboardButton(**button) line.append(button) diff --git a/hydrogram/nav/__init__.py b/hydrogram/nav/__init__.py index be822dedf..4e80d3f6d 100644 --- a/hydrogram/nav/__init__.py +++ b/hydrogram/nav/__init__.py @@ -19,3 +19,5 @@ """ from .pagination import Pagination + +__all__ = ["Pagination"] diff --git a/hydrogram/nav/pagination.py b/hydrogram/nav/pagination.py index a68839d74..6eb502a6c 100644 --- a/hydrogram/nav/pagination.py +++ b/hydrogram/nav/pagination.py @@ -81,9 +81,8 @@ def create(self, page, lines=5, columns=1): (f"{last_page} »", self.page_data(last_page)), ] - buttons = [] - for item in cutted: - buttons.append((self.item_title(item, page), self.item_data(item, page))) + buttons = [(self.item_title(item, page), self.item_data(item, page)) for item in cutted] + kb_lines = array_chunk(buttons, columns) if last_page > 1: kb_lines.append(nav) diff --git a/hydrogram/types/messages_and_media/message.py b/hydrogram/types/messages_and_media/message.py index f3314852b..52eb8476d 100644 --- a/hydrogram/types/messages_and_media/message.py +++ b/hydrogram/types/messages_and_media/message.py @@ -507,7 +507,7 @@ def __init__( async def wait_for_click( self, - from_user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None, + from_user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, timeout: Optional[int] = None, filters=None, alert: Union[str, bool] = True, diff --git a/hydrogram/types/pyromod/identifier.py b/hydrogram/types/pyromod/identifier.py index 20895f201..dd8d30921 100644 --- a/hydrogram/types/pyromod/identifier.py +++ b/hydrogram/types/pyromod/identifier.py @@ -1,13 +1,13 @@ from dataclasses import dataclass -from typing import List, Optional, Union +from typing import Optional, Union @dataclass class Identifier: - inline_message_id: Optional[Union[str, List[str]]] = None - chat_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None - message_id: Optional[Union[int, List[int]]] = None - from_user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None + inline_message_id: Optional[Union[str, list[str]]] = None + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None + message_id: Optional[Union[int, list[int]]] = None + from_user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None def matches(self, update: "Identifier") -> bool: # Compare each property of other with the corresponding property in self diff --git a/hydrogram/types/pyromod/listener.py b/hydrogram/types/pyromod/listener.py index 4e771ee5d..fe0108f3f 100644 --- a/hydrogram/types/pyromod/listener.py +++ b/hydrogram/types/pyromod/listener.py @@ -3,7 +3,6 @@ from typing import Callable import hydrogram -from hydrogram import filters from .identifier import Identifier from .listener_types import ListenerTypes diff --git a/hydrogram/utils.py b/hydrogram/utils.py index b01df0eca..57ca761b7 100644 --- a/hydrogram/utils.py +++ b/hydrogram/utils.py @@ -27,7 +27,7 @@ from datetime import datetime, timezone from getpass import getpass from types import SimpleNamespace -from typing import Dict, List, Optional, Union +from typing import Optional, Union import hydrogram from hydrogram import enums, raw, types From e7364c4bada80e4099e04290ac9ab102cef7c8b2 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sat, 2 Dec 2023 14:01:05 -0300 Subject: [PATCH 03/10] chore: add copyright notices where missing --- hydrogram/errors/pyromod/__init__.py | 19 +++++++++++ hydrogram/errors/pyromod/listener_stopped.py | 20 +++++++++++ hydrogram/errors/pyromod/listener_timeout.py | 20 +++++++++++ hydrogram/helpers/__init__.py | 36 ++++++++++---------- hydrogram/helpers/helpers.py | 19 +++++++++++ hydrogram/nav/__init__.py | 36 ++++++++++---------- hydrogram/nav/pagination.py | 36 ++++++++++---------- hydrogram/types/pyromod/__init__.py | 19 +++++++++++ hydrogram/types/pyromod/identifier.py | 19 +++++++++++ hydrogram/types/pyromod/listener.py | 19 +++++++++++ hydrogram/types/pyromod/listener_types.py | 19 +++++++++++ 11 files changed, 208 insertions(+), 54 deletions(-) diff --git a/hydrogram/errors/pyromod/__init__.py b/hydrogram/errors/pyromod/__init__.py index 00921aa28..33262d328 100644 --- a/hydrogram/errors/pyromod/__init__.py +++ b/hydrogram/errors/pyromod/__init__.py @@ -1,3 +1,22 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + from .listener_stopped import ListenerStopped from .listener_timeout import ListenerTimeout diff --git a/hydrogram/errors/pyromod/listener_stopped.py b/hydrogram/errors/pyromod/listener_stopped.py index 7c63c7658..a1575fc27 100644 --- a/hydrogram/errors/pyromod/listener_stopped.py +++ b/hydrogram/errors/pyromod/listener_stopped.py @@ -1,2 +1,22 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + + class ListenerStopped(Exception): # noqa: N818 pass diff --git a/hydrogram/errors/pyromod/listener_timeout.py b/hydrogram/errors/pyromod/listener_timeout.py index b7d8ca0fb..cd945829e 100644 --- a/hydrogram/errors/pyromod/listener_timeout.py +++ b/hydrogram/errors/pyromod/listener_timeout.py @@ -1,2 +1,22 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + + class ListenerTimeout(Exception): # noqa: N818 pass diff --git a/hydrogram/helpers/__init__.py b/hydrogram/helpers/__init__.py index ffcd7dbbc..ecd234fe3 100644 --- a/hydrogram/helpers/__init__.py +++ b/hydrogram/helpers/__init__.py @@ -1,22 +1,22 @@ -""" -pyromod - A monkeypatcher add-on for Pyrogram -Copyright (C) 2020 Cezar H. +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . -This file is part of pyromod. - -pyromod is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -pyromod 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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with pyromod. If not, see . -""" from .helpers import array_chunk, bki, btn, force_reply, ikb, kb, kbtn, ntb diff --git a/hydrogram/helpers/helpers.py b/hydrogram/helpers/helpers.py index 05a900a57..648d60df6 100644 --- a/hydrogram/helpers/helpers.py +++ b/hydrogram/helpers/helpers.py @@ -1,3 +1,22 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + from hydrogram.types import ( ForceReply, InlineKeyboardButton, diff --git a/hydrogram/nav/__init__.py b/hydrogram/nav/__init__.py index 4e80d3f6d..399aaf674 100644 --- a/hydrogram/nav/__init__.py +++ b/hydrogram/nav/__init__.py @@ -1,22 +1,22 @@ -""" -pyromod - A monkeypatcher add-on for Pyrogram -Copyright (C) 2020 Cezar H. +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . -This file is part of pyromod. - -pyromod is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -pyromod 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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with pyromod. If not, see . -""" from .pagination import Pagination diff --git a/hydrogram/nav/pagination.py b/hydrogram/nav/pagination.py index 6eb502a6c..a3091ba0b 100644 --- a/hydrogram/nav/pagination.py +++ b/hydrogram/nav/pagination.py @@ -1,22 +1,22 @@ -""" -pyromod - A monkeypatcher add-on for Hydrogram -Copyright (C) 2020 Cezar H. +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . -This file is part of pyromod. - -pyromod is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -pyromod 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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with pyromod. If not, see . -""" import math diff --git a/hydrogram/types/pyromod/__init__.py b/hydrogram/types/pyromod/__init__.py index d6303133a..f3dbffdf5 100644 --- a/hydrogram/types/pyromod/__init__.py +++ b/hydrogram/types/pyromod/__init__.py @@ -1,3 +1,22 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + from .identifier import Identifier from .listener import Listener from .listener_types import ListenerTypes diff --git a/hydrogram/types/pyromod/identifier.py b/hydrogram/types/pyromod/identifier.py index dd8d30921..73f709b8e 100644 --- a/hydrogram/types/pyromod/identifier.py +++ b/hydrogram/types/pyromod/identifier.py @@ -1,3 +1,22 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + from dataclasses import dataclass from typing import Optional, Union diff --git a/hydrogram/types/pyromod/listener.py b/hydrogram/types/pyromod/listener.py index fe0108f3f..9553cd162 100644 --- a/hydrogram/types/pyromod/listener.py +++ b/hydrogram/types/pyromod/listener.py @@ -1,3 +1,22 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + from asyncio import Future from dataclasses import dataclass from typing import Callable diff --git a/hydrogram/types/pyromod/listener_types.py b/hydrogram/types/pyromod/listener_types.py index 5b7c41dfc..77b759544 100644 --- a/hydrogram/types/pyromod/listener_types.py +++ b/hydrogram/types/pyromod/listener_types.py @@ -1,3 +1,22 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + from enum import Enum From 09994a39b17659315f0fe7b7287e9d43f84e7980 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sat, 2 Dec 2023 14:30:50 -0300 Subject: [PATCH 04/10] chore(news): add news fragment --- news/1.feature.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 news/1.feature.rst diff --git a/news/1.feature.rst b/news/1.feature.rst new file mode 100644 index 000000000..baf5634eb --- /dev/null +++ b/news/1.feature.rst @@ -0,0 +1,2 @@ +Integrate pyromod patches into the project (many thanks to @usernein for his excellent work). To check out the pyromod specific features, have a look at https://pyromod.pauxis.dev/. +You can use the features in the same way as in pyromod, except that you import them directly from the hydrogram package. From 9133b84ac65ba9c2e97ffce212fb90b2526cc50e Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sat, 2 Dec 2023 15:17:25 -0300 Subject: [PATCH 05/10] refactor: split out pyromod methods from client.py --- hydrogram/client.py | 308 +----------------- hydrogram/methods/__init__.py | 2 + hydrogram/methods/pyromod/__init__.py | 46 +++ hydrogram/methods/pyromod/ask.py | 77 +++++ .../get_listener_matching_with_data.py | 47 +++ ...stener_matching_with_identifier_pattern.py | 52 +++ .../get_many_listeners_matching_with_data.py | 42 +++ ...teners_matching_with_identifier_pattern.py | 42 +++ hydrogram/methods/pyromod/listen.py | 91 ++++++ .../pyromod/register_next_step_handler.py | 67 ++++ hydrogram/methods/pyromod/remove_listener.py | 35 ++ hydrogram/methods/pyromod/stop_listener.py | 52 +++ hydrogram/methods/pyromod/stop_listening.py | 57 ++++ 13 files changed, 612 insertions(+), 306 deletions(-) create mode 100644 hydrogram/methods/pyromod/__init__.py create mode 100644 hydrogram/methods/pyromod/ask.py create mode 100644 hydrogram/methods/pyromod/get_listener_matching_with_data.py create mode 100644 hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py create mode 100644 hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py create mode 100644 hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py create mode 100644 hydrogram/methods/pyromod/listen.py create mode 100644 hydrogram/methods/pyromod/register_next_step_handler.py create mode 100644 hydrogram/methods/pyromod/remove_listener.py create mode 100644 hydrogram/methods/pyromod/stop_listener.py create mode 100644 hydrogram/methods/pyromod/stop_listening.py diff --git a/hydrogram/client.py b/hydrogram/client.py index 878641851..fd21da9ea 100644 --- a/hydrogram/client.py +++ b/hydrogram/client.py @@ -44,8 +44,6 @@ BadRequest, CDNFileHashMismatch, ChannelPrivate, - ListenerStopped, - ListenerTimeout, SessionPasswordNeeded, VolumeLocNotFound, ) @@ -53,12 +51,11 @@ from hydrogram.methods import Methods from hydrogram.session import Auth, Session from hydrogram.storage import BaseStorage, SQLiteStorage -from hydrogram.types import Identifier, Listener, ListenerTypes, TermsOfService, User -from hydrogram.utils import PyromodConfig, ainput +from hydrogram.types import ListenerTypes, TermsOfService, User +from hydrogram.utils import ainput from .dispatcher import Dispatcher from .file_id import FileId, FileType, ThumbnailSource -from .filters import Filter from .mime_types import mime_types from .parser import Parser from .session.internals import MsgId @@ -345,307 +342,6 @@ async def updates_watchdog(self): ): await self.invoke(raw.functions.updates.GetState()) - async def listen( - self, - filters: Optional[Filter] = None, - listener_type: ListenerTypes = ListenerTypes.MESSAGE, - timeout: Optional[int] = None, - unallowed_click_alert: bool = True, - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - message_id: Optional[Union[int, list[int]]] = None, - inline_message_id: Optional[Union[str, list[str]]] = None, - ): - """ - Creates a listener and waits for it to be fulfilled. - - :param filters: A filter to check if the listener should be fulfilled. - :param listener_type: The type of listener to create. Defaults to :attr:`pyromod.types.ListenerTypes.MESSAGE`. - :param timeout: The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``. - :param unallowed_click_alert: Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. - :param chat_id: The chat ID(s) to listen for. Defaults to ``None``. - :param user_id: The user ID(s) to listen for. Defaults to ``None``. - :param message_id: The message ID(s) to listen for. Defaults to ``None``. - :param inline_message_id: The inline message ID(s) to listen for. Defaults to ``None``. - :return: The Message or CallbackQuery that fulfilled the listener. - """ - pattern = Identifier( - from_user_id=user_id, - chat_id=chat_id, - message_id=message_id, - inline_message_id=inline_message_id, - ) - - loop = asyncio.get_event_loop() - future = loop.create_future() - - listener = Listener( - future=future, - filters=filters, - unallowed_click_alert=unallowed_click_alert, - identifier=pattern, - listener_type=listener_type, - ) - - future.add_done_callback(lambda _future: self.remove_listener(listener)) - - self.listeners[listener_type].append(listener) - - try: - return await asyncio.wait_for(future, timeout) - except asyncio.exceptions.TimeoutError: - if callable(PyromodConfig.timeout_handler): - if inspect.iscoroutinefunction(PyromodConfig.timeout_handler.__call__): - await PyromodConfig.timeout_handler(pattern, listener, timeout) - else: - await self.loop.run_in_executor( - None, PyromodConfig.timeout_handler, pattern, listener, timeout - ) - elif PyromodConfig.throw_exceptions: - raise ListenerTimeout(timeout) - - async def ask( - self, - chat_id: Union[Union[int, str], list[Union[int, str]]], - text: str, - filters: Optional[Filter] = None, - listener_type: ListenerTypes = ListenerTypes.MESSAGE, - timeout: Optional[int] = None, - unallowed_click_alert: bool = True, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - message_id: Optional[Union[int, list[int]]] = None, - inline_message_id: Optional[Union[str, list[str]]] = None, - *args, - **kwargs, - ): - """ - Sends a message and waits for a response. - - :param chat_id: The chat ID(s) to wait for a message from. The first chat ID will be used to send the message. - :param text: The text to send. - :param filters: Same as :meth:`pyromod.types.Client.listen`. - :param listener_type: Same as :meth:`pyromod.types.Client.listen`. - :param timeout: Same as :meth:`pyromod.types.Client.listen`. - :param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`. - :param user_id: Same as :meth:`pyromod.types.Client.listen`. - :param message_id: Same as :meth:`pyromod.types.Client.listen`. - :param inline_message_id: Same as :meth:`pyromod.types.Client.listen`. - :param args: Additional arguments to pass to :meth:`hydrogram.Client.send_message`. - :param kwargs: Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`. - :return: - Same as :meth:`pyromod.types.Client.listen`. The sent message is returned as the attribute ``sent_message``. - """ - sent_message = None - if text.strip() != "": - chat_to_ask = chat_id[0] if isinstance(chat_id, list) else chat_id - sent_message = await self.send_message(chat_to_ask, text, *args, **kwargs) - - response = await self.listen( - filters=filters, - listener_type=listener_type, - timeout=timeout, - unallowed_click_alert=unallowed_click_alert, - chat_id=chat_id, - user_id=user_id, - message_id=message_id, - inline_message_id=inline_message_id, - ) - if response: - response.sent_message = sent_message - - return response - - def remove_listener(self, listener: Listener): - """ - Removes a listener from the :meth:`pyromod.types.Client.listeners` dictionary. - - :param listener: The listener to remove. - :return: ``void`` - """ - with contextlib.suppress(ValueError): - self.listeners[listener.listener_type].remove(listener) - - def get_listener_matching_with_data( - self, data: Identifier, listener_type: ListenerTypes - ) -> Optional[Listener]: - """ - Gets a listener that matches the given data. - - :param data: A :class:`pyromod.types.Identifier` to match against. - :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. - :return: The listener that matches the given data or ``None`` if no listener matches. - """ - matching = [ - listener - for listener in self.listeners[listener_type] - if listener.identifier.matches(data) - ] - - # in case of multiple matching listeners, the most specific should be returned - def count_populated_attributes(listener_item: Listener): - return listener_item.identifier.count_populated() - - return max(matching, key=count_populated_attributes, default=None) - - def get_listener_matching_with_identifier_pattern( - self, pattern: Identifier, listener_type: ListenerTypes - ) -> Optional[Listener]: - """ - Gets a listener that matches the given identifier pattern. - - The difference from :meth:`pyromod.types.Client.get_listener_matching_with_data` is that this method - intends to get a listener by passing partial info of the listener identifier, while the other method - intends to get a listener by passing the full info of the update data, which the listener should match with. - - :param pattern: A :class:`pyromod.types.Identifier` to match against. - :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. - :return: The listener that matches the given identifier pattern or ``None`` if no listener matches. - """ - matching = [ - listener - for listener in self.listeners[listener_type] - if pattern.matches(listener.identifier) - ] - - # in case of multiple matching listeners, the most specific should be returned - - def count_populated_attributes(listener_item: Listener): - return listener_item.identifier.count_populated() - - return max(matching, key=count_populated_attributes, default=None) - - def get_many_listeners_matching_with_data( - self, - data: Identifier, - listener_type: ListenerTypes, - ) -> list[Listener]: - """ - Same of :meth:`pyromod.types.Client.get_listener_matching_with_data` but returns a list of listeners instead of one. - - :param data: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. - :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. - :return: A list of listeners that match the given data. - """ - return [ - listener - for listener in self.listeners[listener_type] - if listener.identifier.matches(data) - ] - - def get_many_listeners_matching_with_identifier_pattern( - self, - pattern: Identifier, - listener_type: ListenerTypes, - ) -> list[Listener]: - """ - Same of :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one. - - :param pattern: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. - :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. - :return: A list of listeners that match the given identifier pattern. - """ - return [ - listener - for listener in self.listeners[listener_type] - if pattern.matches(listener.identifier) - ] - - async def stop_listening( - self, - listener_type: ListenerTypes = ListenerTypes.MESSAGE, - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - message_id: Optional[Union[int, list[int]]] = None, - inline_message_id: Optional[Union[str, list[str]]] = None, - ): - """ - Stops all listeners that match the given identifier pattern. - Uses :meth:`pyromod.types.Client.get_many_listeners_matching_with_identifier_pattern`. - - :param listener_type: The type of listener to stop. Must be a value from :class:`pyromod.types.ListenerTypes`. - :param chat_id: The chat_id to match against. - :param user_id: The user_id to match against. - :param message_id: The message_id to match against. - :param inline_message_id: The inline_message_id to match against. - :return: ``void`` - """ - pattern = Identifier( - from_user_id=user_id, - chat_id=chat_id, - message_id=message_id, - inline_message_id=inline_message_id, - ) - listeners = self.get_many_listeners_matching_with_identifier_pattern( - pattern, listener_type - ) - - for listener in listeners: - await self.stop_listener(listener) - - async def stop_listener(self, listener: Listener): - """ - Stops a listener, calling stopped_handler if applicable or raising ListenerStopped if throw_exceptions is True. - - :param listener: The :class:`pyromod.types.Listener` to stop. - :return: ``void`` - :raises ListenerStopped: If throw_exceptions is True. - """ - self.remove_listener(listener) - - if listener.future.done(): - return - - if callable(PyromodConfig.stopped_handler): - if inspect.iscoroutinefunction(PyromodConfig.stopped_handler.__call__): - await PyromodConfig.stopped_handler(None, listener) - else: - await self.loop.run_in_executor( - None, PyromodConfig.stopped_handler, None, listener - ) - elif PyromodConfig.throw_exceptions: - listener.future.set_exception(ListenerStopped()) - - def register_next_step_handler( - self, - callback: Callable, - filters: Optional[Filter] = None, - listener_type: ListenerTypes = ListenerTypes.MESSAGE, - unallowed_click_alert: bool = True, - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - message_id: Optional[Union[int, list[int]]] = None, - inline_message_id: Optional[Union[str, list[str]]] = None, - ): - """ - Registers a listener with a callback to be called when the listener is fulfilled. - - :param callback: The callback to call when the listener is fulfilled. - :param filters: Same as :meth:`pyromod.types.Client.listen`. - :param listener_type: Same as :meth:`pyromod.types.Client.listen`. - :param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`. - :param chat_id: Same as :meth:`pyromod.types.Client.listen`. - :param user_id: Same as :meth:`pyromod.types.Client.listen`. - :param message_id: Same as :meth:`pyromod.types.Client.listen`. - :param inline_message_id: Same as :meth:`pyromod.types.Client.listen`. - :return: ``void`` - """ - pattern = Identifier( - from_user_id=user_id, - chat_id=chat_id, - message_id=message_id, - inline_message_id=inline_message_id, - ) - - listener = Listener( - callback=callback, - filters=filters, - unallowed_click_alert=unallowed_click_alert, - identifier=pattern, - listener_type=listener_type, - ) - - self.listeners[listener_type].append(listener) - async def authorize(self) -> User: if self.bot_token: return await self.sign_in_bot(self.bot_token) diff --git a/hydrogram/methods/__init__.py b/hydrogram/methods/__init__.py index d8c38f795..d77eade20 100644 --- a/hydrogram/methods/__init__.py +++ b/hydrogram/methods/__init__.py @@ -26,6 +26,7 @@ from .invite_links import InviteLinks from .messages import Messages from .password import Password +from .pyromod import Pyromod from .users import Users from .utilities import Utilities @@ -39,6 +40,7 @@ class Methods( Chats, Users, Messages, + Pyromod, Decorators, Utilities, InviteLinks, diff --git a/hydrogram/methods/pyromod/__init__.py b/hydrogram/methods/pyromod/__init__.py new file mode 100644 index 000000000..a88d70cf4 --- /dev/null +++ b/hydrogram/methods/pyromod/__init__.py @@ -0,0 +1,46 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +from .ask import Ask +from .get_listener_matching_with_data import GetListenerMatchingWithData +from .get_listener_matching_with_identifier_pattern import GetListenerMatchingWithIdentifierPattern +from .get_many_listeners_matching_with_data import GetManyListenersMatchingWithData +from .get_many_listeners_matching_with_identifier_pattern import ( + GetManyListenersMatchingWithIdentifierPattern, +) +from .listen import Listen +from .register_next_step_handler import RegisterNextStepHandler +from .remove_listener import RemoveListener +from .stop_listener import StopListener +from .stop_listening import StopListening + + +class Pyromod( + Ask, + GetListenerMatchingWithData, + GetListenerMatchingWithIdentifierPattern, + GetManyListenersMatchingWithData, + GetManyListenersMatchingWithIdentifierPattern, + Listen, + RegisterNextStepHandler, + RemoveListener, + StopListener, + StopListening, +): + pass diff --git a/hydrogram/methods/pyromod/ask.py b/hydrogram/methods/pyromod/ask.py new file mode 100644 index 000000000..9594971ed --- /dev/null +++ b/hydrogram/methods/pyromod/ask.py @@ -0,0 +1,77 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +from typing import Optional, Union + +import hydrogram +from hydrogram.filters import Filter +from hydrogram.types import ListenerTypes + + +class Ask: + async def ask( + self: "hydrogram.Client", + chat_id: Union[Union[int, str], list[Union[int, str]]], + text: str, + filters: Optional[Filter] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + *args, + **kwargs, + ): + """ + Sends a message and waits for a response. + + :param chat_id: The chat ID(s) to wait for a message from. The first chat ID will be used to send the message. + :param text: The text to send. + :param filters: Same as :meth:`pyromod.types.Client.listen`. + :param listener_type: Same as :meth:`pyromod.types.Client.listen`. + :param timeout: Same as :meth:`pyromod.types.Client.listen`. + :param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`. + :param user_id: Same as :meth:`pyromod.types.Client.listen`. + :param message_id: Same as :meth:`pyromod.types.Client.listen`. + :param inline_message_id: Same as :meth:`pyromod.types.Client.listen`. + :param args: Additional arguments to pass to :meth:`hydrogram.Client.send_message`. + :param kwargs: Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`. + :return: + Same as :meth:`pyromod.types.Client.listen`. The sent message is returned as the attribute ``sent_message``. + """ + sent_message = None + if text.strip() != "": + chat_to_ask = chat_id[0] if isinstance(chat_id, list) else chat_id + sent_message = await self.send_message(chat_to_ask, text, *args, **kwargs) + + response = await self.listen( + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + chat_id=chat_id, + user_id=user_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + if response: + response.sent_message = sent_message + + return response diff --git a/hydrogram/methods/pyromod/get_listener_matching_with_data.py b/hydrogram/methods/pyromod/get_listener_matching_with_data.py new file mode 100644 index 000000000..d2005de84 --- /dev/null +++ b/hydrogram/methods/pyromod/get_listener_matching_with_data.py @@ -0,0 +1,47 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +from typing import Optional + +import hydrogram +from hydrogram.types import Identifier, Listener, ListenerTypes + + +class GetListenerMatchingWithData: + def get_listener_matching_with_data( + self: "hydrogram.Client", data: Identifier, listener_type: ListenerTypes + ) -> Optional[Listener]: + """ + Gets a listener that matches the given data. + + :param data: A :class:`pyromod.types.Identifier` to match against. + :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. + :return: The listener that matches the given data or ``None`` if no listener matches. + """ + matching = [ + listener + for listener in self.listeners[listener_type] + if listener.identifier.matches(data) + ] + + # in case of multiple matching listeners, the most specific should be returned + def count_populated_attributes(listener_item: Listener): + return listener_item.identifier.count_populated() + + return max(matching, key=count_populated_attributes, default=None) diff --git a/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py b/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py new file mode 100644 index 000000000..46e78abca --- /dev/null +++ b/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py @@ -0,0 +1,52 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +from typing import Optional + +import hydrogram +from hydrogram.types import Identifier, Listener, ListenerTypes + + +class GetListenerMatchingWithIdentifierPattern: + def get_listener_matching_with_identifier_pattern( + self: "hydrogram.Client", pattern: Identifier, listener_type: ListenerTypes + ) -> Optional[Listener]: + """ + Gets a listener that matches the given identifier pattern. + + The difference from :meth:`pyromod.types.Client.get_listener_matching_with_data` is that this method + intends to get a listener by passing partial info of the listener identifier, while the other method + intends to get a listener by passing the full info of the update data, which the listener should match with. + + :param pattern: A :class:`pyromod.types.Identifier` to match against. + :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. + :return: The listener that matches the given identifier pattern or ``None`` if no listener matches. + """ + matching = [ + listener + for listener in self.listeners[listener_type] + if pattern.matches(listener.identifier) + ] + + # in case of multiple matching listeners, the most specific should be returned + + def count_populated_attributes(listener_item: Listener): + return listener_item.identifier.count_populated() + + return max(matching, key=count_populated_attributes, default=None) diff --git a/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py b/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py new file mode 100644 index 000000000..bacba645b --- /dev/null +++ b/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py @@ -0,0 +1,42 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + + +import hydrogram +from hydrogram.types import Identifier, Listener, ListenerTypes + + +class GetManyListenersMatchingWithData: + def get_many_listeners_matching_with_data( + self: "hydrogram.Client", + data: Identifier, + listener_type: ListenerTypes, + ) -> list[Listener]: + """ + Same of :meth:`pyromod.types.Client.get_listener_matching_with_data` but returns a list of listeners instead of one. + + :param data: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. + :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. + :return: A list of listeners that match the given data. + """ + return [ + listener + for listener in self.listeners[listener_type] + if listener.identifier.matches(data) + ] diff --git a/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py b/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py new file mode 100644 index 000000000..27c082010 --- /dev/null +++ b/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py @@ -0,0 +1,42 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + + +import hydrogram +from hydrogram.types import Identifier, Listener, ListenerTypes + + +class GetManyListenersMatchingWithIdentifierPattern: + def get_many_listeners_matching_with_identifier_pattern( + self: "hydrogram.Client", + pattern: Identifier, + listener_type: ListenerTypes, + ) -> list[Listener]: + """ + Same of :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one. + + :param pattern: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. + :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. + :return: A list of listeners that match the given identifier pattern. + """ + return [ + listener + for listener in self.listeners[listener_type] + if pattern.matches(listener.identifier) + ] diff --git a/hydrogram/methods/pyromod/listen.py b/hydrogram/methods/pyromod/listen.py new file mode 100644 index 000000000..352b5c096 --- /dev/null +++ b/hydrogram/methods/pyromod/listen.py @@ -0,0 +1,91 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +import asyncio +import inspect +from typing import Optional, Union + +import hydrogram +from hydrogram.errors import ( + ListenerTimeout, +) +from hydrogram.filters import Filter +from hydrogram.types import Identifier, Listener, ListenerTypes +from hydrogram.utils import PyromodConfig + + +class Listen: + async def listen( + self: "hydrogram.Client", + filters: Optional[Filter] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): + """ + Creates a listener and waits for it to be fulfilled. + + :param filters: A filter to check if the listener should be fulfilled. + :param listener_type: The type of listener to create. Defaults to :attr:`pyromod.types.ListenerTypes.MESSAGE`. + :param timeout: The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``. + :param unallowed_click_alert: Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. + :param chat_id: The chat ID(s) to listen for. Defaults to ``None``. + :param user_id: The user ID(s) to listen for. Defaults to ``None``. + :param message_id: The message ID(s) to listen for. Defaults to ``None``. + :param inline_message_id: The inline message ID(s) to listen for. Defaults to ``None``. + :return: The Message or CallbackQuery that fulfilled the listener. + """ + pattern = Identifier( + from_user_id=user_id, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + + loop = asyncio.get_event_loop() + future = loop.create_future() + + listener = Listener( + future=future, + filters=filters, + unallowed_click_alert=unallowed_click_alert, + identifier=pattern, + listener_type=listener_type, + ) + + future.add_done_callback(lambda _future: self.remove_listener(listener)) + + self.listeners[listener_type].append(listener) + + try: + return await asyncio.wait_for(future, timeout) + except asyncio.exceptions.TimeoutError: + if callable(PyromodConfig.timeout_handler): + if inspect.iscoroutinefunction(PyromodConfig.timeout_handler.__call__): + await PyromodConfig.timeout_handler(pattern, listener, timeout) + else: + await self.loop.run_in_executor( + None, PyromodConfig.timeout_handler, pattern, listener, timeout + ) + elif PyromodConfig.throw_exceptions: + raise ListenerTimeout(timeout) diff --git a/hydrogram/methods/pyromod/register_next_step_handler.py b/hydrogram/methods/pyromod/register_next_step_handler.py new file mode 100644 index 000000000..9accc0848 --- /dev/null +++ b/hydrogram/methods/pyromod/register_next_step_handler.py @@ -0,0 +1,67 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +from typing import Callable, Optional, Union + +import hydrogram +from hydrogram.filters import Filter +from hydrogram.types import Identifier, Listener, ListenerTypes + + +class RegisterNextStepHandler: + def register_next_step_handler( + self: "hydrogram.Client", + callback: Callable, + filters: Optional[Filter] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + unallowed_click_alert: bool = True, + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): + """ + Registers a listener with a callback to be called when the listener is fulfilled. + + :param callback: The callback to call when the listener is fulfilled. + :param filters: Same as :meth:`pyromod.types.Client.listen`. + :param listener_type: Same as :meth:`pyromod.types.Client.listen`. + :param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`. + :param chat_id: Same as :meth:`pyromod.types.Client.listen`. + :param user_id: Same as :meth:`pyromod.types.Client.listen`. + :param message_id: Same as :meth:`pyromod.types.Client.listen`. + :param inline_message_id: Same as :meth:`pyromod.types.Client.listen`. + :return: ``void`` + """ + pattern = Identifier( + from_user_id=user_id, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + + listener = Listener( + callback=callback, + filters=filters, + unallowed_click_alert=unallowed_click_alert, + identifier=pattern, + listener_type=listener_type, + ) + + self.listeners[listener_type].append(listener) diff --git a/hydrogram/methods/pyromod/remove_listener.py b/hydrogram/methods/pyromod/remove_listener.py new file mode 100644 index 000000000..0a1250043 --- /dev/null +++ b/hydrogram/methods/pyromod/remove_listener.py @@ -0,0 +1,35 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +import contextlib + +import hydrogram +from hydrogram.types import Listener + + +class RemoveListener: + def remove_listener(self: "hydrogram.Client", listener: Listener): + """ + Removes a listener from the :meth:`pyromod.types.Client.listeners` dictionary. + + :param listener: The listener to remove. + :return: ``void`` + """ + with contextlib.suppress(ValueError): + self.listeners[listener.listener_type].remove(listener) diff --git a/hydrogram/methods/pyromod/stop_listener.py b/hydrogram/methods/pyromod/stop_listener.py new file mode 100644 index 000000000..ce21d1156 --- /dev/null +++ b/hydrogram/methods/pyromod/stop_listener.py @@ -0,0 +1,52 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +import inspect + +import hydrogram +from hydrogram.errors import ( + ListenerStopped, +) +from hydrogram.types import Listener +from hydrogram.utils import PyromodConfig + + +class StopListener: + async def stop_listener(self: "hydrogram.Client", listener: Listener): + """ + Stops a listener, calling stopped_handler if applicable or raising ListenerStopped if throw_exceptions is True. + + :param listener: The :class:`pyromod.types.Listener` to stop. + :return: ``void`` + :raises ListenerStopped: If throw_exceptions is True. + """ + self.remove_listener(listener) + + if listener.future.done(): + return + + if callable(PyromodConfig.stopped_handler): + if inspect.iscoroutinefunction(PyromodConfig.stopped_handler.__call__): + await PyromodConfig.stopped_handler(None, listener) + else: + await self.loop.run_in_executor( + None, PyromodConfig.stopped_handler, None, listener + ) + elif PyromodConfig.throw_exceptions: + listener.future.set_exception(ListenerStopped()) diff --git a/hydrogram/methods/pyromod/stop_listening.py b/hydrogram/methods/pyromod/stop_listening.py new file mode 100644 index 000000000..930ed7410 --- /dev/null +++ b/hydrogram/methods/pyromod/stop_listening.py @@ -0,0 +1,57 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2020-present Cezar H. +# Copyright (C) 2023-present Amano LLC +# +# This file is part of Hydrogram. +# +# Hydrogram 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. +# +# Hydrogram 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 Hydrogram. If not, see . + +from typing import Optional, Union + +import hydrogram +from hydrogram.types import Identifier, ListenerTypes + + +class StopListening: + async def stop_listening( + self: "hydrogram.Client", + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): + """ + Stops all listeners that match the given identifier pattern. + Uses :meth:`pyromod.types.Client.get_many_listeners_matching_with_identifier_pattern`. + + :param listener_type: The type of listener to stop. Must be a value from :class:`pyromod.types.ListenerTypes`. + :param chat_id: The chat_id to match against. + :param user_id: The user_id to match against. + :param message_id: The message_id to match against. + :param inline_message_id: The inline_message_id to match against. + :return: ``void`` + """ + pattern = Identifier( + from_user_id=user_id, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + listeners = self.get_many_listeners_matching_with_identifier_pattern( + pattern, listener_type + ) + + for listener in listeners: + await self.stop_listener(listener) From ec0a2f5d9f5f89fb08f0b237d07cdca450eda133 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sat, 2 Dec 2023 20:36:02 -0300 Subject: [PATCH 06/10] chore: change return types in docstrings --- hydrogram/handlers/callback_query_handler.py | 1 + hydrogram/handlers/message_handler.py | 1 + hydrogram/methods/pyromod/ask.py | 16 ++++++++-------- .../pyromod/get_listener_matching_with_data.py | 4 ++-- ..._listener_matching_with_identifier_pattern.py | 6 +++--- .../get_many_listeners_matching_with_data.py | 6 +++--- ...listeners_matching_with_identifier_pattern.py | 6 +++--- hydrogram/methods/pyromod/listen.py | 2 +- .../pyromod/register_next_step_handler.py | 14 +++++++------- hydrogram/methods/pyromod/remove_listener.py | 2 +- hydrogram/methods/pyromod/stop_listener.py | 2 +- hydrogram/methods/pyromod/stop_listening.py | 4 ++-- 12 files changed, 33 insertions(+), 31 deletions(-) diff --git a/hydrogram/handlers/callback_query_handler.py b/hydrogram/handlers/callback_query_handler.py index 2b93becc4..f377f2f27 100644 --- a/hydrogram/handlers/callback_query_handler.py +++ b/hydrogram/handlers/callback_query_handler.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . + from asyncio import iscoroutinefunction from typing import Callable diff --git a/hydrogram/handlers/message_handler.py b/hydrogram/handlers/message_handler.py index 1647f66db..633ac6a0d 100644 --- a/hydrogram/handlers/message_handler.py +++ b/hydrogram/handlers/message_handler.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . + from inspect import iscoroutinefunction from typing import Callable diff --git a/hydrogram/methods/pyromod/ask.py b/hydrogram/methods/pyromod/ask.py index 9594971ed..f59410912 100644 --- a/hydrogram/methods/pyromod/ask.py +++ b/hydrogram/methods/pyromod/ask.py @@ -44,17 +44,17 @@ async def ask( :param chat_id: The chat ID(s) to wait for a message from. The first chat ID will be used to send the message. :param text: The text to send. - :param filters: Same as :meth:`pyromod.types.Client.listen`. - :param listener_type: Same as :meth:`pyromod.types.Client.listen`. - :param timeout: Same as :meth:`pyromod.types.Client.listen`. - :param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`. - :param user_id: Same as :meth:`pyromod.types.Client.listen`. - :param message_id: Same as :meth:`pyromod.types.Client.listen`. - :param inline_message_id: Same as :meth:`pyromod.types.Client.listen`. + :param filters: Same as :meth:`hydrogram.Client.listen`. + :param listener_type: Same as :meth:`hydrogram.Client.listen`. + :param timeout: Same as :meth:`hydrogram.Client.listen`. + :param unallowed_click_alert: Same as :meth:`hydrogram.Client.listen`. + :param user_id: Same as :meth:`hydrogram.Client.listen`. + :param message_id: Same as :meth:`hydrogram.Client.listen`. + :param inline_message_id: Same as :meth:`hydrogram.Client.listen`. :param args: Additional arguments to pass to :meth:`hydrogram.Client.send_message`. :param kwargs: Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`. :return: - Same as :meth:`pyromod.types.Client.listen`. The sent message is returned as the attribute ``sent_message``. + Same as :meth:`hydrogram.Client.listen`. The sent message is returned as the attribute ``sent_message``. """ sent_message = None if text.strip() != "": diff --git a/hydrogram/methods/pyromod/get_listener_matching_with_data.py b/hydrogram/methods/pyromod/get_listener_matching_with_data.py index d2005de84..6ba86cb66 100644 --- a/hydrogram/methods/pyromod/get_listener_matching_with_data.py +++ b/hydrogram/methods/pyromod/get_listener_matching_with_data.py @@ -30,8 +30,8 @@ def get_listener_matching_with_data( """ Gets a listener that matches the given data. - :param data: A :class:`pyromod.types.Identifier` to match against. - :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. + :param data: A :class:`hydrogram.types.Identifier` to match against. + :param listener_type: The type of listener to get. Must be a value from :class:`hydrogram.types.ListenerTypes`. :return: The listener that matches the given data or ``None`` if no listener matches. """ matching = [ diff --git a/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py b/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py index 46e78abca..803d61b8c 100644 --- a/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py +++ b/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py @@ -30,12 +30,12 @@ def get_listener_matching_with_identifier_pattern( """ Gets a listener that matches the given identifier pattern. - The difference from :meth:`pyromod.types.Client.get_listener_matching_with_data` is that this method + The difference from :meth:`hydrogram.Client.get_listener_matching_with_data` is that this method intends to get a listener by passing partial info of the listener identifier, while the other method intends to get a listener by passing the full info of the update data, which the listener should match with. - :param pattern: A :class:`pyromod.types.Identifier` to match against. - :param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`. + :param pattern: A :class:`hydrogram.types.Identifier` to match against. + :param listener_type: The type of listener to get. Must be a value from :class:`hydrogram.types.ListenerTypes`. :return: The listener that matches the given identifier pattern or ``None`` if no listener matches. """ matching = [ diff --git a/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py b/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py index bacba645b..25fd6ca44 100644 --- a/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py +++ b/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py @@ -29,10 +29,10 @@ def get_many_listeners_matching_with_data( listener_type: ListenerTypes, ) -> list[Listener]: """ - Same of :meth:`pyromod.types.Client.get_listener_matching_with_data` but returns a list of listeners instead of one. + Same of :meth:`hydrogram.Client.get_listener_matching_with_data` but returns a list of listeners instead of one. - :param data: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. - :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`. + :param data: Same as :meth:`hydrogram.Client.get_listener_matching_with_data`. + :param listener_type: Same as :meth:`hydrogram.Client.get_listener_matching_with_data`. :return: A list of listeners that match the given data. """ return [ diff --git a/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py b/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py index 27c082010..fe4729742 100644 --- a/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py +++ b/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py @@ -29,10 +29,10 @@ def get_many_listeners_matching_with_identifier_pattern( listener_type: ListenerTypes, ) -> list[Listener]: """ - Same of :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one. + Same of :meth:`hydrogram.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one. - :param pattern: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. - :param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`. + :param pattern: Same as :meth:`hydrogram.Client.get_listener_matching_with_identifier_pattern`. + :param listener_type: Same as :meth:`hydrogram.Client.get_listener_matching_with_identifier_pattern`. :return: A list of listeners that match the given identifier pattern. """ return [ diff --git a/hydrogram/methods/pyromod/listen.py b/hydrogram/methods/pyromod/listen.py index 352b5c096..5e64d5701 100644 --- a/hydrogram/methods/pyromod/listen.py +++ b/hydrogram/methods/pyromod/listen.py @@ -46,7 +46,7 @@ async def listen( Creates a listener and waits for it to be fulfilled. :param filters: A filter to check if the listener should be fulfilled. - :param listener_type: The type of listener to create. Defaults to :attr:`pyromod.types.ListenerTypes.MESSAGE`. + :param listener_type: The type of listener to create. Defaults to :attr:`hydrogram.types.ListenerTypes.MESSAGE`. :param timeout: The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``. :param unallowed_click_alert: Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. :param chat_id: The chat ID(s) to listen for. Defaults to ``None``. diff --git a/hydrogram/methods/pyromod/register_next_step_handler.py b/hydrogram/methods/pyromod/register_next_step_handler.py index 9accc0848..0f296874d 100644 --- a/hydrogram/methods/pyromod/register_next_step_handler.py +++ b/hydrogram/methods/pyromod/register_next_step_handler.py @@ -40,13 +40,13 @@ def register_next_step_handler( Registers a listener with a callback to be called when the listener is fulfilled. :param callback: The callback to call when the listener is fulfilled. - :param filters: Same as :meth:`pyromod.types.Client.listen`. - :param listener_type: Same as :meth:`pyromod.types.Client.listen`. - :param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`. - :param chat_id: Same as :meth:`pyromod.types.Client.listen`. - :param user_id: Same as :meth:`pyromod.types.Client.listen`. - :param message_id: Same as :meth:`pyromod.types.Client.listen`. - :param inline_message_id: Same as :meth:`pyromod.types.Client.listen`. + :param filters: Same as :meth:`hydrogram.Client.listen`. + :param listener_type: Same as :meth:`hydrogram.Client.listen`. + :param unallowed_click_alert: Same as :meth:`hydrogram.Client.listen`. + :param chat_id: Same as :meth:`hydrogram.Client.listen`. + :param user_id: Same as :meth:`hydrogram.Client.listen`. + :param message_id: Same as :meth:`hydrogram.Client.listen`. + :param inline_message_id: Same as :meth:`hydrogram.Client.listen`. :return: ``void`` """ pattern = Identifier( diff --git a/hydrogram/methods/pyromod/remove_listener.py b/hydrogram/methods/pyromod/remove_listener.py index 0a1250043..9a6b37a52 100644 --- a/hydrogram/methods/pyromod/remove_listener.py +++ b/hydrogram/methods/pyromod/remove_listener.py @@ -26,7 +26,7 @@ class RemoveListener: def remove_listener(self: "hydrogram.Client", listener: Listener): """ - Removes a listener from the :meth:`pyromod.types.Client.listeners` dictionary. + Removes a listener from the :meth:`hydrogram.Client.listeners` dictionary. :param listener: The listener to remove. :return: ``void`` diff --git a/hydrogram/methods/pyromod/stop_listener.py b/hydrogram/methods/pyromod/stop_listener.py index ce21d1156..ef0cbcef4 100644 --- a/hydrogram/methods/pyromod/stop_listener.py +++ b/hydrogram/methods/pyromod/stop_listener.py @@ -32,7 +32,7 @@ async def stop_listener(self: "hydrogram.Client", listener: Listener): """ Stops a listener, calling stopped_handler if applicable or raising ListenerStopped if throw_exceptions is True. - :param listener: The :class:`pyromod.types.Listener` to stop. + :param listener: The :class:`hydrogram.types.Listener` to stop. :return: ``void`` :raises ListenerStopped: If throw_exceptions is True. """ diff --git a/hydrogram/methods/pyromod/stop_listening.py b/hydrogram/methods/pyromod/stop_listening.py index 930ed7410..ac4f9f101 100644 --- a/hydrogram/methods/pyromod/stop_listening.py +++ b/hydrogram/methods/pyromod/stop_listening.py @@ -34,9 +34,9 @@ async def stop_listening( ): """ Stops all listeners that match the given identifier pattern. - Uses :meth:`pyromod.types.Client.get_many_listeners_matching_with_identifier_pattern`. + Uses :meth:`hydrogram.Client.get_many_listeners_matching_with_identifier_pattern`. - :param listener_type: The type of listener to stop. Must be a value from :class:`pyromod.types.ListenerTypes`. + :param listener_type: The type of listener to stop. Must be a value from :class:`hydrogram.types.ListenerTypes`. :param chat_id: The chat_id to match against. :param user_id: The user_id to match against. :param message_id: The message_id to match against. From ae0ef6f47d227c117bd43fa4f178d4370e92d441 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Mon, 4 Dec 2023 20:14:53 -0300 Subject: [PATCH 07/10] chore: update docstrings --- hydrogram/handlers/callback_query_handler.py | 62 ++++-- hydrogram/handlers/message_handler.py | 49 ++-- hydrogram/helpers/helpers.py | 91 +++++--- hydrogram/methods/pyromod/ask.py | 47 +++- .../get_listener_matching_with_data.py | 12 +- ...stener_matching_with_identifier_pattern.py | 12 +- .../get_many_listeners_matching_with_data.py | 12 +- ...teners_matching_with_identifier_pattern.py | 12 +- hydrogram/methods/pyromod/listen.py | 38 +++- .../pyromod/register_next_step_handler.py | 36 ++- hydrogram/methods/pyromod/remove_listener.py | 5 +- hydrogram/methods/pyromod/stop_listener.py | 12 +- hydrogram/methods/pyromod/stop_listening.py | 21 +- hydrogram/types/messages_and_media/message.py | 23 +- hydrogram/types/user_and_chats/chat.py | 209 ++++++++++++++++-- hydrogram/types/user_and_chats/user.py | 170 ++++++++++++-- 16 files changed, 648 insertions(+), 163 deletions(-) diff --git a/hydrogram/handlers/callback_query_handler.py b/hydrogram/handlers/callback_query_handler.py index f377f2f27..3a7cf1f40 100644 --- a/hydrogram/handlers/callback_query_handler.py +++ b/hydrogram/handlers/callback_query_handler.py @@ -18,7 +18,7 @@ # along with Hydrogram. If not, see . from asyncio import iscoroutinefunction -from typing import Callable +from typing import Callable, Optional import hydrogram from hydrogram.types import CallbackQuery, Identifier, Listener, ListenerTypes @@ -55,12 +55,16 @@ def __init__(self, callback: Callable, filters=None): self.original_callback = callback super().__init__(self.resolve_future_or_callback, filters) - def compose_data_identifier(self, query: CallbackQuery): + def compose_data_identifier(self, query: CallbackQuery) -> Identifier: """ Composes an Identifier object from a CallbackQuery object. - :param query: The CallbackQuery object to compose of. - :return: An Identifier object. + Parameters: + query (:obj:`~hydrogram.types.CallbackQuery`): + The CallbackQuery object to compose of. + + Returns: + :obj:`~hydrogram.types.Identifier`: An Identifier object. """ from_user = query.from_user from_user_id = from_user.id if from_user else None @@ -84,15 +88,20 @@ def compose_data_identifier(self, query: CallbackQuery): async def check_if_has_matching_listener( self, client: "hydrogram.Client", query: CallbackQuery - ) -> tuple[bool, Listener]: + ) -> tuple[bool, Optional[Listener]]: """ Checks if the CallbackQuery object has a matching listener. - :param client: The Client object to check with. - :param query: The CallbackQuery object to check with. - :return: A tuple of a boolean and a Listener object. The boolean indicates whether - the found listener has filters and its filters matches with the CallbackQuery object. - The Listener object is the matching listener. + Parameters: + client (:obj:`~hydrogram.Client`): + The Client object to check with. + + query (:obj:`~hydrogram.types.CallbackQuery`): + The CallbackQuery object to check with. + + Returns: + A tuple of whether the CallbackQuery object has a matching listener and its filters does match with the + CallbackQuery and the matching listener; """ data = self.compose_data_identifier(query) @@ -114,14 +123,19 @@ async def check_if_has_matching_listener( return listener_does_match, listener - async def check(self, client: "hydrogram.Client", query: CallbackQuery): + async def check(self, client: "hydrogram.Client", query: CallbackQuery) -> bool: """ Checks if the CallbackQuery object has a matching listener or handler. - :param client: The Client object to check with. - :param query: The CallbackQuery object to check with. - :return: A boolean indicating whether the CallbackQuery object has a matching listener or the handler - filter matches. + Parameters: + client (:obj:`~hydrogram.Client`): + The Client object to check with. + + query (:obj:`~hydrogram.types.CallbackQuery`): + The CallbackQuery object to check with. + + Returns: + ``bool``: A boolean indicating whether the CallbackQuery object has a matching listener or the handler filter matches. """ listener_does_match, listener = await self.check_if_has_matching_listener(client, query) @@ -167,14 +181,22 @@ async def check(self, client: "hydrogram.Client", query: CallbackQuery): async def resolve_future_or_callback( self, client: "hydrogram.Client", query: CallbackQuery, *args - ): + ) -> None: """ Resolves the future or calls the callback of the listener. Will call the original handler if no listener. - :param client: The Client object to resolve or call with. - :param query: The CallbackQuery object to resolve or call with. - :param args: The arguments to call the callback with. - :return: None + Parameters: + client (:obj:`~hydrogram.Client`): + The Client object to resolve or call with. + + query (:obj:`~hydrogram.types.CallbackQuery`): + The CallbackQuery object to resolve or call with. + + args: + The arguments to call the callback with. + + Returns: + ``None`` """ listener_does_match, listener = await self.check_if_has_matching_listener(client, query) diff --git a/hydrogram/handlers/message_handler.py b/hydrogram/handlers/message_handler.py index 633ac6a0d..d923f9494 100644 --- a/hydrogram/handlers/message_handler.py +++ b/hydrogram/handlers/message_handler.py @@ -18,10 +18,10 @@ # along with Hydrogram. If not, see . from inspect import iscoroutinefunction -from typing import Callable +from typing import Callable, Optional import hydrogram -from hydrogram.types import Identifier, ListenerTypes, Message +from hydrogram.types import Identifier, Listener, ListenerTypes, Message from .handler import Handler @@ -54,14 +54,22 @@ 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: "hydrogram.Client", message: Message): + async def check_if_has_matching_listener( + self, client: "hydrogram.Client", message: Message + ) -> tuple[bool, Optional[Listener]]: """ 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; + Parameters: + client (:obj:`~hydrogram.Client`): + The Client object to check with. + + message (:obj:`~hydrogram.types.Message`): + The Message object to check with. + + Returns: + ``tuple``: A tuple of two elements, the first one is whether the message has a matching listener or not, + the second one is the matching listener if exists. """ from_user = message.from_user from_user_id = from_user.id if from_user else None @@ -93,13 +101,19 @@ async def check_if_has_matching_listener(self, client: "hydrogram.Client", messa return listener_does_match, listener - async def check(self, client: "hydrogram.Client", message: Message): + async def check(self, client: "hydrogram.Client", message: Message) -> bool: """ 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. + Parameters: + client (:obj:`~hydrogram.Client`): + The Client object to check with. + + message (:obj:`~hydrogram.types.Message`): + The Message object to check with. + + Returns: + ``bool``: 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] @@ -123,10 +137,15 @@ async def resolve_future_or_callback( """ 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 + Parameters: + client (:obj:`~hydrogram.Client`): + The Client object to resolve or call with. + + message (:obj:`~hydrogram.types.Message`): + The Message object to resolve or call with. + + args (``tuple``): + Arguments to call the callback with. """ listener_does_match, listener = await self.check_if_has_matching_listener(client, message) diff --git a/hydrogram/helpers/helpers.py b/hydrogram/helpers/helpers.py index 648d60df6..76a525f41 100644 --- a/hydrogram/helpers/helpers.py +++ b/hydrogram/helpers/helpers.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . +from typing import Optional, Union + from hydrogram.types import ( ForceReply, InlineKeyboardButton, @@ -26,11 +28,16 @@ ) -def ikb(rows=None): +def ikb(rows: Optional[list[list[Union[str, tuple[str, str]]]]] = None) -> InlineKeyboardMarkup: """ Create an InlineKeyboardMarkup from a list of lists of buttons. - :param rows: List of lists of buttons. Defaults to empty list. - :return: InlineKeyboardMarkup + + Parameters: + rows (List[List[Union[str, Tuple[str, str]]]]): + List of lists of buttons. Defaults to empty list. + + Returns: + :obj:`~hydrogram.types.InlineKeyboardMarkup`: An InlineKeyboardMarkup object. """ if rows is None: rows = [] @@ -48,26 +55,38 @@ def ikb(rows=None): # return {'inline_keyboard': lines} -def btn(text, value, type="callback_data"): +def btn(text: str, value: str, type="callback_data") -> InlineKeyboardButton: """ Create an InlineKeyboardButton. - :param text: Text of the button. - :param value: Value of the button. - :param type: Type of the button. Defaults to "callback_data". - :return: InlineKeyboardButton + Parameters: + text (str): + Text of the button. + + value (str): + Value of the button. + + type (str): + Type of the button. Defaults to "callback_data". + + Returns: + :obj:`~hydrogram.types.InlineKeyboardButton`: An InlineKeyboardButton object. """ return InlineKeyboardButton(text, **{type: value}) # return {'text': text, type: value} -# The inverse of above -def bki(keyboard): +# The inverse of ikb() +def bki(keyboard: InlineKeyboardButton) -> list[list[Union[str, tuple[str, str]]]]: """ Create a list of lists of buttons from an InlineKeyboardMarkup. - :param keyboard: InlineKeyboardMarkup - :return: List of lists of buttons + Parameters: + keyboard (:obj:`~hydrogram.types.InlineKeyboardMarkup`): + An InlineKeyboardMarkup object. + + Returns: + List of lists of buttons. """ lines = [] for row in keyboard.inline_keyboard: @@ -80,12 +99,16 @@ def bki(keyboard): # return ikb() format -def ntb(button): +def ntb(button: InlineKeyboardButton) -> list: """ Create a button list from an InlineKeyboardButton. - :param button: InlineKeyboardButton - :return: Button as a list to be used in btn() + Parameters: + button (:obj:`~hydrogram.types.InlineKeyboardButton`): + An InlineKeyboardButton object. + + Returns: + ``list``: A button list. """ for btn_type in [ "callback_data", @@ -104,13 +127,19 @@ def ntb(button): # return {'text': text, type: value} -def kb(rows=None, **kwargs): +def kb(rows=None, **kwargs) -> ReplyKeyboardMarkup: """ Create a ReplyKeyboardMarkup from a list of lists of buttons. - :param rows: List of lists of buttons. Defaults to empty list. - :param kwargs: Other arguments to pass to ReplyKeyboardMarkup. - :return: ReplyKeyboardMarkup + Parameters: + rows (List[List[str]]): + List of lists of buttons. Defaults to an empty list. + + kwargs: + Other arguments to pass to ReplyKeyboardMarkup. + + Returns: + :obj:`~hydrogram.types.ReplyKeyboardMarkup`: A ReplyKeyboardMarkup object. """ if rows is None: rows = [] @@ -136,22 +165,32 @@ def kb(rows=None, **kwargs): """ -def force_reply(selective=True): +def force_reply(selective=True) -> ForceReply: """ Create a ForceReply. - :param selective: Whether the reply should be selective. Defaults to True. - :return: ForceReply + Parameters: + selective (bool): + Whether the reply should be selective. Defaults to True. + + Returns: + :obj:`~hydrogram.types.ForceReply`: A ForceReply object. """ return ForceReply(selective=selective) -def array_chunk(input_array, size): +def array_chunk(input_array, size) -> list[list]: """ Split an array into chunks. - :param input_array: The array to split. - :param size: The size of each chunk. - :return: List of chunks. + Parameters: + input_array (list): + The array to split. + + size (int): + The size of each chunk. + + Returns: + list: A list of chunks. """ return [input_array[i : i + size] for i in range(0, len(input_array), size)] diff --git a/hydrogram/methods/pyromod/ask.py b/hydrogram/methods/pyromod/ask.py index f59410912..5c5779324 100644 --- a/hydrogram/methods/pyromod/ask.py +++ b/hydrogram/methods/pyromod/ask.py @@ -42,18 +42,41 @@ async def ask( """ Sends a message and waits for a response. - :param chat_id: The chat ID(s) to wait for a message from. The first chat ID will be used to send the message. - :param text: The text to send. - :param filters: Same as :meth:`hydrogram.Client.listen`. - :param listener_type: Same as :meth:`hydrogram.Client.listen`. - :param timeout: Same as :meth:`hydrogram.Client.listen`. - :param unallowed_click_alert: Same as :meth:`hydrogram.Client.listen`. - :param user_id: Same as :meth:`hydrogram.Client.listen`. - :param message_id: Same as :meth:`hydrogram.Client.listen`. - :param inline_message_id: Same as :meth:`hydrogram.Client.listen`. - :param args: Additional arguments to pass to :meth:`hydrogram.Client.send_message`. - :param kwargs: Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`. - :return: + Parameters: + chat_id (``Union[int, str], List[Union[int, str]]``): + The chat ID(s) to wait for a message from. The first chat ID will be used to send the message. + + text (``str``): + The text to send. + + filters (``Optional[Filter]``): + Same as :meth:`hydrogram.Client.listen`. + + listener_type (``ListenerTypes``): + Same as :meth:`hydrogram.Client.listen`. + + timeout (``Optional[int]``): + Same as :meth:`hydrogram.Client.listen`. + + unallowed_click_alert (``bool``): + Same as :meth:`hydrogram.Client.listen`. + + user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + Same as :meth:`hydrogram.Client.listen`. + + message_id (``Optional[Union[int, List[int]]]``): + Same as :meth:`hydrogram.Client.listen`. + + inline_message_id (``Optional[Union[str, List[str]]]``): + Same as :meth:`hydrogram.Client.listen`. + + args (``Any``): + Additional arguments to pass to :meth:`hydrogram.Client.send_message`. + + kwargs (``Any``): + Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`. + + Returns: Same as :meth:`hydrogram.Client.listen`. The sent message is returned as the attribute ``sent_message``. """ sent_message = None diff --git a/hydrogram/methods/pyromod/get_listener_matching_with_data.py b/hydrogram/methods/pyromod/get_listener_matching_with_data.py index 6ba86cb66..9655119e5 100644 --- a/hydrogram/methods/pyromod/get_listener_matching_with_data.py +++ b/hydrogram/methods/pyromod/get_listener_matching_with_data.py @@ -30,9 +30,15 @@ def get_listener_matching_with_data( """ Gets a listener that matches the given data. - :param data: A :class:`hydrogram.types.Identifier` to match against. - :param listener_type: The type of listener to get. Must be a value from :class:`hydrogram.types.ListenerTypes`. - :return: The listener that matches the given data or ``None`` if no listener matches. + Parameters: + data (:obj:`~hydrogram.types.Identifier`): + The data to match against. + + listener_type (:obj:`~hydrogram.types.ListenerTypes`): + The type of listener to get. + + Returns: + :obj:`~hydrogram.types.Listener`: The listener that matches the given data or ``None`` if no listener matches. """ matching = [ listener diff --git a/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py b/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py index 803d61b8c..0edc7b160 100644 --- a/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py +++ b/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py @@ -34,9 +34,15 @@ def get_listener_matching_with_identifier_pattern( intends to get a listener by passing partial info of the listener identifier, while the other method intends to get a listener by passing the full info of the update data, which the listener should match with. - :param pattern: A :class:`hydrogram.types.Identifier` to match against. - :param listener_type: The type of listener to get. Must be a value from :class:`hydrogram.types.ListenerTypes`. - :return: The listener that matches the given identifier pattern or ``None`` if no listener matches. + Parameters: + pattern (:obj:`~hydrogram.types.Identifier`): + The identifier pattern to match against. + + listener_type (:obj:`~hydrogram.types.ListenerTypes`): + The type of listener to get. + + Returns: + :obj:`~hydrogram.types.Listener`: The listener that matches the given identifier pattern or ``None`` if no listener matches. """ matching = [ listener diff --git a/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py b/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py index 25fd6ca44..7c254fbeb 100644 --- a/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py +++ b/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py @@ -31,9 +31,15 @@ def get_many_listeners_matching_with_data( """ Same of :meth:`hydrogram.Client.get_listener_matching_with_data` but returns a list of listeners instead of one. - :param data: Same as :meth:`hydrogram.Client.get_listener_matching_with_data`. - :param listener_type: Same as :meth:`hydrogram.Client.get_listener_matching_with_data`. - :return: A list of listeners that match the given data. + Parameters: + data (:obj:`~hydrogram.types.Identifier`): + Same as :meth:`hydrogram.Client.get_listener_matching_with_data`. + + listener_type (:obj:`~hydrogram.types.ListenerTypes`): + Same as :meth:`hydrogram.Client.get_listener_matching_with_data`. + + Returns: + List[:obj:`~hydrogram.types.Listener`]: A list of listeners that match the given data. """ return [ listener diff --git a/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py b/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py index fe4729742..608004913 100644 --- a/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py +++ b/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py @@ -31,9 +31,15 @@ def get_many_listeners_matching_with_identifier_pattern( """ Same of :meth:`hydrogram.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one. - :param pattern: Same as :meth:`hydrogram.Client.get_listener_matching_with_identifier_pattern`. - :param listener_type: Same as :meth:`hydrogram.Client.get_listener_matching_with_identifier_pattern`. - :return: A list of listeners that match the given identifier pattern. + Parameters: + pattern (:obj:`~hydrogram.types.Identifier`): + Same as :meth:`hydrogram.Client.get_listener_matching_with_identifier_pattern`. + + listener_type (:obj:`~hydrogram.types.ListenerTypes`): + Same as :meth:`hydrogram.Client.get_listener_matching_with_identifier_pattern`. + + Returns: + List[:obj:`~hydrogram.types.Listener`]: A list of listeners that match the given identifier pattern. """ return [ listener diff --git a/hydrogram/methods/pyromod/listen.py b/hydrogram/methods/pyromod/listen.py index 5e64d5701..a9e543eb9 100644 --- a/hydrogram/methods/pyromod/listen.py +++ b/hydrogram/methods/pyromod/listen.py @@ -41,19 +41,37 @@ async def listen( user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, - ): + ) -> Union["hydrogram.types.Message", "hydrogram.types.CallbackQuery"]: """ Creates a listener and waits for it to be fulfilled. - :param filters: A filter to check if the listener should be fulfilled. - :param listener_type: The type of listener to create. Defaults to :attr:`hydrogram.types.ListenerTypes.MESSAGE`. - :param timeout: The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``. - :param unallowed_click_alert: Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. - :param chat_id: The chat ID(s) to listen for. Defaults to ``None``. - :param user_id: The user ID(s) to listen for. Defaults to ``None``. - :param message_id: The message ID(s) to listen for. Defaults to ``None``. - :param inline_message_id: The inline message ID(s) to listen for. Defaults to ``None``. - :return: The Message or CallbackQuery that fulfilled the listener. + Parameters: + filters (``Optional[Filter]``): + A filter to check if the listener should be fulfilled. + + listener_type (``ListenerTypes``): + The type of listener to create. Defaults to :attr:`hydrogram.types.ListenerTypes.MESSAGE`. + + timeout (``Optional[int]``): + The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``. + + unallowed_click_alert (``bool``): + Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. + + chat_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The chat ID(s) to listen for. Defaults to ``None``. + + user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The user ID(s) to listen for. Defaults to ``None``. + + message_id (``Optional[Union[int, List[int]]]``): + The message ID(s) to listen for. Defaults to ``None``. + + inline_message_id (``Optional[Union[str, List[str]]]``): + The inline message ID(s) to listen for. Defaults to ``None``. + + Returns: + ``Union[Message, CallbackQuery]``: The Message or CallbackQuery that fulfilled the listener. """ pattern = Identifier( from_user_id=user_id, diff --git a/hydrogram/methods/pyromod/register_next_step_handler.py b/hydrogram/methods/pyromod/register_next_step_handler.py index 0f296874d..84655db66 100644 --- a/hydrogram/methods/pyromod/register_next_step_handler.py +++ b/hydrogram/methods/pyromod/register_next_step_handler.py @@ -39,15 +39,33 @@ def register_next_step_handler( """ Registers a listener with a callback to be called when the listener is fulfilled. - :param callback: The callback to call when the listener is fulfilled. - :param filters: Same as :meth:`hydrogram.Client.listen`. - :param listener_type: Same as :meth:`hydrogram.Client.listen`. - :param unallowed_click_alert: Same as :meth:`hydrogram.Client.listen`. - :param chat_id: Same as :meth:`hydrogram.Client.listen`. - :param user_id: Same as :meth:`hydrogram.Client.listen`. - :param message_id: Same as :meth:`hydrogram.Client.listen`. - :param inline_message_id: Same as :meth:`hydrogram.Client.listen`. - :return: ``void`` + Parameters: + callback (``Callable``): + The callback to call when the listener is fulfilled. + + filters (``Optional[Filter]``): + Same as :meth:`hydrogram.Client.listen`. + + listener_type (``ListenerTypes``): + Same as :meth:`hydrogram.Client.listen`. + + unallowed_click_alert (``bool``): + Same as :meth:`hydrogram.Client.listen`. + + chat_id (``Union[int, str], List[Union[int, str]]``): + Same as :meth:`hydrogram.Client.listen`. + + user_id (``Union[int, str], List[Union[int, str]]``): + Same as :meth:`hydrogram.Client.listen`. + + message_id (``Union[int, List[int]]``): + Same as :meth:`hydrogram.Client.listen`. + + inline_message_id (``Union[str, List[str]]``): + Same as :meth:`hydrogram.Client.listen`. + + Returns: + ``None`` """ pattern = Identifier( from_user_id=user_id, diff --git a/hydrogram/methods/pyromod/remove_listener.py b/hydrogram/methods/pyromod/remove_listener.py index 9a6b37a52..97be66361 100644 --- a/hydrogram/methods/pyromod/remove_listener.py +++ b/hydrogram/methods/pyromod/remove_listener.py @@ -28,8 +28,9 @@ def remove_listener(self: "hydrogram.Client", listener: Listener): """ Removes a listener from the :meth:`hydrogram.Client.listeners` dictionary. - :param listener: The listener to remove. - :return: ``void`` + Parameters: + listener (:obj:`~hydrogram.types.Listener`): + The listener to remove. """ with contextlib.suppress(ValueError): self.listeners[listener.listener_type].remove(listener) diff --git a/hydrogram/methods/pyromod/stop_listener.py b/hydrogram/methods/pyromod/stop_listener.py index ef0cbcef4..0b62c96a2 100644 --- a/hydrogram/methods/pyromod/stop_listener.py +++ b/hydrogram/methods/pyromod/stop_listener.py @@ -32,9 +32,15 @@ async def stop_listener(self: "hydrogram.Client", listener: Listener): """ Stops a listener, calling stopped_handler if applicable or raising ListenerStopped if throw_exceptions is True. - :param listener: The :class:`hydrogram.types.Listener` to stop. - :return: ``void`` - :raises ListenerStopped: If throw_exceptions is True. + Parameters: + listener (:obj:`~hydrogram.types.Listener`): + The :class:`hydrogram.types.Listener` to stop. + + Returns: + None + + Raises: + ListenerStopped: If throw_exceptions is True. """ self.remove_listener(listener) diff --git a/hydrogram/methods/pyromod/stop_listening.py b/hydrogram/methods/pyromod/stop_listening.py index ac4f9f101..8425b91aa 100644 --- a/hydrogram/methods/pyromod/stop_listening.py +++ b/hydrogram/methods/pyromod/stop_listening.py @@ -36,12 +36,21 @@ async def stop_listening( Stops all listeners that match the given identifier pattern. Uses :meth:`hydrogram.Client.get_many_listeners_matching_with_identifier_pattern`. - :param listener_type: The type of listener to stop. Must be a value from :class:`hydrogram.types.ListenerTypes`. - :param chat_id: The chat_id to match against. - :param user_id: The user_id to match against. - :param message_id: The message_id to match against. - :param inline_message_id: The inline_message_id to match against. - :return: ``void`` + Parameters: + listener_type (:obj:`~hydrogram.types.ListenerTypes`): + The type of listener to stop. Must be a value from :class:`hydrogram.types.ListenerTypes`. + + chat_id (``Union[int, str], List[Union[int, str]]``): + The chat_id to match against. + + user_id (``Union[int, str], List[Union[int, str]]``): + The user_id to match against. + + message_id (``Union[int, List[int]]``): + The message_id to match against. + + inline_message_id (``Union[str, List[str]]``): + The inline_message_id to match against. """ pattern = Identifier( from_user_id=user_id, diff --git a/hydrogram/types/messages_and_media/message.py b/hydrogram/types/messages_and_media/message.py index 52eb8476d..48dfba635 100644 --- a/hydrogram/types/messages_and_media/message.py +++ b/hydrogram/types/messages_and_media/message.py @@ -511,15 +511,26 @@ async def wait_for_click( timeout: Optional[int] = None, filters=None, alert: Union[str, bool] = True, - ): + ) -> "types.CallbackQuery": """ Waits for a callback query to be clicked on the message. - :param from_user_id: The user ID(s) to wait for. If None, waits for any user. - :param timeout: The timeout in seconds. If None, waits forever. - :param filters: The filters to pass to Client.listen(). - :param alert: The alert to show when the button is clicked by users that are not allowed in from_user_id. - :return: The callback query that was clicked. + Parameters: + from_user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The user ID(s) to wait for. If None, waits for any user. + + timeout (``Optional[int]``): + The timeout in seconds. If None, waits forever. + + filters (``Optional[Filter]``): + A filter to check if the callback query should be accepted. + + alert (``Union[str, bool]``): + The alert to show when the button is clicked by users that are not allowed in from_user_id. + If True, shows the default alert. If False, shows no alert. + + Returns: + :obj:`~hydrogram.types.CallbackQuery`: The callback query that was clicked. """ message_id = getattr(self, "id", getattr(self, "message_id", None)) diff --git a/hydrogram/types/user_and_chats/chat.py b/hydrogram/types/user_and_chats/chat.py index 5f9beb29c..a1319e8d7 100644 --- a/hydrogram/types/user_and_chats/chat.py +++ b/hydrogram/types/user_and_chats/chat.py @@ -22,7 +22,8 @@ from typing import BinaryIO, Optional, Union import hydrogram -from hydrogram import enums, raw, types, utils +from hydrogram import enums, filters, raw, types, utils +from hydrogram.types import ListenerTypes from hydrogram.types.object import Object @@ -405,35 +406,199 @@ def _parse_chat( return Chat._parse_user_chat(client, chat) return Chat._parse_channel_chat(client, chat) - def listen(self, *args, **kwargs): + def listen( + self, + filters: Optional["filters.Filter"] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): """ - Listens for messages in the chat. Calls Client.listen() with the chat_id set to the chat's id. + Bound method *listen* of :obj:`~hydrogram.types.Chat`. + + Use as a shortcut for: + + .. code-block:: python + + await client.listen( + chat_id=chat_id + ) + + Example: + .. code-block:: python + + await chat.listen() + + Parameters: + filters (``Optional[filters.Filter]``): + A filter to check if the listener should be fulfilled. + + listener_type (``ListenerTypes``): + The type of listener to create. Defaults to :attr:`hydrogram.types.ListenerTypes.MESSAGE`. + + timeout (``Optional[int]``): + The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``. + + unallowed_click_alert (``bool``): + Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. + + chat_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The chat ID(s) to listen for. Defaults to ``None``. + + user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The user ID(s) to listen for. Defaults to ``None``. + + message_id (``Optional[Union[int, List[int]]]``): + The message ID(s) to listen for. Defaults to ``None``. - :param args: Arguments to pass to Client.listen(). - :param kwargs: Keyword arguments to pass to Client.listen(). - :return: The return value of Client.listen(). + inline_message_id (``Optional[Union[str, List[str]]]``): + The inline message ID(s) to listen for. Defaults to ``None``. + + Returns: + Union[:obj:`~hydrogram.types.Message`, :obj:`~hydrogram.types.CallbackQuery`]: The Message or CallbackQuery """ - return self._client.listen(*args, chat_id=self.id, **kwargs) + return self._client.listen( + chat_id=self.id, + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + user_id=user_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) - def ask(self, text, *args, **kwargs): + def ask( + self, + text: str, + filters: Optional["filters.Filter"] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + *args, + **kwargs, + ): """ - Asks a question in the chat. Calls Client.ask() with the chat_id set to the chat's id. - :param text: The text to send. - :param args: Arguments to pass to Client.ask(). - :param kwargs: Keyword arguments to pass to Client.ask(). - :return: The return value of Client.ask(). + Bound method *ask* of :obj:`~hydrogram.types.Chat`. + + Use as a shortcut for: + + .. code-block:: python + + await client.ask( + chat_id=chat_id, + text=text + ) + + Example: + + .. code-block:: python + + await chat.ask("What's your name?") + + Parameters: + text (``str``): + The text to send. + + filters (``Optional[filters.Filter]``): + Same as :meth:`hydrogram.Client.listen`. + + listener_type (``ListenerTypes``): + Same as :meth:`hydrogram.Client.listen`. + + timeout (``Optional[int]``): + Same as :meth:`hydrogram.Client.listen`. + + unallowed_click_alert (``bool``): + Same as :meth:`hydrogram.Client.listen`. + + chat_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The chat ID(s) to listen for. Defaults to ``None``. + + user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The user ID(s) to listen for. Defaults to ``None``. + + message_id (``Optional[Union[int, List[int]]]``): + The message ID(s) to listen for. Defaults to ``None``. + + inline_message_id (``Optional[Union[str, List[str]]]``): + The inline message ID(s) to listen for. Defaults to ``None``. + + args (``Any``): + Additional arguments to pass to :meth:`hydrogram.Client.send_message`. + + kwargs (``Any``): + Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`. + + Returns: + Union[:obj:`~hydrogram.types.Message`, :obj:`~hydrogram.types.CallbackQuery`]: The Message or CallbackQuery """ - return self._client.ask(self.id, text, *args, **kwargs) + return self._client.ask( + chat_id=self.id, + text=text, + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + user_id=user_id, + message_id=message_id, + inline_message_id=inline_message_id, + *args, + **kwargs, + ) - def stop_listening(self, *args, **kwargs): + def stop_listening( + self, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): """ - Stops listening for messages in the chat. Calls Client.stop_listening() with the chat_id set to the chat's id. + Bound method *stop_listening* of :obj:`~hydrogram.types.Chat`. - :param args: Arguments to pass to Client.stop_listening(). - :param kwargs: Keyword arguments to pass to Client.stop_listening(). - :return: The return value of Client.stop_listening(). + Use as a shortcut for: + + .. code-block:: python + + await client.stop_listening( + chat_id=chat_id + ) + + Example: + .. code-block:: python + + await chat.stop_listening() + + Parameters: + listener_type (``ListenerTypes``): + The type of listener to stop listening for. Defaults to :attr:`hydrogram.types.ListenerTypes.MESSAGE`. + + user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The user ID(s) to stop listening for. Defaults to ``None``. + + message_id (``Optional[Union[int, List[int]]]``): + The message ID(s) to stop listening for. Defaults to ``None``. + + inline_message_id (``Optional[Union[str, List[str]]]``): + The inline message ID(s) to stop listening for. Defaults to ``None``. + + Returns: + ``bool``: The return value of :meth:`hydrogram.Client.stop_listening`. """ - return self._client.stop_listening(*args, chat_id=self.id, **kwargs) + return self._client.stop_listening( + chat_id=self.id, + listener_type=listener_type, + user_id=user_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) async def archive(self): """Bound method *archive* of :obj:`~hydrogram.types.Chat`. @@ -442,7 +607,7 @@ async def archive(self): .. code-block:: python - await client.archive_chats(-100123456789) + await client.archive_chats(chat_id=chat_id) Example: .. code-block:: python @@ -465,7 +630,7 @@ async def unarchive(self): .. code-block:: python - await client.unarchive_chats(-100123456789) + await client.unarchive_chats(chat_id=chat_id) Example: .. code-block:: python diff --git a/hydrogram/types/user_and_chats/user.py b/hydrogram/types/user_and_chats/user.py index 5de772389..4b4907aa6 100644 --- a/hydrogram/types/user_and_chats/user.py +++ b/hydrogram/types/user_and_chats/user.py @@ -19,11 +19,12 @@ import html from datetime import datetime -from typing import Optional +from typing import Optional, Union import hydrogram -from hydrogram import enums, raw, types, utils +from hydrogram import enums, filters, raw, types, utils from hydrogram.types.object import Object +from hydrogram.types.pyromod import ListenerTypes from hydrogram.types.update import Update @@ -301,36 +302,165 @@ def _parse_user_status(client, user_status: "raw.types.UpdateUserStatus"): client=client, ) - def listen(self, *args, **kwargs): + def listen( + self, + filters: Optional["filters.Filter"] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): """ - Listens for messages from the user. Calls Client.listen() with the user_id set to the user's id. + Bound method *listen* of :obj:`~hydrogram.types.User`. + + Use as a shortcut for: + + .. code-block:: python + + client.listen(user_id=user.id) + + Example: + .. code-block:: python + + user.listen() + + Parameters: + filters (``Optional[hydrogram.Filter]``): + Same as :meth:`hydrogram.Client.listen`. + + listener_type (``ListenerTypes``): + Same as :meth:`hydrogram.Client.listen`. - :param args: Arguments to pass to Client.listen(). - :param kwargs: Keyword arguments to pass to Client.listen(). - :return: The return value of Client.listen(). + timeout (``Optional[int]``): + Same as :meth:`hydrogram.Client.listen`. + + unallowed_click_alert (``bool``): + Same as :meth:`hydrogram.Client.listen`. + + chat_id (``Union[int, str], List[Union[int, str]]``): + Same as :meth:`hydrogram.Client.listen`. + + message_id (``Union[int, List[int]]``): + Same as :meth:`hydrogram.Client.listen`. + + inline_message_id (``Union[str, List[str]]``): + Same as :meth:`hydrogram.Client.listen`. + + Returns: + ``Union[Message, CallbackQuery]``: The Message or CallbackQuery that fulfilled the listener. """ - return self._client.listen(*args, user_id=self.id, **kwargs) + return self._client.listen( + user_id=self.id, + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + chat_id=self.id, + message_id=message_id, + inline_message_id=inline_message_id, + ) - def ask(self, text, *args, **kwargs): + def ask( + self, + text: str, + filters: Optional["filters.Filter"] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + *args, + **kwargs, + ): """ - Asks a question to the user. Calls Client.ask() with both chat_id and user_id set to the user's id. + Bound method *ask* of :obj:`~hydrogram.types.User`. - :param text: The text to send. - :param args: Arguments to pass to Client.ask(). - :param kwargs: Keyword arguments to pass to Client.ask(). - :return: The return value of Client.ask(). + Use as a shortcut for: + + .. code-block:: python + + client.ask(user_id=user.id) + + Example: + .. code-block:: python + + user.ask("Hello!") + + Parameters: + text (``str``): + Same as :meth:`hydrogram.Client.ask`. + + filters (``Optional[hydrogram.Filter]``): + Same as :meth:`hydrogram.Client.ask`. + + listener_type (``ListenerTypes``): + Same as :meth:`hydrogram.Client.ask`. + + timeout (``Optional[int]``): + Same as :meth:`hydrogram.Client.ask`. + + unallowed_click_alert (``bool``): + Same as :meth:`hydrogram.Client.ask`. + + message_id (``Union[int, List[int]]``): + Same as :meth:`hydrogram.Client.ask`. + + inline_message_id (``Union[str, List[str]]``): + Same as :meth:`hydrogram.Client.ask`. + + args (``Any``): + Same as :meth:`hydrogram.Client.ask`. + + kwargs (``Any``): + Same as :meth:`hydrogram.Client.ask`. + + Returns: + ``Union[Message, CallbackQuery]``: The Message or CallbackQuery that fulfilled the listener. """ - return self._client.ask(self.id, text, *args, user_id=self.id, **kwargs) + return self._client.ask( + chat_id=self.id, + text=text, + user_id=self.id, + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + message_id=message_id, + inline_message_id=inline_message_id, + *args, + **kwargs, + ) - def stop_listening(self, *args, **kwargs): + def stop_listening( + self, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): """ Stops listening for messages from the user. Calls Client.stop_listening() with the user_id set to the user's id. - :param args: Arguments to pass to Client.stop_listening(). - :param kwargs: Keyword arguments to pass to Client.stop_listening(). - :return: The return value of Client.stop_listening(). + Parameters: + listener_type (``ListenerTypes``): + Same as :meth:`hydrogram.Client.stop_listening`. + + message_id (``Union[int, List[int]]``): + Same as :meth:`hydrogram.Client.stop_listening`. + + inline_message_id (``Union[str, List[str]]``): + Same as :meth:`hydrogram.Client.stop_listening`. + + Returns: + ``None`` """ - return self._client.stop_listening(*args, user_id=self.id, **kwargs) + return self._client.stop_listening( + user_id=self.id, + listener_type=listener_type, + chat_id=self.id, + message_id=message_id, + inline_message_id=inline_message_id, + ) async def archive(self): """Bound method *archive* of :obj:`~hydrogram.types.User`. From 7db2eed6896d447310c48db02bd86d9b33370e6e Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Wed, 6 Dec 2023 12:22:22 -0300 Subject: [PATCH 08/10] chore: readd chat_id param where removed and update docs --- compiler/docs/compiler.py | 6 ++++++ hydrogram/types/user_and_chats/chat.py | 6 ------ hydrogram/types/user_and_chats/user.py | 9 +++++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 3604c9762..0e11175a7 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -581,6 +581,9 @@ def get_title_list(s: str) -> list: Chat.mark_unread Chat.set_protected_content Chat.unpin_all_messages + Chat.ask + Chat.listen + Chat.stop_listening """, "user": """ User @@ -588,6 +591,9 @@ def get_title_list(s: str) -> list: User.unarchive User.block User.unblock + User.ask + User.listen + User.stop_listening """, "callback_query": """ Callback Query diff --git a/hydrogram/types/user_and_chats/chat.py b/hydrogram/types/user_and_chats/chat.py index a1319e8d7..e9b0070ca 100644 --- a/hydrogram/types/user_and_chats/chat.py +++ b/hydrogram/types/user_and_chats/chat.py @@ -445,9 +445,6 @@ def listen( unallowed_click_alert (``bool``): Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. - chat_id (``Optional[Union[int, str], List[Union[int, str]]]``): - The chat ID(s) to listen for. Defaults to ``None``. - user_id (``Optional[Union[int, str], List[Union[int, str]]]``): The user ID(s) to listen for. Defaults to ``None``. @@ -518,9 +515,6 @@ def ask( unallowed_click_alert (``bool``): Same as :meth:`hydrogram.Client.listen`. - chat_id (``Optional[Union[int, str], List[Union[int, str]]]``): - The chat ID(s) to listen for. Defaults to ``None``. - user_id (``Optional[Union[int, str], List[Union[int, str]]]``): The user ID(s) to listen for. Defaults to ``None``. diff --git a/hydrogram/types/user_and_chats/user.py b/hydrogram/types/user_and_chats/user.py index 4b4907aa6..870ceeffe 100644 --- a/hydrogram/types/user_and_chats/user.py +++ b/hydrogram/types/user_and_chats/user.py @@ -308,6 +308,7 @@ def listen( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): @@ -356,7 +357,7 @@ def listen( listener_type=listener_type, timeout=timeout, unallowed_click_alert=unallowed_click_alert, - chat_id=self.id, + chat_id=chat_id, message_id=message_id, inline_message_id=inline_message_id, ) @@ -435,6 +436,7 @@ def ask( def stop_listening( self, listener_type: ListenerTypes = ListenerTypes.MESSAGE, + chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): @@ -445,6 +447,9 @@ def stop_listening( listener_type (``ListenerTypes``): Same as :meth:`hydrogram.Client.stop_listening`. + chat_id (``Union[int, str], List[Union[int, str]]``): + Same as :meth:`hydrogram.Client.stop_listening`. + message_id (``Union[int, List[int]]``): Same as :meth:`hydrogram.Client.stop_listening`. @@ -457,7 +462,7 @@ def stop_listening( return self._client.stop_listening( user_id=self.id, listener_type=listener_type, - chat_id=self.id, + chat_id=chat_id, message_id=message_id, inline_message_id=inline_message_id, ) From 0671877de11fb3b9b6a90c25d5ffbc150926d5c5 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sat, 6 Jan 2024 00:47:18 -0300 Subject: [PATCH 09/10] feat(pyromod): add `Message.{listen,ask,stop_listening}` --- hydrogram/types/messages_and_media/message.py | 182 +++++++++++++++++- 1 file changed, 181 insertions(+), 1 deletion(-) diff --git a/hydrogram/types/messages_and_media/message.py b/hydrogram/types/messages_and_media/message.py index 48dfba635..988e5f60d 100644 --- a/hydrogram/types/messages_and_media/message.py +++ b/hydrogram/types/messages_and_media/message.py @@ -24,11 +24,12 @@ from typing import BinaryIO, Callable, Optional, Union import hydrogram -from hydrogram import enums, raw, types, utils +from hydrogram import enums, filters, raw, types, utils from hydrogram.errors import MessageIdsEmpty, PeerIdInvalid from hydrogram.parser import Parser from hydrogram.parser import utils as parser_utils from hydrogram.types.object import Object +from hydrogram.types.pyromod import ListenerTypes from hydrogram.types.update import Update log = logging.getLogger(__name__) @@ -1030,6 +1031,185 @@ async def _parse( return parsed_message return None + def listen( + self, + filters: Optional["filters.Filter"] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): + """ + Bound method *listen* of :obj:`~hydrogram.types.Chat`. + + Use as a shortcut for: + + .. code-block:: python + + await client.listen( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + await chat.listen() + + Parameters: + filters (``Optional[filters.Filter]``): + A filter to check if the listener should be fulfilled. + + listener_type (``ListenerTypes``): + The type of listener to create. Defaults to :attr:`hydrogram.types.ListenerTypes.MESSAGE`. + + timeout (``Optional[int]``): + The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``. + + unallowed_click_alert (``bool``): + Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. + + message_id (``Optional[Union[int, List[int]]]``): + The message ID(s) to listen for. Defaults to ``None``. + + inline_message_id (``Optional[Union[str, List[str]]]``): + The inline message ID(s) to listen for. Defaults to ``None``. + + Returns: + Union[:obj:`~hydrogram.types.Message`, :obj:`~hydrogram.types.CallbackQuery`]: The Message or CallbackQuery + """ + return self._client.listen( + chat_id=self.chat.id if self.chat else None, + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + user_id=self.from_user.id if self.from_user else None, + message_id=message_id, + inline_message_id=inline_message_id, + ) + + def ask( + self, + text: str, + filters: Optional["filters.Filter"] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + *args, + **kwargs, + ): + """ + Bound method *ask* of :obj:`~hydrogram.types.Chat`. + + Use as a shortcut for: + + .. code-block:: python + + await client.ask( + chat_id=chat_id, + user_id=user_id, + text=text + ) + + Example: + + .. code-block:: python + + await chat.ask("What's your name?") + + Parameters: + text (``str``): + The text to send. + + filters (``Optional[filters.Filter]``): + Same as :meth:`hydrogram.Client.listen`. + + listener_type (``ListenerTypes``): + Same as :meth:`hydrogram.Client.listen`. + + timeout (``Optional[int]``): + Same as :meth:`hydrogram.Client.listen`. + + unallowed_click_alert (``bool``): + Same as :meth:`hydrogram.Client.listen`. + + message_id (``Optional[Union[int, List[int]]]``): + The message ID(s) to listen for. Defaults to ``None``. + + inline_message_id (``Optional[Union[str, List[str]]]``): + The inline message ID(s) to listen for. Defaults to ``None``. + + args (``Any``): + Additional arguments to pass to :meth:`hydrogram.Client.send_message`. + + kwargs (``Any``): + Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`. + + Returns: + Union[:obj:`~hydrogram.types.Message`, :obj:`~hydrogram.types.CallbackQuery`]: The Message or CallbackQuery + """ + return self._client.ask( + chat_id=self.chat.id if self.chat else None, + text=text, + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + user_id=self.from_user.id if self.from_user else None, + message_id=message_id, + inline_message_id=inline_message_id, + *args, + **kwargs, + ) + + def stop_listening( + self, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + message_id: Optional[Union[int, list[int]]] = None, + inline_message_id: Optional[Union[str, list[str]]] = None, + ): + """ + Bound method *stop_listening* of :obj:`~hydrogram.types.Chat`. + + Use as a shortcut for: + + .. code-block:: python + + await client.stop_listening( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + await chat.stop_listening() + + Parameters: + listener_type (``ListenerTypes``): + The type of listener to stop listening for. Defaults to :attr:`hydrogram.types.ListenerTypes.MESSAGE`. + + message_id (``Optional[Union[int, List[int]]]``): + The message ID(s) to stop listening for. Defaults to ``None``. + + inline_message_id (``Optional[Union[str, List[str]]]``): + The inline message ID(s) to stop listening for. Defaults to ``None``. + + Returns: + ``bool``: The return value of :meth:`hydrogram.Client.stop_listening`. + """ + return self._client.stop_listening( + chat_id=self.chat.id if self.chat else None, + listener_type=listener_type, + user_id=self.from_user.id if self.from_user else None, + message_id=message_id, + inline_message_id=inline_message_id, + ) + @property def link(self) -> str: if ( From c67d0d3276d91e1027ae31859436d035632ea900 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Tue, 16 Jan 2024 00:35:56 -0300 Subject: [PATCH 10/10] chore(pyromod): re-add user_id in `Message.{listen,ask,stop_listening}` methods and cleanup chat_id and user_id typing hints --- hydrogram/methods/pyromod/ask.py | 2 +- hydrogram/methods/pyromod/listen.py | 4 +-- .../pyromod/register_next_step_handler.py | 4 +-- hydrogram/methods/pyromod/stop_listening.py | 4 +-- hydrogram/types/messages_and_media/message.py | 27 ++++++++++++------- hydrogram/types/pyromod/identifier.py | 4 +-- hydrogram/types/user_and_chats/chat.py | 6 ++--- hydrogram/types/user_and_chats/user.py | 4 +-- 8 files changed, 32 insertions(+), 23 deletions(-) diff --git a/hydrogram/methods/pyromod/ask.py b/hydrogram/methods/pyromod/ask.py index 5c5779324..e3770a817 100644 --- a/hydrogram/methods/pyromod/ask.py +++ b/hydrogram/methods/pyromod/ask.py @@ -33,7 +33,7 @@ async def ask( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, *args, diff --git a/hydrogram/methods/pyromod/listen.py b/hydrogram/methods/pyromod/listen.py index a9e543eb9..9598307a1 100644 --- a/hydrogram/methods/pyromod/listen.py +++ b/hydrogram/methods/pyromod/listen.py @@ -37,8 +37,8 @@ async def listen( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + chat_id: Optional[Union[int, str, list[Union[int, str]]]] = None, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ) -> Union["hydrogram.types.Message", "hydrogram.types.CallbackQuery"]: diff --git a/hydrogram/methods/pyromod/register_next_step_handler.py b/hydrogram/methods/pyromod/register_next_step_handler.py index 84655db66..251fc40dc 100644 --- a/hydrogram/methods/pyromod/register_next_step_handler.py +++ b/hydrogram/methods/pyromod/register_next_step_handler.py @@ -31,8 +31,8 @@ def register_next_step_handler( filters: Optional[Filter] = None, listener_type: ListenerTypes = ListenerTypes.MESSAGE, unallowed_click_alert: bool = True, - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + chat_id: Optional[Union[int, str, list[Union[int, str]]]] = None, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): diff --git a/hydrogram/methods/pyromod/stop_listening.py b/hydrogram/methods/pyromod/stop_listening.py index 8425b91aa..ec814a690 100644 --- a/hydrogram/methods/pyromod/stop_listening.py +++ b/hydrogram/methods/pyromod/stop_listening.py @@ -27,8 +27,8 @@ class StopListening: async def stop_listening( self: "hydrogram.Client", listener_type: ListenerTypes = ListenerTypes.MESSAGE, - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + chat_id: Optional[Union[int, str, list[Union[int, str]]]] = None, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): diff --git a/hydrogram/types/messages_and_media/message.py b/hydrogram/types/messages_and_media/message.py index 988e5f60d..32b4f010e 100644 --- a/hydrogram/types/messages_and_media/message.py +++ b/hydrogram/types/messages_and_media/message.py @@ -508,7 +508,7 @@ def __init__( async def wait_for_click( self, - from_user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + from_user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, timeout: Optional[int] = None, filters=None, alert: Union[str, bool] = True, @@ -1037,6 +1037,7 @@ def listen( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): @@ -1048,8 +1049,7 @@ def listen( .. code-block:: python await client.listen( - chat_id=chat_id, - user_id=user_id + chat_id=chat_id ) Example: @@ -1070,6 +1070,9 @@ def listen( unallowed_click_alert (``bool``): Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``. + user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The user ID(s) to listen for. Defaults to ``None``. + message_id (``Optional[Union[int, List[int]]]``): The message ID(s) to listen for. Defaults to ``None``. @@ -1085,7 +1088,7 @@ def listen( listener_type=listener_type, timeout=timeout, unallowed_click_alert=unallowed_click_alert, - user_id=self.from_user.id if self.from_user else None, + user_id=user_id, message_id=message_id, inline_message_id=inline_message_id, ) @@ -1097,6 +1100,7 @@ def ask( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, *args, @@ -1111,7 +1115,6 @@ def ask( await client.ask( chat_id=chat_id, - user_id=user_id, text=text ) @@ -1137,6 +1140,9 @@ def ask( unallowed_click_alert (``bool``): Same as :meth:`hydrogram.Client.listen`. + user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The user ID(s) to listen for. Defaults to ``None``. + message_id (``Optional[Union[int, List[int]]]``): The message ID(s) to listen for. Defaults to ``None``. @@ -1159,7 +1165,7 @@ def ask( listener_type=listener_type, timeout=timeout, unallowed_click_alert=unallowed_click_alert, - user_id=self.from_user.id if self.from_user else None, + user_id=user_id, message_id=message_id, inline_message_id=inline_message_id, *args, @@ -1169,6 +1175,7 @@ def ask( def stop_listening( self, listener_type: ListenerTypes = ListenerTypes.MESSAGE, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): @@ -1180,8 +1187,7 @@ def stop_listening( .. code-block:: python await client.stop_listening( - chat_id=chat_id, - user_id=user_id + chat_id=chat_id ) Example: @@ -1193,6 +1199,9 @@ def stop_listening( listener_type (``ListenerTypes``): The type of listener to stop listening for. Defaults to :attr:`hydrogram.types.ListenerTypes.MESSAGE`. + user_id (``Optional[Union[int, str], List[Union[int, str]]]``): + The user ID(s) to stop listening for. Defaults to ``None``. + message_id (``Optional[Union[int, List[int]]]``): The message ID(s) to stop listening for. Defaults to ``None``. @@ -1205,7 +1214,7 @@ def stop_listening( return self._client.stop_listening( chat_id=self.chat.id if self.chat else None, listener_type=listener_type, - user_id=self.from_user.id if self.from_user else None, + user_id=user_id, message_id=message_id, inline_message_id=inline_message_id, ) diff --git a/hydrogram/types/pyromod/identifier.py b/hydrogram/types/pyromod/identifier.py index 73f709b8e..6f82994c9 100644 --- a/hydrogram/types/pyromod/identifier.py +++ b/hydrogram/types/pyromod/identifier.py @@ -24,9 +24,9 @@ @dataclass class Identifier: inline_message_id: Optional[Union[str, list[str]]] = None - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None + chat_id: Optional[Union[int, str, list[Union[int, str]]]] = None message_id: Optional[Union[int, list[int]]] = None - from_user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None + from_user_id: Optional[Union[int, str, list[Union[int, str]]]] = None def matches(self, update: "Identifier") -> bool: # Compare each property of other with the corresponding property in self diff --git a/hydrogram/types/user_and_chats/chat.py b/hydrogram/types/user_and_chats/chat.py index e9b0070ca..ec0cc5a7c 100644 --- a/hydrogram/types/user_and_chats/chat.py +++ b/hydrogram/types/user_and_chats/chat.py @@ -412,7 +412,7 @@ def listen( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): @@ -475,7 +475,7 @@ def ask( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, *args, @@ -550,7 +550,7 @@ def ask( def stop_listening( self, listener_type: ListenerTypes = ListenerTypes.MESSAGE, - user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): diff --git a/hydrogram/types/user_and_chats/user.py b/hydrogram/types/user_and_chats/user.py index 870ceeffe..c2857e2f9 100644 --- a/hydrogram/types/user_and_chats/user.py +++ b/hydrogram/types/user_and_chats/user.py @@ -308,7 +308,7 @@ def listen( listener_type: ListenerTypes = ListenerTypes.MESSAGE, timeout: Optional[int] = None, unallowed_click_alert: bool = True, - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + chat_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ): @@ -436,7 +436,7 @@ def ask( def stop_listening( self, listener_type: ListenerTypes = ListenerTypes.MESSAGE, - chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None, + chat_id: Optional[Union[int, str, list[Union[int, str]]]] = None, message_id: Optional[Union[int, list[int]]] = None, inline_message_id: Optional[Union[str, list[str]]] = None, ):