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.
- telegrinder/__init__.py +129 -22
- telegrinder/api/__init__.py +11 -2
- telegrinder/api/abc.py +25 -9
- telegrinder/api/api.py +29 -24
- telegrinder/api/error.py +14 -4
- telegrinder/api/response.py +11 -7
- telegrinder/bot/__init__.py +68 -7
- telegrinder/bot/bot.py +30 -24
- telegrinder/bot/cute_types/__init__.py +11 -1
- telegrinder/bot/cute_types/base.py +47 -0
- telegrinder/bot/cute_types/callback_query.py +64 -14
- telegrinder/bot/cute_types/inline_query.py +22 -16
- telegrinder/bot/cute_types/message.py +145 -53
- telegrinder/bot/cute_types/update.py +23 -0
- telegrinder/bot/dispatch/__init__.py +56 -3
- telegrinder/bot/dispatch/abc.py +9 -7
- telegrinder/bot/dispatch/composition.py +74 -0
- telegrinder/bot/dispatch/context.py +71 -0
- telegrinder/bot/dispatch/dispatch.py +86 -49
- telegrinder/bot/dispatch/handler/__init__.py +3 -0
- telegrinder/bot/dispatch/handler/abc.py +11 -5
- telegrinder/bot/dispatch/handler/func.py +41 -32
- telegrinder/bot/dispatch/handler/message_reply.py +46 -0
- telegrinder/bot/dispatch/middleware/__init__.py +2 -0
- telegrinder/bot/dispatch/middleware/abc.py +10 -4
- telegrinder/bot/dispatch/process.py +53 -49
- telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
- telegrinder/bot/dispatch/return_manager/abc.py +95 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +25 -0
- telegrinder/bot/dispatch/view/__init__.py +14 -2
- telegrinder/bot/dispatch/view/abc.py +121 -2
- telegrinder/bot/dispatch/view/box.py +38 -0
- telegrinder/bot/dispatch/view/callback_query.py +13 -39
- telegrinder/bot/dispatch/view/inline_query.py +11 -39
- telegrinder/bot/dispatch/view/message.py +11 -47
- telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
- telegrinder/bot/polling/__init__.py +2 -0
- telegrinder/bot/polling/abc.py +11 -4
- telegrinder/bot/polling/polling.py +89 -40
- telegrinder/bot/rules/__init__.py +91 -5
- telegrinder/bot/rules/abc.py +81 -63
- telegrinder/bot/rules/adapter/__init__.py +11 -0
- telegrinder/bot/rules/adapter/abc.py +21 -0
- telegrinder/bot/rules/adapter/errors.py +5 -0
- telegrinder/bot/rules/adapter/event.py +43 -0
- telegrinder/bot/rules/adapter/raw_update.py +24 -0
- telegrinder/bot/rules/callback_data.py +159 -38
- telegrinder/bot/rules/command.py +116 -0
- telegrinder/bot/rules/enum_text.py +28 -0
- telegrinder/bot/rules/func.py +17 -17
- telegrinder/bot/rules/fuzzy.py +13 -10
- telegrinder/bot/rules/inline.py +61 -0
- telegrinder/bot/rules/integer.py +12 -7
- telegrinder/bot/rules/is_from.py +148 -7
- telegrinder/bot/rules/markup.py +21 -18
- telegrinder/bot/rules/mention.py +17 -0
- telegrinder/bot/rules/message_entities.py +33 -0
- telegrinder/bot/rules/regex.py +27 -19
- telegrinder/bot/rules/rule_enum.py +74 -0
- telegrinder/bot/rules/start.py +25 -13
- telegrinder/bot/rules/text.py +23 -14
- telegrinder/bot/scenario/__init__.py +2 -0
- telegrinder/bot/scenario/abc.py +12 -5
- telegrinder/bot/scenario/checkbox.py +48 -30
- telegrinder/bot/scenario/choice.py +16 -10
- telegrinder/client/__init__.py +2 -0
- telegrinder/client/abc.py +8 -21
- telegrinder/client/aiohttp.py +30 -21
- telegrinder/model.py +68 -37
- telegrinder/modules.py +189 -21
- telegrinder/msgspec_json.py +14 -0
- telegrinder/msgspec_utils.py +207 -0
- telegrinder/node/__init__.py +31 -0
- telegrinder/node/attachment.py +71 -0
- telegrinder/node/base.py +93 -0
- telegrinder/node/composer.py +71 -0
- telegrinder/node/container.py +22 -0
- telegrinder/node/message.py +18 -0
- telegrinder/node/rule.py +56 -0
- telegrinder/node/source.py +31 -0
- telegrinder/node/text.py +13 -0
- telegrinder/node/tools/__init__.py +3 -0
- telegrinder/node/tools/generator.py +40 -0
- telegrinder/node/update.py +12 -0
- telegrinder/rules.py +1 -1
- telegrinder/tools/__init__.py +165 -4
- telegrinder/tools/buttons.py +75 -51
- telegrinder/tools/error_handler/__init__.py +8 -0
- telegrinder/tools/error_handler/abc.py +30 -0
- telegrinder/tools/error_handler/error_handler.py +156 -0
- telegrinder/tools/formatting/__init__.py +81 -3
- telegrinder/tools/formatting/html.py +283 -37
- telegrinder/tools/formatting/links.py +32 -0
- telegrinder/tools/formatting/spec_html_formats.py +121 -0
- telegrinder/tools/global_context/__init__.py +12 -0
- telegrinder/tools/global_context/abc.py +66 -0
- telegrinder/tools/global_context/global_context.py +451 -0
- telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
- telegrinder/tools/i18n/__init__.py +12 -0
- telegrinder/tools/i18n/base.py +31 -0
- telegrinder/tools/i18n/middleware/__init__.py +3 -0
- telegrinder/tools/i18n/middleware/base.py +26 -0
- telegrinder/tools/i18n/simple.py +48 -0
- telegrinder/tools/inline_query.py +684 -0
- telegrinder/tools/kb_set/__init__.py +2 -0
- telegrinder/tools/kb_set/base.py +3 -0
- telegrinder/tools/kb_set/yaml.py +28 -17
- telegrinder/tools/keyboard.py +84 -62
- telegrinder/tools/loop_wrapper/__init__.py +4 -0
- telegrinder/tools/loop_wrapper/abc.py +18 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +132 -0
- telegrinder/tools/magic.py +48 -23
- telegrinder/tools/parse_mode.py +1 -2
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/enums.py +651 -0
- telegrinder/types/methods.py +3920 -1251
- telegrinder/types/objects.py +4702 -1718
- {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev158.dist-info}/LICENSE +2 -1
- telegrinder-0.1.dev158.dist-info/METADATA +108 -0
- telegrinder-0.1.dev158.dist-info/RECORD +126 -0
- {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev158.dist-info}/WHEEL +1 -1
- telegrinder/bot/dispatch/waiter.py +0 -38
- telegrinder/result.py +0 -38
- telegrinder/tools/formatting/abc.py +0 -52
- telegrinder/tools/formatting/markdown.py +0 -57
- telegrinder-0.1.dev20.dist-info/METADATA +0 -22
- 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:
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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:
|
|
22
|
-
show_alert:
|
|
23
|
-
url:
|
|
24
|
-
cache_time:
|
|
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
|
-
|
|
8
|
-
|
|
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:
|
|
21
|
-
cache_time:
|
|
22
|
-
is_personal:
|
|
23
|
-
next_offset:
|
|
24
|
-
switch_pm_text:
|
|
25
|
-
switch_pm_parameter:
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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:
|
|
42
|
+
class MessageCute(BaseCute[Message], Message, kw_only=True):
|
|
43
|
+
api: ABCAPI
|
|
17
44
|
|
|
18
45
|
@property
|
|
19
|
-
def
|
|
20
|
-
|
|
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:
|
|
25
|
-
parse_mode:
|
|
26
|
-
entities:
|
|
27
|
-
disable_web_page_preview:
|
|
28
|
-
disable_notification:
|
|
29
|
-
protect_content:
|
|
30
|
-
reply_to_message_id:
|
|
31
|
-
allow_sending_without_reply:
|
|
32
|
-
reply_markup:
|
|
33
|
-
|
|
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:
|
|
50
|
-
parse_mode:
|
|
51
|
-
entities:
|
|
52
|
-
disable_web_page_preview:
|
|
53
|
-
disable_notification:
|
|
54
|
-
protect_content:
|
|
55
|
-
allow_sending_without_reply:
|
|
56
|
-
reply_markup:
|
|
57
|
-
|
|
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:
|
|
88
|
-
parse_mode:
|
|
89
|
-
entities:
|
|
90
|
-
disable_web_page_preview:
|
|
91
|
-
reply_markup:
|
|
92
|
-
|
|
93
|
-
|
|
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 .
|
|
3
|
-
from .
|
|
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 .
|
|
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
|
+
)
|
telegrinder/bot/dispatch/abc.py
CHANGED
|
@@ -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
|
-
|
|
10
|
-
def feed(self, event: Update, api: ABCAPI) -> bool:
|
|
11
|
-
pass
|
|
10
|
+
global_context: ABCGlobalContext
|
|
12
11
|
|
|
13
12
|
@abstractmethod
|
|
14
|
-
def
|
|
13
|
+
async def feed(self, event: Update, api: ABCAPI) -> bool:
|
|
15
14
|
pass
|
|
16
15
|
|
|
17
16
|
@abstractmethod
|
|
18
|
-
def
|
|
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",)
|