telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.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 (169) hide show
  1. telegrinder/__init__.py +30 -31
  2. telegrinder/api/__init__.py +2 -1
  3. telegrinder/api/api.py +28 -20
  4. telegrinder/api/error.py +8 -4
  5. telegrinder/api/response.py +2 -2
  6. telegrinder/api/token.py +2 -2
  7. telegrinder/bot/__init__.py +6 -0
  8. telegrinder/bot/bot.py +38 -31
  9. telegrinder/bot/cute_types/__init__.py +2 -0
  10. telegrinder/bot/cute_types/base.py +54 -128
  11. telegrinder/bot/cute_types/callback_query.py +76 -61
  12. telegrinder/bot/cute_types/chat_join_request.py +4 -3
  13. telegrinder/bot/cute_types/chat_member_updated.py +28 -31
  14. telegrinder/bot/cute_types/inline_query.py +5 -4
  15. telegrinder/bot/cute_types/message.py +555 -602
  16. telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
  17. telegrinder/bot/cute_types/update.py +20 -12
  18. telegrinder/bot/cute_types/utils.py +3 -36
  19. telegrinder/bot/dispatch/__init__.py +4 -0
  20. telegrinder/bot/dispatch/abc.py +8 -9
  21. telegrinder/bot/dispatch/context.py +5 -7
  22. telegrinder/bot/dispatch/dispatch.py +85 -33
  23. telegrinder/bot/dispatch/handler/abc.py +5 -6
  24. telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
  25. telegrinder/bot/dispatch/handler/base.py +3 -3
  26. telegrinder/bot/dispatch/handler/document_reply.py +2 -2
  27. telegrinder/bot/dispatch/handler/func.py +36 -42
  28. telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
  29. telegrinder/bot/dispatch/handler/message_reply.py +2 -2
  30. telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
  31. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
  32. telegrinder/bot/dispatch/handler/video_reply.py +2 -2
  33. telegrinder/bot/dispatch/middleware/abc.py +83 -8
  34. telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
  35. telegrinder/bot/dispatch/process.py +44 -50
  36. telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
  37. telegrinder/bot/dispatch/return_manager/abc.py +6 -10
  38. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
  39. telegrinder/bot/dispatch/view/__init__.py +2 -0
  40. telegrinder/bot/dispatch/view/abc.py +10 -6
  41. telegrinder/bot/dispatch/view/base.py +81 -50
  42. telegrinder/bot/dispatch/view/box.py +20 -9
  43. telegrinder/bot/dispatch/view/callback_query.py +3 -4
  44. telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
  45. telegrinder/bot/dispatch/view/chat_member.py +3 -5
  46. telegrinder/bot/dispatch/view/inline_query.py +3 -4
  47. telegrinder/bot/dispatch/view/message.py +3 -4
  48. telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
  49. telegrinder/bot/dispatch/view/raw.py +42 -40
  50. telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
  51. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
  52. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
  53. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
  54. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  55. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
  56. telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
  57. telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
  58. telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
  59. telegrinder/bot/polling/polling.py +62 -54
  60. telegrinder/bot/rules/__init__.py +24 -1
  61. telegrinder/bot/rules/abc.py +17 -10
  62. telegrinder/bot/rules/callback_data.py +20 -61
  63. telegrinder/bot/rules/chat_join.py +6 -4
  64. telegrinder/bot/rules/command.py +4 -4
  65. telegrinder/bot/rules/enum_text.py +1 -4
  66. telegrinder/bot/rules/func.py +5 -3
  67. telegrinder/bot/rules/fuzzy.py +1 -1
  68. telegrinder/bot/rules/id.py +24 -0
  69. telegrinder/bot/rules/inline.py +6 -4
  70. telegrinder/bot/rules/integer.py +2 -1
  71. telegrinder/bot/rules/logic.py +18 -0
  72. telegrinder/bot/rules/markup.py +5 -6
  73. telegrinder/bot/rules/message.py +2 -4
  74. telegrinder/bot/rules/message_entities.py +1 -3
  75. telegrinder/bot/rules/node.py +15 -9
  76. telegrinder/bot/rules/payload.py +81 -0
  77. telegrinder/bot/rules/payment_invoice.py +29 -0
  78. telegrinder/bot/rules/regex.py +5 -6
  79. telegrinder/bot/rules/state.py +1 -3
  80. telegrinder/bot/rules/text.py +10 -5
  81. telegrinder/bot/rules/update.py +0 -0
  82. telegrinder/bot/scenario/abc.py +2 -4
  83. telegrinder/bot/scenario/checkbox.py +12 -14
  84. telegrinder/bot/scenario/choice.py +6 -9
  85. telegrinder/client/__init__.py +9 -1
  86. telegrinder/client/abc.py +35 -10
  87. telegrinder/client/aiohttp.py +28 -24
  88. telegrinder/client/form_data.py +31 -0
  89. telegrinder/client/sonic.py +212 -0
  90. telegrinder/model.py +38 -145
  91. telegrinder/modules.py +3 -1
  92. telegrinder/msgspec_utils.py +136 -68
  93. telegrinder/node/__init__.py +74 -13
  94. telegrinder/node/attachment.py +92 -16
  95. telegrinder/node/base.py +196 -68
  96. telegrinder/node/callback_query.py +17 -16
  97. telegrinder/node/command.py +3 -2
  98. telegrinder/node/composer.py +40 -75
  99. telegrinder/node/container.py +13 -7
  100. telegrinder/node/either.py +82 -0
  101. telegrinder/node/event.py +20 -31
  102. telegrinder/node/file.py +51 -0
  103. telegrinder/node/me.py +4 -5
  104. telegrinder/node/payload.py +78 -0
  105. telegrinder/node/polymorphic.py +27 -8
  106. telegrinder/node/rule.py +2 -6
  107. telegrinder/node/scope.py +4 -6
  108. telegrinder/node/source.py +37 -21
  109. telegrinder/node/text.py +20 -8
  110. telegrinder/node/tools/generator.py +7 -11
  111. telegrinder/py.typed +0 -0
  112. telegrinder/rules.py +0 -61
  113. telegrinder/tools/__init__.py +97 -38
  114. telegrinder/tools/adapter/__init__.py +19 -0
  115. telegrinder/tools/adapter/abc.py +49 -0
  116. telegrinder/tools/adapter/dataclass.py +56 -0
  117. telegrinder/{bot/rules → tools}/adapter/event.py +8 -10
  118. telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
  119. telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
  120. telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
  121. telegrinder/tools/buttons.py +52 -26
  122. telegrinder/tools/callback_data_serilization/__init__.py +5 -0
  123. telegrinder/tools/callback_data_serilization/abc.py +51 -0
  124. telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
  125. telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
  126. telegrinder/tools/error_handler/abc.py +4 -7
  127. telegrinder/tools/error_handler/error.py +0 -0
  128. telegrinder/tools/error_handler/error_handler.py +34 -48
  129. telegrinder/tools/formatting/__init__.py +57 -37
  130. telegrinder/tools/formatting/deep_links.py +541 -0
  131. telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
  132. telegrinder/tools/formatting/spec_html_formats.py +14 -60
  133. telegrinder/tools/functional.py +1 -5
  134. telegrinder/tools/global_context/global_context.py +26 -51
  135. telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
  136. telegrinder/tools/i18n/abc.py +0 -0
  137. telegrinder/tools/i18n/middleware/abc.py +3 -6
  138. telegrinder/tools/input_file_directory.py +30 -0
  139. telegrinder/tools/keyboard.py +9 -9
  140. telegrinder/tools/lifespan.py +105 -0
  141. telegrinder/tools/limited_dict.py +5 -10
  142. telegrinder/tools/loop_wrapper/abc.py +7 -2
  143. telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
  144. telegrinder/tools/magic.py +184 -34
  145. telegrinder/tools/state_storage/__init__.py +0 -0
  146. telegrinder/tools/state_storage/abc.py +5 -9
  147. telegrinder/tools/state_storage/memory.py +1 -1
  148. telegrinder/tools/strings.py +13 -0
  149. telegrinder/types/__init__.py +8 -0
  150. telegrinder/types/enums.py +31 -21
  151. telegrinder/types/input_file.py +51 -0
  152. telegrinder/types/methods.py +531 -109
  153. telegrinder/types/objects.py +934 -826
  154. telegrinder/verification_utils.py +0 -2
  155. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +2 -2
  156. telegrinder-0.4.0.dist-info/METADATA +144 -0
  157. telegrinder-0.4.0.dist-info/RECORD +182 -0
  158. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
  159. telegrinder/bot/rules/adapter/__init__.py +0 -17
  160. telegrinder/bot/rules/adapter/abc.py +0 -31
  161. telegrinder/node/message.py +0 -14
  162. telegrinder/node/update.py +0 -15
  163. telegrinder/tools/formatting/links.py +0 -38
  164. telegrinder/tools/kb_set/__init__.py +0 -4
  165. telegrinder/tools/kb_set/base.py +0 -15
  166. telegrinder/tools/kb_set/yaml.py +0 -63
  167. telegrinder-0.3.4.post1.dist-info/METADATA +0 -110
  168. telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
  169. /telegrinder/{bot/rules → tools}/adapter/errors.py +0 -0
@@ -1,26 +1,27 @@
1
1
  import asyncio
2
+ import sys
2
3
  import typing
4
+ from http import HTTPStatus
3
5
 
4
- import aiohttp
5
6
  import msgspec
6
7
  from fntypes.result import Error, Ok
7
8
 
8
- from telegrinder.api.api import API
9
- from telegrinder.api.error import InvalidTokenError
9
+ from telegrinder.api.api import API, HTTPClient
10
+ from telegrinder.api.error import APIServerError, InvalidTokenError
10
11
  from telegrinder.bot.polling.abc import ABCPolling
11
12
  from telegrinder.modules import logger
12
13
  from telegrinder.msgspec_utils import decoder
13
14
  from telegrinder.types.objects import Update, UpdateType
14
15
 
15
16
 
16
- class Polling(ABCPolling):
17
+ class Polling(ABCPolling, typing.Generic[HTTPClient]):
17
18
  def __init__(
18
19
  self,
19
- api: API,
20
+ api: API[HTTPClient],
20
21
  *,
21
22
  offset: int = 0,
22
- reconnection_timeout: float = 5,
23
- max_reconnetions: int = 10,
23
+ reconnection_timeout: float = 5.0,
24
+ max_reconnetions: int = 15,
24
25
  include_updates: set[str | UpdateType] | None = None,
25
26
  exclude_updates: set[str | UpdateType] | None = None,
26
27
  ) -> None:
@@ -30,9 +31,9 @@ class Polling(ABCPolling):
30
31
  exclude_updates=exclude_updates,
31
32
  )
32
33
  self.reconnection_timeout = 5.0 if reconnection_timeout < 0 else reconnection_timeout
33
- self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
34
+ self.max_reconnetions = 15 if max_reconnetions < 0 else max_reconnetions
34
35
  self.offset = offset
35
- self._stop = False
36
+ self._stop = True
36
37
 
37
38
  def __repr__(self) -> str:
38
39
  return (
@@ -59,9 +60,7 @@ class Polling(ABCPolling):
59
60
  return allowed_updates
60
61
 
61
62
  if include_updates and exclude_updates:
62
- allowed_updates = [
63
- x for x in allowed_updates if x in include_updates and x not in exclude_updates
64
- ]
63
+ allowed_updates = [x for x in allowed_updates if x in include_updates and x not in exclude_updates]
65
64
  elif exclude_updates:
66
65
  allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
67
66
  elif include_updates:
@@ -69,60 +68,69 @@ class Polling(ABCPolling):
69
68
 
70
69
  return [x.value if isinstance(x, UpdateType) else x for x in allowed_updates]
71
70
 
72
- async def get_updates(self) -> msgspec.Raw | None:
71
+ async def get_updates(self) -> msgspec.Raw:
73
72
  raw_updates = await self.api.request_raw(
74
- "getUpdates",
75
- {
76
- "offset": self.offset,
77
- "allowed_updates": self.allowed_updates,
78
- },
73
+ method="getUpdates",
74
+ data=dict(
75
+ offset=self.offset,
76
+ allowed_updates=self.allowed_updates,
77
+ ),
79
78
  )
79
+
80
80
  match raw_updates:
81
81
  case Ok(value):
82
82
  return value
83
- case Error(err) if err.code in (401, 404):
84
- raise InvalidTokenError("Token seems to be invalid")
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
85
89
 
86
90
  async def listen(self) -> typing.AsyncGenerator[list[Update], None]:
87
91
  logger.debug("Listening polling")
88
92
  reconn_counter = 0
93
+ self._stop = False
89
94
 
90
- while not self._stop:
91
- try:
92
- updates = await self.get_updates()
93
- reconn_counter = 0
94
- if not updates:
95
- continue
96
- updates_list: list[Update] = decoder.decode(updates, type=list[Update])
97
- if updates_list:
98
- yield updates_list
99
- self.offset = updates_list[-1].update_id + 1
100
- except InvalidTokenError as e:
101
- logger.error(e)
102
- self.stop()
103
- exit(3)
104
- except asyncio.CancelledError:
105
- logger.info("Caught cancel, polling stopping...")
106
- self.stop()
107
- except (aiohttp.client.ServerConnectionError, TimeoutError):
108
- if reconn_counter > self.max_reconnetions:
109
- logger.error(
110
- "Failed to reconnect to the server after {} attempts, polling stopping.",
111
- self.max_reconnetions,
112
- )
95
+ with decoder(list[Update]) as dec: # For improve performance
96
+ while not self._stop:
97
+ 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
113
  self.stop()
114
- exit(6)
115
- else:
116
- logger.warning(
117
- "Server disconnected, waiting 5 seconds to reconnect...",
118
- )
119
- reconn_counter += 1
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...")
120
131
  await asyncio.sleep(self.reconnection_timeout)
121
- except (aiohttp.ClientConnectorError, aiohttp.ClientOSError):
122
- logger.error("Client connection failed, attempted to reconnect...")
123
- await asyncio.sleep(self.reconnection_timeout)
124
- except BaseException as e:
125
- logger.exception(e)
132
+ except BaseException as e:
133
+ logger.exception("Traceback message below:")
126
134
 
127
135
  def stop(self) -> None:
128
136
  self._stop = True
@@ -19,6 +19,7 @@ from telegrinder.bot.rules.command import Argument, Command
19
19
  from telegrinder.bot.rules.enum_text import EnumTextRule
20
20
  from telegrinder.bot.rules.func import FuncRule
21
21
  from telegrinder.bot.rules.fuzzy import FuzzyText
22
+ from telegrinder.bot.rules.id import IdRule
22
23
  from telegrinder.bot.rules.inline import (
23
24
  HasLocation,
24
25
  InlineQueryChatType,
@@ -45,16 +46,28 @@ from telegrinder.bot.rules.is_from import (
45
46
  IsUser,
46
47
  IsUserId,
47
48
  )
49
+ from telegrinder.bot.rules.logic import If
48
50
  from telegrinder.bot.rules.markup import Markup
49
51
  from telegrinder.bot.rules.mention import HasMention
50
52
  from telegrinder.bot.rules.message import MessageRule
51
53
  from telegrinder.bot.rules.message_entities import HasEntities, MessageEntities
52
54
  from telegrinder.bot.rules.node import NodeRule
55
+ from telegrinder.bot.rules.payload import (
56
+ PayloadEqRule,
57
+ PayloadJsonEqRule,
58
+ PayloadMarkupRule,
59
+ PayloadModelRule,
60
+ PayloadRule,
61
+ )
62
+ from telegrinder.bot.rules.payment_invoice import (
63
+ PaymentInvoiceCurrency,
64
+ PaymentInvoiceRule,
65
+ )
53
66
  from telegrinder.bot.rules.regex import Regex
54
67
  from telegrinder.bot.rules.rule_enum import RuleEnum
55
68
  from telegrinder.bot.rules.start import StartCommand
56
69
  from telegrinder.bot.rules.state import State, StateMeta
57
- from telegrinder.bot.rules.text import HasText, Text
70
+ from telegrinder.bot.rules.text import HasCaption, HasText, Text
58
71
  from telegrinder.bot.rules.update import IsUpdateType
59
72
 
60
73
  __all__ = (
@@ -73,12 +86,15 @@ __all__ = (
73
86
  "EnumTextRule",
74
87
  "FuncRule",
75
88
  "FuzzyText",
89
+ "HasCaption",
76
90
  "HasData",
77
91
  "HasEntities",
78
92
  "HasInviteLink",
79
93
  "HasLocation",
80
94
  "HasMention",
81
95
  "HasText",
96
+ "IdRule",
97
+ "If",
82
98
  "InlineQueryChatType",
83
99
  "InlineQueryMarkup",
84
100
  "InlineQueryRule",
@@ -110,6 +126,13 @@ __all__ = (
110
126
  "NodeRule",
111
127
  "NotRule",
112
128
  "OrRule",
129
+ "PayloadEqRule",
130
+ "PayloadJsonEqRule",
131
+ "PayloadMarkupRule",
132
+ "PayloadModelRule",
133
+ "PayloadRule",
134
+ "PaymentInvoiceCurrency",
135
+ "PaymentInvoiceRule",
113
136
  "Regex",
114
137
  "RuleEnum",
115
138
  "StartCommand",
@@ -7,9 +7,10 @@ import typing_extensions as typing
7
7
  from telegrinder.bot.cute_types import MessageCute, UpdateCute
8
8
  from telegrinder.bot.dispatch.context import Context
9
9
  from telegrinder.bot.dispatch.process import check_rule
10
- from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
11
- from telegrinder.bot.rules.adapter.node import Event
12
- from telegrinder.node.base import Node, get_nodes, is_node
10
+ from telegrinder.node.base import NodeType, get_nodes, is_node
11
+ from telegrinder.tools.adapter import ABCAdapter
12
+ from telegrinder.tools.adapter.node import Event
13
+ from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
13
14
  from telegrinder.tools.i18n.abc import ABCTranslator
14
15
  from telegrinder.tools.magic import (
15
16
  cache_translation,
@@ -24,7 +25,8 @@ if typing.TYPE_CHECKING:
24
25
 
25
26
  AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any, contravariant=True)
26
27
 
27
- CheckResult: typing.TypeAlias = bool | typing.Awaitable[bool]
28
+ type CheckResult = bool | typing.Awaitable[bool]
29
+
28
30
  Message: typing.TypeAlias = MessageCute
29
31
  Update: typing.TypeAlias = UpdateCute
30
32
 
@@ -58,8 +60,15 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
58
60
  def check(self, *args, **kwargs):
59
61
  pass
60
62
 
61
- def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
63
+ def __init_subclass__(
64
+ cls,
65
+ *,
66
+ requires: list["ABCRule"] | None = None,
67
+ adapter: ABCAdapter[UpdateObject, AdaptTo] | None = None,
68
+ ) -> None:
62
69
  """Merges requirements from inherited classes and rule-specific requirements."""
70
+ if adapter is not None:
71
+ cls.adapter = adapter
63
72
 
64
73
  requirements = []
65
74
  for base in inspect.getmro(cls):
@@ -77,7 +86,6 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
77
86
  rule #> AndRule(HasText(), HasCaption()) -> True if all rules in an AndRule are True, otherwise False.
78
87
  ```
79
88
  """
80
-
81
89
  return AndRule(self, other)
82
90
 
83
91
  def __or__(self, other: "ABCRule") -> "OrRule":
@@ -88,7 +96,6 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
88
96
  rule #> OrRule(HasText(), HasCaption()) -> True if any rule in an OrRule are True, otherwise False.
89
97
  ```
90
98
  """
91
-
92
99
  return OrRule(self, other)
93
100
 
94
101
  def __invert__(self) -> "NotRule":
@@ -99,7 +106,6 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
99
106
  rule # NotRule(HasText()) -> True if rule returned False, otherwise False.
100
107
  ```
101
108
  """
102
-
103
109
  return NotRule(self)
104
110
 
105
111
  def __repr__(self) -> str:
@@ -109,7 +115,7 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
109
115
  )
110
116
 
111
117
  @cached_property
112
- def required_nodes(self) -> dict[str, type[Node]]:
118
+ def required_nodes(self) -> dict[str, type[NodeType]]:
113
119
  return get_nodes(self.check)
114
120
 
115
121
  def as_optional(self) -> "ABCRule":
@@ -120,8 +126,9 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
120
126
 
121
127
  async def bounding_check(
122
128
  self,
123
- adapted_value: AdaptTo,
124
129
  ctx: Context,
130
+ *,
131
+ adapted_value: AdaptTo,
125
132
  node_col: "NodeCollection | None" = None,
126
133
  ) -> bool:
127
134
  bound_check_rule = self.check
@@ -3,35 +3,41 @@ import inspect
3
3
  import typing
4
4
  from contextlib import suppress
5
5
 
6
- import msgspec
7
-
8
6
  from telegrinder.bot.cute_types import CallbackQueryCute
9
7
  from telegrinder.bot.dispatch.context import Context
10
- from telegrinder.bot.rules.adapter import EventAdapter
11
- from telegrinder.model import decoder
12
- from telegrinder.tools.buttons import DataclassInstance
8
+ from telegrinder.bot.rules.abc import ABCRule, CheckResult
9
+ from telegrinder.bot.rules.payload import (
10
+ PayloadEqRule,
11
+ PayloadJsonEqRule,
12
+ PayloadMarkupRule,
13
+ PayloadModelRule,
14
+ )
15
+ from telegrinder.tools.adapter.event import EventAdapter
13
16
  from telegrinder.types.enums import UpdateType
14
17
 
15
- from .abc import ABCRule, CheckResult
16
- from .markup import Markup, PatternLike, check_string
17
-
18
18
  CallbackQuery: typing.TypeAlias = CallbackQueryCute
19
19
  Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
20
20
  MapDict: typing.TypeAlias = dict[str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"]
21
21
  CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type[typing.Any] | Validator | CallbackMap"]]
22
22
  CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
23
+ CallbackDataEq: typing.TypeAlias = PayloadEqRule
24
+ CallbackDataJsonEq: typing.TypeAlias = PayloadJsonEqRule
25
+ CallbackDataMarkup: typing.TypeAlias = PayloadMarkupRule
26
+ CallbackDataJsonModel: typing.TypeAlias = PayloadModelRule
23
27
 
24
28
 
25
- class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
26
- adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
27
-
29
+ class CallbackQueryRule(
30
+ ABCRule[CallbackQuery],
31
+ abc.ABC,
32
+ adapter=EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery),
33
+ ):
28
34
  @abc.abstractmethod
29
35
  def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
30
36
 
31
37
 
32
38
  class HasData(CallbackQueryRule):
33
39
  def check(self, event: CallbackQuery) -> bool:
34
- return bool(event.data.unwrap_or_none())
40
+ return bool(event.data)
35
41
 
36
42
 
37
43
  class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
@@ -47,7 +53,6 @@ class CallbackDataMap(CallbackQueryDataRule):
47
53
  @classmethod
48
54
  def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
49
55
  """Transforms MapDict to CallbackMap."""
50
-
51
56
  callback_map = []
52
57
 
53
58
  for k, v in mapping.items():
@@ -60,7 +65,6 @@ class CallbackDataMap(CallbackQueryDataRule):
60
65
  @classmethod
61
66
  def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
62
67
  """Transforms `CallbackMap` to `CallbackMapStrict`."""
63
-
64
68
  callback_map_result = []
65
69
 
66
70
  for key, value in callback_map:
@@ -79,7 +83,6 @@ class CallbackDataMap(CallbackQueryDataRule):
79
83
  @staticmethod
80
84
  async def run_validator(value: typing.Any, validator: Validator) -> bool:
81
85
  """Run async or sync validator."""
82
-
83
86
  with suppress(BaseException):
84
87
  result = validator(value)
85
88
  if inspect.isawaitable(result):
@@ -91,15 +94,12 @@ class CallbackDataMap(CallbackQueryDataRule):
91
94
  @classmethod
92
95
  async def match(cls, callback_data: dict[str, typing.Any], callback_map: CallbackMapStrict) -> bool:
93
96
  """Matches callback_data with callback_map recursively."""
94
-
95
97
  for key, validator in callback_map:
96
98
  if key not in callback_data:
97
99
  return False
98
100
 
99
101
  if isinstance(validator, list):
100
- if not (
101
- isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)
102
- ):
102
+ if not (isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)):
103
103
  return False
104
104
 
105
105
  elif not await cls.run_validator(callback_data[key], validator):
@@ -108,7 +108,7 @@ class CallbackDataMap(CallbackQueryDataRule):
108
108
  return True
109
109
 
110
110
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
111
- callback_data = event.decode_callback_data().unwrap_or_none()
111
+ callback_data = event.decode_data().unwrap_or_none()
112
112
  if callback_data is None:
113
113
  return False
114
114
  if await self.match(callback_data, self.mapping):
@@ -117,47 +117,6 @@ class CallbackDataMap(CallbackQueryDataRule):
117
117
  return False
118
118
 
119
119
 
120
- class CallbackDataEq(CallbackQueryDataRule):
121
- def __init__(self, value: str, /) -> None:
122
- self.value = value
123
-
124
- def check(self, event: CallbackQuery) -> bool:
125
- return event.data.unwrap() == self.value
126
-
127
-
128
- class CallbackDataJsonEq(CallbackQueryDataRule):
129
- def __init__(self, d: dict[str, typing.Any], /) -> None:
130
- self.d = d
131
-
132
- def check(self, event: CallbackQuery) -> bool:
133
- return event.decode_callback_data().unwrap_or_none() == self.d
134
-
135
-
136
- class CallbackDataJsonModel(CallbackQueryDataRule):
137
- def __init__(
138
- self,
139
- model: type[msgspec.Struct] | type[DataclassInstance],
140
- *,
141
- alias: str | None = None,
142
- ) -> None:
143
- self.model = model
144
- self.alias = alias or "data"
145
-
146
- def check(self, event: CallbackQuery, ctx: Context) -> bool:
147
- with suppress(BaseException):
148
- ctx.set(self.alias, decoder.decode(event.data.unwrap().encode(), type=self.model))
149
- return True
150
- return False
151
-
152
-
153
- class CallbackDataMarkup(CallbackQueryDataRule):
154
- def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
155
- self.patterns = Markup(patterns).patterns
156
-
157
- def check(self, event: CallbackQuery, ctx: Context) -> bool:
158
- return check_string(self.patterns, event.data.unwrap(), ctx)
159
-
160
-
161
120
  __all__ = (
162
121
  "CallbackDataEq",
163
122
  "CallbackDataJsonEq",
@@ -2,7 +2,7 @@ import abc
2
2
  import typing
3
3
 
4
4
  from telegrinder.bot.cute_types import ChatJoinRequestCute
5
- from telegrinder.bot.rules.adapter import EventAdapter
5
+ from telegrinder.tools.adapter.event import EventAdapter
6
6
  from telegrinder.types.enums import UpdateType
7
7
 
8
8
  from .abc import ABCRule, CheckResult
@@ -10,9 +10,11 @@ from .abc import ABCRule, CheckResult
10
10
  ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
11
11
 
12
12
 
13
- class ChatJoinRequestRule(ABCRule[ChatJoinRequest], requires=[]):
14
- adapter: EventAdapter[ChatJoinRequest] = EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest)
15
-
13
+ class ChatJoinRequestRule(
14
+ ABCRule[ChatJoinRequest],
15
+ abc.ABC,
16
+ adapter=EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest),
17
+ ):
16
18
  @abc.abstractmethod
17
19
  def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
18
20
 
@@ -4,12 +4,12 @@ import typing
4
4
  from telegrinder.bot.dispatch.context import Context
5
5
  from telegrinder.node.command import CommandInfo, single_split
6
6
  from telegrinder.node.me import Me
7
- from telegrinder.node.source import Source
7
+ from telegrinder.node.source import ChatSource
8
8
  from telegrinder.types.enums import ChatType
9
9
 
10
10
  from .abc import ABCRule
11
11
 
12
- Validator: typing.TypeAlias = typing.Callable[[str], typing.Any | None]
12
+ type Validator = typing.Callable[[str], typing.Any | None]
13
13
 
14
14
 
15
15
  @dataclasses.dataclass(frozen=True, slots=True)
@@ -97,7 +97,7 @@ class Command(ABCRule):
97
97
 
98
98
  return None
99
99
 
100
- def check(self, command: CommandInfo, me: Me, src: Source, ctx: Context) -> bool:
100
+ def check(self, command: CommandInfo, me: Me, chat: ChatSource, ctx: Context) -> bool:
101
101
  name = self.remove_prefix(command.name)
102
102
  if name is None:
103
103
  return False
@@ -105,7 +105,7 @@ class Command(ABCRule):
105
105
  if name not in self.names:
106
106
  return False
107
107
 
108
- if not command.mention and self.mention_needed_in_chat and src.chat.type is not ChatType.PRIVATE:
108
+ if not command.mention and self.mention_needed_in_chat and chat.type is not ChatType.PRIVATE:
109
109
  return False
110
110
 
111
111
  if command.mention and self.validate_mention: # noqa
@@ -1,15 +1,12 @@
1
1
  import enum
2
- import typing
3
2
 
4
3
  from telegrinder.bot.dispatch.context import Context
5
4
  from telegrinder.node.text import Text
6
5
 
7
6
  from .abc import ABCRule
8
7
 
9
- T = typing.TypeVar("T", bound=enum.Enum)
10
8
 
11
-
12
- class EnumTextRule(ABCRule, typing.Generic[T]):
9
+ class EnumTextRule[T: enum.Enum](ABCRule):
13
10
  def __init__(self, enum_t: type[T], *, lower_case: bool = True) -> None:
14
11
  self.enum_t = enum_t
15
12
  self.texts = list(
@@ -2,9 +2,11 @@ import inspect
2
2
  import typing
3
3
 
4
4
  from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.tools.adapter.abc import ABCAdapter
6
+ from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
5
7
  from telegrinder.types.objects import Update
6
8
 
7
- from .abc import ABCAdapter, ABCRule, AdaptTo, RawUpdateAdapter
9
+ from .abc import ABCRule, AdaptTo
8
10
 
9
11
 
10
12
  class FuncRule(ABCRule, typing.Generic[AdaptTo]):
@@ -19,8 +21,8 @@ class FuncRule(ABCRule, typing.Generic[AdaptTo]):
19
21
  async def check(self, event: AdaptTo, ctx: Context) -> bool:
20
22
  result = self.func(event, ctx)
21
23
  if inspect.isawaitable(result):
22
- return await result
23
- return result # type: ignore
24
+ result = await result
25
+ return result
24
26
 
25
27
 
26
28
  __all__ = ("FuncRule",)
@@ -7,7 +7,7 @@ from .abc import ABCRule
7
7
 
8
8
 
9
9
  class FuzzyText(ABCRule):
10
- def __init__(self, texts: str | list[str], min_ratio: float = 0.7) -> None:
10
+ def __init__(self, texts: str | list[str], /, min_ratio: float = 0.7) -> None:
11
11
  if isinstance(texts, str):
12
12
  texts = [texts]
13
13
  self.texts = texts
@@ -0,0 +1,24 @@
1
+ import typing
2
+
3
+ from telegrinder.types.objects import Update
4
+
5
+ from .abc import ABCRule
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from telegrinder.tools.adapter.abc import ABCAdapter
9
+
10
+
11
+ class IdRule[Identifier](ABCRule[Identifier]):
12
+ def __init__(
13
+ self,
14
+ adapter: "ABCAdapter[Update, Identifier]",
15
+ tracked_identifiers: set[Identifier] | None = None,
16
+ ):
17
+ self.tracked_identifiers = tracked_identifiers or set()
18
+ self.adapter = adapter
19
+
20
+ async def check(self, event: Identifier) -> bool:
21
+ return event in self.tracked_identifiers
22
+
23
+
24
+ __all__ = ("IdRule",)
@@ -4,7 +4,7 @@ import typing
4
4
  from telegrinder.bot.cute_types import InlineQueryCute
5
5
  from telegrinder.bot.dispatch.context import Context
6
6
  from telegrinder.bot.rules.abc import ABCRule, CheckResult
7
- from telegrinder.bot.rules.adapter import EventAdapter
7
+ from telegrinder.tools.adapter.event import EventAdapter
8
8
  from telegrinder.types.enums import ChatType, UpdateType
9
9
 
10
10
  from .markup import Markup, PatternLike, check_string
@@ -12,9 +12,11 @@ from .markup import Markup, PatternLike, check_string
12
12
  InlineQuery: typing.TypeAlias = InlineQueryCute
13
13
 
14
14
 
15
- class InlineQueryRule(ABCRule[InlineQuery], abc.ABC):
16
- adapter: EventAdapter[InlineQuery] = EventAdapter(UpdateType.INLINE_QUERY, InlineQuery)
17
-
15
+ class InlineQueryRule(
16
+ ABCRule[InlineQuery],
17
+ abc.ABC,
18
+ adapter=EventAdapter(UpdateType.INLINE_QUERY, InlineQuery),
19
+ ):
18
20
  @abc.abstractmethod
19
21
  def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
20
22
 
@@ -1,3 +1,4 @@
1
+ from telegrinder.node.base import as_node
1
2
  from telegrinder.node.text import TextInteger
2
3
 
3
4
  from .abc import ABCRule
@@ -6,7 +7,7 @@ from .node import NodeRule
6
7
 
7
8
  class IsInteger(NodeRule):
8
9
  def __init__(self) -> None:
9
- super().__init__(TextInteger)
10
+ super().__init__(as_node(TextInteger))
10
11
 
11
12
 
12
13
  class IntegerInRange(ABCRule):
@@ -0,0 +1,18 @@
1
+ import typing
2
+
3
+ from .abc import ABCRule, Context, UpdateCute, check_rule
4
+
5
+
6
+ class If(ABCRule):
7
+ def __init__(self, condition: ABCRule) -> None:
8
+ self.conditions = [condition]
9
+
10
+ async def check(self, update: UpdateCute, ctx: Context) -> bool:
11
+ for condition in self.conditions[:-1]:
12
+ if not await check_rule(update.api, condition, update, ctx):
13
+ return True
14
+ return await check_rule(update.api, self.conditions[-1], update, ctx)
15
+
16
+ def then(self, condition: ABCRule) -> typing.Self:
17
+ self.conditions.append(condition)
18
+ return self