telegrinder 0.1.dev164__py3-none-any.whl → 0.1.dev166__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 +58 -0
- telegrinder/api/abc.py +1 -1
- telegrinder/api/api.py +8 -6
- telegrinder/api/error.py +2 -3
- telegrinder/bot/__init__.py +14 -0
- telegrinder/bot/bot.py +13 -2
- telegrinder/bot/cute_types/__init__.py +4 -0
- telegrinder/bot/cute_types/base.py +22 -13
- telegrinder/bot/cute_types/chat_join_request.py +63 -0
- telegrinder/bot/cute_types/chat_member_updated.py +244 -0
- telegrinder/bot/cute_types/message.py +33 -6
- telegrinder/bot/cute_types/update.py +5 -4
- telegrinder/bot/cute_types/utils.py +39 -17
- telegrinder/bot/dispatch/__init__.py +9 -1
- telegrinder/bot/dispatch/composition.py +10 -8
- telegrinder/bot/dispatch/context.py +9 -10
- telegrinder/bot/dispatch/dispatch.py +29 -19
- telegrinder/bot/dispatch/handler/abc.py +1 -0
- telegrinder/bot/dispatch/handler/func.py +29 -6
- 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 +39 -21
- 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 +12 -6
- telegrinder/bot/dispatch/view/__init__.py +8 -2
- telegrinder/bot/dispatch/view/abc.py +26 -20
- telegrinder/bot/dispatch/view/box.py +72 -1
- 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 +116 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -1
- telegrinder/bot/dispatch/waiter_machine/machine.py +73 -19
- telegrinder/bot/dispatch/waiter_machine/middleware.py +3 -3
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -3
- telegrinder/bot/polling/polling.py +17 -1
- telegrinder/bot/rules/__init__.py +20 -12
- telegrinder/bot/rules/abc.py +0 -9
- telegrinder/bot/rules/adapter/event.py +31 -22
- telegrinder/bot/rules/callback_data.py +15 -20
- telegrinder/bot/rules/chat_join.py +47 -0
- telegrinder/bot/rules/enum_text.py +7 -2
- telegrinder/bot/rules/inline.py +3 -3
- telegrinder/bot/rules/is_from.py +36 -50
- telegrinder/bot/rules/markup.py +1 -1
- telegrinder/bot/rules/message.py +17 -0
- telegrinder/bot/rules/message_entities.py +1 -1
- telegrinder/bot/rules/start.py +6 -4
- telegrinder/bot/rules/text.py +2 -1
- telegrinder/bot/rules/update.py +16 -0
- telegrinder/bot/scenario/checkbox.py +9 -7
- telegrinder/client/aiohttp.py +4 -4
- telegrinder/model.py +43 -19
- telegrinder/modules.py +16 -32
- telegrinder/msgspec_utils.py +48 -36
- telegrinder/node/__init__.py +12 -12
- telegrinder/node/attachment.py +15 -5
- telegrinder/node/base.py +24 -16
- telegrinder/node/composer.py +8 -6
- telegrinder/node/container.py +1 -1
- telegrinder/node/rule.py +4 -4
- telegrinder/node/source.py +4 -2
- telegrinder/node/tools/generator.py +1 -1
- telegrinder/tools/__init__.py +2 -1
- telegrinder/tools/error_handler/abc.py +4 -3
- telegrinder/tools/error_handler/error_handler.py +22 -16
- telegrinder/tools/formatting/html.py +20 -18
- telegrinder/tools/formatting/links.py +12 -6
- telegrinder/tools/formatting/spec_html_formats.py +5 -6
- telegrinder/tools/global_context/abc.py +5 -1
- telegrinder/tools/global_context/global_context.py +2 -2
- telegrinder/tools/global_context/telegrinder_ctx.py +1 -1
- telegrinder/tools/i18n/base.py +4 -3
- telegrinder/tools/i18n/simple.py +1 -3
- telegrinder/tools/keyboard.py +1 -1
- telegrinder/tools/loop_wrapper/__init__.py +2 -2
- telegrinder/tools/loop_wrapper/abc.py +1 -4
- telegrinder/tools/loop_wrapper/loop_wrapper.py +90 -36
- telegrinder/tools/magic.py +1 -1
- telegrinder/types/__init__.py +206 -0
- telegrinder/types/enums.py +34 -0
- telegrinder/types/methods.py +52 -47
- telegrinder/types/objects.py +531 -88
- telegrinder/verification_utils.py +33 -0
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/METADATA +1 -1
- telegrinder-0.1.dev166.dist-info/RECORD +136 -0
- telegrinder-0.1.dev164.dist-info/RECORD +0 -127
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev164.dist-info → telegrinder-0.1.dev166.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import datetime
|
|
3
3
|
import typing
|
|
4
|
+
from collections import deque
|
|
4
5
|
|
|
5
6
|
from telegrinder.api.abc import ABCAPI
|
|
6
7
|
from telegrinder.bot.dispatch.context import Context
|
|
@@ -9,17 +10,76 @@ from telegrinder.bot.rules.abc import ABCRule
|
|
|
9
10
|
from .middleware import WaiterMiddleware
|
|
10
11
|
from .short_state import Behaviour, EventModel, ShortState
|
|
11
12
|
|
|
13
|
+
T = typing.TypeVar("T")
|
|
14
|
+
|
|
15
|
+
Storage: typing.TypeAlias = dict[str, "ShortStateStorage"]
|
|
12
16
|
Identificator: typing.TypeAlias = str | int
|
|
13
|
-
Storage: typing.TypeAlias = dict[str, dict[Identificator, "ShortState"]]
|
|
14
17
|
|
|
15
18
|
if typing.TYPE_CHECKING:
|
|
16
19
|
from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
|
|
17
20
|
|
|
18
21
|
|
|
22
|
+
class ShortStateStorage(dict[Identificator, ShortState[EventModel]]):
|
|
23
|
+
def __init__(self, *, maxlimit: int = 1000) -> None:
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.maxlimit = maxlimit
|
|
26
|
+
self.queue: deque[Identificator] = deque(maxlen=maxlimit)
|
|
27
|
+
|
|
28
|
+
def __repr__(self) -> str:
|
|
29
|
+
return "<{}: {}, (current={} | maxlimit={})>".format(
|
|
30
|
+
self.__class__.__name__,
|
|
31
|
+
super().__repr__(),
|
|
32
|
+
len(self.queue),
|
|
33
|
+
self.maxlimit,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def __setitem__(self, key: Identificator, value: ShortState[EventModel], /) -> None:
|
|
37
|
+
self.add(key, value)
|
|
38
|
+
|
|
39
|
+
def __delitem__(self, key: Identificator, /) -> None:
|
|
40
|
+
self.pop(key, None)
|
|
41
|
+
|
|
42
|
+
def add(self, id: Identificator, short_state: ShortState[EventModel]) -> None:
|
|
43
|
+
if len(self.queue) >= self.maxlimit:
|
|
44
|
+
self.pop(self.queue.popleft(), None)
|
|
45
|
+
if id not in self.queue:
|
|
46
|
+
self.queue.append(id)
|
|
47
|
+
super().__setitem__(id, short_state)
|
|
48
|
+
|
|
49
|
+
def clear(self) -> None:
|
|
50
|
+
self.queue.clear()
|
|
51
|
+
super().clear()
|
|
52
|
+
|
|
53
|
+
def setdefault(self, id: Identificator, default: ShortState[EventModel]) -> ShortState[EventModel]:
|
|
54
|
+
if id in self:
|
|
55
|
+
return self[id]
|
|
56
|
+
self.add(id, default)
|
|
57
|
+
return default
|
|
58
|
+
|
|
59
|
+
def pop(self, id: Identificator, default: T): # type: ignore
|
|
60
|
+
if id in self.queue:
|
|
61
|
+
self.queue.remove(id)
|
|
62
|
+
return super().pop(id, default)
|
|
63
|
+
|
|
64
|
+
def popitem(self) -> tuple[Identificator, ShortState[EventModel]]:
|
|
65
|
+
item = super().popitem()
|
|
66
|
+
self.queue.remove(item[0])
|
|
67
|
+
return item
|
|
68
|
+
|
|
69
|
+
def update(
|
|
70
|
+
self,
|
|
71
|
+
mapping: typing.Mapping[Identificator, ShortState[EventModel]] | None = None,
|
|
72
|
+
/,
|
|
73
|
+
**kwargs: ShortState[EventModel],
|
|
74
|
+
) -> None:
|
|
75
|
+
for key, value in (mapping if mapping is not None else kwargs).items():
|
|
76
|
+
self.add(key, value)
|
|
77
|
+
|
|
78
|
+
|
|
19
79
|
class WaiterMachine:
|
|
20
80
|
def __init__(self) -> None:
|
|
21
81
|
self.storage: Storage = {}
|
|
22
|
-
|
|
82
|
+
|
|
23
83
|
def __repr__(self) -> str:
|
|
24
84
|
return "<{}: storage={!r}>".format(
|
|
25
85
|
self.__class__.__name__,
|
|
@@ -37,7 +97,7 @@ class WaiterMachine:
|
|
|
37
97
|
raise LookupError("No record of view {!r} found".format(view_name))
|
|
38
98
|
|
|
39
99
|
short_state = self.storage[view_name].pop(id, None)
|
|
40
|
-
if
|
|
100
|
+
if short_state is None:
|
|
41
101
|
raise LookupError(
|
|
42
102
|
"Waiter with identificator {} is not found for view {!r}".format(
|
|
43
103
|
id,
|
|
@@ -47,7 +107,7 @@ class WaiterMachine:
|
|
|
47
107
|
|
|
48
108
|
waiters = typing.cast(
|
|
49
109
|
typing.Iterable[asyncio.Future[typing.Any]],
|
|
50
|
-
short_state.event._waiters # type: ignore
|
|
110
|
+
short_state.event._waiters, # type: ignore
|
|
51
111
|
)
|
|
52
112
|
for future in waiters:
|
|
53
113
|
future.cancel()
|
|
@@ -67,20 +127,17 @@ class WaiterMachine:
|
|
|
67
127
|
default: Behaviour = None,
|
|
68
128
|
on_drop: Behaviour = None,
|
|
69
129
|
expiration: datetime.timedelta | int | float | None = None,
|
|
130
|
+
short_state_storage: ShortStateStorage[EventModel] | None = None,
|
|
70
131
|
) -> tuple[EventModel, Context]:
|
|
71
132
|
if isinstance(expiration, int | float):
|
|
72
133
|
expiration = datetime.timedelta(seconds=expiration)
|
|
73
134
|
|
|
74
|
-
api: ABCAPI
|
|
75
|
-
key:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
api, key = linked
|
|
79
|
-
else:
|
|
80
|
-
api, key = linked.ctx_api, state_view.get_state_key(linked) # type: ignore
|
|
81
|
-
if not key:
|
|
82
|
-
raise RuntimeError("Unable to get state key.")
|
|
135
|
+
api: ABCAPI; key: Identificator
|
|
136
|
+
api, key = linked if isinstance(linked, tuple) else (linked.ctx_api, state_view.get_state_key(linked)) # type: ignore
|
|
137
|
+
if not key:
|
|
138
|
+
raise RuntimeError("Unable to get state key.")
|
|
83
139
|
|
|
140
|
+
event = asyncio.Event()
|
|
84
141
|
short_state = ShortState(
|
|
85
142
|
key,
|
|
86
143
|
api,
|
|
@@ -90,19 +147,16 @@ class WaiterMachine:
|
|
|
90
147
|
default_behaviour=default,
|
|
91
148
|
on_drop_behaviour=on_drop,
|
|
92
149
|
)
|
|
93
|
-
|
|
94
150
|
view_name = state_view.__class__.__name__
|
|
95
151
|
if view_name not in self.storage:
|
|
96
152
|
state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
|
|
97
|
-
self.storage[view_name] =
|
|
98
|
-
|
|
99
|
-
self.storage[view_name][key] = short_state
|
|
153
|
+
self.storage[view_name] = short_state_storage or ShortStateStorage()
|
|
100
154
|
|
|
155
|
+
self.storage[view_name].add(key, short_state)
|
|
101
156
|
await event.wait()
|
|
102
157
|
|
|
103
158
|
e, ctx = getattr(event, "context")
|
|
104
|
-
self.storage[view_name].pop(key)
|
|
105
|
-
|
|
159
|
+
self.storage[view_name].pop(key, None)
|
|
106
160
|
return e, ctx
|
|
107
161
|
|
|
108
162
|
async def call_behaviour(
|
|
@@ -38,7 +38,7 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
38
38
|
if key is None:
|
|
39
39
|
raise RuntimeError("Unable to get state key.")
|
|
40
40
|
|
|
41
|
-
short_state:
|
|
41
|
+
short_state: "ShortState[EventType] | None" = self.machine.storage[view_name].get(key)
|
|
42
42
|
if not short_state:
|
|
43
43
|
return True
|
|
44
44
|
|
|
@@ -48,13 +48,13 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
48
48
|
):
|
|
49
49
|
await self.machine.drop(self.view, short_state.key)
|
|
50
50
|
return True
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
handler = FuncHandler(
|
|
53
53
|
self.pass_runtime,
|
|
54
54
|
list(short_state.rules),
|
|
55
55
|
dataclass=None,
|
|
56
|
+
preset_context=Context(short_state=short_state),
|
|
56
57
|
)
|
|
57
|
-
handler.preset_context.set("short_state", short_state)
|
|
58
58
|
result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
|
|
59
59
|
|
|
60
60
|
if result is True:
|
|
@@ -12,6 +12,7 @@ if typing.TYPE_CHECKING:
|
|
|
12
12
|
from .machine import Identificator
|
|
13
13
|
|
|
14
14
|
EventModel = typing.TypeVar("EventModel", bound=BaseCute)
|
|
15
|
+
|
|
15
16
|
Behaviour: typing.TypeAlias = ABCHandler | None
|
|
16
17
|
|
|
17
18
|
|
|
@@ -22,15 +23,17 @@ class ShortState(typing.Generic[EventModel]):
|
|
|
22
23
|
event: asyncio.Event
|
|
23
24
|
rules: tuple[ABCRule[EventModel], ...]
|
|
24
25
|
_: dataclasses.KW_ONLY
|
|
25
|
-
expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
26
|
+
expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
27
|
+
default=None,
|
|
28
|
+
)
|
|
26
29
|
default_behaviour: Behaviour | None = dataclasses.field(default=None)
|
|
27
30
|
on_drop_behaviour: Behaviour | None = dataclasses.field(default=None)
|
|
28
31
|
expiration_date: datetime.datetime | None = dataclasses.field(init=False)
|
|
29
32
|
|
|
30
33
|
def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
|
|
31
34
|
self.expiration_date = (
|
|
32
|
-
datetime.datetime.now() - expiration
|
|
33
|
-
)
|
|
35
|
+
(datetime.datetime.now() - expiration) if expiration is not None else None
|
|
36
|
+
)
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
__all__ = ("ShortState",)
|
|
@@ -29,11 +29,27 @@ class Polling(ABCPolling):
|
|
|
29
29
|
include_updates=include_updates,
|
|
30
30
|
exclude_updates=exclude_updates,
|
|
31
31
|
)
|
|
32
|
-
self.reconnection_timeout =
|
|
32
|
+
self.reconnection_timeout = (
|
|
33
|
+
5 if reconnection_timeout < 0 else reconnection_timeout
|
|
34
|
+
)
|
|
33
35
|
self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
|
|
34
36
|
self.offset = offset
|
|
35
37
|
self._stop = False
|
|
36
38
|
|
|
39
|
+
def __repr__(self) -> str:
|
|
40
|
+
return (
|
|
41
|
+
"<{}: with api={!r}, stopped={}, offset={}, allowed_updates={!r}, "
|
|
42
|
+
"max_reconnetions={}, reconnection_timeout={}>"
|
|
43
|
+
).format(
|
|
44
|
+
self.__class__.__name__,
|
|
45
|
+
self.api,
|
|
46
|
+
self._stop,
|
|
47
|
+
self.offset,
|
|
48
|
+
self.allowed_updates,
|
|
49
|
+
self.max_reconnetions,
|
|
50
|
+
self.reconnection_timeout,
|
|
51
|
+
)
|
|
52
|
+
|
|
37
53
|
def get_allowed_updates(
|
|
38
54
|
self,
|
|
39
55
|
*,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .abc import ABCRule, AndRule,
|
|
1
|
+
from .abc import ABCRule, AndRule, NotRule, OrRule
|
|
2
2
|
from .callback_data import (
|
|
3
3
|
CallbackDataEq,
|
|
4
4
|
CallbackDataJsonEq,
|
|
@@ -9,6 +9,12 @@ from .callback_data import (
|
|
|
9
9
|
CallbackQueryRule,
|
|
10
10
|
HasData,
|
|
11
11
|
)
|
|
12
|
+
from .chat_join import (
|
|
13
|
+
ChatJoinRequestRule,
|
|
14
|
+
HasInviteLink,
|
|
15
|
+
InviteLinkByCreator,
|
|
16
|
+
InviteLinkName,
|
|
17
|
+
)
|
|
12
18
|
from .command import Argument, Command
|
|
13
19
|
from .enum_text import EnumTextRule
|
|
14
20
|
from .func import FuncRule
|
|
@@ -22,13 +28,10 @@ from .inline import (
|
|
|
22
28
|
)
|
|
23
29
|
from .integer import Integer, IntegerInRange
|
|
24
30
|
from .is_from import (
|
|
25
|
-
IsBasketballDice,
|
|
26
31
|
IsBot,
|
|
27
|
-
IsBowlingDice,
|
|
28
32
|
IsChat,
|
|
29
33
|
IsChatId,
|
|
30
|
-
|
|
31
|
-
IsDice,
|
|
34
|
+
IsDiceEmoji,
|
|
32
35
|
IsForum,
|
|
33
36
|
IsForward,
|
|
34
37
|
IsForwardType,
|
|
@@ -43,11 +46,13 @@ from .is_from import (
|
|
|
43
46
|
)
|
|
44
47
|
from .markup import Markup
|
|
45
48
|
from .mention import HasMention
|
|
49
|
+
from .message import MessageRule
|
|
46
50
|
from .message_entities import HasEntities, MessageEntities
|
|
47
51
|
from .regex import Regex
|
|
48
52
|
from .rule_enum import RuleEnum
|
|
49
53
|
from .start import StartCommand
|
|
50
54
|
from .text import HasText, Text, TextMessageRule
|
|
55
|
+
from .update import IsUpdate
|
|
51
56
|
|
|
52
57
|
__all__ = (
|
|
53
58
|
"ABCRule",
|
|
@@ -60,25 +65,29 @@ __all__ = (
|
|
|
60
65
|
"CallbackDataMarkup",
|
|
61
66
|
"CallbackQueryDataRule",
|
|
62
67
|
"CallbackQueryRule",
|
|
68
|
+
"ChatJoinRequestRule",
|
|
63
69
|
"Command",
|
|
64
70
|
"EnumTextRule",
|
|
65
71
|
"FuncRule",
|
|
66
72
|
"FuzzyText",
|
|
67
73
|
"HasData",
|
|
68
74
|
"HasEntities",
|
|
75
|
+
"HasInviteLink",
|
|
76
|
+
"HasLocation",
|
|
69
77
|
"HasMention",
|
|
70
78
|
"HasText",
|
|
79
|
+
"InlineQueryChatType",
|
|
80
|
+
"InlineQueryMarkup",
|
|
71
81
|
"InlineQueryRule",
|
|
72
82
|
"InlineQueryText",
|
|
73
83
|
"Integer",
|
|
74
84
|
"IntegerInRange",
|
|
75
|
-
"
|
|
85
|
+
"InviteLinkByCreator",
|
|
86
|
+
"InviteLinkName",
|
|
76
87
|
"IsBot",
|
|
77
|
-
"IsBowlingDice",
|
|
78
88
|
"IsChat",
|
|
79
89
|
"IsChatId",
|
|
80
|
-
"
|
|
81
|
-
"IsDice",
|
|
90
|
+
"IsDiceEmoji",
|
|
82
91
|
"IsForum",
|
|
83
92
|
"IsForward",
|
|
84
93
|
"IsForwardType",
|
|
@@ -88,14 +97,13 @@ __all__ = (
|
|
|
88
97
|
"IsPrivate",
|
|
89
98
|
"IsReply",
|
|
90
99
|
"IsSuperGroup",
|
|
100
|
+
"IsUpdate",
|
|
91
101
|
"IsUser",
|
|
92
102
|
"IsUserId",
|
|
93
|
-
"HasLocation",
|
|
94
|
-
"InlineQueryChatType",
|
|
95
|
-
"InlineQueryMarkup",
|
|
96
103
|
"Markup",
|
|
97
104
|
"MessageEntities",
|
|
98
105
|
"MessageRule",
|
|
106
|
+
"NotRule",
|
|
99
107
|
"OrRule",
|
|
100
108
|
"Regex",
|
|
101
109
|
"RuleEnum",
|
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -102,18 +102,9 @@ class NotRule(ABCRule[T]):
|
|
|
102
102
|
return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
class MessageRule(ABCRule[Message], ABC, requires=[]):
|
|
106
|
-
adapter = EventAdapter("message", Message)
|
|
107
|
-
|
|
108
|
-
@abstractmethod
|
|
109
|
-
async def check(self, message: Message, ctx: Context) -> bool:
|
|
110
|
-
...
|
|
111
|
-
|
|
112
|
-
|
|
113
105
|
__all__ = (
|
|
114
106
|
"ABCRule",
|
|
115
107
|
"AndRule",
|
|
116
|
-
"MessageRule",
|
|
117
108
|
"NotRule",
|
|
118
109
|
"OrRule",
|
|
119
110
|
"with_caching_translations",
|
|
@@ -9,41 +9,50 @@ from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
|
9
9
|
from telegrinder.msgspec_utils import Nothing
|
|
10
10
|
from telegrinder.types.objects import Model, Update
|
|
11
11
|
|
|
12
|
-
EventT = typing.TypeVar("EventT", bound=Model)
|
|
13
12
|
CuteT = typing.TypeVar("CuteT", bound=BaseCute)
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class EventAdapter(ABCAdapter[Update, CuteT]):
|
|
17
|
-
def __init__(self,
|
|
18
|
-
self.
|
|
19
|
-
self.
|
|
20
|
-
|
|
16
|
+
def __init__(self, event: str | type[Model], cute_model: type[CuteT]) -> None:
|
|
17
|
+
self.event = event
|
|
18
|
+
self.cute_model = cute_model
|
|
19
|
+
|
|
21
20
|
def __repr__(self) -> str:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
if isinstance(self.event, str):
|
|
22
|
+
raw_update_type = Update.__annotations__.get(self.event, "Unknown")
|
|
23
|
+
raw_update_type = (
|
|
24
|
+
typing.get_args(raw_update_type)[0].__forward_arg__
|
|
25
|
+
if typing.get_args(raw_update_type)
|
|
26
|
+
else raw_update_type
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
raw_update_type = self.event.__name__
|
|
28
30
|
return "<{}: adapt {} -> {}>".format(
|
|
29
31
|
self.__class__.__name__,
|
|
30
32
|
raw_update_type,
|
|
31
|
-
self.
|
|
33
|
+
self.cute_model.__name__,
|
|
32
34
|
)
|
|
33
35
|
|
|
34
36
|
async def adapt(self, api: ABCAPI, update: Update) -> Result[CuteT, AdapterError]:
|
|
35
37
|
update_dct = update.to_dict()
|
|
36
|
-
if self.
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
if isinstance(self.event, str):
|
|
39
|
+
if self.event not in update_dct:
|
|
40
|
+
return Error(
|
|
41
|
+
AdapterError(f"Update is not of event type {self.event!r}."),
|
|
42
|
+
)
|
|
43
|
+
if update_dct[self.event] is Nothing:
|
|
44
|
+
return Error(
|
|
45
|
+
AdapterError(f"Update is not an {self.event!r}."),
|
|
46
|
+
)
|
|
47
|
+
return Ok(
|
|
48
|
+
self.cute_model.from_update(
|
|
49
|
+
update_dct[self.event].unwrap(), bound_api=api
|
|
50
|
+
),
|
|
39
51
|
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return Ok(
|
|
45
|
-
self.model.from_update(update_dct[self.event_name].unwrap(), bound_api=api),
|
|
46
|
-
)
|
|
52
|
+
event = update_dct[update.update_type.unwrap()].unwrap()
|
|
53
|
+
if not update.update_type or not issubclass(event.__class__, self.event):
|
|
54
|
+
return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
|
|
55
|
+
return Ok(self.cute_model.from_update(event, bound_api=api))
|
|
47
56
|
|
|
48
57
|
|
|
49
58
|
__all__ = ("EventAdapter",)
|
|
@@ -14,16 +14,11 @@ from telegrinder.tools.buttons import DataclassInstance
|
|
|
14
14
|
from .abc import ABCRule
|
|
15
15
|
from .markup import Markup, PatternLike, check_string
|
|
16
16
|
|
|
17
|
-
T = typing.TypeVar("T")
|
|
18
|
-
|
|
19
|
-
Ref: typing.TypeAlias = typing.Annotated[T, ...]
|
|
20
17
|
CallbackQuery: typing.TypeAlias = CallbackQueryCute
|
|
21
18
|
Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
|
|
22
|
-
MapDict: typing.TypeAlias = dict[
|
|
23
|
-
|
|
24
|
-
]
|
|
25
|
-
CallbackMap: typing.TypeAlias = list[tuple[str, typing.Any | type | Validator | Ref["CallbackMap"]]]
|
|
26
|
-
CallbackMapStrict: typing.TypeAlias = list[tuple[str, Validator | Ref["CallbackMapStrict"]]]
|
|
19
|
+
MapDict: typing.TypeAlias = dict[str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"]
|
|
20
|
+
CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type | Validator | CallbackMap"]]
|
|
21
|
+
CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
|
|
27
22
|
|
|
28
23
|
|
|
29
24
|
class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
|
|
@@ -52,20 +47,20 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
52
47
|
@classmethod
|
|
53
48
|
def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
|
|
54
49
|
"""Transforms MapDict to CallbackMap."""
|
|
55
|
-
|
|
50
|
+
|
|
56
51
|
callback_map = []
|
|
57
|
-
|
|
52
|
+
|
|
58
53
|
for k, v in mapping.items():
|
|
59
54
|
if isinstance(v, dict):
|
|
60
55
|
v = cls.transform_to_map(v)
|
|
61
56
|
callback_map.append((k, v))
|
|
62
|
-
|
|
57
|
+
|
|
63
58
|
return callback_map
|
|
64
59
|
|
|
65
60
|
@classmethod
|
|
66
61
|
def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
|
|
67
62
|
"""Transforms `CallbackMap` to `CallbackMapStrict`."""
|
|
68
|
-
|
|
63
|
+
|
|
69
64
|
callback_map_result = []
|
|
70
65
|
|
|
71
66
|
for key, value in callback_map:
|
|
@@ -78,21 +73,21 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
78
73
|
else:
|
|
79
74
|
validator = value
|
|
80
75
|
callback_map_result.append((key, validator))
|
|
81
|
-
|
|
76
|
+
|
|
82
77
|
return callback_map_result
|
|
83
78
|
|
|
84
79
|
@staticmethod
|
|
85
80
|
async def run_validator(value: typing.Any, validator: Validator) -> bool:
|
|
86
81
|
"""Run async or sync validator."""
|
|
87
|
-
|
|
82
|
+
|
|
88
83
|
with suppress(BaseException):
|
|
89
84
|
result = validator(value)
|
|
90
85
|
if inspect.isawaitable(result):
|
|
91
86
|
result = await result
|
|
92
87
|
return result # type: ignore
|
|
93
|
-
|
|
88
|
+
|
|
94
89
|
return False
|
|
95
|
-
|
|
90
|
+
|
|
96
91
|
@classmethod
|
|
97
92
|
async def match(cls, callback_data: dict, callback_map: CallbackMapStrict) -> bool:
|
|
98
93
|
"""Matches callback_data with callback_map recursively."""
|
|
@@ -100,19 +95,19 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
100
95
|
for key, validator in callback_map:
|
|
101
96
|
if key not in callback_data:
|
|
102
97
|
return False
|
|
103
|
-
|
|
98
|
+
|
|
104
99
|
if isinstance(validator, list):
|
|
105
100
|
if not (
|
|
106
101
|
isinstance(callback_data[key], dict)
|
|
107
102
|
and await cls.match(callback_data[key], validator)
|
|
108
103
|
):
|
|
109
104
|
return False
|
|
110
|
-
|
|
105
|
+
|
|
111
106
|
elif not await cls.run_validator(callback_data[key], validator):
|
|
112
107
|
return False
|
|
113
108
|
|
|
114
109
|
return True
|
|
115
|
-
|
|
110
|
+
|
|
116
111
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
117
112
|
callback_data = event.decode_callback_data().unwrap_or_none()
|
|
118
113
|
if callback_data is None:
|
|
@@ -142,7 +137,7 @@ class CallbackDataJsonEq(CallbackQueryDataRule):
|
|
|
142
137
|
class CallbackDataJsonModel(CallbackQueryDataRule):
|
|
143
138
|
def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance]):
|
|
144
139
|
self.model = model
|
|
145
|
-
|
|
140
|
+
|
|
146
141
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
147
142
|
with suppress(BaseException):
|
|
148
143
|
ctx.data = decoder.decode(event.data.unwrap().encode(), type=self.model)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
5
|
+
from telegrinder.bot.dispatch.context import Context
|
|
6
|
+
from telegrinder.bot.rules.adapter import EventAdapter
|
|
7
|
+
|
|
8
|
+
from .abc import ABCRule
|
|
9
|
+
|
|
10
|
+
ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChatJoinRequestRule(ABCRule[ChatJoinRequestCute], requires=[]):
|
|
14
|
+
adapter = EventAdapter("chat_join_request", ChatJoinRequest)
|
|
15
|
+
|
|
16
|
+
@abc.abstractmethod
|
|
17
|
+
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HasInviteLink(ChatJoinRequestRule):
|
|
22
|
+
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|
|
23
|
+
return bool(event.invite_link)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
|
|
27
|
+
def __init__(self, name: str, /) -> None:
|
|
28
|
+
self.name = name
|
|
29
|
+
|
|
30
|
+
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|
|
31
|
+
return event.invite_link.unwrap().name.unwrap_or_none() == self.name
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
|
|
35
|
+
def __init__(self, creator_id: int, /) -> None:
|
|
36
|
+
self.creator_id = creator_id
|
|
37
|
+
|
|
38
|
+
async def check(self, event: ChatJoinRequest, ctx: Context) -> bool:
|
|
39
|
+
return event.invite_link.unwrap().creator.id == self.creator_id
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = (
|
|
43
|
+
"ChatJoinRequestRule",
|
|
44
|
+
"HasInviteLink",
|
|
45
|
+
"InviteLinkByCreator",
|
|
46
|
+
"InviteLinkName",
|
|
47
|
+
)
|
|
@@ -6,13 +6,18 @@ from telegrinder.bot.dispatch.context import Context
|
|
|
6
6
|
from .abc import Message
|
|
7
7
|
from .text import TextMessageRule
|
|
8
8
|
|
|
9
|
-
T = typing.TypeVar("T", bound=enum.Enum
|
|
9
|
+
T = typing.TypeVar("T", bound=enum.Enum)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class EnumTextRule(TextMessageRule, typing.Generic[T]):
|
|
13
13
|
def __init__(self, enum_t: type[T], *, lower_case: bool = True) -> None:
|
|
14
14
|
self.enum_t = enum_t
|
|
15
|
-
self.texts = list(
|
|
15
|
+
self.texts = list(
|
|
16
|
+
map(
|
|
17
|
+
lambda x: x.value.lower() if lower_case else x.value,
|
|
18
|
+
self.enum_t,
|
|
19
|
+
)
|
|
20
|
+
)
|
|
16
21
|
|
|
17
22
|
def find(self, s: str) -> T:
|
|
18
23
|
for enumeration in self.enum_t:
|
telegrinder/bot/rules/inline.py
CHANGED
|
@@ -48,15 +48,15 @@ class InlineQueryText(InlineQueryRule):
|
|
|
48
48
|
class InlineQueryMarkup(InlineQueryRule):
|
|
49
49
|
def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
|
|
50
50
|
self.patterns = Markup(patterns).patterns
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
async def check(self, query: InlineQuery, ctx: Context) -> bool:
|
|
53
53
|
return check_string(self.patterns, query.query, ctx)
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
__all__ = (
|
|
57
57
|
"HasLocation",
|
|
58
|
+
"InlineQueryChatType",
|
|
59
|
+
"InlineQueryMarkup",
|
|
58
60
|
"InlineQueryRule",
|
|
59
61
|
"InlineQueryText",
|
|
60
|
-
"InlineQueryMarkup",
|
|
61
|
-
"InlineQueryChatType",
|
|
62
62
|
)
|