Skip to content

Commit 855e406

Browse files
feat: integrate pyromod patches (#1)
* feat: integrate pyromod patches * refactor: fix ruff reported issues * chore: add copyright notices where missing * chore(news): add news fragment * refactor: split out pyromod methods from client.py * chore: change return types in docstrings * chore: update docstrings * chore: readd chat_id param where removed and update docs * feat(pyromod): add `Message.{listen,ask,stop_listening}` * chore(pyromod): re-add user_id in `Message.{listen,ask,stop_listening}` methods and cleanup chat_id and user_id typing hints --------- Co-authored-by: Cezar H <pauxiscezar@gmail.com>
1 parent ff9d493 commit 855e406

34 files changed

+2162
-11
lines changed

compiler/docs/compiler.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,13 +581,19 @@ def get_title_list(s: str) -> list:
581581
Chat.mark_unread
582582
Chat.set_protected_content
583583
Chat.unpin_all_messages
584+
Chat.ask
585+
Chat.listen
586+
Chat.stop_listening
584587
""",
585588
"user": """
586589
User
587590
User.archive
588591
User.unarchive
589592
User.block
590593
User.unblock
594+
User.ask
595+
User.listen
596+
User.stop_listening
591597
""",
592598
"callback_query": """
593599
Callback Query

hydrogram/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
from hydrogram.methods import Methods
5252
from hydrogram.session import Auth, Session
5353
from hydrogram.storage import BaseStorage, SQLiteStorage
54-
from hydrogram.types import TermsOfService, User
54+
from hydrogram.types import ListenerTypes, TermsOfService, User
5555
from hydrogram.utils import ainput
5656

5757
from .dispatcher import Dispatcher
@@ -310,6 +310,7 @@ def __init__(
310310
self.last_update_time = datetime.now()
311311

312312
self.loop = asyncio.get_event_loop()
313+
self.listeners = {listener_type: [] for listener_type in ListenerTypes}
313314

314315
def __enter__(self):
315316
return self.start()

hydrogram/errors/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@
493493
service_unavailable_503,
494494
unauthorized_401,
495495
)
496+
from .pyromod import ListenerStopped, ListenerTimeout
496497
from .rpc_error import UnknownError
497498

498499

@@ -955,6 +956,8 @@ def __init__(self, msg: Optional[str] = None):
955956
"TypesEmpty",
956957
"Unauthorized",
957958
"UnknownError",
959+
"ListenerStopped",
960+
"ListenerTimeout",
958961
"UnknownMethod",
959962
"UntilDateInvalid",
960963
"UploadNoVolume",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Hydrogram - Telegram MTProto API Client Library for Python
2+
# Copyright (C) 2020-present Cezar H. <https://github.com/usernein>
3+
# Copyright (C) 2023-present Amano LLC <https://amanoteam.com>
4+
#
5+
# This file is part of Hydrogram.
6+
#
7+
# Hydrogram is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU Lesser General Public License as published
9+
# by the Free Software Foundation, either version 3 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# Hydrogram is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public License
18+
# along with Hydrogram. If not, see <http://www.gnu.org/licenses/>.
19+
20+
from .listener_stopped import ListenerStopped
21+
from .listener_timeout import ListenerTimeout
22+
23+
__all__ = ["ListenerStopped", "ListenerTimeout"]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Hydrogram - Telegram MTProto API Client Library for Python
2+
# Copyright (C) 2020-present Cezar H. <https://github.com/usernein>
3+
# Copyright (C) 2023-present Amano LLC <https://amanoteam.com>
4+
#
5+
# This file is part of Hydrogram.
6+
#
7+
# Hydrogram is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU Lesser General Public License as published
9+
# by the Free Software Foundation, either version 3 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# Hydrogram is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public License
18+
# along with Hydrogram. If not, see <http://www.gnu.org/licenses/>.
19+
20+
21+
class ListenerStopped(Exception): # noqa: N818
22+
pass
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Hydrogram - Telegram MTProto API Client Library for Python
2+
# Copyright (C) 2020-present Cezar H. <https://github.com/usernein>
3+
# Copyright (C) 2023-present Amano LLC <https://amanoteam.com>
4+
#
5+
# This file is part of Hydrogram.
6+
#
7+
# Hydrogram is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU Lesser General Public License as published
9+
# by the Free Software Foundation, either version 3 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# Hydrogram is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public License
18+
# along with Hydrogram. If not, see <http://www.gnu.org/licenses/>.
19+
20+
21+
class ListenerTimeout(Exception): # noqa: N818
22+
pass

hydrogram/handlers/callback_query_handler.py

Lines changed: 172 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
# You should have received a copy of the GNU Lesser General Public License
1818
# along with Hydrogram. If not, see <http://www.gnu.org/licenses/>.
1919

20-
from typing import Callable
20+
from asyncio import iscoroutinefunction
21+
from typing import Callable, Optional
22+
23+
import hydrogram
24+
from hydrogram.types import CallbackQuery, Identifier, Listener, ListenerTypes
25+
from hydrogram.utils import PyromodConfig
2126

2227
from .handler import Handler
2328

@@ -47,4 +52,169 @@ class CallbackQueryHandler(Handler):
4752
"""
4853

4954
def __init__(self, callback: Callable, filters=None):
50-
super().__init__(callback, filters)
55+
self.original_callback = callback
56+
super().__init__(self.resolve_future_or_callback, filters)
57+
58+
def compose_data_identifier(self, query: CallbackQuery) -> Identifier:
59+
"""
60+
Composes an Identifier object from a CallbackQuery object.
61+
62+
Parameters:
63+
query (:obj:`~hydrogram.types.CallbackQuery`):
64+
The CallbackQuery object to compose of.
65+
66+
Returns:
67+
:obj:`~hydrogram.types.Identifier`: An Identifier object.
68+
"""
69+
from_user = query.from_user
70+
from_user_id = from_user.id if from_user else None
71+
from_user_username = from_user.username if from_user else None
72+
73+
chat_id = None
74+
message_id = None
75+
76+
if query.message:
77+
message_id = getattr(query.message, "id", getattr(query.message, "message_id", None))
78+
79+
if query.message.chat:
80+
chat_id = [query.message.chat.id, query.message.chat.username]
81+
82+
return Identifier(
83+
message_id=message_id,
84+
chat_id=chat_id,
85+
from_user_id=[from_user_id, from_user_username],
86+
inline_message_id=query.inline_message_id,
87+
)
88+
89+
async def check_if_has_matching_listener(
90+
self, client: "hydrogram.Client", query: CallbackQuery
91+
) -> tuple[bool, Optional[Listener]]:
92+
"""
93+
Checks if the CallbackQuery object has a matching listener.
94+
95+
Parameters:
96+
client (:obj:`~hydrogram.Client`):
97+
The Client object to check with.
98+
99+
query (:obj:`~hydrogram.types.CallbackQuery`):
100+
The CallbackQuery object to check with.
101+
102+
Returns:
103+
A tuple of whether the CallbackQuery object has a matching listener and its filters does match with the
104+
CallbackQuery and the matching listener;
105+
"""
106+
data = self.compose_data_identifier(query)
107+
108+
listener = client.get_listener_matching_with_data(data, ListenerTypes.CALLBACK_QUERY)
109+
110+
listener_does_match = False
111+
112+
if listener:
113+
filters = listener.filters
114+
if callable(filters):
115+
if iscoroutinefunction(filters.__call__):
116+
listener_does_match = await filters(client, query)
117+
else:
118+
listener_does_match = await client.loop.run_in_executor(
119+
None, filters, client, query
120+
)
121+
else:
122+
listener_does_match = True
123+
124+
return listener_does_match, listener
125+
126+
async def check(self, client: "hydrogram.Client", query: CallbackQuery) -> bool:
127+
"""
128+
Checks if the CallbackQuery object has a matching listener or handler.
129+
130+
Parameters:
131+
client (:obj:`~hydrogram.Client`):
132+
The Client object to check with.
133+
134+
query (:obj:`~hydrogram.types.CallbackQuery`):
135+
The CallbackQuery object to check with.
136+
137+
Returns:
138+
``bool``: A boolean indicating whether the CallbackQuery object has a matching listener or the handler filter matches.
139+
"""
140+
listener_does_match, listener = await self.check_if_has_matching_listener(client, query)
141+
142+
if callable(self.filters):
143+
if iscoroutinefunction(self.filters.__call__):
144+
handler_does_match = await self.filters(client, query)
145+
else:
146+
handler_does_match = await client.loop.run_in_executor(
147+
None, self.filters, client, query
148+
)
149+
else:
150+
handler_does_match = True
151+
152+
data = self.compose_data_identifier(query)
153+
154+
if PyromodConfig.unallowed_click_alert:
155+
# matches with the current query but from any user
156+
permissive_identifier = Identifier(
157+
chat_id=data.chat_id,
158+
message_id=data.message_id,
159+
inline_message_id=data.inline_message_id,
160+
from_user_id=None,
161+
)
162+
163+
matches = permissive_identifier.matches(data)
164+
165+
if (
166+
listener
167+
and (matches and not listener_does_match)
168+
and listener.unallowed_click_alert
169+
):
170+
alert = (
171+
listener.unallowed_click_alert
172+
if isinstance(listener.unallowed_click_alert, str)
173+
else PyromodConfig.unallowed_click_alert_text
174+
)
175+
await query.answer(alert)
176+
return False
177+
178+
# let handler get the chance to handle if listener
179+
# exists but its filters doesn't match
180+
return listener_does_match or handler_does_match
181+
182+
async def resolve_future_or_callback(
183+
self, client: "hydrogram.Client", query: CallbackQuery, *args
184+
) -> None:
185+
"""
186+
Resolves the future or calls the callback of the listener. Will call the original handler if no listener.
187+
188+
Parameters:
189+
client (:obj:`~hydrogram.Client`):
190+
The Client object to resolve or call with.
191+
192+
query (:obj:`~hydrogram.types.CallbackQuery`):
193+
The CallbackQuery object to resolve or call with.
194+
195+
args:
196+
The arguments to call the callback with.
197+
198+
Returns:
199+
``None``
200+
"""
201+
listener_does_match, listener = await self.check_if_has_matching_listener(client, query)
202+
203+
if listener and listener_does_match:
204+
client.remove_listener(listener)
205+
206+
if listener.future and not listener.future.done():
207+
listener.future.set_result(query)
208+
209+
raise hydrogram.StopPropagation
210+
if listener.callback:
211+
if iscoroutinefunction(listener.callback):
212+
await listener.callback(client, query, *args)
213+
else:
214+
listener.callback(client, query, *args)
215+
216+
raise hydrogram.StopPropagation
217+
218+
raise ValueError("Listener must have either a future or a callback")
219+
220+
await self.original_callback(client, query, *args)

0 commit comments

Comments
 (0)