telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.1__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 +55 -129
  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 +28 -9
  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 +236 -60
  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.1.dist-info}/LICENSE +2 -2
  156. telegrinder-0.4.1.dist-info/METADATA +143 -0
  157. telegrinder-0.4.1.dist-info/RECORD +182 -0
  158. {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.1.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,46 +1,54 @@
1
- import dataclasses
2
- import inspect
3
1
  import typing
4
- from functools import wraps
5
2
 
6
3
  import msgspec
7
- import typing_extensions
8
- from fntypes.result import Result
9
4
 
10
5
  from telegrinder.api.api import API
11
- from telegrinder.model import Model, get_params
6
+ from telegrinder.model import Model, is_none
12
7
 
13
- F = typing.TypeVar("F", bound=typing.Callable[..., typing.Any])
14
- Cute = typing.TypeVar("Cute", bound="BaseCute")
15
- Update = typing_extensions.TypeVar("Update", bound=Model)
16
- CtxAPI = typing_extensions.TypeVar("CtxAPI", bound=API, default=API)
17
8
 
18
- Executor: typing.TypeAlias = typing.Callable[
19
- [Cute, str, dict[str, typing.Any]],
20
- typing.Awaitable[Result[typing.Any, typing.Any]],
21
- ]
9
+ def compose_method_params[Cute: BaseCute](
10
+ params: dict[str, typing.Any],
11
+ update: Cute,
12
+ *,
13
+ default_params: set[str | tuple[str, str]] | None = None,
14
+ validators: dict[str, typing.Callable[[Cute], bool]] | None = None,
15
+ ) -> dict[str, typing.Any]:
16
+ default_params = default_params or set()
17
+ validators = validators or {}
18
+
19
+ for param in default_params:
20
+ param_name = param if isinstance(param, str) else param[0]
21
+ if param_name not in params:
22
+ if param_name in validators and not validators[param_name](update):
23
+ continue
24
+ params[param_name] = getattr(update, param if isinstance(param, str) else param[1])
25
+
26
+ return params
27
+
22
28
 
23
29
  if typing.TYPE_CHECKING:
30
+ from telegrinder.node.base import Node
24
31
 
25
- class BaseCute(Model, typing.Generic[Update, CtxAPI]):
32
+ class BaseCute[Update: Model](Model):
26
33
  api: API
27
34
 
35
+ @classmethod
36
+ def as_node(cls) -> type[Node]: ...
37
+
28
38
  @classmethod
29
39
  def from_update(cls, update: Update, bound_api: API) -> typing.Self: ...
30
40
 
31
41
  @property
32
- def ctx_api(self) -> CtxAPI: ...
42
+ def ctx_api(self) -> API: ...
33
43
 
34
44
  def to_dict(
35
45
  self,
36
46
  *,
37
47
  exclude_fields: set[str] | None = None,
38
48
  ) -> dict[str, typing.Any]:
39
- """
40
- :param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
49
+ """:param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
41
50
  :return: A dictionary representation of this cute model.
42
51
  """
43
-
44
52
  ...
45
53
 
46
54
  def to_full_dict(
@@ -48,21 +56,19 @@ if typing.TYPE_CHECKING:
48
56
  *,
49
57
  exclude_fields: set[str] | None = None,
50
58
  ) -> dict[str, typing.Any]:
51
- """
52
- :param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
59
+ """:param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
53
60
  :return: A dictionary representation of this model including all models, structs, custom types.
54
61
  """
55
-
56
62
  ...
57
63
 
58
64
  else:
59
65
  import msgspec
60
66
  from fntypes.co import Nothing, Some, Variative
61
67
 
62
- from telegrinder.msgspec_utils import Option, decoder, encoder
68
+ from telegrinder.msgspec_utils import Option, decoder, encoder, struct_as_dict
63
69
  from telegrinder.msgspec_utils import get_class_annotations as _get_class_annotations
64
70
 
65
- def _get_cute_from_generic(generic_args):
71
+ def _get_cute_from_generic(generic_args, /):
66
72
  for arg in generic_args:
67
73
  orig_arg = typing.get_origin(arg) or arg
68
74
 
@@ -70,12 +76,12 @@ else:
70
76
  continue
71
77
  if orig_arg in (Variative, Some, Option):
72
78
  return _get_cute_from_generic(typing.get_args(arg))
73
- if issubclass(arg, BaseCute):
79
+ if issubclass(orig_arg, BaseCute):
74
80
  return arg
75
81
 
76
82
  return None
77
83
 
78
- def _get_cute_annotations(annotations):
84
+ def _get_cute_annotations(annotations, /):
79
85
  cute_annotations = {}
80
86
 
81
87
  for key, hint in annotations.items():
@@ -88,7 +94,7 @@ else:
88
94
 
89
95
  return cute_annotations
90
96
 
91
- def _get_value(value):
97
+ def _validate_value(value, /):
92
98
  while isinstance(value, Variative | Some):
93
99
  if isinstance(value, Variative):
94
100
  value = value.v
@@ -96,7 +102,9 @@ else:
96
102
  value = value.value
97
103
  return value
98
104
 
99
- class BaseCute(typing.Generic[Update, CtxAPI]):
105
+ class BaseCute[Update]:
106
+ api: API
107
+
100
108
  def __init_subclass__(cls, *args, **kwargs):
101
109
  super().__init_subclass__(*args, **kwargs)
102
110
 
@@ -105,6 +113,15 @@ else:
105
113
 
106
114
  cls.__is_solved_annotations__ = False
107
115
  cls.__cute_annotations__ = None
116
+ cls.__event_node__ = None
117
+
118
+ @classmethod
119
+ def as_node(cls):
120
+ if cls.__event_node__ is None:
121
+ from telegrinder.node.event import _EventNode
122
+
123
+ cls.__event_node__ = _EventNode[cls]
124
+ return cls.__event_node__
108
125
 
109
126
  @classmethod
110
127
  def from_update(cls, update, bound_api):
@@ -118,10 +135,10 @@ else:
118
135
  return cls(
119
136
  **{
120
137
  field: decoder.convert(
121
- cls.__cute_annotations__[field].from_update(_get_value(value), bound_api=bound_api),
138
+ cls.__cute_annotations__[field].from_update(_validate_value(value), bound_api=bound_api),
122
139
  type=cls.__annotations__[field],
123
140
  )
124
- if field in cls.__cute_annotations__ and not isinstance(value, Nothing)
141
+ if field in cls.__cute_annotations__ and not isinstance(value, Nothing | msgspec.UnsetType)
125
142
  else value
126
143
  for field, value in update.to_dict().items()
127
144
  },
@@ -135,15 +152,17 @@ else:
135
152
  def _to_dict(self, dct_name, exclude_fields, full):
136
153
  if dct_name not in self.__dict__:
137
154
  self.__dict__[dct_name] = (
138
- msgspec.structs.asdict(self)
155
+ struct_as_dict(self)
139
156
  if not full
140
157
  else encoder.to_builtins(
141
158
  {
142
159
  k: field.to_dict(exclude_fields=exclude_fields)
143
- if isinstance(field := _get_value(getattr(self, k)), BaseCute)
160
+ if isinstance(field, BaseCute)
144
161
  else field
145
162
  for k in self.__struct_fields__
146
163
  if k not in exclude_fields
164
+ and not isinstance(field := _validate_value(getattr(self, k)), msgspec.UnsetType)
165
+ and not is_none(field)
147
166
  },
148
167
  order="deterministic",
149
168
  )
@@ -154,105 +173,12 @@ else:
154
173
 
155
174
  return {key: value for key, value in self.__dict__[dct_name].items() if key not in exclude_fields}
156
175
 
157
- def to_dict(self, *, exclude_fields=None):
176
+ def to_dict(self, *, exclude_fields=None, full=False):
158
177
  exclude_fields = exclude_fields or set()
159
- return self._to_dict("model_as_dict", exclude_fields={"api"} | exclude_fields, full=False)
178
+ return self._to_dict("model_as_dict", exclude_fields={"api"} | exclude_fields, full=full)
160
179
 
161
180
  def to_full_dict(self, *, exclude_fields=None):
162
- exclude_fields = exclude_fields or set()
163
- return self._to_dict("model_as_full_dict", exclude_fields={"api"} | exclude_fields, full=True)
164
-
165
-
166
- def compose_method_params(
167
- params: dict[str, typing.Any],
168
- update: Cute,
169
- *,
170
- default_params: set[str | tuple[str, str]] | None = None,
171
- validators: dict[str, typing.Callable[[Cute], bool]] | None = None,
172
- ) -> dict[str, typing.Any]:
173
- """Compose method `params` from `update` by `default_params` and `validators`.
174
-
175
- :param params: Method params.
176
- :param update: Update object.
177
- :param default_params: Default params. \
178
- (`str`) - Attribute name to be get from `update` if param is undefined. \
179
- (`tuple[str, str]`): tuple[0] - Parameter name to be set in `params`, \
180
- tuple[1] - attribute name to be get from `update`.
181
- :param validators: Validators mapping (`str, Callable`), key - `Parameter name` \
182
- for which the validator will be applied, value - `Validator`, if returned `True` \
183
- parameter will be set, otherwise will not.
184
- :return: Composed params.
185
- """
186
-
187
- default_params = default_params or set()
188
- validators = validators or {}
189
-
190
- for param in default_params:
191
- param_name = param if isinstance(param, str) else param[0]
192
- if param_name not in params:
193
- if param_name in validators and not validators[param_name](update):
194
- continue
195
- params[param_name] = getattr(update, param if isinstance(param, str) else param[1])
196
-
197
- return params
198
-
199
-
200
- def shortcut(
201
- method_name: str,
202
- *,
203
- executor: Executor[Cute] | None = None,
204
- custom_params: set[str] | None = None,
205
- ):
206
- def wrapper(func: F) -> F:
207
- @wraps(func)
208
- async def inner(
209
- self: Cute,
210
- *args: typing.Any,
211
- **kwargs: typing.Any,
212
- ) -> typing.Any:
213
- if executor is None:
214
- return await func(self, *args, **kwargs)
215
-
216
- if not hasattr(func, "_signature_params"):
217
- setattr(
218
- func,
219
- "_signature_params",
220
- {k: p for k, p in inspect.signature(func).parameters.items() if k != "self"},
221
- )
222
-
223
- signature_params: dict[str, inspect.Parameter] = getattr(func, "_signature_params")
224
- params: dict[str, typing.Any] = {}
225
- index = 0
226
-
227
- for k, p in signature_params.items():
228
- if p.kind in (p.POSITIONAL_OR_KEYWORD, p.POSITIONAL_ONLY) and len(args) > index:
229
- params[k] = args[index]
230
- index += 1
231
- continue
232
-
233
- if p.kind in (p.VAR_KEYWORD, p.VAR_POSITIONAL):
234
- params[k] = kwargs.copy() if p.kind is p.VAR_KEYWORD else args[index:]
235
- continue
236
-
237
- params[k] = kwargs.pop(k, p.default) if p.default is not p.empty else kwargs.pop(k)
238
-
239
- return await executor(self, method_name, get_params(params))
240
-
241
- inner.__shortcut__ = Shortcut( # type: ignore
242
- method_name=method_name,
243
- executor=executor,
244
- custom_params=custom_params or set(),
245
- )
246
- return inner # type: ignore
247
-
248
- return wrapper
249
-
250
-
251
- @dataclasses.dataclass(slots=True, frozen=True)
252
- class Shortcut:
253
- method_name: str
254
- executor: Executor | None = dataclasses.field(default=None, kw_only=True)
255
- custom_params: set[str] = dataclasses.field(default_factory=lambda: set(), kw_only=True)
181
+ return self.to_dict(exclude_fields=exclude_fields, full=True)
256
182
 
257
183
 
258
- __all__ = ("BaseCute", "Shortcut", "compose_method_params", "shortcut")
184
+ __all__ = ("BaseCute", "compose_method_params")
@@ -1,3 +1,4 @@
1
+ import base64
1
2
  import typing
2
3
  from contextlib import suppress
3
4
 
@@ -5,10 +6,11 @@ import msgspec
5
6
  from fntypes.co import Nothing, Result, Some, Variative, unwrapping
6
7
 
7
8
  from telegrinder.api import API, APIError
8
- from telegrinder.bot.cute_types.base import BaseCute, compose_method_params, shortcut
9
+ from telegrinder.bot.cute_types.base import BaseCute, compose_method_params
9
10
  from telegrinder.bot.cute_types.message import MediaType, MessageCute, ReplyMarkup, execute_method_edit
10
- from telegrinder.model import From, field, get_params
11
+ from telegrinder.model import UNSET, From, field, get_params
11
12
  from telegrinder.msgspec_utils import Option, decoder
13
+ from telegrinder.tools.magic import shortcut
12
14
  from telegrinder.types.objects import *
13
15
 
14
16
 
@@ -16,7 +18,7 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
16
18
  api: API
17
19
 
18
20
  message: Option[Variative[MessageCute, InaccessibleMessage]] = field(
19
- default=Nothing(),
21
+ default=UNSET,
20
22
  converter=From[MessageCute | InaccessibleMessage | None],
21
23
  )
22
24
  """Optional. Message sent by the bot with the callback button that originated
@@ -29,16 +31,16 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
29
31
  @property
30
32
  def chat_id(self) -> Option[int]:
31
33
  """Optional. Message from chat ID. This will be present if the message is sent
32
- by the bot with the callback button that originated the query."""
33
-
34
+ by the bot with the callback button that originated the query.
35
+ """
34
36
  return self.message.map(lambda m: m.v.chat.id)
35
37
 
36
38
  @property
37
39
  def is_topic_message(self) -> Option[bool]:
38
40
  """Optional. True, if the message is a topic message with a name,
39
41
  color and icon. This will be present if the message is sent
40
- by the bot with the callback button that originated the query."""
41
-
42
+ by the bot with the callback button that originated the query.
43
+ """
42
44
  return self.message.map(
43
45
  lambda m: m.only().map(lambda m: m.is_topic_message.unwrap_or(False)).unwrap_or(False),
44
46
  )
@@ -48,8 +50,8 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
48
50
  def message_thread_id(self) -> Option[int]:
49
51
  """Optional. Unique identifier of the target message thread (for forum supergroups only).
50
52
  This will be present if the message is sent
51
- by the bot with the callback button that originated the query."""
52
-
53
+ by the bot with the callback button that originated the query.
54
+ """
53
55
  return self.message.unwrap().only().map(lambda m: m.message_thread_id.unwrap()).cast(Some, Nothing)
54
56
 
55
57
  @property
@@ -57,7 +59,6 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
57
59
  """Optional. Unique message identifier inside this chat. This will be present
58
60
  if the message is sent by the bot with the callback button that originated the query.
59
61
  """
60
-
61
62
  return self.message.map(lambda m: m.v.message_id)
62
63
 
63
64
  @property
@@ -65,26 +66,43 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
65
66
  """Optional. Chat the callback query originated from. This will be present
66
67
  if the message is sent by the bot with the callback button that originated the query.
67
68
  """
68
-
69
69
  return self.message.map(lambda m: m.v.chat)
70
70
 
71
- def decode_callback_data(self) -> Option[dict[str, typing.Any]]:
71
+ @typing.overload
72
+ def decode_data(self) -> Option[dict[str, typing.Any]]: ...
73
+
74
+ @typing.overload
75
+ def decode_data[T](self, *, to: type[T]) -> Option[T]: ...
76
+
77
+ def decode_data[T](self, *, to: type[T] = dict[str, typing.Any]) -> Option[T]:
78
+ if not self.data:
79
+ return Nothing()
80
+
72
81
  if "cached_callback_data" in self.__dict__:
73
82
  return self.__dict__["cached_callback_data"]
83
+
74
84
  data = Nothing()
75
85
  with suppress(msgspec.ValidationError, msgspec.DecodeError):
76
- data = Some(decoder.decode(self.data.unwrap()))
86
+ data = (
87
+ Some(decoder.decode(self.data.unwrap(), type=to))
88
+ if not issubclass(to, str | bytes)
89
+ else self.data
90
+ if issubclass(to, str)
91
+ else Some(base64.urlsafe_b64decode(self.data.unwrap()))
92
+ )
93
+
77
94
  self.__dict__["cached_callback_data"] = data
78
- return data
95
+ return data # type: ignore
79
96
 
80
97
  @shortcut("answer_callback_query", custom_params={"callback_query_id"})
81
98
  async def answer(
82
99
  self,
83
100
  text: str | None = None,
101
+ *,
102
+ cache_time: int | None = None,
84
103
  callback_query_id: str | None = None,
85
104
  show_alert: bool | None = None,
86
105
  url: str | None = None,
87
- cache_time: int | None = None,
88
106
  **other: typing.Any,
89
107
  ) -> Result[bool, APIError]:
90
108
  """Shortcut `API.answer_callback_query()`, see the [documentation](https://core.telegram.org/bots/api#answercallbackquery)
@@ -92,16 +110,12 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
92
110
  Use this method to send answers to callback queries sent from inline keyboards.
93
111
  The answer will be displayed to the user as a notification at the top of the
94
112
  chat screen or as an alert. On success, True is returned."""
95
-
96
- params = compose_method_params(
97
- get_params(locals()), self, default_params={("callback_query_id", "id")}
98
- )
113
+ params = compose_method_params(get_params(locals()), self, default_params={("callback_query_id", "id")})
99
114
  return await self.ctx_api.answer_callback_query(**params)
100
115
 
101
116
  @shortcut(
102
117
  "copy_message",
103
118
  custom_params={
104
- "reply_parameters",
105
119
  "message_thread_id",
106
120
  "chat_id",
107
121
  "message_id",
@@ -112,17 +126,20 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
112
126
  async def copy(
113
127
  self,
114
128
  chat_id: int | str | None = None,
129
+ *,
130
+ allow_paid_broadcast: bool | None = None,
131
+ caption: str | None = None,
132
+ caption_entities: list[MessageEntity] | None = None,
133
+ disable_notification: bool | None = None,
115
134
  from_chat_id: int | str | None = None,
116
135
  message_id: int | None = None,
117
136
  message_thread_id: int | None = None,
118
- caption: str | None = None,
119
137
  parse_mode: str | None = None,
120
- caption_entities: list[MessageEntity] | None = None,
121
- disable_notification: bool | None = None,
122
138
  protect_content: bool | None = None,
123
- reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
124
139
  reply_markup: ReplyMarkup | None = None,
140
+ reply_parameters: ReplyParameters | None = None,
125
141
  show_caption_above_media: bool | None = None,
142
+ video_start_timestamp: int | None = None,
126
143
  **other: typing.Any,
127
144
  ) -> Result[MessageId, APIError]:
128
145
  """Shortcut `API.copy_message()`, see the [documentation](https://core.telegram.org/bots/api#copymessage)
@@ -133,12 +150,12 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
133
150
  field correct_option_id is known to the bot. The method is analogous to
134
151
  the method forwardMessage, but the copied message doesn't have a link to
135
152
  the original message. Returns the MessageId of the sent message on success."""
136
-
137
153
  return await MessageCute.copy(self, **get_params(locals())) # type: ignore
138
154
 
139
155
  @shortcut("delete_message", custom_params={"message_thread_id", "chat_id", "message_id"})
140
156
  async def delete(
141
157
  self,
158
+ *,
142
159
  chat_id: int | None = None,
143
160
  message_id: int | None = None,
144
161
  message_thread_id: int | None = None,
@@ -157,26 +174,26 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
157
174
  of a group, it can delete any message there. - If the bot has can_delete_messages
158
175
  permission in a supergroup or a channel, it can delete any message there.
159
176
  Returns True on success."""
160
-
161
177
  return await MessageCute.delete(self, **get_params(locals())) # type: ignore
162
178
 
163
179
  @shortcut(
164
180
  "edit_message_text",
165
181
  executor=execute_method_edit,
166
- custom_params={"message_thread_id", "link_preview_options"},
182
+ custom_params={"message_thread_id"},
167
183
  )
168
184
  async def edit_text(
169
185
  self,
170
186
  text: str,
171
- inline_message_id: str | None = None,
187
+ *,
188
+ business_connection_id: str | None = None,
172
189
  chat_id: int | str | None = None,
190
+ entities: list[MessageEntity] | None = None,
191
+ inline_message_id: str | None = None,
192
+ link_preview_options: LinkPreviewOptions | None = None,
173
193
  message_id: int | None = None,
174
194
  message_thread_id: int | None = None,
175
195
  parse_mode: str | None = None,
176
- entities: list[MessageEntity] | None = None,
177
- link_preview_options: LinkPreviewOptions | dict[str, typing.Any] | None = None,
178
196
  reply_markup: InlineKeyboardMarkup | None = None,
179
- business_connection_id: str | None = None,
180
197
  **other: typing.Any,
181
198
  ) -> Result[Variative[MessageCute, bool], APIError]:
182
199
  """Shortcut `API.edit_message_text()`, see the [documentation](https://core.telegram.org/bots/api#editmessagetext)
@@ -202,7 +219,6 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
202
219
  :param link_preview_options: Link preview generation options for the message.
203
220
 
204
221
  :param reply_markup: A JSON-serialized object for an inline keyboard."""
205
-
206
222
  ...
207
223
 
208
224
  @shortcut(
@@ -212,18 +228,18 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
212
228
  )
213
229
  async def edit_live_location(
214
230
  self,
215
- latitude: float,
231
+ *,
216
232
  longitude: float,
217
- inline_message_id: str | None = None,
218
- message_thread_id: int | None = None,
233
+ business_connection_id: str | None = None,
219
234
  chat_id: int | str | None = None,
220
- message_id: int | None = None,
221
- horizontal_accuracy: float | None = None,
222
235
  heading: int | None = None,
236
+ horizontal_accuracy: float | None = None,
237
+ inline_message_id: str | None = None,
238
+ live_period: int | None = None,
239
+ message_id: int | None = None,
240
+ message_thread_id: int | None = None,
223
241
  proximity_alert_radius: int | None = None,
224
242
  reply_markup: InlineKeyboardMarkup | None = None,
225
- business_connection_id: str | None = None,
226
- live_period: int | None = None,
227
243
  **other: typing.Any,
228
244
  ) -> Result[Variative[MessageCute, bool], APIError]:
229
245
  """Shortcut `API.edit_message_live_location()`, see the [documentation](https://core.telegram.org/bots/api#editmessagelivelocation)
@@ -251,7 +267,6 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
251
267
  :param proximity_alert_radius: The maximum distance for proximity alerts about approaching another chatmember, in meters. Must be between 1 and 100000 if specified.
252
268
 
253
269
  :param reply_markup: A JSON-serialized object for a new inline keyboard."""
254
-
255
270
  ...
256
271
 
257
272
  @shortcut(
@@ -262,14 +277,15 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
262
277
  async def edit_caption(
263
278
  self,
264
279
  caption: str | None = None,
280
+ *,
281
+ business_connection_id: str | None = None,
282
+ caption_entities: list[MessageEntity] | None = None,
265
283
  chat_id: int | str | None = None,
284
+ inline_message_id: str | None = None,
266
285
  message_id: int | None = None,
267
286
  message_thread_id: int | None = None,
268
- inline_message_id: str | None = None,
269
287
  parse_mode: str | None = None,
270
- caption_entities: list[MessageEntity] | None = None,
271
288
  reply_markup: InlineKeyboardMarkup | None = None,
272
- business_connection_id: str | None = None,
273
289
  show_caption_above_media: bool | None = None,
274
290
  **other: typing.Any,
275
291
  ) -> Result[Variative[MessageCute, bool], APIError]:
@@ -295,7 +311,6 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
295
311
  :param show_caption_above_media: Pass True, if the caption must be shown above the message media. Supportedonly for animation, photo and video messages.
296
312
 
297
313
  :param reply_markup: A JSON-serialized object for an inline keyboard."""
298
-
299
314
  ...
300
315
 
301
316
  @shortcut(
@@ -312,29 +327,30 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
312
327
  async def edit_media(
313
328
  self,
314
329
  media: str | InputFile | InputMedia,
315
- type: MediaType | None = None,
330
+ *,
331
+ business_connection_id: str | None = None,
316
332
  caption: str | None = None,
317
- parse_mode: str | None = None,
318
333
  caption_entities: list[MessageEntity] | None = None,
319
- inline_message_id: str | None = None,
320
334
  chat_id: int | str | None = None,
335
+ inline_message_id: str | None = None,
321
336
  message_id: int | None = None,
322
337
  message_thread_id: int | None = None,
338
+ parse_mode: str | None = None,
323
339
  reply_markup: InlineKeyboardMarkup | None = None,
324
- business_connection_id: str | None = None,
340
+ type: MediaType | None = None,
325
341
  **other: typing.Any,
326
342
  ) -> Result[Variative[MessageCute, bool], APIError]:
327
343
  """Shortcut `API.edit_message_media()`, see the [documentation](https://core.telegram.org/bots/api#editmessagemedia)
328
344
 
329
- Use this method to edit animation, audio, document, photo, or video messages.
330
- If a message is part of a message album, then it can be edited only to an audio
331
- for audio albums, only to a document for document albums and to a photo or
332
- a video otherwise. When an inline message is edited, a new file can't be uploaded;
333
- use a previously uploaded file via its file_id or specify a URL. On success,
334
- if the edited message is not an inline message, the edited Message is returned,
335
- otherwise True is returned. Note that business messages that were not sent
336
- by the bot and do not contain an inline keyboard can only be edited within
337
- 48 hours from the time they were sent.
345
+ Use this method to edit animation, audio, document, photo, or video messages,
346
+ or to add media to text messages. If a message is part of a message album, then
347
+ it can be edited only to an audio for audio albums, only to a document for document
348
+ albums and to a photo or a video otherwise. When an inline message is edited,
349
+ a new file can't be uploaded; use a previously uploaded file via its file_id
350
+ or specify a URL. On success, if the edited message is not an inline message,
351
+ the edited Message is returned, otherwise True is returned. Note that business
352
+ messages that were not sent by the bot and do not contain an inline keyboard
353
+ can only be edited within 48 hours from the time they were sent.
338
354
  :param business_connection_id: Unique identifier of the business connection on behalf of which the messageto be edited was sent.
339
355
 
340
356
  :param chat_id: Required if inline_message_id is not specified. Unique identifier forthe target chat or username of the target channel (in the format @channelusername).
@@ -345,7 +361,6 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
345
361
  :param media: A JSON-serialized object for a new media content of the message.
346
362
 
347
363
  :param reply_markup: A JSON-serialized object for a new inline keyboard."""
348
-
349
364
  return await MessageCute.edit_media(self, **get_params(locals())) # type: ignore
350
365
 
351
366
  @shortcut(
@@ -355,12 +370,13 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
355
370
  )
356
371
  async def edit_reply_markup(
357
372
  self,
373
+ *,
374
+ business_connection_id: str | None = None,
375
+ chat_id: int | str | None = None,
358
376
  inline_message_id: str | None = None,
359
377
  message_id: int | None = None,
360
378
  message_thread_id: int | None = None,
361
- chat_id: int | str | None = None,
362
379
  reply_markup: InlineKeyboardMarkup | None = None,
363
- business_connection_id: str | None = None,
364
380
  **other: typing.Any,
365
381
  ) -> Result[Variative[MessageCute, bool], APIError]:
366
382
  """Shortcut `API.edit_message_reply_markup()`, see the [documentation](https://core.telegram.org/bots/api#editmessagereplymarkup)
@@ -378,7 +394,6 @@ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True):
378
394
  :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of theinline message.
379
395
 
380
396
  :param reply_markup: A JSON-serialized object for an inline keyboard."""
381
-
382
397
  ...
383
398
 
384
399
 
@@ -3,8 +3,9 @@ import typing
3
3
  from fntypes.result import Result
4
4
 
5
5
  from telegrinder.api.api import API, APIError
6
- from telegrinder.bot.cute_types.base import BaseCute, shortcut
6
+ from telegrinder.bot.cute_types.base import BaseCute
7
7
  from telegrinder.bot.cute_types.chat_member_updated import ChatMemberShortcuts, chat_member_interaction
8
+ from telegrinder.tools.magic import shortcut
8
9
  from telegrinder.types.objects import *
9
10
 
10
11
 
@@ -26,6 +27,7 @@ class ChatJoinRequestCute(BaseCute[ChatJoinRequest], ChatJoinRequest, ChatMember
26
27
  )
27
28
  async def approve(
28
29
  self,
30
+ *,
29
31
  chat_id: int | str | None = None,
30
32
  user_id: int | None = None,
31
33
  **other: typing.Any,
@@ -35,7 +37,6 @@ class ChatJoinRequestCute(BaseCute[ChatJoinRequest], ChatJoinRequest, ChatMember
35
37
  Use this method to approve a chat join request. The bot must be an administrator
36
38
  in the chat for this to work and must have the can_invite_users administrator
37
39
  right. Returns True on success."""
38
-
39
40
  ...
40
41
 
41
42
  @shortcut(
@@ -45,6 +46,7 @@ class ChatJoinRequestCute(BaseCute[ChatJoinRequest], ChatJoinRequest, ChatMember
45
46
  )
46
47
  async def decline(
47
48
  self,
49
+ *,
48
50
  chat_id: int | str | None = None,
49
51
  user_id: int | None = None,
50
52
  **other: typing.Any,
@@ -54,7 +56,6 @@ class ChatJoinRequestCute(BaseCute[ChatJoinRequest], ChatJoinRequest, ChatMember
54
56
  Use this method to decline a chat join request. The bot must be an administrator
55
57
  in the chat for this to work and must have the can_invite_users administrator
56
58
  right. Returns True on success."""
57
-
58
59
  ...
59
60
 
60
61