Skip to content

Commit 2b005b6

Browse files
refactor: split out pyromod methods from client.py
1 parent 601c24d commit 2b005b6

13 files changed

Lines changed: 612 additions & 306 deletions

hydrogram/client.py

Lines changed: 2 additions & 306 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,18 @@
4444
BadRequest,
4545
CDNFileHashMismatch,
4646
ChannelPrivate,
47-
ListenerStopped,
48-
ListenerTimeout,
4947
SessionPasswordNeeded,
5048
VolumeLocNotFound,
5149
)
5250
from hydrogram.handlers.handler import Handler
5351
from hydrogram.methods import Methods
5452
from hydrogram.session import Auth, Session
5553
from hydrogram.storage import BaseStorage, SQLiteStorage
56-
from hydrogram.types import Identifier, Listener, ListenerTypes, TermsOfService, User
57-
from hydrogram.utils import PyromodConfig, ainput
54+
from hydrogram.types import ListenerTypes, TermsOfService, User
55+
from hydrogram.utils import ainput
5856

5957
from .dispatcher import Dispatcher
6058
from .file_id import FileId, FileType, ThumbnailSource
61-
from .filters import Filter
6259
from .mime_types import mime_types
6360
from .parser import Parser
6461
from .session.internals import MsgId
@@ -345,307 +342,6 @@ async def updates_watchdog(self):
345342
):
346343
await self.invoke(raw.functions.updates.GetState())
347344

348-
async def listen(
349-
self,
350-
filters: Optional[Filter] = None,
351-
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
352-
timeout: Optional[int] = None,
353-
unallowed_click_alert: bool = True,
354-
chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None,
355-
user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None,
356-
message_id: Optional[Union[int, list[int]]] = None,
357-
inline_message_id: Optional[Union[str, list[str]]] = None,
358-
):
359-
"""
360-
Creates a listener and waits for it to be fulfilled.
361-
362-
:param filters: A filter to check if the listener should be fulfilled.
363-
:param listener_type: The type of listener to create. Defaults to :attr:`pyromod.types.ListenerTypes.MESSAGE`.
364-
:param timeout: The maximum amount of time to wait for the listener to be fulfilled. Defaults to ``None``.
365-
:param unallowed_click_alert: Whether to alert the user if they click on a button that is not intended for them. Defaults to ``True``.
366-
:param chat_id: The chat ID(s) to listen for. Defaults to ``None``.
367-
:param user_id: The user ID(s) to listen for. Defaults to ``None``.
368-
:param message_id: The message ID(s) to listen for. Defaults to ``None``.
369-
:param inline_message_id: The inline message ID(s) to listen for. Defaults to ``None``.
370-
:return: The Message or CallbackQuery that fulfilled the listener.
371-
"""
372-
pattern = Identifier(
373-
from_user_id=user_id,
374-
chat_id=chat_id,
375-
message_id=message_id,
376-
inline_message_id=inline_message_id,
377-
)
378-
379-
loop = asyncio.get_event_loop()
380-
future = loop.create_future()
381-
382-
listener = Listener(
383-
future=future,
384-
filters=filters,
385-
unallowed_click_alert=unallowed_click_alert,
386-
identifier=pattern,
387-
listener_type=listener_type,
388-
)
389-
390-
future.add_done_callback(lambda _future: self.remove_listener(listener))
391-
392-
self.listeners[listener_type].append(listener)
393-
394-
try:
395-
return await asyncio.wait_for(future, timeout)
396-
except asyncio.exceptions.TimeoutError:
397-
if callable(PyromodConfig.timeout_handler):
398-
if inspect.iscoroutinefunction(PyromodConfig.timeout_handler.__call__):
399-
await PyromodConfig.timeout_handler(pattern, listener, timeout)
400-
else:
401-
await self.loop.run_in_executor(
402-
None, PyromodConfig.timeout_handler, pattern, listener, timeout
403-
)
404-
elif PyromodConfig.throw_exceptions:
405-
raise ListenerTimeout(timeout)
406-
407-
async def ask(
408-
self,
409-
chat_id: Union[Union[int, str], list[Union[int, str]]],
410-
text: str,
411-
filters: Optional[Filter] = None,
412-
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
413-
timeout: Optional[int] = None,
414-
unallowed_click_alert: bool = True,
415-
user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None,
416-
message_id: Optional[Union[int, list[int]]] = None,
417-
inline_message_id: Optional[Union[str, list[str]]] = None,
418-
*args,
419-
**kwargs,
420-
):
421-
"""
422-
Sends a message and waits for a response.
423-
424-
:param chat_id: The chat ID(s) to wait for a message from. The first chat ID will be used to send the message.
425-
:param text: The text to send.
426-
:param filters: Same as :meth:`pyromod.types.Client.listen`.
427-
:param listener_type: Same as :meth:`pyromod.types.Client.listen`.
428-
:param timeout: Same as :meth:`pyromod.types.Client.listen`.
429-
:param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`.
430-
:param user_id: Same as :meth:`pyromod.types.Client.listen`.
431-
:param message_id: Same as :meth:`pyromod.types.Client.listen`.
432-
:param inline_message_id: Same as :meth:`pyromod.types.Client.listen`.
433-
:param args: Additional arguments to pass to :meth:`hydrogram.Client.send_message`.
434-
:param kwargs: Additional keyword arguments to pass to :meth:`hydrogram.Client.send_message`.
435-
:return:
436-
Same as :meth:`pyromod.types.Client.listen`. The sent message is returned as the attribute ``sent_message``.
437-
"""
438-
sent_message = None
439-
if text.strip() != "":
440-
chat_to_ask = chat_id[0] if isinstance(chat_id, list) else chat_id
441-
sent_message = await self.send_message(chat_to_ask, text, *args, **kwargs)
442-
443-
response = await self.listen(
444-
filters=filters,
445-
listener_type=listener_type,
446-
timeout=timeout,
447-
unallowed_click_alert=unallowed_click_alert,
448-
chat_id=chat_id,
449-
user_id=user_id,
450-
message_id=message_id,
451-
inline_message_id=inline_message_id,
452-
)
453-
if response:
454-
response.sent_message = sent_message
455-
456-
return response
457-
458-
def remove_listener(self, listener: Listener):
459-
"""
460-
Removes a listener from the :meth:`pyromod.types.Client.listeners` dictionary.
461-
462-
:param listener: The listener to remove.
463-
:return: ``void``
464-
"""
465-
with contextlib.suppress(ValueError):
466-
self.listeners[listener.listener_type].remove(listener)
467-
468-
def get_listener_matching_with_data(
469-
self, data: Identifier, listener_type: ListenerTypes
470-
) -> Optional[Listener]:
471-
"""
472-
Gets a listener that matches the given data.
473-
474-
:param data: A :class:`pyromod.types.Identifier` to match against.
475-
:param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`.
476-
:return: The listener that matches the given data or ``None`` if no listener matches.
477-
"""
478-
matching = [
479-
listener
480-
for listener in self.listeners[listener_type]
481-
if listener.identifier.matches(data)
482-
]
483-
484-
# in case of multiple matching listeners, the most specific should be returned
485-
def count_populated_attributes(listener_item: Listener):
486-
return listener_item.identifier.count_populated()
487-
488-
return max(matching, key=count_populated_attributes, default=None)
489-
490-
def get_listener_matching_with_identifier_pattern(
491-
self, pattern: Identifier, listener_type: ListenerTypes
492-
) -> Optional[Listener]:
493-
"""
494-
Gets a listener that matches the given identifier pattern.
495-
496-
The difference from :meth:`pyromod.types.Client.get_listener_matching_with_data` is that this method
497-
intends to get a listener by passing partial info of the listener identifier, while the other method
498-
intends to get a listener by passing the full info of the update data, which the listener should match with.
499-
500-
:param pattern: A :class:`pyromod.types.Identifier` to match against.
501-
:param listener_type: The type of listener to get. Must be a value from :class:`pyromod.types.ListenerTypes`.
502-
:return: The listener that matches the given identifier pattern or ``None`` if no listener matches.
503-
"""
504-
matching = [
505-
listener
506-
for listener in self.listeners[listener_type]
507-
if pattern.matches(listener.identifier)
508-
]
509-
510-
# in case of multiple matching listeners, the most specific should be returned
511-
512-
def count_populated_attributes(listener_item: Listener):
513-
return listener_item.identifier.count_populated()
514-
515-
return max(matching, key=count_populated_attributes, default=None)
516-
517-
def get_many_listeners_matching_with_data(
518-
self,
519-
data: Identifier,
520-
listener_type: ListenerTypes,
521-
) -> list[Listener]:
522-
"""
523-
Same of :meth:`pyromod.types.Client.get_listener_matching_with_data` but returns a list of listeners instead of one.
524-
525-
:param data: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`.
526-
:param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_data`.
527-
:return: A list of listeners that match the given data.
528-
"""
529-
return [
530-
listener
531-
for listener in self.listeners[listener_type]
532-
if listener.identifier.matches(data)
533-
]
534-
535-
def get_many_listeners_matching_with_identifier_pattern(
536-
self,
537-
pattern: Identifier,
538-
listener_type: ListenerTypes,
539-
) -> list[Listener]:
540-
"""
541-
Same of :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern` but returns a list of listeners instead of one.
542-
543-
:param pattern: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`.
544-
:param listener_type: Same as :meth:`pyromod.types.Client.get_listener_matching_with_identifier_pattern`.
545-
:return: A list of listeners that match the given identifier pattern.
546-
"""
547-
return [
548-
listener
549-
for listener in self.listeners[listener_type]
550-
if pattern.matches(listener.identifier)
551-
]
552-
553-
async def stop_listening(
554-
self,
555-
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
556-
chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None,
557-
user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None,
558-
message_id: Optional[Union[int, list[int]]] = None,
559-
inline_message_id: Optional[Union[str, list[str]]] = None,
560-
):
561-
"""
562-
Stops all listeners that match the given identifier pattern.
563-
Uses :meth:`pyromod.types.Client.get_many_listeners_matching_with_identifier_pattern`.
564-
565-
:param listener_type: The type of listener to stop. Must be a value from :class:`pyromod.types.ListenerTypes`.
566-
:param chat_id: The chat_id to match against.
567-
:param user_id: The user_id to match against.
568-
:param message_id: The message_id to match against.
569-
:param inline_message_id: The inline_message_id to match against.
570-
:return: ``void``
571-
"""
572-
pattern = Identifier(
573-
from_user_id=user_id,
574-
chat_id=chat_id,
575-
message_id=message_id,
576-
inline_message_id=inline_message_id,
577-
)
578-
listeners = self.get_many_listeners_matching_with_identifier_pattern(
579-
pattern, listener_type
580-
)
581-
582-
for listener in listeners:
583-
await self.stop_listener(listener)
584-
585-
async def stop_listener(self, listener: Listener):
586-
"""
587-
Stops a listener, calling stopped_handler if applicable or raising ListenerStopped if throw_exceptions is True.
588-
589-
:param listener: The :class:`pyromod.types.Listener` to stop.
590-
:return: ``void``
591-
:raises ListenerStopped: If throw_exceptions is True.
592-
"""
593-
self.remove_listener(listener)
594-
595-
if listener.future.done():
596-
return
597-
598-
if callable(PyromodConfig.stopped_handler):
599-
if inspect.iscoroutinefunction(PyromodConfig.stopped_handler.__call__):
600-
await PyromodConfig.stopped_handler(None, listener)
601-
else:
602-
await self.loop.run_in_executor(
603-
None, PyromodConfig.stopped_handler, None, listener
604-
)
605-
elif PyromodConfig.throw_exceptions:
606-
listener.future.set_exception(ListenerStopped())
607-
608-
def register_next_step_handler(
609-
self,
610-
callback: Callable,
611-
filters: Optional[Filter] = None,
612-
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
613-
unallowed_click_alert: bool = True,
614-
chat_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None,
615-
user_id: Optional[Union[Union[int, str], list[Union[int, str]]]] = None,
616-
message_id: Optional[Union[int, list[int]]] = None,
617-
inline_message_id: Optional[Union[str, list[str]]] = None,
618-
):
619-
"""
620-
Registers a listener with a callback to be called when the listener is fulfilled.
621-
622-
:param callback: The callback to call when the listener is fulfilled.
623-
:param filters: Same as :meth:`pyromod.types.Client.listen`.
624-
:param listener_type: Same as :meth:`pyromod.types.Client.listen`.
625-
:param unallowed_click_alert: Same as :meth:`pyromod.types.Client.listen`.
626-
:param chat_id: Same as :meth:`pyromod.types.Client.listen`.
627-
:param user_id: Same as :meth:`pyromod.types.Client.listen`.
628-
:param message_id: Same as :meth:`pyromod.types.Client.listen`.
629-
:param inline_message_id: Same as :meth:`pyromod.types.Client.listen`.
630-
:return: ``void``
631-
"""
632-
pattern = Identifier(
633-
from_user_id=user_id,
634-
chat_id=chat_id,
635-
message_id=message_id,
636-
inline_message_id=inline_message_id,
637-
)
638-
639-
listener = Listener(
640-
callback=callback,
641-
filters=filters,
642-
unallowed_click_alert=unallowed_click_alert,
643-
identifier=pattern,
644-
listener_type=listener_type,
645-
)
646-
647-
self.listeners[listener_type].append(listener)
648-
649345
async def authorize(self) -> User:
650346
if self.bot_token:
651347
return await self.sign_in_bot(self.bot_token)

hydrogram/methods/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .invite_links import InviteLinks
2727
from .messages import Messages
2828
from .password import Password
29+
from .pyromod import Pyromod
2930
from .users import Users
3031
from .utilities import Utilities
3132

@@ -39,6 +40,7 @@ class Methods(
3940
Chats,
4041
Users,
4142
Messages,
43+
Pyromod,
4244
Decorators,
4345
Utilities,
4446
InviteLinks,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 .ask import Ask
21+
from .get_listener_matching_with_data import GetListenerMatchingWithData
22+
from .get_listener_matching_with_identifier_pattern import GetListenerMatchingWithIdentifierPattern
23+
from .get_many_listeners_matching_with_data import GetManyListenersMatchingWithData
24+
from .get_many_listeners_matching_with_identifier_pattern import (
25+
GetManyListenersMatchingWithIdentifierPattern,
26+
)
27+
from .listen import Listen
28+
from .register_next_step_handler import RegisterNextStepHandler
29+
from .remove_listener import RemoveListener
30+
from .stop_listener import StopListener
31+
from .stop_listening import StopListening
32+
33+
34+
class Pyromod(
35+
Ask,
36+
GetListenerMatchingWithData,
37+
GetListenerMatchingWithIdentifierPattern,
38+
GetManyListenersMatchingWithData,
39+
GetManyListenersMatchingWithIdentifierPattern,
40+
Listen,
41+
RegisterNextStepHandler,
42+
RemoveListener,
43+
StopListener,
44+
StopListening,
45+
):
46+
pass

0 commit comments

Comments
 (0)