telegrinder 0.4.2__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of telegrinder might be problematic. Click here for more details.

Files changed (233) hide show
  1. telegrinder/__init__.py +37 -55
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +2621 -2590
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +64 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +7846 -7058
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.0.dist-info/METADATA +162 -0
  198. telegrinder-0.5.0.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/WHEEL +0 -0
@@ -1,139 +1,260 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import sys
3
5
  import typing
4
6
  from http import HTTPStatus
5
7
 
6
8
  import msgspec
7
- from fntypes.result import Error, Ok
9
+ from fntypes.misc import is_ok
8
10
 
9
- from telegrinder.api.api import API, HTTPClient
11
+ from telegrinder.api.api import API
10
12
  from telegrinder.api.error import APIServerError, InvalidTokenError
11
13
  from telegrinder.bot.polling.abc import ABCPolling
12
14
  from telegrinder.modules import logger
13
15
  from telegrinder.msgspec_utils import decoder
16
+ from telegrinder.tools.aio import maybe_awaitable
14
17
  from telegrinder.types.objects import Update, UpdateType
15
18
 
19
+ DEFAULT_OFFSET: typing.Final[int] = 0
20
+ DEFAULT_RECONNECT_AFTER: typing.Final[float] = 5.0
21
+ DEFAULT_MAX_RECONNECTS: typing.Final[int] = 15
22
+
23
+
24
+ def _compute_number(
25
+ default: int | float,
26
+ input_value: int | float,
27
+ conditional_value: int | float,
28
+ /,
29
+ ) -> int | float:
30
+ return max(default, input_value) * (input_value <= conditional_value) + input_value * (
31
+ input_value >= conditional_value
32
+ )
33
+
34
+
35
+ class PollingErrorHandler:
36
+ _handlers: dict[type[BaseException], typing.Callable[[BaseException], typing.Any]]
37
+
38
+ __slots__ = ("_polling", "_handlers")
39
+
40
+ def __init__(self, polling: Polling, /) -> None:
41
+ self._polling = polling
42
+ self._handlers = { # type: ignore
43
+ InvalidTokenError: self._handle_invalid_token_error,
44
+ asyncio.CancelledError: self._handle_cancelled_error,
45
+ APIServerError: self._handle_api_server_error,
46
+ **{e: self._handle_connection_timeout_error for e in polling.api.http.CONNECTION_TIMEOUT_ERRORS},
47
+ **{e: self._handle_client_connection_error for e in polling.api.http.CLIENT_CONNECTION_ERRORS},
48
+ }
49
+
50
+ async def handle(self, error: BaseException) -> bool:
51
+ error_class = type(error)
52
+
53
+ if error_class is SystemExit:
54
+ self._handle_system_exit(error) # type: ignore
55
+
56
+ if error_class not in self._handlers:
57
+ return False
58
+
59
+ try:
60
+ await maybe_awaitable(self._handlers[error_class](error))
61
+ return True
62
+ except SystemExit as sys_exit_err:
63
+ self._handle_system_exit(sys_exit_err)
64
+
65
+ def _handle_system_exit(self, error: SystemExit) -> typing.NoReturn:
66
+ logger.error(f"Forced exit from the program with code {error.code}.")
67
+ raise error from None
68
+
69
+ def _handle_invalid_token_error(
70
+ self,
71
+ error: InvalidTokenError,
72
+ ) -> typing.NoReturn:
73
+ logger.error(error)
74
+ self._polling.stop()
75
+ sys.exit(3)
76
+
77
+ def _handle_cancelled_error(self, _: asyncio.CancelledError) -> None:
78
+ logger.info("Caught cancel, stopping polling...")
79
+ self._polling.stop()
80
+
81
+ async def _handle_connection_timeout_error(self, _: BaseException) -> None:
82
+ if self._polling.reconnects_counter > self._polling.max_reconnects:
83
+ logger.error(
84
+ "Failed to reconnect to Telegram API server after {} attempts, stopping polling...",
85
+ self._polling.max_reconnects,
86
+ )
87
+ self._polling.stop()
88
+ sys.exit(6)
89
+
90
+ logger.warning(
91
+ "Server disconnected, waiting {} seconds to reconnect...",
92
+ self._polling.reconnect_after,
93
+ )
94
+ await asyncio.sleep(self._polling.reconnect_after)
95
+
96
+ async def _handle_client_connection_error(self, _: BaseException) -> None:
97
+ logger.error(
98
+ "Client connection failed, attempt to reconnect after {} seconds...",
99
+ self._polling.reconnect_after,
100
+ )
101
+ await asyncio.sleep(self._polling.reconnect_after)
102
+
103
+ async def _handle_api_server_error(
104
+ self,
105
+ error: APIServerError,
106
+ ) -> None:
107
+ logger.error(f"{error}, waiting {error.retry_after} seconds to the next request...")
108
+ await asyncio.sleep(error.retry_after)
109
+
110
+
111
+ class Polling(ABCPolling):
112
+ __slots__ = (
113
+ "api",
114
+ "timeout",
115
+ "limit",
116
+ "allowed_updates",
117
+ "reconnect_after",
118
+ "max_reconnects",
119
+ "offset",
120
+ "_running",
121
+ "_reconnects_counter",
122
+ "_error_handler",
123
+ )
16
124
 
17
- class Polling(ABCPolling, typing.Generic[HTTPClient]):
18
125
  def __init__(
19
126
  self,
20
- api: API[HTTPClient],
127
+ api: API,
21
128
  *,
22
- offset: int = 0,
23
- reconnection_timeout: float = 5.0,
24
- max_reconnetions: int = 15,
25
- include_updates: set[str | UpdateType] | None = None,
26
- exclude_updates: set[str | UpdateType] | None = None,
129
+ timeout: int | None = None,
130
+ limit: int | None = None,
131
+ offset: int = DEFAULT_OFFSET,
132
+ reconnect_after: float = DEFAULT_RECONNECT_AFTER,
133
+ max_reconnects: int = DEFAULT_MAX_RECONNECTS,
134
+ include_updates: set[UpdateType] | None = None,
135
+ exclude_updates: set[UpdateType] | None = None,
27
136
  ) -> None:
28
137
  self.api = api
138
+ self.timeout = timeout
139
+ self.limit = limit
29
140
  self.allowed_updates = self.get_allowed_updates(
30
141
  include_updates=include_updates,
31
142
  exclude_updates=exclude_updates,
32
143
  )
33
- self.reconnection_timeout = 5.0 if reconnection_timeout < 0 else reconnection_timeout
34
- self.max_reconnetions = 15 if max_reconnetions < 0 else max_reconnetions
35
- self.offset = offset
36
- self._stop = True
144
+ self.reconnect_after = _compute_number(DEFAULT_RECONNECT_AFTER, reconnect_after, 0.0)
145
+ self.max_reconnects = _compute_number(DEFAULT_MAX_RECONNECTS, max_reconnects, 0)
146
+ self.offset = max(DEFAULT_OFFSET, offset)
147
+ self._running = False
148
+ self._reconnects_counter = 0
149
+ self._error_handler = PollingErrorHandler(self)
37
150
 
38
151
  def __repr__(self) -> str:
39
152
  return (
40
- "<{}: with api={!r}, stopped={}, offset={}, allowed_updates={!r}, "
41
- "max_reconnetions={}, reconnection_timeout={}>"
153
+ "<{}: api={!r}, running={}, offset={}, timeout={}, limit={}, "
154
+ "allowed_updates={!r}, max_reconnects={}, reconnect_after={}>"
42
155
  ).format(
43
- self.__class__.__name__,
156
+ type(self).__name__,
44
157
  self.api,
45
- self._stop,
158
+ self._running,
46
159
  self.offset,
160
+ self.timeout,
161
+ self.limit,
47
162
  self.allowed_updates,
48
- self.max_reconnetions,
49
- self.reconnection_timeout,
163
+ self.max_reconnects,
164
+ self.reconnect_after,
50
165
  )
51
166
 
52
167
  @staticmethod
53
168
  def get_allowed_updates(
54
169
  *,
55
- include_updates: set[str | UpdateType] | None = None,
56
- exclude_updates: set[str | UpdateType] | None = None,
57
- ) -> list[str]:
58
- allowed_updates: list[str] = list(x.value for x in UpdateType)
59
- if not include_updates and not exclude_updates:
60
- return allowed_updates
170
+ include_updates: set[UpdateType] | None = None,
171
+ exclude_updates: set[UpdateType] | None = None,
172
+ ) -> list[UpdateType]:
173
+ allowed_updates = list(UpdateType)
61
174
 
62
175
  if include_updates and exclude_updates:
63
- allowed_updates = [x for x in allowed_updates if x in include_updates and x not in exclude_updates]
64
- elif exclude_updates:
65
- allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
66
- elif include_updates:
67
- allowed_updates = [x for x in allowed_updates if x in include_updates]
176
+ return [x for x in allowed_updates if x in include_updates and x not in exclude_updates]
177
+
178
+ if exclude_updates:
179
+ return [x for x in allowed_updates if x not in exclude_updates]
180
+
181
+ if include_updates:
182
+ return [x for x in allowed_updates if x in include_updates]
68
183
 
69
- return [x.value if isinstance(x, UpdateType) else x for x in allowed_updates]
184
+ return allowed_updates
185
+
186
+ @property
187
+ def reconnects_counter(self) -> int:
188
+ return self._reconnects_counter
189
+
190
+ @property
191
+ def running(self) -> bool:
192
+ return self._running
193
+
194
+ def _reset_reconnects_counter(self) -> None:
195
+ self._reconnects_counter = 0
70
196
 
71
197
  async def get_updates(self) -> msgspec.Raw:
72
- raw_updates = await self.api.request_raw(
73
- method="getUpdates",
74
- data=dict(
75
- offset=self.offset,
76
- allowed_updates=self.allowed_updates,
77
- ),
78
- )
198
+ try:
199
+ raw_updates = await self.api.request_raw(
200
+ method="getUpdates",
201
+ data=dict(
202
+ offset=self.offset,
203
+ limit=self.limit,
204
+ timeout=self.timeout,
205
+ allowed_updates=self.allowed_updates,
206
+ ),
207
+ timeout=self.timeout or 0 + self.api.http.timeout,
208
+ )
209
+ except TimeoutError:
210
+ return msgspec.Raw(b"")
79
211
 
80
- match raw_updates:
81
- case Ok(value):
82
- return value
83
- case Error(err):
84
- if err.code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.NOT_FOUND):
85
- raise InvalidTokenError("Token seems to be invalid")
86
- if err.code in (HTTPStatus.BAD_GATEWAY, HTTPStatus.GATEWAY_TIMEOUT):
87
- raise APIServerError("Unavilability of the API Telegram server")
88
- raise err from None
212
+ if is_ok(raw_updates):
213
+ return raw_updates.value
214
+
215
+ match (error := raw_updates.error).status_code:
216
+ case HTTPStatus.TOO_MANY_REQUESTS:
217
+ error = APIServerError(
218
+ message="Too many requests to get updates",
219
+ retry_after=error.retry_after.unwrap_or(int(self.reconnect_after)),
220
+ )
221
+ case HTTPStatus.UNAUTHORIZED | HTTPStatus.NOT_FOUND as status:
222
+ error = InvalidTokenError(
223
+ "Token seems to be invalid"
224
+ if status == HTTPStatus.NOT_FOUND
225
+ else "Invalid token (unauthorized)",
226
+ )
227
+ case HTTPStatus.BAD_GATEWAY | HTTPStatus.GATEWAY_TIMEOUT as status:
228
+ error = APIServerError(
229
+ message="Telegram API server responded a {}".format(status.name.replace("_", " ")),
230
+ retry_after=int(self.reconnect_after),
231
+ )
232
+
233
+ raise error from None
89
234
 
90
235
  async def listen(self) -> typing.AsyncGenerator[list[Update], None]:
91
236
  logger.debug("Listening polling")
92
- reconn_counter = 0
93
- self._stop = False
237
+ self._running = True
94
238
 
95
- with decoder(list[Update]) as dec: # For improve performance
96
- while not self._stop:
239
+ with decoder(list[Update]) as dec:
240
+ while self._running:
97
241
  try:
98
- updates = await self.get_updates()
99
- reconn_counter = 0
100
- updates_list = dec.decode(updates)
101
- if updates_list:
102
- yield updates_list
103
- self.offset = updates_list[-1].update_id + 1
104
- except InvalidTokenError as e:
105
- logger.error(e)
106
- self.stop()
107
- sys.exit(3)
108
- except APIServerError as e:
109
- logger.error(f"{e}, waiting {self.reconnection_timeout} seconds to the next request...")
110
- await asyncio.sleep(self.reconnection_timeout)
111
- except asyncio.CancelledError:
112
- logger.info("Caught cancel, polling stopping...")
113
- self.stop()
114
- except self.api.http.CONNECTION_TIMEOUT_ERRORS:
115
- if reconn_counter > self.max_reconnetions:
116
- logger.error(
117
- "Failed to reconnect to the server after {} attempts, polling stopping.",
118
- self.max_reconnetions,
119
- )
120
- self.stop()
121
- sys.exit(6)
122
- else:
123
- logger.warning(
124
- "Server disconnected, waiting {} seconds to reconnect...",
125
- self.reconnection_timeout,
126
- )
127
- reconn_counter += 1
128
- await asyncio.sleep(self.reconnection_timeout)
129
- except self.api.http.CLIENT_CONNECTION_ERRORS:
130
- logger.error("Client connection failed, attempted to reconnect...")
131
- await asyncio.sleep(self.reconnection_timeout)
132
- except BaseException as e:
133
- logger.exception("Traceback message below:")
242
+ if (raw := await self.get_updates()) and (updates := dec.decode(raw)):
243
+ yield updates
244
+ self.offset = updates[-1].update_id + 1
245
+
246
+ if self._reconnects_counter != 0:
247
+ self._reset_reconnects_counter()
248
+ except BaseException as error:
249
+ if not await self._error_handler.handle(error):
250
+ logger.exception("Traceback message below:")
251
+
252
+ if isinstance(error, self.api.http.CONNECTION_TIMEOUT_ERRORS):
253
+ self._reconnects_counter += 1
134
254
 
135
255
  def stop(self) -> None:
136
- self._stop = True
256
+ self._running = False
257
+ self._reset_reconnects_counter()
137
258
 
138
259
 
139
260
  __all__ = ("Polling",)
@@ -1,4 +1,4 @@
1
- from telegrinder.bot.rules.abc import ABCRule, AndRule, NotRule, OrRule
1
+ from telegrinder.bot.rules.abc import ABCRule, AndRule, NotRule, OrRule, check_rule
2
2
  from telegrinder.bot.rules.callback_data import (
3
3
  CallbackDataEq,
4
4
  CallbackDataJsonEq,
@@ -6,11 +6,9 @@ from telegrinder.bot.rules.callback_data import (
6
6
  CallbackDataMap,
7
7
  CallbackDataMarkup,
8
8
  CallbackQueryDataRule,
9
- CallbackQueryRule,
10
9
  HasData,
11
10
  )
12
11
  from telegrinder.bot.rules.chat_join import (
13
- ChatJoinRequestRule,
14
12
  HasInviteLink,
15
13
  InviteLinkByCreator,
16
14
  InviteLinkName,
@@ -19,12 +17,10 @@ from telegrinder.bot.rules.command import Argument, Command
19
17
  from telegrinder.bot.rules.enum_text import EnumTextRule
20
18
  from telegrinder.bot.rules.func import FuncRule
21
19
  from telegrinder.bot.rules.fuzzy import FuzzyText
22
- from telegrinder.bot.rules.id import IdRule
23
20
  from telegrinder.bot.rules.inline import (
24
21
  HasLocation,
25
22
  InlineQueryChatType,
26
23
  InlineQueryMarkup,
27
- InlineQueryRule,
28
24
  InlineQueryText,
29
25
  )
30
26
  from telegrinder.bot.rules.integer import IntegerInRange, IsInteger
@@ -49,7 +45,6 @@ from telegrinder.bot.rules.is_from import (
49
45
  from telegrinder.bot.rules.logic import If
50
46
  from telegrinder.bot.rules.markup import Markup
51
47
  from telegrinder.bot.rules.mention import HasMention
52
- from telegrinder.bot.rules.message import MessageRule
53
48
  from telegrinder.bot.rules.message_entities import HasEntities, MessageEntities
54
49
  from telegrinder.bot.rules.node import NodeRule
55
50
  from telegrinder.bot.rules.payload import (
@@ -59,10 +54,7 @@ from telegrinder.bot.rules.payload import (
59
54
  PayloadModelRule,
60
55
  PayloadRule,
61
56
  )
62
- from telegrinder.bot.rules.payment_invoice import (
63
- PaymentInvoiceCurrency,
64
- PaymentInvoiceRule,
65
- )
57
+ from telegrinder.bot.rules.payment_invoice import PaymentInvoiceCurrency
66
58
  from telegrinder.bot.rules.regex import Regex
67
59
  from telegrinder.bot.rules.rule_enum import RuleEnum
68
60
  from telegrinder.bot.rules.start import StartCommand
@@ -80,8 +72,6 @@ __all__ = (
80
72
  "CallbackDataMap",
81
73
  "CallbackDataMarkup",
82
74
  "CallbackQueryDataRule",
83
- "CallbackQueryRule",
84
- "ChatJoinRequestRule",
85
75
  "Command",
86
76
  "EnumTextRule",
87
77
  "FuncRule",
@@ -93,11 +83,9 @@ __all__ = (
93
83
  "HasLocation",
94
84
  "HasMention",
95
85
  "HasText",
96
- "IdRule",
97
86
  "If",
98
87
  "InlineQueryChatType",
99
88
  "InlineQueryMarkup",
100
- "InlineQueryRule",
101
89
  "InlineQueryText",
102
90
  "IntegerInRange",
103
91
  "InviteLinkByCreator",
@@ -122,7 +110,6 @@ __all__ = (
122
110
  "IsUserId",
123
111
  "Markup",
124
112
  "MessageEntities",
125
- "MessageRule",
126
113
  "NodeRule",
127
114
  "NotRule",
128
115
  "OrRule",
@@ -132,11 +119,11 @@ __all__ = (
132
119
  "PayloadModelRule",
133
120
  "PayloadRule",
134
121
  "PaymentInvoiceCurrency",
135
- "PaymentInvoiceRule",
136
122
  "Regex",
137
123
  "RuleEnum",
138
124
  "StartCommand",
139
125
  "State",
140
126
  "StateMeta",
141
127
  "Text",
128
+ "check_rule",
142
129
  )