Pytdbot 0.9.3__tar.gz → 0.9.5.dev0__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.
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/PKG-INFO +2 -2
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/PKG-INFO +2 -2
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/SOURCES.txt +3 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/README.md +1 -1
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/__init__.py +1 -1
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/client.py +168 -26
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/handlers/decorators.py +45 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/handlers/handler.py +3 -1
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/handlers/td_updates.py +76 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/methods/td_functions.py +378 -45
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/__init__.py +23 -1
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/types.py +583 -42
- pytdbot-0.9.5.dev0/pytdbot/types/tdserver/__init__.py +3 -0
- pytdbot-0.9.5.dev0/pytdbot/types/tdserver/schedule.py +96 -0
- pytdbot-0.9.5.dev0/pytdbot/types/tdserver/stats.py +65 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/__init__.py +4 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/text_format.py +44 -12
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/LICENSE +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/MANIFEST.in +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/dependency_links.txt +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/requires.txt +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/top_level.txt +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/client_manager.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/exception/__init__.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/filters.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/handlers/__init__.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/methods/__init__.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/methods/methods.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/tdjson/__init__.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/tdjson/tdjson.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/plugins/__init__.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/__init__.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/__init__.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/callback_query.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/chatActions.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/file.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/message.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/asyncio_utils.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/escape.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/json_utils.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/obj_encoder.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/strings.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/webapps.py +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/requirements.txt +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/setup.cfg +0 -0
- {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Pytdbot
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5.dev0
|
|
4
4
|
Summary: Easy-to-use asynchronous TDLib wrapper for Python.
|
|
5
5
|
Home-page: https://github.com/pytdbot/client
|
|
6
6
|
Author: AYMEN Mohammed
|
|
@@ -30,7 +30,7 @@ Dynamic: requires-dist
|
|
|
30
30
|
Dynamic: requires-python
|
|
31
31
|
Dynamic: summary
|
|
32
32
|
|
|
33
|
-
# Pytdbot [](https://pypi.org/project/Pytdbot) [](https://pypi.org/project/Pytdbot) [](https://github.com/tdlib/td) [](https://pepy.tech/project/pytdbot) [](https://t.me/pytdbotchat)
|
|
34
34
|
|
|
35
35
|
Pytdbot (Python TDLib) is an asynchronous [**TDLib**](https://github.com/tdlib/td) wrapper for **Telegram** users/bots written in **Python**.
|
|
36
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Pytdbot
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5.dev0
|
|
4
4
|
Summary: Easy-to-use asynchronous TDLib wrapper for Python.
|
|
5
5
|
Home-page: https://github.com/pytdbot/client
|
|
6
6
|
Author: AYMEN Mohammed
|
|
@@ -30,7 +30,7 @@ Dynamic: requires-dist
|
|
|
30
30
|
Dynamic: requires-python
|
|
31
31
|
Dynamic: summary
|
|
32
32
|
|
|
33
|
-
# Pytdbot [](https://pypi.org/project/Pytdbot) [](https://pypi.org/project/Pytdbot) [](https://github.com/tdlib/td) [](https://pepy.tech/project/pytdbot) [](https://t.me/pytdbotchat)
|
|
34
34
|
|
|
35
35
|
Pytdbot (Python TDLib) is an asynchronous [**TDLib**](https://github.com/tdlib/td) wrapper for **Telegram** users/bots written in **Python**.
|
|
36
36
|
|
|
@@ -31,6 +31,9 @@ pytdbot/types/td_types/bound_methods/callback_query.py
|
|
|
31
31
|
pytdbot/types/td_types/bound_methods/chatActions.py
|
|
32
32
|
pytdbot/types/td_types/bound_methods/file.py
|
|
33
33
|
pytdbot/types/td_types/bound_methods/message.py
|
|
34
|
+
pytdbot/types/tdserver/__init__.py
|
|
35
|
+
pytdbot/types/tdserver/schedule.py
|
|
36
|
+
pytdbot/types/tdserver/stats.py
|
|
34
37
|
pytdbot/utils/__init__.py
|
|
35
38
|
pytdbot/utils/asyncio_utils.py
|
|
36
39
|
pytdbot/utils/escape.py
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Pytdbot [](https://pypi.org/project/Pytdbot) [](https://pypi.org/project/Pytdbot) [](https://github.com/tdlib/td) [](https://pepy.tech/project/pytdbot) [](https://t.me/pytdbotchat)
|
|
2
2
|
|
|
3
3
|
Pytdbot (Python TDLib) is an asynchronous [**TDLib**](https://github.com/tdlib/td) wrapper for **Telegram** users/bots written in **Python**.
|
|
4
4
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import signal
|
|
3
|
-
from importlib import import_module
|
|
3
|
+
from importlib import import_module, reload as reload_module
|
|
4
4
|
from json import dumps
|
|
5
5
|
from logging import DEBUG, getLogger
|
|
6
6
|
from os.path import join as join_path
|
|
@@ -102,6 +102,9 @@ class Client(Decorators, Methods):
|
|
|
102
102
|
|
|
103
103
|
td_log (:class:`~pytdbot.types.LogStream`, *optional*):
|
|
104
104
|
Log stream. Default is ``None`` (Log to ``stdout``)
|
|
105
|
+
|
|
106
|
+
user_bot (``bool``, *optional*):
|
|
107
|
+
Pass ``True`` if this is a user-bot. Default is ``False``
|
|
105
108
|
"""
|
|
106
109
|
|
|
107
110
|
def __init__(
|
|
@@ -172,14 +175,18 @@ class Client(Decorators, Methods):
|
|
|
172
175
|
self.is_running = None
|
|
173
176
|
self.me: types.User = None
|
|
174
177
|
self.is_authenticated = False
|
|
178
|
+
self.is_reloading_plugins = False
|
|
175
179
|
self.is_rabbitmq = True if rabbitmq_url else False
|
|
176
180
|
self.options = {}
|
|
181
|
+
self.allow_outgoing_message_types: tuple = (types.MessagePaymentRefunded,)
|
|
177
182
|
|
|
178
183
|
self._check_init_args()
|
|
179
184
|
|
|
180
185
|
self._handlers = {"initializer": [], "finalizer": []}
|
|
186
|
+
self._current_handlers = {}
|
|
181
187
|
self._results: Dict[str, asyncio.Future] = {}
|
|
182
188
|
self._workers_tasks = None
|
|
189
|
+
self.__rabbitmq_iterator_task = None
|
|
183
190
|
self.__authorization_state = None
|
|
184
191
|
self.__cache = {"is_coro_filter": {}}
|
|
185
192
|
self.__local_handlers = {
|
|
@@ -223,17 +230,72 @@ class Client(Decorators, Methods):
|
|
|
223
230
|
|
|
224
231
|
return self.__authorization_state
|
|
225
232
|
|
|
233
|
+
async def getServerStats(
|
|
234
|
+
self,
|
|
235
|
+
) -> Union["pytdbot.types.ServerStats", "pytdbot.types.Error"]:
|
|
236
|
+
"""Returns TDLib Server stats"""
|
|
237
|
+
|
|
238
|
+
self._check_rabbitmq()
|
|
239
|
+
|
|
240
|
+
return await self.invoke({"@type": "getServerStats"})
|
|
241
|
+
|
|
242
|
+
async def scheduleEvent(
|
|
243
|
+
self, data: dict, send_at: int
|
|
244
|
+
) -> Union["pytdbot.types.ScheduledEvent", "pytdbot.types.Error"]:
|
|
245
|
+
"""Schedule an event
|
|
246
|
+
|
|
247
|
+
Parameters:
|
|
248
|
+
data (:class:`dict`):
|
|
249
|
+
The event data to be scheduled
|
|
250
|
+
|
|
251
|
+
send_at (:class:`int`):
|
|
252
|
+
Unix timestamp when the event should be sent
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
self._check_rabbitmq()
|
|
256
|
+
|
|
257
|
+
if not isinstance(data, dict):
|
|
258
|
+
raise ValueError("data must be dict")
|
|
259
|
+
if not isinstance(send_at, (int, float)):
|
|
260
|
+
raise ValueError("send_at must be int")
|
|
261
|
+
|
|
262
|
+
return await self.invoke(
|
|
263
|
+
{"@type": "scheduleEvent", "data": data, "send_at": send_at}
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
async def cancelScheduledEvent(
|
|
267
|
+
self, event_id: str
|
|
268
|
+
) -> Union["pytdbot.types.Ok", "pytdbot.types.Error"]:
|
|
269
|
+
"""Cancel a scheduled event
|
|
270
|
+
|
|
271
|
+
Parameters:
|
|
272
|
+
event_id (:class:`str`):
|
|
273
|
+
Event ID to cancel
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
self._check_rabbitmq()
|
|
277
|
+
|
|
278
|
+
if not isinstance(event_id, str):
|
|
279
|
+
raise ValueError("event_id must be str")
|
|
280
|
+
|
|
281
|
+
return await self.invoke(
|
|
282
|
+
{"@type": "cancelScheduledEvent", "event_id": event_id}
|
|
283
|
+
)
|
|
284
|
+
|
|
226
285
|
async def start(self) -> None:
|
|
227
286
|
r"""Start pytdbot client"""
|
|
228
287
|
|
|
229
288
|
if not self.is_running:
|
|
230
289
|
self.logger.info("Starting pytdbot client...")
|
|
231
290
|
|
|
232
|
-
if
|
|
291
|
+
if self.is_rabbitmq:
|
|
292
|
+
await self.__start_rabbitmq()
|
|
293
|
+
elif not self.client_manager:
|
|
233
294
|
self.client_manager = ClientManager(
|
|
234
295
|
self, self.lib_path, self.td_verbosity, loop=self.loop
|
|
235
296
|
)
|
|
236
297
|
await self.client_manager.start()
|
|
298
|
+
self.is_running = True
|
|
237
299
|
|
|
238
300
|
if isinstance(self.td_log, LogStream) and not self.is_rabbitmq:
|
|
239
301
|
await self.__send(
|
|
@@ -242,21 +304,22 @@ class Client(Decorators, Methods):
|
|
|
242
304
|
|
|
243
305
|
if isinstance(self.workers, int):
|
|
244
306
|
self._workers_tasks = [
|
|
245
|
-
self.loop.create_task(
|
|
307
|
+
self.loop.create_task(
|
|
308
|
+
self._queue_update_worker()
|
|
309
|
+
if not self.is_rabbitmq
|
|
310
|
+
else self.__rabbitmq_worker()
|
|
311
|
+
)
|
|
246
312
|
for _ in range(self.workers)
|
|
247
313
|
]
|
|
248
314
|
self.__is_queue_worker = True
|
|
249
315
|
|
|
250
316
|
self.logger.info(f"Started with {self.workers} workers")
|
|
317
|
+
elif self.is_rabbitmq:
|
|
318
|
+
raise ValueError("workers must be an int when using TDLib Server")
|
|
251
319
|
else:
|
|
252
320
|
self.__is_queue_worker = False
|
|
253
321
|
self.logger.info("Started with unlimited updates processes")
|
|
254
322
|
|
|
255
|
-
if self.is_rabbitmq:
|
|
256
|
-
await self.__start_rabbitmq()
|
|
257
|
-
else: # client_manager
|
|
258
|
-
self.is_running = True
|
|
259
|
-
|
|
260
323
|
self.loop.create_task(
|
|
261
324
|
self.getOption("version")
|
|
262
325
|
) # Ping TDLib to start processing updates
|
|
@@ -268,6 +331,7 @@ class Client(Decorators, Methods):
|
|
|
268
331
|
filters: pytdbot.filters.Filter = None,
|
|
269
332
|
position: int = None,
|
|
270
333
|
inner_object: bool = False,
|
|
334
|
+
is_from_plugin: bool = False,
|
|
271
335
|
) -> None:
|
|
272
336
|
r"""Add an update handler
|
|
273
337
|
|
|
@@ -287,6 +351,9 @@ class Client(Decorators, Methods):
|
|
|
287
351
|
inner_object (``bool``, *optional*):
|
|
288
352
|
Wether to pass an inner object of update or not; for example ``UpdateNewMessage.message``. Default is ``False``
|
|
289
353
|
|
|
354
|
+
is_from_plugin (``bool``, *optional*):
|
|
355
|
+
Wether this handler is from a loaded plugin (this can help reloading plugin during runtime; for development only). Default is ``False``
|
|
356
|
+
|
|
290
357
|
Raises:
|
|
291
358
|
TypeError
|
|
292
359
|
"""
|
|
@@ -298,14 +365,38 @@ class Client(Decorators, Methods):
|
|
|
298
365
|
elif filters is not None and not isinstance(filters, Filter):
|
|
299
366
|
raise TypeError("filters must be instance of pytdbot.filters.Filter")
|
|
300
367
|
else:
|
|
301
|
-
|
|
368
|
+
handler = Handler(
|
|
369
|
+
func, update_type, filters, position, inner_object, is_from_plugin
|
|
370
|
+
)
|
|
371
|
+
|
|
302
372
|
if update_type not in self._handlers:
|
|
303
373
|
self._handlers[update_type] = []
|
|
374
|
+
|
|
304
375
|
if isinstance(position, int):
|
|
305
|
-
self._handlers[update_type].insert(position,
|
|
376
|
+
self._handlers[update_type].insert(position, handler)
|
|
306
377
|
else:
|
|
307
|
-
self._handlers[update_type].append(
|
|
308
|
-
|
|
378
|
+
self._handlers[update_type].append(handler)
|
|
379
|
+
|
|
380
|
+
self._update_handlers()
|
|
381
|
+
|
|
382
|
+
def reload_plugins(self):
|
|
383
|
+
"""Reload all plugins, non-plugin handlers are not ``reloaded``
|
|
384
|
+
.. note::
|
|
385
|
+
This is for ``development purposes only`` and should not be used
|
|
386
|
+
in production environments
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
if self.is_reloading_plugins:
|
|
390
|
+
return
|
|
391
|
+
|
|
392
|
+
self.is_reloading_plugins = True
|
|
393
|
+
for handlers in self._handlers.values():
|
|
394
|
+
for handler in handlers.copy():
|
|
395
|
+
if handler.is_from_plugin:
|
|
396
|
+
self.remove_handler(handler.func)
|
|
397
|
+
|
|
398
|
+
self._load_plugins(reload_plugins=True)
|
|
399
|
+
self.is_reloading_plugins = False
|
|
309
400
|
|
|
310
401
|
def remove_handler(self, func: Callable) -> bool:
|
|
311
402
|
r"""Remove an update handler
|
|
@@ -323,13 +414,17 @@ class Client(Decorators, Methods):
|
|
|
323
414
|
|
|
324
415
|
if not isinstance(func, Callable):
|
|
325
416
|
raise TypeError("func must be callable")
|
|
326
|
-
|
|
417
|
+
|
|
418
|
+
removed = False
|
|
419
|
+
for handlers in self._handlers.values():
|
|
327
420
|
for handler in handlers.copy():
|
|
328
421
|
if handler.func == func:
|
|
329
422
|
handlers.remove(handler)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
423
|
+
removed = True
|
|
424
|
+
|
|
425
|
+
if removed:
|
|
426
|
+
self._update_handlers()
|
|
427
|
+
return removed
|
|
333
428
|
|
|
334
429
|
async def invoke(
|
|
335
430
|
self,
|
|
@@ -500,7 +595,7 @@ class Client(Decorators, Methods):
|
|
|
500
595
|
|
|
501
596
|
self.__stop_client()
|
|
502
597
|
|
|
503
|
-
if not self.client_manager.start_clients_on_add:
|
|
598
|
+
if self.client_manager and not self.client_manager.start_clients_on_add:
|
|
504
599
|
await self.client_manager.close()
|
|
505
600
|
|
|
506
601
|
self.logger.info("Instance closed")
|
|
@@ -532,6 +627,9 @@ class Client(Decorators, Methods):
|
|
|
532
627
|
else:
|
|
533
628
|
self.client_manager.send(self.client_id, request)
|
|
534
629
|
|
|
630
|
+
def _check_rabbitmq(self):
|
|
631
|
+
assert self.is_rabbitmq, "This method is only available for TDLib Server"
|
|
632
|
+
|
|
535
633
|
def _check_init_args(self):
|
|
536
634
|
if self.user_bot:
|
|
537
635
|
return
|
|
@@ -554,7 +652,13 @@ class Client(Decorators, Methods):
|
|
|
554
652
|
if isinstance(self.workers, int) and self.workers < 1:
|
|
555
653
|
raise ValueError("workers must be greater than 0")
|
|
556
654
|
|
|
557
|
-
def
|
|
655
|
+
def _update_handlers(self):
|
|
656
|
+
self._current_handlers = {
|
|
657
|
+
k: tuple(sorted(v, key=lambda x: (x.position is None, x.position)))
|
|
658
|
+
for k, v in self._handlers.items()
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
def _load_plugins(self, reload_plugins: bool = False):
|
|
558
662
|
count = 0
|
|
559
663
|
handlers = 0
|
|
560
664
|
plugin_paths = sorted(Path(self.plugins.folder).rglob("*.py"))
|
|
@@ -578,6 +682,8 @@ class Client(Decorators, Methods):
|
|
|
578
682
|
|
|
579
683
|
try:
|
|
580
684
|
module = import_module(module_path)
|
|
685
|
+
if reload_plugins:
|
|
686
|
+
reload_module(module)
|
|
581
687
|
except Exception:
|
|
582
688
|
self.logger.exception(f"Failed to import plugin {module_path}")
|
|
583
689
|
continue
|
|
@@ -600,6 +706,7 @@ class Client(Decorators, Methods):
|
|
|
600
706
|
handler.filter,
|
|
601
707
|
handler.position,
|
|
602
708
|
handler.inner_object,
|
|
709
|
+
True,
|
|
603
710
|
)
|
|
604
711
|
handlers += 1
|
|
605
712
|
plugin_handlers_count += 1
|
|
@@ -652,7 +759,7 @@ class Client(Decorators, Methods):
|
|
|
652
759
|
if update_handler:
|
|
653
760
|
self.loop.create_task(update_handler(update))
|
|
654
761
|
|
|
655
|
-
if self.__is_queue_worker:
|
|
762
|
+
if not self.is_rabbitmq and self.__is_queue_worker:
|
|
656
763
|
self.queue.put_nowait(update)
|
|
657
764
|
else:
|
|
658
765
|
await self._handle_update(update)
|
|
@@ -665,7 +772,7 @@ class Client(Decorators, Methods):
|
|
|
665
772
|
async def __run_initializers(self, update):
|
|
666
773
|
inner_object = self.get_inner_object(update)
|
|
667
774
|
|
|
668
|
-
for initializer in self.
|
|
775
|
+
for initializer in self._current_handlers["initializer"]:
|
|
669
776
|
try:
|
|
670
777
|
handler_value = inner_object if initializer.inner_object else update
|
|
671
778
|
|
|
@@ -687,7 +794,7 @@ class Client(Decorators, Methods):
|
|
|
687
794
|
async def __run_handlers(self, update):
|
|
688
795
|
inner_object = self.get_inner_object(update)
|
|
689
796
|
|
|
690
|
-
for handler in self.
|
|
797
|
+
for handler in self._current_handlers[update.getType()]:
|
|
691
798
|
try:
|
|
692
799
|
handler_value = inner_object if handler.inner_object else update
|
|
693
800
|
|
|
@@ -708,7 +815,7 @@ class Client(Decorators, Methods):
|
|
|
708
815
|
async def __run_finalizers(self, update):
|
|
709
816
|
inner_object = self.get_inner_object(update)
|
|
710
817
|
|
|
711
|
-
for finalizer in self.
|
|
818
|
+
for finalizer in self._current_handlers["finalizer"]:
|
|
712
819
|
try:
|
|
713
820
|
handler_value = inner_object if finalizer.inner_object else update
|
|
714
821
|
|
|
@@ -728,10 +835,13 @@ class Client(Decorators, Methods):
|
|
|
728
835
|
self.logger.exception(f"Finalizer {finalizer} failed")
|
|
729
836
|
|
|
730
837
|
async def _handle_update(self, update):
|
|
731
|
-
if update.getType() in self.
|
|
838
|
+
if update.getType() in self._current_handlers:
|
|
732
839
|
if (
|
|
733
840
|
not self.user_bot
|
|
734
841
|
and isinstance(update, types.UpdateNewMessage)
|
|
842
|
+
and not isinstance(
|
|
843
|
+
update.message.content, self.allow_outgoing_message_types
|
|
844
|
+
)
|
|
735
845
|
and update.message.is_outgoing
|
|
736
846
|
):
|
|
737
847
|
return
|
|
@@ -837,7 +947,9 @@ class Client(Decorators, Methods):
|
|
|
837
947
|
f"{str(self.me.id) if not self.me.usernames else '@' + self.me.usernames.editable_username}"
|
|
838
948
|
)
|
|
839
949
|
|
|
840
|
-
if
|
|
950
|
+
if self.authorization_state == "authorizationStateClosing":
|
|
951
|
+
self.__is_closing = True
|
|
952
|
+
elif (
|
|
841
953
|
self.authorization_state == "authorizationStateClosed"
|
|
842
954
|
and self.__is_closing is False
|
|
843
955
|
):
|
|
@@ -908,6 +1020,8 @@ class Client(Decorators, Methods):
|
|
|
908
1020
|
)
|
|
909
1021
|
self.__rchannel = await self.__rconnection.channel()
|
|
910
1022
|
|
|
1023
|
+
self.logger.info("Connected to TDLib server via RabbitMQ")
|
|
1024
|
+
|
|
911
1025
|
updates_queue = await self.__get_updates_queue()
|
|
912
1026
|
|
|
913
1027
|
notify_queue = await self.__rchannel.declare_queue(
|
|
@@ -921,7 +1035,7 @@ class Client(Decorators, Methods):
|
|
|
921
1035
|
|
|
922
1036
|
self.__rqueues = {
|
|
923
1037
|
"updates": updates_queue,
|
|
924
|
-
"requests": await self.__rchannel.get_queue(self.my_id
|
|
1038
|
+
"requests": await self.__rchannel.get_queue(f"{self.my_id}_requests"),
|
|
925
1039
|
"notify": notify_queue,
|
|
926
1040
|
"responses": responses_queue,
|
|
927
1041
|
}
|
|
@@ -939,10 +1053,35 @@ class Client(Decorators, Methods):
|
|
|
939
1053
|
await self.process_update(obj_to_dict(update))
|
|
940
1054
|
|
|
941
1055
|
if not self.no_updates:
|
|
942
|
-
|
|
1056
|
+
self.__rabbitmq_iterator_task = self.loop.create_task(
|
|
1057
|
+
self.__rabbitmq_iterator()
|
|
1058
|
+
)
|
|
943
1059
|
|
|
944
1060
|
await self.__rqueues["notify"].consume(self.__on_update, no_ack=True)
|
|
945
1061
|
|
|
1062
|
+
async def __rabbitmq_iterator(self):
|
|
1063
|
+
async with self.__rqueues["updates"].iterator() as iterator:
|
|
1064
|
+
async for message in iterator:
|
|
1065
|
+
await self.queue.put(message)
|
|
1066
|
+
|
|
1067
|
+
async def __rabbitmq_worker(self):
|
|
1068
|
+
while self.is_running:
|
|
1069
|
+
message: aio_pika.IncomingMessage = await self.queue.get()
|
|
1070
|
+
|
|
1071
|
+
try:
|
|
1072
|
+
update = json_loads(message.body)
|
|
1073
|
+
if self.__is_closing and not isinstance(
|
|
1074
|
+
update, types.UpdateAuthorizationState
|
|
1075
|
+
):
|
|
1076
|
+
await message.nack(requeue=True)
|
|
1077
|
+
continue
|
|
1078
|
+
|
|
1079
|
+
await self.process_update(update)
|
|
1080
|
+
except Exception:
|
|
1081
|
+
self.logger.exception("Error processing message")
|
|
1082
|
+
|
|
1083
|
+
await message.ack() # ack after processing
|
|
1084
|
+
|
|
946
1085
|
async def __handle_rabbitmq_message(self, message: aio_pika.IncomingMessage):
|
|
947
1086
|
await self.process_update(json_loads(message.body))
|
|
948
1087
|
|
|
@@ -979,6 +1118,9 @@ class Client(Decorators, Methods):
|
|
|
979
1118
|
self.is_authenticated = False
|
|
980
1119
|
self.is_running = False
|
|
981
1120
|
|
|
1121
|
+
if self.__rabbitmq_iterator_task:
|
|
1122
|
+
self.__rabbitmq_iterator_task.cancel()
|
|
1123
|
+
|
|
982
1124
|
if self.__is_queue_worker:
|
|
983
1125
|
for worker_task in self._workers_tasks:
|
|
984
1126
|
worker_task.cancel()
|
|
@@ -134,3 +134,48 @@ class Decorators(Updates):
|
|
|
134
134
|
return func
|
|
135
135
|
|
|
136
136
|
return decorator
|
|
137
|
+
|
|
138
|
+
def on_updateScheduledEvent(
|
|
139
|
+
self: "pytdbot.Client" = None,
|
|
140
|
+
filters: "pytdbot.filters.Filter" = None,
|
|
141
|
+
position: int = None,
|
|
142
|
+
inner_object: bool = False,
|
|
143
|
+
) -> None:
|
|
144
|
+
r"""A scheduled event has been triggered
|
|
145
|
+
|
|
146
|
+
Parameters:
|
|
147
|
+
filters (:class:`~pytdbot.filters.Filter`, *optional*):
|
|
148
|
+
An update filter
|
|
149
|
+
|
|
150
|
+
position (``int``, *optional*):
|
|
151
|
+
The function position in handlers list. Default is ``None`` (append)
|
|
152
|
+
|
|
153
|
+
inner_object (``bool``, *optional*):
|
|
154
|
+
Wether to pass an inner object of update or not; for example ``UpdateNewMessage.message``. Default is ``False``
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
:py:class:`TypeError`
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def decorator(func: Callable) -> Callable:
|
|
161
|
+
if hasattr(func, "_handler"):
|
|
162
|
+
return func
|
|
163
|
+
elif isinstance(self, pytdbot.Client):
|
|
164
|
+
if iscoroutinefunction(func):
|
|
165
|
+
self.add_handler(
|
|
166
|
+
"updateScheduledEvent", func, filters, position, inner_object
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
raise TypeError("Handler must be async")
|
|
170
|
+
elif isinstance(self, pytdbot.filters.Filter):
|
|
171
|
+
func._handler = Handler(
|
|
172
|
+
func, "updateScheduledEvent", self, position, inner_object
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
func._handler = Handler(
|
|
176
|
+
func, "updateScheduledEvent", filters, position, inner_object
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return func
|
|
180
|
+
|
|
181
|
+
return decorator
|
|
@@ -15,18 +15,20 @@ class Handler:
|
|
|
15
15
|
filter: Filter = None,
|
|
16
16
|
position: int = None,
|
|
17
17
|
inner_object: bool = False,
|
|
18
|
+
is_from_plugin: bool = False,
|
|
18
19
|
) -> None:
|
|
19
20
|
self.func = func
|
|
20
21
|
self.update_type = update_type
|
|
21
22
|
self.filter = filter
|
|
22
23
|
self.position = position
|
|
23
24
|
self.inner_object = inner_object
|
|
25
|
+
self.is_from_plugin = is_from_plugin
|
|
24
26
|
|
|
25
27
|
def __call__(self, client: "pytdbot.Client", update: "pytdbot.types.Update"):
|
|
26
28
|
return self.func(client, update)
|
|
27
29
|
|
|
28
30
|
def __str__(self) -> str:
|
|
29
|
-
return f"Handler(func={self.func}, update_type={self.update_type}, filter={self.filter}, position={self.position}, inner_object={self.inner_object})"
|
|
31
|
+
return f"Handler(func={self.func}, update_type={self.update_type}, filter={self.filter}, position={self.position}, inner_object={self.inner_object}, is_from_plugin={self.is_from_plugin})"
|
|
30
32
|
|
|
31
33
|
def __repr__(self) -> str:
|
|
32
34
|
return str(self)
|
|
@@ -1947,6 +1947,82 @@ class Updates:
|
|
|
1947
1947
|
|
|
1948
1948
|
return decorator
|
|
1949
1949
|
|
|
1950
|
+
def on_updateDirectMessagesChatTopic(
|
|
1951
|
+
self: "pytdbot.Client" = None,
|
|
1952
|
+
filters: "pytdbot.filters.Filter" = None,
|
|
1953
|
+
position: int = None,
|
|
1954
|
+
) -> Callable:
|
|
1955
|
+
r"""Basic information about a topic in a channel direct messages chat administered by the current user has changed\. This update is guaranteed to come before the topic identifier is returned to the application
|
|
1956
|
+
|
|
1957
|
+
Parameters:
|
|
1958
|
+
filters (:class:`pytdbot.filters.Filter`, *optional*):
|
|
1959
|
+
An update filter
|
|
1960
|
+
|
|
1961
|
+
position (``int``, *optional*):
|
|
1962
|
+
The function position in handlers list. Default is ``None`` (append)
|
|
1963
|
+
|
|
1964
|
+
Raises:
|
|
1965
|
+
:py:class:`TypeError`
|
|
1966
|
+
"""
|
|
1967
|
+
|
|
1968
|
+
def decorator(func: Callable) -> Callable:
|
|
1969
|
+
if hasattr(func, "_handler"):
|
|
1970
|
+
return func
|
|
1971
|
+
elif isinstance(self, pytdbot.Client):
|
|
1972
|
+
if iscoroutinefunction(func):
|
|
1973
|
+
self.add_handler(
|
|
1974
|
+
"updateDirectMessagesChatTopic", func, filters, position
|
|
1975
|
+
)
|
|
1976
|
+
else:
|
|
1977
|
+
raise TypeError("Handler must be async")
|
|
1978
|
+
elif isinstance(self, pytdbot.filters.Filter):
|
|
1979
|
+
func._handler = Handler(
|
|
1980
|
+
func, "updateDirectMessagesChatTopic", self, position
|
|
1981
|
+
)
|
|
1982
|
+
else:
|
|
1983
|
+
func._handler = Handler(
|
|
1984
|
+
func, "updateDirectMessagesChatTopic", filters, position
|
|
1985
|
+
)
|
|
1986
|
+
return func
|
|
1987
|
+
|
|
1988
|
+
return decorator
|
|
1989
|
+
|
|
1990
|
+
def on_updateTopicMessageCount(
|
|
1991
|
+
self: "pytdbot.Client" = None,
|
|
1992
|
+
filters: "pytdbot.filters.Filter" = None,
|
|
1993
|
+
position: int = None,
|
|
1994
|
+
) -> Callable:
|
|
1995
|
+
r"""Number of messages in a topic has changed; for Saved Messages and channel direct messages chat topics only
|
|
1996
|
+
|
|
1997
|
+
Parameters:
|
|
1998
|
+
filters (:class:`pytdbot.filters.Filter`, *optional*):
|
|
1999
|
+
An update filter
|
|
2000
|
+
|
|
2001
|
+
position (``int``, *optional*):
|
|
2002
|
+
The function position in handlers list. Default is ``None`` (append)
|
|
2003
|
+
|
|
2004
|
+
Raises:
|
|
2005
|
+
:py:class:`TypeError`
|
|
2006
|
+
"""
|
|
2007
|
+
|
|
2008
|
+
def decorator(func: Callable) -> Callable:
|
|
2009
|
+
if hasattr(func, "_handler"):
|
|
2010
|
+
return func
|
|
2011
|
+
elif isinstance(self, pytdbot.Client):
|
|
2012
|
+
if iscoroutinefunction(func):
|
|
2013
|
+
self.add_handler("updateTopicMessageCount", func, filters, position)
|
|
2014
|
+
else:
|
|
2015
|
+
raise TypeError("Handler must be async")
|
|
2016
|
+
elif isinstance(self, pytdbot.filters.Filter):
|
|
2017
|
+
func._handler = Handler(func, "updateTopicMessageCount", self, position)
|
|
2018
|
+
else:
|
|
2019
|
+
func._handler = Handler(
|
|
2020
|
+
func, "updateTopicMessageCount", filters, position
|
|
2021
|
+
)
|
|
2022
|
+
return func
|
|
2023
|
+
|
|
2024
|
+
return decorator
|
|
2025
|
+
|
|
1950
2026
|
def on_updateQuickReplyShortcut(
|
|
1951
2027
|
self: "pytdbot.Client" = None,
|
|
1952
2028
|
filters: "pytdbot.filters.Filter" = None,
|