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.
Files changed (46) hide show
  1. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/PKG-INFO +2 -2
  2. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/PKG-INFO +2 -2
  3. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/SOURCES.txt +3 -0
  4. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/README.md +1 -1
  5. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/__init__.py +1 -1
  6. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/client.py +168 -26
  7. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/handlers/decorators.py +45 -0
  8. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/handlers/handler.py +3 -1
  9. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/handlers/td_updates.py +76 -0
  10. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/methods/td_functions.py +378 -45
  11. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/__init__.py +23 -1
  12. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/types.py +583 -42
  13. pytdbot-0.9.5.dev0/pytdbot/types/tdserver/__init__.py +3 -0
  14. pytdbot-0.9.5.dev0/pytdbot/types/tdserver/schedule.py +96 -0
  15. pytdbot-0.9.5.dev0/pytdbot/types/tdserver/stats.py +65 -0
  16. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/__init__.py +4 -0
  17. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/text_format.py +44 -12
  18. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/LICENSE +0 -0
  19. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/MANIFEST.in +0 -0
  20. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/dependency_links.txt +0 -0
  21. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/requires.txt +0 -0
  22. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/Pytdbot.egg-info/top_level.txt +0 -0
  23. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/client_manager.py +0 -0
  24. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/exception/__init__.py +0 -0
  25. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/filters.py +0 -0
  26. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/handlers/__init__.py +0 -0
  27. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/methods/__init__.py +0 -0
  28. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/methods/methods.py +0 -0
  29. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/tdjson/__init__.py +0 -0
  30. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/tdjson/tdjson.py +0 -0
  31. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/plugins/__init__.py +0 -0
  32. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/__init__.py +0 -0
  33. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/__init__.py +0 -0
  34. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/callback_query.py +0 -0
  35. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/chatActions.py +0 -0
  36. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/file.py +0 -0
  37. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/types/td_types/bound_methods/message.py +0 -0
  38. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/asyncio_utils.py +0 -0
  39. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/escape.py +0 -0
  40. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/json_utils.py +0 -0
  41. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/obj_encoder.py +0 -0
  42. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/strings.py +0 -0
  43. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/pytdbot/utils/webapps.py +0 -0
  44. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/requirements.txt +0 -0
  45. {pytdbot-0.9.3 → pytdbot-0.9.5.dev0}/setup.cfg +0 -0
  46. {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
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 [![Version](https://img.shields.io/pypi/v/Pytdbot?style=flat&logo=pypi)](https://pypi.org/project/Pytdbot) [![TDLib version](https://img.shields.io/badge/TDLib-v1.8.49-blue?logo=telegram)](https://github.com/tdlib/td) [![Downloads](https://static.pepy.tech/personalized-badge/pytdbot?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/pytdbot) [![Telegram Chat](https://img.shields.io/badge/Pytdbot%20chat-blue?logo=telegram&label=Telegram)](https://t.me/pytdbotchat)
33
+ # Pytdbot [![Version](https://img.shields.io/pypi/v/Pytdbot?style=flat&logo=pypi)](https://pypi.org/project/Pytdbot) [![TDLib version](https://img.shields.io/badge/TDLib-v1.8.50-blue?logo=telegram)](https://github.com/tdlib/td) [![Downloads](https://static.pepy.tech/personalized-badge/pytdbot?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/pytdbot) [![Telegram Chat](https://img.shields.io/badge/Pytdbot%20chat-blue?logo=telegram&label=Telegram)](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
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 [![Version](https://img.shields.io/pypi/v/Pytdbot?style=flat&logo=pypi)](https://pypi.org/project/Pytdbot) [![TDLib version](https://img.shields.io/badge/TDLib-v1.8.49-blue?logo=telegram)](https://github.com/tdlib/td) [![Downloads](https://static.pepy.tech/personalized-badge/pytdbot?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/pytdbot) [![Telegram Chat](https://img.shields.io/badge/Pytdbot%20chat-blue?logo=telegram&label=Telegram)](https://t.me/pytdbotchat)
33
+ # Pytdbot [![Version](https://img.shields.io/pypi/v/Pytdbot?style=flat&logo=pypi)](https://pypi.org/project/Pytdbot) [![TDLib version](https://img.shields.io/badge/TDLib-v1.8.50-blue?logo=telegram)](https://github.com/tdlib/td) [![Downloads](https://static.pepy.tech/personalized-badge/pytdbot?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/pytdbot) [![Telegram Chat](https://img.shields.io/badge/Pytdbot%20chat-blue?logo=telegram&label=Telegram)](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 [![Version](https://img.shields.io/pypi/v/Pytdbot?style=flat&logo=pypi)](https://pypi.org/project/Pytdbot) [![TDLib version](https://img.shields.io/badge/TDLib-v1.8.49-blue?logo=telegram)](https://github.com/tdlib/td) [![Downloads](https://static.pepy.tech/personalized-badge/pytdbot?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/pytdbot) [![Telegram Chat](https://img.shields.io/badge/Pytdbot%20chat-blue?logo=telegram&label=Telegram)](https://t.me/pytdbotchat)
1
+ # Pytdbot [![Version](https://img.shields.io/pypi/v/Pytdbot?style=flat&logo=pypi)](https://pypi.org/project/Pytdbot) [![TDLib version](https://img.shields.io/badge/TDLib-v1.8.50-blue?logo=telegram)](https://github.com/tdlib/td) [![Downloads](https://static.pepy.tech/personalized-badge/pytdbot?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/pytdbot) [![Telegram Chat](https://img.shields.io/badge/Pytdbot%20chat-blue?logo=telegram&label=Telegram)](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
 
@@ -13,7 +13,7 @@ __all__ = [
13
13
  "Client",
14
14
  ]
15
15
 
16
- __version__ = "0.9.3"
16
+ __version__ = "0.9.5.dev0"
17
17
  __copyright__ = "Copyright (c) 2022-2025 Pytdbot, AYMENJD"
18
18
  __license__ = "MIT License"
19
19
 
@@ -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 not self.client_manager:
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(self._queue_update_worker())
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
- func = Handler(func, update_type, filters, position, inner_object)
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, func)
376
+ self._handlers[update_type].insert(position, handler)
306
377
  else:
307
- self._handlers[update_type].append(func)
308
- self._handlers[update_type].sort(key=lambda x: (x.position is None, x.position))
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
- for _, handlers in self._handlers.items():
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
- handlers.sort(key=lambda x: (x.position is None, x.position))
331
- return True
332
- return False
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 _load_plugins(self):
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._handlers["initializer"]:
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._handlers[update.getType()]:
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._handlers["finalizer"]:
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._handlers:
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 + "_requests"),
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
- await self.__rqueues["updates"].consume(self.__on_update, no_ack=True)
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,