telegrinder 0.2.0.post2__py3-none-any.whl → 0.2.2__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.

Files changed (70) hide show
  1. telegrinder/__init__.py +26 -7
  2. telegrinder/bot/__init__.py +16 -4
  3. telegrinder/bot/cute_types/chat_member_updated.py +1 -1
  4. telegrinder/bot/cute_types/message.py +18 -11
  5. telegrinder/bot/dispatch/__init__.py +18 -2
  6. telegrinder/bot/dispatch/abc.py +1 -1
  7. telegrinder/bot/dispatch/context.py +2 -2
  8. telegrinder/bot/dispatch/dispatch.py +1 -1
  9. telegrinder/bot/dispatch/handler/__init__.py +17 -1
  10. telegrinder/bot/dispatch/handler/audio_reply.py +44 -0
  11. telegrinder/bot/dispatch/handler/base.py +57 -0
  12. telegrinder/bot/dispatch/handler/document_reply.py +44 -0
  13. telegrinder/bot/dispatch/handler/func.py +3 -3
  14. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
  15. telegrinder/bot/dispatch/handler/message_reply.py +12 -35
  16. telegrinder/bot/dispatch/handler/photo_reply.py +44 -0
  17. telegrinder/bot/dispatch/handler/sticker_reply.py +37 -0
  18. telegrinder/bot/dispatch/handler/video_reply.py +44 -0
  19. telegrinder/bot/dispatch/process.py +2 -2
  20. telegrinder/bot/dispatch/return_manager/abc.py +11 -8
  21. telegrinder/bot/dispatch/return_manager/callback_query.py +2 -2
  22. telegrinder/bot/dispatch/return_manager/inline_query.py +2 -2
  23. telegrinder/bot/dispatch/return_manager/message.py +3 -3
  24. telegrinder/bot/dispatch/view/__init__.py +2 -1
  25. telegrinder/bot/dispatch/view/abc.py +2 -181
  26. telegrinder/bot/dispatch/view/base.py +200 -0
  27. telegrinder/bot/dispatch/view/callback_query.py +3 -3
  28. telegrinder/bot/dispatch/view/chat_join_request.py +2 -2
  29. telegrinder/bot/dispatch/view/chat_member.py +2 -3
  30. telegrinder/bot/dispatch/view/inline_query.py +2 -2
  31. telegrinder/bot/dispatch/view/message.py +5 -4
  32. telegrinder/bot/dispatch/view/raw.py +4 -3
  33. telegrinder/bot/dispatch/waiter_machine/machine.py +18 -7
  34. telegrinder/bot/dispatch/waiter_machine/middleware.py +0 -8
  35. telegrinder/bot/dispatch/waiter_machine/short_state.py +1 -1
  36. telegrinder/bot/polling/polling.py +5 -2
  37. telegrinder/bot/rules/__init__.py +5 -2
  38. telegrinder/bot/rules/abc.py +6 -5
  39. telegrinder/bot/rules/adapter/__init__.py +1 -1
  40. telegrinder/bot/rules/integer.py +1 -1
  41. telegrinder/bot/rules/is_from.py +19 -0
  42. telegrinder/bot/rules/state.py +37 -0
  43. telegrinder/bot/scenario/checkbox.py +3 -3
  44. telegrinder/bot/scenario/choice.py +2 -2
  45. telegrinder/client/aiohttp.py +5 -7
  46. telegrinder/model.py +1 -8
  47. telegrinder/modules.py +16 -25
  48. telegrinder/msgspec_utils.py +5 -5
  49. telegrinder/node/base.py +2 -2
  50. telegrinder/node/composer.py +5 -9
  51. telegrinder/node/container.py +6 -1
  52. telegrinder/node/polymorphic.py +7 -7
  53. telegrinder/node/rule.py +6 -4
  54. telegrinder/node/scope.py +3 -3
  55. telegrinder/node/source.py +4 -2
  56. telegrinder/node/tools/generator.py +7 -6
  57. telegrinder/rules.py +2 -2
  58. telegrinder/tools/__init__.py +11 -7
  59. telegrinder/tools/loop_wrapper/loop_wrapper.py +4 -5
  60. telegrinder/tools/magic.py +17 -19
  61. telegrinder/tools/state_storage/__init__.py +4 -0
  62. telegrinder/tools/state_storage/abc.py +35 -0
  63. telegrinder/tools/state_storage/memory.py +25 -0
  64. telegrinder/types/__init__.py +1 -0
  65. telegrinder/types/methods.py +12 -4
  66. telegrinder/types/objects.py +57 -6
  67. {telegrinder-0.2.0.post2.dist-info → telegrinder-0.2.2.dist-info}/METADATA +3 -4
  68. {telegrinder-0.2.0.post2.dist-info → telegrinder-0.2.2.dist-info}/RECORD +70 -58
  69. {telegrinder-0.2.0.post2.dist-info → telegrinder-0.2.2.dist-info}/LICENSE +0 -0
  70. {telegrinder-0.2.0.post2.dist-info → telegrinder-0.2.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,200 @@
1
+ import typing
2
+ from abc import ABC, abstractmethod
3
+ from functools import cached_property
4
+
5
+ from fntypes.option import Nothing, Some
6
+
7
+ from telegrinder.api.api import API
8
+ from telegrinder.bot.cute_types.base import BaseCute
9
+ from telegrinder.bot.dispatch.handler.abc import ABCHandler
10
+ from telegrinder.bot.dispatch.handler.func import FuncHandler
11
+ from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
12
+ from telegrinder.bot.dispatch.process import process_inner
13
+ from telegrinder.bot.dispatch.return_manager.abc import ABCReturnManager
14
+ from telegrinder.bot.dispatch.view.abc import ABCStateView, ABCView
15
+ from telegrinder.bot.rules.abc import ABCRule
16
+ from telegrinder.model import Model
17
+ from telegrinder.msgspec_utils import Option
18
+ from telegrinder.tools.error_handler.error_handler import ABCErrorHandler, ErrorHandler
19
+ from telegrinder.types.objects import Update
20
+
21
+ Event = typing.TypeVar("Event", bound=BaseCute)
22
+ ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ABCErrorHandler)
23
+ MiddlewareT = typing.TypeVar("MiddlewareT", bound=ABCMiddleware)
24
+
25
+ FuncType: typing.TypeAlias = typing.Callable[
26
+ typing.Concatenate[Event, ...],
27
+ typing.Coroutine[typing.Any, typing.Any, typing.Any],
28
+ ]
29
+
30
+
31
+ def get_event_model_class(view: "BaseView[Event]") -> Option[type[Event]]:
32
+ for base in view.__class__.__bases__ + (view.__class__,):
33
+ if "__orig_bases__" not in base.__dict__:
34
+ continue
35
+
36
+ for orig_base in base.__dict__["__orig_bases__"]:
37
+ origin_base = typing.get_origin(orig_base) or orig_base
38
+ if not isinstance(origin_base, type) and not issubclass(origin_base, object):
39
+ continue
40
+
41
+ for generic_type in typing.get_args(orig_base):
42
+ orig_generic_type = typing.get_origin(generic_type) or generic_type
43
+ if isinstance(orig_generic_type, type) and issubclass(orig_generic_type, BaseCute):
44
+ return Some(generic_type)
45
+
46
+ return Nothing()
47
+
48
+
49
+ class BaseView(ABCView, typing.Generic[Event]):
50
+ auto_rules: list[ABCRule]
51
+ handlers: list[ABCHandler[Event]]
52
+ middlewares: list[ABCMiddleware[Event]]
53
+ return_manager: ABCReturnManager[Event] | None = None
54
+
55
+ @staticmethod
56
+ def get_raw_event(update: Update) -> Option[Model]:
57
+ return getattr(update, update.update_type.value)
58
+
59
+ @cached_property
60
+ def event_model_class(self) -> Option[type[Event]]:
61
+ return get_event_model_class(self)
62
+
63
+ @typing.overload
64
+ @classmethod
65
+ def to_handler(
66
+ cls,
67
+ *rules: ABCRule,
68
+ ) -> typing.Callable[
69
+ [FuncType[Event]],
70
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
71
+ ]: ...
72
+
73
+ @typing.overload
74
+ @classmethod
75
+ def to_handler(
76
+ cls,
77
+ *rules: ABCRule,
78
+ error_handler: ErrorHandlerT,
79
+ is_blocking: bool = True,
80
+ ) -> typing.Callable[[FuncType[Event]], FuncHandler[Event, FuncType[Event], ErrorHandlerT]]: ...
81
+
82
+ @typing.overload
83
+ @classmethod
84
+ def to_handler(
85
+ cls,
86
+ *rules: ABCRule,
87
+ error_handler: typing.Literal[None] = None,
88
+ is_blocking: bool = True,
89
+ ) -> typing.Callable[
90
+ [FuncType[Event]],
91
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
92
+ ]: ...
93
+
94
+ @classmethod
95
+ def to_handler( # type: ignore
96
+ cls,
97
+ *rules: ABCRule,
98
+ error_handler: ABCErrorHandler | None = None,
99
+ is_blocking: bool = True,
100
+ ):
101
+ def wrapper(func: FuncType[Event]):
102
+ return FuncHandler(
103
+ func,
104
+ list(rules),
105
+ is_blocking=is_blocking,
106
+ dataclass=None,
107
+ error_handler=error_handler or ErrorHandler(),
108
+ )
109
+
110
+ return wrapper
111
+
112
+ @typing.overload
113
+ def __call__(
114
+ self,
115
+ *rules: ABCRule,
116
+ ) -> typing.Callable[
117
+ [FuncType[Event]],
118
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
119
+ ]: ...
120
+
121
+ @typing.overload
122
+ def __call__( # type: ignore
123
+ self,
124
+ *rules: ABCRule,
125
+ error_handler: ErrorHandlerT,
126
+ is_blocking: bool = True,
127
+ ) -> typing.Callable[[FuncType[Event]], FuncHandler[Event, FuncType[Event], ErrorHandlerT]]: ...
128
+
129
+ @typing.overload
130
+ def __call__(
131
+ self,
132
+ *rules: ABCRule,
133
+ error_handler: typing.Literal[None] = None,
134
+ is_blocking: bool = True,
135
+ ) -> typing.Callable[
136
+ [FuncType[Event]],
137
+ FuncHandler[Event, FuncType[Event], ErrorHandler[Event]],
138
+ ]: ...
139
+
140
+ def __call__( # type: ignore
141
+ self,
142
+ *rules: ABCRule,
143
+ error_handler: ABCErrorHandler | None = None,
144
+ is_blocking: bool = True,
145
+ ):
146
+ def wrapper(func: FuncType[Event]):
147
+ func_handler = FuncHandler(
148
+ func,
149
+ [*self.auto_rules, *rules],
150
+ is_blocking=is_blocking,
151
+ dataclass=None,
152
+ error_handler=error_handler or ErrorHandler(),
153
+ )
154
+ self.handlers.append(func_handler)
155
+ return func_handler
156
+
157
+ return wrapper
158
+
159
+ def register_middleware(self, *args: typing.Any, **kwargs: typing.Any):
160
+ def wrapper(cls: type[MiddlewareT]) -> type[MiddlewareT]:
161
+ self.middlewares.append(cls(*args, **kwargs))
162
+ return cls
163
+
164
+ return wrapper
165
+
166
+ async def check(self, event: Update) -> bool:
167
+ match self.get_raw_event(event):
168
+ case Some(e) if issubclass(
169
+ self.event_model_class.expect(
170
+ "{!r} has no event model class in generic.".format(self.__class__.__qualname__),
171
+ ),
172
+ e.__class__,
173
+ ) and (self.handlers or self.middlewares):
174
+ return True
175
+ case _:
176
+ return False
177
+
178
+ async def process(self, event: Update, api: API) -> bool:
179
+ return await process_inner(
180
+ api,
181
+ self.event_model_class.unwrap().from_update(
182
+ update=self.get_raw_event(event).unwrap(),
183
+ bound_api=api,
184
+ ),
185
+ event,
186
+ self.middlewares,
187
+ self.handlers,
188
+ self.return_manager,
189
+ )
190
+
191
+ def load(self, external: typing.Self) -> None:
192
+ self.auto_rules.extend(external.auto_rules)
193
+ self.handlers.extend(external.handlers)
194
+ self.middlewares.extend(external.middlewares)
195
+
196
+
197
+ class BaseStateView(ABCStateView[Event], BaseView[Event], ABC, typing.Generic[Event]):
198
+ @abstractmethod
199
+ def get_state_key(self, event: Event) -> int | None:
200
+ pass
@@ -1,6 +1,6 @@
1
- from telegrinder.bot.cute_types import CallbackQueryCute
2
- from telegrinder.bot.dispatch.return_manager import CallbackQueryReturnManager
3
- from telegrinder.bot.dispatch.view.abc import BaseStateView
1
+ from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
2
+ from telegrinder.bot.dispatch.return_manager.callback_query import CallbackQueryReturnManager
3
+ from telegrinder.bot.dispatch.view.base import BaseStateView
4
4
 
5
5
 
6
6
  class CallbackQueryView(BaseStateView[CallbackQueryCute]):
@@ -1,5 +1,5 @@
1
- from telegrinder.bot.cute_types import ChatJoinRequestCute
2
- from telegrinder.bot.dispatch.view.abc import BaseStateView
1
+ from telegrinder.bot.cute_types.chat_join_request import ChatJoinRequestCute
2
+ from telegrinder.bot.dispatch.view.base import BaseStateView
3
3
 
4
4
 
5
5
  class ChatJoinRequestView(BaseStateView[ChatJoinRequestCute]):
@@ -1,7 +1,7 @@
1
1
  import typing
2
2
 
3
- from telegrinder.bot.cute_types import ChatMemberUpdatedCute
4
- from telegrinder.bot.dispatch.view.abc import BaseStateView
3
+ from telegrinder.bot.cute_types.chat_member_updated import ChatMemberUpdatedCute
4
+ from telegrinder.bot.dispatch.view.base import BaseStateView
5
5
  from telegrinder.types.enums import UpdateType
6
6
  from telegrinder.types.objects import Update
7
7
 
@@ -28,7 +28,6 @@ class ChatMemberView(BaseStateView[ChatMemberUpdatedCute]):
28
28
  def get_state_key(self, event: ChatMemberUpdatedCute) -> int | None:
29
29
  return event.chat_id
30
30
 
31
-
32
31
  async def check(self, event: Update) -> bool:
33
32
  return not (
34
33
  self.update_type is not None
@@ -1,6 +1,6 @@
1
- from telegrinder.bot.cute_types import InlineQueryCute
1
+ from telegrinder.bot.cute_types.inline_query import InlineQueryCute
2
2
  from telegrinder.bot.dispatch.return_manager import InlineQueryReturnManager
3
- from telegrinder.bot.dispatch.view.abc import BaseStateView
3
+ from telegrinder.bot.dispatch.view.base import BaseStateView
4
4
 
5
5
 
6
6
  class InlineQueryView(BaseStateView[InlineQueryCute]):
@@ -1,9 +1,10 @@
1
1
  import typing
2
2
 
3
- from telegrinder.bot.cute_types import MessageCute
4
- from telegrinder.bot.dispatch.return_manager import MessageReturnManager
5
- from telegrinder.bot.dispatch.view.abc import BaseStateView
6
- from telegrinder.types import Update, UpdateType
3
+ from telegrinder.bot.cute_types.message import MessageCute
4
+ from telegrinder.bot.dispatch.return_manager.message import MessageReturnManager
5
+ from telegrinder.bot.dispatch.view.base import BaseStateView
6
+ from telegrinder.types.enums import UpdateType
7
+ from telegrinder.types.objects import Update
7
8
 
8
9
  MessageUpdateType: typing.TypeAlias = typing.Literal[
9
10
  UpdateType.MESSAGE,
@@ -1,10 +1,11 @@
1
1
  import typing
2
2
 
3
- from telegrinder.api import API
4
- from telegrinder.bot.cute_types import UpdateCute
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.cute_types.update import UpdateCute
5
5
  from telegrinder.bot.dispatch.handler.func import FuncHandler
6
6
  from telegrinder.bot.dispatch.process import process_inner
7
- from telegrinder.bot.dispatch.view.abc import ABCEventRawView, BaseView, ErrorHandlerT
7
+ from telegrinder.bot.dispatch.view.abc import ABCEventRawView
8
+ from telegrinder.bot.dispatch.view.base import BaseView, ErrorHandlerT
8
9
  from telegrinder.bot.rules.abc import ABCRule
9
10
  from telegrinder.tools.error_handler.error_handler import ABCErrorHandler, ErrorHandler
10
11
  from telegrinder.types.enums import UpdateType
@@ -2,9 +2,10 @@ import asyncio
2
2
  import datetime
3
3
  import typing
4
4
 
5
- from telegrinder.api import API
5
+ from telegrinder.api.api import API
6
6
  from telegrinder.bot.dispatch.context import Context
7
- from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
7
+ from telegrinder.bot.dispatch.view.abc import ABCStateView
8
+ from telegrinder.bot.dispatch.view.base import BaseStateView
8
9
  from telegrinder.bot.dispatch.waiter_machine.middleware import WaiterMiddleware
9
10
  from telegrinder.bot.dispatch.waiter_machine.short_state import (
10
11
  Behaviour,
@@ -45,13 +46,13 @@ class WaiterMachine:
45
46
 
46
47
  async def drop(
47
48
  self,
48
- state_view: "ABCStateView[EventModel]",
49
+ state_view: "ABCStateView[EventModel] | str",
49
50
  id: Identificator,
50
51
  event: EventModel,
51
52
  update: Update,
52
53
  **context: typing.Any,
53
54
  ) -> None:
54
- view_name = state_view.__class__.__name__
55
+ view_name = state_view if isinstance(state_view, str) else state_view.__class__.__name__
55
56
  if view_name not in self.storage:
56
57
  raise LookupError("No record of view {!r} found.".format(view_name))
57
58
 
@@ -61,13 +62,24 @@ class WaiterMachine:
61
62
 
62
63
  short_state.cancel()
63
64
  await self.call_behaviour(
64
- state_view,
65
65
  event,
66
66
  update,
67
67
  behaviour=short_state.on_drop_behaviour,
68
68
  **context,
69
69
  )
70
70
 
71
+ async def drop_all(self) -> None:
72
+ """Drops all waiters in storage."""
73
+
74
+ for view_name in self.storage:
75
+ for ident, short_state in self.storage[view_name].items():
76
+ if short_state.context:
77
+ await self.drop(
78
+ view_name, ident, short_state.context.event, short_state.context.context.raw_update
79
+ )
80
+ else:
81
+ short_state.cancel()
82
+
71
83
  async def wait(
72
84
  self,
73
85
  state_view: "BaseStateView[EventModel]",
@@ -114,7 +126,6 @@ class WaiterMachine:
114
126
 
115
127
  async def call_behaviour(
116
128
  self,
117
- view: "ABCStateView[EventModel]",
118
129
  event: EventModel,
119
130
  update: Update,
120
131
  behaviour: Behaviour[EventModel] | None = None,
@@ -147,7 +158,7 @@ class WaiterMachine:
147
158
  now = datetime.datetime.now()
148
159
  for ident, short_state in self.storage.get(view_name, {}).copy().items():
149
160
  if short_state.expiration_date is not None and now > short_state.expiration_date:
150
- assert short_state.context
161
+ assert short_state.context # FIXME: why???
151
162
  await self.drop(
152
163
  view,
153
164
  ident,
@@ -25,12 +25,6 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
25
25
  self.view = view
26
26
 
27
27
  async def pre(self, event: EventType, ctx: Context) -> bool:
28
- if not self.view or not hasattr(self.view, "get_state_key"):
29
- raise RuntimeError(
30
- "WaiterMiddleware cannot be used inside a view which doesn't "
31
- "provide get_state_key (ABCStateView interface)."
32
- )
33
-
34
28
  view_name = self.view.__class__.__name__
35
29
  if view_name not in self.machine.storage:
36
30
  return True
@@ -59,7 +53,6 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
59
53
 
60
54
  # before running the handler we check if the user wants to exit waiting
61
55
  if short_state.exit_behaviour is not None and await self.machine.call_behaviour(
62
- self.view,
63
56
  event,
64
57
  ctx.raw_update,
65
58
  behaviour=short_state.exit_behaviour,
@@ -87,7 +80,6 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
87
80
 
88
81
  elif short_state.default_behaviour is not None:
89
82
  await self.machine.call_behaviour(
90
- self.view,
91
83
  event,
92
84
  ctx.raw_update,
93
85
  behaviour=short_state.default_behaviour,
@@ -3,7 +3,7 @@ import dataclasses
3
3
  import datetime
4
4
  import typing
5
5
 
6
- from telegrinder.api import API
6
+ from telegrinder.api.api import API
7
7
  from telegrinder.bot.cute_types import BaseCute
8
8
  from telegrinder.bot.dispatch.context import Context
9
9
  from telegrinder.bot.dispatch.handler.abc import ABCHandler
@@ -5,7 +5,7 @@ import aiohttp
5
5
  import msgspec
6
6
  from fntypes.result import Error, Ok
7
7
 
8
- from telegrinder.api import API
8
+ from telegrinder.api.api import API
9
9
  from telegrinder.api.error import InvalidTokenError
10
10
  from telegrinder.bot.polling.abc import ABCPolling
11
11
  from telegrinder.modules import logger
@@ -72,7 +72,10 @@ class Polling(ABCPolling):
72
72
  async def get_updates(self) -> msgspec.Raw | None:
73
73
  raw_updates = await self.api.request_raw(
74
74
  "getUpdates",
75
- {"offset": self.offset, "allowed_updates": self.allowed_updates},
75
+ {
76
+ "offset": self.offset,
77
+ "allowed_updates": self.allowed_updates,
78
+ },
76
79
  )
77
80
  match raw_updates:
78
81
  case Ok(value):
@@ -53,6 +53,7 @@ from telegrinder.bot.rules.node import NodeRule
53
53
  from telegrinder.bot.rules.regex import Regex
54
54
  from telegrinder.bot.rules.rule_enum import RuleEnum
55
55
  from telegrinder.bot.rules.start import StartCommand
56
+ from telegrinder.bot.rules.state import State, StateMeta
56
57
  from telegrinder.bot.rules.text import HasText, Text
57
58
  from telegrinder.bot.rules.update import IsUpdateType
58
59
 
@@ -82,7 +83,6 @@ __all__ = (
82
83
  "InlineQueryMarkup",
83
84
  "InlineQueryRule",
84
85
  "InlineQueryText",
85
- "IsInteger",
86
86
  "IntegerInRange",
87
87
  "InviteLinkByCreator",
88
88
  "InviteLinkName",
@@ -95,6 +95,7 @@ __all__ = (
95
95
  "IsForward",
96
96
  "IsForwardType",
97
97
  "IsGroup",
98
+ "IsInteger",
98
99
  "IsLanguageCode",
99
100
  "IsPremium",
100
101
  "IsPrivate",
@@ -106,11 +107,13 @@ __all__ = (
106
107
  "Markup",
107
108
  "MessageEntities",
108
109
  "MessageRule",
110
+ "NodeRule",
109
111
  "NotRule",
110
112
  "OrRule",
111
113
  "Regex",
112
114
  "RuleEnum",
113
115
  "StartCommand",
116
+ "State",
117
+ "StateMeta",
114
118
  "Text",
115
- "NodeRule",
116
119
  )
@@ -118,12 +118,13 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
118
118
  ctx: Context,
119
119
  node_col: "NodeCollection | None" = None,
120
120
  ) -> bool:
121
+ bound_check_rule = self.check
121
122
  kw = {}
122
123
  node_col_values = node_col.values if node_col is not None else {}
123
- temp_ctx = get_default_args(self.check) | ctx
124
+ temp_ctx = get_default_args(bound_check_rule) | ctx
124
125
 
125
- for i, (k, v) in enumerate(get_annotations(self.check).items()):
126
- if (isinstance(adapted_value, Event) and not i) or (
126
+ for i, (k, v) in enumerate(get_annotations(bound_check_rule).items()):
127
+ if (isinstance(adapted_value, Event) and i == 0) or ( # First arg is Event
127
128
  isinstance(v, type) and isinstance(adapted_value, v)
128
129
  ):
129
130
  kw[k] = adapted_value if not isinstance(adapted_value, Event) else adapted_value.obj
@@ -140,14 +141,14 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
140
141
  "because it cannot be resolved."
141
142
  )
142
143
 
143
- return await self.check(**kw)
144
+ return await bound_check_rule(**kw)
144
145
 
145
146
  async def translate(self, translator: ABCTranslator) -> typing.Self:
146
147
  return self
147
148
 
148
149
 
149
150
  class AndRule(ABCRule):
150
- def __init__(self, *rules: ABCRule[AdaptTo]) -> None:
151
+ def __init__(self, *rules: ABCRule) -> None:
151
152
  self.rules = rules
152
153
 
153
154
  async def check(self, event: Update, ctx: Context) -> bool:
@@ -7,8 +7,8 @@ from telegrinder.bot.rules.adapter.raw_update import RawUpdateAdapter
7
7
  __all__ = (
8
8
  "ABCAdapter",
9
9
  "AdapterError",
10
+ "Event",
10
11
  "EventAdapter",
11
12
  "NodeAdapter",
12
13
  "RawUpdateAdapter",
13
- "Event",
14
14
  )
@@ -17,4 +17,4 @@ class IntegerInRange(ABCRule):
17
17
  return integer in self.rng
18
18
 
19
19
 
20
- __all__ = ("IsInteger", "IntegerInRange")
20
+ __all__ = ("IntegerInRange", "IsInteger")
@@ -107,21 +107,40 @@ class IsSticker(MessageRule):
107
107
  return bool(message.sticker)
108
108
 
109
109
 
110
+ class IsVideoNote(MessageRule):
111
+ async def check(self, message: Message) -> bool:
112
+ return bool(message.video_note)
113
+
114
+
115
+ class IsDocument(MessageRule):
116
+ async def check(self, message: Message) -> bool:
117
+ return bool(message.document)
118
+
119
+
120
+ class IsPhoto(MessageRule):
121
+ async def check(self, message: Message) -> bool:
122
+ return bool(message.photo)
123
+
124
+
110
125
  __all__ = (
111
126
  "IsBot",
112
127
  "IsChat",
113
128
  "IsChatId",
114
129
  "IsDice",
115
130
  "IsDiceEmoji",
131
+ "IsDocument",
116
132
  "IsForum",
117
133
  "IsForward",
118
134
  "IsForwardType",
119
135
  "IsGroup",
120
136
  "IsLanguageCode",
137
+ "IsPhoto",
121
138
  "IsPremium",
122
139
  "IsPrivate",
123
140
  "IsReply",
141
+ "IsSticker",
124
142
  "IsSuperGroup",
125
143
  "IsUser",
126
144
  "IsUserId",
145
+ "IsVideoNote",
127
146
  )
@@ -0,0 +1,37 @@
1
+ import dataclasses
2
+ import enum
3
+ import typing
4
+
5
+ from telegrinder.bot.dispatch.context import Context
6
+ from telegrinder.bot.rules.abc import ABCRule
7
+ from telegrinder.node.source import Source
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from telegrinder.tools.state_storage.abc import ABCStateStorage
11
+
12
+ Payload = typing.TypeVar("Payload")
13
+
14
+
15
+ class StateMeta(enum.Enum):
16
+ NO_STATE = enum.auto()
17
+ ANY = enum.auto()
18
+
19
+
20
+ @dataclasses.dataclass(frozen=True, slots=True, repr=False)
21
+ class State(ABCRule, typing.Generic[Payload]):
22
+ storage: "ABCStateStorage[Payload]"
23
+ key: str | StateMeta | enum.Enum
24
+
25
+ async def check(self, source: Source, ctx: Context) -> bool:
26
+ state = await self.storage.get(source.from_user.id)
27
+ if not state:
28
+ return self.key == StateMeta.NO_STATE
29
+
30
+ if self.key != StateMeta.ANY and self.key != state.unwrap().key:
31
+ return False
32
+
33
+ ctx["state"] = state.unwrap()
34
+ return True
35
+
36
+
37
+ __all__ = ("State", "StateMeta")
@@ -2,16 +2,16 @@ import dataclasses
2
2
  import secrets
3
3
  import typing
4
4
 
5
- from telegrinder.bot.cute_types import CallbackQueryCute
5
+ from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
6
6
  from telegrinder.bot.dispatch.waiter_machine import WaiterMachine
7
7
  from telegrinder.bot.scenario.abc import ABCScenario
8
- from telegrinder.tools import InlineButton, InlineKeyboard
8
+ from telegrinder.tools.keyboard import InlineButton, InlineKeyboard
9
9
  from telegrinder.tools.parse_mode import ParseMode
10
10
  from telegrinder.types.objects import InlineKeyboardMarkup
11
11
 
12
12
  if typing.TYPE_CHECKING:
13
13
  from telegrinder.api import API
14
- from telegrinder.bot.dispatch.view.abc import BaseStateView
14
+ from telegrinder.bot.dispatch.view.base import BaseStateView
15
15
 
16
16
 
17
17
  @dataclasses.dataclass(slots=True)
@@ -1,11 +1,11 @@
1
1
  import typing
2
2
 
3
- from telegrinder.bot.cute_types import CallbackQueryCute
3
+ from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
4
4
  from telegrinder.bot.scenario.checkbox import Checkbox
5
5
 
6
6
  if typing.TYPE_CHECKING:
7
7
  from telegrinder.api import API
8
- from telegrinder.bot.dispatch.view.abc import BaseStateView
8
+ from telegrinder.bot.dispatch.view.base import BaseStateView
9
9
 
10
10
 
11
11
  class Choice(Checkbox):
@@ -5,8 +5,8 @@ import aiohttp
5
5
  import certifi
6
6
  from aiohttp import ClientSession, TCPConnector
7
7
 
8
+ import telegrinder.msgspec_json as json
8
9
  from telegrinder.client.abc import ABCClient
9
- from telegrinder.modules import JSONModule, json
10
10
 
11
11
  if typing.TYPE_CHECKING:
12
12
  from aiohttp import ClientResponse
@@ -16,12 +16,10 @@ class AiohttpClient(ABCClient):
16
16
  def __init__(
17
17
  self,
18
18
  session: ClientSession | None = None,
19
- json_processing_module: JSONModule | None = None,
20
19
  timeout: aiohttp.ClientTimeout | None = None,
21
20
  **session_params: typing.Any,
22
21
  ) -> None:
23
22
  self.session = session
24
- self.json_processing_module = json_processing_module or json
25
23
  self.session_params = session_params
26
24
  self.timeout = timeout or aiohttp.ClientTimeout(total=0)
27
25
 
@@ -43,7 +41,7 @@ class AiohttpClient(ABCClient):
43
41
  if not self.session:
44
42
  self.session = ClientSession(
45
43
  connector=TCPConnector(ssl=ssl.create_default_context(cafile=certifi.where())),
46
- json_serialize=self.json_processing_module.dumps,
44
+ json_serialize=json.dumps,
47
45
  **self.session_params,
48
46
  )
49
47
  async with self.session.request(
@@ -65,8 +63,8 @@ class AiohttpClient(ABCClient):
65
63
  ) -> dict[str, typing.Any]:
66
64
  response = await self.request_raw(url, method, data, **kwargs)
67
65
  return await response.json(
68
- encoding="utf-8",
69
- loads=self.json_processing_module.loads,
66
+ encoding="UTF-8",
67
+ loads=json.loads,
70
68
  content_type=None,
71
69
  )
72
70
 
@@ -78,7 +76,7 @@ class AiohttpClient(ABCClient):
78
76
  **kwargs: typing.Any,
79
77
  ) -> str:
80
78
  response = await self.request_raw(url, method, data, **kwargs) # type: ignore
81
- return await response.text(encoding="utf-8")
79
+ return await response.text(encoding="UTF-8")
82
80
 
83
81
  async def request_bytes(
84
82
  self,