telegrinder 0.1.dev165__py3-none-any.whl → 0.1.dev166__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 (88) hide show
  1. telegrinder/__init__.py +22 -0
  2. telegrinder/api/abc.py +1 -1
  3. telegrinder/api/api.py +8 -6
  4. telegrinder/api/error.py +2 -3
  5. telegrinder/bot/__init__.py +14 -0
  6. telegrinder/bot/bot.py +5 -3
  7. telegrinder/bot/cute_types/__init__.py +4 -0
  8. telegrinder/bot/cute_types/base.py +22 -13
  9. telegrinder/bot/cute_types/chat_join_request.py +63 -0
  10. telegrinder/bot/cute_types/chat_member_updated.py +244 -0
  11. telegrinder/bot/cute_types/message.py +34 -7
  12. telegrinder/bot/cute_types/update.py +5 -4
  13. telegrinder/bot/cute_types/utils.py +39 -17
  14. telegrinder/bot/dispatch/__init__.py +9 -1
  15. telegrinder/bot/dispatch/composition.py +10 -8
  16. telegrinder/bot/dispatch/context.py +9 -10
  17. telegrinder/bot/dispatch/dispatch.py +29 -19
  18. telegrinder/bot/dispatch/handler/abc.py +1 -0
  19. telegrinder/bot/dispatch/handler/func.py +29 -6
  20. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  21. telegrinder/bot/dispatch/middleware/abc.py +2 -4
  22. telegrinder/bot/dispatch/process.py +4 -3
  23. telegrinder/bot/dispatch/return_manager/__init__.py +1 -1
  24. telegrinder/bot/dispatch/return_manager/abc.py +33 -21
  25. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -2
  26. telegrinder/bot/dispatch/return_manager/inline_query.py +4 -2
  27. telegrinder/bot/dispatch/return_manager/message.py +12 -6
  28. telegrinder/bot/dispatch/view/__init__.py +8 -2
  29. telegrinder/bot/dispatch/view/abc.py +26 -20
  30. telegrinder/bot/dispatch/view/box.py +72 -1
  31. telegrinder/bot/dispatch/view/callback_query.py +1 -3
  32. telegrinder/bot/dispatch/view/chat_join_request.py +17 -0
  33. telegrinder/bot/dispatch/view/chat_member.py +26 -0
  34. telegrinder/bot/dispatch/view/message.py +23 -1
  35. telegrinder/bot/dispatch/view/raw.py +116 -0
  36. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
  37. telegrinder/bot/dispatch/waiter_machine/machine.py +73 -19
  38. telegrinder/bot/dispatch/waiter_machine/middleware.py +3 -3
  39. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -3
  40. telegrinder/bot/polling/polling.py +4 -2
  41. telegrinder/bot/rules/__init__.py +20 -12
  42. telegrinder/bot/rules/abc.py +0 -9
  43. telegrinder/bot/rules/adapter/event.py +31 -22
  44. telegrinder/bot/rules/callback_data.py +15 -20
  45. telegrinder/bot/rules/chat_join.py +47 -0
  46. telegrinder/bot/rules/enum_text.py +7 -2
  47. telegrinder/bot/rules/inline.py +3 -3
  48. telegrinder/bot/rules/is_from.py +36 -50
  49. telegrinder/bot/rules/message.py +17 -0
  50. telegrinder/bot/rules/message_entities.py +1 -1
  51. telegrinder/bot/rules/start.py +6 -4
  52. telegrinder/bot/rules/text.py +2 -1
  53. telegrinder/bot/rules/update.py +16 -0
  54. telegrinder/bot/scenario/checkbox.py +9 -7
  55. telegrinder/client/aiohttp.py +4 -4
  56. telegrinder/model.py +33 -19
  57. telegrinder/modules.py +16 -32
  58. telegrinder/msgspec_utils.py +37 -36
  59. telegrinder/node/__init__.py +12 -12
  60. telegrinder/node/attachment.py +15 -5
  61. telegrinder/node/base.py +24 -16
  62. telegrinder/node/composer.py +8 -6
  63. telegrinder/node/container.py +1 -1
  64. telegrinder/node/rule.py +4 -4
  65. telegrinder/node/source.py +4 -2
  66. telegrinder/node/tools/generator.py +1 -1
  67. telegrinder/tools/__init__.py +1 -1
  68. telegrinder/tools/error_handler/abc.py +4 -3
  69. telegrinder/tools/error_handler/error_handler.py +22 -16
  70. telegrinder/tools/formatting/html.py +15 -7
  71. telegrinder/tools/formatting/spec_html_formats.py +1 -1
  72. telegrinder/tools/global_context/abc.py +5 -1
  73. telegrinder/tools/global_context/telegrinder_ctx.py +1 -1
  74. telegrinder/tools/i18n/base.py +4 -3
  75. telegrinder/tools/i18n/simple.py +1 -3
  76. telegrinder/tools/keyboard.py +1 -1
  77. telegrinder/tools/loop_wrapper/loop_wrapper.py +24 -16
  78. telegrinder/tools/magic.py +1 -1
  79. telegrinder/types/__init__.py +206 -0
  80. telegrinder/types/enums.py +34 -0
  81. telegrinder/types/methods.py +52 -47
  82. telegrinder/types/objects.py +531 -88
  83. telegrinder/verification_utils.py +3 -1
  84. {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev166.dist-info}/METADATA +1 -1
  85. telegrinder-0.1.dev166.dist-info/RECORD +136 -0
  86. telegrinder-0.1.dev165.dist-info/RECORD +0 -128
  87. {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev166.dist-info}/LICENSE +0 -0
  88. {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev166.dist-info}/WHEEL +0 -0
@@ -14,6 +14,7 @@ from telegrinder.types import (
14
14
  InlineKeyboardMarkup,
15
15
  InputFile,
16
16
  InputMedia,
17
+ InputPollOption,
17
18
  LabeledPrice,
18
19
  LinkPreviewOptions,
19
20
  Message,
@@ -80,7 +81,7 @@ async def execute_method_answer(
80
81
  params["link_preview_options"] = compose_link_preview_options(
81
82
  **link_preview_options
82
83
  )
83
-
84
+
84
85
  result = await getattr(message.ctx_api, method_name)(**params)
85
86
  return result.map(
86
87
  lambda x: (
@@ -1213,10 +1214,12 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
1213
1214
  async def answer_poll(
1214
1215
  self,
1215
1216
  question: str,
1216
- options: list[str],
1217
+ options: list[InputPollOption],
1217
1218
  chat_id: int | str | None = None,
1218
1219
  business_connection_id: str | None = None,
1219
1220
  message_thread_id: int | None = None,
1221
+ question_parse_mode: str | None = None,
1222
+ question_entities: list[MessageEntity] | None = None,
1220
1223
  is_anonymous: bool | None = None,
1221
1224
  type: typing.Literal["quiz", "regular"] | None = None,
1222
1225
  allows_multiple_answers: bool | None = None,
@@ -1246,10 +1249,15 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
1246
1249
  :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
1247
1250
  forum supergroups only.
1248
1251
 
1252
+ :param question_parse_mode: Mode for parsing entities in the question. See formatting options for more \
1253
+ details. Currently, only custom emoji entities are allowed.
1254
+
1255
+ :param question_entities: A JSON-serialized list of special entities that appear in the poll question. \
1256
+ It can be specified instead of question_parse_mode.
1257
+
1249
1258
  :param question: Poll question, 1-300 characters.
1250
1259
 
1251
- :param options: A JSON-serialized list of answer options, 2-10 strings 1-100 characters \
1252
- each.
1260
+ :param options: A JSON-serialized list of 2-10 answer options.
1253
1261
 
1254
1262
  :param is_anonymous: True, if the poll needs to be anonymous, defaults to True.
1255
1263
 
@@ -2365,10 +2373,12 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
2365
2373
  async def reply_poll(
2366
2374
  self,
2367
2375
  question: str,
2368
- options: list[str],
2376
+ options: list[InputPollOption],
2369
2377
  chat_id: int | str | None = None,
2370
2378
  business_connection_id: str | None = None,
2371
2379
  message_thread_id: int | None = None,
2380
+ question_parse_mode: str | None = None,
2381
+ question_entities: list[MessageEntity] | None = None,
2372
2382
  is_anonymous: bool | None = None,
2373
2383
  type: typing.Literal["quiz", "regular"] | None = None,
2374
2384
  allows_multiple_answers: bool | None = None,
@@ -2398,10 +2408,15 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
2398
2408
  :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
2399
2409
  forum supergroups only.
2400
2410
 
2411
+ :param question_parse_mode: Mode for parsing entities in the question. See formatting options for more \
2412
+ details. Currently, only custom emoji entities are allowed.
2413
+
2414
+ :param question_entities: A JSON-serialized list of special entities that appear in the poll question. \
2415
+ It can be specified instead of question_parse_mode.
2416
+
2401
2417
  :param question: Poll question, 1-300 characters.
2402
2418
 
2403
- :param options: A JSON-serialized list of answer options, 2-10 strings 1-100 characters \
2404
- each.
2419
+ :param options: A JSON-serialized list of 2-10 answer options.
2405
2420
 
2406
2421
  :param is_anonymous: True, if the poll needs to be anonymous, defaults to True.
2407
2422
 
@@ -2910,6 +2925,8 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
2910
2925
  chat_id: int | str | None = None,
2911
2926
  message_id: int | None = None,
2912
2927
  message_thread_id: int | None = None,
2928
+ inline_message_id: str | None = None,
2929
+ live_period: int | None = None,
2913
2930
  horizontal_accuracy: float | None = None,
2914
2931
  heading: int | None = None,
2915
2932
  proximity_alert_radius: int | None = None,
@@ -2932,6 +2949,16 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
2932
2949
  :param message_thread_id: Unique identifier for the target message thread (topic) of the forum; for \
2933
2950
  forum supergroups only.
2934
2951
 
2952
+ :param live_period: New period in seconds during which the location can be updated, starting \
2953
+ from the message send date. If 0x7FFFFFFF is specified, then the location \
2954
+ can be updated forever. Otherwise, the new value must not exceed the current \
2955
+ live_period by more than a day, and the live location expiration date must \
2956
+ remain within the next 90 days. If not specified, then live_period remains \
2957
+ unchanged.
2958
+
2959
+ :param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the \
2960
+ inline message.
2961
+
2935
2962
  :param latitude: Latitude of new location.
2936
2963
 
2937
2964
  :param longitude: Longitude of new location.
@@ -15,15 +15,16 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
15
15
  api: ABCAPI
16
16
 
17
17
  @property
18
- def incoming_update(self) -> Model:
18
+ def incoming_update(self) -> Option[Model]:
19
19
  return getattr(
20
20
  self,
21
21
  self.update_type.expect("Update object has no incoming update.").value,
22
22
  )
23
-
23
+
24
24
  def get_event(self, event_model: type[ModelT]) -> Option[ModelT]:
25
- if isinstance(self.incoming_update, event_model):
26
- return Some(self.incoming_update)
25
+ match self.incoming_update:
26
+ case Some(event) if isinstance(event, event_model):
27
+ return Some(event)
27
28
  return Nothing()
28
29
 
29
30
 
@@ -2,6 +2,7 @@ import typing
2
2
 
3
3
  from telegrinder.model import get_params
4
4
  from telegrinder.types import (
5
+ ChatPermissions,
5
6
  InlineKeyboardMarkup,
6
7
  InlineQueryResultArticle,
7
8
  InlineQueryResultAudio,
@@ -115,6 +116,26 @@ def compose_link_preview_options(
115
116
  return LinkPreviewOptions(**get_params(locals()))
116
117
 
117
118
 
119
+ def compose_chat_permissions(
120
+ *,
121
+ can_send_messages: bool | None = None,
122
+ can_send_audios: bool | None = None,
123
+ can_send_documents: bool | None = None,
124
+ can_send_photos: bool | None = None,
125
+ can_send_videos: bool | None = None,
126
+ can_send_video_notes: bool | None = None,
127
+ can_send_voice_notes: bool | None = None,
128
+ can_send_polls: bool | None = None,
129
+ can_send_other_messages: bool | None = None,
130
+ can_add_web_page_previews: bool | None = None,
131
+ can_change_info: bool | None = None,
132
+ can_invite_users: bool | None = None,
133
+ can_pin_messages: bool | None = None,
134
+ can_manage_topics: bool | None = None,
135
+ ) -> ChatPermissions:
136
+ return ChatPermissions(**get_params(locals()))
137
+
138
+
118
139
  def input_media(
119
140
  type: typing.Literal["animation", "audio", "document", "photo", "video"],
120
141
  media: str | InputFile,
@@ -510,33 +531,34 @@ def inline_query_cached_photo(
510
531
 
511
532
 
512
533
  __all__ = (
534
+ "compose_chat_permissions",
513
535
  "compose_link_preview_options",
514
536
  "compose_reactions",
515
537
  "compose_reply_params",
516
538
  "inline_query_article",
517
- "inline_query_photo",
518
- "inline_query_mpeg4_gif",
519
- "inline_query_gif",
520
- "inline_query_video",
521
539
  "inline_query_audio",
522
- "inline_query_voice",
523
- "inline_query_document",
524
- "inline_query_location",
525
- "inline_query_venue",
526
- "inline_query_contact",
527
- "inline_query_game",
528
- "inline_query_cached_sticker",
529
- "inline_query_cached_document",
530
540
  "inline_query_cached_audio",
531
- "inline_query_cached_video",
541
+ "inline_query_cached_document",
532
542
  "inline_query_cached_gif",
533
543
  "inline_query_cached_mpeg4_gif",
534
- "inline_query_cached_voice",
535
544
  "inline_query_cached_photo",
545
+ "inline_query_cached_sticker",
546
+ "inline_query_cached_video",
547
+ "inline_query_cached_voice",
548
+ "inline_query_contact",
549
+ "inline_query_document",
550
+ "inline_query_game",
551
+ "inline_query_gif",
552
+ "inline_query_location",
553
+ "inline_query_mpeg4_gif",
554
+ "inline_query_photo",
555
+ "inline_query_venue",
556
+ "inline_query_video",
557
+ "inline_query_voice",
558
+ "input_contact_message_content",
559
+ "input_invoice_message_content",
560
+ "input_location_message_content",
536
561
  "input_media",
537
562
  "input_text_message_content",
538
- "input_location_message_content",
539
563
  "input_venue_message_content",
540
- "input_contact_message_content",
541
- "input_invoice_message_content",
542
564
  )
@@ -20,11 +20,14 @@ from .view import (
20
20
  BaseStateView,
21
21
  BaseView,
22
22
  CallbackQueryView,
23
+ ChatJoinRequestView,
24
+ ChatMemberView,
23
25
  InlineQueryView,
24
26
  MessageView,
27
+ RawEventView,
25
28
  ViewBox,
26
29
  )
27
- from .waiter_machine import WaiterMachine
30
+ from .waiter_machine import ShortState, ShortStateStorage, WaiterMachine
28
31
 
29
32
  __all__ = (
30
33
  "ABCDispatch",
@@ -39,6 +42,8 @@ __all__ = (
39
42
  "BaseView",
40
43
  "CallbackQueryReturnManager",
41
44
  "CallbackQueryView",
45
+ "ChatJoinRequestView",
46
+ "ChatMemberView",
42
47
  "CompositionDispatch",
43
48
  "Context",
44
49
  "Dispatch",
@@ -49,6 +54,9 @@ __all__ = (
49
54
  "MessageReplyHandler",
50
55
  "MessageReturnManager",
51
56
  "MessageView",
57
+ "RawEventView",
58
+ "ShortState",
59
+ "ShortStateStorage",
52
60
  "TelegrinderCtx",
53
61
  "ViewBox",
54
62
  "WaiterMachine",
@@ -26,15 +26,14 @@ class Composition:
26
26
  for name, parameter in inspect.signature(func).parameters.items()
27
27
  }
28
28
  self.is_blocking = is_blocking
29
-
29
+
30
30
  def __repr__(self) -> str:
31
31
  return "<{}: for function={!r} with nodes={}>".format(
32
- ("blocking " if self.is_blocking else "")
33
- + self.__class__.__name__,
32
+ ("blocking " if self.is_blocking else "") + self.__class__.__name__,
34
33
  self.func.__name__,
35
34
  self.nodes,
36
35
  )
37
-
36
+
38
37
  async def compose_nodes(self, update: UpdateCute) -> NodeCollection | None:
39
38
  nodes: dict[str, NodeSession] = {}
40
39
  for name, node_t in self.nodes.items():
@@ -44,7 +43,7 @@ class Composition:
44
43
  await NodeCollection(nodes).close_all()
45
44
  return None
46
45
  return NodeCollection(nodes)
47
-
46
+
48
47
  async def __call__(self, **kwargs: typing.Any) -> typing.Any:
49
48
  return await self.func(**magic_bundle(self.func, kwargs, start_idx=0, bundle_ctx=False)) # type: ignore
50
49
 
@@ -52,7 +51,7 @@ class Composition:
52
51
  class CompositionDispatch(ABCDispatch):
53
52
  def __init__(self) -> None:
54
53
  self.compositions: list[Composition] = []
55
-
54
+
56
55
  def __repr__(self) -> str:
57
56
  return "<{}: with compositions={!r}>".format(
58
57
  self.__class__.__name__,
@@ -71,7 +70,7 @@ class CompositionDispatch(ABCDispatch):
71
70
  return True
72
71
  is_found = True
73
72
  return is_found
74
-
73
+
75
74
  def load(self, external: typing.Self):
76
75
  self.compositions.extend(external.compositions)
77
76
 
@@ -79,9 +78,12 @@ class CompositionDispatch(ABCDispatch):
79
78
  def wrapper(func: typing.Callable):
80
79
  composition = Composition(func, is_blocking)
81
80
  if container_nodes:
82
- composition.nodes["container"] = ContainerNode.link_nodes(list(container_nodes))
81
+ composition.nodes["container"] = ContainerNode.link_nodes(
82
+ list(container_nodes)
83
+ )
83
84
  self.compositions.append(composition)
84
85
  return func
86
+
85
87
  return wrapper
86
88
 
87
89
 
@@ -33,45 +33,44 @@ class Context(dict[str, AnyValue]):
33
33
  defaults[k] = cls_vars[k]
34
34
  delattr(self.__class__, k)
35
35
  dict.__init__(self, **defaults | kwargs)
36
-
36
+
37
37
  @recursive_repr()
38
38
  def __repr__(self) -> str:
39
39
  return "{}({})".format(
40
- self.__class__.__name__,
41
- ", ".join(f"{k}={v!r}" for k, v in self.items())
40
+ self.__class__.__name__, ", ".join(f"{k}={v!r}" for k, v in self.items())
42
41
  )
43
42
 
44
43
  def __setitem__(self, __key: Key, __value: AnyValue) -> None:
45
44
  dict.__setitem__(self, self.key_to_str(__key), __value)
46
-
45
+
47
46
  def __getitem__(self, __key: Key) -> AnyValue:
48
47
  return dict.__getitem__(self, self.key_to_str(__key))
49
-
48
+
50
49
  def __delitem__(self, __key: Key) -> None:
51
50
  dict.__delitem__(self, self.key_to_str(__key))
52
51
 
53
52
  def __setattr__(self, __name: str, __value: AnyValue) -> None:
54
53
  self.__setitem__(__name, __value)
55
-
54
+
56
55
  def __getattr__(self, __name: str) -> AnyValue:
57
56
  return self.__getitem__(__name)
58
-
57
+
59
58
  def __delattr__(self, __name: str) -> None:
60
59
  self.__delitem__(__name)
61
60
 
62
61
  @staticmethod
63
62
  def key_to_str(key: Key) -> str:
64
63
  return key if isinstance(key, str) else str(key.value)
65
-
64
+
66
65
  def copy(self) -> typing.Self:
67
66
  return self.__class__(**self)
68
67
 
69
68
  def set(self, key: Key, value: AnyValue) -> None:
70
69
  self[key] = value
71
-
70
+
72
71
  def get(self, key: Key, default: T | None = None) -> T | AnyValue:
73
72
  return dict.get(self, key, default)
74
-
73
+
75
74
  def delete(self, key: Key) -> None:
76
75
  del self[key]
77
76
 
@@ -16,12 +16,22 @@ from telegrinder.types import Update
16
16
  from .abc import ABCDispatch
17
17
  from .handler import ABCHandler, FuncHandler
18
18
  from .handler.func import ErrorHandlerT
19
- from .view.box import CallbackQueryViewT, InlineQueryViewT, MessageViewT, ViewBox
19
+ from .view.box import (
20
+ CallbackQueryViewT,
21
+ ChatJoinRequestViewT,
22
+ ChatMemberViewT,
23
+ InlineQueryViewT,
24
+ MessageViewT,
25
+ RawEventViewT,
26
+ ViewBox,
27
+ )
20
28
 
21
29
  T = typing.TypeVar("T")
22
30
  R = typing.TypeVar("R")
23
31
  P = typing.ParamSpec("P")
24
- Handler = typing.Callable[typing.Concatenate[T, ...], typing.Coroutine[typing.Any, typing.Any, typing.Any]]
32
+ Handler = typing.Callable[
33
+ typing.Concatenate[T, ...], typing.Coroutine[typing.Any, typing.Any, typing.Any]
34
+ ]
25
35
  Event = typing.TypeVar("Event", bound=BaseCute)
26
36
 
27
37
  DEFAULT_DATACLASS: typing.Final[type[Update]] = Update
@@ -30,7 +40,14 @@ DEFAULT_DATACLASS: typing.Final[type[Update]] = Update
30
40
  @dataclasses.dataclass(repr=False, kw_only=True)
31
41
  class Dispatch(
32
42
  ABCDispatch,
33
- ViewBox[CallbackQueryViewT, InlineQueryViewT, MessageViewT],
43
+ ViewBox[
44
+ CallbackQueryViewT,
45
+ ChatJoinRequestViewT,
46
+ ChatMemberViewT,
47
+ InlineQueryViewT,
48
+ MessageViewT,
49
+ RawEventViewT,
50
+ ],
34
51
  ):
35
52
  global_context: TelegrinderCtx = dataclasses.field(
36
53
  init=False,
@@ -42,29 +59,25 @@ class Dispatch(
42
59
  )
43
60
 
44
61
  def __repr__(self) -> str:
45
- return "Dispatch(%s)" % ", ".join(
46
- f"{k}={v!r}" for k, v in self.__dict__.items()
47
- )
62
+ return "Dispatch(%s)" % ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
48
63
 
49
64
  @property
50
65
  def patcher(self) -> Patcher:
51
66
  """Alias `patcher` to get `vbml.Patcher` from the global context"""
52
67
  return self.global_context.vbml_patcher
53
-
68
+
54
69
  @typing.overload
55
70
  def handle(
56
71
  self,
57
72
  *rules: ABCRule[Event],
58
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]:
59
- ...
73
+ ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
60
74
 
61
75
  @typing.overload
62
76
  def handle(
63
77
  self,
64
78
  *rules: ABCRule[Event],
65
79
  is_blocking: bool = True,
66
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]:
67
- ...
80
+ ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
68
81
 
69
82
  @typing.overload
70
83
  def handle(
@@ -72,8 +85,7 @@ class Dispatch(
72
85
  *rules: ABCRule[Event],
73
86
  dataclass: type[T],
74
87
  is_blocking: bool = True,
75
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]:
76
- ...
88
+ ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
77
89
 
78
90
  @typing.overload
79
91
  def handle( # type: ignore
@@ -81,8 +93,7 @@ class Dispatch(
81
93
  *rules: ABCRule[Event],
82
94
  error_handler: ErrorHandlerT,
83
95
  is_blocking: bool = True,
84
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandlerT]]:
85
- ...
96
+ ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandlerT]]: ...
86
97
 
87
98
  @typing.overload
88
99
  def handle(
@@ -91,8 +102,7 @@ class Dispatch(
91
102
  dataclass: type[T],
92
103
  error_handler: ErrorHandlerT,
93
104
  is_blocking: bool = True,
94
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandlerT]]:
95
- ...
105
+ ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandlerT]]: ...
96
106
 
97
107
  @typing.overload
98
108
  def handle(
@@ -101,8 +111,7 @@ class Dispatch(
101
111
  dataclass: type[T] = DEFAULT_DATACLASS,
102
112
  error_handler: typing.Literal[None] = None,
103
113
  is_blocking: bool = True,
104
- ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]:
105
- ...
114
+ ) -> typing.Callable[[Handler[T]], FuncHandler[Event, Handler[T], ErrorHandler]]: ...
106
115
 
107
116
  def handle( # type: ignore
108
117
  self,
@@ -126,6 +135,7 @@ class Dispatch(
126
135
 
127
136
  async def feed(self, event: Update, api: ABCAPI) -> bool:
128
137
  logger.debug("Processing update (update_id={})", event.update_id)
138
+ await self.raw_event.process(event, api)
129
139
  for view in self.get_views().values():
130
140
  if await view.check(event):
131
141
  logger.debug(
@@ -20,4 +20,5 @@ class ABCHandler(ABC, typing.Generic[T]):
20
20
  async def run(self, event: T, ctx: Context) -> typing.Any:
21
21
  pass
22
22
 
23
+
23
24
  __all__ = ("ABCHandler",)
@@ -9,15 +9,23 @@ from telegrinder.bot.dispatch.process import check_rule
9
9
  from telegrinder.modules import logger
10
10
  from telegrinder.tools.error_handler import ABCErrorHandler, ErrorHandler
11
11
  from telegrinder.types import Update
12
+ from telegrinder.types.enums import UpdateType
12
13
 
13
14
  from .abc import ABCHandler
14
15
 
15
16
  if typing.TYPE_CHECKING:
16
17
  from telegrinder.bot.rules import ABCRule
17
18
 
18
- F = typing.TypeVar("F", bound=typing.Callable[typing.Concatenate[typing.Any, ...], typing.Awaitable[typing.Any]])
19
+ F = typing.TypeVar(
20
+ "F",
21
+ bound=typing.Callable[
22
+ typing.Concatenate[typing.Any, ...], typing.Awaitable[typing.Any]
23
+ ],
24
+ )
19
25
  EventT = typing.TypeVar("EventT", bound=BaseCute)
20
- ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler, default=ErrorHandler)
26
+ ErrorHandlerT = typing.TypeVar(
27
+ "ErrorHandlerT", bound=ABCErrorHandler, default=ErrorHandler
28
+ )
21
29
 
22
30
 
23
31
  @dataclasses.dataclass(repr=False)
@@ -31,6 +39,7 @@ class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
31
39
  default_factory=lambda: typing.cast(ErrorHandlerT, ErrorHandler()),
32
40
  )
33
41
  preset_context: Context = dataclasses.field(default_factory=lambda: Context())
42
+ update_type: UpdateType | None = dataclasses.field(default=None)
34
43
 
35
44
  def __repr__(self) -> str:
36
45
  return "<{}: {}={!r} with rules={!r}, dataclass={!r}, error_handler={!r}>".format(
@@ -41,8 +50,13 @@ class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
41
50
  self.dataclass,
42
51
  self.error_handler,
43
52
  )
44
-
53
+
45
54
  async def check(self, api: ABCAPI, event: Update, ctx: Context | None = None) -> bool:
55
+ if (
56
+ self.update_type is not None
57
+ and self.update_type != event.update_type.unwrap_or_none()
58
+ ):
59
+ return False
46
60
  ctx = ctx or Context()
47
61
  temp_ctx = ctx.copy()
48
62
  temp_ctx |= self.preset_context
@@ -51,14 +65,23 @@ class FuncHandler(ABCHandler[EventT], typing.Generic[EventT, F, ErrorHandlerT]):
51
65
  if not await check_rule(api, rule, event, temp_ctx):
52
66
  logger.debug("Rule {!r} failed!", rule)
53
67
  return False
54
-
68
+
55
69
  ctx |= temp_ctx
56
70
  return True
57
71
 
58
72
  async def run(self, event: EventT, ctx: Context) -> typing.Any:
73
+ api = event.api
59
74
  if self.dataclass is not None:
60
- event = self.dataclass(**event.to_dict())
61
- return (await self.error_handler.run(self.func, event, event.api, ctx)).unwrap()
75
+ if self.update_type is not None:
76
+ update = event.to_dict()[self.update_type.value].unwrap()
77
+ event = (
78
+ self.dataclass.from_update(update, bound_api=api) # type: ignore
79
+ if issubclass(self.dataclass, BaseCute)
80
+ else self.dataclass(**update.to_dict())
81
+ )
82
+ else:
83
+ event = self.dataclass(**event.to_dict())
84
+ return (await self.error_handler.run(self.func, event, api, ctx)).unwrap()
62
85
 
63
86
 
64
87
  __all__ = ("FuncHandler",)
@@ -25,11 +25,10 @@ class MessageReplyHandler(ABCHandler[MessageCute]):
25
25
  self.as_reply = as_reply
26
26
  self.is_blocking = is_blocking
27
27
  self.preset_context = Context()
28
-
28
+
29
29
  def __repr__(self) -> str:
30
30
  return "<{}: with rules={!r}, {}: {!r}>".format(
31
- ("blocking " if self.is_blocking else "")
32
- + self.__class__.__name__,
31
+ ("blocking " if self.is_blocking else "") + self.__class__.__name__,
33
32
  self.rules,
34
33
  "answer text as reply" if self.as_reply else "answer text",
35
34
  self.text,
@@ -8,11 +8,9 @@ T = typing.TypeVar("T", bound=BaseCute)
8
8
 
9
9
 
10
10
  class ABCMiddleware(ABC, typing.Generic[T]):
11
- async def pre(self, event: T, ctx: Context) -> bool:
12
- ...
11
+ async def pre(self, event: T, ctx: Context) -> bool: ...
13
12
 
14
- async def post(self, event: T, responses: list[typing.Any], ctx: Context) -> None:
15
- ...
13
+ async def post(self, event: T, responses: list[typing.Any], ctx: Context) -> None: ...
16
14
 
17
15
 
18
16
  __all__ = ("ABCMiddleware",)
@@ -25,7 +25,7 @@ async def process_inner(
25
25
  raw_event: Update,
26
26
  middlewares: list[ABCMiddleware[T]],
27
27
  handlers: list["ABCHandler[T]"],
28
- return_manager: ABCReturnManager[T],
28
+ return_manager: ABCReturnManager[T] | None = None,
29
29
  ) -> bool:
30
30
  logger.debug("Processing {!r}...", event.__class__.__name__)
31
31
  ctx = Context(raw_update=raw_event)
@@ -43,7 +43,8 @@ async def process_inner(
43
43
  found = True
44
44
  response = await handler.run(event, ctx)
45
45
  responses.append(response)
46
- await return_manager.run(response, event, ctx)
46
+ if return_manager is not None:
47
+ await return_manager.run(response, event, ctx)
47
48
  if handler.is_blocking:
48
49
  break
49
50
  ctx = ctx_copy
@@ -56,7 +57,7 @@ async def process_inner(
56
57
 
57
58
  async def check_rule(
58
59
  api: ABCAPI,
59
- rule: "ABCRule",
60
+ rule: "ABCRule[T]",
60
61
  update: Update,
61
62
  ctx: Context,
62
63
  ) -> bool:
@@ -16,4 +16,4 @@ __all__ = (
16
16
  "Manager",
17
17
  "MessageReturnManager",
18
18
  "register_manager",
19
- )
19
+ )