kurimod 3.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kurimod/__init__.py +46 -0
- kurimod/config/__init__.py +12 -0
- kurimod/config.py +9 -0
- kurimod/decorators/__init__.py +0 -0
- kurimod/decorators/on_callback_query.py +0 -0
- kurimod/decorators/on_inline_query.py +0 -0
- kurimod/decorators/on_message.py +0 -0
- kurimod/exceptions/__init__.py +4 -0
- kurimod/exceptions/listener_stopped.py +2 -0
- kurimod/exceptions/listener_timeout.py +2 -0
- kurimod/helpers/__init__.py +22 -0
- kurimod/helpers/helpers.py +90 -0
- kurimod/listen/__init__.py +35 -0
- kurimod/listen/callback_query_handler.py +146 -0
- kurimod/listen/chat.py +21 -0
- kurimod/listen/client.py +232 -0
- kurimod/listen/message.py +32 -0
- kurimod/listen/message_handler.py +98 -0
- kurimod/listen/user.py +21 -0
- kurimod/nav/__init__.py +22 -0
- kurimod/nav/pagination.py +96 -0
- kurimod/types/__init__.py +5 -0
- kurimod/types/controller.py +0 -0
- kurimod/types/identifier.py +41 -0
- kurimod/types/listener.py +18 -0
- kurimod/types/listener_types.py +6 -0
- kurimod/utils/__init__.py +23 -0
- kurimod/utils/patch.py +110 -0
- kurimod-3.2.0.dist-info/METADATA +157 -0
- kurimod-3.2.0.dist-info/RECORD +34 -0
- kurimod-3.2.0.dist-info/WHEEL +4 -0
- kurimod-3.2.0.dist-info/licenses/COPYING +674 -0
- kurimod-3.2.0.dist-info/licenses/COPYING.lesser +165 -0
- kurimod-3.2.0.dist-info/licenses/NOTICE +17 -0
kurimod/__init__.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kurimod - A monkeypatched add-on for Kurigram/Pyrogram
|
|
3
|
+
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
|
4
|
+
|
|
5
|
+
This file is part of kurimod.
|
|
6
|
+
|
|
7
|
+
kurimod is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
kurimod 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 General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with kurimod. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .config import config
|
|
22
|
+
from .helpers import ikb, bki, ntb, btn, kb, kbtn, array_chunk, force_reply
|
|
23
|
+
from .listen import Client, MessageHandler, CallbackQueryHandler, Message, Chat, User
|
|
24
|
+
from .nav import Pagination
|
|
25
|
+
from .utils import patch_into, should_patch
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"config",
|
|
29
|
+
"Client",
|
|
30
|
+
"MessageHandler",
|
|
31
|
+
"Message",
|
|
32
|
+
"Chat",
|
|
33
|
+
"User",
|
|
34
|
+
"CallbackQueryHandler",
|
|
35
|
+
"patch_into",
|
|
36
|
+
"should_patch",
|
|
37
|
+
"ikb",
|
|
38
|
+
"bki",
|
|
39
|
+
"ntb",
|
|
40
|
+
"btn",
|
|
41
|
+
"kb",
|
|
42
|
+
"kbtn",
|
|
43
|
+
"array_chunk",
|
|
44
|
+
"force_reply",
|
|
45
|
+
"Pagination",
|
|
46
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
|
|
3
|
+
config = SimpleNamespace(
|
|
4
|
+
timeout_handler=None,
|
|
5
|
+
stopped_handler=None,
|
|
6
|
+
throw_exceptions=True,
|
|
7
|
+
unallowed_click_alert=True,
|
|
8
|
+
unallowed_click_alert_text=("[kurimod] You're not expected to click this button."),
|
|
9
|
+
disable_startup_logs=False,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = ["config"]
|
kurimod/config.py
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kurimod - A monkeypatcher add-on for Pyrogram
|
|
3
|
+
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
|
4
|
+
|
|
5
|
+
This file is part of kurimod.
|
|
6
|
+
|
|
7
|
+
kurimod is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
kurimod 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 General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with kurimod. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
"""
|
|
20
|
+
from .helpers import ikb, bki, ntb, btn, kb, kbtn, array_chunk, force_reply
|
|
21
|
+
|
|
22
|
+
__all__ = ["ikb", "bki", "ntb", "btn", "kb", "kbtn", "array_chunk", "force_reply"]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from pyrogram.types import (
|
|
2
|
+
InlineKeyboardButton,
|
|
3
|
+
InlineKeyboardMarkup,
|
|
4
|
+
KeyboardButton,
|
|
5
|
+
ReplyKeyboardMarkup,
|
|
6
|
+
ForceReply,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def ikb(rows=None):
|
|
11
|
+
if rows is None:
|
|
12
|
+
rows = []
|
|
13
|
+
|
|
14
|
+
lines = []
|
|
15
|
+
for row in rows:
|
|
16
|
+
line = []
|
|
17
|
+
for button in row:
|
|
18
|
+
button = (
|
|
19
|
+
btn(button, button) if isinstance(button, str) else btn(*button)
|
|
20
|
+
) # InlineKeyboardButton
|
|
21
|
+
line.append(button)
|
|
22
|
+
lines.append(line)
|
|
23
|
+
return InlineKeyboardMarkup(inline_keyboard=lines)
|
|
24
|
+
# return {'inline_keyboard': lines}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def btn(text, value, type="callback_data"):
|
|
28
|
+
return InlineKeyboardButton(text, **{type: value})
|
|
29
|
+
# return {'text': text, type: value}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# The inverse of above
|
|
33
|
+
def bki(keyboard):
|
|
34
|
+
lines = []
|
|
35
|
+
for row in keyboard.inline_keyboard:
|
|
36
|
+
line = []
|
|
37
|
+
for button in row:
|
|
38
|
+
button = ntb(button) # btn() format
|
|
39
|
+
line.append(button)
|
|
40
|
+
lines.append(line)
|
|
41
|
+
return lines
|
|
42
|
+
# return ikb() format
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def ntb(button):
|
|
46
|
+
for btn_type in [
|
|
47
|
+
"callback_data",
|
|
48
|
+
"url",
|
|
49
|
+
"switch_inline_query",
|
|
50
|
+
"switch_inline_query_current_chat",
|
|
51
|
+
"callback_game",
|
|
52
|
+
]:
|
|
53
|
+
value = getattr(button, btn_type)
|
|
54
|
+
if value:
|
|
55
|
+
break
|
|
56
|
+
button = [button.text, value]
|
|
57
|
+
if btn_type != "callback_data":
|
|
58
|
+
button.append(btn_type)
|
|
59
|
+
return button
|
|
60
|
+
# return {'text': text, type: value}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def kb(rows=None, **kwargs):
|
|
64
|
+
if rows is None:
|
|
65
|
+
rows = []
|
|
66
|
+
|
|
67
|
+
lines = []
|
|
68
|
+
for row in rows:
|
|
69
|
+
line = []
|
|
70
|
+
for button in row:
|
|
71
|
+
button_type = type(button)
|
|
72
|
+
if button_type == str:
|
|
73
|
+
button = KeyboardButton(button)
|
|
74
|
+
elif button_type == dict:
|
|
75
|
+
button = KeyboardButton(**button)
|
|
76
|
+
|
|
77
|
+
line.append(button)
|
|
78
|
+
lines.append(line)
|
|
79
|
+
return ReplyKeyboardMarkup(keyboard=lines, **kwargs)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
kbtn = KeyboardButton
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def force_reply(selective=True):
|
|
86
|
+
return ForceReply(selective=selective)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def array_chunk(input_array, size):
|
|
90
|
+
return [input_array[i : i + size] for i in range(0, len(input_array), size)]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kurimod - A monkeypatcher add-on for Pyrogram
|
|
3
|
+
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
|
|
4
|
+
|
|
5
|
+
This file is part of kurimod.
|
|
6
|
+
|
|
7
|
+
kurimod is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
kurimod 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 General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with kurimod. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .callback_query_handler import CallbackQueryHandler
|
|
22
|
+
from .chat import Chat
|
|
23
|
+
from .client import Client
|
|
24
|
+
from .message import Message
|
|
25
|
+
from .message_handler import MessageHandler
|
|
26
|
+
from .user import User
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Client",
|
|
30
|
+
"MessageHandler",
|
|
31
|
+
"Message",
|
|
32
|
+
"Chat",
|
|
33
|
+
"User",
|
|
34
|
+
"CallbackQueryHandler",
|
|
35
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from inspect import iscoroutinefunction
|
|
2
|
+
from typing import Callable, Tuple
|
|
3
|
+
|
|
4
|
+
import pyrogram
|
|
5
|
+
from pyrogram.filters import Filter
|
|
6
|
+
from pyrogram.types import CallbackQuery
|
|
7
|
+
|
|
8
|
+
from .client import Client
|
|
9
|
+
from ..config import config
|
|
10
|
+
from ..types import ListenerTypes, Identifier, Listener
|
|
11
|
+
from ..utils import patch_into, should_patch
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@patch_into(pyrogram.handlers.callback_query_handler.CallbackQueryHandler)
|
|
15
|
+
class CallbackQueryHandler(
|
|
16
|
+
pyrogram.handlers.callback_query_handler.CallbackQueryHandler
|
|
17
|
+
):
|
|
18
|
+
old__init__: Callable
|
|
19
|
+
|
|
20
|
+
@should_patch()
|
|
21
|
+
def __init__(self, callback: Callable, filters: Filter = None):
|
|
22
|
+
self.original_callback = callback
|
|
23
|
+
self.old__init__(self.resolve_future_or_callback, filters)
|
|
24
|
+
|
|
25
|
+
@should_patch()
|
|
26
|
+
def compose_data_identifier(self, query: CallbackQuery):
|
|
27
|
+
from_user = query.from_user
|
|
28
|
+
from_user_id = from_user.id if from_user else None
|
|
29
|
+
from_user_username = from_user.username if from_user else None
|
|
30
|
+
|
|
31
|
+
chat_id = None
|
|
32
|
+
message_id = None
|
|
33
|
+
|
|
34
|
+
if query.message:
|
|
35
|
+
message_id = getattr(
|
|
36
|
+
query.message, "id", getattr(query.message, "message_id", None)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if query.message.chat:
|
|
40
|
+
chat_id = [query.message.chat.id, query.message.chat.username]
|
|
41
|
+
|
|
42
|
+
return Identifier(
|
|
43
|
+
message_id=message_id,
|
|
44
|
+
chat_id=chat_id,
|
|
45
|
+
from_user_id=[from_user_id, from_user_username],
|
|
46
|
+
inline_message_id=query.inline_message_id,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@should_patch()
|
|
50
|
+
async def check_if_has_matching_listener(
|
|
51
|
+
self, client: Client, query: CallbackQuery
|
|
52
|
+
) -> Tuple[bool, Listener]:
|
|
53
|
+
data = self.compose_data_identifier(query)
|
|
54
|
+
|
|
55
|
+
listener = client.get_listener_matching_with_data(
|
|
56
|
+
data, ListenerTypes.CALLBACK_QUERY
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
listener_does_match = False
|
|
60
|
+
|
|
61
|
+
if listener:
|
|
62
|
+
filters = listener.filters
|
|
63
|
+
if callable(filters):
|
|
64
|
+
if iscoroutinefunction(filters.__call__):
|
|
65
|
+
listener_does_match = await filters(client, query)
|
|
66
|
+
else:
|
|
67
|
+
listener_does_match = await client.loop.run_in_executor(
|
|
68
|
+
None, filters, client, query
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
listener_does_match = True
|
|
72
|
+
|
|
73
|
+
return listener_does_match, listener
|
|
74
|
+
|
|
75
|
+
@should_patch()
|
|
76
|
+
async def check(self, client: Client, query: CallbackQuery):
|
|
77
|
+
listener_does_match, listener = await self.check_if_has_matching_listener(
|
|
78
|
+
client, query
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if callable(self.filters):
|
|
82
|
+
if iscoroutinefunction(self.filters.__call__):
|
|
83
|
+
handler_does_match = await self.filters(client, query)
|
|
84
|
+
else:
|
|
85
|
+
handler_does_match = await client.loop.run_in_executor(
|
|
86
|
+
None, self.filters, client, query
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
handler_does_match = True
|
|
90
|
+
|
|
91
|
+
data = self.compose_data_identifier(query)
|
|
92
|
+
|
|
93
|
+
if config.unallowed_click_alert:
|
|
94
|
+
# matches with the current query but from any user
|
|
95
|
+
permissive_identifier = Identifier(
|
|
96
|
+
chat_id=data.chat_id,
|
|
97
|
+
message_id=data.message_id,
|
|
98
|
+
inline_message_id=data.inline_message_id,
|
|
99
|
+
from_user_id=None,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
matches = permissive_identifier.matches(data)
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
listener
|
|
106
|
+
and (matches and not listener_does_match)
|
|
107
|
+
and listener.unallowed_click_alert
|
|
108
|
+
):
|
|
109
|
+
alert = (
|
|
110
|
+
listener.unallowed_click_alert
|
|
111
|
+
if isinstance(listener.unallowed_click_alert, str)
|
|
112
|
+
else config.unallowed_click_alert_text
|
|
113
|
+
)
|
|
114
|
+
await query.answer(alert)
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
# let handler get the chance to handle if listener
|
|
118
|
+
# exists but its filters doesn't match
|
|
119
|
+
return listener_does_match or handler_does_match
|
|
120
|
+
|
|
121
|
+
@should_patch()
|
|
122
|
+
async def resolve_future_or_callback(
|
|
123
|
+
self, client: Client, query: CallbackQuery, *args
|
|
124
|
+
):
|
|
125
|
+
listener_does_match, listener = await self.check_if_has_matching_listener(
|
|
126
|
+
client, query
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if listener and listener_does_match:
|
|
130
|
+
client.remove_listener(listener)
|
|
131
|
+
|
|
132
|
+
if listener.future and not listener.future.done():
|
|
133
|
+
listener.future.set_result(query)
|
|
134
|
+
|
|
135
|
+
raise pyrogram.StopPropagation
|
|
136
|
+
elif listener.callback:
|
|
137
|
+
if iscoroutinefunction(listener.callback):
|
|
138
|
+
await listener.callback(client, query, *args)
|
|
139
|
+
else:
|
|
140
|
+
listener.callback(client, query, *args)
|
|
141
|
+
|
|
142
|
+
raise pyrogram.StopPropagation
|
|
143
|
+
else:
|
|
144
|
+
raise ValueError("Listener must have either a future or a callback")
|
|
145
|
+
else:
|
|
146
|
+
await self.original_callback(client, query, *args)
|
kurimod/listen/chat.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import pyrogram
|
|
2
|
+
|
|
3
|
+
from .client import Client
|
|
4
|
+
from ..utils import patch_into, should_patch
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@patch_into(pyrogram.types.user_and_chats.chat.Chat)
|
|
8
|
+
class Chat(pyrogram.types.user_and_chats.chat.Chat):
|
|
9
|
+
_client: Client
|
|
10
|
+
|
|
11
|
+
@should_patch()
|
|
12
|
+
def listen(self, *args, **kwargs):
|
|
13
|
+
return self._client.listen(*args, chat_id=self.id, **kwargs)
|
|
14
|
+
|
|
15
|
+
@should_patch()
|
|
16
|
+
def ask(self, text, *args, **kwargs):
|
|
17
|
+
return self._client.ask(self.id, text, *args, **kwargs)
|
|
18
|
+
|
|
19
|
+
@should_patch()
|
|
20
|
+
def stop_listening(self, *args, **kwargs):
|
|
21
|
+
return self._client.stop_listening(*args, chat_id=self.id, **kwargs)
|
kurimod/listen/client.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from inspect import iscoroutinefunction
|
|
3
|
+
from typing import Optional, Callable, Dict, List, Union
|
|
4
|
+
|
|
5
|
+
import pyrogram
|
|
6
|
+
from pyrogram.filters import Filter
|
|
7
|
+
|
|
8
|
+
from ..config import config
|
|
9
|
+
from ..exceptions import ListenerTimeout, ListenerStopped
|
|
10
|
+
from ..types import ListenerTypes, Identifier, Listener
|
|
11
|
+
from ..utils import should_patch, patch_into
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@patch_into(pyrogram.client.Client)
|
|
16
|
+
class Client(pyrogram.client.Client.on_callback_query()):
|
|
17
|
+
listeners: Dict[ListenerTypes, List[Listener]]
|
|
18
|
+
old__init__: Callable
|
|
19
|
+
|
|
20
|
+
@should_patch()
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
self.listeners = {listener_type: [] for listener_type in ListenerTypes}
|
|
23
|
+
self.old__init__(*args, **kwargs)
|
|
24
|
+
|
|
25
|
+
@should_patch()
|
|
26
|
+
async def listen(
|
|
27
|
+
self,
|
|
28
|
+
filters: Optional[Filter] = None,
|
|
29
|
+
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
|
|
30
|
+
timeout: Optional[int] = None,
|
|
31
|
+
unallowed_click_alert: bool = True,
|
|
32
|
+
chat_id: Union[Union[int, str], List[Union[int, str]]] = None,
|
|
33
|
+
user_id: Union[Union[int, str], List[Union[int, str]]] = None,
|
|
34
|
+
message_id: Union[int, List[int]] = None,
|
|
35
|
+
inline_message_id: Union[str, List[str]] = None,
|
|
36
|
+
):
|
|
37
|
+
pattern = Identifier(
|
|
38
|
+
from_user_id=user_id,
|
|
39
|
+
chat_id=chat_id,
|
|
40
|
+
message_id=message_id,
|
|
41
|
+
inline_message_id=inline_message_id,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
loop = asyncio.get_event_loop()
|
|
45
|
+
future = loop.create_future()
|
|
46
|
+
|
|
47
|
+
listener = Listener(
|
|
48
|
+
future=future,
|
|
49
|
+
filters=filters,
|
|
50
|
+
unallowed_click_alert=unallowed_click_alert,
|
|
51
|
+
identifier=pattern,
|
|
52
|
+
listener_type=listener_type,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
future.add_done_callback(lambda _future: self.remove_listener(listener))
|
|
56
|
+
|
|
57
|
+
self.listeners[listener_type].append(listener)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
return await asyncio.wait_for(future, timeout)
|
|
61
|
+
except asyncio.exceptions.TimeoutError:
|
|
62
|
+
if callable(config.timeout_handler):
|
|
63
|
+
if iscoroutinefunction(config.timeout_handler.__call__):
|
|
64
|
+
await config.timeout_handler(pattern, listener, timeout)
|
|
65
|
+
else:
|
|
66
|
+
await self.loop.run_in_executor(
|
|
67
|
+
None, config.timeout_handler, pattern, listener, timeout
|
|
68
|
+
)
|
|
69
|
+
elif config.throw_exceptions:
|
|
70
|
+
raise ListenerTimeout(timeout)
|
|
71
|
+
|
|
72
|
+
@should_patch()
|
|
73
|
+
async def ask(
|
|
74
|
+
self,
|
|
75
|
+
chat_id: Union[Union[int, str], List[Union[int, str]]],
|
|
76
|
+
text: str,
|
|
77
|
+
filters: Optional[Filter] = None,
|
|
78
|
+
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
|
|
79
|
+
timeout: Optional[int] = None,
|
|
80
|
+
unallowed_click_alert: bool = True,
|
|
81
|
+
user_id: Union[Union[int, str], List[Union[int, str]]] = None,
|
|
82
|
+
message_id: Union[int, List[int]] = None,
|
|
83
|
+
inline_message_id: Union[str, List[str]] = None,
|
|
84
|
+
*args,
|
|
85
|
+
**kwargs,
|
|
86
|
+
):
|
|
87
|
+
sent_message = None
|
|
88
|
+
if text.strip() != "":
|
|
89
|
+
chat_to_ask = chat_id[0] if isinstance(chat_id, list) else chat_id
|
|
90
|
+
sent_message = await self.send_message(chat_to_ask, text, *args, **kwargs)
|
|
91
|
+
|
|
92
|
+
response = await self.listen(
|
|
93
|
+
filters=filters,
|
|
94
|
+
listener_type=listener_type,
|
|
95
|
+
timeout=timeout,
|
|
96
|
+
unallowed_click_alert=unallowed_click_alert,
|
|
97
|
+
chat_id=chat_id,
|
|
98
|
+
user_id=user_id,
|
|
99
|
+
message_id=message_id,
|
|
100
|
+
inline_message_id=inline_message_id,
|
|
101
|
+
)
|
|
102
|
+
if response:
|
|
103
|
+
response.sent_message = sent_message
|
|
104
|
+
|
|
105
|
+
return response
|
|
106
|
+
|
|
107
|
+
@should_patch()
|
|
108
|
+
def remove_listener(self, listener: Listener):
|
|
109
|
+
try:
|
|
110
|
+
self.listeners[listener.listener_type].remove(listener)
|
|
111
|
+
except ValueError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
@should_patch()
|
|
115
|
+
def get_listener_matching_with_data(
|
|
116
|
+
self, data: Identifier, listener_type: ListenerTypes
|
|
117
|
+
) -> Optional[Listener]:
|
|
118
|
+
matching = []
|
|
119
|
+
for listener in self.listeners[listener_type]:
|
|
120
|
+
if listener.identifier.matches(data):
|
|
121
|
+
matching.append(listener)
|
|
122
|
+
|
|
123
|
+
# in case of multiple matching listeners, the most specific should be returned
|
|
124
|
+
def count_populated_attributes(listener_item: Listener):
|
|
125
|
+
return listener_item.identifier.count_populated()
|
|
126
|
+
|
|
127
|
+
return max(matching, key=count_populated_attributes, default=None)
|
|
128
|
+
|
|
129
|
+
def get_listener_matching_with_identifier_pattern(
|
|
130
|
+
self, pattern: Identifier, listener_type: ListenerTypes
|
|
131
|
+
) -> Optional[Listener]:
|
|
132
|
+
matching = []
|
|
133
|
+
for listener in self.listeners[listener_type]:
|
|
134
|
+
if pattern.matches(listener.identifier):
|
|
135
|
+
matching.append(listener)
|
|
136
|
+
|
|
137
|
+
# in case of multiple matching listeners, the most specific should be returned
|
|
138
|
+
|
|
139
|
+
def count_populated_attributes(listener_item: Listener):
|
|
140
|
+
return listener_item.identifier.count_populated()
|
|
141
|
+
|
|
142
|
+
return max(matching, key=count_populated_attributes, default=None)
|
|
143
|
+
|
|
144
|
+
@should_patch()
|
|
145
|
+
def get_many_listeners_matching_with_data(
|
|
146
|
+
self,
|
|
147
|
+
data: Identifier,
|
|
148
|
+
listener_type: ListenerTypes,
|
|
149
|
+
) -> List[Listener]:
|
|
150
|
+
listeners = []
|
|
151
|
+
for listener in self.listeners[listener_type]:
|
|
152
|
+
if listener.identifier.matches(data):
|
|
153
|
+
listeners.append(listener)
|
|
154
|
+
return listeners
|
|
155
|
+
|
|
156
|
+
@should_patch()
|
|
157
|
+
def get_many_listeners_matching_with_identifier_pattern(
|
|
158
|
+
self,
|
|
159
|
+
pattern: Identifier,
|
|
160
|
+
listener_type: ListenerTypes,
|
|
161
|
+
) -> List[Listener]:
|
|
162
|
+
listeners = []
|
|
163
|
+
for listener in self.listeners[listener_type]:
|
|
164
|
+
if pattern.matches(listener.identifier):
|
|
165
|
+
listeners.append(listener)
|
|
166
|
+
return listeners
|
|
167
|
+
|
|
168
|
+
@should_patch()
|
|
169
|
+
async def stop_listening(
|
|
170
|
+
self,
|
|
171
|
+
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
|
|
172
|
+
chat_id: Union[Union[int, str], List[Union[int, str]]] = None,
|
|
173
|
+
user_id: Union[Union[int, str], List[Union[int, str]]] = None,
|
|
174
|
+
message_id: Union[int, List[int]] = None,
|
|
175
|
+
inline_message_id: Union[str, List[str]] = None,
|
|
176
|
+
):
|
|
177
|
+
pattern = Identifier(
|
|
178
|
+
from_user_id=user_id,
|
|
179
|
+
chat_id=chat_id,
|
|
180
|
+
message_id=message_id,
|
|
181
|
+
inline_message_id=inline_message_id,
|
|
182
|
+
)
|
|
183
|
+
listeners = self.get_many_listeners_matching_with_identifier_pattern(pattern, listener_type)
|
|
184
|
+
|
|
185
|
+
for listener in listeners:
|
|
186
|
+
await self.stop_listener(listener)
|
|
187
|
+
|
|
188
|
+
@should_patch()
|
|
189
|
+
async def stop_listener(self, listener: Listener):
|
|
190
|
+
self.remove_listener(listener)
|
|
191
|
+
|
|
192
|
+
if listener.future.done():
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
if callable(config.stopped_handler):
|
|
196
|
+
if iscoroutinefunction(config.stopped_handler.__call__):
|
|
197
|
+
await config.stopped_handler(None, listener)
|
|
198
|
+
else:
|
|
199
|
+
await self.loop.run_in_executor(
|
|
200
|
+
None, config.stopped_handler, None, listener
|
|
201
|
+
)
|
|
202
|
+
elif config.throw_exceptions:
|
|
203
|
+
listener.future.set_exception(ListenerStopped())
|
|
204
|
+
|
|
205
|
+
@should_patch()
|
|
206
|
+
def register_next_step_handler(
|
|
207
|
+
self,
|
|
208
|
+
callback: Callable,
|
|
209
|
+
filters: Optional[Filter] = None,
|
|
210
|
+
listener_type: ListenerTypes = ListenerTypes.MESSAGE,
|
|
211
|
+
unallowed_click_alert: bool = True,
|
|
212
|
+
chat_id: Union[Union[int, str], List[Union[int, str]]] = None,
|
|
213
|
+
user_id: Union[Union[int, str], List[Union[int, str]]] = None,
|
|
214
|
+
message_id: Union[int, List[int]] = None,
|
|
215
|
+
inline_message_id: Union[str, List[str]] = None,
|
|
216
|
+
):
|
|
217
|
+
pattern = Identifier(
|
|
218
|
+
from_user_id=user_id,
|
|
219
|
+
chat_id=chat_id,
|
|
220
|
+
message_id=message_id,
|
|
221
|
+
inline_message_id=inline_message_id,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
listener = Listener(
|
|
225
|
+
callback=callback,
|
|
226
|
+
filters=filters,
|
|
227
|
+
unallowed_click_alert=unallowed_click_alert,
|
|
228
|
+
identifier=pattern,
|
|
229
|
+
listener_type=listener_type,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
self.listeners[listener_type].append(listener)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Optional, Union, List
|
|
2
|
+
|
|
3
|
+
import pyrogram
|
|
4
|
+
|
|
5
|
+
from .client import Client
|
|
6
|
+
from ..types import ListenerTypes
|
|
7
|
+
from ..utils import patch_into, should_patch
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@patch_into(pyrogram.types.messages_and_media.message.Message)
|
|
11
|
+
class Message(pyrogram.types.messages_and_media.message.Message):
|
|
12
|
+
_client = Client
|
|
13
|
+
|
|
14
|
+
@should_patch()
|
|
15
|
+
async def wait_for_click(
|
|
16
|
+
self,
|
|
17
|
+
from_user_id: Optional[Union[Union[int, str], List[Union[int, str]]]] = None,
|
|
18
|
+
timeout: Optional[int] = None,
|
|
19
|
+
filters=None,
|
|
20
|
+
alert: Union[str, bool] = True,
|
|
21
|
+
):
|
|
22
|
+
message_id = getattr(self, "id", getattr(self, "message_id", None))
|
|
23
|
+
|
|
24
|
+
return await self._client.listen(
|
|
25
|
+
listener_type=ListenerTypes.CALLBACK_QUERY,
|
|
26
|
+
timeout=timeout,
|
|
27
|
+
filters=filters,
|
|
28
|
+
unallowed_click_alert=alert,
|
|
29
|
+
chat_id=self.chat.id,
|
|
30
|
+
user_id=from_user_id,
|
|
31
|
+
message_id=message_id,
|
|
32
|
+
)
|