telegrinder 0.1.dev165__py3-none-any.whl → 0.1.dev167__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 +20 -0
- telegrinder/api/abc.py +1 -1
- telegrinder/api/api.py +8 -6
- telegrinder/api/error.py +2 -3
- telegrinder/bot/__init__.py +12 -0
- telegrinder/bot/bot.py +2 -2
- telegrinder/bot/cute_types/__init__.py +4 -0
- telegrinder/bot/cute_types/base.py +10 -10
- telegrinder/bot/cute_types/callback_query.py +1 -3
- telegrinder/bot/cute_types/chat_join_request.py +65 -0
- telegrinder/bot/cute_types/chat_member_updated.py +246 -0
- telegrinder/bot/cute_types/message.py +44 -38
- telegrinder/bot/cute_types/update.py +5 -4
- telegrinder/bot/cute_types/utils.py +40 -20
- telegrinder/bot/dispatch/__init__.py +8 -1
- telegrinder/bot/dispatch/composition.py +7 -7
- telegrinder/bot/dispatch/context.py +9 -10
- telegrinder/bot/dispatch/dispatch.py +30 -22
- telegrinder/bot/dispatch/handler/abc.py +1 -0
- telegrinder/bot/dispatch/handler/func.py +21 -5
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/abc.py +2 -4
- telegrinder/bot/dispatch/process.py +4 -3
- telegrinder/bot/dispatch/return_manager/__init__.py +1 -1
- telegrinder/bot/dispatch/return_manager/abc.py +28 -20
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -2
- telegrinder/bot/dispatch/return_manager/inline_query.py +4 -2
- telegrinder/bot/dispatch/return_manager/message.py +8 -4
- telegrinder/bot/dispatch/view/__init__.py +8 -2
- telegrinder/bot/dispatch/view/abc.py +27 -23
- telegrinder/bot/dispatch/view/box.py +74 -11
- telegrinder/bot/dispatch/view/callback_query.py +1 -3
- telegrinder/bot/dispatch/view/chat_join_request.py +17 -0
- telegrinder/bot/dispatch/view/chat_member.py +26 -0
- telegrinder/bot/dispatch/view/message.py +23 -1
- telegrinder/bot/dispatch/view/raw.py +112 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +41 -26
- telegrinder/bot/dispatch/waiter_machine/middleware.py +14 -7
- telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -7
- telegrinder/bot/polling/polling.py +2 -4
- telegrinder/bot/rules/__init__.py +20 -12
- telegrinder/bot/rules/abc.py +0 -9
- telegrinder/bot/rules/adapter/event.py +29 -22
- telegrinder/bot/rules/callback_data.py +15 -18
- telegrinder/bot/rules/chat_join.py +47 -0
- telegrinder/bot/rules/enum_text.py +7 -2
- telegrinder/bot/rules/fuzzy.py +1 -2
- telegrinder/bot/rules/inline.py +3 -3
- telegrinder/bot/rules/is_from.py +39 -51
- telegrinder/bot/rules/markup.py +1 -2
- telegrinder/bot/rules/mention.py +1 -4
- telegrinder/bot/rules/message.py +17 -0
- telegrinder/bot/rules/message_entities.py +1 -1
- telegrinder/bot/rules/regex.py +1 -2
- telegrinder/bot/rules/rule_enum.py +1 -3
- telegrinder/bot/rules/start.py +7 -7
- telegrinder/bot/rules/text.py +2 -1
- telegrinder/bot/rules/update.py +16 -0
- telegrinder/bot/scenario/checkbox.py +5 -7
- telegrinder/client/aiohttp.py +5 -7
- telegrinder/model.py +37 -22
- telegrinder/modules.py +15 -33
- telegrinder/msgspec_utils.py +34 -35
- telegrinder/node/__init__.py +12 -12
- telegrinder/node/attachment.py +21 -7
- telegrinder/node/base.py +14 -13
- telegrinder/node/composer.py +5 -5
- telegrinder/node/container.py +1 -1
- telegrinder/node/message.py +3 -1
- telegrinder/node/rule.py +4 -4
- telegrinder/node/source.py +6 -2
- telegrinder/node/text.py +3 -1
- telegrinder/node/tools/generator.py +1 -1
- telegrinder/tools/__init__.py +3 -1
- telegrinder/tools/buttons.py +4 -6
- telegrinder/tools/error_handler/abc.py +1 -2
- telegrinder/tools/error_handler/error.py +3 -6
- telegrinder/tools/error_handler/error_handler.py +34 -24
- telegrinder/tools/formatting/html.py +9 -5
- telegrinder/tools/formatting/links.py +1 -3
- telegrinder/tools/formatting/spec_html_formats.py +1 -1
- telegrinder/tools/global_context/abc.py +3 -1
- telegrinder/tools/global_context/global_context.py +13 -31
- telegrinder/tools/global_context/telegrinder_ctx.py +1 -1
- telegrinder/tools/i18n/base.py +4 -3
- telegrinder/tools/i18n/middleware/base.py +1 -3
- telegrinder/tools/i18n/simple.py +1 -3
- telegrinder/tools/keyboard.py +1 -1
- telegrinder/tools/limited_dict.py +27 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +18 -14
- telegrinder/tools/magic.py +1 -1
- telegrinder/types/__init__.py +236 -0
- telegrinder/types/enums.py +34 -0
- telegrinder/types/methods.py +52 -47
- telegrinder/types/objects.py +533 -90
- telegrinder/verification_utils.py +4 -1
- {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/METADATA +1 -1
- telegrinder-0.1.dev167.dist-info/RECORD +137 -0
- telegrinder-0.1.dev165.dist-info/RECORD +0 -128
- {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/WHEEL +0 -0
telegrinder/bot/rules/is_from.py
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
|
|
3
|
+
from fntypes.co import Nothing, Some
|
|
4
|
+
|
|
3
5
|
from telegrinder.bot.cute_types.base import BaseCute
|
|
6
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
4
7
|
from telegrinder.bot.dispatch.context import Context
|
|
5
8
|
from telegrinder.msgspec_utils import Option
|
|
6
9
|
from telegrinder.types.enums import ChatType, DiceEmoji
|
|
7
10
|
from telegrinder.types.objects import User
|
|
8
11
|
|
|
9
|
-
from .abc import ABCRule, Message
|
|
12
|
+
from .abc import ABCRule, Message
|
|
13
|
+
from .message import MessageRule
|
|
10
14
|
|
|
11
15
|
T = typing.TypeVar("T", bound=BaseCute)
|
|
12
16
|
|
|
13
17
|
|
|
18
|
+
def get_from_user(obj: typing.Any) -> User:
|
|
19
|
+
assert isinstance(obj, FromUserProto)
|
|
20
|
+
return obj.from_.unwrap() if isinstance(obj.from_, Some | Nothing) else obj.from_
|
|
21
|
+
|
|
22
|
+
|
|
14
23
|
@typing.runtime_checkable
|
|
15
24
|
class FromUserProto(typing.Protocol):
|
|
16
25
|
from_: User | Option[User]
|
|
17
26
|
|
|
18
27
|
|
|
19
28
|
class HasFrom(ABCRule[T]):
|
|
20
|
-
async def check(self, event:
|
|
21
|
-
|
|
29
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
30
|
+
event_model = event.incoming_update.unwrap()
|
|
31
|
+
return isinstance(event_model, FromUserProto) and bool(event_model.from_)
|
|
22
32
|
|
|
23
33
|
|
|
24
34
|
class HasDice(MessageRule):
|
|
@@ -32,7 +42,9 @@ class IsForward(MessageRule):
|
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
class IsForwardType(MessageRule, requires=[IsForward()]):
|
|
35
|
-
def __init__(
|
|
45
|
+
def __init__(
|
|
46
|
+
self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /
|
|
47
|
+
) -> None:
|
|
36
48
|
self.fwd_type = fwd_type
|
|
37
49
|
|
|
38
50
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
@@ -49,29 +61,30 @@ class IsSticker(MessageRule):
|
|
|
49
61
|
return bool(message.sticker)
|
|
50
62
|
|
|
51
63
|
|
|
52
|
-
class IsBot(
|
|
53
|
-
async def check(self,
|
|
54
|
-
return
|
|
64
|
+
class IsBot(ABCRule[T], requires=[HasFrom()]):
|
|
65
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
66
|
+
return get_from_user(event.incoming_update.unwrap()).is_bot
|
|
55
67
|
|
|
56
68
|
|
|
57
|
-
class IsUser(
|
|
58
|
-
async def check(self,
|
|
59
|
-
return not
|
|
69
|
+
class IsUser(ABCRule[T], requires=[HasFrom()]):
|
|
70
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
71
|
+
return not get_from_user(event.incoming_update.unwrap()).is_bot
|
|
60
72
|
|
|
61
73
|
|
|
62
|
-
class IsPremium(
|
|
63
|
-
async def check(self,
|
|
64
|
-
return
|
|
74
|
+
class IsPremium(ABCRule[T], requires=[HasFrom()]):
|
|
75
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
76
|
+
return get_from_user(event.incoming_update.unwrap()).is_premium.unwrap_or(False)
|
|
65
77
|
|
|
66
78
|
|
|
67
|
-
class IsLanguageCode(
|
|
79
|
+
class IsLanguageCode(ABCRule[T], requires=[HasFrom()]):
|
|
68
80
|
def __init__(self, lang_codes: str | list[str], /) -> None:
|
|
69
81
|
self.lang_codes = [lang_codes] if isinstance(lang_codes, str) else lang_codes
|
|
70
82
|
|
|
71
|
-
async def check(self,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
84
|
+
return (
|
|
85
|
+
get_from_user(event.incoming_update.unwrap()).language_code.unwrap_or_none()
|
|
86
|
+
in self.lang_codes
|
|
87
|
+
)
|
|
75
88
|
|
|
76
89
|
|
|
77
90
|
class IsForum(MessageRule):
|
|
@@ -79,12 +92,12 @@ class IsForum(MessageRule):
|
|
|
79
92
|
return message.chat.is_forum.unwrap_or(False)
|
|
80
93
|
|
|
81
94
|
|
|
82
|
-
class IsUserId(
|
|
95
|
+
class IsUserId(ABCRule[T], requires=[HasFrom()]):
|
|
83
96
|
def __init__(self, user_ids: int | list[int], /) -> None:
|
|
84
97
|
self.user_ids = [user_ids] if isinstance(user_ids, int) else user_ids
|
|
85
98
|
|
|
86
|
-
async def check(self,
|
|
87
|
-
return
|
|
99
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
100
|
+
return get_from_user(event.incoming_update.unwrap()).id in self.user_ids
|
|
88
101
|
|
|
89
102
|
|
|
90
103
|
class IsChatId(MessageRule):
|
|
@@ -115,44 +128,19 @@ class IsChat(MessageRule):
|
|
|
115
128
|
return message.chat.type in (ChatType.GROUP, ChatType.SUPERGROUP)
|
|
116
129
|
|
|
117
130
|
|
|
118
|
-
class
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
class IsDartDice(MessageRule, requires=[HasDice()]):
|
|
124
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
125
|
-
return message.dice.unwrap().emoji == DiceEmoji.DART
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class IsBasketballDice(MessageRule, requires=[HasDice()]):
|
|
129
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
130
|
-
return message.dice.unwrap().emoji == DiceEmoji.BASKETBALL
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class IsFootballDice(MessageRule, requires=[HasDice()]):
|
|
134
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
135
|
-
return message.dice.unwrap().emoji == DiceEmoji.FOOTBALL
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class IsSlotMachineDice(MessageRule, requires=[HasDice()]):
|
|
139
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
140
|
-
return message.dice.unwrap().emoji == DiceEmoji.SLOT_MACHINE
|
|
141
|
-
|
|
131
|
+
class IsDiceEmoji(MessageRule, requires=[HasDice()]):
|
|
132
|
+
def __init__(self, dice_emoji: DiceEmoji, /) -> None:
|
|
133
|
+
self.dice_emoji = dice_emoji
|
|
142
134
|
|
|
143
|
-
class IsBowlingDice(MessageRule, requires=[HasDice()]):
|
|
144
135
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
145
|
-
return message.dice.unwrap().emoji ==
|
|
136
|
+
return message.dice.unwrap().emoji == self.dice_emoji
|
|
146
137
|
|
|
147
138
|
|
|
148
139
|
__all__ = (
|
|
149
|
-
"IsBasketballDice",
|
|
150
140
|
"IsBot",
|
|
151
|
-
"IsBowlingDice",
|
|
152
141
|
"IsChat",
|
|
153
142
|
"IsChatId",
|
|
154
|
-
"
|
|
155
|
-
"IsDice",
|
|
143
|
+
"IsDiceEmoji",
|
|
156
144
|
"IsForum",
|
|
157
145
|
"IsForward",
|
|
158
146
|
"IsForwardType",
|
telegrinder/bot/rules/markup.py
CHANGED
|
@@ -28,8 +28,7 @@ class Markup(TextMessageRule):
|
|
|
28
28
|
if not isinstance(patterns, list):
|
|
29
29
|
patterns = [patterns]
|
|
30
30
|
self.patterns = [
|
|
31
|
-
vbml.Pattern(pattern) if isinstance(pattern, str) else pattern
|
|
32
|
-
for pattern in patterns
|
|
31
|
+
vbml.Pattern(pattern) if isinstance(pattern, str) else pattern for pattern in patterns
|
|
33
32
|
]
|
|
34
33
|
|
|
35
34
|
async def check(self, message: Message, ctx: Context) -> bool:
|
telegrinder/bot/rules/mention.py
CHANGED
|
@@ -8,10 +8,7 @@ class HasMention(TextMessageRule):
|
|
|
8
8
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
9
9
|
if not message.entities.unwrap_or_none():
|
|
10
10
|
return False
|
|
11
|
-
return any(
|
|
12
|
-
entity.type == MessageEntityType.MENTION
|
|
13
|
-
for entity in message.entities.unwrap()
|
|
14
|
-
)
|
|
11
|
+
return any(entity.type == MessageEntityType.MENTION for entity in message.entities.unwrap())
|
|
15
12
|
|
|
16
13
|
|
|
17
14
|
__all__ = ("HasMention",)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
|
|
3
|
+
from telegrinder.bot.dispatch.context import Context
|
|
4
|
+
from telegrinder.types.objects import Message as MessageEvent
|
|
5
|
+
|
|
6
|
+
from .abc import ABCRule, Message
|
|
7
|
+
from .adapter import EventAdapter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MessageRule(ABCRule[Message], abc.ABC):
|
|
11
|
+
adapter = EventAdapter(MessageEvent, Message)
|
|
12
|
+
|
|
13
|
+
@abc.abstractmethod
|
|
14
|
+
async def check(self, message: Message, ctx: Context) -> bool: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ("MessageRule",)
|
|
@@ -4,7 +4,7 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
4
4
|
from telegrinder.types.enums import MessageEntityType
|
|
5
5
|
from telegrinder.types.objects import MessageEntity
|
|
6
6
|
|
|
7
|
-
from .
|
|
7
|
+
from .message import Message, MessageRule
|
|
8
8
|
|
|
9
9
|
Entity: typing.TypeAlias = str | MessageEntityType
|
|
10
10
|
|
telegrinder/bot/rules/regex.py
CHANGED
|
@@ -19,8 +19,7 @@ class Regex(TextMessageRule):
|
|
|
19
19
|
self.regexp.append(re.compile(regex))
|
|
20
20
|
case _:
|
|
21
21
|
self.regexp.extend(
|
|
22
|
-
re.compile(regexp) if isinstance(regexp, str) else regexp
|
|
23
|
-
for regexp in regexp
|
|
22
|
+
re.compile(regexp) if isinstance(regexp, str) else regexp for regexp in regexp
|
|
24
23
|
)
|
|
25
24
|
|
|
26
25
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
@@ -21,9 +21,7 @@ class RuleEnum(ABCRule[T]):
|
|
|
21
21
|
__enum__: list[RuleEnumState]
|
|
22
22
|
|
|
23
23
|
def __init_subclass__(cls, *args, **kwargs):
|
|
24
|
-
new_attributes = (
|
|
25
|
-
set(cls.__dict__) - set(RuleEnum.__dict__) - {"__enum__", "__init__"}
|
|
26
|
-
)
|
|
24
|
+
new_attributes = set(cls.__dict__) - set(RuleEnum.__dict__) - {"__enum__", "__init__"}
|
|
27
25
|
enum_lst: list[RuleEnumState] = []
|
|
28
26
|
|
|
29
27
|
self = cls.__new__(cls)
|
telegrinder/bot/rules/start.py
CHANGED
|
@@ -3,17 +3,19 @@ import typing
|
|
|
3
3
|
from telegrinder.bot.dispatch.context import Context
|
|
4
4
|
from telegrinder.types.enums import MessageEntityType
|
|
5
5
|
|
|
6
|
-
from .abc import MessageRule
|
|
7
6
|
from .is_from import IsPrivate
|
|
8
7
|
from .markup import Markup, Message
|
|
8
|
+
from .message import MessageRule
|
|
9
9
|
from .message_entities import MessageEntities
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class StartCommand(
|
|
13
|
-
MessageRule,
|
|
14
|
-
|
|
13
|
+
MessageRule,
|
|
14
|
+
requires=[
|
|
15
|
+
IsPrivate()
|
|
16
|
+
& MessageEntities(MessageEntityType.BOT_COMMAND)
|
|
15
17
|
& Markup(["/start <param>", "/start"]),
|
|
16
|
-
]
|
|
18
|
+
],
|
|
17
19
|
):
|
|
18
20
|
def __init__(
|
|
19
21
|
self,
|
|
@@ -28,9 +30,7 @@ class StartCommand(
|
|
|
28
30
|
|
|
29
31
|
async def check(self, _: Message, ctx: Context) -> bool:
|
|
30
32
|
param: str | None = ctx.pop("param", None)
|
|
31
|
-
validated_param = (
|
|
32
|
-
self.validator(param) if self.validator and param is not None else param
|
|
33
|
-
)
|
|
33
|
+
validated_param = self.validator(param) if self.validator and param is not None else param
|
|
34
34
|
|
|
35
35
|
if self.param_required and validated_param is None:
|
|
36
36
|
return False
|
telegrinder/bot/rules/text.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from telegrinder.bot.dispatch.context import Context
|
|
2
2
|
from telegrinder.tools.i18n.base import ABCTranslator
|
|
3
3
|
|
|
4
|
-
from .abc import ABC, Message,
|
|
4
|
+
from .abc import ABC, Message, with_caching_translations
|
|
5
|
+
from .message import MessageRule
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class HasText(MessageRule):
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
2
|
+
from telegrinder.bot.dispatch.context import Context
|
|
3
|
+
from telegrinder.types.enums import UpdateType
|
|
4
|
+
|
|
5
|
+
from .abc import ABCRule, T
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IsUpdate(ABCRule[T]):
|
|
9
|
+
def __init__(self, update_type: UpdateType, /) -> None:
|
|
10
|
+
self.update_type = update_type
|
|
11
|
+
|
|
12
|
+
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
13
|
+
return event.update_type.unwrap_or_none() == self.update_type
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ("IsUpdate",)
|
|
@@ -45,7 +45,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
45
45
|
self.max_in_row = max_in_row
|
|
46
46
|
self.random_code = secrets.token_hex(8)
|
|
47
47
|
self.waiter_machine = waiter_machine
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
def __repr__(self) -> str:
|
|
50
50
|
return (
|
|
51
51
|
"<{}@{!r}: (choices={!r}, max_in_row={}) with waiter_machine={!r}, ready_text={!r} "
|
|
@@ -69,14 +69,12 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
69
69
|
choice = choices.pop(0)
|
|
70
70
|
kb.add(
|
|
71
71
|
InlineButton(
|
|
72
|
-
text=choice.default_text
|
|
73
|
-
if not choice.is_picked
|
|
74
|
-
else choice.picked_text,
|
|
72
|
+
text=(choice.default_text if not choice.is_picked else choice.picked_text),
|
|
75
73
|
callback_data=self.random_code + "/" + choice.code,
|
|
76
74
|
)
|
|
77
75
|
)
|
|
78
76
|
kb.row()
|
|
79
|
-
|
|
77
|
+
|
|
80
78
|
kb.add(InlineButton(self.ready, callback_data=self.random_code + "/ready"))
|
|
81
79
|
return kb.get_markup()
|
|
82
80
|
|
|
@@ -125,14 +123,14 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
125
123
|
reply_markup=self.get_markup(),
|
|
126
124
|
)
|
|
127
125
|
).unwrap()
|
|
128
|
-
|
|
126
|
+
|
|
129
127
|
while True:
|
|
130
128
|
q, _ = await self.waiter_machine.wait(view, (api, message.message_id))
|
|
131
129
|
should_continue = await self.handle(q)
|
|
132
130
|
await q.answer(self.CALLBACK_ANSWER)
|
|
133
131
|
if not should_continue:
|
|
134
132
|
break
|
|
135
|
-
|
|
133
|
+
|
|
136
134
|
return (
|
|
137
135
|
{choice.name: choice.is_picked for choice in self.choices},
|
|
138
136
|
message.message_id,
|
telegrinder/client/aiohttp.py
CHANGED
|
@@ -24,7 +24,7 @@ class AiohttpClient(ABCClient):
|
|
|
24
24
|
self.json_processing_module = json_processing_module or json
|
|
25
25
|
self.session_params = session_params
|
|
26
26
|
self.timeout = timeout or aiohttp.ClientTimeout(total=0)
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
def __repr__(self) -> str:
|
|
29
29
|
return "<{}: session={!r}, timeout={}, closed={}>".format(
|
|
30
30
|
self.__class__.__name__,
|
|
@@ -32,7 +32,7 @@ class AiohttpClient(ABCClient):
|
|
|
32
32
|
self.timeout,
|
|
33
33
|
True if self.session is None else self.session.closed,
|
|
34
34
|
)
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
async def request_raw(
|
|
37
37
|
self,
|
|
38
38
|
url: str,
|
|
@@ -42,9 +42,7 @@ class AiohttpClient(ABCClient):
|
|
|
42
42
|
) -> "ClientResponse":
|
|
43
43
|
if not self.session:
|
|
44
44
|
self.session = ClientSession(
|
|
45
|
-
connector=TCPConnector(
|
|
46
|
-
ssl=ssl.create_default_context(cafile=certifi.where())
|
|
47
|
-
),
|
|
45
|
+
connector=TCPConnector(ssl=ssl.create_default_context(cafile=certifi.where())),
|
|
48
46
|
json_serialize=self.json_processing_module.dumps,
|
|
49
47
|
**self.session_params,
|
|
50
48
|
)
|
|
@@ -118,10 +116,10 @@ class AiohttpClient(ABCClient):
|
|
|
118
116
|
form = aiohttp.formdata.FormData(quote_fields=False)
|
|
119
117
|
for k, v in data.items():
|
|
120
118
|
form.add_field(k, str(v))
|
|
121
|
-
|
|
119
|
+
|
|
122
120
|
for n, f in files.items():
|
|
123
121
|
form.add_field(n, f[1], filename=f[0])
|
|
124
|
-
|
|
122
|
+
|
|
125
123
|
return form
|
|
126
124
|
|
|
127
125
|
def __del__(self) -> None:
|
telegrinder/model.py
CHANGED
|
@@ -10,11 +10,11 @@ from fntypes.co import Nothing, Result, Some
|
|
|
10
10
|
|
|
11
11
|
from .msgspec_utils import decoder, encoder, get_origin
|
|
12
12
|
|
|
13
|
-
T = typing.TypeVar("T")
|
|
14
|
-
|
|
15
13
|
if typing.TYPE_CHECKING:
|
|
16
14
|
from telegrinder.api.error import APIError
|
|
17
|
-
|
|
15
|
+
|
|
16
|
+
T = typing.TypeVar("T")
|
|
17
|
+
|
|
18
18
|
|
|
19
19
|
MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
|
|
20
20
|
"omit_defaults": True,
|
|
@@ -25,17 +25,16 @@ MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
|
|
|
25
25
|
|
|
26
26
|
@typing.overload
|
|
27
27
|
def full_result(
|
|
28
|
-
result: Result[msgspec.Raw, "APIError"],
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
result: Result[msgspec.Raw, "APIError"],
|
|
29
|
+
full_t: type[T],
|
|
30
|
+
) -> Result[T, "APIError"]: ...
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@typing.overload
|
|
34
34
|
def full_result(
|
|
35
35
|
result: Result[msgspec.Raw, "APIError"],
|
|
36
36
|
full_t: tuple[type[T], ...],
|
|
37
|
-
) -> Result[T, "APIError"]:
|
|
38
|
-
...
|
|
37
|
+
) -> Result[T, "APIError"]: ...
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
def full_result(
|
|
@@ -65,7 +64,7 @@ class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
|
65
64
|
self,
|
|
66
65
|
*,
|
|
67
66
|
exclude_fields: set[str] | None = None,
|
|
68
|
-
) -> dict[str, typing.Any]:
|
|
67
|
+
) -> dict[str, typing.Any]:
|
|
69
68
|
exclude_fields = exclude_fields or set()
|
|
70
69
|
if "model_as_dict" not in self.__dict__:
|
|
71
70
|
self.__dict__["model_as_dict"] = msgspec.structs.asdict(self)
|
|
@@ -91,13 +90,13 @@ class DataConverter:
|
|
|
91
90
|
return {
|
|
92
91
|
get_origin(value.__annotations__["data"]): value
|
|
93
92
|
for key, value in vars(self.__class__).items()
|
|
94
|
-
if key.startswith("convert_") and callable(value)
|
|
93
|
+
if key.startswith("convert_") and callable(value)
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
@staticmethod
|
|
98
97
|
def convert_enum(data: enum.Enum, _: bool = True) -> typing.Any:
|
|
99
98
|
return data.value
|
|
100
|
-
|
|
99
|
+
|
|
101
100
|
@staticmethod
|
|
102
101
|
def convert_datetime(data: datetime, _: bool = True) -> int:
|
|
103
102
|
return int(data.timestamp())
|
|
@@ -109,29 +108,45 @@ class DataConverter:
|
|
|
109
108
|
return converter(data, serialize)
|
|
110
109
|
return converter(self, data, serialize)
|
|
111
110
|
return data
|
|
112
|
-
|
|
111
|
+
|
|
113
112
|
def get_converter(self, t: type[typing.Any]):
|
|
114
113
|
for type, converter in self.converters.items():
|
|
115
114
|
if issubclass(t, type):
|
|
116
115
|
return converter
|
|
117
116
|
return None
|
|
118
|
-
|
|
119
|
-
def convert_model(
|
|
117
|
+
|
|
118
|
+
def convert_model(
|
|
119
|
+
self,
|
|
120
|
+
data: Model,
|
|
121
|
+
serialize: bool = True,
|
|
122
|
+
) -> str | dict[str, typing.Any]:
|
|
120
123
|
converted_dct = self(data.to_dict(), serialize=False)
|
|
121
124
|
return encoder.encode(converted_dct) if serialize is True else converted_dct
|
|
122
|
-
|
|
123
|
-
def convert_dct(
|
|
125
|
+
|
|
126
|
+
def convert_dct(
|
|
127
|
+
self,
|
|
128
|
+
data: dict[str, typing.Any],
|
|
129
|
+
serialize: bool = True,
|
|
130
|
+
) -> dict[str, typing.Any]:
|
|
124
131
|
return {
|
|
125
132
|
k: self(v, serialize=serialize)
|
|
126
133
|
for k, v in data.items()
|
|
127
134
|
if type(v) not in (NoneType, Nothing)
|
|
128
135
|
}
|
|
129
|
-
|
|
130
|
-
def convert_lst(
|
|
136
|
+
|
|
137
|
+
def convert_lst(
|
|
138
|
+
self,
|
|
139
|
+
data: list[typing.Any],
|
|
140
|
+
serialize: bool = True,
|
|
141
|
+
) -> str | list[typing.Any]:
|
|
131
142
|
converted_lst = [self(x, serialize=False) for x in data]
|
|
132
143
|
return encoder.encode(converted_lst) if serialize is True else converted_lst
|
|
133
|
-
|
|
134
|
-
def convert_tpl(
|
|
144
|
+
|
|
145
|
+
def convert_tpl(
|
|
146
|
+
self,
|
|
147
|
+
data: tuple[typing.Any, ...],
|
|
148
|
+
_: bool = True,
|
|
149
|
+
) -> str | tuple[typing.Any, ...]:
|
|
135
150
|
if (
|
|
136
151
|
isinstance(data, tuple)
|
|
137
152
|
and len(data) == 2
|
|
@@ -142,12 +157,12 @@ class DataConverter:
|
|
|
142
157
|
self.files[attach_name] = data
|
|
143
158
|
return "attach://{}".format(attach_name)
|
|
144
159
|
return data
|
|
145
|
-
|
|
160
|
+
|
|
146
161
|
|
|
147
162
|
__all__ = (
|
|
148
163
|
"DataConverter",
|
|
164
|
+
"MODEL_CONFIG",
|
|
149
165
|
"Model",
|
|
150
166
|
"full_result",
|
|
151
167
|
"get_params",
|
|
152
|
-
"MODEL_CONFIG",
|
|
153
168
|
)
|
telegrinder/modules.py
CHANGED
|
@@ -6,32 +6,24 @@ from choicelib import choice_in_order
|
|
|
6
6
|
|
|
7
7
|
@typing.runtime_checkable
|
|
8
8
|
class JSONModule(typing.Protocol):
|
|
9
|
-
def loads(self, s: str | bytes) -> typing.Any:
|
|
10
|
-
...
|
|
9
|
+
def loads(self, s: str | bytes) -> typing.Any: ...
|
|
11
10
|
|
|
12
|
-
def dumps(self, o: typing.Any) -> str:
|
|
13
|
-
...
|
|
11
|
+
def dumps(self, o: typing.Any) -> str: ...
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
@typing.runtime_checkable
|
|
17
15
|
class LoggerModule(typing.Protocol):
|
|
18
|
-
def debug(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
19
|
-
...
|
|
16
|
+
def debug(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
20
17
|
|
|
21
|
-
def info(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
22
|
-
...
|
|
18
|
+
def info(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
23
19
|
|
|
24
|
-
def warning(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
25
|
-
...
|
|
20
|
+
def warning(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
26
21
|
|
|
27
|
-
def error(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
28
|
-
...
|
|
22
|
+
def error(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
29
23
|
|
|
30
|
-
def critical(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
31
|
-
...
|
|
24
|
+
def critical(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
32
25
|
|
|
33
|
-
def exception(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
34
|
-
...
|
|
26
|
+
def exception(self, __msg: object, *args: object, **kwargs: object) -> None: ...
|
|
35
27
|
|
|
36
28
|
def set_level(
|
|
37
29
|
self,
|
|
@@ -43,8 +35,7 @@ class LoggerModule(typing.Protocol):
|
|
|
43
35
|
"CRITICAL",
|
|
44
36
|
"EXCEPTION",
|
|
45
37
|
],
|
|
46
|
-
) -> None:
|
|
47
|
-
...
|
|
38
|
+
) -> None: ...
|
|
48
39
|
|
|
49
40
|
|
|
50
41
|
logger: LoggerModule
|
|
@@ -147,8 +138,7 @@ elif logging_module == "logging":
|
|
|
147
138
|
},
|
|
148
139
|
}
|
|
149
140
|
FORMAT = (
|
|
150
|
-
FORMAT
|
|
151
|
-
.replace("<white>", COLORS["white"])
|
|
141
|
+
FORMAT.replace("<white>", COLORS["white"])
|
|
152
142
|
.replace("</white>", COLORS["reset"])
|
|
153
143
|
.replace("<green>", COLORS["green"])
|
|
154
144
|
.replace("</green>", COLORS["reset"])
|
|
@@ -157,24 +147,18 @@ elif logging_module == "logging":
|
|
|
157
147
|
for level, settings in LEVEL_SETTINGS.items():
|
|
158
148
|
fmt = FORMAT
|
|
159
149
|
for name, color in settings.items():
|
|
160
|
-
fmt = (
|
|
161
|
-
fmt
|
|
162
|
-
.replace(f"<{name}>", COLORS[color])
|
|
163
|
-
.replace(f"</{name}>", COLORS["reset"])
|
|
164
|
-
)
|
|
150
|
+
fmt = fmt.replace(f"<{name}>", COLORS[color]).replace(f"</{name}>", COLORS["reset"])
|
|
165
151
|
LEVEL_FORMATS[level] = fmt
|
|
166
152
|
|
|
167
|
-
|
|
168
153
|
class TelegrinderLoggingFormatter(logging.Formatter):
|
|
169
|
-
def format(self, record: logging.LogRecord) -> str:
|
|
154
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
170
155
|
if not record.funcName or record.funcName == "<module>":
|
|
171
156
|
record.funcName = "\b"
|
|
172
157
|
frame = next(
|
|
173
158
|
(
|
|
174
159
|
frame
|
|
175
160
|
for frame in inspect.stack()
|
|
176
|
-
if frame.filename == record.pathname
|
|
177
|
-
and frame.lineno == record.lineno
|
|
161
|
+
if frame.filename == record.pathname and frame.lineno == record.lineno
|
|
178
162
|
),
|
|
179
163
|
None,
|
|
180
164
|
)
|
|
@@ -187,7 +171,6 @@ elif logging_module == "logging":
|
|
|
187
171
|
style="{",
|
|
188
172
|
).format(record)
|
|
189
173
|
|
|
190
|
-
|
|
191
174
|
class LogMessage:
|
|
192
175
|
def __init__(self, fmt: typing.Any, args: typing.Any, kwargs: typing.Any) -> None:
|
|
193
176
|
self.fmt = fmt
|
|
@@ -197,7 +180,6 @@ elif logging_module == "logging":
|
|
|
197
180
|
def __str__(self) -> str:
|
|
198
181
|
return self.fmt.format(*self.args, **self.kwargs)
|
|
199
182
|
|
|
200
|
-
|
|
201
183
|
class TelegrinderLoggingStyleAdapter(logging.LoggerAdapter):
|
|
202
184
|
def __init__(
|
|
203
185
|
self,
|
|
@@ -223,7 +205,7 @@ elif logging_module == "logging":
|
|
|
223
205
|
for key in inspect.getfullargspec(self.logger._log).args[1:]
|
|
224
206
|
if key in kwargs
|
|
225
207
|
}
|
|
226
|
-
|
|
208
|
+
|
|
227
209
|
if isinstance(msg, str):
|
|
228
210
|
msg = LogMessage(msg, args, kwargs)
|
|
229
211
|
args = tuple()
|
|
@@ -245,7 +227,7 @@ def _set_logger_level(level):
|
|
|
245
227
|
logging.getLogger("telegrinder").setLevel(logging.getLevelName(level))
|
|
246
228
|
elif logging_module == "loguru":
|
|
247
229
|
import loguru # type: ignore
|
|
248
|
-
|
|
230
|
+
|
|
249
231
|
if handler_id in loguru.logger._core.handlers: # type: ignore
|
|
250
232
|
loguru.logger._core.handlers[handler_id]._levelno = loguru.logger.level(level).no # type: ignore
|
|
251
233
|
|