telegrinder 0.2.1__py3-none-any.whl → 0.2.2__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 (71) hide show
  1. telegrinder/__init__.py +22 -11
  2. telegrinder/bot/__init__.py +16 -4
  3. telegrinder/bot/cute_types/chat_member_updated.py +1 -1
  4. telegrinder/bot/cute_types/message.py +18 -11
  5. telegrinder/bot/dispatch/__init__.py +18 -2
  6. telegrinder/bot/dispatch/abc.py +1 -1
  7. telegrinder/bot/dispatch/context.py +2 -2
  8. telegrinder/bot/dispatch/dispatch.py +1 -1
  9. telegrinder/bot/dispatch/handler/__init__.py +17 -1
  10. telegrinder/bot/dispatch/handler/audio_reply.py +44 -0
  11. telegrinder/bot/dispatch/handler/base.py +57 -0
  12. telegrinder/bot/dispatch/handler/document_reply.py +44 -0
  13. telegrinder/bot/dispatch/handler/func.py +3 -3
  14. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
  15. telegrinder/bot/dispatch/handler/message_reply.py +12 -35
  16. telegrinder/bot/dispatch/handler/photo_reply.py +44 -0
  17. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -0
  18. telegrinder/bot/dispatch/handler/video_reply.py +44 -0
  19. telegrinder/bot/dispatch/process.py +2 -2
  20. telegrinder/bot/dispatch/return_manager/abc.py +11 -8
  21. telegrinder/bot/dispatch/return_manager/callback_query.py +2 -2
  22. telegrinder/bot/dispatch/return_manager/inline_query.py +2 -2
  23. telegrinder/bot/dispatch/return_manager/message.py +3 -3
  24. telegrinder/bot/dispatch/view/__init__.py +2 -1
  25. telegrinder/bot/dispatch/view/abc.py +2 -181
  26. telegrinder/bot/dispatch/view/base.py +200 -0
  27. telegrinder/bot/dispatch/view/callback_query.py +3 -3
  28. telegrinder/bot/dispatch/view/chat_join_request.py +2 -2
  29. telegrinder/bot/dispatch/view/chat_member.py +2 -3
  30. telegrinder/bot/dispatch/view/inline_query.py +2 -2
  31. telegrinder/bot/dispatch/view/message.py +5 -4
  32. telegrinder/bot/dispatch/view/raw.py +4 -3
  33. telegrinder/bot/dispatch/waiter_machine/machine.py +6 -7
  34. telegrinder/bot/dispatch/waiter_machine/middleware.py +0 -6
  35. telegrinder/bot/dispatch/waiter_machine/short_state.py +1 -1
  36. telegrinder/bot/polling/polling.py +5 -2
  37. telegrinder/bot/rules/__init__.py +3 -3
  38. telegrinder/bot/rules/abc.py +6 -5
  39. telegrinder/bot/rules/adapter/__init__.py +1 -1
  40. telegrinder/bot/rules/integer.py +1 -1
  41. telegrinder/bot/rules/is_from.py +19 -0
  42. telegrinder/bot/rules/state.py +9 -6
  43. telegrinder/bot/scenario/checkbox.py +3 -3
  44. telegrinder/bot/scenario/choice.py +2 -2
  45. telegrinder/client/aiohttp.py +5 -7
  46. telegrinder/model.py +1 -8
  47. telegrinder/modules.py +16 -25
  48. telegrinder/msgspec_utils.py +5 -5
  49. telegrinder/node/base.py +2 -2
  50. telegrinder/node/composer.py +5 -9
  51. telegrinder/node/container.py +6 -1
  52. telegrinder/node/polymorphic.py +7 -7
  53. telegrinder/node/rule.py +6 -4
  54. telegrinder/node/scope.py +3 -3
  55. telegrinder/node/source.py +4 -2
  56. telegrinder/node/tools/generator.py +7 -6
  57. telegrinder/rules.py +2 -2
  58. telegrinder/tools/__init__.py +10 -10
  59. telegrinder/tools/keyboard.py +6 -1
  60. telegrinder/tools/loop_wrapper/loop_wrapper.py +4 -5
  61. telegrinder/tools/magic.py +17 -19
  62. telegrinder/tools/state_storage/__init__.py +3 -3
  63. telegrinder/tools/state_storage/abc.py +12 -10
  64. telegrinder/tools/state_storage/memory.py +6 -3
  65. telegrinder/types/__init__.py +1 -0
  66. telegrinder/types/methods.py +10 -2
  67. telegrinder/types/objects.py +47 -5
  68. {telegrinder-0.2.1.dist-info → telegrinder-0.2.2.dist-info}/METADATA +3 -4
  69. {telegrinder-0.2.1.dist-info → telegrinder-0.2.2.dist-info}/RECORD +71 -63
  70. {telegrinder-0.2.1.dist-info → telegrinder-0.2.2.dist-info}/LICENSE +0 -0
  71. {telegrinder-0.2.1.dist-info → telegrinder-0.2.2.dist-info}/WHEEL +0 -0
telegrinder/__init__.py CHANGED
@@ -7,6 +7,7 @@ Modern visionary telegram bot framework.
7
7
  * Ready to use scenarios and rules
8
8
  * Fast models built on msgspec
9
9
  * Both low-level and high-level API
10
+ * Support [optional dependecies](https://github.com/timoniq/telegrinder/blob/dev/docs/guide/optional_dependencies.md)
10
11
 
11
12
  Basic example:
12
13
 
@@ -23,9 +24,7 @@ logger.set_level("INFO")
23
24
  @bot.on.message(Text("/start"))
24
25
  async def start(message: Message):
25
26
  me = (await api.get_me()).unwrap()
26
- await message.answer(
27
- f"Hello, {message.from_user.full_name}! I'm {me.full_name}."
28
- )
27
+ await message.answer(f"Hello, {message.from_user.full_name}! I'm {me.full_name}.")
29
28
 
30
29
 
31
30
  bot.run_forever()
@@ -45,6 +44,7 @@ from .bot import (
45
44
  ABCScenario,
46
45
  ABCStateView,
47
46
  ABCView,
47
+ AudioReplyHandler,
48
48
  BaseCute,
49
49
  BaseReturnManager,
50
50
  BaseStateView,
@@ -60,21 +60,27 @@ from .bot import (
60
60
  ChatMemberView,
61
61
  Checkbox,
62
62
  Choice,
63
+ Context,
63
64
  Dispatch,
65
+ DocumentReplyHandler,
64
66
  FuncHandler,
65
67
  InlineQueryCute,
66
68
  InlineQueryReturnManager,
67
69
  InlineQueryRule,
70
+ MediaGroupReplyHandler,
68
71
  MessageCute,
69
72
  MessageReplyHandler,
70
73
  MessageReturnManager,
71
74
  MessageRule,
72
75
  MessageView,
76
+ PhotoReplyHandler,
73
77
  Polling,
74
78
  RawEventView,
75
79
  ShortState,
80
+ StickerReplyHandler,
76
81
  Telegrinder,
77
82
  UpdateCute,
83
+ VideoReplyHandler,
78
84
  ViewBox,
79
85
  WaiterMachine,
80
86
  register_manager,
@@ -126,7 +132,6 @@ Bot: typing.TypeAlias = Telegrinder
126
132
 
127
133
 
128
134
  __all__ = (
129
- "API",
130
135
  "ABCClient",
131
136
  "ABCDispatch",
132
137
  "ABCErrorHandler",
@@ -138,6 +143,7 @@ __all__ = (
138
143
  "ABCReturnManager",
139
144
  "ABCRule",
140
145
  "ABCScenario",
146
+ "ABCStateStorage",
141
147
  "ABCStateView",
142
148
  "ABCTranslator",
143
149
  "ABCTranslatorMiddleware",
@@ -156,17 +162,17 @@ __all__ = (
156
162
  "CallbackQuery",
157
163
  "CallbackQueryCute",
158
164
  "CallbackQueryReturnManager",
165
+ "CallbackQueryRule",
159
166
  "CallbackQueryView",
160
167
  "ChatJoinRequest",
161
168
  "ChatJoinRequestCute",
162
- "CallbackQueryRule",
163
169
  "ChatJoinRequestRule",
164
- "InlineQueryRule",
165
170
  "ChatJoinRequestView",
166
171
  "ChatMemberUpdated",
167
172
  "ChatMemberUpdatedCute",
168
173
  "ChatMemberView",
169
174
  "Checkbox",
175
+ "Choice",
170
176
  "CtxVar",
171
177
  "DelayedTask",
172
178
  "Dispatch",
@@ -181,38 +187,43 @@ __all__ = (
181
187
  "InlineQuery",
182
188
  "InlineQueryCute",
183
189
  "InlineQueryReturnManager",
190
+ "InlineQueryRule",
184
191
  "Keyboard",
185
192
  "KeyboardSetBase",
186
193
  "KeyboardSetYAML",
187
194
  "Lifespan",
188
195
  "LoopWrapper",
196
+ "MediaGroupReplyHandler",
197
+ "MemoryStateStorage",
189
198
  "Message",
190
199
  "MessageCute",
191
200
  "MessageReplyHandler",
201
+ "MessageReplyHandler",
192
202
  "MessageReturnManager",
193
203
  "MessageRule",
194
204
  "MessageView",
195
205
  "Model",
196
206
  "ParseMode",
207
+ "PhotoReplyHandler",
197
208
  "Polling",
198
209
  "RawEventView",
199
210
  "RowButtons",
200
211
  "ShortState",
201
212
  "SimpleI18n",
202
213
  "SimpleTranslator",
203
- "Choice",
214
+ "StateData",
215
+ "StateMeta",
216
+ "StickerReplyHandler",
204
217
  "Telegrinder",
205
218
  "Token",
206
219
  "Update",
207
220
  "UpdateCute",
221
+ "VideoReplyHandler",
208
222
  "ViewBox",
209
223
  "WaiterMachine",
210
224
  "ctx_var",
211
225
  "logger",
212
226
  "magic_bundle",
213
227
  "register_manager",
214
- "ABCStateStorage",
215
- "MemoryStateStorage",
216
- "StateData",
217
- "StateMeta",
228
+ "Context",
218
229
  )
@@ -15,6 +15,7 @@ from telegrinder.bot.dispatch import (
15
15
  ABCReturnManager,
16
16
  ABCStateView,
17
17
  ABCView,
18
+ AudioReplyHandler,
18
19
  BaseReturnManager,
19
20
  BaseStateView,
20
21
  BaseView,
@@ -24,14 +25,19 @@ from telegrinder.bot.dispatch import (
24
25
  ChatMemberView,
25
26
  Context,
26
27
  Dispatch,
28
+ DocumentReplyHandler,
27
29
  FuncHandler,
28
30
  InlineQueryReturnManager,
29
31
  Manager,
32
+ MediaGroupReplyHandler,
30
33
  MessageReplyHandler,
31
34
  MessageReturnManager,
32
35
  MessageView,
36
+ PhotoReplyHandler,
33
37
  RawEventView,
34
38
  ShortState,
39
+ StickerReplyHandler,
40
+ VideoReplyHandler,
35
41
  ViewBox,
36
42
  WaiterMachine,
37
43
  clear_wm_storage_worker,
@@ -57,6 +63,7 @@ __all__ = (
57
63
  "ABCScenario",
58
64
  "ABCStateView",
59
65
  "ABCView",
66
+ "AudioReplyHandler",
60
67
  "BaseCute",
61
68
  "BaseReturnManager",
62
69
  "BaseStateView",
@@ -65,32 +72,37 @@ __all__ = (
65
72
  "CallbackQueryReturnManager",
66
73
  "CallbackQueryRule",
67
74
  "CallbackQueryView",
68
- "ChatJoinRequestRule",
69
- "InlineQueryRule",
70
75
  "ChatJoinRequestCute",
76
+ "ChatJoinRequestRule",
71
77
  "ChatJoinRequestView",
72
78
  "ChatMemberUpdatedCute",
73
79
  "ChatMemberView",
74
80
  "Checkbox",
81
+ "Choice",
75
82
  "Context",
76
83
  "Dispatch",
84
+ "DocumentReplyHandler",
77
85
  "FuncHandler",
78
86
  "InlineQueryCute",
79
87
  "InlineQueryReturnManager",
88
+ "InlineQueryRule",
80
89
  "Manager",
90
+ "MediaGroupReplyHandler",
81
91
  "MessageCute",
82
92
  "MessageReplyHandler",
83
93
  "MessageReturnManager",
84
94
  "MessageRule",
85
95
  "MessageView",
96
+ "PhotoReplyHandler",
86
97
  "Polling",
87
98
  "RawEventView",
88
99
  "ShortState",
89
- "Choice",
100
+ "StickerReplyHandler",
90
101
  "Telegrinder",
91
102
  "UpdateCute",
103
+ "VideoReplyHandler",
92
104
  "ViewBox",
93
105
  "WaiterMachine",
94
- "register_manager",
95
106
  "clear_wm_storage_worker",
107
+ "register_manager",
96
108
  )
@@ -245,6 +245,6 @@ class ChatMemberUpdatedCute(
245
245
 
246
246
 
247
247
  __all__ = (
248
- "ChatMemberUpdatedCute",
249
248
  "ChatMemberShortcuts",
249
+ "ChatMemberUpdatedCute",
250
250
  )
@@ -42,7 +42,6 @@ if typing.TYPE_CHECKING:
42
42
 
43
43
  from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
44
44
 
45
-
46
45
  MediaType: typing.TypeAlias = typing.Literal[
47
46
  "animation",
48
47
  "audio",
@@ -50,6 +49,7 @@ MediaType: typing.TypeAlias = typing.Literal[
50
49
  "photo",
51
50
  "video",
52
51
  ]
52
+ InputMediaType: typing.TypeAlias = InputMedia | tuple[MediaType, InputFile | str]
53
53
  ReplyMarkup: typing.TypeAlias = InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply
54
54
 
55
55
 
@@ -1676,14 +1676,14 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
1676
1676
  )
1677
1677
  async def answer_media_group(
1678
1678
  self,
1679
- media: list[InputMedia | tuple[MediaType, InputFile | str]],
1679
+ media: InputMediaType | list[InputMediaType],
1680
1680
  chat_id: int | str | None = None,
1681
1681
  business_connection_id: str | None = None,
1682
1682
  message_thread_id: int | None = None,
1683
1683
  message_effect_id: str | None = None,
1684
- caption: str | None = None,
1685
- parse_mode: str | None = None,
1686
- caption_entities: list[MessageEntity] | None = None,
1684
+ caption: str | list[str] | None = None,
1685
+ parse_mode: str | list[str] | None = None,
1686
+ caption_entities: list[MessageEntity] | list[list[MessageEntity]] | None = None,
1687
1687
  disable_notification: bool | None = None,
1688
1688
  protect_content: bool | None = None,
1689
1689
  reply_parameters: ReplyParameters | dict[str, typing.Any] | None = None,
@@ -1724,7 +1724,14 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
1724
1724
 
1725
1725
  :param reply_parameters: Description of the message to reply to."""
1726
1726
 
1727
+ media = [media] if not isinstance(media, list) else media
1727
1728
  params = get_params(locals())
1729
+ caption_entities_lst = typing.cast(
1730
+ list[list[MessageEntity]],
1731
+ [caption_entities]
1732
+ if caption_entities and len(caption_entities) == 1 and not isinstance(caption_entities[0], list)
1733
+ else caption_entities,
1734
+ )
1728
1735
 
1729
1736
  for i, m in enumerate(media[:]):
1730
1737
  if isinstance(m, tuple):
@@ -1732,9 +1739,9 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
1732
1739
  i,
1733
1740
  input_media( # type: ignore
1734
1741
  *media.pop(i), # type: ignore
1735
- caption=caption,
1736
- caption_entities=caption_entities,
1737
- parse_mode=parse_mode,
1742
+ caption=caption if not isinstance(caption, list) else caption[i],
1743
+ caption_entities=caption_entities_lst[i] if caption_entities_lst else None,
1744
+ parse_mode=parse_mode if not isinstance(parse_mode, list) else parse_mode[i],
1738
1745
  ),
1739
1746
  )
1740
1747
 
@@ -2873,13 +2880,13 @@ class MessageCute(BaseCute[Message], Message, kw_only=True):
2873
2880
  )
2874
2881
  async def reply_media_group(
2875
2882
  self,
2876
- media: list[InputMedia | tuple[MediaType, InputFile | str]],
2883
+ media: InputMediaType | list[InputMediaType],
2877
2884
  chat_id: int | str | None = None,
2878
2885
  business_connection_id: str | None = None,
2879
2886
  message_thread_id: int | None = None,
2880
2887
  message_effect_id: str | None = None,
2881
- caption: str | None = None,
2882
- parse_mode: str | None = None,
2888
+ caption: str | list[str] | None = None,
2889
+ parse_mode: str | list[str] | None = None,
2883
2890
  caption_entities: list[MessageEntity] | None = None,
2884
2891
  disable_notification: bool | None = None,
2885
2892
  protect_content: bool | None = None,
@@ -1,7 +1,17 @@
1
1
  from telegrinder.bot.dispatch.abc import ABCDispatch
2
2
  from telegrinder.bot.dispatch.context import Context
3
3
  from telegrinder.bot.dispatch.dispatch import Dispatch, TelegrinderContext
4
- from telegrinder.bot.dispatch.handler import ABCHandler, FuncHandler, MessageReplyHandler
4
+ from telegrinder.bot.dispatch.handler import (
5
+ ABCHandler,
6
+ AudioReplyHandler,
7
+ DocumentReplyHandler,
8
+ FuncHandler,
9
+ MediaGroupReplyHandler,
10
+ MessageReplyHandler,
11
+ PhotoReplyHandler,
12
+ StickerReplyHandler,
13
+ VideoReplyHandler,
14
+ )
5
15
  from telegrinder.bot.dispatch.middleware import ABCMiddleware
6
16
  from telegrinder.bot.dispatch.process import check_rule, process_inner
7
17
  from telegrinder.bot.dispatch.return_manager import (
@@ -35,6 +45,7 @@ __all__ = (
35
45
  "ABCReturnManager",
36
46
  "ABCStateView",
37
47
  "ABCView",
48
+ "AudioReplyHandler",
38
49
  "BaseReturnManager",
39
50
  "BaseStateView",
40
51
  "BaseView",
@@ -44,20 +55,25 @@ __all__ = (
44
55
  "ChatMemberView",
45
56
  "Context",
46
57
  "Dispatch",
58
+ "DocumentReplyHandler",
47
59
  "FuncHandler",
48
60
  "InlineQueryReturnManager",
49
61
  "InlineQueryView",
50
62
  "Manager",
63
+ "MediaGroupReplyHandler",
51
64
  "MessageReplyHandler",
52
65
  "MessageReturnManager",
53
66
  "MessageView",
67
+ "PhotoReplyHandler",
54
68
  "RawEventView",
55
69
  "ShortState",
70
+ "StickerReplyHandler",
56
71
  "TelegrinderContext",
72
+ "VideoReplyHandler",
57
73
  "ViewBox",
58
74
  "WaiterMachine",
59
75
  "check_rule",
76
+ "clear_wm_storage_worker",
60
77
  "process_inner",
61
78
  "register_manager",
62
- "clear_wm_storage_worker",
63
79
  )
@@ -1,7 +1,7 @@
1
1
  import typing
2
2
  from abc import ABC, abstractmethod
3
3
 
4
- from telegrinder.api import API
4
+ from telegrinder.api.api import API
5
5
  from telegrinder.tools.global_context.abc import ABCGlobalContext
6
6
  from telegrinder.types.objects import Update
7
7
 
@@ -2,7 +2,7 @@ import enum
2
2
  import typing
3
3
  from reprlib import recursive_repr
4
4
 
5
- from telegrinder.types import Update
5
+ from telegrinder.types.objects import Update
6
6
 
7
7
  T = typing.TypeVar("T")
8
8
 
@@ -63,7 +63,7 @@ class Context(dict[str, AnyValue]):
63
63
  return key if isinstance(key, str) else str(key.value)
64
64
 
65
65
  def copy(self) -> typing.Self:
66
- return self.__class__(**self)
66
+ return self.__class__(**dict.copy(self))
67
67
 
68
68
  def set(self, key: Key, value: AnyValue) -> None:
69
69
  self[key] = value
@@ -3,7 +3,7 @@ import typing
3
3
 
4
4
  from vbml.patcher import Patcher
5
5
 
6
- from telegrinder.api import API
6
+ from telegrinder.api.api import API
7
7
  from telegrinder.bot.cute_types.base import BaseCute
8
8
  from telegrinder.bot.cute_types.update import UpdateCute
9
9
  from telegrinder.bot.dispatch.abc import ABCDispatch
@@ -1,5 +1,21 @@
1
1
  from telegrinder.bot.dispatch.handler.abc import ABCHandler
2
+ from telegrinder.bot.dispatch.handler.audio_reply import AudioReplyHandler
3
+ from telegrinder.bot.dispatch.handler.document_reply import DocumentReplyHandler
2
4
  from telegrinder.bot.dispatch.handler.func import FuncHandler
5
+ from telegrinder.bot.dispatch.handler.media_group_reply import MediaGroupReplyHandler
3
6
  from telegrinder.bot.dispatch.handler.message_reply import MessageReplyHandler
7
+ from telegrinder.bot.dispatch.handler.photo_reply import PhotoReplyHandler
8
+ from telegrinder.bot.dispatch.handler.sticker_reply import StickerReplyHandler
9
+ from telegrinder.bot.dispatch.handler.video_reply import VideoReplyHandler
4
10
 
5
- __all__ = ("ABCHandler", "FuncHandler", "MessageReplyHandler")
11
+ __all__ = (
12
+ "ABCHandler",
13
+ "AudioReplyHandler",
14
+ "DocumentReplyHandler",
15
+ "FuncHandler",
16
+ "MediaGroupReplyHandler",
17
+ "MessageReplyHandler",
18
+ "PhotoReplyHandler",
19
+ "StickerReplyHandler",
20
+ "VideoReplyHandler",
21
+ )
@@ -0,0 +1,44 @@
1
+ import typing
2
+
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.cute_types.message import MessageCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.types.objects import InputFile
9
+
10
+
11
+ class AudioReplyHandler(BaseReplyHandler):
12
+ def __init__(
13
+ self,
14
+ audio: InputFile | str,
15
+ *rules: ABCRule,
16
+ caption: str | None = None,
17
+ parse_mode: str | None = None,
18
+ is_blocking: bool = True,
19
+ as_reply: bool = False,
20
+ preset_context: Context | None = None,
21
+ **default_params: typing.Any,
22
+ ) -> None:
23
+ self.audio = audio
24
+ self.parse_mode = parse_mode
25
+ self.caption = caption
26
+ super().__init__(
27
+ *rules,
28
+ is_blocking=is_blocking,
29
+ as_reply=as_reply,
30
+ preset_context=preset_context,
31
+ **default_params,
32
+ )
33
+
34
+ async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
35
+ method = event.answer_audio if not self.as_reply else event.reply_audio
36
+ await method(
37
+ audio=self.audio,
38
+ parse_mode=self.parse_mode,
39
+ caption=self.caption,
40
+ **self.default_params,
41
+ )
42
+
43
+
44
+ __all__ = ("AudioReplyHandler",)
@@ -0,0 +1,57 @@
1
+ import abc
2
+ import typing
3
+
4
+ from fntypes.result import Result
5
+
6
+ from telegrinder.api.api import API
7
+ from telegrinder.api.error import APIError
8
+ from telegrinder.bot.cute_types.message import MessageCute
9
+ from telegrinder.bot.dispatch.context import Context
10
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
11
+ from telegrinder.bot.dispatch.process import check_rule
12
+ from telegrinder.bot.rules.abc import ABCRule
13
+ from telegrinder.modules import logger
14
+ from telegrinder.types.objects import Update
15
+
16
+ APIMethod: typing.TypeAlias = typing.Callable[
17
+ typing.Concatenate[MessageCute, ...], typing.Awaitable[Result[typing.Any, APIError]]
18
+ ]
19
+
20
+
21
+ class BaseReplyHandler(ABCHandler[MessageCute], abc.ABC):
22
+ def __init__(
23
+ self,
24
+ *rules: ABCRule,
25
+ is_blocking: bool = True,
26
+ as_reply: bool = False,
27
+ preset_context: Context | None = None,
28
+ **default_params: typing.Any,
29
+ ) -> None:
30
+ self.rules = list(rules)
31
+ self.as_reply = as_reply
32
+ self.is_blocking = is_blocking
33
+ self.default_params = default_params
34
+ self.preset_context = preset_context or Context()
35
+
36
+ def __repr__(self) -> str:
37
+ return f"<{self.__class__.__qualname__}>"
38
+
39
+ async def check(self, api: API, event: Update, ctx: Context | None = None) -> bool:
40
+ ctx = Context(raw_update=event) if ctx is None else ctx
41
+ temp_ctx = ctx.copy()
42
+ temp_ctx |= self.preset_context
43
+
44
+ for rule in self.rules:
45
+ if not await check_rule(api, rule, event, ctx):
46
+ logger.debug("Rule {!r} failed!", rule)
47
+ return False
48
+
49
+ ctx |= temp_ctx
50
+ return True
51
+
52
+ @abc.abstractmethod
53
+ async def run(self, api: API, event: MessageCute, ctx: Context) -> typing.Any:
54
+ pass
55
+
56
+
57
+ __all__ = ("BaseReplyHandler",)
@@ -0,0 +1,44 @@
1
+ import typing
2
+
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.cute_types.message import MessageCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.types.objects import InputFile
9
+
10
+
11
+ class DocumentReplyHandler(BaseReplyHandler):
12
+ def __init__(
13
+ self,
14
+ document: InputFile | str,
15
+ *rules: ABCRule,
16
+ caption: str | None = None,
17
+ parse_mode: str | None = None,
18
+ is_blocking: bool = True,
19
+ as_reply: bool = False,
20
+ preset_context: Context | None = None,
21
+ **default_params: typing.Any,
22
+ ) -> None:
23
+ self.document = document
24
+ self.parse_mode = parse_mode
25
+ self.caption = caption
26
+ super().__init__(
27
+ *rules,
28
+ is_blocking=is_blocking,
29
+ as_reply=as_reply,
30
+ preset_context=preset_context,
31
+ **default_params,
32
+ )
33
+
34
+ async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
35
+ method = event.answer_document if not self.as_reply else event.reply_document
36
+ await method(
37
+ document=self.document,
38
+ parse_mode=self.parse_mode,
39
+ caption=self.caption,
40
+ **self.default_params,
41
+ )
42
+
43
+
44
+ __all__ = ("DocumentReplyHandler",)
@@ -3,7 +3,7 @@ from functools import cached_property
3
3
 
4
4
  import typing_extensions as typing
5
5
 
6
- from telegrinder.api import API
6
+ from telegrinder.api.api import API
7
7
  from telegrinder.bot.cute_types import BaseCute, UpdateCute
8
8
  from telegrinder.bot.dispatch.context import Context
9
9
  from telegrinder.bot.dispatch.process import check_rule
@@ -72,7 +72,7 @@ class FuncHandler(ABCHandler[Event], typing.Generic[Event, F, ErrorHandlerT]):
72
72
  if nodes:
73
73
  result = await compose_nodes(nodes, ctx, data={Update: event, API: api})
74
74
  if not result:
75
- logger.debug(f"Cannot compose nodes for handler. {result.error}")
75
+ logger.debug(f"Cannot compose nodes for handler. Error: {result.error!r}")
76
76
  return False
77
77
 
78
78
  node_col = result.value
@@ -95,7 +95,7 @@ class FuncHandler(ABCHandler[Event], typing.Generic[Event, F, ErrorHandlerT]):
95
95
  return True
96
96
 
97
97
  async def run(self, api: API, event: Event, ctx: Context) -> typing.Any:
98
- logger.debug(f"Running func handler {self.func}")
98
+ logger.debug(f"Running func handler {self.func.__qualname__!r}")
99
99
  dataclass_type = typing.get_origin(self.dataclass) or self.dataclass
100
100
 
101
101
  if dataclass_type is Update and (event_node := ctx.pop(EVENT_NODE_KEY, None)) is not None:
@@ -0,0 +1,43 @@
1
+ import typing
2
+
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.cute_types.message import InputMediaType, MessageCute
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.dispatch.handler.base import BaseReplyHandler
7
+ from telegrinder.bot.rules.abc import ABCRule
8
+
9
+
10
+ class MediaGroupReplyHandler(BaseReplyHandler):
11
+ def __init__(
12
+ self,
13
+ media: InputMediaType | list[InputMediaType],
14
+ *rules: ABCRule,
15
+ caption: str | list[str] | None = None,
16
+ parse_mode: str | list[str] | None = None,
17
+ is_blocking: bool = True,
18
+ as_reply: bool = False,
19
+ preset_context: Context | None = None,
20
+ **default_params: typing.Any,
21
+ ) -> None:
22
+ self.media = media
23
+ self.parse_mode = parse_mode
24
+ self.caption = caption
25
+ super().__init__(
26
+ *rules,
27
+ is_blocking=is_blocking,
28
+ as_reply=as_reply,
29
+ preset_context=preset_context,
30
+ **default_params,
31
+ )
32
+
33
+ async def run(self, _: API, event: MessageCute, __: Context) -> typing.Any:
34
+ method = event.answer_media_group if not self.as_reply else event.reply_media_group
35
+ await method(
36
+ media=self.media,
37
+ parse_mode=self.parse_mode,
38
+ caption=self.caption,
39
+ **self.default_params,
40
+ )
41
+
42
+
43
+ __all__ = ("MediaGroupReplyHandler",)