telegrinder 0.1.dev169__py3-none-any.whl → 0.1.dev171__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 (79) hide show
  1. telegrinder/api/abc.py +7 -1
  2. telegrinder/api/api.py +12 -3
  3. telegrinder/api/error.py +2 -1
  4. telegrinder/bot/bot.py +6 -1
  5. telegrinder/bot/cute_types/base.py +144 -17
  6. telegrinder/bot/cute_types/callback_query.py +6 -1
  7. telegrinder/bot/cute_types/chat_member_updated.py +1 -2
  8. telegrinder/bot/cute_types/message.py +23 -11
  9. telegrinder/bot/cute_types/update.py +48 -0
  10. telegrinder/bot/cute_types/utils.py +2 -465
  11. telegrinder/bot/dispatch/__init__.py +2 -3
  12. telegrinder/bot/dispatch/abc.py +6 -3
  13. telegrinder/bot/dispatch/context.py +6 -6
  14. telegrinder/bot/dispatch/dispatch.py +61 -23
  15. telegrinder/bot/dispatch/handler/abc.py +2 -2
  16. telegrinder/bot/dispatch/handler/func.py +36 -17
  17. telegrinder/bot/dispatch/handler/message_reply.py +2 -2
  18. telegrinder/bot/dispatch/middleware/abc.py +2 -2
  19. telegrinder/bot/dispatch/process.py +10 -10
  20. telegrinder/bot/dispatch/return_manager/abc.py +3 -3
  21. telegrinder/bot/dispatch/view/abc.py +12 -15
  22. telegrinder/bot/dispatch/view/box.py +73 -62
  23. telegrinder/bot/dispatch/view/message.py +11 -3
  24. telegrinder/bot/dispatch/view/raw.py +3 -0
  25. telegrinder/bot/dispatch/waiter_machine/machine.py +2 -2
  26. telegrinder/bot/dispatch/waiter_machine/middleware.py +1 -1
  27. telegrinder/bot/dispatch/waiter_machine/short_state.py +2 -1
  28. telegrinder/bot/polling/polling.py +3 -3
  29. telegrinder/bot/rules/abc.py +11 -7
  30. telegrinder/bot/rules/adapter/event.py +7 -4
  31. telegrinder/bot/rules/adapter/node.py +1 -1
  32. telegrinder/bot/rules/command.py +5 -7
  33. telegrinder/bot/rules/func.py +1 -1
  34. telegrinder/bot/rules/fuzzy.py +1 -1
  35. telegrinder/bot/rules/integer.py +1 -2
  36. telegrinder/bot/rules/markup.py +3 -3
  37. telegrinder/bot/rules/message_entities.py +1 -1
  38. telegrinder/bot/rules/node.py +2 -2
  39. telegrinder/bot/rules/regex.py +1 -1
  40. telegrinder/bot/rules/rule_enum.py +1 -1
  41. telegrinder/bot/scenario/checkbox.py +2 -2
  42. telegrinder/model.py +87 -47
  43. telegrinder/modules.py +3 -3
  44. telegrinder/msgspec_utils.py +94 -13
  45. telegrinder/node/__init__.py +20 -11
  46. telegrinder/node/attachment.py +19 -16
  47. telegrinder/node/base.py +120 -24
  48. telegrinder/node/callback_query.py +5 -9
  49. telegrinder/node/command.py +6 -2
  50. telegrinder/node/composer.py +82 -54
  51. telegrinder/node/container.py +4 -4
  52. telegrinder/node/event.py +59 -0
  53. telegrinder/node/me.py +3 -0
  54. telegrinder/node/message.py +6 -10
  55. telegrinder/node/polymorphic.py +11 -12
  56. telegrinder/node/rule.py +27 -5
  57. telegrinder/node/source.py +10 -11
  58. telegrinder/node/text.py +4 -4
  59. telegrinder/node/update.py +1 -2
  60. telegrinder/py.typed +0 -0
  61. telegrinder/tools/__init__.py +2 -2
  62. telegrinder/tools/buttons.py +5 -10
  63. telegrinder/tools/error_handler/error.py +2 -0
  64. telegrinder/tools/error_handler/error_handler.py +1 -1
  65. telegrinder/tools/formatting/spec_html_formats.py +10 -10
  66. telegrinder/tools/global_context/__init__.py +2 -2
  67. telegrinder/tools/global_context/global_context.py +2 -2
  68. telegrinder/tools/global_context/telegrinder_ctx.py +4 -4
  69. telegrinder/tools/keyboard.py +2 -2
  70. telegrinder/tools/loop_wrapper/loop_wrapper.py +39 -5
  71. telegrinder/tools/magic.py +48 -15
  72. telegrinder/types/enums.py +1 -0
  73. telegrinder/types/methods.py +14 -5
  74. telegrinder/types/objects.py +3 -0
  75. {telegrinder-0.1.dev169.dist-info → telegrinder-0.1.dev171.dist-info}/METADATA +2 -2
  76. telegrinder-0.1.dev171.dist-info/RECORD +145 -0
  77. telegrinder-0.1.dev169.dist-info/RECORD +0 -143
  78. {telegrinder-0.1.dev169.dist-info → telegrinder-0.1.dev171.dist-info}/LICENSE +0 -0
  79. {telegrinder-0.1.dev169.dist-info → telegrinder-0.1.dev171.dist-info}/WHEEL +0 -0
telegrinder/api/abc.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import pathlib
2
2
  import typing
3
3
  from abc import ABC, abstractmethod
4
+ from functools import cached_property
4
5
 
5
6
  import msgspec
6
7
  from envparse import env
@@ -33,7 +34,7 @@ class Token(str):
33
34
  env.read_envfile(path_to_envfile)
34
35
  return cls(env.str(var_name))
35
36
 
36
- @property
37
+ @cached_property
37
38
  def bot_id(self) -> int:
38
39
  return int(self.split(":")[0])
39
40
 
@@ -64,6 +65,11 @@ class ABCAPI(ABC):
64
65
  def request_url(self) -> str:
65
66
  pass
66
67
 
68
+ @property
69
+ @abstractmethod
70
+ def request_file_url(self) -> str:
71
+ pass
72
+
67
73
  @property
68
74
  @abstractmethod
69
75
  def id(self) -> int:
telegrinder/api/api.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import typing
2
+ from functools import cached_property
2
3
 
3
4
  import msgspec
4
5
  from fntypes.result import Error, Ok, Result
@@ -16,7 +17,7 @@ def compose_data(
16
17
  data: dict[str, typing.Any],
17
18
  files: dict[str, tuple[str, bytes]],
18
19
  ) -> typing.Any:
19
- converter = DataConverter(files=files.copy())
20
+ converter = DataConverter(_files=files.copy())
20
21
  return client.get_form(
21
22
  data={k: converter(v) for k, v in data.items()},
22
23
  files=converter.files,
@@ -24,9 +25,10 @@ def compose_data(
24
25
 
25
26
 
26
27
  class API(ABCAPI, APIMethods):
27
- """Bot API with available API methods."""
28
+ """Bot API with available API methods and http client."""
28
29
 
29
30
  API_URL = "https://api.telegram.org/"
31
+ API_FILE_URL = "https://api.telegram.org/file/"
30
32
 
31
33
  def __init__(self, token: Token, *, http: ABCClient | None = None) -> None:
32
34
  self.token = token
@@ -40,7 +42,7 @@ class API(ABCAPI, APIMethods):
40
42
  self.http,
41
43
  )
42
44
 
43
- @property
45
+ @cached_property
44
46
  def id(self) -> int:
45
47
  return self.token.bot_id
46
48
 
@@ -48,6 +50,13 @@ class API(ABCAPI, APIMethods):
48
50
  def request_url(self) -> str:
49
51
  return self.API_URL + f"bot{self.token}/"
50
52
 
53
+ @property
54
+ def request_file_url(self) -> str:
55
+ return self.API_FILE_URL + f"bot{self.token}/"
56
+
57
+ async def download_file(self, file_path: str) -> bytes:
58
+ return await self.http.request_content(f"{self.request_file_url}/{file_path}")
59
+
51
60
  async def request(
52
61
  self,
53
62
  method: str,
telegrinder/api/error.py CHANGED
@@ -9,7 +9,8 @@ class APIError(BaseException):
9
9
  return f"<APIError: {self.__str__()}>"
10
10
 
11
11
 
12
- class InvalidTokenError(BaseException): ...
12
+ class InvalidTokenError(BaseException):
13
+ pass
13
14
 
14
15
 
15
16
  __all__ = ("APIError", "InvalidTokenError")
telegrinder/bot/bot.py CHANGED
@@ -43,7 +43,12 @@ class Telegrinder(typing.Generic[DispatchT, PollingT, LoopWrapperT]):
43
43
  return
44
44
  await self.api.delete_webhook()
45
45
 
46
- async def run_polling(self, *, offset: int = 0, skip_updates: bool = False) -> None:
46
+ async def run_polling(
47
+ self,
48
+ *,
49
+ offset: int = 0,
50
+ skip_updates: bool = False,
51
+ ) -> None:
47
52
  if skip_updates:
48
53
  logger.debug("Dropping pending updates")
49
54
  await self.reset_webhook()
@@ -3,6 +3,7 @@ import inspect
3
3
  import typing
4
4
  from functools import wraps
5
5
 
6
+ import typing_extensions
6
7
  from fntypes.result import Result
7
8
 
8
9
  from telegrinder.api import ABCAPI, API
@@ -10,7 +11,8 @@ from telegrinder.model import Model, get_params
10
11
 
11
12
  F = typing.TypeVar("F", bound=typing.Callable[..., typing.Any])
12
13
  Cute = typing.TypeVar("Cute", bound="BaseCute")
13
- Update = typing.TypeVar("Update", bound=Model)
14
+ Update = typing_extensions.TypeVar("Update", bound=Model)
15
+ CtxAPI = typing_extensions.TypeVar("CtxAPI", bound=ABCAPI, default=API)
14
16
 
15
17
  Executor: typing.TypeAlias = typing.Callable[
16
18
  [Cute, str, dict[str, typing.Any]],
@@ -19,31 +21,149 @@ Executor: typing.TypeAlias = typing.Callable[
19
21
 
20
22
  if typing.TYPE_CHECKING:
21
23
 
22
- class BaseCute(Model, typing.Generic[Update]):
24
+ class BaseCute(Model, typing.Generic[Update, CtxAPI]):
23
25
  api: ABCAPI
24
26
 
25
27
  @classmethod
26
28
  def from_update(cls, update: Update, bound_api: ABCAPI) -> typing.Self: ...
27
29
 
28
30
  @property
29
- def ctx_api(self) -> API: ...
31
+ def ctx_api(self) -> CtxAPI: ...
32
+
33
+ def to_dict(
34
+ self,
35
+ *,
36
+ exclude_fields: set[str] | None = None,
37
+ ) -> dict[str, typing.Any]:
38
+ """
39
+ :param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
40
+ :return: A dictionary representation of this cute model.
41
+ """
42
+
43
+ ...
44
+
45
+ def to_full_dict(
46
+ self,
47
+ *,
48
+ exclude_fields: set[str] | None = None,
49
+ ) -> dict[str, typing.Any]:
50
+ """
51
+ :param exclude_fields: Cute model field names to exclude from the dictionary representation of this cute model.
52
+ :return: A dictionary representation of this model including all models, structs, custom types.
53
+ """
54
+
55
+ ...
30
56
 
31
57
  else:
58
+ from fntypes.co import Nothing, Some, Variative
59
+ from msgspec._utils import get_class_annotations as _get_class_annotations
60
+
61
+ from telegrinder.msgspec_utils import Option, decoder
62
+
63
+ _DEFAULT_API_CLASS = API
64
+
65
+ def _get_ctx_api_class(cute_class):
66
+ if hasattr(cute_class, "__ctx_api_class__"):
67
+ return cute_class.__ctx_api_class__
68
+
69
+ ctx_api_class = _DEFAULT_API_CLASS
70
+ for base in cute_class.__dict__.get("__orig_bases__", ()):
71
+ if ctx_api_class is not _DEFAULT_API_CLASS:
72
+ break
73
+ if issubclass(typing.get_origin(base) or base, BaseCute):
74
+ for generic_type in typing.get_args(base):
75
+ if issubclass(typing.get_origin(generic_type) or generic_type, ABCAPI):
76
+ ctx_api_class = generic_type
77
+ break
78
+
79
+ cute_class.__ctx_api_class__ = ctx_api_class
80
+ return ctx_api_class
81
+
82
+ def _get_cute_from_args(args):
83
+ for hint in args:
84
+ if not isinstance(hint, type):
85
+ hint = typing.get_origin(hint) or hint
86
+ if not isinstance(hint, type):
87
+ continue
88
+
89
+ if hint is Variative or issubclass(hint, Option) or issubclass(hint, Some | Nothing):
90
+ return _get_cute_from_args(typing.get_args(hint))
91
+
92
+ if issubclass(hint, BaseCute):
93
+ return hint
94
+
95
+ return None
96
+
97
+ def _get_cute_annotations(annotations):
98
+ cute_annotations = {}
99
+
100
+ for key, hint in annotations.items():
101
+ if not isinstance(hint, type):
102
+ if (val := _get_cute_from_args(typing.get_args(hint))) is not None:
103
+ cute_annotations[key] = val
104
+
105
+ elif issubclass(hint, BaseCute):
106
+ cute_annotations[key] = hint
107
+
108
+ return cute_annotations
109
+
110
+ def _get_value(value):
111
+ while isinstance(value, Variative | Some):
112
+ if isinstance(value, Variative):
113
+ value = value.v
114
+ if isinstance(value, Some):
115
+ value = value.value
116
+ return value
117
+
118
+ class BaseCute(typing.Generic[Update, CtxAPI]):
119
+ def __init_subclass__(cls, *args, **kwargs):
120
+ super().__init_subclass__(*args, **kwargs)
121
+
122
+ if not cls.__bases__ or not issubclass(cls.__bases__[0], BaseCute):
123
+ return
124
+
125
+ cls.__is_solved_annotations__ = False
126
+ cls.__cute_annotations__ = None
32
127
 
33
- class BaseCute(typing.Generic[Update]):
34
128
  @classmethod
35
129
  def from_update(cls, update, bound_api):
36
- return cls(**update.to_dict(), api=bound_api)
130
+ if not cls.__is_solved_annotations__:
131
+ cls.__is_solved_annotations__ = True
132
+ cls.__annotations__ = _get_class_annotations(cls)
133
+
134
+ if cls.__cute_annotations__ is None:
135
+ cls.__cute_annotations__ = _get_cute_annotations(cls.__annotations__)
136
+
137
+ return cls(
138
+ **{
139
+ field: decoder.convert(
140
+ cls.__cute_annotations__[field].from_update(_get_value(value), bound_api=bound_api),
141
+ type=cls.__annotations__[field],
142
+ )
143
+ if field in cls.__cute_annotations__ and value
144
+ else value
145
+ for field, value in update.to_dict().items()
146
+ },
147
+ api=bound_api,
148
+ )
37
149
 
38
150
  @property
39
151
  def ctx_api(self):
40
- assert isinstance(self.api, API)
152
+ ctx_api_class = _get_ctx_api_class(self.__class__)
153
+ assert isinstance(
154
+ self.api,
155
+ ctx_api_class,
156
+ ), f"Bound API of type {self.api.__class__.__name__!r} is incompatible with {ctx_api_class.__name__!r}."
41
157
  return self.api
42
158
 
43
159
  def to_dict(self, *, exclude_fields=None):
44
160
  exclude_fields = exclude_fields or set()
45
161
  return super().to_dict(exclude_fields={"api"} | exclude_fields)
46
162
 
163
+ def to_full_dict(self, *, exclude_fields=None):
164
+ exclude_fields = exclude_fields or set()
165
+ return super().to_full_dict(exclude_fields={"api"} | exclude_fields)
166
+
47
167
 
48
168
  def compose_method_params(
49
169
  params: dict[str, typing.Any],
@@ -57,12 +177,12 @@ def compose_method_params(
57
177
  :param params: Method params.
58
178
  :param update: Update object.
59
179
  :param default_params: Default params. \
60
- type (`str`) - Attribute name to be taken from `update` if param undefined. \
61
- type (`tuple[str, str]`) - tuple[0] Parameter name to be set in `params`, \
62
- tuple[1] attribute name to be taken from `update`.
180
+ (`str`) - Attribute name to be get from `update` if param is undefined. \
181
+ (`tuple[str, str]`): tuple[0] - Parameter name to be set in `params`, \
182
+ tuple[1] - attribute name to be get from `update`.
63
183
  :param validators: Validators mapping (`str, Callable`), key - `Parameter name` \
64
184
  for which the validator will be applied, value - `Validator`, if returned `True` \
65
- parameter will be set, otherwise will not be set.
185
+ parameter will be set, otherwise will not.
66
186
  :return: Composed params.
67
187
  """
68
188
 
@@ -79,8 +199,6 @@ def compose_method_params(
79
199
  return params
80
200
 
81
201
 
82
- # NOTE: implement parser on ast for methods decorated this decorator
83
- # to support updates to the schema Bot API.
84
202
  def shortcut(
85
203
  method_name: str,
86
204
  *,
@@ -96,7 +214,15 @@ def shortcut(
96
214
  ) -> typing.Any:
97
215
  if executor is None:
98
216
  return await func(self, *args, **kwargs)
99
- signature_params = {k: p for k, p in inspect.signature(func).parameters.items() if k != "self"}
217
+
218
+ if not hasattr(func, "_signature_params"):
219
+ setattr(
220
+ func,
221
+ "_signature_params",
222
+ {k: p for k, p in inspect.signature(func).parameters.items() if k != "self"},
223
+ )
224
+
225
+ signature_params: dict[str, inspect.Parameter] = getattr(func, "_signature_params")
100
226
  params: dict[str, typing.Any] = {}
101
227
  index = 0
102
228
 
@@ -105,9 +231,11 @@ def shortcut(
105
231
  params[k] = args[index]
106
232
  index += 1
107
233
  continue
234
+
108
235
  if p.kind in (p.VAR_KEYWORD, p.VAR_POSITIONAL):
109
236
  params[k] = kwargs.copy() if p.kind is p.VAR_KEYWORD else args[index:]
110
237
  continue
238
+
111
239
  params[k] = kwargs.pop(k, p.default) if p.default is not p.empty else kwargs.pop(k)
112
240
 
113
241
  return await executor(self, method_name, get_params(params))
@@ -122,12 +250,11 @@ def shortcut(
122
250
  return wrapper
123
251
 
124
252
 
125
- @dataclasses.dataclass
253
+ @dataclasses.dataclass(slots=True, frozen=True)
126
254
  class Shortcut:
127
255
  method_name: str
128
- _: dataclasses.KW_ONLY
129
- executor: Executor | None = dataclasses.field(default=None)
130
- custom_params: set[str] = dataclasses.field(default_factory=lambda: set())
256
+ executor: Executor | None = dataclasses.field(default=None, kw_only=True)
257
+ custom_params: set[str] = dataclasses.field(default_factory=lambda: set(), kw_only=True)
131
258
 
132
259
 
133
260
  __all__ = ("BaseCute", "Shortcut", "compose_method_params", "shortcut")
@@ -7,9 +7,10 @@ from fntypes.co import Nothing, Result, Some, Variative, unwrapping
7
7
  from telegrinder.api import ABCAPI, APIError
8
8
  from telegrinder.model import get_params
9
9
  from telegrinder.msgspec_utils import Option, decoder
10
- from telegrinder.types import (
10
+ from telegrinder.types.objects import (
11
11
  CallbackQuery,
12
12
  Chat,
13
+ InaccessibleMessage,
13
14
  InlineKeyboardMarkup,
14
15
  InputFile,
15
16
  InputMedia,
@@ -27,6 +28,10 @@ from .message import MediaType, MessageCute, ReplyMarkup, execute_method_edit
27
28
  class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True, dict=True):
28
29
  api: ABCAPI
29
30
 
31
+ message: Option[Variative[MessageCute, InaccessibleMessage]] = Nothing()
32
+ """Optional. Message sent by the bot with the callback button that originated
33
+ the query."""
34
+
30
35
  @property
31
36
  def from_user(self) -> User:
32
37
  return self.from_
@@ -8,7 +8,6 @@ from telegrinder.model import get_params
8
8
  from telegrinder.types.objects import ChatMemberUpdated, ChatPermissions, User
9
9
 
10
10
  from .base import BaseCute, compose_method_params, shortcut
11
- from .utils import compose_chat_permissions
12
11
 
13
12
 
14
13
  async def chat_member_interaction(
@@ -17,7 +16,7 @@ async def chat_member_interaction(
17
16
  params: dict[str, typing.Any],
18
17
  ) -> Result[typing.Any, APIError]:
19
18
  if isinstance(params.get("permissions"), dict):
20
- params["permissions"] = compose_chat_permissions(**params["permissions"])
19
+ params["permissions"] = ChatPermissions(**params["permissions"])
21
20
  params = compose_method_params(
22
21
  get_params(locals()),
23
22
  update,
@@ -2,15 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import typing
4
4
 
5
- from fntypes.co import Option, Result, Some, Variative
5
+ import fntypes.option
6
+ from fntypes.co import Result, Some, Variative
6
7
 
7
8
  from telegrinder.api import ABCAPI, APIError
8
9
  from telegrinder.model import get_params
9
- from telegrinder.msgspec_utils import Nothing
10
- from telegrinder.types import (
10
+ from telegrinder.msgspec_utils import Nothing, Option
11
+ from telegrinder.types.objects import (
11
12
  ChatAction,
12
13
  DiceEmoji,
13
14
  ForceReply,
15
+ InaccessibleMessage,
14
16
  InlineKeyboardMarkup,
15
17
  InputFile,
16
18
  InputMedia,
@@ -127,7 +129,7 @@ async def execute_method_edit(
127
129
 
128
130
  result = await getattr(update.ctx_api, method_name)(**params)
129
131
  return result.map(
130
- lambda v: Variative["MessageCute", bool](
132
+ lambda v: Variative[MessageCute, bool](
131
133
  v.only()
132
134
  .map(
133
135
  lambda x: MessageCute.from_update(x, bound_api=update.api),
@@ -139,9 +141,9 @@ async def execute_method_edit(
139
141
 
140
142
  def get_entity_value(
141
143
  entity_value: typing.Literal["user", "url", "custom_emoji_id", "language"],
142
- entities: Option[list[MessageEntity]] = Nothing,
143
- caption_entities: Option[list[MessageEntity]] = Nothing,
144
- ) -> Option[typing.Any]:
144
+ entities: fntypes.option.Option[list[MessageEntity]] = Nothing,
145
+ caption_entities: fntypes.option.Option[list[MessageEntity]] = Nothing,
146
+ ) -> fntypes.option.Option[typing.Any]:
145
147
  ents = entities.unwrap_or(caption_entities.unwrap_or_none())
146
148
  if not ents:
147
149
  return Nothing
@@ -154,26 +156,36 @@ def get_entity_value(
154
156
  class MessageCute(BaseCute[Message], Message, kw_only=True):
155
157
  api: ABCAPI
156
158
 
159
+ reply_to_message: Option[MessageCute] = Nothing
160
+ """Optional. For replies in the same chat and message thread, the original
161
+ message. Note that the Message object in this field will not contain further
162
+ reply_to_message fields even if it itself is a reply."""
163
+
164
+ pinned_message: Option[Variative[MessageCute, InaccessibleMessage]] = Nothing
165
+ """Optional. Specified message was pinned. Note that the Message object in
166
+ this field will not contain further reply_to_message fields even if it
167
+ itself is a reply."""
168
+
157
169
  @property
158
- def mentioned_user(self) -> Option[User]:
170
+ def mentioned_user(self) -> fntypes.option.Option[User]:
159
171
  """Mentioned user without username."""
160
172
 
161
173
  return get_entity_value("user", self.entities, self.caption_entities)
162
174
 
163
175
  @property
164
- def url(self) -> Option[str]:
176
+ def url(self) -> fntypes.option.Option[str]:
165
177
  """Clickable text URL."""
166
178
 
167
179
  return get_entity_value("url", self.entities, self.caption_entities)
168
180
 
169
181
  @property
170
- def programming_language(self) -> Option[str]:
182
+ def programming_language(self) -> fntypes.option.Option[str]:
171
183
  """The programming language of the entity text."""
172
184
 
173
185
  return get_entity_value("language", self.entities, self.caption_entities)
174
186
 
175
187
  @property
176
- def custom_emoji_id(self) -> Option[str]:
188
+ def custom_emoji_id(self) -> fntypes.option.Option[str]:
177
189
  """Unique identifier of the custom emoji."""
178
190
 
179
191
  return get_entity_value("custom_emoji_id", self.entities, self.caption_entities)
@@ -7,6 +7,11 @@ from telegrinder.msgspec_utils import Option
7
7
  from telegrinder.types import Model, Update
8
8
 
9
9
  from .base import BaseCute
10
+ from .callback_query import CallbackQueryCute
11
+ from .chat_join_request import ChatJoinRequestCute
12
+ from .chat_member_updated import ChatMemberUpdatedCute
13
+ from .inline_query import InlineQueryCute
14
+ from .message import MessageCute
10
15
 
11
16
  ModelT = typing.TypeVar("ModelT", bound=Model)
12
17
 
@@ -14,6 +19,49 @@ ModelT = typing.TypeVar("ModelT", bound=Model)
14
19
  class UpdateCute(BaseCute[Update], Update, kw_only=True):
15
20
  api: ABCAPI
16
21
 
22
+ message: Option[MessageCute] = Nothing()
23
+ """Optional. New incoming message of any kind - text, photo, sticker, etc."""
24
+
25
+ edited_message: Option[MessageCute] = Nothing()
26
+ """Optional. New version of a message that is known to the bot and was edited.
27
+ This update may at times be triggered by changes to message fields that are
28
+ either unavailable or not actively used by your bot."""
29
+
30
+ channel_post: Option[MessageCute] = Nothing()
31
+ """Optional. New incoming channel post of any kind - text, photo, sticker,
32
+ etc."""
33
+
34
+ edited_channel_post: Option[MessageCute] = Nothing()
35
+ """Optional. New version of a channel post that is known to the bot and was edited.
36
+ This update may at times be triggered by changes to message fields that are
37
+ either unavailable or not actively used by your bot."""
38
+
39
+ business_message: Option[MessageCute] = Nothing()
40
+ """Optional. New message from a connected business account."""
41
+
42
+ edited_business_message: Option[MessageCute] = Nothing()
43
+ """Optional. New version of a message from a connected business account."""
44
+
45
+ inline_query: Option[InlineQueryCute] = Nothing()
46
+ """Optional. New incoming inline query."""
47
+
48
+ callback_query: Option[CallbackQueryCute] = Nothing()
49
+ """Optional. New incoming callback query."""
50
+
51
+ my_chat_member: Option[ChatMemberUpdatedCute] = Nothing()
52
+ """Optional. The bot's chat member status was updated in a chat. For private
53
+ chats, this update is received only when the bot is blocked or unblocked
54
+ by the user."""
55
+
56
+ chat_member: Option[ChatMemberUpdatedCute] = Nothing()
57
+ """Optional. A chat member's status was updated in a chat. The bot must be an
58
+ administrator in the chat and must explicitly specify `chat_member` in
59
+ the list of allowed_updates to receive these updates."""
60
+
61
+ chat_join_request: Option[ChatJoinRequestCute] = Nothing()
62
+ """Optional. A request to join the chat has been sent. The bot must have the can_invite_users
63
+ administrator right in the chat to receive these updates."""
64
+
17
65
  @property
18
66
  def incoming_update(self) -> Model:
19
67
  return getattr(self, self.update_type.value).unwrap()