telegrinder 0.1.dev20__py3-none-any.whl → 0.1.dev158__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 (132) hide show
  1. telegrinder/__init__.py +129 -22
  2. telegrinder/api/__init__.py +11 -2
  3. telegrinder/api/abc.py +25 -9
  4. telegrinder/api/api.py +29 -24
  5. telegrinder/api/error.py +14 -4
  6. telegrinder/api/response.py +11 -7
  7. telegrinder/bot/__init__.py +68 -7
  8. telegrinder/bot/bot.py +30 -24
  9. telegrinder/bot/cute_types/__init__.py +11 -1
  10. telegrinder/bot/cute_types/base.py +47 -0
  11. telegrinder/bot/cute_types/callback_query.py +64 -14
  12. telegrinder/bot/cute_types/inline_query.py +22 -16
  13. telegrinder/bot/cute_types/message.py +145 -53
  14. telegrinder/bot/cute_types/update.py +23 -0
  15. telegrinder/bot/dispatch/__init__.py +56 -3
  16. telegrinder/bot/dispatch/abc.py +9 -7
  17. telegrinder/bot/dispatch/composition.py +74 -0
  18. telegrinder/bot/dispatch/context.py +71 -0
  19. telegrinder/bot/dispatch/dispatch.py +86 -49
  20. telegrinder/bot/dispatch/handler/__init__.py +3 -0
  21. telegrinder/bot/dispatch/handler/abc.py +11 -5
  22. telegrinder/bot/dispatch/handler/func.py +41 -32
  23. telegrinder/bot/dispatch/handler/message_reply.py +46 -0
  24. telegrinder/bot/dispatch/middleware/__init__.py +2 -0
  25. telegrinder/bot/dispatch/middleware/abc.py +10 -4
  26. telegrinder/bot/dispatch/process.py +53 -49
  27. telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
  28. telegrinder/bot/dispatch/return_manager/abc.py +95 -0
  29. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  30. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  31. telegrinder/bot/dispatch/return_manager/message.py +25 -0
  32. telegrinder/bot/dispatch/view/__init__.py +14 -2
  33. telegrinder/bot/dispatch/view/abc.py +121 -2
  34. telegrinder/bot/dispatch/view/box.py +38 -0
  35. telegrinder/bot/dispatch/view/callback_query.py +13 -39
  36. telegrinder/bot/dispatch/view/inline_query.py +11 -39
  37. telegrinder/bot/dispatch/view/message.py +11 -47
  38. telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
  39. telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
  40. telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
  41. telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
  42. telegrinder/bot/polling/__init__.py +2 -0
  43. telegrinder/bot/polling/abc.py +11 -4
  44. telegrinder/bot/polling/polling.py +89 -40
  45. telegrinder/bot/rules/__init__.py +91 -5
  46. telegrinder/bot/rules/abc.py +81 -63
  47. telegrinder/bot/rules/adapter/__init__.py +11 -0
  48. telegrinder/bot/rules/adapter/abc.py +21 -0
  49. telegrinder/bot/rules/adapter/errors.py +5 -0
  50. telegrinder/bot/rules/adapter/event.py +43 -0
  51. telegrinder/bot/rules/adapter/raw_update.py +24 -0
  52. telegrinder/bot/rules/callback_data.py +159 -38
  53. telegrinder/bot/rules/command.py +116 -0
  54. telegrinder/bot/rules/enum_text.py +28 -0
  55. telegrinder/bot/rules/func.py +17 -17
  56. telegrinder/bot/rules/fuzzy.py +13 -10
  57. telegrinder/bot/rules/inline.py +61 -0
  58. telegrinder/bot/rules/integer.py +12 -7
  59. telegrinder/bot/rules/is_from.py +148 -7
  60. telegrinder/bot/rules/markup.py +21 -18
  61. telegrinder/bot/rules/mention.py +17 -0
  62. telegrinder/bot/rules/message_entities.py +33 -0
  63. telegrinder/bot/rules/regex.py +27 -19
  64. telegrinder/bot/rules/rule_enum.py +74 -0
  65. telegrinder/bot/rules/start.py +25 -13
  66. telegrinder/bot/rules/text.py +23 -14
  67. telegrinder/bot/scenario/__init__.py +2 -0
  68. telegrinder/bot/scenario/abc.py +12 -5
  69. telegrinder/bot/scenario/checkbox.py +48 -30
  70. telegrinder/bot/scenario/choice.py +16 -10
  71. telegrinder/client/__init__.py +2 -0
  72. telegrinder/client/abc.py +8 -21
  73. telegrinder/client/aiohttp.py +30 -21
  74. telegrinder/model.py +68 -37
  75. telegrinder/modules.py +189 -21
  76. telegrinder/msgspec_json.py +14 -0
  77. telegrinder/msgspec_utils.py +207 -0
  78. telegrinder/node/__init__.py +31 -0
  79. telegrinder/node/attachment.py +71 -0
  80. telegrinder/node/base.py +93 -0
  81. telegrinder/node/composer.py +71 -0
  82. telegrinder/node/container.py +22 -0
  83. telegrinder/node/message.py +18 -0
  84. telegrinder/node/rule.py +56 -0
  85. telegrinder/node/source.py +31 -0
  86. telegrinder/node/text.py +13 -0
  87. telegrinder/node/tools/__init__.py +3 -0
  88. telegrinder/node/tools/generator.py +40 -0
  89. telegrinder/node/update.py +12 -0
  90. telegrinder/rules.py +1 -1
  91. telegrinder/tools/__init__.py +165 -4
  92. telegrinder/tools/buttons.py +75 -51
  93. telegrinder/tools/error_handler/__init__.py +8 -0
  94. telegrinder/tools/error_handler/abc.py +30 -0
  95. telegrinder/tools/error_handler/error_handler.py +156 -0
  96. telegrinder/tools/formatting/__init__.py +81 -3
  97. telegrinder/tools/formatting/html.py +283 -37
  98. telegrinder/tools/formatting/links.py +32 -0
  99. telegrinder/tools/formatting/spec_html_formats.py +121 -0
  100. telegrinder/tools/global_context/__init__.py +12 -0
  101. telegrinder/tools/global_context/abc.py +66 -0
  102. telegrinder/tools/global_context/global_context.py +451 -0
  103. telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
  104. telegrinder/tools/i18n/__init__.py +12 -0
  105. telegrinder/tools/i18n/base.py +31 -0
  106. telegrinder/tools/i18n/middleware/__init__.py +3 -0
  107. telegrinder/tools/i18n/middleware/base.py +26 -0
  108. telegrinder/tools/i18n/simple.py +48 -0
  109. telegrinder/tools/inline_query.py +684 -0
  110. telegrinder/tools/kb_set/__init__.py +2 -0
  111. telegrinder/tools/kb_set/base.py +3 -0
  112. telegrinder/tools/kb_set/yaml.py +28 -17
  113. telegrinder/tools/keyboard.py +84 -62
  114. telegrinder/tools/loop_wrapper/__init__.py +4 -0
  115. telegrinder/tools/loop_wrapper/abc.py +18 -0
  116. telegrinder/tools/loop_wrapper/loop_wrapper.py +132 -0
  117. telegrinder/tools/magic.py +48 -23
  118. telegrinder/tools/parse_mode.py +1 -2
  119. telegrinder/types/__init__.py +1 -0
  120. telegrinder/types/enums.py +651 -0
  121. telegrinder/types/methods.py +3920 -1251
  122. telegrinder/types/objects.py +4702 -1718
  123. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev158.dist-info}/LICENSE +2 -1
  124. telegrinder-0.1.dev158.dist-info/METADATA +108 -0
  125. telegrinder-0.1.dev158.dist-info/RECORD +126 -0
  126. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev158.dist-info}/WHEEL +1 -1
  127. telegrinder/bot/dispatch/waiter.py +0 -38
  128. telegrinder/result.py +0 -38
  129. telegrinder/tools/formatting/abc.py +0 -52
  130. telegrinder/tools/formatting/markdown.py +0 -57
  131. telegrinder-0.1.dev20.dist-info/METADATA +0 -22
  132. telegrinder-0.1.dev20.dist-info/RECORD +0 -71
@@ -1,28 +1,78 @@
1
- from telegrinder.types import CallbackQuery, User
2
- from telegrinder.model import get_params
3
- from telegrinder.api import API, APIError
4
- from telegrinder.result import Result
5
1
  import typing
2
+ from contextlib import suppress
3
+
4
+ import msgspec
5
+ from fntypes.co import Result, Some, Variative
6
+
7
+ from telegrinder.api import ABCAPI, APIError
8
+ from telegrinder.model import get_params
9
+ from telegrinder.msgspec_utils import Nothing, Option, decoder
10
+ from telegrinder.types import (
11
+ CallbackQuery,
12
+ InlineKeyboardMarkup,
13
+ Message,
14
+ MessageEntity,
15
+ User,
16
+ )
17
+
18
+ from .base import BaseCute
6
19
 
7
20
 
8
- class CallbackQueryCute(CallbackQuery):
9
- api: API
21
+ class CallbackQueryCute(BaseCute[CallbackQuery], CallbackQuery, kw_only=True, dict=True):
22
+ api: ABCAPI
10
23
 
11
24
  @property
12
25
  def from_user(self) -> User:
13
26
  return self.from_
27
+
28
+ def decode_callback_data(self, *, strict: bool = True) -> Option[dict]:
29
+ if "cached_callback_data" in self.__dict__:
30
+ return self.__dict__["cached_callback_data"]
14
31
 
15
- @property
16
- def ctx_api(self) -> API:
17
- return self.api
32
+ data = Nothing
33
+ with suppress(msgspec.ValidationError):
34
+ data = Some(decoder.decode(self.data.unwrap()))
35
+
36
+ self.__dict__["cached_callback_data"] = data
37
+ return data
18
38
 
19
39
  async def answer(
20
40
  self,
21
- text: typing.Optional[str] = None,
22
- show_alert: typing.Optional[bool] = None,
23
- url: typing.Optional[str] = None,
24
- cache_time: typing.Optional[int] = None,
25
- **other
41
+ text: str | Option[str] = Nothing,
42
+ show_alert: bool | Option[bool] = Nothing,
43
+ url: str | Option[str] = Nothing,
44
+ cache_time: int | Option[int] = Nothing,
45
+ **other: typing.Any,
26
46
  ) -> Result[bool, APIError]:
27
47
  params = get_params(locals())
28
48
  return await self.ctx_api.answer_callback_query(self.id, **params)
49
+
50
+ async def edit_text(
51
+ self,
52
+ text: str | Option[str],
53
+ parse_mode: str | Option[str] = Nothing,
54
+ entities: list[MessageEntity] | Option[list[MessageEntity]] = Nothing,
55
+ disable_web_page_preview: bool | Option[bool] = Nothing,
56
+ reply_markup: InlineKeyboardMarkup
57
+ | Option[InlineKeyboardMarkup]
58
+ = Nothing,
59
+ **other: typing.Any,
60
+ ) -> Result[Variative[Message, bool], APIError]:
61
+ params = get_params(locals())
62
+
63
+ if message := self.message.map(lambda message: message.only().unwrap_or_none()).unwrap_or_none():
64
+ if message.message_thread_id and "message_thread_id" not in params:
65
+ params["message_thread_id"] = message.message_thread_id.unwrap()
66
+ return await self.ctx_api.edit_message_text(
67
+ chat_id=message.chat.id,
68
+ message_id=message.message_id,
69
+ **params,
70
+ )
71
+
72
+ return await self.ctx_api.edit_message_text(
73
+ inline_message_id=self.inline_message_id,
74
+ **params,
75
+ )
76
+
77
+
78
+ __all__ = ("CallbackQueryCute",)
@@ -1,35 +1,41 @@
1
- from telegrinder.types import InlineQuery, User
2
- from telegrinder.api import API, APIError
3
- from telegrinder.result import Result
4
1
  import typing
5
2
 
3
+ from fntypes.result import Result
6
4
 
7
- class InlineQueryCute(InlineQuery):
8
- api: API
5
+ from telegrinder.api import ABCAPI, APIError
6
+ from telegrinder.msgspec_utils import Nothing, Option
7
+ from telegrinder.types import InlineQuery, InlineQueryResult, User
8
+
9
+ from .base import BaseCute
10
+
11
+
12
+ class InlineQueryCute(BaseCute[InlineQuery], InlineQuery, kw_only=True):
13
+ api: ABCAPI
9
14
 
10
15
  @property
11
16
  def from_user(self) -> User:
12
17
  return self.from_
13
18
 
14
- @property
15
- def ctx_api(self) -> API:
16
- return self.api
17
-
18
19
  async def answer(
19
20
  self,
20
- results: typing.Optional[list] = None,
21
- cache_time: typing.Optional[int] = None,
22
- is_personal: typing.Optional[bool] = None,
23
- next_offset: typing.Optional[str] = None,
24
- switch_pm_text: typing.Optional[str] = None,
25
- switch_pm_parameter: typing.Optional[str] = None,
21
+ results: InlineQueryResult | list[InlineQueryResult],
22
+ cache_time: int | Option[int] = Nothing,
23
+ is_personal: bool | Option[bool] = Nothing,
24
+ next_offset: str | Option[str] = Nothing,
25
+ switch_pm_text: str | Option[str] = Nothing,
26
+ switch_pm_parameter: str | Option[str] = Nothing,
27
+ **other: typing.Any,
26
28
  ) -> Result[bool, APIError]:
27
29
  return await self.ctx_api.answer_inline_query(
28
30
  self.id,
29
- results=results,
31
+ results=[results] if not isinstance(results, list) else results,
30
32
  cache_time=cache_time,
31
33
  is_personal=is_personal,
32
34
  next_offset=next_offset,
33
35
  switch_pm_text=switch_pm_text,
34
36
  switch_pm_parameter=switch_pm_parameter,
37
+ **other,
35
38
  )
39
+
40
+
41
+ __all__ = ("InlineQueryCute",)
@@ -1,67 +1,131 @@
1
+ import typing
2
+
3
+ from fntypes.co import Option, Result, Some, Variative
4
+
5
+ from telegrinder.api import ABCAPI, APIError
6
+ from telegrinder.model import get_params
7
+ from telegrinder.msgspec_utils import Nothing
1
8
  from telegrinder.types import (
9
+ ForceReply,
10
+ InlineKeyboardMarkup,
2
11
  Message,
3
12
  MessageEntity,
13
+ ReactionType,
14
+ ReactionTypeEmoji,
15
+ ReactionTypeType,
16
+ ReplyKeyboardMarkup,
17
+ ReplyKeyboardRemove,
18
+ ReplyParameters,
19
+ User,
20
+ )
21
+ from telegrinder.types.objects import InputFile
22
+
23
+ from .base import BaseCute
24
+
25
+ ReplyMarkup = typing.Union[
4
26
  InlineKeyboardMarkup,
5
27
  ReplyKeyboardMarkup,
6
28
  ReplyKeyboardRemove,
7
29
  ForceReply,
8
- )
9
- from telegrinder.model import get_params
10
- from telegrinder.api import API, APIError
11
- from telegrinder.result import Result
12
- import typing
30
+ ]
31
+
32
+
33
+ def get_entity_value(
34
+ entities: list[MessageEntity], entity_value: str
35
+ ) -> Option[typing.Any]:
36
+ for entity in entities:
37
+ if (obj := getattr(entity, entity_value, Nothing)):
38
+ return Some(obj.value if isinstance(obj, Some) else obj)
39
+ return Nothing
13
40
 
14
41
 
15
- class MessageCute(Message):
16
- api: API
42
+ class MessageCute(BaseCute[Message], Message, kw_only=True):
43
+ api: ABCAPI
17
44
 
18
45
  @property
19
- def ctx_api(self) -> API:
20
- return self.api
46
+ def mentioned_user(self) -> Option[User]:
47
+ """Mentioned user without username."""
48
+
49
+ if not self.entities:
50
+ return Nothing
51
+ return get_entity_value(self.entities.unwrap(), "user")
52
+
53
+ @property
54
+ def url(self) -> Option[str]:
55
+ """Clickable text URL."""
56
+
57
+ if not self.entities:
58
+ return Nothing
59
+ return get_entity_value(self.entities.unwrap(), "url")
60
+
61
+ @property
62
+ def programming_language(self) -> Option[str]:
63
+ """The programming language of the entity text."""
64
+
65
+ if not self.entities:
66
+ return Nothing
67
+ return get_entity_value(self.entities.unwrap(), "language")
68
+
69
+ @property
70
+ def custom_emoji_id(self) -> Option[str]:
71
+ """Unique identifier of the custom emoji."""
72
+
73
+ if not self.entities:
74
+ return Nothing
75
+ return get_entity_value(self.entities.unwrap(), "custom_emoji_id")
21
76
 
22
77
  async def answer(
23
78
  self,
24
- text: typing.Optional[str] = None,
25
- parse_mode: typing.Optional[str] = None,
26
- entities: typing.Optional[typing.List["MessageEntity"]] = None,
27
- disable_web_page_preview: typing.Optional[bool] = None,
28
- disable_notification: typing.Optional[bool] = None,
29
- protect_content: typing.Optional[bool] = None,
30
- reply_to_message_id: typing.Optional[int] = None,
31
- allow_sending_without_reply: typing.Optional[bool] = None,
32
- reply_markup: typing.Optional[
33
- typing.Union[
34
- "InlineKeyboardMarkup",
35
- "ReplyKeyboardMarkup",
36
- "ReplyKeyboardRemove",
37
- "ForceReply",
38
- ]
39
- ] = None,
40
- **other
79
+ text: str | Option[str],
80
+ parse_mode: str | Option[str] = Nothing,
81
+ entities: list[MessageEntity] | Option[list[MessageEntity]] = Nothing,
82
+ disable_web_page_preview: bool | Option[bool] = Nothing,
83
+ disable_notification: bool | Option[bool] = Nothing,
84
+ protect_content: bool | Option[bool] = Nothing,
85
+ reply_to_message_id: int | Option[int] = Nothing,
86
+ allow_sending_without_reply: bool | Option[bool] = Nothing,
87
+ reply_markup: ReplyMarkup | Option[ReplyMarkup] = Nothing,
88
+ **other: typing.Any,
41
89
  ) -> Result["Message", APIError]:
42
90
  params = get_params(locals())
43
91
  if "message_thread_id" not in params and self.is_topic_message:
44
92
  params["message_thread_id"] = self.message_thread_id
45
93
  return await self.ctx_api.send_message(chat_id=self.chat.id, **params)
94
+
95
+ async def answer_file(
96
+ self,
97
+ file: str | InputFile,
98
+ caption: str | Option[str] = Nothing,
99
+ parse_mode: str | Option[str] = Nothing,
100
+ thumbnail: Option[InputFile | str] | InputFile | str = Nothing,
101
+ caption_entities: list[MessageEntity] | Option[list[MessageEntity]] = Nothing,
102
+ disable_content_type_detection: Option[bool] | bool = Nothing,
103
+ disable_notification: Option[bool] | bool = Nothing,
104
+ protect_content: bool | Option[bool] = Nothing,
105
+ reply_parameters: Option[ReplyParameters] | ReplyParameters = Nothing,
106
+ reply_markup: ReplyMarkup | Option[ReplyMarkup] = Nothing,
107
+ **other: typing.Any,
108
+ ) -> Result[Message, APIError]:
109
+ params = get_params(locals())
110
+ if "message_thread_id" not in params and self.is_topic_message:
111
+ params["message_thread_id"] = self.message_thread_id
112
+ return await self.ctx_api.send_document(
113
+ chat_id=self.chat.id,
114
+ document=file,
115
+ **params,
116
+ )
46
117
 
47
118
  async def reply(
48
119
  self,
49
- text: typing.Optional[str] = None,
50
- parse_mode: typing.Optional[str] = None,
51
- entities: typing.Optional[typing.List["MessageEntity"]] = None,
52
- disable_web_page_preview: typing.Optional[bool] = None,
53
- disable_notification: typing.Optional[bool] = None,
54
- protect_content: typing.Optional[bool] = None,
55
- allow_sending_without_reply: typing.Optional[bool] = None,
56
- reply_markup: typing.Optional[
57
- typing.Union[
58
- "InlineKeyboardMarkup",
59
- "ReplyKeyboardMarkup",
60
- "ReplyKeyboardRemove",
61
- "ForceReply",
62
- ]
63
- ] = None,
64
- **other
120
+ text: str | Option[str],
121
+ parse_mode: str | Option[str] = Nothing,
122
+ entities: list[MessageEntity] | Option[list[MessageEntity]] = Nothing,
123
+ disable_web_page_preview: bool | Option[bool] = Nothing,
124
+ disable_notification: bool | Option[bool] = Nothing,
125
+ protect_content: bool | Option[bool] = Nothing,
126
+ allow_sending_without_reply: bool | Option[bool] = Nothing,
127
+ reply_markup: ReplyMarkup | Option[ReplyMarkup] = Nothing,
128
+ **other: typing.Any,
65
129
  ) -> Result["Message", APIError]:
66
130
  params = get_params(locals())
67
131
  if "message_thread_id" not in params and self.is_topic_message:
@@ -69,33 +133,61 @@ class MessageCute(Message):
69
133
  return await self.ctx_api.send_message(
70
134
  chat_id=self.chat.id,
71
135
  reply_to_message_id=self.message_id,
72
- **params
136
+ **params,
73
137
  )
74
138
 
75
- async def delete(self, **other) -> Result[bool, APIError]:
139
+ async def delete(self, **other: typing.Any) -> Result[bool, APIError]:
76
140
  params = get_params(locals())
77
141
  if "message_thread_id" not in params and self.is_topic_message:
78
142
  params["message_thread_id"] = self.message_thread_id
79
143
  return await self.ctx_api.delete_message(
80
144
  chat_id=self.chat.id,
81
145
  message_id=self.message_id,
82
- **params
146
+ **params,
83
147
  )
84
148
 
85
149
  async def edit(
86
150
  self,
87
- text: typing.Optional[str] = None,
88
- parse_mode: typing.Optional[str] = None,
89
- entities: typing.Optional[typing.List[MessageEntity]] = None,
90
- disable_web_page_preview: typing.Optional[bool] = None,
91
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
92
- **other
93
- ) -> Result[Message | bool, APIError]:
151
+ text: str | Option[str] = Nothing,
152
+ parse_mode: str | Option[str] = Nothing,
153
+ entities: list[MessageEntity] | Option[list[MessageEntity]] = Nothing,
154
+ disable_web_page_preview: bool | Option[bool] = Nothing,
155
+ reply_markup: InlineKeyboardMarkup
156
+ | Option[InlineKeyboardMarkup] = Nothing,
157
+ **other: typing.Any,
158
+ ) -> Result[Variative[Message, bool], APIError]:
94
159
  params = get_params(locals())
95
160
  if "message_thread_id" not in params and self.is_topic_message:
96
161
  params["message_thread_id"] = self.message_thread_id
97
162
  return await self.ctx_api.edit_message_text(
98
163
  chat_id=self.chat.id,
99
164
  message_id=self.message_id,
100
- **params
165
+ **params,
166
+ )
167
+
168
+ async def react(
169
+ self,
170
+ reaction: str | ReactionType
171
+ | list[str | ReactionType]
172
+ | Option[list[str | ReactionType]] = Nothing,
173
+ is_big: bool | Option[bool] = Nothing,
174
+ **other: typing.Any,
175
+ ) -> Result[bool, APIError]:
176
+ if reaction:
177
+ reaction = [
178
+ ReactionTypeEmoji(ReactionTypeType.EMOJI, r)
179
+ if isinstance(r, str)
180
+ else r
181
+ for r in (
182
+ reaction.unwrap_or([]) if isinstance(reaction, Some | type(Nothing))
183
+ else [reaction] if not isinstance(reaction, list) else reaction
184
+ )
185
+ ]
186
+ return await self.ctx_api.set_message_reaction(
187
+ chat_id=self.chat.id,
188
+ message_id=self.message_id,
189
+ **get_params(locals())
101
190
  )
191
+
192
+
193
+ __all__ = ("MessageCute", "get_entity_value")
@@ -0,0 +1,23 @@
1
+ from fntypes.co import Nothing, Some
2
+
3
+ from telegrinder.api import ABCAPI
4
+ from telegrinder.msgspec_utils import Option
5
+ from telegrinder.types import Update, UpdateType
6
+
7
+ from .base import BaseCute
8
+
9
+
10
+ class UpdateCute(BaseCute[Update], Update, kw_only=True):
11
+ api: ABCAPI
12
+
13
+ @property
14
+ def update_type(self) -> Option[UpdateType]:
15
+ for name, update in self.to_dict(
16
+ exclude_fields={"update_id"},
17
+ ).items():
18
+ if update is not None:
19
+ return Some(UpdateType(name))
20
+ return Nothing()
21
+
22
+
23
+ __all__ = ("UpdateCute",)
@@ -1,5 +1,58 @@
1
1
  from .abc import ABCDispatch
2
- from .dispatch import ABCDispatch, Dispatch, ABCRule
3
- from .handler import ABCHandler, FuncHandler
2
+ from .composition import CompositionDispatch
3
+ from .context import Context
4
+ from .dispatch import ABCRule, Dispatch, TelegrinderCtx
5
+ from .handler import ABCHandler, FuncHandler, MessageReplyHandler
4
6
  from .middleware import ABCMiddleware
5
- from .view import ABCView, MessageView, CallbackQueryView, InlineQueryView
7
+ from .process import check_rule, process_inner
8
+ from .return_manager import (
9
+ ABCReturnManager,
10
+ BaseReturnManager,
11
+ CallbackQueryReturnManager,
12
+ InlineQueryReturnManager,
13
+ Manager,
14
+ MessageReturnManager,
15
+ register_manager,
16
+ )
17
+ from .view import (
18
+ ABCStateView,
19
+ ABCView,
20
+ BaseStateView,
21
+ BaseView,
22
+ CallbackQueryView,
23
+ InlineQueryView,
24
+ MessageView,
25
+ ViewBox,
26
+ )
27
+ from .waiter_machine import WaiterMachine
28
+
29
+ __all__ = (
30
+ "ABCDispatch",
31
+ "ABCHandler",
32
+ "ABCMiddleware",
33
+ "ABCReturnManager",
34
+ "ABCRule",
35
+ "ABCStateView",
36
+ "ABCView",
37
+ "BaseReturnManager",
38
+ "BaseStateView",
39
+ "BaseView",
40
+ "CallbackQueryReturnManager",
41
+ "CallbackQueryView",
42
+ "CompositionDispatch",
43
+ "Context",
44
+ "Dispatch",
45
+ "FuncHandler",
46
+ "InlineQueryReturnManager",
47
+ "InlineQueryView",
48
+ "Manager",
49
+ "MessageReplyHandler",
50
+ "MessageReturnManager",
51
+ "MessageView",
52
+ "TelegrinderCtx",
53
+ "ViewBox",
54
+ "WaiterMachine",
55
+ "check_rule",
56
+ "process_inner",
57
+ "register_manager",
58
+ )
@@ -1,19 +1,21 @@
1
+ import typing
1
2
  from abc import ABC, abstractmethod
3
+
2
4
  from telegrinder.api.abc import ABCAPI
5
+ from telegrinder.tools.global_context import ABCGlobalContext
3
6
  from telegrinder.types import Update
4
- from .view.abc import ABCView
5
- import typing
6
7
 
7
8
 
8
9
  class ABCDispatch(ABC):
9
- @abstractmethod
10
- def feed(self, event: Update, api: ABCAPI) -> bool:
11
- pass
10
+ global_context: ABCGlobalContext
12
11
 
13
12
  @abstractmethod
14
- def load(self, external: "ABCDispatch"):
13
+ async def feed(self, event: Update, api: ABCAPI) -> bool:
15
14
  pass
16
15
 
17
16
  @abstractmethod
18
- def mount(self, view_t: typing.Type["ABCView"], name: str):
17
+ def load(self, external: typing.Self):
19
18
  pass
19
+
20
+
21
+ __all__ = ("ABCDispatch",)
@@ -0,0 +1,74 @@
1
+ import inspect
2
+ import typing
3
+
4
+ from telegrinder.api.abc import ABCAPI
5
+ from telegrinder.bot.cute_types import UpdateCute
6
+ from telegrinder.bot.dispatch.abc import ABCDispatch
7
+ from telegrinder.node import (
8
+ ComposeError,
9
+ ContainerNode,
10
+ Node,
11
+ NodeCollection,
12
+ NodeSession,
13
+ compose_node,
14
+ )
15
+ from telegrinder.tools import magic_bundle
16
+ from telegrinder.types import Update
17
+
18
+
19
+ class Composition:
20
+ nodes: dict[str, type[Node]]
21
+
22
+ def __init__(self, func: typing.Callable, is_blocking: bool) -> None:
23
+ self.func = func
24
+ self.nodes = {
25
+ name: parameter.annotation
26
+ for name, parameter in inspect.signature(func).parameters.items()
27
+ }
28
+ self.is_blocking = is_blocking
29
+
30
+ async def compose_nodes(self, update: UpdateCute) -> NodeCollection | None:
31
+ nodes: dict[str, NodeSession] = {}
32
+ for name, node_t in self.nodes.items():
33
+ try:
34
+ nodes[name] = await compose_node(node_t, update)
35
+ except ComposeError as err:
36
+ await NodeCollection(nodes).close_all()
37
+ return None
38
+ return NodeCollection(nodes)
39
+
40
+ async def __call__(self, **kwargs) -> typing.Any:
41
+ return await self.func(**magic_bundle(self.func, kwargs, start_idx=0, bundle_ctx=False)) # type: ignore
42
+
43
+
44
+ class CompositionDispatch(ABCDispatch):
45
+ def __init__(self) -> None:
46
+ self.compositions: list[Composition] = []
47
+
48
+ async def feed(self, event: Update, api: ABCAPI) -> bool:
49
+ update = UpdateCute(**event.to_dict(), api=api)
50
+ is_found = False
51
+ for composition in self.compositions:
52
+ nodes = await composition.compose_nodes(update)
53
+ if nodes is not None:
54
+ result = await composition(**nodes.values())
55
+ await nodes.close_all(with_value=result)
56
+ if composition.is_blocking:
57
+ return True
58
+ is_found = True
59
+ return is_found
60
+
61
+ def load(self, external: typing.Self):
62
+ self.compositions.extend(external.compositions)
63
+
64
+ def __call__(self, *container_nodes: type[Node], is_blocking: bool = True):
65
+ def wrapper(func: typing.Callable):
66
+ composition = Composition(func, is_blocking)
67
+ if container_nodes:
68
+ composition.nodes["container"] = ContainerNode.link_nodes(list(container_nodes))
69
+ self.compositions.append(composition)
70
+ return func
71
+ return wrapper
72
+
73
+
74
+ __all__ = ("Composition", "CompositionDispatch")
@@ -0,0 +1,71 @@
1
+ import enum
2
+ import typing
3
+
4
+ from telegrinder.types import Update
5
+
6
+ T = typing.TypeVar("T")
7
+
8
+ Key: typing.TypeAlias = str | enum.Enum
9
+ AnyValue: typing.TypeAlias = typing.Any
10
+
11
+
12
+ @typing.dataclass_transform(kw_only_default=True, order_default=True)
13
+ class Context(dict[str, AnyValue]):
14
+ """Context class for rules and middlewares.
15
+ ```
16
+ class MyRule(ABCRule[T]):
17
+ adapter: ABCAdapter[Update, T] = RawUpdateAdapter()
18
+
19
+ async def check(self, event: T, ctx: Context) -> bool:
20
+ ctx.set("value", (await event.ctx_api.get_me()).unwrap())
21
+ return True
22
+ ```
23
+ """
24
+
25
+ raw_update: Update
26
+
27
+ def __init__(self, **kwargs: AnyValue) -> None:
28
+ cls_vars = vars(self.__class__)
29
+ defaults = {}
30
+ for k in self.__class__.__annotations__:
31
+ if k in cls_vars:
32
+ defaults[k] = cls_vars[k]
33
+ delattr(self.__class__, k)
34
+ dict.__init__(self, **defaults | kwargs)
35
+
36
+ def __setitem__(self, __key: Key, __value: AnyValue) -> None:
37
+ dict.__setitem__(self, self.key_to_str(__key), __value)
38
+
39
+ def __getitem__(self, __key: Key) -> AnyValue:
40
+ return dict.__getitem__(self, self.key_to_str(__key))
41
+
42
+ def __delitem__(self, __key: Key) -> None:
43
+ dict.__delitem__(self, self.key_to_str(__key))
44
+
45
+ def __setattr__(self, __name: str, __value: AnyValue) -> None:
46
+ self.__setitem__(__name, __value)
47
+
48
+ def __getattr__(self, __name: str) -> AnyValue:
49
+ return self.__getitem__(__name)
50
+
51
+ def __delattr__(self, __name: str) -> None:
52
+ self.__delitem__(__name)
53
+
54
+ @staticmethod
55
+ def key_to_str(key: Key) -> str:
56
+ return key if isinstance(key, str) else str(key.value)
57
+
58
+ def copy(self) -> typing.Self:
59
+ return self.__class__(**self)
60
+
61
+ def set(self, key: Key, value: AnyValue) -> None:
62
+ self[key] = value
63
+
64
+ def get(self, key: Key, default: T | None = None) -> T | AnyValue:
65
+ return dict.get(self, key, default)
66
+
67
+ def delete(self, key: Key) -> None:
68
+ del self[key]
69
+
70
+
71
+ __all__ = ("Context",)