telegrinder 1.0.0rc1__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.
- telegrinder/__init__.py +258 -0
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +15 -0
- telegrinder/api/api.py +175 -0
- telegrinder/api/error.py +50 -0
- telegrinder/api/response.py +23 -0
- telegrinder/api/token.py +30 -0
- telegrinder/api/validators.py +30 -0
- telegrinder/bot/__init__.py +144 -0
- telegrinder/bot/bot.py +70 -0
- telegrinder/bot/cute_types/__init__.py +41 -0
- telegrinder/bot/cute_types/base.py +228 -0
- telegrinder/bot/cute_types/base.pyi +49 -0
- telegrinder/bot/cute_types/business_connection.py +9 -0
- telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
- telegrinder/bot/cute_types/callback_query.py +248 -0
- telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
- telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
- telegrinder/bot/cute_types/chat_join_request.py +59 -0
- telegrinder/bot/cute_types/chat_member_updated.py +158 -0
- telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
- telegrinder/bot/cute_types/inline_query.py +41 -0
- telegrinder/bot/cute_types/message.py +2809 -0
- telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
- telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
- telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
- telegrinder/bot/cute_types/poll.py +9 -0
- telegrinder/bot/cute_types/poll_answer.py +9 -0
- telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
- telegrinder/bot/cute_types/shipping_query.py +11 -0
- telegrinder/bot/cute_types/update.py +209 -0
- telegrinder/bot/cute_types/utils.py +141 -0
- telegrinder/bot/dispatch/__init__.py +99 -0
- telegrinder/bot/dispatch/abc.py +74 -0
- telegrinder/bot/dispatch/action.py +99 -0
- telegrinder/bot/dispatch/context.py +162 -0
- telegrinder/bot/dispatch/dispatch.py +362 -0
- telegrinder/bot/dispatch/handler/__init__.py +23 -0
- telegrinder/bot/dispatch/handler/abc.py +25 -0
- telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
- telegrinder/bot/dispatch/handler/base.py +34 -0
- telegrinder/bot/dispatch/handler/document_reply.py +43 -0
- telegrinder/bot/dispatch/handler/func.py +73 -0
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +35 -0
- telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
- telegrinder/bot/dispatch/handler/video_reply.py +43 -0
- telegrinder/bot/dispatch/middleware/__init__.py +13 -0
- telegrinder/bot/dispatch/middleware/abc.py +112 -0
- telegrinder/bot/dispatch/middleware/box.py +32 -0
- telegrinder/bot/dispatch/middleware/filter.py +88 -0
- telegrinder/bot/dispatch/middleware/media_group.py +69 -0
- telegrinder/bot/dispatch/process.py +93 -0
- telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
- telegrinder/bot/dispatch/return_manager/abc.py +107 -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 +34 -0
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/utils.py +20 -0
- telegrinder/bot/dispatch/router/__init__.py +4 -0
- telegrinder/bot/dispatch/router/abc.py +15 -0
- telegrinder/bot/dispatch/router/base.py +154 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -0
- telegrinder/bot/dispatch/view/abc.py +15 -0
- telegrinder/bot/dispatch/view/base.py +226 -0
- telegrinder/bot/dispatch/view/box.py +207 -0
- telegrinder/bot/dispatch/view/media_group.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
- telegrinder/bot/polling/__init__.py +4 -0
- telegrinder/bot/polling/abc.py +25 -0
- telegrinder/bot/polling/error_handler.py +93 -0
- telegrinder/bot/polling/polling.py +167 -0
- telegrinder/bot/polling/utils.py +12 -0
- telegrinder/bot/rules/__init__.py +166 -0
- telegrinder/bot/rules/abc.py +150 -0
- telegrinder/bot/rules/button.py +20 -0
- telegrinder/bot/rules/callback_data.py +109 -0
- telegrinder/bot/rules/chat_join.py +28 -0
- telegrinder/bot/rules/chat_member_updated.py +145 -0
- telegrinder/bot/rules/command.py +137 -0
- telegrinder/bot/rules/enum_text.py +29 -0
- telegrinder/bot/rules/func.py +21 -0
- telegrinder/bot/rules/fuzzy.py +21 -0
- telegrinder/bot/rules/inline.py +45 -0
- telegrinder/bot/rules/integer.py +19 -0
- telegrinder/bot/rules/is_from.py +213 -0
- telegrinder/bot/rules/logic.py +22 -0
- telegrinder/bot/rules/magic.py +60 -0
- telegrinder/bot/rules/markup.py +51 -0
- telegrinder/bot/rules/media.py +13 -0
- telegrinder/bot/rules/mention.py +15 -0
- telegrinder/bot/rules/message_entities.py +37 -0
- telegrinder/bot/rules/node.py +43 -0
- telegrinder/bot/rules/payload.py +89 -0
- telegrinder/bot/rules/payment_invoice.py +14 -0
- telegrinder/bot/rules/regex.py +34 -0
- telegrinder/bot/rules/rule_enum.py +71 -0
- telegrinder/bot/rules/start.py +73 -0
- telegrinder/bot/rules/state.py +35 -0
- telegrinder/bot/rules/text.py +27 -0
- telegrinder/bot/rules/update.py +14 -0
- telegrinder/bot/scenario/__init__.py +5 -0
- telegrinder/bot/scenario/abc.py +16 -0
- telegrinder/bot/scenario/checkbox.py +183 -0
- telegrinder/bot/scenario/choice.py +44 -0
- telegrinder/client/__init__.py +11 -0
- telegrinder/client/abc.py +136 -0
- telegrinder/client/form_data.py +34 -0
- telegrinder/client/rnet.py +198 -0
- telegrinder/model.py +133 -0
- telegrinder/model.pyi +57 -0
- telegrinder/modules.py +1081 -0
- telegrinder/msgspec_utils/__init__.py +42 -0
- telegrinder/msgspec_utils/abc.py +16 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +388 -0
- telegrinder/msgspec_utils/encoder.py +204 -0
- telegrinder/msgspec_utils/json.py +15 -0
- telegrinder/msgspec_utils/tools.py +80 -0
- telegrinder/node/__init__.py +80 -0
- telegrinder/node/compose.py +193 -0
- telegrinder/node/nodes/__init__.py +96 -0
- telegrinder/node/nodes/attachment.py +169 -0
- telegrinder/node/nodes/callback_query.py +25 -0
- telegrinder/node/nodes/channel.py +97 -0
- telegrinder/node/nodes/command.py +33 -0
- telegrinder/node/nodes/error.py +43 -0
- telegrinder/node/nodes/event.py +70 -0
- telegrinder/node/nodes/file.py +39 -0
- telegrinder/node/nodes/global_node.py +66 -0
- telegrinder/node/nodes/i18n.py +110 -0
- telegrinder/node/nodes/me.py +26 -0
- telegrinder/node/nodes/message_entities.py +15 -0
- telegrinder/node/nodes/payload.py +84 -0
- telegrinder/node/nodes/reply_message.py +14 -0
- telegrinder/node/nodes/source.py +172 -0
- telegrinder/node/nodes/state_mutator.py +71 -0
- telegrinder/node/nodes/text.py +62 -0
- telegrinder/node/scope.py +88 -0
- telegrinder/node/utils.py +38 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -0
- telegrinder/tools/__init__.py +183 -0
- telegrinder/tools/aio.py +147 -0
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +85 -0
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/deep_links/links.py +468 -0
- telegrinder/tools/formatting/deep_links/parsing.py +88 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html.py +241 -0
- telegrinder/tools/fullname.py +82 -0
- telegrinder/tools/global_context/__init__.py +13 -0
- telegrinder/tools/global_context/abc.py +63 -0
- telegrinder/tools/global_context/builtin_context.py +45 -0
- telegrinder/tools/global_context/global_context.py +614 -0
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard/__init__.py +6 -0
- telegrinder/tools/keyboard/abc.py +84 -0
- telegrinder/tools/keyboard/base.py +108 -0
- telegrinder/tools/keyboard/button.py +181 -0
- telegrinder/tools/keyboard/data.py +31 -0
- telegrinder/tools/keyboard/keyboard.py +160 -0
- telegrinder/tools/keyboard/utils.py +95 -0
- telegrinder/tools/lifespan.py +188 -0
- telegrinder/tools/limited_dict.py +35 -0
- telegrinder/tools/loop_wrapper.py +271 -0
- telegrinder/tools/magic/__init__.py +29 -0
- telegrinder/tools/magic/annotations.py +172 -0
- telegrinder/tools/magic/descriptors.py +57 -0
- telegrinder/tools/magic/function.py +254 -0
- telegrinder/tools/magic/inspect.py +16 -0
- telegrinder/tools/magic/shortcut.py +107 -0
- telegrinder/tools/member_descriptor_proxy.py +95 -0
- telegrinder/tools/parse_mode.py +12 -0
- telegrinder/tools/serialization/__init__.py +5 -0
- telegrinder/tools/serialization/abc.py +34 -0
- telegrinder/tools/serialization/json_ser.py +60 -0
- telegrinder/tools/serialization/msgpack_ser.py +197 -0
- telegrinder/tools/serialization/utils.py +18 -0
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_mutator/__init__.py +4 -0
- telegrinder/tools/state_mutator/mutation.py +85 -0
- telegrinder/tools/state_storage/__init__.py +4 -0
- telegrinder/tools/state_storage/abc.py +38 -0
- telegrinder/tools/state_storage/memory.py +27 -0
- telegrinder/tools/strings.py +22 -0
- telegrinder/types/__init__.py +323 -0
- telegrinder/types/enums.py +754 -0
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +6143 -0
- telegrinder/types/methods_utils.py +66 -0
- telegrinder/types/objects.py +8184 -0
- telegrinder/types/webapp.py +129 -0
- telegrinder/verification_utils.py +35 -0
- telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
- telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
- telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
- telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from kungfu.library.monad.result import Error, Ok
|
|
5
|
+
from nodnod.error import NodeError
|
|
6
|
+
from nodnod.interface.generic import generic_node
|
|
7
|
+
from nodnod.interface.polymorphic import case, polymorphic
|
|
8
|
+
from nodnod.interface.scalar import scalar_node
|
|
9
|
+
|
|
10
|
+
from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
|
|
11
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
12
|
+
from telegrinder.bot.cute_types.pre_checkout_query import PreCheckoutQueryCute
|
|
13
|
+
from telegrinder.bot.cute_types.shipping_query import ShippingQueryCute
|
|
14
|
+
from telegrinder.node.nodes.global_node import GlobalNode
|
|
15
|
+
from telegrinder.tools.serialization.abc import ABCDataSerializer
|
|
16
|
+
from telegrinder.tools.serialization.json_ser import JSONSerializer
|
|
17
|
+
|
|
18
|
+
type _UndefinedSerializer = typing.Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@scalar_node
|
|
22
|
+
@polymorphic[str]
|
|
23
|
+
class Payload:
|
|
24
|
+
@case
|
|
25
|
+
def compose_callback_query(cls, event: CallbackQueryCute) -> str:
|
|
26
|
+
return event.data.expect(NodeError("CallbackQuery has no data."))
|
|
27
|
+
|
|
28
|
+
@case
|
|
29
|
+
def compose_pre_checkout_query(cls, event: PreCheckoutQueryCute) -> str:
|
|
30
|
+
return event.invoice_payload
|
|
31
|
+
|
|
32
|
+
@case
|
|
33
|
+
def compose_shipping_query(cls, event: ShippingQueryCute) -> str:
|
|
34
|
+
return event.invoice_payload
|
|
35
|
+
|
|
36
|
+
@case
|
|
37
|
+
def compose_message(cls, event: MessageCute) -> str:
|
|
38
|
+
return event.successful_payment.map(
|
|
39
|
+
lambda payment: payment.invoice_payload,
|
|
40
|
+
).expect(NodeError("Message has no successful payment."))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclasses.dataclass(frozen=True)
|
|
44
|
+
class PayloadSerializer[T: type[ABCDataSerializer] = typing.Any](GlobalNode[T]):
|
|
45
|
+
serializer: type[ABCDataSerializer[typing.Any]]
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def __compose__(cls) -> typing.Self:
|
|
49
|
+
return cls(serializer=JSONSerializer)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@generic_node
|
|
53
|
+
class _PayloadData[Data, Serializer: ABCDataSerializer = _UndefinedSerializer]:
|
|
54
|
+
@classmethod
|
|
55
|
+
def __compose__(
|
|
56
|
+
cls,
|
|
57
|
+
payload: Payload,
|
|
58
|
+
data: type[Data],
|
|
59
|
+
payload_serializer: type[Serializer],
|
|
60
|
+
global_serializer: PayloadSerializer,
|
|
61
|
+
) -> typing.Any:
|
|
62
|
+
if payload_serializer is _UndefinedSerializer:
|
|
63
|
+
serializer = getattr(data, "__serializer__", global_serializer.serializer)
|
|
64
|
+
else:
|
|
65
|
+
serializer = payload_serializer
|
|
66
|
+
|
|
67
|
+
match serializer(data).deserialize(payload):
|
|
68
|
+
case Ok(value):
|
|
69
|
+
return value
|
|
70
|
+
case Error(err):
|
|
71
|
+
raise NodeError(err)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if typing.TYPE_CHECKING:
|
|
75
|
+
type PayloadData[
|
|
76
|
+
DataType = typing.Any,
|
|
77
|
+
Serializer: ABCDataSerializer = _UndefinedSerializer,
|
|
78
|
+
] = typing.Annotated[DataType, Serializer]
|
|
79
|
+
|
|
80
|
+
else:
|
|
81
|
+
PayloadData = _PayloadData
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = ("Payload", "PayloadData", "PayloadSerializer")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from nodnod.error import NodeError
|
|
2
|
+
from nodnod.interface.scalar import scalar_node
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@scalar_node
|
|
8
|
+
class ReplyMessage:
|
|
9
|
+
@classmethod
|
|
10
|
+
def __compose__(cls, message: MessageCute) -> MessageCute:
|
|
11
|
+
return message.reply_to_message.expect(NodeError("Message doesn't have reply"))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ("ReplyMessage",)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from kungfu.library.monad.option import Nothing, Option, Some
|
|
5
|
+
from nodnod.error import NodeError
|
|
6
|
+
from nodnod.interface.polymorphic import case, polymorphic
|
|
7
|
+
from nodnod.interface.scalar import scalar_node
|
|
8
|
+
|
|
9
|
+
from telegrinder.api.api import API
|
|
10
|
+
from telegrinder.bot.cute_types import (
|
|
11
|
+
CallbackQueryCute,
|
|
12
|
+
ChatJoinRequestCute,
|
|
13
|
+
ChatMemberUpdatedCute,
|
|
14
|
+
ChosenInlineResultCute,
|
|
15
|
+
InlineQueryCute,
|
|
16
|
+
MessageCute,
|
|
17
|
+
MessageReactionUpdatedCute,
|
|
18
|
+
PaidMediaPurchasedCute,
|
|
19
|
+
PollAnswerCute,
|
|
20
|
+
PreCheckoutQueryCute,
|
|
21
|
+
ShippingQueryCute,
|
|
22
|
+
)
|
|
23
|
+
from telegrinder.types.objects import Chat, Message, User
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@scalar_node
|
|
27
|
+
@polymorphic["Source"]
|
|
28
|
+
@dataclasses.dataclass(kw_only=True)
|
|
29
|
+
class Source:
|
|
30
|
+
api: API
|
|
31
|
+
from_user: User
|
|
32
|
+
chat: Option[Chat] = dataclasses.field(default_factory=Nothing)
|
|
33
|
+
thread_id: Option[int] = dataclasses.field(default_factory=Nothing)
|
|
34
|
+
|
|
35
|
+
@case
|
|
36
|
+
def compose_message(cls, message: MessageCute) -> typing.Self:
|
|
37
|
+
return cls(
|
|
38
|
+
api=message.api,
|
|
39
|
+
from_user=message.from_.expect(NodeError("Message is from a channel.")),
|
|
40
|
+
chat=Some(message.chat),
|
|
41
|
+
thread_id=message.message_thread_id,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@case
|
|
45
|
+
def compose_callback_query(cls, callback_query: CallbackQueryCute) -> typing.Self:
|
|
46
|
+
return cls(
|
|
47
|
+
api=callback_query.api,
|
|
48
|
+
from_user=callback_query.from_user,
|
|
49
|
+
chat=callback_query.chat,
|
|
50
|
+
thread_id=callback_query.message_thread_id,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@case
|
|
54
|
+
def compose_inline_query(cls, inline_query: InlineQueryCute) -> typing.Self:
|
|
55
|
+
return cls(
|
|
56
|
+
api=inline_query.api,
|
|
57
|
+
from_user=inline_query.from_user,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@case
|
|
61
|
+
def compose_chosen_inline_result(cls, chosen_inline_result: ChosenInlineResultCute) -> typing.Self:
|
|
62
|
+
return cls(
|
|
63
|
+
api=chosen_inline_result.api,
|
|
64
|
+
from_user=chosen_inline_result.from_user,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@case
|
|
68
|
+
def compose_chat_member_updated(cls, chat_member_updated: ChatMemberUpdatedCute) -> typing.Self:
|
|
69
|
+
return cls(
|
|
70
|
+
api=chat_member_updated.api,
|
|
71
|
+
from_user=chat_member_updated.from_user,
|
|
72
|
+
chat=Some(chat_member_updated.chat),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@case
|
|
76
|
+
def compose_chat_join_request(cls, chat_join_request: ChatJoinRequestCute) -> typing.Self:
|
|
77
|
+
return cls(
|
|
78
|
+
api=chat_join_request.api,
|
|
79
|
+
from_user=chat_join_request.from_user,
|
|
80
|
+
chat=Some(chat_join_request.chat),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@case
|
|
84
|
+
def compose_pre_checkout_query(cls, pre_checkout_query: PreCheckoutQueryCute) -> typing.Self:
|
|
85
|
+
return cls(
|
|
86
|
+
api=pre_checkout_query.api,
|
|
87
|
+
from_user=pre_checkout_query.from_user,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@case
|
|
91
|
+
def compose_message_reaction_updated(cls, message_reaction_updated: MessageReactionUpdatedCute) -> typing.Self:
|
|
92
|
+
return cls(
|
|
93
|
+
api=message_reaction_updated.api,
|
|
94
|
+
from_user=message_reaction_updated.user.expect(NodeError("Message reaction is from an anonymous user.")),
|
|
95
|
+
chat=Some(message_reaction_updated.chat),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@case
|
|
99
|
+
def compose_paid_media_purchased(cls, paid_media_purchased: PaidMediaPurchasedCute) -> typing.Self:
|
|
100
|
+
return cls(
|
|
101
|
+
api=paid_media_purchased.api,
|
|
102
|
+
from_user=paid_media_purchased.from_user,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@case
|
|
106
|
+
def compose_poll_answer(cls, poll_answer: PollAnswerCute) -> typing.Self:
|
|
107
|
+
return cls(
|
|
108
|
+
api=poll_answer.api,
|
|
109
|
+
from_user=poll_answer.user.expect(NodeError("Poll answer is from an anonymous user.")),
|
|
110
|
+
chat=poll_answer.voter_chat,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@case
|
|
114
|
+
def compose_shipping_query(cls, shipping_query: ShippingQueryCute) -> typing.Self:
|
|
115
|
+
return cls(
|
|
116
|
+
api=shipping_query.api,
|
|
117
|
+
from_user=shipping_query.from_user,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
async def send(self, text: str, **kwargs: typing.Any) -> Message:
|
|
121
|
+
result = await self.api.send_message(
|
|
122
|
+
chat_id=self.chat.map_or(self.from_user.id, lambda chat: chat.id).unwrap(),
|
|
123
|
+
message_thread_id=self.thread_id.unwrap_or_none(),
|
|
124
|
+
text=text,
|
|
125
|
+
**kwargs,
|
|
126
|
+
)
|
|
127
|
+
return result.unwrap()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@scalar_node
|
|
131
|
+
class ChatSource:
|
|
132
|
+
@classmethod
|
|
133
|
+
def __compose__(cls, source: Source) -> Chat:
|
|
134
|
+
return source.chat.expect(NodeError("Source has no chat."))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@scalar_node
|
|
138
|
+
class UserSource:
|
|
139
|
+
@classmethod
|
|
140
|
+
def __compose__(cls, source: Source) -> User:
|
|
141
|
+
return source.from_user
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@scalar_node
|
|
145
|
+
class ChatId:
|
|
146
|
+
@classmethod
|
|
147
|
+
def __compose__(cls, chat: ChatSource) -> int:
|
|
148
|
+
return chat.id
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@scalar_node
|
|
152
|
+
class UserId:
|
|
153
|
+
@classmethod
|
|
154
|
+
def __compose__(cls, user: UserSource) -> int:
|
|
155
|
+
return user.id
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@scalar_node
|
|
159
|
+
class Locale:
|
|
160
|
+
@classmethod
|
|
161
|
+
def __compose__(cls, user: UserSource) -> str:
|
|
162
|
+
return user.language_code.expect(NodeError("User has no language code."))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
__all__ = (
|
|
166
|
+
"ChatId",
|
|
167
|
+
"ChatSource",
|
|
168
|
+
"Locale",
|
|
169
|
+
"Source",
|
|
170
|
+
"UserId",
|
|
171
|
+
"UserSource",
|
|
172
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from kungfu.library.monad.option import Some
|
|
4
|
+
from nodnod.error import NodeError
|
|
5
|
+
from nodnod.node import Node
|
|
6
|
+
|
|
7
|
+
from telegrinder.node.nodes.payload import PayloadSerializer
|
|
8
|
+
from telegrinder.node.nodes.source import Source
|
|
9
|
+
from telegrinder.tools.fullname import fullname
|
|
10
|
+
from telegrinder.tools.serialization import ABCDataSerializer
|
|
11
|
+
from telegrinder.tools.state_storage.memory import ABCStateStorage, MemoryStateStorage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StateMutator(Node):
|
|
15
|
+
STORAGE = MemoryStateStorage[str]() # TODO: use nodnod injection to get storage inside StateMutator.compose
|
|
16
|
+
KEY_MAP: dict[str, type[State]] = {}
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
storage: ABCStateStorage[str],
|
|
21
|
+
user_id: int,
|
|
22
|
+
serializer: type[ABCDataSerializer[State]],
|
|
23
|
+
) -> None:
|
|
24
|
+
self.storage = storage
|
|
25
|
+
self.user_id = user_id
|
|
26
|
+
self.serializer = serializer
|
|
27
|
+
|
|
28
|
+
async def get(self) -> State | None:
|
|
29
|
+
match await self.storage.get(self.user_id):
|
|
30
|
+
case Some(state_data) if state_data.key in self.KEY_MAP:
|
|
31
|
+
return (
|
|
32
|
+
self.serializer(self.KEY_MAP[state_data.key])
|
|
33
|
+
.deserialize(state_data.payload)
|
|
34
|
+
.map(lambda state: state.bind(self))
|
|
35
|
+
.unwrap_or_none()
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
async def set(self, state: State) -> None:
|
|
41
|
+
state_cls = state.__class__
|
|
42
|
+
key = fullname(state_cls)
|
|
43
|
+
payload = self.serializer(state_cls).serialize(state)
|
|
44
|
+
await self.storage.set(self.user_id, key, payload)
|
|
45
|
+
self.KEY_MAP[key] = state_cls
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def __compose__(cls, src: Source, serializer: PayloadSerializer) -> typing.Self:
|
|
49
|
+
return cls(cls.STORAGE, src.from_user.id, serializer.serializer)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class State:
|
|
53
|
+
def bind(self, mutator: StateMutator) -> typing.Self:
|
|
54
|
+
self.__mutator__ = mutator
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
async def enter(self) -> None:
|
|
58
|
+
await self.__mutator__.set(state=self)
|
|
59
|
+
|
|
60
|
+
async def exit(self) -> None:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
async def __compose__(cls, mutator: StateMutator) -> typing.Self:
|
|
65
|
+
current_state = await mutator.get()
|
|
66
|
+
if current_state is None or not isinstance(current_state, cls):
|
|
67
|
+
raise NodeError("State mismatch.")
|
|
68
|
+
return current_state
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = ("State", "StateMutator")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from nodnod.error import NodeError
|
|
4
|
+
from nodnod.interface.node_constructor import NodeConstructor
|
|
5
|
+
from nodnod.interface.scalar import scalar_node
|
|
6
|
+
|
|
7
|
+
from telegrinder.bot.cute_types.message import MessageCute
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@scalar_node
|
|
11
|
+
class Caption:
|
|
12
|
+
@classmethod
|
|
13
|
+
def __compose__(cls, message: MessageCute) -> str:
|
|
14
|
+
return message.caption.expect(NodeError("Message has no caption."))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@scalar_node
|
|
18
|
+
class HTMLCaption:
|
|
19
|
+
@classmethod
|
|
20
|
+
def __compose__(cls, message: MessageCute) -> str:
|
|
21
|
+
return message.html_caption.expect(NodeError("Message has no HTML caption."))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@scalar_node
|
|
25
|
+
class Text:
|
|
26
|
+
@classmethod
|
|
27
|
+
def __compose__(cls, message: MessageCute) -> str:
|
|
28
|
+
return message.text.expect(NodeError("Message has no text."))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@scalar_node
|
|
32
|
+
class HTMLText:
|
|
33
|
+
@classmethod
|
|
34
|
+
def __compose__(cls, message: MessageCute) -> str:
|
|
35
|
+
return message.html_text.expect(NodeError("Message has no HTML text."))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@scalar_node
|
|
39
|
+
class TextInteger:
|
|
40
|
+
@classmethod
|
|
41
|
+
def __compose__(cls, text: Text | Caption) -> int:
|
|
42
|
+
if not text.isdigit():
|
|
43
|
+
raise NodeError("Text is not digit.")
|
|
44
|
+
return int(text)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if typing.TYPE_CHECKING:
|
|
48
|
+
from typing import Literal as TextLiteral
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
|
|
52
|
+
class TextLiteral(NodeConstructor):
|
|
53
|
+
def __init__(self, *texts: str) -> None:
|
|
54
|
+
self.texts = texts
|
|
55
|
+
|
|
56
|
+
def __compose__(self, text: Text | Caption) -> str:
|
|
57
|
+
if text in self.texts:
|
|
58
|
+
return text
|
|
59
|
+
raise NodeError("Text mismatched literal.")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
__all__ = ("Caption", "HTMLCaption", "HTMLText", "Text", "TextInteger", "TextLiteral")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Specific `scopes` for node scope system.
|
|
2
|
+
|
|
3
|
+
Scopes:
|
|
4
|
+
- `GLOBAL`: compose only once during runtime, and later be stored and reused when needed ~ `@global_node`
|
|
5
|
+
- `PER_EVENT`: compose once per event, so if during the composition the node was already composed, it will be reused and won't be composed twice ~ `@per_event`
|
|
6
|
+
- `PER_CALL`: compose each time any node will require it to build itself or if we require it to be delivered into the handler ~ `@per_call`
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import enum
|
|
12
|
+
import typing
|
|
13
|
+
|
|
14
|
+
from nodnod.node import Node
|
|
15
|
+
from nodnod.scope import Scope
|
|
16
|
+
|
|
17
|
+
from telegrinder.modules import logger
|
|
18
|
+
from telegrinder.node.utils import as_node
|
|
19
|
+
from telegrinder.tools.global_context.builtin_context import TelegrinderContext
|
|
20
|
+
|
|
21
|
+
if typing.TYPE_CHECKING:
|
|
22
|
+
from telegrinder.node.scope import NodeScope
|
|
23
|
+
|
|
24
|
+
# Import members from NodeScope
|
|
25
|
+
GLOBAL, PER_CALL, PER_EVENT = NodeScope.GLOBAL, NodeScope.PER_CALL, NodeScope.PER_EVENT
|
|
26
|
+
|
|
27
|
+
TELEGRINDER_CONTEXT: typing.Final = TelegrinderContext()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@TELEGRINDER_CONTEXT.loop_wrapper.lifespan.on_shutdown
|
|
31
|
+
async def close_node_global_scope() -> None:
|
|
32
|
+
logger.debug("Closing node global scope")
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
await TELEGRINDER_CONTEXT.close_global_scope()
|
|
36
|
+
except Exception as error:
|
|
37
|
+
logger.error("While closing node global scope, an error occurred: {!r}", error)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class NodeScopeInfo(dict[type[Node], "NodeScope"]):
|
|
41
|
+
def set_node_scope[T: Node[typing.Any, typing.Any]](self, node: type[T], scope: NodeScope, /) -> None:
|
|
42
|
+
self[node] = scope
|
|
43
|
+
|
|
44
|
+
def get_node_scope[T: Node[typing.Any, typing.Any]](self, node: type[T], /) -> NodeScope | None:
|
|
45
|
+
return self.get(node, None)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
NODE_SCOPE_INFO: typing.Final = NodeScopeInfo()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MappedScopes(dict[type[Node], Scope]):
|
|
52
|
+
scopes: dict[NodeScope, Scope]
|
|
53
|
+
|
|
54
|
+
def __init__(self, global_scope: Scope, per_event_scope: Scope) -> None:
|
|
55
|
+
self.scopes = {NodeScope.GLOBAL: global_scope, NodeScope.PER_EVENT: per_event_scope}
|
|
56
|
+
|
|
57
|
+
def get(self, key: type[Node], default: Scope | None = None) -> Scope | None:
|
|
58
|
+
scope = NODE_SCOPE_INFO.get_node_scope(key) or NodeScope.PER_EVENT
|
|
59
|
+
return self.scopes.get(scope, default)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Declare NodeScope members in a global scope
|
|
63
|
+
@enum.global_enum
|
|
64
|
+
class NodeScope(enum.StrEnum):
|
|
65
|
+
GLOBAL = "global"
|
|
66
|
+
PER_CALL = "local"
|
|
67
|
+
PER_EVENT = "event"
|
|
68
|
+
|
|
69
|
+
def __call__[T](self, node: type[T], /) -> type[T]:
|
|
70
|
+
NODE_SCOPE_INFO.set_node_scope(as_node(node), self)
|
|
71
|
+
return node
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Decorators
|
|
75
|
+
global_node, per_call, per_event = GLOBAL, PER_CALL, PER_EVENT
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
__all__ = (
|
|
79
|
+
"GLOBAL",
|
|
80
|
+
"NODE_SCOPE_INFO",
|
|
81
|
+
"PER_CALL",
|
|
82
|
+
"PER_EVENT",
|
|
83
|
+
"MappedScopes",
|
|
84
|
+
"NodeScope",
|
|
85
|
+
"global_node",
|
|
86
|
+
"per_call",
|
|
87
|
+
"per_event",
|
|
88
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from nodnod.interface.is_node import is_node
|
|
5
|
+
from nodnod.node import Node
|
|
6
|
+
|
|
7
|
+
from telegrinder.tools.fullname import fullname
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def as_node(obj: typing.Any, /) -> type[Node[typing.Any, typing.Any]]:
|
|
11
|
+
if not is_node(obj):
|
|
12
|
+
raise TypeError(f"Object `{fullname(obj)}` is not a node.")
|
|
13
|
+
return obj
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_locals_from_function(func: typing.Callable[..., typing.Any], /) -> typing.Mapping[str, typing.Any] | None:
|
|
17
|
+
bound_class = func.__self__ if hasattr(func, "__self__") else None
|
|
18
|
+
bound_class = func.__objclass__ if bound_class is None and hasattr(func, "__objclass__") else None
|
|
19
|
+
|
|
20
|
+
if bound_class is not None:
|
|
21
|
+
return (bound_class if isinstance(bound_class, type) else type(bound_class)).__dict__
|
|
22
|
+
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_globals_from_function(func: typing.Callable[..., typing.Any], /) -> dict[str, typing.Any]:
|
|
27
|
+
if hasattr(func, "__globals__"):
|
|
28
|
+
return getattr(func, "__globals__")
|
|
29
|
+
|
|
30
|
+
module = getattr(func, "__module__", type(func).__module__)
|
|
31
|
+
|
|
32
|
+
if module in sys.modules:
|
|
33
|
+
return vars(sys.modules[module])
|
|
34
|
+
|
|
35
|
+
return {}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = ("as_node", "get_globals_from_function", "get_locals_from_function", "is_node")
|
telegrinder/py.typed
ADDED
|
File without changes
|
telegrinder/rules.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from telegrinder.bot.rules import *
|