telegrinder 0.1.dev166__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 +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 +1 -3
- 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 +1 -3
- telegrinder/bot/dispatch/handler/func.py +3 -10
- telegrinder/bot/dispatch/return_manager/abc.py +9 -13
- telegrinder/bot/dispatch/return_manager/message.py +5 -7
- telegrinder/bot/dispatch/view/abc.py +1 -3
- 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 +35 -74
- telegrinder/bot/dispatch/waiter_machine/middleware.py +12 -5
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -6
- telegrinder/bot/polling/polling.py +2 -6
- telegrinder/bot/rules/adapter/event.py +1 -3
- telegrinder/bot/rules/callback_data.py +3 -1
- 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 +1 -5
- 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 +27 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +3 -7
- telegrinder/types/__init__.py +30 -0
- telegrinder/types/objects.py +4 -4
- telegrinder/verification_utils.py +2 -1
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev167.dist-info}/METADATA +1 -1
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev167.dist-info}/RECORD +62 -61
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev167.dist-info}/LICENSE +0 -0
- {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev167.dist-info}/WHEEL +0 -0
|
@@ -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,12 @@ 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
|
-
(datetime.datetime.now() - expiration) if expiration is not None else None
|
|
36
|
-
)
|
|
36
|
+
self.expiration_date = (datetime.datetime.now() - expiration) if expiration is not None else None
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
__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):
|
|
@@ -16,7 +16,9 @@ from .markup import Markup, PatternLike, check_string
|
|
|
16
16
|
|
|
17
17
|
CallbackQuery: typing.TypeAlias = CallbackQueryCute
|
|
18
18
|
Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
|
|
19
|
-
MapDict: typing.TypeAlias = dict[
|
|
19
|
+
MapDict: typing.TypeAlias = dict[
|
|
20
|
+
str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"
|
|
21
|
+
]
|
|
20
22
|
CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type | Validator | CallbackMap"]]
|
|
21
23
|
CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
|
|
22
24
|
|
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
|
|
@@ -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
|
)
|
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
|
}
|
telegrinder/node/attachment.py
CHANGED
|
@@ -14,21 +14,15 @@ from .message import MessageNode
|
|
|
14
14
|
class Attachment(DataNode):
|
|
15
15
|
attachment_type: typing.Literal["audio", "document", "photo", "poll", "video"]
|
|
16
16
|
_: dataclasses.KW_ONLY
|
|
17
|
-
audio: Option[telegrinder.types.Audio] = dataclasses.field(
|
|
18
|
-
default_factory=lambda: Nothing()
|
|
19
|
-
)
|
|
17
|
+
audio: Option[telegrinder.types.Audio] = dataclasses.field(default_factory=lambda: Nothing())
|
|
20
18
|
document: Option[telegrinder.types.Document] = dataclasses.field(
|
|
21
19
|
default_factory=lambda: Nothing()
|
|
22
20
|
)
|
|
23
21
|
photo: Option[list[telegrinder.types.PhotoSize]] = dataclasses.field(
|
|
24
22
|
default_factory=lambda: Nothing()
|
|
25
23
|
)
|
|
26
|
-
poll: Option[telegrinder.types.Poll] = dataclasses.field(
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
video: Option[telegrinder.types.Video] = dataclasses.field(
|
|
30
|
-
default_factory=lambda: Nothing()
|
|
31
|
-
)
|
|
24
|
+
poll: Option[telegrinder.types.Poll] = dataclasses.field(default_factory=lambda: Nothing())
|
|
25
|
+
video: Option[telegrinder.types.Video] = dataclasses.field(default_factory=lambda: Nothing())
|
|
32
26
|
|
|
33
27
|
@classmethod
|
|
34
28
|
async def compose(cls, message: MessageNode) -> "Attachment":
|
|
@@ -44,31 +38,41 @@ class Photo(DataNode):
|
|
|
44
38
|
|
|
45
39
|
@classmethod
|
|
46
40
|
async def compose(cls, attachment: Attachment) -> typing.Self:
|
|
47
|
-
|
|
41
|
+
if not attachment.photo:
|
|
42
|
+
raise ComposeError("Attachment is not an photo")
|
|
43
|
+
return cls(attachment.photo.unwrap())
|
|
48
44
|
|
|
49
45
|
|
|
50
46
|
class Video(ScalarNode, telegrinder.types.Video):
|
|
51
47
|
@classmethod
|
|
52
48
|
async def compose(cls, attachment: Attachment) -> telegrinder.types.Video:
|
|
53
|
-
|
|
49
|
+
if not attachment.video:
|
|
50
|
+
raise ComposeError("Attachment is not an video")
|
|
51
|
+
return attachment.video.unwrap()
|
|
54
52
|
|
|
55
53
|
|
|
56
54
|
class Audio(ScalarNode, telegrinder.types.Audio):
|
|
57
55
|
@classmethod
|
|
58
56
|
async def compose(cls, attachment: Attachment) -> telegrinder.types.Audio:
|
|
59
|
-
|
|
57
|
+
if not attachment.audio:
|
|
58
|
+
raise ComposeError("Attachment is not an audio")
|
|
59
|
+
return attachment.audio.unwrap()
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
class Document(ScalarNode, telegrinder.types.Document):
|
|
63
63
|
@classmethod
|
|
64
64
|
async def compose(cls, attachment: Attachment) -> telegrinder.types.Document:
|
|
65
|
-
|
|
65
|
+
if not attachment.document:
|
|
66
|
+
raise ComposeError("Attachment is not an document")
|
|
67
|
+
return attachment.document.unwrap()
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
class Poll(ScalarNode, telegrinder.types.Poll):
|
|
69
71
|
@classmethod
|
|
70
72
|
async def compose(cls, attachment: Attachment) -> telegrinder.types.Poll:
|
|
71
|
-
|
|
73
|
+
if not attachment.poll:
|
|
74
|
+
raise ComposeError("Attachment is not an poll")
|
|
75
|
+
return attachment.poll.unwrap()
|
|
72
76
|
|
|
73
77
|
|
|
74
78
|
__all__ = (
|
telegrinder/node/base.py
CHANGED
|
@@ -3,8 +3,7 @@ import inspect
|
|
|
3
3
|
import typing
|
|
4
4
|
|
|
5
5
|
ComposeResult: typing.TypeAlias = (
|
|
6
|
-
typing.Coroutine[typing.Any, typing.Any, typing.Any]
|
|
7
|
-
| typing.AsyncGenerator[typing.Any, None]
|
|
6
|
+
typing.Coroutine[typing.Any, typing.Any, typing.Any] | typing.AsyncGenerator[typing.Any, None]
|
|
8
7
|
)
|
|
9
8
|
|
|
10
9
|
|
|
@@ -16,9 +15,7 @@ class Node(abc.ABC):
|
|
|
16
15
|
|
|
17
16
|
@classmethod
|
|
18
17
|
@abc.abstractmethod
|
|
19
|
-
def compose(
|
|
20
|
-
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
21
|
-
) -> ComposeResult:
|
|
18
|
+
def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
|
|
22
19
|
pass
|
|
23
20
|
|
|
24
21
|
@classmethod
|
|
@@ -52,18 +49,14 @@ class DataNode(Node, abc.ABC):
|
|
|
52
49
|
@typing.dataclass_transform()
|
|
53
50
|
@classmethod
|
|
54
51
|
@abc.abstractmethod
|
|
55
|
-
async def compose(
|
|
56
|
-
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
57
|
-
) -> ComposeResult:
|
|
52
|
+
async def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
|
|
58
53
|
pass
|
|
59
54
|
|
|
60
55
|
|
|
61
56
|
class ScalarNodeProto(Node, abc.ABC):
|
|
62
57
|
@classmethod
|
|
63
58
|
@abc.abstractmethod
|
|
64
|
-
async def compose(
|
|
65
|
-
cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any
|
|
66
|
-
) -> ComposeResult:
|
|
59
|
+
async def compose(cls, *args: tuple[typing.Any, ...], **kwargs: typing.Any) -> ComposeResult:
|
|
67
60
|
pass
|
|
68
61
|
|
|
69
62
|
|
telegrinder/node/composer.py
CHANGED
|
@@ -27,9 +27,7 @@ class NodeSession:
|
|
|
27
27
|
self.generator = None
|
|
28
28
|
|
|
29
29
|
def __repr__(self) -> str:
|
|
30
|
-
return f"<{self.__class__.__name__}: {self.value}" + (
|
|
31
|
-
"ACTIVE>" if self.generator else ">"
|
|
32
|
-
)
|
|
30
|
+
return f"<{self.__class__.__name__}: {self.value}" + ("ACTIVE>" if self.generator else ">")
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
class NodeCollection:
|
telegrinder/node/message.py
CHANGED
|
@@ -9,8 +9,10 @@ from .update import UpdateNode
|
|
|
9
9
|
class MessageNode(ScalarNode, MessageCute):
|
|
10
10
|
@classmethod
|
|
11
11
|
async def compose(cls, update: UpdateNode) -> typing.Self:
|
|
12
|
+
if not update.message:
|
|
13
|
+
raise ComposeError
|
|
12
14
|
return cls(
|
|
13
|
-
**update.message.
|
|
15
|
+
**update.message.unwrap().to_dict(),
|
|
14
16
|
api=update.api,
|
|
15
17
|
)
|
|
16
18
|
|
telegrinder/node/source.py
CHANGED
|
@@ -24,7 +24,9 @@ class Source(DataNode):
|
|
|
24
24
|
|
|
25
25
|
async def send(self, text: str) -> Message:
|
|
26
26
|
result = await self.api.send_message(
|
|
27
|
-
self.chat.id,
|
|
27
|
+
chat_id=self.chat.id,
|
|
28
|
+
message_thread_id=self.thread_id,
|
|
29
|
+
text=text,
|
|
28
30
|
)
|
|
29
31
|
return result.unwrap()
|
|
30
32
|
|
telegrinder/node/text.py
CHANGED
|
@@ -7,7 +7,9 @@ from .message import MessageNode
|
|
|
7
7
|
class Text(ScalarNode, str):
|
|
8
8
|
@classmethod
|
|
9
9
|
async def compose(cls, message: MessageNode) -> typing.Self:
|
|
10
|
-
|
|
10
|
+
if not message.text:
|
|
11
|
+
raise ComposeError("Message has no text")
|
|
12
|
+
return cls(message.text.unwrap())
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
__all__ = ("Text",)
|
telegrinder/tools/__init__.py
CHANGED
|
@@ -66,6 +66,7 @@ from .keyboard import (
|
|
|
66
66
|
Keyboard,
|
|
67
67
|
RowButtons,
|
|
68
68
|
)
|
|
69
|
+
from .limited_dict import LimitedDict
|
|
69
70
|
from .loop_wrapper import ABCLoopWrapper, DelayedTask, Lifespan, LoopWrapper
|
|
70
71
|
from .magic import magic_bundle, resolve_arg_names
|
|
71
72
|
from .parse_mode import ParseMode
|
|
@@ -100,6 +101,7 @@ __all__ = (
|
|
|
100
101
|
"KeyboardSetYAML",
|
|
101
102
|
"Lifespan",
|
|
102
103
|
"Link",
|
|
104
|
+
"LimitedDict",
|
|
103
105
|
"LoopWrapper",
|
|
104
106
|
"Mention",
|
|
105
107
|
"ParseMode",
|
telegrinder/tools/buttons.py
CHANGED
|
@@ -63,15 +63,13 @@ class InlineButton(BaseButton):
|
|
|
63
63
|
url: str | None = None
|
|
64
64
|
login_url: dict[str, typing.Any] | LoginUrl | None = None
|
|
65
65
|
pay: bool | None = None
|
|
66
|
-
callback_data:
|
|
67
|
-
str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct | None
|
|
68
|
-
) = None
|
|
66
|
+
callback_data: str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct | None = None
|
|
69
67
|
callback_game: dict[str, typing.Any] | CallbackGame | None = None
|
|
70
68
|
switch_inline_query: str | None = None
|
|
71
69
|
switch_inline_query_current_chat: str | None = None
|
|
72
|
-
switch_inline_query_chosen_chat: (
|
|
73
|
-
|
|
74
|
-
)
|
|
70
|
+
switch_inline_query_chosen_chat: dict[str, typing.Any] | SwitchInlineQueryChosenChat | None = (
|
|
71
|
+
None
|
|
72
|
+
)
|
|
75
73
|
web_app: dict[str, typing.Any] | WebAppInfo | None = None
|
|
76
74
|
|
|
77
75
|
|
|
@@ -16,9 +16,7 @@ class ABCErrorHandler(ABC, typing.Generic[EventT]):
|
|
|
16
16
|
self,
|
|
17
17
|
*args: typing.Any,
|
|
18
18
|
**kwargs: typing.Any,
|
|
19
|
-
) -> typing.Callable[
|
|
20
|
-
[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]
|
|
21
|
-
]:
|
|
19
|
+
) -> typing.Callable[[typing.Callable[..., typing.Any]], typing.Callable[..., typing.Any]]:
|
|
22
20
|
"""Decorator for registering callback as an error handler."""
|
|
23
21
|
|
|
24
22
|
@abstractmethod
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class CatcherError(TypeError):
|
|
5
|
-
def __init__(self, exc: typing.Any, error: str) -> None:
|
|
1
|
+
class CatcherError(BaseException):
|
|
2
|
+
def __init__(self, exc: BaseException, message: str) -> None:
|
|
6
3
|
self.exc = exc
|
|
7
|
-
self.
|
|
4
|
+
self.message = message
|
|
8
5
|
|
|
9
6
|
|
|
10
7
|
__all__ = ("CatcherError",)
|
|
@@ -13,9 +13,7 @@ from .error import CatcherError
|
|
|
13
13
|
|
|
14
14
|
F = typing.TypeVar("F", bound="FuncCatcher")
|
|
15
15
|
ExceptionT = typing.TypeVar("ExceptionT", bound=BaseException, contravariant=True)
|
|
16
|
-
FuncCatcher = typing.Callable[
|
|
17
|
-
typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]
|
|
18
|
-
]
|
|
16
|
+
FuncCatcher = typing.Callable[typing.Concatenate[ExceptionT, ...], typing.Awaitable[typing.Any]]
|
|
19
17
|
|
|
20
18
|
|
|
21
19
|
@dataclasses.dataclass(frozen=True, repr=False)
|
|
@@ -59,8 +57,7 @@ class Catcher(typing.Generic[EventT]):
|
|
|
59
57
|
) -> Result[typing.Any, BaseException]:
|
|
60
58
|
if self.match_exception(exception):
|
|
61
59
|
logger.debug(
|
|
62
|
-
"Catcher {!r} caught an exception in handler {!r}, "
|
|
63
|
-
"running catcher...".format(
|
|
60
|
+
"Catcher {!r} caught an exception in handler {!r}, " "running catcher...".format(
|
|
64
61
|
self.func.__name__,
|
|
65
62
|
handler_name,
|
|
66
63
|
)
|
|
@@ -109,7 +106,8 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
109
106
|
ignore_errors: bool = False,
|
|
110
107
|
):
|
|
111
108
|
"""Register the catcher.
|
|
112
|
-
|
|
109
|
+
|
|
110
|
+
:param logging: Logging the result of the catcher at the level 'DEBUG'.
|
|
113
111
|
:param raise_exception: Raise an exception if the catcher hasn't started.
|
|
114
112
|
:param ignore_errors: Ignore errors that may occur.
|
|
115
113
|
"""
|
|
@@ -142,21 +140,21 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
142
140
|
return Error(
|
|
143
141
|
CatcherError(
|
|
144
142
|
exc,
|
|
145
|
-
"Exception {
|
|
143
|
+
"Exception {} was occurred during the running catcher {!r}.".format(
|
|
146
144
|
repr(exc), self.catcher.func.__name__
|
|
147
145
|
),
|
|
148
146
|
)
|
|
149
147
|
)
|
|
150
148
|
|
|
151
|
-
def process_catcher_error(self, error: CatcherError) -> Result[None,
|
|
149
|
+
def process_catcher_error(self, error: CatcherError) -> Result[None, BaseException]:
|
|
152
150
|
assert self.catcher is not None
|
|
153
151
|
|
|
154
152
|
if self.catcher.raise_exception:
|
|
155
153
|
raise error.exc from None
|
|
156
|
-
if not self.catcher.ignore_errors:
|
|
157
|
-
return Error(error.error)
|
|
158
154
|
if self.catcher.logging:
|
|
159
|
-
logger.error(error.
|
|
155
|
+
logger.error(error.message)
|
|
156
|
+
if not self.catcher.ignore_errors:
|
|
157
|
+
return Error(error.exc)
|
|
160
158
|
|
|
161
159
|
return Ok(None)
|
|
162
160
|
|
|
@@ -166,12 +164,18 @@ class ErrorHandler(ABCErrorHandler[EventT]):
|
|
|
166
164
|
event: EventT,
|
|
167
165
|
api: ABCAPI,
|
|
168
166
|
ctx: Context,
|
|
169
|
-
) -> Result[typing.Any,
|
|
167
|
+
) -> Result[typing.Any, BaseException]:
|
|
170
168
|
if not self.catcher:
|
|
171
169
|
return Ok(await handler(event, **magic_bundle(handler, ctx))) # type: ignore
|
|
172
170
|
|
|
173
171
|
match await self.process(handler, event, api, ctx):
|
|
174
|
-
case Ok(
|
|
172
|
+
case Ok(value) as ok:
|
|
173
|
+
if self.catcher.logging:
|
|
174
|
+
logger.debug(
|
|
175
|
+
"Catcher {!r} returned a value: {!r}",
|
|
176
|
+
self.catcher.func.__name__,
|
|
177
|
+
value,
|
|
178
|
+
)
|
|
175
179
|
return ok
|
|
176
180
|
case Error(exc) as err:
|
|
177
181
|
if isinstance(exc, CatcherError):
|
|
@@ -49,9 +49,7 @@ class StringFormatter(string.Formatter):
|
|
|
49
49
|
)
|
|
50
50
|
return fmt
|
|
51
51
|
|
|
52
|
-
def get_spec_formatter(
|
|
53
|
-
self, value: SpecialFormat
|
|
54
|
-
) -> typing.Callable[..., "TagFormat"]:
|
|
52
|
+
def get_spec_formatter(self, value: SpecialFormat) -> typing.Callable[..., "TagFormat"]:
|
|
55
53
|
return globals()[value.__formatter_name__]
|
|
56
54
|
|
|
57
55
|
def check_formats(self, value: typing.Any, fmts: list[str]) -> "TagFormat":
|
|
@@ -241,9 +239,7 @@ def start_bot_link(bot_id: str | int, data: str, string: str | None = None) -> T
|
|
|
241
239
|
return link(get_start_bot_link(bot_id, data), string)
|
|
242
240
|
|
|
243
241
|
|
|
244
|
-
def start_group_link(
|
|
245
|
-
bot_id: str | int, data: str, string: str | None = None
|
|
246
|
-
) -> TagFormat:
|
|
242
|
+
def start_group_link(bot_id: str | int, data: str, string: str | None = None) -> TagFormat:
|
|
247
243
|
return link(get_start_group_link(bot_id, data), string)
|
|
248
244
|
|
|
249
245
|
|
|
@@ -29,9 +29,7 @@ def get_invite_chat_link(invite_link: str) -> str:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def user_open_message_link(user_id: int, message: str | None = None) -> str:
|
|
32
|
-
return f"tg://openmessage?user_id={user_id}" + (
|
|
33
|
-
"" if not message else f"&msg?text={message}"
|
|
34
|
-
)
|
|
32
|
+
return f"tg://openmessage?user_id={user_id}" + ("" if not message else f"&msg?text={message}")
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
__all__ = (
|