telegrinder 0.1.dev166__py3-none-any.whl → 0.1.dev168__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 +0 -2
- telegrinder/bot/__init__.py +0 -2
- telegrinder/bot/bot.py +1 -3
- telegrinder/bot/cute_types/base.py +3 -12
- telegrinder/bot/cute_types/callback_query.py +2 -4
- telegrinder/bot/cute_types/chat_join_request.py +3 -1
- telegrinder/bot/cute_types/chat_member_updated.py +3 -1
- telegrinder/bot/cute_types/message.py +10 -31
- telegrinder/bot/cute_types/utils.py +1 -3
- telegrinder/bot/dispatch/__init__.py +1 -2
- telegrinder/bot/dispatch/composition.py +1 -3
- telegrinder/bot/dispatch/dispatch.py +5 -6
- telegrinder/bot/dispatch/handler/func.py +4 -11
- telegrinder/bot/dispatch/return_manager/abc.py +9 -13
- telegrinder/bot/dispatch/return_manager/message.py +5 -7
- telegrinder/bot/dispatch/view/abc.py +54 -5
- telegrinder/bot/dispatch/view/box.py +3 -11
- telegrinder/bot/dispatch/view/raw.py +2 -6
- telegrinder/bot/dispatch/waiter_machine/__init__.py +1 -2
- telegrinder/bot/dispatch/waiter_machine/machine.py +43 -88
- telegrinder/bot/dispatch/waiter_machine/middleware.py +12 -5
- telegrinder/bot/dispatch/waiter_machine/short_state.py +15 -5
- telegrinder/bot/polling/polling.py +2 -6
- telegrinder/bot/rules/adapter/event.py +1 -3
- telegrinder/bot/rules/callback_data.py +8 -8
- telegrinder/bot/rules/fuzzy.py +1 -2
- telegrinder/bot/rules/is_from.py +6 -4
- telegrinder/bot/rules/markup.py +1 -2
- telegrinder/bot/rules/mention.py +1 -4
- telegrinder/bot/rules/regex.py +1 -2
- telegrinder/bot/rules/rule_enum.py +1 -3
- telegrinder/bot/rules/start.py +1 -3
- telegrinder/bot/scenario/checkbox.py +6 -10
- telegrinder/bot/scenario/choice.py +4 -3
- telegrinder/client/aiohttp.py +1 -3
- telegrinder/model.py +4 -3
- telegrinder/modules.py +1 -3
- telegrinder/msgspec_utils.py +1 -3
- telegrinder/node/attachment.py +18 -14
- telegrinder/node/base.py +4 -11
- telegrinder/node/composer.py +1 -3
- telegrinder/node/message.py +3 -1
- telegrinder/node/source.py +3 -1
- telegrinder/node/text.py +3 -1
- telegrinder/tools/__init__.py +2 -0
- telegrinder/tools/buttons.py +4 -6
- telegrinder/tools/error_handler/abc.py +1 -3
- telegrinder/tools/error_handler/error.py +3 -6
- telegrinder/tools/error_handler/error_handler.py +17 -13
- telegrinder/tools/formatting/html.py +2 -6
- telegrinder/tools/formatting/links.py +1 -3
- telegrinder/tools/global_context/abc.py +1 -3
- telegrinder/tools/global_context/global_context.py +13 -31
- telegrinder/tools/i18n/middleware/base.py +1 -3
- telegrinder/tools/limited_dict.py +37 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +3 -7
- telegrinder/types/__init__.py +30 -0
- telegrinder/types/methods.py +20 -89
- telegrinder/types/objects.py +16 -45
- telegrinder/verification_utils.py +2 -1
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/METADATA +5 -5
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/RECORD +64 -63
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/WHEEL +0 -0
|
@@ -1,79 +1,23 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import datetime
|
|
3
3
|
import typing
|
|
4
|
-
from collections import deque
|
|
5
4
|
|
|
6
5
|
from telegrinder.api.abc import ABCAPI
|
|
7
6
|
from telegrinder.bot.dispatch.context import Context
|
|
8
7
|
from telegrinder.bot.rules.abc import ABCRule
|
|
8
|
+
from telegrinder.tools.limited_dict import LimitedDict
|
|
9
|
+
from telegrinder.types import Update
|
|
9
10
|
|
|
10
11
|
from .middleware import WaiterMiddleware
|
|
11
12
|
from .short_state import Behaviour, EventModel, ShortState
|
|
12
13
|
|
|
13
|
-
T = typing.TypeVar("T")
|
|
14
|
-
|
|
15
|
-
Storage: typing.TypeAlias = dict[str, "ShortStateStorage"]
|
|
16
|
-
Identificator: typing.TypeAlias = str | int
|
|
17
|
-
|
|
18
14
|
if typing.TYPE_CHECKING:
|
|
19
15
|
from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
|
|
20
16
|
|
|
17
|
+
T = typing.TypeVar("T")
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
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)
|
|
19
|
+
Identificator: typing.TypeAlias = str | int
|
|
20
|
+
Storage: typing.TypeAlias = dict[str, LimitedDict[Identificator, ShortState[EventModel]]]
|
|
77
21
|
|
|
78
22
|
|
|
79
23
|
class WaiterMachine:
|
|
@@ -90,32 +34,27 @@ class WaiterMachine:
|
|
|
90
34
|
self,
|
|
91
35
|
state_view: "ABCStateView[EventModel]",
|
|
92
36
|
id: Identificator,
|
|
37
|
+
update: Update,
|
|
93
38
|
**context: typing.Any,
|
|
94
39
|
) -> None:
|
|
95
40
|
view_name = state_view.__class__.__name__
|
|
96
41
|
if view_name not in self.storage:
|
|
97
|
-
raise LookupError("No record of view {!r} found".format(view_name))
|
|
42
|
+
raise LookupError("No record of view {!r} found.".format(view_name))
|
|
98
43
|
|
|
99
44
|
short_state = self.storage[view_name].pop(id, None)
|
|
100
45
|
if short_state is None:
|
|
101
46
|
raise LookupError(
|
|
102
47
|
"Waiter with identificator {} is not found for view {!r}".format(
|
|
103
|
-
id,
|
|
104
|
-
view_name,
|
|
48
|
+
id, view_name
|
|
105
49
|
)
|
|
106
50
|
)
|
|
107
51
|
|
|
108
|
-
|
|
109
|
-
typing.Iterable[asyncio.Future[typing.Any]],
|
|
110
|
-
short_state.event._waiters, # type: ignore
|
|
111
|
-
)
|
|
112
|
-
for future in waiters:
|
|
113
|
-
future.cancel()
|
|
114
|
-
|
|
52
|
+
short_state.cancel()
|
|
115
53
|
await self.call_behaviour(
|
|
116
54
|
state_view,
|
|
117
|
-
short_state.on_drop_behaviour,
|
|
118
55
|
short_state.event,
|
|
56
|
+
update,
|
|
57
|
+
behaviour=short_state.on_drop_behaviour,
|
|
119
58
|
**context,
|
|
120
59
|
)
|
|
121
60
|
|
|
@@ -124,19 +63,24 @@ class WaiterMachine:
|
|
|
124
63
|
state_view: "BaseStateView[EventModel]",
|
|
125
64
|
linked: EventModel | tuple[ABCAPI, Identificator],
|
|
126
65
|
*rules: ABCRule[EventModel],
|
|
127
|
-
default: Behaviour = None,
|
|
128
|
-
on_drop: Behaviour = None,
|
|
129
|
-
expiration: datetime.timedelta |
|
|
130
|
-
short_state_storage: ShortStateStorage[EventModel] | None = None,
|
|
66
|
+
default: Behaviour[EventModel] | None = None,
|
|
67
|
+
on_drop: Behaviour[EventModel] | None = None,
|
|
68
|
+
expiration: datetime.timedelta | float | None = None,
|
|
131
69
|
) -> tuple[EventModel, Context]:
|
|
132
70
|
if isinstance(expiration, int | float):
|
|
133
71
|
expiration = datetime.timedelta(seconds=expiration)
|
|
134
72
|
|
|
135
|
-
api: ABCAPI
|
|
136
|
-
|
|
73
|
+
api: ABCAPI
|
|
74
|
+
key: Identificator
|
|
75
|
+
api, key = (
|
|
76
|
+
linked
|
|
77
|
+
if isinstance(linked, tuple)
|
|
78
|
+
else (linked.ctx_api, state_view.get_state_key(linked))
|
|
79
|
+
) # type: ignore
|
|
137
80
|
if not key:
|
|
138
81
|
raise RuntimeError("Unable to get state key.")
|
|
139
82
|
|
|
83
|
+
view_name = state_view.__class__.__name__
|
|
140
84
|
event = asyncio.Event()
|
|
141
85
|
short_state = ShortState(
|
|
142
86
|
key,
|
|
@@ -147,30 +91,41 @@ class WaiterMachine:
|
|
|
147
91
|
default_behaviour=default,
|
|
148
92
|
on_drop_behaviour=on_drop,
|
|
149
93
|
)
|
|
150
|
-
|
|
94
|
+
|
|
151
95
|
if view_name not in self.storage:
|
|
152
96
|
state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
|
|
153
|
-
self.storage[view_name] =
|
|
97
|
+
self.storage[view_name] = LimitedDict()
|
|
154
98
|
|
|
155
|
-
self.storage[view_name].
|
|
99
|
+
if (deleted_short_state := self.storage[view_name].set(key, short_state)) is not None:
|
|
100
|
+
deleted_short_state.cancel()
|
|
101
|
+
|
|
156
102
|
await event.wait()
|
|
157
|
-
|
|
158
|
-
e, ctx = getattr(event, "context")
|
|
159
103
|
self.storage[view_name].pop(key, None)
|
|
160
|
-
return
|
|
104
|
+
return getattr(event, "context")
|
|
161
105
|
|
|
162
106
|
async def call_behaviour(
|
|
163
107
|
self,
|
|
164
108
|
view: "ABCStateView[EventModel]",
|
|
165
|
-
behaviour: Behaviour,
|
|
166
109
|
event: asyncio.Event | EventModel,
|
|
110
|
+
update: Update,
|
|
111
|
+
behaviour: Behaviour[EventModel] | None = None,
|
|
167
112
|
**context: typing.Any,
|
|
168
113
|
) -> None:
|
|
114
|
+
# TODO: support param view as a behaviour
|
|
115
|
+
|
|
169
116
|
if behaviour is None:
|
|
170
117
|
return
|
|
171
|
-
# TODO: add behaviour check
|
|
172
|
-
# TODO: support view as a behaviour
|
|
173
|
-
await behaviour.run(event, context) # type: ignore
|
|
174
118
|
|
|
119
|
+
ctx = Context(**context)
|
|
120
|
+
if isinstance(event, asyncio.Event):
|
|
121
|
+
event, preset_ctx = typing.cast(
|
|
122
|
+
tuple[EventModel, Context],
|
|
123
|
+
getattr(event, "context"),
|
|
124
|
+
)
|
|
125
|
+
ctx.update(preset_ctx)
|
|
126
|
+
|
|
127
|
+
if await behaviour.check(event.api, update, ctx):
|
|
128
|
+
await behaviour.run(event, ctx)
|
|
129
|
+
|
|
175
130
|
|
|
176
131
|
__all__ = ("WaiterMachine",)
|
|
@@ -41,19 +41,20 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
41
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
|
+
|
|
45
|
+
preset_context = Context(short_state=short_state)
|
|
45
46
|
if (
|
|
46
47
|
short_state.expiration_date is not None
|
|
47
48
|
and datetime.datetime.now() >= short_state.expiration_date
|
|
48
49
|
):
|
|
49
|
-
await self.machine.drop(self.view, short_state.key)
|
|
50
|
+
await self.machine.drop(self.view, short_state.key, ctx.raw_update, **preset_context.copy())
|
|
50
51
|
return True
|
|
51
52
|
|
|
52
53
|
handler = FuncHandler(
|
|
53
54
|
self.pass_runtime,
|
|
54
55
|
list(short_state.rules),
|
|
55
56
|
dataclass=None,
|
|
56
|
-
preset_context=
|
|
57
|
+
preset_context=preset_context,
|
|
57
58
|
)
|
|
58
59
|
result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
|
|
59
60
|
|
|
@@ -63,14 +64,20 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
|
|
|
63
64
|
elif short_state.default_behaviour is not None:
|
|
64
65
|
await self.machine.call_behaviour(
|
|
65
66
|
self.view,
|
|
66
|
-
short_state.default_behaviour,
|
|
67
67
|
event,
|
|
68
|
+
ctx.raw_update,
|
|
69
|
+
behaviour=short_state.default_behaviour,
|
|
68
70
|
**handler.preset_context,
|
|
69
71
|
)
|
|
70
72
|
|
|
71
73
|
return False
|
|
72
74
|
|
|
73
|
-
async def pass_runtime(
|
|
75
|
+
async def pass_runtime(
|
|
76
|
+
self,
|
|
77
|
+
event: EventType,
|
|
78
|
+
short_state: "ShortState[EventType]",
|
|
79
|
+
ctx: Context,
|
|
80
|
+
) -> None:
|
|
74
81
|
setattr(short_state.event, "context", (event, ctx))
|
|
75
82
|
short_state.event.set()
|
|
76
83
|
|
|
@@ -7,13 +7,15 @@ from telegrinder.api import ABCAPI
|
|
|
7
7
|
from telegrinder.bot.cute_types import BaseCute
|
|
8
8
|
from telegrinder.bot.dispatch.handler.abc import ABCHandler
|
|
9
9
|
from telegrinder.bot.rules.abc import ABCRule
|
|
10
|
+
from telegrinder.model import Model
|
|
10
11
|
|
|
11
12
|
if typing.TYPE_CHECKING:
|
|
12
13
|
from .machine import Identificator
|
|
13
14
|
|
|
15
|
+
T = typing.TypeVar("T", bound=Model)
|
|
14
16
|
EventModel = typing.TypeVar("EventModel", bound=BaseCute)
|
|
15
17
|
|
|
16
|
-
Behaviour: typing.TypeAlias = ABCHandler | None
|
|
18
|
+
Behaviour: typing.TypeAlias = ABCHandler[T] | None
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
@dataclasses.dataclass
|
|
@@ -26,14 +28,22 @@ class ShortState(typing.Generic[EventModel]):
|
|
|
26
28
|
expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
|
|
27
29
|
default=None,
|
|
28
30
|
)
|
|
29
|
-
default_behaviour: Behaviour | None = dataclasses.field(default=None)
|
|
30
|
-
on_drop_behaviour: Behaviour | None = dataclasses.field(default=None)
|
|
31
|
+
default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
|
|
32
|
+
on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
|
|
31
33
|
expiration_date: datetime.datetime | None = dataclasses.field(init=False)
|
|
32
34
|
|
|
33
35
|
def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
|
|
34
|
-
self.expiration_date = (
|
|
35
|
-
|
|
36
|
+
self.expiration_date = (datetime.datetime.now() - expiration) if expiration is not None else None
|
|
37
|
+
|
|
38
|
+
def cancel(self) -> None:
|
|
39
|
+
"""Cancel schedule waiters."""
|
|
40
|
+
|
|
41
|
+
waiters = typing.cast(
|
|
42
|
+
typing.Iterable[asyncio.Future[typing.Any]],
|
|
43
|
+
self.event._waiters, # type: ignore
|
|
36
44
|
)
|
|
45
|
+
for future in waiters:
|
|
46
|
+
future.cancel()
|
|
37
47
|
|
|
38
48
|
|
|
39
49
|
__all__ = ("ShortState",)
|
|
@@ -29,9 +29,7 @@ class Polling(ABCPolling):
|
|
|
29
29
|
include_updates=include_updates,
|
|
30
30
|
exclude_updates=exclude_updates,
|
|
31
31
|
)
|
|
32
|
-
self.reconnection_timeout =
|
|
33
|
-
5 if reconnection_timeout < 0 else reconnection_timeout
|
|
34
|
-
)
|
|
32
|
+
self.reconnection_timeout = 5 if reconnection_timeout < 0 else reconnection_timeout
|
|
35
33
|
self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
|
|
36
34
|
self.offset = offset
|
|
37
35
|
self._stop = False
|
|
@@ -62,9 +60,7 @@ class Polling(ABCPolling):
|
|
|
62
60
|
|
|
63
61
|
if include_updates and exclude_updates:
|
|
64
62
|
allowed_updates = [
|
|
65
|
-
x
|
|
66
|
-
for x in allowed_updates
|
|
67
|
-
if x in include_updates and x not in exclude_updates
|
|
63
|
+
x for x in allowed_updates if x in include_updates and x not in exclude_updates
|
|
68
64
|
]
|
|
69
65
|
elif exclude_updates:
|
|
70
66
|
allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
|
|
@@ -45,9 +45,7 @@ class EventAdapter(ABCAdapter[Update, CuteT]):
|
|
|
45
45
|
AdapterError(f"Update is not an {self.event!r}."),
|
|
46
46
|
)
|
|
47
47
|
return Ok(
|
|
48
|
-
self.cute_model.from_update(
|
|
49
|
-
update_dct[self.event].unwrap(), bound_api=api
|
|
50
|
-
),
|
|
48
|
+
self.cute_model.from_update(update_dct[self.event].unwrap(), bound_api=api),
|
|
51
49
|
)
|
|
52
50
|
event = update_dct[update.update_type.unwrap()].unwrap()
|
|
53
51
|
if not update.update_type or not issubclass(event.__class__, self.event):
|
|
@@ -17,7 +17,7 @@ from .markup import Markup, PatternLike, check_string
|
|
|
17
17
|
CallbackQuery: typing.TypeAlias = CallbackQueryCute
|
|
18
18
|
Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
|
|
19
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"]]
|
|
20
|
+
CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type[typing.Any] | Validator | CallbackMap"]]
|
|
21
21
|
CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
|
|
22
22
|
|
|
23
23
|
|
|
@@ -39,7 +39,7 @@ class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class CallbackDataMap(CallbackQueryDataRule):
|
|
42
|
-
def __init__(self, mapping: MapDict) -> None:
|
|
42
|
+
def __init__(self, mapping: MapDict, /) -> None:
|
|
43
43
|
self.mapping = self.transform_to_callbacks(
|
|
44
44
|
self.transform_to_map(mapping),
|
|
45
45
|
)
|
|
@@ -84,12 +84,12 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
84
84
|
result = validator(value)
|
|
85
85
|
if inspect.isawaitable(result):
|
|
86
86
|
result = await result
|
|
87
|
-
return result
|
|
87
|
+
return result
|
|
88
88
|
|
|
89
89
|
return False
|
|
90
90
|
|
|
91
91
|
@classmethod
|
|
92
|
-
async def match(cls, callback_data: dict, callback_map: CallbackMapStrict) -> bool:
|
|
92
|
+
async def match(cls, callback_data: dict[str, typing.Any], callback_map: CallbackMapStrict) -> bool:
|
|
93
93
|
"""Matches callback_data with callback_map recursively."""
|
|
94
94
|
|
|
95
95
|
for key, validator in callback_map:
|
|
@@ -119,7 +119,7 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
class CallbackDataEq(CallbackQueryDataRule):
|
|
122
|
-
def __init__(self, value: str):
|
|
122
|
+
def __init__(self, value: str, /) -> None:
|
|
123
123
|
self.value = value
|
|
124
124
|
|
|
125
125
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
@@ -127,7 +127,7 @@ class CallbackDataEq(CallbackQueryDataRule):
|
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
class CallbackDataJsonEq(CallbackQueryDataRule):
|
|
130
|
-
def __init__(self, d: dict[str, typing.Any]):
|
|
130
|
+
def __init__(self, d: dict[str, typing.Any], /) -> None:
|
|
131
131
|
self.d = d
|
|
132
132
|
|
|
133
133
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
@@ -135,7 +135,7 @@ class CallbackDataJsonEq(CallbackQueryDataRule):
|
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
class CallbackDataJsonModel(CallbackQueryDataRule):
|
|
138
|
-
def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance]):
|
|
138
|
+
def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance], /) -> None:
|
|
139
139
|
self.model = model
|
|
140
140
|
|
|
141
141
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
@@ -146,7 +146,7 @@ class CallbackDataJsonModel(CallbackQueryDataRule):
|
|
|
146
146
|
|
|
147
147
|
|
|
148
148
|
class CallbackDataMarkup(CallbackQueryDataRule):
|
|
149
|
-
def __init__(self, patterns: PatternLike | list[PatternLike]):
|
|
149
|
+
def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
|
|
150
150
|
self.patterns = Markup(patterns).patterns
|
|
151
151
|
|
|
152
152
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
telegrinder/bot/rules/fuzzy.py
CHANGED
|
@@ -15,8 +15,7 @@ class FuzzyText(TextMessageRule):
|
|
|
15
15
|
|
|
16
16
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
17
17
|
match = max(
|
|
18
|
-
difflib.SequenceMatcher(a=message.text.unwrap(), b=text).ratio()
|
|
19
|
-
for text in self.texts
|
|
18
|
+
difflib.SequenceMatcher(a=message.text.unwrap(), b=text).ratio() for text in self.texts
|
|
20
19
|
)
|
|
21
20
|
if match < self.min_ratio:
|
|
22
21
|
return False
|
telegrinder/bot/rules/is_from.py
CHANGED
|
@@ -42,7 +42,9 @@ class IsForward(MessageRule):
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class IsForwardType(MessageRule, requires=[IsForward()]):
|
|
45
|
-
def __init__(
|
|
45
|
+
def __init__(
|
|
46
|
+
self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /
|
|
47
|
+
) -> None:
|
|
46
48
|
self.fwd_type = fwd_type
|
|
47
49
|
|
|
48
50
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
@@ -80,8 +82,8 @@ class IsLanguageCode(ABCRule[T], requires=[HasFrom()]):
|
|
|
80
82
|
|
|
81
83
|
async def check(self, event: UpdateCute, ctx: Context) -> bool:
|
|
82
84
|
return (
|
|
83
|
-
get_from_user(event.incoming_update.unwrap())
|
|
84
|
-
|
|
85
|
+
get_from_user(event.incoming_update.unwrap()).language_code.unwrap_or_none()
|
|
86
|
+
in self.lang_codes
|
|
85
87
|
)
|
|
86
88
|
|
|
87
89
|
|
|
@@ -131,7 +133,7 @@ class IsDiceEmoji(MessageRule, requires=[HasDice()]):
|
|
|
131
133
|
self.dice_emoji = dice_emoji
|
|
132
134
|
|
|
133
135
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
134
|
-
return message.dice.unwrap().emoji == self.dice_emoji
|
|
136
|
+
return message.dice.unwrap().emoji == self.dice_emoji
|
|
135
137
|
|
|
136
138
|
|
|
137
139
|
__all__ = (
|
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",)
|
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
|
@@ -30,9 +30,7 @@ class StartCommand(
|
|
|
30
30
|
|
|
31
31
|
async def check(self, _: Message, ctx: Context) -> bool:
|
|
32
32
|
param: str | None = ctx.pop("param", None)
|
|
33
|
-
validated_param = (
|
|
34
|
-
self.validator(param) if self.validator and param is not None else param
|
|
35
|
-
)
|
|
33
|
+
validated_param = self.validator(param) if self.validator and param is not None else param
|
|
36
34
|
|
|
37
35
|
if self.param_required and validated_param is None:
|
|
38
36
|
return False
|
|
@@ -33,13 +33,13 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
33
33
|
self,
|
|
34
34
|
waiter_machine: WaiterMachine,
|
|
35
35
|
chat_id: int,
|
|
36
|
-
|
|
36
|
+
message: str,
|
|
37
37
|
*,
|
|
38
38
|
ready_text: str = "Ready",
|
|
39
39
|
max_in_row: int = 3,
|
|
40
40
|
) -> None:
|
|
41
41
|
self.chat_id = chat_id
|
|
42
|
-
self.
|
|
42
|
+
self.message = message
|
|
43
43
|
self.choices: list[Choice] = []
|
|
44
44
|
self.ready = ready_text
|
|
45
45
|
self.max_in_row = max_in_row
|
|
@@ -58,7 +58,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
58
58
|
self.waiter_machine,
|
|
59
59
|
self.ready,
|
|
60
60
|
self.chat_id,
|
|
61
|
-
self.
|
|
61
|
+
self.message,
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
def get_markup(self) -> InlineKeyboardMarkup:
|
|
@@ -69,11 +69,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
69
69
|
choice = choices.pop(0)
|
|
70
70
|
kb.add(
|
|
71
71
|
InlineButton(
|
|
72
|
-
text=(
|
|
73
|
-
choice.default_text
|
|
74
|
-
if not choice.is_picked
|
|
75
|
-
else choice.picked_text
|
|
76
|
-
),
|
|
72
|
+
text=(choice.default_text if not choice.is_picked else choice.picked_text),
|
|
77
73
|
callback_data=self.random_code + "/" + choice.code,
|
|
78
74
|
)
|
|
79
75
|
)
|
|
@@ -105,7 +101,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
105
101
|
# Toggle choice
|
|
106
102
|
self.choices[i].is_picked = not self.choices[i].is_picked
|
|
107
103
|
await cb.edit_text(
|
|
108
|
-
text=self.
|
|
104
|
+
text=self.message,
|
|
109
105
|
parse_mode=self.PARSE_MODE,
|
|
110
106
|
reply_markup=self.get_markup(),
|
|
111
107
|
)
|
|
@@ -122,7 +118,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
122
118
|
message = (
|
|
123
119
|
await api.send_message(
|
|
124
120
|
chat_id=self.chat_id,
|
|
125
|
-
text=self.
|
|
121
|
+
text=self.message,
|
|
126
122
|
parse_mode=self.PARSE_MODE,
|
|
127
123
|
reply_markup=self.get_markup(),
|
|
128
124
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
|
|
3
3
|
from telegrinder.bot.cute_types import CallbackQueryCute
|
|
4
|
+
from telegrinder.bot.dispatch.waiter_machine import WaiterMachine
|
|
4
5
|
|
|
5
6
|
from .checkbox import Checkbox
|
|
6
7
|
|
|
@@ -22,9 +23,9 @@ class SingleChoice(Checkbox):
|
|
|
22
23
|
if choice.code == code:
|
|
23
24
|
self.choices[i].is_picked = True
|
|
24
25
|
await cb.ctx_api.edit_message_text(
|
|
26
|
+
text=self.message,
|
|
25
27
|
chat_id=cb.message.unwrap().v.chat.id,
|
|
26
28
|
message_id=cb.message.unwrap().v.message_id,
|
|
27
|
-
text=self.msg,
|
|
28
29
|
parse_mode=self.PARSE_MODE,
|
|
29
30
|
reply_markup=self.get_markup(),
|
|
30
31
|
)
|
|
@@ -36,10 +37,10 @@ class SingleChoice(Checkbox):
|
|
|
36
37
|
api: "API",
|
|
37
38
|
view: "BaseStateView[CallbackQueryCute]",
|
|
38
39
|
) -> tuple[str, int]:
|
|
39
|
-
if len(
|
|
40
|
+
if len(tuple(choice for choice in self.choices if choice.is_picked)) != 1:
|
|
40
41
|
raise ValueError("Exactly one choice must be picked")
|
|
41
42
|
choices, m_id = await super().wait(api, view)
|
|
42
|
-
return
|
|
43
|
+
return tuple(choices.keys())[tuple(choices.values()).index(True)], m_id
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
__all__ = ("SingleChoice",)
|
telegrinder/client/aiohttp.py
CHANGED
|
@@ -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
|
)
|
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,7 +25,8 @@ 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"],
|
|
28
|
+
result: Result[msgspec.Raw, "APIError"],
|
|
29
|
+
full_t: type[T],
|
|
29
30
|
) -> Result[T, "APIError"]: ...
|
|
30
31
|
|
|
31
32
|
|
telegrinder/modules.py
CHANGED
|
@@ -147,9 +147,7 @@ elif logging_module == "logging":
|
|
|
147
147
|
for level, settings in LEVEL_SETTINGS.items():
|
|
148
148
|
fmt = FORMAT
|
|
149
149
|
for name, color in settings.items():
|
|
150
|
-
fmt = fmt.replace(f"<{name}>", COLORS[color]).replace(
|
|
151
|
-
f"</{name}>", COLORS["reset"]
|
|
152
|
-
)
|
|
150
|
+
fmt = fmt.replace(f"<{name}>", COLORS[color]).replace(f"</{name}>", COLORS["reset"])
|
|
153
151
|
LEVEL_FORMATS[level] = fmt
|
|
154
152
|
|
|
155
153
|
class TelegrinderLoggingFormatter(logging.Formatter):
|
telegrinder/msgspec_utils.py
CHANGED
|
@@ -66,9 +66,7 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
|
66
66
|
union_types = tuple(t for t in union_types if t not in struct_fields_match_sums)
|
|
67
67
|
reverse = False
|
|
68
68
|
|
|
69
|
-
if len(set(struct_fields_match_sums.values())) != len(
|
|
70
|
-
struct_fields_match_sums.values()
|
|
71
|
-
):
|
|
69
|
+
if len(set(struct_fields_match_sums.values())) != len(struct_fields_match_sums.values()):
|
|
72
70
|
struct_fields_match_sums = {
|
|
73
71
|
m: len(m.__struct_fields__) for m in struct_fields_match_sums
|
|
74
72
|
}
|