Pytdbot 0.10.0.dev0__py3-none-any.whl → 0.10.0.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pytdbot/__init__.py CHANGED
@@ -13,7 +13,7 @@ __all__ = [
13
13
  "Client",
14
14
  ]
15
15
 
16
- __version__ = "0.10.0.dev0"
16
+ __version__ = "0.10.0.dev2"
17
17
  __copyright__ = "Copyright (c) 2022-2026 Pytdbot, AYMENJD"
18
18
  __license__ = "MIT License"
19
19
 
pytdbot/client.py CHANGED
@@ -5,6 +5,7 @@ import signal
5
5
  from collections.abc import Callable
6
6
  from importlib import import_module
7
7
  from importlib import reload as reload_module
8
+ from inspect import iscoroutinefunction
8
9
  from json import dumps
9
10
  from logging import DEBUG, getLogger
10
11
  from os.path import join as join_path
@@ -28,7 +29,6 @@ from .utils import (
28
29
  create_extra_id,
29
30
  dict_to_obj,
30
31
  get_bot_id_from_token,
31
- get_running_loop,
32
32
  json_dumps,
33
33
  json_loads,
34
34
  obj_to_dict,
@@ -88,9 +88,6 @@ class Client(Decorators, Methods):
88
88
  use_message_database (``bool``, *optional*):
89
89
  If set to true, the library will maintain a cache of chats and messages. Implies use_chat_info_database. Default is ``True``
90
90
 
91
- loop (:py:class:`asyncio.AbstractEventLoop`, *optional*):
92
- Event loop. Default is ``None`` (auto-detect)
93
-
94
91
  options (``dict``, *optional*):
95
92
  Pass key-value dictionary to set TDLib options. Check the list of available options at https://core.telegram.org/tdlib/options
96
93
 
@@ -131,7 +128,6 @@ class Client(Decorators, Methods):
131
128
  use_file_database: bool = True,
132
129
  use_chat_info_database: bool = True,
133
130
  use_message_database: bool = True,
134
- loop: asyncio.AbstractEventLoop | None = None,
135
131
  options: dict | None = None,
136
132
  workers: int = 5,
137
133
  queue_size: int = 1000,
@@ -220,15 +216,15 @@ class Client(Decorators, Methods):
220
216
  }
221
217
  self.__is_queue_worker = False
222
218
  self.__is_closing = False
219
+ self.__idle_event: asyncio.Event = None
220
+ self.__closed_event: asyncio.Event = None
223
221
 
224
222
  # RabbitMQ
225
223
  self.__rqueues = None
226
224
  self.__rconnection = None
227
225
  self.__rchannel = None
228
226
 
229
- self.loop = (
230
- loop if isinstance(loop, asyncio.AbstractEventLoop) else get_running_loop()
231
- )
227
+ self.loop = None
232
228
 
233
229
  if plugins is not None:
234
230
  self._load_plugins()
@@ -324,6 +320,10 @@ class Client(Decorators, Methods):
324
320
  if not self.is_running:
325
321
  self.logger.info("Starting pytdbot client...")
326
322
 
323
+ self.loop = asyncio.get_running_loop()
324
+ self.__idle_event = asyncio.Event()
325
+ self.__closed_event = asyncio.Event()
326
+
327
327
  if self.is_rabbitmq:
328
328
  await self.__start_rabbitmq()
329
329
  elif not self.client_manager:
@@ -611,12 +611,13 @@ class Client(Decorators, Methods):
611
611
 
612
612
  return await self.invoke(kwargs)
613
613
 
614
- def run(self) -> None:
614
+ async def run(self) -> None:
615
615
  r"""Start the client and block until the client is stopped
616
616
 
617
617
  Example:
618
618
  .. code-block:: python
619
619
 
620
+ import asyncio
620
621
  from pytdbot import Client
621
622
 
622
623
  client = Client(...)
@@ -625,19 +626,22 @@ class Client(Decorators, Methods):
625
626
  async def new_message(c,update):
626
627
  await update.reply_text('Hello!')
627
628
 
628
- client.run()
629
+ asyncio.run(client.run())
629
630
  """
630
631
 
632
+ await self.start()
633
+
631
634
  self._register_signal_handlers()
632
635
 
633
- self.loop.run_until_complete(self.start())
634
- self.loop.run_until_complete(self.idle())
636
+ await self.idle()
635
637
 
636
638
  async def idle(self):
637
- r"""Idle and wait until the client is stopped."""
639
+ r"""Idle and wait until the client is stopped"""
638
640
 
639
- while self.is_running:
640
- await asyncio.sleep(1)
641
+ if not self.__idle_event:
642
+ self.__idle_event = asyncio.Event()
643
+
644
+ await self.__idle_event.wait()
641
645
 
642
646
  async def stop(self) -> bool:
643
647
  r"""Stop the client
@@ -666,8 +670,7 @@ class Client(Decorators, Methods):
666
670
  }:
667
671
  await self.close()
668
672
 
669
- while self.authorization_state != "authorizationStateClosed":
670
- await asyncio.sleep(0.1)
673
+ await self.__closed_event.wait()
671
674
 
672
675
  if self.is_rabbitmq:
673
676
  await self.__rchannel.close()
@@ -679,6 +682,7 @@ class Client(Decorators, Methods):
679
682
  await self.client_manager.close()
680
683
 
681
684
  self.logger.info("Instance closed")
685
+ self.__idle_event.set()
682
686
 
683
687
  return True
684
688
 
@@ -778,7 +782,7 @@ class Client(Decorators, Methods):
778
782
  ]
779
783
 
780
784
  for handler in handlers_to_load:
781
- if asyncio.iscoroutinefunction(handler.func):
785
+ if iscoroutinefunction(handler.func):
782
786
  self.add_handler(
783
787
  update_type=handler.update_type,
784
788
  func=handler.func,
@@ -810,39 +814,45 @@ class Client(Decorators, Methods):
810
814
  if func in self.__cache["is_coro_filter"]:
811
815
  return self.__cache["is_coro_filter"][func]
812
816
  else:
813
- is_coro = asyncio.iscoroutinefunction(func)
817
+ is_coro = iscoroutinefunction(func)
814
818
  self.__cache["is_coro_filter"][func] = is_coro
815
819
  return is_coro
816
820
 
817
- async def process_update(self, update):
821
+ async def process_update(self, update: dict) -> None:
818
822
  if not update:
819
823
  self.logger.warning("Received None update")
820
824
  return
821
825
 
822
- if (
823
- self.logger.root.level >= DEBUG or self.logger.level >= DEBUG
824
- ): # dumping all results may create performance issues
825
- self.logger.debug(f"Received: {dumps(update, indent=4)}")
826
+ is_debug = self.logger.isEnabledFor(DEBUG)
826
827
 
827
- if "@extra" in update:
828
- if result := self._results.pop(update["@extra"]["id"], None):
829
- obj = dict_to_obj(update, self)
828
+ if extra := update.get("@extra"):
829
+ result_id = extra["id"]
830
830
 
831
- result.set_result(obj)
832
- elif update["@type"] == "error" and "option" in update["@extra"]:
833
- self.logger.error(f"{update['@extra']['option']}: {update['message']}")
831
+ if is_debug:
832
+ self.logger.debug(
833
+ f"Received result for {result_id}: {dumps(update, indent=4)}"
834
+ )
834
835
 
835
- else:
836
- update_handler = self.__local_handlers.get(update["@type"])
837
- update = dict_to_obj(update, self)
836
+ if result_id and (result := self._results.pop(result_id, None)):
837
+ result.set_result(dict_to_obj(update, self))
838
838
 
839
- if update_handler:
840
- self.loop.create_task(update_handler(update))
839
+ elif update["@type"] == "error" and "option" in extra:
840
+ self.logger.error(f"{extra['option']}: {update['message']}")
841
841
 
842
- if not self.is_rabbitmq and self.__is_queue_worker:
843
- self.queue.put_nowait(update)
844
- else:
845
- await self._handle_update(update)
842
+ return
843
+
844
+ if is_debug:
845
+ self.logger.debug(f"Received: {dumps(update, indent=4)}")
846
+
847
+ update_obj = dict_to_obj(update, self)
848
+
849
+ if handler := self.__local_handlers.get(update.get("@type")):
850
+ self.loop.create_task(handler(update_obj))
851
+
852
+ if not self.is_rabbitmq and self.__is_queue_worker:
853
+ self.queue.put_nowait(update_obj)
854
+ else:
855
+ await self._handle_update(update_obj)
846
856
 
847
857
  def get_inner_object(self, update: types.TlObject):
848
858
  if isinstance(update, types.UpdateNewMessage):
@@ -1073,11 +1083,10 @@ class Client(Decorators, Methods):
1073
1083
 
1074
1084
  if self.authorization_state == "authorizationStateClosing":
1075
1085
  self.__is_closing = True
1076
- elif (
1077
- self.authorization_state == "authorizationStateClosed"
1078
- and self.__is_closing is False
1079
- ):
1080
- await self.stop()
1086
+ elif self.authorization_state == "authorizationStateClosed":
1087
+ self.__closed_event.set()
1088
+ if self.__is_closing is False:
1089
+ await self.stop()
1081
1090
 
1082
1091
  async def __handle_connection_state(self, update: types.UpdateConnectionState):
1083
1092
  self.connection_state: str = update.state.getType()
@@ -1099,7 +1108,9 @@ class Client(Decorators, Methods):
1099
1108
  m_id = f"{update.message.chat_id}:{update.old_message_id}"
1100
1109
 
1101
1110
  if result := self._results.pop(m_id, None):
1102
- result.set_result(update.error)
1111
+ result.set_result(
1112
+ update.error if not update.message.media_album_id else update.message
1113
+ )
1103
1114
 
1104
1115
  async def __handle_update_option(self, update: types.UpdateOption):
1105
1116
  if isinstance(update.value, types.OptionValueBoolean):
pytdbot/filters.py CHANGED
@@ -50,7 +50,7 @@ def create(func: Callable) -> Filter:
50
50
  async def photo_handler(c,update):
51
51
  await update.reply_text('I got a photo!')
52
52
 
53
- client.run()
53
+ asyncio.run(client.run())
54
54
 
55
55
  Parameters:
56
56
  func (``Callable``):
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from asyncio import iscoroutinefunction
4
3
  from collections.abc import Callable
4
+ from inspect import iscoroutinefunction
5
5
  from logging import getLogger
6
6
 
7
7
  import pytdbot
@@ -2433,6 +2433,65 @@ class Updates:
2433
2433
 
2434
2434
  return decorator
2435
2435
 
2436
+ def on_updateChatUnreadPollVoteCount(
2437
+ self: pytdbot.Client | None = None,
2438
+ filters: pytdbot.filters.Filter | None = None,
2439
+ position: int | None = None,
2440
+ timeout: float | None = None,
2441
+ ) -> Callable:
2442
+ r"""The chat unread\_poll\_vote\_count has changed
2443
+
2444
+ Parameters:
2445
+ filters (:class:`pytdbot.filters.Filter`, *optional*):
2446
+ An update filter
2447
+
2448
+ position (``int``, *optional*):
2449
+ The function position in handlers list. Default is ``None`` (append)
2450
+
2451
+ timeout (``float``, *optional*):
2452
+ Max execution time for the handler before it timeout. Default is ``None``
2453
+
2454
+ Raises:
2455
+ :py:class:`TypeError`
2456
+ """
2457
+
2458
+ def decorator(func: Callable) -> Callable:
2459
+ if hasattr(func, "_handler"):
2460
+ return func
2461
+ elif isinstance(self, pytdbot.Client):
2462
+ if iscoroutinefunction(func):
2463
+ self.add_handler(
2464
+ update_type="updateChatUnreadPollVoteCount",
2465
+ func=func,
2466
+ filters=filters,
2467
+ position=position,
2468
+ inner_object=False,
2469
+ timeout=timeout,
2470
+ )
2471
+ else:
2472
+ raise TypeError("Handler must be async")
2473
+ elif isinstance(self, pytdbot.filters.Filter):
2474
+ func._handler = Handler(
2475
+ func=func,
2476
+ update_type="updateChatUnreadPollVoteCount",
2477
+ filter=self,
2478
+ position=position,
2479
+ inner_object=False,
2480
+ timeout=timeout,
2481
+ )
2482
+ else:
2483
+ func._handler = Handler(
2484
+ func=func,
2485
+ update_type="updateChatUnreadPollVoteCount",
2486
+ filter=filters,
2487
+ position=position,
2488
+ inner_object=False,
2489
+ timeout=timeout,
2490
+ )
2491
+ return func
2492
+
2493
+ return decorator
2494
+
2436
2495
  def on_updateChatVideoChat(
2437
2496
  self: pytdbot.Client | None = None,
2438
2497
  filters: pytdbot.filters.Filter | None = None,
@@ -8864,6 +8923,65 @@ class Updates:
8864
8923
 
8865
8924
  return decorator
8866
8925
 
8926
+ def on_updateTextCompositionStyles(
8927
+ self: pytdbot.Client | None = None,
8928
+ filters: pytdbot.filters.Filter | None = None,
8929
+ position: int | None = None,
8930
+ timeout: float | None = None,
8931
+ ) -> Callable:
8932
+ r"""The styles supported for text composition have changed
8933
+
8934
+ Parameters:
8935
+ filters (:class:`pytdbot.filters.Filter`, *optional*):
8936
+ An update filter
8937
+
8938
+ position (``int``, *optional*):
8939
+ The function position in handlers list. Default is ``None`` (append)
8940
+
8941
+ timeout (``float``, *optional*):
8942
+ Max execution time for the handler before it timeout. Default is ``None``
8943
+
8944
+ Raises:
8945
+ :py:class:`TypeError`
8946
+ """
8947
+
8948
+ def decorator(func: Callable) -> Callable:
8949
+ if hasattr(func, "_handler"):
8950
+ return func
8951
+ elif isinstance(self, pytdbot.Client):
8952
+ if iscoroutinefunction(func):
8953
+ self.add_handler(
8954
+ update_type="updateTextCompositionStyles",
8955
+ func=func,
8956
+ filters=filters,
8957
+ position=position,
8958
+ inner_object=False,
8959
+ timeout=timeout,
8960
+ )
8961
+ else:
8962
+ raise TypeError("Handler must be async")
8963
+ elif isinstance(self, pytdbot.filters.Filter):
8964
+ func._handler = Handler(
8965
+ func=func,
8966
+ update_type="updateTextCompositionStyles",
8967
+ filter=self,
8968
+ position=position,
8969
+ inner_object=False,
8970
+ timeout=timeout,
8971
+ )
8972
+ else:
8973
+ func._handler = Handler(
8974
+ func=func,
8975
+ update_type="updateTextCompositionStyles",
8976
+ filter=filters,
8977
+ position=position,
8978
+ inner_object=False,
8979
+ timeout=timeout,
8980
+ )
8981
+ return func
8982
+
8983
+ return decorator
8984
+
8867
8985
  def on_updateSuggestedActions(
8868
8986
  self: pytdbot.Client | None = None,
8869
8987
  filters: pytdbot.filters.Filter | None = None,
@@ -9985,6 +10103,65 @@ class Updates:
9985
10103
 
9986
10104
  return decorator
9987
10105
 
10106
+ def on_updateManagedBot(
10107
+ self: pytdbot.Client | None = None,
10108
+ filters: pytdbot.filters.Filter | None = None,
10109
+ position: int | None = None,
10110
+ timeout: float | None = None,
10111
+ ) -> Callable:
10112
+ r"""A bot that can be managed by the current bot was created or updated; for bots only
10113
+
10114
+ Parameters:
10115
+ filters (:class:`pytdbot.filters.Filter`, *optional*):
10116
+ An update filter
10117
+
10118
+ position (``int``, *optional*):
10119
+ The function position in handlers list. Default is ``None`` (append)
10120
+
10121
+ timeout (``float``, *optional*):
10122
+ Max execution time for the handler before it timeout. Default is ``None``
10123
+
10124
+ Raises:
10125
+ :py:class:`TypeError`
10126
+ """
10127
+
10128
+ def decorator(func: Callable) -> Callable:
10129
+ if hasattr(func, "_handler"):
10130
+ return func
10131
+ elif isinstance(self, pytdbot.Client):
10132
+ if iscoroutinefunction(func):
10133
+ self.add_handler(
10134
+ update_type="updateManagedBot",
10135
+ func=func,
10136
+ filters=filters,
10137
+ position=position,
10138
+ inner_object=False,
10139
+ timeout=timeout,
10140
+ )
10141
+ else:
10142
+ raise TypeError("Handler must be async")
10143
+ elif isinstance(self, pytdbot.filters.Filter):
10144
+ func._handler = Handler(
10145
+ func=func,
10146
+ update_type="updateManagedBot",
10147
+ filter=self,
10148
+ position=position,
10149
+ inner_object=False,
10150
+ timeout=timeout,
10151
+ )
10152
+ else:
10153
+ func._handler = Handler(
10154
+ func=func,
10155
+ update_type="updateManagedBot",
10156
+ filter=filters,
10157
+ position=position,
10158
+ inner_object=False,
10159
+ timeout=timeout,
10160
+ )
10161
+ return func
10162
+
10163
+ return decorator
10164
+
9988
10165
  def on_updateChatMember(
9989
10166
  self: pytdbot.Client | None = None,
9990
10167
  filters: pytdbot.filters.Filter | None = None,