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/client.py b/hydrogram/client.py index 080ff756b..fd21da9ea 100644 --- a/hydrogram/client.py +++ b/hydrogram/client.py @@ -51,7 +51,7 @@ 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.types import ListenerTypes, TermsOfService, User from hydrogram.utils import ainput from .dispatcher import Dispatcher @@ -310,6 +310,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() 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..33262d328 --- /dev/null +++ b/hydrogram/errors/pyromod/__init__.py @@ -0,0 +1,23 @@ +# 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 + +__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..a1575fc27 --- /dev/null +++ b/hydrogram/errors/pyromod/listener_stopped.py @@ -0,0 +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 new file mode 100644 index 000000000..cd945829e --- /dev/null +++ b/hydrogram/errors/pyromod/listener_timeout.py @@ -0,0 +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/handlers/callback_query_handler.py b/hydrogram/handlers/callback_query_handler.py index 97984128d..3a7cf1f40 100644 --- a/hydrogram/handlers/callback_query_handler.py +++ b/hydrogram/handlers/callback_query_handler.py @@ -17,7 +17,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . -from typing import Callable +from asyncio import iscoroutinefunction +from typing import Callable, Optional + +import hydrogram +from hydrogram.types import CallbackQuery, Identifier, Listener, ListenerTypes +from hydrogram.utils import PyromodConfig from .handler import Handler @@ -47,4 +52,169 @@ 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) -> Identifier: + """ + Composes an Identifier object from a CallbackQuery 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 + 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, Optional[Listener]]: + """ + Checks if the CallbackQuery object has a 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) + + 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) -> bool: + """ + Checks if the CallbackQuery object has a matching listener or handler. + + 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) + + 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 + ) -> None: + """ + Resolves the future or calls the callback of the listener. Will call the original handler if no listener. + + 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) + + 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 + if listener.callback: + if iscoroutinefunction(listener.callback): + await listener.callback(client, query, *args) + else: + listener.callback(client, query, *args) + + raise hydrogram.StopPropagation + + 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 a0d2c3992..d923f9494 100644 --- a/hydrogram/handlers/message_handler.py +++ b/hydrogram/handlers/message_handler.py @@ -17,7 +17,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . -from typing import Callable +from inspect import iscoroutinefunction +from typing import Callable, Optional + +import hydrogram +from hydrogram.types import Identifier, Listener, ListenerTypes, Message from .handler import Handler @@ -47,4 +51,119 @@ 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 + ) -> tuple[bool, Optional[Listener]]: + """ + Checks if the message has a 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 + 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) -> bool: + """ + Checks if 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] + + 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. + + 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) + + 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 + if listener.callback: + if iscoroutinefunction(listener.callback): + await listener.callback(client, message, *args) + else: + listener.callback(client, message, *args) + + raise hydrogram.StopPropagation + + 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 new file mode 100644 index 000000000..ecd234fe3 --- /dev/null +++ b/hydrogram/helpers/__init__.py @@ -0,0 +1,23 @@ +# 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 .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 new file mode 100644 index 000000000..76a525f41 --- /dev/null +++ b/hydrogram/helpers/helpers.py @@ -0,0 +1,196 @@ +# 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 + +from hydrogram.types import ( + ForceReply, + InlineKeyboardButton, + InlineKeyboardMarkup, + KeyboardButton, + ReplyKeyboardMarkup, +) + + +def ikb(rows: Optional[list[list[Union[str, tuple[str, str]]]]] = None) -> InlineKeyboardMarkup: + """ + Create an InlineKeyboardMarkup from a list of lists of buttons. + + 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 = [] + + 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: str, value: str, type="callback_data") -> InlineKeyboardButton: + """ + Create an 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 ikb() +def bki(keyboard: InlineKeyboardButton) -> list[list[Union[str, tuple[str, str]]]]: + """ + Create a list of lists of buttons from an InlineKeyboardMarkup. + + Parameters: + keyboard (:obj:`~hydrogram.types.InlineKeyboardMarkup`): + An InlineKeyboardMarkup object. + + Returns: + 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: InlineKeyboardButton) -> list: + """ + Create a button list from an InlineKeyboardButton. + + Parameters: + button (:obj:`~hydrogram.types.InlineKeyboardButton`): + An InlineKeyboardButton object. + + Returns: + ``list``: A button list. + """ + 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) -> ReplyKeyboardMarkup: + """ + Create a ReplyKeyboardMarkup from a list of lists of buttons. + + 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 = [] + + lines = [] + for row in rows: + line = [] + for button in row: + button_type = type(button) + if isinstance(button_type, str): + button = KeyboardButton(button) + elif isinstance(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) -> ForceReply: + """ + Create a 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) -> list[list]: + """ + Split an array into 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/__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..e3770a817 --- /dev/null +++ b/hydrogram/methods/pyromod/ask.py @@ -0,0 +1,100 @@ +# 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[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. + + 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 + 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..9655119e5 --- /dev/null +++ b/hydrogram/methods/pyromod/get_listener_matching_with_data.py @@ -0,0 +1,53 @@ +# 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. + + 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 + 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..0edc7b160 --- /dev/null +++ b/hydrogram/methods/pyromod/get_listener_matching_with_identifier_pattern.py @@ -0,0 +1,58 @@ +# 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:`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. + + 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 + 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..7c254fbeb --- /dev/null +++ b/hydrogram/methods/pyromod/get_many_listeners_matching_with_data.py @@ -0,0 +1,48 @@ +# 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:`hydrogram.Client.get_listener_matching_with_data` but returns a list of listeners instead of one. + + 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 + 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..608004913 --- /dev/null +++ b/hydrogram/methods/pyromod/get_many_listeners_matching_with_identifier_pattern.py @@ -0,0 +1,48 @@ +# 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:`hydrogram.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one. + + 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 + 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..9598307a1 --- /dev/null +++ b/hydrogram/methods/pyromod/listen.py @@ -0,0 +1,109 @@ +# 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[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"]: + """ + Creates a listener and waits for it to be fulfilled. + + 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, + 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..251fc40dc --- /dev/null +++ b/hydrogram/methods/pyromod/register_next_step_handler.py @@ -0,0 +1,85 @@ +# 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[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, + ): + """ + Registers a listener with a callback to be called when the listener is fulfilled. + + 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, + 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..97be66361 --- /dev/null +++ b/hydrogram/methods/pyromod/remove_listener.py @@ -0,0 +1,36 @@ +# 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:`hydrogram.Client.listeners` dictionary. + + 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 new file mode 100644 index 000000000..0b62c96a2 --- /dev/null +++ b/hydrogram/methods/pyromod/stop_listener.py @@ -0,0 +1,58 @@ +# 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. + + 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) + + 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..ec814a690 --- /dev/null +++ b/hydrogram/methods/pyromod/stop_listening.py @@ -0,0 +1,66 @@ +# 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[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, + ): + """ + Stops all listeners that match the given identifier pattern. + Uses :meth:`hydrogram.Client.get_many_listeners_matching_with_identifier_pattern`. + + 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, + 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) diff --git a/hydrogram/nav/__init__.py b/hydrogram/nav/__init__.py new file mode 100644 index 000000000..399aaf674 --- /dev/null +++ b/hydrogram/nav/__init__.py @@ -0,0 +1,23 @@ +# 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 .pagination import Pagination + +__all__ = ["Pagination"] diff --git a/hydrogram/nav/pagination.py b/hydrogram/nav/pagination.py new file mode 100644 index 000000000..a3091ba0b --- /dev/null +++ b/hydrogram/nav/pagination.py @@ -0,0 +1,90 @@ +# 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 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 = [(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) + + 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..32b4f010e 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__) @@ -505,6 +506,45 @@ def __init__( self.web_app_data = web_app_data self.reactions = reactions + async def wait_for_click( + self, + from_user_id: Optional[Union[int, str, list[Union[int, str]]]] = None, + timeout: Optional[int] = None, + filters=None, + alert: Union[str, bool] = True, + ) -> "types.CallbackQuery": + """ + Waits for a callback query to be clicked on the message. + + 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)) + + 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", @@ -991,6 +1031,194 @@ 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, + 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, + ): + """ + 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``. + + 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[: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=user_id, + 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, + 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, + **kwargs, + ): + """ + 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`. + + 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( + 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=user_id, + message_id=message_id, + inline_message_id=inline_message_id, + *args, + **kwargs, + ) + + 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, + ): + """ + 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 + ) + + 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( + chat_id=self.chat.id if self.chat else None, + listener_type=listener_type, + user_id=user_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + @property def link(self) -> str: if ( diff --git a/hydrogram/types/pyromod/__init__.py b/hydrogram/types/pyromod/__init__.py new file mode 100644 index 000000000..f3dbffdf5 --- /dev/null +++ b/hydrogram/types/pyromod/__init__.py @@ -0,0 +1,24 @@ +# 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 + +__all__ = ["Identifier", "Listener", "ListenerTypes"] diff --git a/hydrogram/types/pyromod/identifier.py b/hydrogram/types/pyromod/identifier.py new file mode 100644 index 000000000..6f82994c9 --- /dev/null +++ b/hydrogram/types/pyromod/identifier.py @@ -0,0 +1,60 @@ +# 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 + + +@dataclass +class Identifier: + inline_message_id: Optional[Union[str, list[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[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..9553cd162 --- /dev/null +++ b/hydrogram/types/pyromod/listener.py @@ -0,0 +1,37 @@ +# 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 + +import hydrogram + +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..77b759544 --- /dev/null +++ b/hydrogram/types/pyromod/listener_types.py @@ -0,0 +1,25 @@ +# 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 + + +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..ec0cc5a7c 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,6 +406,194 @@ def _parse_chat( return Chat._parse_user_chat(client, chat) return Chat._parse_channel_chat(client, chat) + 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[int, str, list[Union[int, str]]]] = None, + 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 + ) + + 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``. + + 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[:obj:`~hydrogram.types.Message`, :obj:`~hydrogram.types.CallbackQuery`]: The Message or CallbackQuery + """ + 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: str, + filters: Optional["filters.Filter"] = None, + 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, + **kwargs, + ): + """ + 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`. + + 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( + 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, + 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, + ): + """ + 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 + ) + + 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( + 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`. @@ -412,7 +601,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 @@ -435,7 +624,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 e93062024..c2857e2f9 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,6 +302,171 @@ def _parse_user_status(client, user_status: "raw.types.UpdateUserStatus"): client=client, ) + def listen( + self, + filters: Optional["filters.Filter"] = None, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + timeout: Optional[int] = None, + unallowed_click_alert: bool = True, + 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, + ): + """ + 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`. + + 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( + user_id=self.id, + filters=filters, + listener_type=listener_type, + timeout=timeout, + unallowed_click_alert=unallowed_click_alert, + chat_id=chat_id, + 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.User`. + + 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( + 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, + listener_type: ListenerTypes = ListenerTypes.MESSAGE, + 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, + ): + """ + Stops listening for messages from the user. Calls Client.stop_listening() with the user_id set to the user's id. + + Parameters: + 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`. + + inline_message_id (``Union[str, List[str]]``): + Same as :meth:`hydrogram.Client.stop_listening`. + + Returns: + ``None`` + """ + return self._client.stop_listening( + user_id=self.id, + listener_type=listener_type, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + ) + async def archive(self): """Bound method *archive* of :obj:`~hydrogram.types.User`. diff --git a/hydrogram/utils.py b/hydrogram/utils.py index c03271415..57ca761b7 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 types import SimpleNamespace from typing import 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""" 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.