patchpyro 2.0.5__tar.gz → 2.1.1__tar.gz

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.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: patchpyro
3
+ Version: 2.1.1
4
+ Summary: A modified pyromod by a.devh.in
5
+ Home-page: https://github.com/adityaprasad502/patchpyro
6
+ Author: Cezar H. & adityaprasad502
7
+ Author-email: plutoniumx502@gmail.com
8
+ License: LGPLv3+
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: COPYING
15
+ License-File: COPYING.lesser
16
+ License-File: NOTICE
17
+ Requires-Dist: kurigram>=2.0.69
18
+ Dynamic: author
19
+ Dynamic: author-email
20
+ Dynamic: classifier
21
+ Dynamic: description
22
+ Dynamic: description-content-type
23
+ Dynamic: home-page
24
+ Dynamic: license
25
+ Dynamic: license-file
26
+ Dynamic: requires-dist
27
+ Dynamic: requires-python
28
+ Dynamic: summary
29
+
30
+ # PatchPyro
31
+
32
+ A fork of pyromod (renamed as patchpyro) providing conversation patches for Pyrogram-based clients.
33
+
34
+ ## Requirements
35
+ ```text
36
+ kurigram>=2.0.69
37
+ python>=3.9
38
+ ```
39
+
40
+ ## Installation
41
+ ```bash
42
+ pip install patchpyro
43
+ ```
44
+
45
+ ## Usage
46
+ Import `patchpyro` at least once in your script so you can use the modified Pyrogram in all files of the same process.
47
+
48
+ ### Example
49
+ ```python
50
+ # config.py
51
+ from patchpyro import listen # or import patchpyro.listen
52
+ from pyrogram import Client
53
+
54
+ listen.thank() # use this if your linter/IDE flags patchpyro as an unused import.
55
+
56
+ mybot = Client("mysession")
57
+ ```
58
+
59
+ ```python
60
+ # any other .py
61
+ from config import mybot
62
+ # no need to import patchpyro again; Pyrogram is already monkeypatched globally (in the same process)
63
+ ```
64
+
65
+ ## Available Methods
66
+ Just importing `patchpyro.listen` will automatically do the monkeypatch and you'll get these new methods:
67
+
68
+ ### `Chat.listen`, `User.listen`
69
+ - `await mybot.listen(chat_id, filters=None, timeout=30)`
70
+ - Awaits a new message in the specified chat and returns it.
71
+ - Raises `asyncio.TimeoutError` if timeout (optional parameter) occurs.
72
+ - You can pass Update Filters to the filters parameter just like you do for the update handlers.
73
+ - E.g. `filters=filters.photo & filters.bot`
74
+
75
+ ### `Chat.ask`, `User.ask`
76
+ - `await mybot.ask(text, chat_id, filters=None, timeout=30)`
77
+ - Same as `.listen()` above, but sends a message before awaiting.
78
+ - You can pass custom parameters to its internal `send_message()` call. Check the example below.
79
+
80
+ ### `Chat.asker`, `User.asker`
81
+ - `await mybot.asker(chat_id, filters=None, timeout=36)`
82
+ - Same as `.listen()` but `.asker()` returns `None` instead of raising `asyncio.TimeoutError`.
83
+ - Useful for graceful timeout handling, `.asker()` has a default timeout of 2 minutes (adjustable via `timeout` argument).
84
+
85
+ ## Examples
86
+
87
+ ### `asker()`
88
+ ```python
89
+ # ...
90
+ sendx = await client.send_message(chat_id, "`Send me your name:`")
91
+ answer = await client.asker(chat_id, filters=None, timeout=60)
92
+ if not answer: # `None` if timeout reached with no reply.
93
+ return await sendx.reply_text("How long should I wait? Bye!")
94
+ await answer.reply_text(f"{answer.text}, That's a cool name!")
95
+ # ...
96
+ ```
97
+
98
+ ### `ask()`
99
+ ```python
100
+ # ...
101
+ answer = await client.ask(chat_id, '*Send me your name:*', parse_mode=enums.ParseMode.MARKDOWN)
102
+ await client.send_message(chat_id, f'Your name is: {answer.text}')
103
+ # ...
104
+ ```
105
+
106
+ ## Copyright & License
107
+ This project includes code from:
108
+ - **[kurimod](https://github.com/ohmyarthur/kurimod)**: Monkeypatcher logic and modern async compatibility fixes.
109
+ - **Pyrogram**: Telegram MTProto API Client Library for Python. Copyright (C) 2017-2022 Dan <<https://github.com/delivrance>>
110
+ - **pyromod**: Original conversation patch logic.
111
+
112
+ Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser)
@@ -0,0 +1,83 @@
1
+ # PatchPyro
2
+
3
+ A fork of pyromod (renamed as patchpyro) providing conversation patches for Pyrogram-based clients.
4
+
5
+ ## Requirements
6
+ ```text
7
+ kurigram>=2.0.69
8
+ python>=3.9
9
+ ```
10
+
11
+ ## Installation
12
+ ```bash
13
+ pip install patchpyro
14
+ ```
15
+
16
+ ## Usage
17
+ Import `patchpyro` at least once in your script so you can use the modified Pyrogram in all files of the same process.
18
+
19
+ ### Example
20
+ ```python
21
+ # config.py
22
+ from patchpyro import listen # or import patchpyro.listen
23
+ from pyrogram import Client
24
+
25
+ listen.thank() # use this if your linter/IDE flags patchpyro as an unused import.
26
+
27
+ mybot = Client("mysession")
28
+ ```
29
+
30
+ ```python
31
+ # any other .py
32
+ from config import mybot
33
+ # no need to import patchpyro again; Pyrogram is already monkeypatched globally (in the same process)
34
+ ```
35
+
36
+ ## Available Methods
37
+ Just importing `patchpyro.listen` will automatically do the monkeypatch and you'll get these new methods:
38
+
39
+ ### `Chat.listen`, `User.listen`
40
+ - `await mybot.listen(chat_id, filters=None, timeout=30)`
41
+ - Awaits a new message in the specified chat and returns it.
42
+ - Raises `asyncio.TimeoutError` if timeout (optional parameter) occurs.
43
+ - You can pass Update Filters to the filters parameter just like you do for the update handlers.
44
+ - E.g. `filters=filters.photo & filters.bot`
45
+
46
+ ### `Chat.ask`, `User.ask`
47
+ - `await mybot.ask(text, chat_id, filters=None, timeout=30)`
48
+ - Same as `.listen()` above, but sends a message before awaiting.
49
+ - You can pass custom parameters to its internal `send_message()` call. Check the example below.
50
+
51
+ ### `Chat.asker`, `User.asker`
52
+ - `await mybot.asker(chat_id, filters=None, timeout=36)`
53
+ - Same as `.listen()` but `.asker()` returns `None` instead of raising `asyncio.TimeoutError`.
54
+ - Useful for graceful timeout handling, `.asker()` has a default timeout of 2 minutes (adjustable via `timeout` argument).
55
+
56
+ ## Examples
57
+
58
+ ### `asker()`
59
+ ```python
60
+ # ...
61
+ sendx = await client.send_message(chat_id, "`Send me your name:`")
62
+ answer = await client.asker(chat_id, filters=None, timeout=60)
63
+ if not answer: # `None` if timeout reached with no reply.
64
+ return await sendx.reply_text("How long should I wait? Bye!")
65
+ await answer.reply_text(f"{answer.text}, That's a cool name!")
66
+ # ...
67
+ ```
68
+
69
+ ### `ask()`
70
+ ```python
71
+ # ...
72
+ answer = await client.ask(chat_id, '*Send me your name:*', parse_mode=enums.ParseMode.MARKDOWN)
73
+ await client.send_message(chat_id, f'Your name is: {answer.text}')
74
+ # ...
75
+ ```
76
+
77
+ ## Copyright & License
78
+ This project includes code from:
79
+ - **[kurimod](https://github.com/ohmyarthur/kurimod)**: Monkeypatcher logic and modern async compatibility fixes.
80
+ - **Pyrogram**: Telegram MTProto API Client Library for Python. Copyright (C) 2017-2022 Dan <<https://github.com/delivrance>>
81
+ - **pyromod**: Original conversation patch logic.
82
+
83
+ Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser)
@@ -1,6 +1,5 @@
1
- """
2
- patchpyro - A monkeypatcher add-on for Pyrogram
3
- Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>
1
+ """patchpyro - A monkeypatcher add-on for Pyrogram
2
+ Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>.
4
3
 
5
4
  This file is part of patchpyro and was forked from usernein/pyromod.
6
5
 
@@ -18,4 +17,8 @@ You should have received a copy of the GNU General Public License
18
17
  along with patchpyro. If not, see <https://www.gnu.org/licenses/>.
19
18
  """
20
19
 
21
- from .listen import Chat, Client, MessageHandler, User
20
+
21
+ __version__ = "2.1.1"
22
+ # change in setup.py aswell
23
+
24
+ from . import listen
@@ -1,6 +1,5 @@
1
- """
2
- patchpyro - A monkeypatcher add-on for Pyrogram
3
- Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>
1
+ """patchpyro - A monkeypatcher add-on for Pyrogram
2
+ Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>.
4
3
 
5
4
  This file is part of patchpyro and was forked from usernein/pyromod.
6
5
 
@@ -19,20 +18,11 @@ along with patchpyro. If not, see <https://www.gnu.org/licenses/>.
19
18
  """
20
19
 
21
20
 
22
- def patch(obj):
23
- def is_patchable(item):
24
- return getattr(item[1], "patchable", False)
25
-
26
- def wrapper(container):
27
- for name, func in filter(is_patchable, container.__dict__.items()):
28
- old = getattr(obj, name, None)
29
- setattr(obj, f"old{name}", old)
30
- setattr(obj, name, func)
31
- return container
32
-
33
- return wrapper
34
-
21
+ from .listen import Chat, Client, MessageHandler, User
22
+ def thank() -> None:
23
+ """A dummy function to prevent patchpyro.listen from being removed by formatters and linters."""
24
+ from patchpyro import __version__
25
+ import logging
26
+ logging.debug(f"Thank you for using patchpyro v{__version__}!")
35
27
 
36
- def patchable(func):
37
- func.patchable = True
38
- return func
28
+ __all__ = ["Chat", "Client", "MessageHandler", "User", "thank"]
@@ -0,0 +1,186 @@
1
+ """patchpyro - A monkeypatcher add-on for Pyrogram
2
+ Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>.
3
+
4
+ This file is part of patchpyro and was forked from usernein/pyromod.
5
+ Additional patching logic was adapted from kurimod (C) Dias Arthur.
6
+
7
+ patchpyro 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
+ patchpyro 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 patchpyro. If not, see <https://www.gnu.org/licenses/>.
19
+ """
20
+
21
+ import asyncio
22
+ import functools
23
+ from contextlib import suppress
24
+ from inspect import iscoroutinefunction
25
+
26
+ import pyrogram
27
+ import pyrogram.client
28
+ import pyrogram.handlers.message_handler
29
+ import pyrogram.types.user_and_chats.chat
30
+ import pyrogram.types.user_and_chats.user
31
+
32
+ from patchpyro.utils import patch_into, should_patch
33
+
34
+
35
+ class ListenerCanceled(Exception):
36
+ pass
37
+
38
+
39
+ pyrogram.errors.ListenerCanceled = ListenerCanceled
40
+
41
+
42
+ @patch_into(pyrogram.client.Client)
43
+ class Client:
44
+ @should_patch()
45
+ def __init__(self, *args, **kwargs) -> None:
46
+ self.listening = {}
47
+ self.using_mod = True
48
+
49
+ self.old__init__(*args, **kwargs)
50
+
51
+ @should_patch()
52
+ async def listen(
53
+ self,
54
+ chat_id: int | str,
55
+ filters: pyrogram.filters.Filter | None = None,
56
+ timeout: int | None = None,
57
+ ) -> pyrogram.types.Message:
58
+ if not isinstance(chat_id, int):
59
+ chat = await self.get_chat(chat_id)
60
+ chat_id = chat.id
61
+
62
+ # Cancel existing listener in this chat if any
63
+ self.cancel_listener(chat_id)
64
+
65
+ loop = asyncio.get_running_loop()
66
+ future = loop.create_future()
67
+ future.add_done_callback(functools.partial(self.clear_listener, chat_id))
68
+ self.listening[chat_id] = {"future": future, "filters": filters}
69
+
70
+ return await asyncio.wait_for(future, timeout)
71
+
72
+ @should_patch()
73
+ async def ask(
74
+ self,
75
+ chat_id: int | str,
76
+ text: str,
77
+ filters: pyrogram.filters.Filter | None = None,
78
+ timeout: int | None = None,
79
+ *args,
80
+ **kwargs,
81
+ ) -> pyrogram.types.Message:
82
+ request = await self.send_message(chat_id, text, *args, **kwargs)
83
+ response = await self.listen(chat_id, filters, timeout)
84
+ response.request = request
85
+ return response
86
+
87
+ @should_patch()
88
+ async def asker(
89
+ self,
90
+ chat_id: int | str,
91
+ filters: pyrogram.filters.Filter | None = None,
92
+ timeout: int = 119,
93
+ ) -> pyrogram.types.Message | None:
94
+ try:
95
+ return await self.listen(chat_id, filters, timeout)
96
+ except asyncio.TimeoutError:
97
+ return None
98
+
99
+ @should_patch()
100
+ def clear_listener(self, chat_id: int, future: asyncio.Future) -> None:
101
+ with suppress(KeyError):
102
+ if (
103
+ chat_id in self.listening
104
+ and future == self.listening[chat_id]["future"]
105
+ ):
106
+ self.listening.pop(chat_id, None)
107
+
108
+ @should_patch()
109
+ def cancel_listener(self, chat_id: int) -> None:
110
+ listener = self.listening.get(chat_id)
111
+ if not listener or listener["future"].done():
112
+ return
113
+
114
+ if not listener["future"].done():
115
+ listener["future"].set_exception(ListenerCanceled())
116
+ self.clear_listener(chat_id, listener["future"])
117
+
118
+
119
+ @patch_into(pyrogram.handlers.message_handler.MessageHandler)
120
+ class MessageHandler:
121
+ @should_patch()
122
+ def __init__(self, callback: callable, filters=None) -> None:
123
+ self.user_callback = callback
124
+ self.old__init__(self.resolve_listener, filters)
125
+
126
+ @should_patch()
127
+ async def resolve_listener(self, client: "pyrogram.Client", message: pyrogram.types.Message, *args) -> None:
128
+ listener = client.listening.get(message.chat.id)
129
+ if listener and not listener["future"].done():
130
+ listener["future"].set_result(message)
131
+ raise pyrogram.StopPropagation
132
+
133
+ if listener and listener["future"].done():
134
+ client.clear_listener(message.chat.id, listener["future"])
135
+
136
+ await self.user_callback(client, message, *args)
137
+
138
+ @should_patch()
139
+ async def check(self, client: "pyrogram.Client", update: pyrogram.types.Message):
140
+ listener = client.listening.get(update.chat.id)
141
+
142
+ if listener and not listener["future"].done():
143
+ filters = listener["filters"]
144
+ if callable(filters):
145
+ if iscoroutinefunction(filters.__call__):
146
+ return await filters(client, update)
147
+ loop = asyncio.get_running_loop()
148
+ return await loop.run_in_executor(None, filters, client, update)
149
+ return True
150
+
151
+ if callable(self.filters):
152
+ if iscoroutinefunction(self.filters.__call__):
153
+ return await self.filters(client, update)
154
+ loop = asyncio.get_running_loop()
155
+ return await loop.run_in_executor(None, self.filters, client, update)
156
+ return True
157
+
158
+
159
+ @patch_into(pyrogram.types.user_and_chats.chat.Chat)
160
+ class Chat(pyrogram.types.Chat):
161
+ @should_patch()
162
+ async def listen(self, *args, **kwargs) -> pyrogram.types.Message:
163
+ return await self._client.listen(self.id, *args, **kwargs)
164
+
165
+ @should_patch()
166
+ async def ask(self, *args, **kwargs) -> pyrogram.types.Message:
167
+ return await self._client.ask(self.id, *args, **kwargs)
168
+
169
+ @should_patch()
170
+ def cancel_listener(self) -> None:
171
+ return self._client.cancel_listener(self.id)
172
+
173
+
174
+ @patch_into(pyrogram.types.user_and_chats.user.User)
175
+ class User(pyrogram.types.User):
176
+ @should_patch()
177
+ async def listen(self, *args, **kwargs) -> pyrogram.types.Message:
178
+ return await self._client.listen(self.id, *args, **kwargs)
179
+
180
+ @should_patch()
181
+ async def ask(self, *args, **kwargs) -> pyrogram.types.Message:
182
+ return await self._client.ask(self.id, *args, **kwargs)
183
+
184
+ @should_patch()
185
+ def cancel_listener(self) -> None:
186
+ return self._client.cancel_listener(self.id)
@@ -1,6 +1,5 @@
1
- """
2
- patchpyro - A monkeypatcher add-on for Pyrogram
3
- Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>
1
+ """patchpyro - A monkeypatcher add-on for Pyrogram
2
+ Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>.
4
3
 
5
4
  This file is part of patchpyro and was forked from usernein/pyromod.
6
5
 
@@ -18,4 +17,6 @@ You should have received a copy of the GNU General Public License
18
17
  along with patchpyro. If not, see <https://www.gnu.org/licenses/>.
19
18
  """
20
19
 
21
- from .utils import patch, patchable
20
+ from .utils import patch, patch_into, patchable, should_patch
21
+
22
+ __all__ = ["patch", "patch_into", "patchable", "should_patch"]
@@ -0,0 +1,90 @@
1
+ """patchpyro - A monkeypatcher add-on for Pyrogram
2
+ Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>.
3
+
4
+ This file is part of patchpyro and was forked from usernein/pyromod.
5
+ Additional patching logic was adapted from kurimod (C) Dias Arthur.
6
+
7
+ patchpyro 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
+ patchpyro 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 patchpyro. If not, see <https://www.gnu.org/licenses/>.
19
+ """
20
+ from collections.abc import Callable
21
+ from contextlib import asynccontextmanager, contextmanager
22
+ from inspect import iscoroutinefunction
23
+ from typing import TypeVar
24
+
25
+ from pyrogram.sync import async_to_sync
26
+
27
+ T = TypeVar("T")
28
+
29
+
30
+ def patch(target_class):
31
+ def is_patchable(item):
32
+ func = item[1]
33
+ return getattr(func, "should_patch", False) or getattr(func, "patchable", False)
34
+
35
+ def wrapper(container: type[T]) -> T:
36
+ for name, func in filter(is_patchable, container.__dict__.items()):
37
+ old = getattr(target_class, name, None)
38
+ if old is not None:
39
+ setattr(target_class, "old" + name, old)
40
+
41
+ tempConf = {
42
+ i: getattr(func, i, False)
43
+ for i in ["is_property", "is_static", "is_context"]
44
+ }
45
+
46
+ if not iscoroutinefunction(func):
47
+ async_to_sync(container, name)
48
+ func = getattr(container, name)
49
+
50
+ for tKey, tValue in tempConf.items():
51
+ setattr(func, tKey, tValue)
52
+
53
+ if func.is_property:
54
+ func = property(func)
55
+ elif func.is_static:
56
+ func = staticmethod(func)
57
+ elif func.is_context:
58
+ if iscoroutinefunction(func.__call__):
59
+ func = asynccontextmanager(func)
60
+ else:
61
+ func = contextmanager(func)
62
+
63
+ setattr(target_class, name, func)
64
+ return container
65
+
66
+ return wrapper
67
+
68
+
69
+ def patchable(
70
+ func: Callable | None = None,
71
+ *,
72
+ is_property: bool = False,
73
+ is_static: bool = False,
74
+ is_context: bool = False,
75
+ ) -> Callable:
76
+ def wrapper(f: Callable) -> Callable:
77
+ f.should_patch = True
78
+ f.patchable = True
79
+ f.is_property = is_property
80
+ f.is_static = is_static
81
+ f.is_context = is_context
82
+ return f
83
+
84
+ if func is not None:
85
+ return wrapper(func)
86
+ return wrapper
87
+
88
+
89
+ patch_into = patch
90
+ should_patch = patchable
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: patchpyro
3
+ Version: 2.1.1
4
+ Summary: A modified pyromod by a.devh.in
5
+ Home-page: https://github.com/adityaprasad502/patchpyro
6
+ Author: Cezar H. & adityaprasad502
7
+ Author-email: plutoniumx502@gmail.com
8
+ License: LGPLv3+
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: COPYING
15
+ License-File: COPYING.lesser
16
+ License-File: NOTICE
17
+ Requires-Dist: kurigram>=2.0.69
18
+ Dynamic: author
19
+ Dynamic: author-email
20
+ Dynamic: classifier
21
+ Dynamic: description
22
+ Dynamic: description-content-type
23
+ Dynamic: home-page
24
+ Dynamic: license
25
+ Dynamic: license-file
26
+ Dynamic: requires-dist
27
+ Dynamic: requires-python
28
+ Dynamic: summary
29
+
30
+ # PatchPyro
31
+
32
+ A fork of pyromod (renamed as patchpyro) providing conversation patches for Pyrogram-based clients.
33
+
34
+ ## Requirements
35
+ ```text
36
+ kurigram>=2.0.69
37
+ python>=3.9
38
+ ```
39
+
40
+ ## Installation
41
+ ```bash
42
+ pip install patchpyro
43
+ ```
44
+
45
+ ## Usage
46
+ Import `patchpyro` at least once in your script so you can use the modified Pyrogram in all files of the same process.
47
+
48
+ ### Example
49
+ ```python
50
+ # config.py
51
+ from patchpyro import listen # or import patchpyro.listen
52
+ from pyrogram import Client
53
+
54
+ listen.thank() # use this if your linter/IDE flags patchpyro as an unused import.
55
+
56
+ mybot = Client("mysession")
57
+ ```
58
+
59
+ ```python
60
+ # any other .py
61
+ from config import mybot
62
+ # no need to import patchpyro again; Pyrogram is already monkeypatched globally (in the same process)
63
+ ```
64
+
65
+ ## Available Methods
66
+ Just importing `patchpyro.listen` will automatically do the monkeypatch and you'll get these new methods:
67
+
68
+ ### `Chat.listen`, `User.listen`
69
+ - `await mybot.listen(chat_id, filters=None, timeout=30)`
70
+ - Awaits a new message in the specified chat and returns it.
71
+ - Raises `asyncio.TimeoutError` if timeout (optional parameter) occurs.
72
+ - You can pass Update Filters to the filters parameter just like you do for the update handlers.
73
+ - E.g. `filters=filters.photo & filters.bot`
74
+
75
+ ### `Chat.ask`, `User.ask`
76
+ - `await mybot.ask(text, chat_id, filters=None, timeout=30)`
77
+ - Same as `.listen()` above, but sends a message before awaiting.
78
+ - You can pass custom parameters to its internal `send_message()` call. Check the example below.
79
+
80
+ ### `Chat.asker`, `User.asker`
81
+ - `await mybot.asker(chat_id, filters=None, timeout=36)`
82
+ - Same as `.listen()` but `.asker()` returns `None` instead of raising `asyncio.TimeoutError`.
83
+ - Useful for graceful timeout handling, `.asker()` has a default timeout of 2 minutes (adjustable via `timeout` argument).
84
+
85
+ ## Examples
86
+
87
+ ### `asker()`
88
+ ```python
89
+ # ...
90
+ sendx = await client.send_message(chat_id, "`Send me your name:`")
91
+ answer = await client.asker(chat_id, filters=None, timeout=60)
92
+ if not answer: # `None` if timeout reached with no reply.
93
+ return await sendx.reply_text("How long should I wait? Bye!")
94
+ await answer.reply_text(f"{answer.text}, That's a cool name!")
95
+ # ...
96
+ ```
97
+
98
+ ### `ask()`
99
+ ```python
100
+ # ...
101
+ answer = await client.ask(chat_id, '*Send me your name:*', parse_mode=enums.ParseMode.MARKDOWN)
102
+ await client.send_message(chat_id, f'Your name is: {answer.text}')
103
+ # ...
104
+ ```
105
+
106
+ ## Copyright & License
107
+ This project includes code from:
108
+ - **[kurimod](https://github.com/ohmyarthur/kurimod)**: Monkeypatcher logic and modern async compatibility fixes.
109
+ - **Pyrogram**: Telegram MTProto API Client Library for Python. Copyright (C) 2017-2022 Dan <<https://github.com/delivrance>>
110
+ - **pyromod**: Original conversation patch logic.
111
+
112
+ Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser)
@@ -2,7 +2,7 @@ import re
2
2
 
3
3
  import setuptools
4
4
 
5
- with open("README.md", "r") as fp:
5
+ with open("README.md") as fp:
6
6
  long_description = fp.read()
7
7
 
8
8
  requirements = ["kurigram>=2.0.69"]
patchpyro-2.0.5/PKG-INFO DELETED
@@ -1,113 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: patchpyro
3
- Version: 2.0.5
4
- Summary: A modified pyromod by a.devh.in
5
- Home-page: https://github.com/adityaprasad502/patchpyro
6
- Author: Cezar H. & adityaprasad502
7
- Author-email: plutoniumx502@gmail.com
8
- License: LGPLv3+
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.9
13
- Description-Content-Type: text/markdown
14
- License-File: COPYING
15
- License-File: COPYING.lesser
16
- License-File: NOTICE
17
- Requires-Dist: kurigram>=2.0.69
18
- Dynamic: author
19
- Dynamic: author-email
20
- Dynamic: classifier
21
- Dynamic: description
22
- Dynamic: description-content-type
23
- Dynamic: home-page
24
- Dynamic: license
25
- Dynamic: license-file
26
- Dynamic: requires-dist
27
- Dynamic: requires-python
28
- Dynamic: summary
29
-
30
- # PatchPyro
31
- ### This is a fork of pyromod (renamed as patchpyro) for personal usecases.
32
-
33
- ### Remember that this fork contains only conversation patch.
34
-
35
- # Requirements:
36
- ~~~python
37
- kurigram>=2.0.69
38
- python>=3.9
39
- ~~~
40
-
41
- # Installation:
42
- ```
43
- python -m pip install patchpyro
44
- ```
45
-
46
- # Usage:
47
- Import `patchpyro` at least one time in your script, so you'll be able to use modified pyrogram in all files of the same proccess.
48
- Example:
49
-
50
- ```python
51
- # config.py
52
- from patchpyro import listen # or import patchpyro.listen
53
- from pyrogram import Client
54
-
55
- patchpyro.thank() # use this if ur linters/ide is removing patchpyro as unused import.
56
-
57
- mybot = Client("mysession")
58
- ```
59
-
60
- ```python
61
- # any other .py
62
- from config import mybot
63
- # no need to import patchpyro again pyrogram is already monkeypatched globally (at the same proccess)
64
- ```
65
-
66
- ## `patchpyro.listen`
67
- Just import it, it will automatically do the monkeypatch and you'll get these new methods:
68
-
69
- - Available bound methods::
70
- - `Chat.listen, User.listen`
71
-
72
- - `await mybot.listen(chat_id, filters=None, timeout=30)`
73
- - raises `asyncio.TimeoutError` if timeout (optional parameter)
74
- - Awaits for a new message in the specified chat and returns it.
75
- - You can pass Update Filters to the filters parameter just like you do for the update handlers.
76
- - E.g. `filters=filters.photo & filters.bot`
77
- - `Chat.ask, User.ask`
78
-
79
- - `await mybot.ask(text, chat_id, filters=None, timeout=30)`
80
- - Same of `.listen()` above, but sends a message before awaiting.
81
- - You can pass custom parameters to its send_message() call. Check the example below.
82
-
83
- - `Chat.asker, User.asker`
84
- - `await mybot.asker(chat_id, filters=None, timeout=36)`
85
- - same as `.listen()` but `.asker()` returns `None` instead of raising `asyncio.TimeoutError`.
86
- - Found useful in some cases for me, `.asker()` has a default timeout of 2 minutes.
87
- - You can adjust it by passing as a argument. Refer the example code given below.
88
-
89
- # Examples:
90
- ### For .asker():
91
- ```python
92
- ...
93
- sendx = await client.send_message(chat_id, "`Send me your name:`")
94
- answer = await client.asker(chat_id, filters=None, timeout=60)
95
- if not answer: # `None` if timeout if no reply received.
96
- return await sendx.reply_text("How long should I wait?, Eh! Bye!")
97
- await answer.reply_text(f"{answer.text}, That's a cool name!")
98
- ...
99
- ```
100
- ### For .ask():
101
- ```python
102
- ...
103
- answer = await client.ask(chat_id, '*Send me your name:*', parse_mode=enums.ParseMode.MARKDOWN)
104
- await client.send_message(chat_id, f'Your name is: {answer.text}')
105
- ...
106
- ```
107
-
108
-
109
- ### Copyright & License
110
- This project may include snippets of Pyrogram code
111
- - Pyrogram - Telegram MTProto API Client Library for Python. Copyright (C) 2017-2022 Dan <<https://github.com/delivrance>>
112
-
113
- Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser)
patchpyro-2.0.5/README.md DELETED
@@ -1,84 +0,0 @@
1
- # PatchPyro
2
- ### This is a fork of pyromod (renamed as patchpyro) for personal usecases.
3
-
4
- ### Remember that this fork contains only conversation patch.
5
-
6
- # Requirements:
7
- ~~~python
8
- kurigram>=2.0.69
9
- python>=3.9
10
- ~~~
11
-
12
- # Installation:
13
- ```
14
- python -m pip install patchpyro
15
- ```
16
-
17
- # Usage:
18
- Import `patchpyro` at least one time in your script, so you'll be able to use modified pyrogram in all files of the same proccess.
19
- Example:
20
-
21
- ```python
22
- # config.py
23
- from patchpyro import listen # or import patchpyro.listen
24
- from pyrogram import Client
25
-
26
- patchpyro.thank() # use this if ur linters/ide is removing patchpyro as unused import.
27
-
28
- mybot = Client("mysession")
29
- ```
30
-
31
- ```python
32
- # any other .py
33
- from config import mybot
34
- # no need to import patchpyro again pyrogram is already monkeypatched globally (at the same proccess)
35
- ```
36
-
37
- ## `patchpyro.listen`
38
- Just import it, it will automatically do the monkeypatch and you'll get these new methods:
39
-
40
- - Available bound methods::
41
- - `Chat.listen, User.listen`
42
-
43
- - `await mybot.listen(chat_id, filters=None, timeout=30)`
44
- - raises `asyncio.TimeoutError` if timeout (optional parameter)
45
- - Awaits for a new message in the specified chat and returns it.
46
- - You can pass Update Filters to the filters parameter just like you do for the update handlers.
47
- - E.g. `filters=filters.photo & filters.bot`
48
- - `Chat.ask, User.ask`
49
-
50
- - `await mybot.ask(text, chat_id, filters=None, timeout=30)`
51
- - Same of `.listen()` above, but sends a message before awaiting.
52
- - You can pass custom parameters to its send_message() call. Check the example below.
53
-
54
- - `Chat.asker, User.asker`
55
- - `await mybot.asker(chat_id, filters=None, timeout=36)`
56
- - same as `.listen()` but `.asker()` returns `None` instead of raising `asyncio.TimeoutError`.
57
- - Found useful in some cases for me, `.asker()` has a default timeout of 2 minutes.
58
- - You can adjust it by passing as a argument. Refer the example code given below.
59
-
60
- # Examples:
61
- ### For .asker():
62
- ```python
63
- ...
64
- sendx = await client.send_message(chat_id, "`Send me your name:`")
65
- answer = await client.asker(chat_id, filters=None, timeout=60)
66
- if not answer: # `None` if timeout if no reply received.
67
- return await sendx.reply_text("How long should I wait?, Eh! Bye!")
68
- await answer.reply_text(f"{answer.text}, That's a cool name!")
69
- ...
70
- ```
71
- ### For .ask():
72
- ```python
73
- ...
74
- answer = await client.ask(chat_id, '*Send me your name:*', parse_mode=enums.ParseMode.MARKDOWN)
75
- await client.send_message(chat_id, f'Your name is: {answer.text}')
76
- ...
77
- ```
78
-
79
-
80
- ### Copyright & License
81
- This project may include snippets of Pyrogram code
82
- - Pyrogram - Telegram MTProto API Client Library for Python. Copyright (C) 2017-2022 Dan <<https://github.com/delivrance>>
83
-
84
- Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser)
@@ -1,28 +0,0 @@
1
- """
2
- patchpyro - A monkeypatcher add-on for Pyrogram
3
- Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>
4
-
5
- This file is part of patchpyro and was forked from usernein/pyromod.
6
-
7
- patchpyro 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
- patchpyro 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 patchpyro. If not, see <https://www.gnu.org/licenses/>.
19
- """
20
-
21
-
22
- __version__ = "2.0.5"
23
- # change in setup.py aswell
24
-
25
- def thank():
26
- "A dummy function to prevent patchpyro from being removed by formatters and linters. It does nothing, just prints a message to the console to indicate that patchpyro is not an unused import."
27
- # print("Thank you for using patchpyro! If you see this message. author: a.devh.in, version: " + __version__)
28
- print("PatchPyro " + __version__ + " is imported! Thank you for using it! Author: a.devh.in")
@@ -1,151 +0,0 @@
1
- """
2
- patchpyro - A monkeypatcher add-on for Pyrogram
3
- Copyright (C) 2026 Aditya Prasad S <https://github.com/adityaprasad502>
4
-
5
- This file is part of patchpyro and was forked from usernein/pyromod.
6
-
7
- patchpyro 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
- patchpyro 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 patchpyro. If not, see <https://www.gnu.org/licenses/>.
19
- """
20
-
21
- import asyncio
22
- import functools
23
- from contextlib import suppress
24
-
25
- import pyrogram
26
-
27
- from ..utils import patch, patchable
28
-
29
- loop = asyncio.get_event_loop()
30
-
31
-
32
- class ListenerCanceled(Exception):
33
- pass
34
-
35
-
36
- pyrogram.errors.ListenerCanceled = ListenerCanceled
37
-
38
-
39
- @patch(pyrogram.client.Client)
40
- class Client:
41
- @patchable
42
- def __init__(self, *args, **kwargs):
43
- self.listening = {}
44
- self.using_mod = True
45
-
46
- self.old__init__(*args, **kwargs)
47
-
48
- @patchable
49
- async def listen(self, chat_id, filters=None, timeout=None):
50
- if type(chat_id) != int:
51
- chat = await self.get_chat(chat_id)
52
- chat_id = chat.id
53
-
54
- future = loop.create_future()
55
- future.add_done_callback(functools.partial(self.clear_listener, chat_id))
56
- self.listening.update({chat_id: {"future": future, "filters": filters}})
57
- return await asyncio.wait_for(future, timeout)
58
-
59
- @patchable
60
- async def ask(self, chat_id, text, filters=None, timeout=None, *args, **kwargs):
61
- request = await self.send_message(chat_id, text, *args, **kwargs)
62
- response = await self.listen(chat_id, filters, timeout)
63
- response.request = request
64
- return response
65
-
66
- @patchable
67
- async def asker(self, chat_id, filters=None, timeout=119):
68
- try:
69
- response = await self.listen(chat_id, filters, timeout)
70
- except asyncio.TimeoutError:
71
- response = None
72
- return response
73
-
74
- @patchable
75
- def clear_listener(self, chat_id, future):
76
- with suppress(KeyError):
77
- if (
78
- chat_id in self.listening
79
- and future == self.listening[chat_id]["future"]
80
- ):
81
- self.listening.pop(chat_id, None)
82
-
83
- @patchable
84
- def cancel_listener(self, chat_id):
85
- listener = self.listening.get(chat_id)
86
- if not listener or listener["future"].done():
87
- return
88
-
89
- listener["future"].set_exception(ListenerCanceled())
90
- self.clear_listener(chat_id, listener["future"])
91
-
92
-
93
- @patch(pyrogram.handlers.message_handler.MessageHandler)
94
- class MessageHandler:
95
- @patchable
96
- def __init__(self, callback: callable, filters=None):
97
- self.user_callback = callback
98
- self.old__init__(self.resolve_listener, filters)
99
-
100
- @patchable
101
- async def resolve_listener(self, client, message, *args):
102
- listener = client.listening.get(message.chat.id)
103
- if listener and not listener["future"].done():
104
- listener["future"].set_result(message)
105
- else:
106
- if listener and listener["future"].done():
107
- client.clear_listener(message.chat.id, listener["future"])
108
- await self.user_callback(client, message, *args)
109
-
110
- @patchable
111
- async def check(self, client, update):
112
- listener = client.listening.get(update.chat.id)
113
-
114
- if listener and not listener["future"].done():
115
- return (
116
- await listener["filters"](client, update)
117
- if callable(listener["filters"])
118
- else True
119
- )
120
-
121
- return await self.filters(client, update) if callable(self.filters) else True
122
-
123
-
124
- @patch(pyrogram.types.user_and_chats.chat.Chat)
125
- class Chat(pyrogram.types.Chat):
126
- @patchable
127
- def listen(self, *args, **kwargs):
128
- return self._client.listen(self.id, *args, **kwargs)
129
-
130
- @patchable
131
- def ask(self, *args, **kwargs):
132
- return self._client.ask(self.id, *args, **kwargs)
133
-
134
- @patchable
135
- def cancel_listener(self):
136
- return self._client.cancel_listener(self.id)
137
-
138
-
139
- @patch(pyrogram.types.user_and_chats.user.User)
140
- class User(pyrogram.types.User):
141
- @patchable
142
- def listen(self, *args, **kwargs):
143
- return self._client.listen(self.id, *args, **kwargs)
144
-
145
- @patchable
146
- def ask(self, *args, **kwargs):
147
- return self._client.ask(self.id, *args, **kwargs)
148
-
149
- @patchable
150
- def cancel_listener(self):
151
- return self._client.cancel_listener(self.id)
@@ -1,113 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: patchpyro
3
- Version: 2.0.5
4
- Summary: A modified pyromod by a.devh.in
5
- Home-page: https://github.com/adityaprasad502/patchpyro
6
- Author: Cezar H. & adityaprasad502
7
- Author-email: plutoniumx502@gmail.com
8
- License: LGPLv3+
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.9
13
- Description-Content-Type: text/markdown
14
- License-File: COPYING
15
- License-File: COPYING.lesser
16
- License-File: NOTICE
17
- Requires-Dist: kurigram>=2.0.69
18
- Dynamic: author
19
- Dynamic: author-email
20
- Dynamic: classifier
21
- Dynamic: description
22
- Dynamic: description-content-type
23
- Dynamic: home-page
24
- Dynamic: license
25
- Dynamic: license-file
26
- Dynamic: requires-dist
27
- Dynamic: requires-python
28
- Dynamic: summary
29
-
30
- # PatchPyro
31
- ### This is a fork of pyromod (renamed as patchpyro) for personal usecases.
32
-
33
- ### Remember that this fork contains only conversation patch.
34
-
35
- # Requirements:
36
- ~~~python
37
- kurigram>=2.0.69
38
- python>=3.9
39
- ~~~
40
-
41
- # Installation:
42
- ```
43
- python -m pip install patchpyro
44
- ```
45
-
46
- # Usage:
47
- Import `patchpyro` at least one time in your script, so you'll be able to use modified pyrogram in all files of the same proccess.
48
- Example:
49
-
50
- ```python
51
- # config.py
52
- from patchpyro import listen # or import patchpyro.listen
53
- from pyrogram import Client
54
-
55
- patchpyro.thank() # use this if ur linters/ide is removing patchpyro as unused import.
56
-
57
- mybot = Client("mysession")
58
- ```
59
-
60
- ```python
61
- # any other .py
62
- from config import mybot
63
- # no need to import patchpyro again pyrogram is already monkeypatched globally (at the same proccess)
64
- ```
65
-
66
- ## `patchpyro.listen`
67
- Just import it, it will automatically do the monkeypatch and you'll get these new methods:
68
-
69
- - Available bound methods::
70
- - `Chat.listen, User.listen`
71
-
72
- - `await mybot.listen(chat_id, filters=None, timeout=30)`
73
- - raises `asyncio.TimeoutError` if timeout (optional parameter)
74
- - Awaits for a new message in the specified chat and returns it.
75
- - You can pass Update Filters to the filters parameter just like you do for the update handlers.
76
- - E.g. `filters=filters.photo & filters.bot`
77
- - `Chat.ask, User.ask`
78
-
79
- - `await mybot.ask(text, chat_id, filters=None, timeout=30)`
80
- - Same of `.listen()` above, but sends a message before awaiting.
81
- - You can pass custom parameters to its send_message() call. Check the example below.
82
-
83
- - `Chat.asker, User.asker`
84
- - `await mybot.asker(chat_id, filters=None, timeout=36)`
85
- - same as `.listen()` but `.asker()` returns `None` instead of raising `asyncio.TimeoutError`.
86
- - Found useful in some cases for me, `.asker()` has a default timeout of 2 minutes.
87
- - You can adjust it by passing as a argument. Refer the example code given below.
88
-
89
- # Examples:
90
- ### For .asker():
91
- ```python
92
- ...
93
- sendx = await client.send_message(chat_id, "`Send me your name:`")
94
- answer = await client.asker(chat_id, filters=None, timeout=60)
95
- if not answer: # `None` if timeout if no reply received.
96
- return await sendx.reply_text("How long should I wait?, Eh! Bye!")
97
- await answer.reply_text(f"{answer.text}, That's a cool name!")
98
- ...
99
- ```
100
- ### For .ask():
101
- ```python
102
- ...
103
- answer = await client.ask(chat_id, '*Send me your name:*', parse_mode=enums.ParseMode.MARKDOWN)
104
- await client.send_message(chat_id, f'Your name is: {answer.text}')
105
- ...
106
- ```
107
-
108
-
109
- ### Copyright & License
110
- This project may include snippets of Pyrogram code
111
- - Pyrogram - Telegram MTProto API Client Library for Python. Copyright (C) 2017-2022 Dan <<https://github.com/delivrance>>
112
-
113
- Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser)
File without changes
File without changes
File without changes
File without changes