telegrinder 0.3.0.post1__py3-none-any.whl → 0.3.1__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 (73) hide show
  1. telegrinder/__init__.py +1 -1
  2. telegrinder/bot/cute_types/callback_query.py +2 -14
  3. telegrinder/bot/cute_types/chat_join_request.py +1 -1
  4. telegrinder/bot/cute_types/chat_member_updated.py +1 -1
  5. telegrinder/bot/cute_types/inline_query.py +1 -6
  6. telegrinder/bot/cute_types/message.py +1 -21
  7. telegrinder/bot/cute_types/update.py +1 -1
  8. telegrinder/bot/dispatch/abc.py +45 -3
  9. telegrinder/bot/dispatch/dispatch.py +8 -8
  10. telegrinder/bot/dispatch/handler/func.py +8 -10
  11. telegrinder/bot/dispatch/process.py +1 -1
  12. telegrinder/bot/dispatch/view/base.py +31 -20
  13. telegrinder/bot/dispatch/view/raw.py +20 -16
  14. telegrinder/bot/dispatch/waiter_machine/actions.py +3 -0
  15. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +15 -18
  16. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +21 -13
  17. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +15 -16
  18. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +6 -6
  19. telegrinder/bot/dispatch/waiter_machine/machine.py +24 -30
  20. telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -4
  21. telegrinder/bot/rules/abc.py +42 -7
  22. telegrinder/bot/rules/adapter/raw_update.py +1 -3
  23. telegrinder/bot/rules/callback_data.py +7 -7
  24. telegrinder/bot/rules/chat_join.py +5 -5
  25. telegrinder/bot/rules/command.py +1 -1
  26. telegrinder/bot/rules/enum_text.py +4 -1
  27. telegrinder/bot/rules/fuzzy.py +1 -1
  28. telegrinder/bot/rules/inline.py +6 -7
  29. telegrinder/bot/rules/integer.py +1 -1
  30. telegrinder/bot/rules/is_from.py +20 -20
  31. telegrinder/bot/rules/markup.py +6 -3
  32. telegrinder/bot/rules/mention.py +1 -1
  33. telegrinder/bot/rules/message.py +2 -2
  34. telegrinder/bot/rules/message_entities.py +2 -2
  35. telegrinder/bot/rules/node.py +1 -1
  36. telegrinder/bot/rules/regex.py +1 -1
  37. telegrinder/bot/rules/start.py +2 -2
  38. telegrinder/bot/rules/text.py +6 -4
  39. telegrinder/bot/rules/update.py +1 -1
  40. telegrinder/bot/scenario/checkbox.py +9 -1
  41. telegrinder/msgspec_utils.py +11 -3
  42. telegrinder/node/attachment.py +6 -6
  43. telegrinder/node/base.py +17 -11
  44. telegrinder/node/callback_query.py +1 -1
  45. telegrinder/node/command.py +1 -1
  46. telegrinder/node/composer.py +5 -2
  47. telegrinder/node/container.py +1 -1
  48. telegrinder/node/event.py +1 -1
  49. telegrinder/node/message.py +1 -1
  50. telegrinder/node/polymorphic.py +6 -3
  51. telegrinder/node/rule.py +1 -1
  52. telegrinder/node/source.py +5 -7
  53. telegrinder/node/text.py +2 -2
  54. telegrinder/node/tools/generator.py +1 -2
  55. telegrinder/node/update.py +3 -3
  56. telegrinder/rules.py +2 -0
  57. telegrinder/tools/buttons.py +4 -4
  58. telegrinder/tools/error_handler/abc.py +7 -7
  59. telegrinder/tools/error_handler/error_handler.py +58 -47
  60. telegrinder/tools/formatting/html.py +0 -2
  61. telegrinder/tools/functional.py +3 -0
  62. telegrinder/tools/global_context/telegrinder_ctx.py +2 -0
  63. telegrinder/tools/i18n/__init__.py +1 -1
  64. telegrinder/tools/i18n/{base.py → abc.py} +0 -0
  65. telegrinder/tools/i18n/middleware/__init__.py +1 -1
  66. telegrinder/tools/i18n/middleware/{base.py → abc.py} +3 -2
  67. telegrinder/tools/i18n/simple.py +11 -12
  68. telegrinder/tools/keyboard.py +9 -9
  69. telegrinder/tools/magic.py +8 -4
  70. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/METADATA +1 -1
  71. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/RECORD +73 -73
  72. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/LICENSE +0 -0
  73. {telegrinder-0.3.0.post1.dist-info → telegrinder-0.3.1.dist-info}/WHEEL +0 -0
@@ -1,7 +1,6 @@
1
1
  from telegrinder.bot.cute_types import MessageCute as Message
2
2
  from telegrinder.bot.dispatch.view import MessageView
3
-
4
- from .hasher import Hasher
3
+ from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
5
4
 
6
5
 
7
6
  def from_chat_hash(chat_id: int) -> int:
@@ -12,9 +11,12 @@ def get_chat_from_event(event: Message) -> int:
12
11
  return event.chat.id
13
12
 
14
13
 
15
- MESSAGE_IN_CHAT = Hasher(
16
- view=MessageView, get_hash_from_data=from_chat_hash, get_data_from_event=get_chat_from_event
17
- )
14
+ def from_user_in_chat_hash(chat_and_user: tuple[int, int]) -> str:
15
+ return f"{chat_and_user[0]}_{chat_and_user[1]}"
16
+
17
+
18
+ def get_user_in_chat_from_event(event: Message) -> tuple[int, int]:
19
+ return event.chat.id, event.from_user.id
18
20
 
19
21
 
20
22
  def from_user_hash(from_id: int) -> int:
@@ -25,23 +27,20 @@ def get_user_from_event(event: Message) -> int:
25
27
  return event.from_user.id
26
28
 
27
29
 
30
+ MESSAGE_IN_CHAT = Hasher(
31
+ view_class=MessageView,
32
+ get_hash_from_data=from_chat_hash,
33
+ get_data_from_event=get_chat_from_event,
34
+ )
35
+
28
36
  MESSAGE_FROM_USER = Hasher(
29
- view=MessageView,
37
+ view_class=MessageView,
30
38
  get_hash_from_data=from_user_hash,
31
39
  get_data_from_event=get_user_from_event,
32
40
  )
33
41
 
34
-
35
- def from_user_in_chat_hash(chat_and_user: tuple[int, int]) -> str:
36
- return f"{chat_and_user[0]}_{chat_and_user[1]}"
37
-
38
-
39
- def get_user_in_chat_from_event(event: Message) -> tuple[int, int]:
40
- return event.chat.id, event.from_user.id
41
-
42
-
43
42
  MESSAGE_FROM_USER_IN_CHAT = Hasher(
44
- view=MessageView,
43
+ view_class=MessageView,
45
44
  get_hash_from_data=from_user_in_chat_hash,
46
45
  get_data_from_event=get_user_in_chat_from_event,
47
46
  )
@@ -1,16 +1,16 @@
1
- from fntypes import Option
1
+ from fntypes.option import Option
2
2
 
3
3
  from telegrinder.bot.dispatch.view import BaseStateView
4
+ from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import ECHO, Event, Hasher
4
5
  from telegrinder.tools.functional import from_optional
5
6
 
6
- from .hasher import ECHO, Event, Hasher
7
-
8
7
 
9
8
  class StateViewHasher(Hasher[Event, int]):
10
- view: BaseStateView
9
+ view: BaseStateView[Event]
11
10
 
12
- def __init__(self, view: type[BaseStateView[Event]]):
13
- super().__init__(view, get_hash_from_data=ECHO)
11
+ def __init__(self, view: BaseStateView[Event]) -> None:
12
+ self.view = view
13
+ super().__init__(view.__class__, get_hash_from_data=ECHO)
14
14
 
15
15
  def get_data_from_event(self, event: Event) -> Option[int]:
16
16
  return from_optional(self.view.get_state_key(event))
@@ -41,16 +41,25 @@ class WaiterMachine:
41
41
  self.storage: Storage = {}
42
42
 
43
43
  def __repr__(self) -> str:
44
- return "<{}: max_storage_size={}, {}>".format(
44
+ return "<{}: max_storage_size={}, base_state_lifetime={!r}>".format(
45
45
  self.__class__.__name__,
46
46
  self.max_storage_size,
47
- ", ".join(
48
- f"{view_name}: {len(self.storage[view_name].values())} shortstates"
49
- for view_name in self.storage
50
- )
51
- or "empty",
47
+ self.base_state_lifetime,
52
48
  )
53
49
 
50
+ async def drop_all(self) -> None:
51
+ """Drops all waiters in storage."""
52
+
53
+ for hasher in self.storage:
54
+ for ident, short_state in self.storage[hasher].items():
55
+ if short_state.context:
56
+ await self.drop(
57
+ hasher,
58
+ ident,
59
+ )
60
+ else:
61
+ short_state.cancel()
62
+
54
63
  async def drop(
55
64
  self,
56
65
  hasher: Hasher[EventModel, HasherData],
@@ -66,7 +75,7 @@ class WaiterMachine:
66
75
  short_state = self.storage[hasher].pop(waiter_id, None)
67
76
  if short_state is None:
68
77
  raise LookupError(
69
- "Waiter with identificator {} is not found for hasher {!r}".format(waiter_id, hasher)
78
+ "Waiter with identificator {} is not found for hasher {!r}.".format(waiter_id, hasher)
70
79
  )
71
80
 
72
81
  if on_drop := short_state.actions.get("on_drop"):
@@ -74,19 +83,6 @@ class WaiterMachine:
74
83
 
75
84
  short_state.cancel()
76
85
 
77
- async def drop_all(self) -> None:
78
- """Drops all waiters in storage."""
79
-
80
- for hasher in self.storage:
81
- for ident, short_state in self.storage[hasher].items():
82
- if short_state.context:
83
- await self.drop(
84
- hasher,
85
- ident,
86
- )
87
- else:
88
- short_state.cancel()
89
-
90
86
  async def wait_from_event(
91
87
  self,
92
88
  view: BaseStateView[EventModel],
@@ -97,11 +93,11 @@ class WaiterMachine:
97
93
  lifetime: datetime.timedelta | float | None = None,
98
94
  **actions: typing.Unpack[WaiterActions[EventModel]],
99
95
  ) -> ShortStateContext[EventModel]:
100
- hasher = StateViewHasher(view.__class__)
96
+ hasher = StateViewHasher(view)
101
97
  return await self.wait(
102
98
  hasher=hasher,
103
99
  data=hasher.get_data_from_event(event).expect(
104
- RuntimeError("Hasher couldn't create data from event.")
100
+ RuntimeError("Hasher couldn't create data from event."),
105
101
  ),
106
102
  filter=filter,
107
103
  release=release,
@@ -124,18 +120,18 @@ class WaiterMachine:
124
120
 
125
121
  event = asyncio.Event()
126
122
  short_state = ShortState[EventModel](
127
- filter=filter,
123
+ event,
124
+ actions,
128
125
  release=release,
129
- event=event,
126
+ filter=filter,
130
127
  lifetime=lifetime or self.base_state_lifetime,
131
- actions=actions,
132
128
  )
133
129
  waiter_hash = hasher.get_hash_from_data(data).expect(RuntimeError("Hasher couldn't create hash."))
134
130
 
135
131
  if hasher not in self.storage:
136
132
  if self.dispatch:
137
- view: BaseView[EventModel] = self.dispatch.get_view(hasher.view).expect(
138
- RuntimeError(f"View {hasher.view.__name__} is not defined in dispatch")
133
+ view: BaseView[EventModel] = self.dispatch.get_view(hasher.view_class).expect(
134
+ RuntimeError(f"View {hasher.view_class.__name__!r} is not defined in dispatch.")
139
135
  )
140
136
  view.middlewares.insert(0, WaiterMiddleware(self, hasher))
141
137
  self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
@@ -148,9 +144,7 @@ class WaiterMachine:
148
144
  assert short_state.context is not None
149
145
  return short_state.context
150
146
 
151
- async def clear_storage(
152
- self,
153
- ) -> None:
147
+ async def clear_storage(self) -> None:
154
148
  """Clears storage."""
155
149
 
156
150
  for hasher in self.storage:
@@ -27,10 +27,17 @@ class ShortStateContext(typing.Generic[EventModel], typing.NamedTuple):
27
27
  @dataclasses.dataclass(slots=True)
28
28
  class ShortState(typing.Generic[EventModel]):
29
29
  event: asyncio.Event
30
- actions: "WaiterActions"
30
+ actions: "WaiterActions[EventModel]"
31
+
32
+ release: ABCRule | None = dataclasses.field(
33
+ default=None,
34
+ kw_only=True,
35
+ )
36
+ filter: ABCRule | None = dataclasses.field(
37
+ default=None,
38
+ kw_only=True,
39
+ )
31
40
 
32
- release: ABCRule | None = None
33
- filter: ABCRule | None = None
34
41
  lifetime: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
35
42
  default=None,
36
43
  kw_only=True,
@@ -38,7 +45,6 @@ class ShortState(typing.Generic[EventModel]):
38
45
 
39
46
  expiration_date: datetime.datetime | None = dataclasses.field(init=False, kw_only=True)
40
47
  creation_date: datetime.datetime = dataclasses.field(init=False)
41
-
42
48
  context: ShortStateContext[EventModel] | None = dataclasses.field(default=None, init=False, kw_only=True)
43
49
 
44
50
  def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
@@ -10,7 +10,7 @@ from telegrinder.bot.dispatch.process import check_rule
10
10
  from telegrinder.bot.rules.adapter import ABCAdapter, RawUpdateAdapter
11
11
  from telegrinder.bot.rules.adapter.node import Event
12
12
  from telegrinder.node.base import Node, get_nodes, is_node
13
- from telegrinder.tools.i18n.base import ABCTranslator
13
+ from telegrinder.tools.i18n.abc import ABCTranslator
14
14
  from telegrinder.tools.magic import (
15
15
  cache_translation,
16
16
  get_annotations,
@@ -22,8 +22,9 @@ from telegrinder.types.objects import Update as UpdateObject
22
22
  if typing.TYPE_CHECKING:
23
23
  from telegrinder.node.composer import NodeCollection
24
24
 
25
- AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any)
25
+ AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any, contravariant=True)
26
26
 
27
+ CheckResult: typing.TypeAlias = bool | typing.Coroutine[typing.Any, typing.Any, bool]
27
28
  Message: typing.TypeAlias = MessageCute
28
29
  Update: typing.TypeAlias = UpdateCute
29
30
 
@@ -45,12 +46,42 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
45
46
  adapter: ABCAdapter[UpdateObject, AdaptTo]
46
47
  requires: list["ABCRule"] = []
47
48
 
48
- if not typing.TYPE_CHECKING:
49
+ if typing.TYPE_CHECKING:
50
+
51
+ @typing.overload
52
+ def check(self) -> CheckResult: ...
53
+
54
+ @typing.overload
55
+ def check(self, event: AdaptTo, /) -> CheckResult: ...
56
+
57
+ @typing.overload
58
+ def check(self, event: AdaptTo, ctx: Context, /) -> CheckResult: ...
59
+
60
+ @typing.overload
61
+ def check(
62
+ self,
63
+ event: AdaptTo,
64
+ ctx: Context,
65
+ /,
66
+ *args: typing.Any,
67
+ **kwargs: typing.Any,
68
+ ) -> CheckResult: ...
69
+
70
+ @typing.overload
71
+ def check(self, event: AdaptTo, /, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
72
+
73
+ @typing.overload
74
+ def check(self, ctx: Context, /, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
75
+
76
+ @abstractmethod
77
+ def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult:
78
+ pass
79
+ else:
49
80
  adapter = RawUpdateAdapter()
50
81
 
51
- @abstractmethod
52
- async def check(self, event: AdaptTo, *, ctx: Context) -> bool:
53
- pass
82
+ @abstractmethod
83
+ def check(self, *args, **kwargs):
84
+ pass
54
85
 
55
86
  def __init_subclass__(cls, requires: list["ABCRule"] | None = None) -> None:
56
87
  """Merges requirements from inherited classes and rule-specific requirements."""
@@ -141,7 +172,10 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
141
172
  "because it cannot be resolved."
142
173
  )
143
174
 
144
- return await bound_check_rule(**kw)
175
+ result = bound_check_rule(**kw) # type: ignore
176
+ if inspect.isawaitable(result):
177
+ result = await result
178
+ return result
145
179
 
146
180
  async def translate(self, translator: ABCTranslator) -> typing.Self:
147
181
  return self
@@ -199,5 +233,6 @@ __all__ = (
199
233
  "Never",
200
234
  "NotRule",
201
235
  "OrRule",
236
+ "CheckResult",
202
237
  "with_caching_translations",
203
238
  )
@@ -22,9 +22,7 @@ class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
22
22
  ) -> Result[UpdateCute, AdapterError]:
23
23
  if self.ADAPTED_VALUE_KEY not in context:
24
24
  context[self.ADAPTED_VALUE_KEY] = (
25
- UpdateCute.from_update(update, api)
26
- if not isinstance(update, UpdateCute)
27
- else update
25
+ UpdateCute.from_update(update, api) if not isinstance(update, UpdateCute) else update
28
26
  )
29
27
  return Ok(context[self.ADAPTED_VALUE_KEY])
30
28
 
@@ -12,7 +12,7 @@ from telegrinder.model import decoder
12
12
  from telegrinder.tools.buttons import DataclassInstance
13
13
  from telegrinder.types.enums import UpdateType
14
14
 
15
- from .abc import ABCRule
15
+ from .abc import ABCRule, CheckResult
16
16
  from .markup import Markup, PatternLike, check_string
17
17
 
18
18
  CallbackQuery: typing.TypeAlias = CallbackQueryCute
@@ -26,12 +26,12 @@ class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
26
26
  adapter: EventAdapter[CallbackQuery] = EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery)
27
27
 
28
28
  @abc.abstractmethod
29
- async def check(self, event: CallbackQuery, context: Context) -> bool:
29
+ def check(self, event: CallbackQuery, context: Context) -> CheckResult:
30
30
  pass
31
31
 
32
32
 
33
33
  class HasData(CallbackQueryRule):
34
- async def check(self, event: CallbackQuery) -> bool:
34
+ def check(self, event: CallbackQuery) -> bool:
35
35
  return bool(event.data.unwrap_or_none())
36
36
 
37
37
 
@@ -122,7 +122,7 @@ class CallbackDataEq(CallbackQueryDataRule):
122
122
  def __init__(self, value: str, /) -> None:
123
123
  self.value = value
124
124
 
125
- async def check(self, event: CallbackQuery) -> bool:
125
+ def check(self, event: CallbackQuery) -> bool:
126
126
  return event.data.unwrap() == self.value
127
127
 
128
128
 
@@ -130,7 +130,7 @@ class CallbackDataJsonEq(CallbackQueryDataRule):
130
130
  def __init__(self, d: dict[str, typing.Any], /) -> None:
131
131
  self.d = d
132
132
 
133
- async def check(self, event: CallbackQuery) -> bool:
133
+ def check(self, event: CallbackQuery) -> bool:
134
134
  return event.decode_callback_data().unwrap_or_none() == self.d
135
135
 
136
136
 
@@ -144,7 +144,7 @@ class CallbackDataJsonModel(CallbackQueryDataRule):
144
144
  self.model = model
145
145
  self.alias = alias or "data"
146
146
 
147
- async def check(self, event: CallbackQuery, ctx: Context) -> bool:
147
+ def check(self, event: CallbackQuery, ctx: Context) -> bool:
148
148
  with suppress(BaseException):
149
149
  ctx.set(self.alias, decoder.decode(event.data.unwrap().encode(), type=self.model))
150
150
  return True
@@ -155,7 +155,7 @@ class CallbackDataMarkup(CallbackQueryDataRule):
155
155
  def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
156
156
  self.patterns = Markup(patterns).patterns
157
157
 
158
- async def check(self, event: CallbackQuery, ctx: Context) -> bool:
158
+ def check(self, event: CallbackQuery, ctx: Context) -> bool:
159
159
  return check_string(self.patterns, event.data.unwrap(), ctx)
160
160
 
161
161
 
@@ -6,7 +6,7 @@ from telegrinder.bot.dispatch.context import Context
6
6
  from telegrinder.bot.rules.adapter import EventAdapter
7
7
  from telegrinder.types.enums import UpdateType
8
8
 
9
- from .abc import ABCRule
9
+ from .abc import ABCRule, CheckResult
10
10
 
11
11
  ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
12
12
 
@@ -15,12 +15,12 @@ class ChatJoinRequestRule(ABCRule[ChatJoinRequest], requires=[]):
15
15
  adapter: EventAdapter[ChatJoinRequest] = EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest)
16
16
 
17
17
  @abc.abstractmethod
18
- async def check(self, event: ChatJoinRequest, context: Context) -> bool:
18
+ def check(self, event: ChatJoinRequest, context: Context) -> CheckResult:
19
19
  pass
20
20
 
21
21
 
22
22
  class HasInviteLink(ChatJoinRequestRule):
23
- async def check(self, event: ChatJoinRequest) -> bool:
23
+ def check(self, event: ChatJoinRequest) -> bool:
24
24
  return bool(event.invite_link)
25
25
 
26
26
 
@@ -28,7 +28,7 @@ class InviteLinkName(ChatJoinRequestRule, requires=[HasInviteLink()]):
28
28
  def __init__(self, name: str, /) -> None:
29
29
  self.name = name
30
30
 
31
- async def check(self, event: ChatJoinRequest) -> bool:
31
+ def check(self, event: ChatJoinRequest) -> bool:
32
32
  return event.invite_link.unwrap().name.unwrap_or_none() == self.name
33
33
 
34
34
 
@@ -36,7 +36,7 @@ class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
36
36
  def __init__(self, creator_id: int, /) -> None:
37
37
  self.creator_id = creator_id
38
38
 
39
- async def check(self, event: ChatJoinRequest) -> bool:
39
+ def check(self, event: ChatJoinRequest) -> bool:
40
40
  return event.invite_link.unwrap().creator.id == self.creator_id
41
41
 
42
42
 
@@ -97,7 +97,7 @@ class Command(ABCRule):
97
97
 
98
98
  return None
99
99
 
100
- async def check(self, command: CommandInfo, me: Me, src: Source, ctx: Context) -> bool:
100
+ def check(self, command: CommandInfo, me: Me, src: Source, ctx: Context) -> bool:
101
101
  name = self.remove_prefix(command.name)
102
102
  if name is None:
103
103
  return False
@@ -25,9 +25,12 @@ class EnumTextRule(ABCRule, typing.Generic[T]):
25
25
  return enumeration
26
26
  raise KeyError("Enumeration is undefined.")
27
27
 
28
- async def check(self, text: Text, ctx: Context) -> bool:
28
+ def check(self, text: Text, ctx: Context) -> bool:
29
29
  text = text.lower() # type: ignore
30
30
  if text not in self.texts:
31
31
  return False
32
32
  ctx.enum_text = self.find(text)
33
33
  return True
34
+
35
+
36
+ __all__ = ("EnumTextRule",)
@@ -13,7 +13,7 @@ class FuzzyText(ABCRule):
13
13
  self.texts = texts
14
14
  self.min_ratio = min_ratio
15
15
 
16
- async def check(self, message_text: Text, ctx: Context) -> bool:
16
+ def check(self, message_text: Text, ctx: Context) -> bool:
17
17
  match = max(difflib.SequenceMatcher(a=message_text, b=text).ratio() for text in self.texts)
18
18
  if match < self.min_ratio:
19
19
  return False
@@ -3,7 +3,7 @@ import typing
3
3
 
4
4
  from telegrinder.bot.cute_types import InlineQueryCute
5
5
  from telegrinder.bot.dispatch.context import Context
6
- from telegrinder.bot.rules.abc import ABCRule
6
+ from telegrinder.bot.rules.abc import ABCRule, CheckResult
7
7
  from telegrinder.bot.rules.adapter import EventAdapter
8
8
  from telegrinder.types.enums import ChatType, UpdateType
9
9
 
@@ -16,12 +16,11 @@ class InlineQueryRule(ABCRule[InlineQuery], abc.ABC):
16
16
  adapter: EventAdapter[InlineQuery] = EventAdapter(UpdateType.INLINE_QUERY, InlineQuery)
17
17
 
18
18
  @abc.abstractmethod
19
- async def check(self, query: InlineQuery, ctx: Context) -> bool:
20
- ...
19
+ def check(self, query: InlineQuery, ctx: Context) -> CheckResult: ...
21
20
 
22
21
 
23
22
  class HasLocation(InlineQueryRule):
24
- async def check(self, query: InlineQuery) -> bool:
23
+ def check(self, query: InlineQuery) -> bool:
25
24
  return bool(query.location)
26
25
 
27
26
 
@@ -29,7 +28,7 @@ class InlineQueryChatType(InlineQueryRule):
29
28
  def __init__(self, chat_type: ChatType, /) -> None:
30
29
  self.chat_type = chat_type
31
30
 
32
- async def check(self, query: InlineQuery) -> bool:
31
+ def check(self, query: InlineQuery) -> bool:
33
32
  return query.chat_type.map(lambda x: x == self.chat_type).unwrap_or(False)
34
33
 
35
34
 
@@ -40,7 +39,7 @@ class InlineQueryText(InlineQueryRule):
40
39
  ]
41
40
  self.lower_case = lower_case
42
41
 
43
- async def check(self, query: InlineQuery) -> bool:
42
+ def check(self, query: InlineQuery) -> bool:
44
43
  return (query.query.lower() if self.lower_case else query.query) in self.texts
45
44
 
46
45
 
@@ -48,7 +47,7 @@ class InlineQueryMarkup(InlineQueryRule):
48
47
  def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
49
48
  self.patterns = Markup(patterns).patterns
50
49
 
51
- async def check(self, query: InlineQuery, ctx: Context) -> bool:
50
+ def check(self, query: InlineQuery, ctx: Context) -> bool:
52
51
  return check_string(self.patterns, query.query, ctx)
53
52
 
54
53
 
@@ -13,7 +13,7 @@ class IntegerInRange(ABCRule):
13
13
  def __init__(self, rng: range) -> None:
14
14
  self.rng = rng
15
15
 
16
- async def check(self, integer: TextInteger) -> bool:
16
+ def check(self, integer: TextInteger) -> bool:
17
17
  return integer in self.rng
18
18
 
19
19
 
@@ -8,17 +8,17 @@ from .message import MessageRule
8
8
 
9
9
 
10
10
  class IsBot(ABCRule):
11
- async def check(self, user: UserSource) -> bool:
11
+ def check(self, user: UserSource) -> bool:
12
12
  return user.is_bot
13
13
 
14
14
 
15
15
  class IsUser(ABCRule):
16
- async def check(self, user: UserSource) -> bool:
16
+ def check(self, user: UserSource) -> bool:
17
17
  return not user.is_bot
18
18
 
19
19
 
20
20
  class IsPremium(ABCRule):
21
- async def check(self, user: UserSource) -> bool:
21
+ def check(self, user: UserSource) -> bool:
22
22
  return user.is_premium.unwrap_or(False)
23
23
 
24
24
 
@@ -26,7 +26,7 @@ class IsLanguageCode(ABCRule):
26
26
  def __init__(self, lang_codes: str | list[str], /) -> None:
27
27
  self.lang_codes = [lang_codes] if isinstance(lang_codes, str) else lang_codes
28
28
 
29
- async def check(self, user: UserSource) -> bool:
29
+ def check(self, user: UserSource) -> bool:
30
30
  return user.language_code.unwrap_or_none() in self.lang_codes
31
31
 
32
32
 
@@ -34,12 +34,12 @@ class IsUserId(ABCRule):
34
34
  def __init__(self, user_ids: int | list[int], /) -> None:
35
35
  self.user_ids = [user_ids] if isinstance(user_ids, int) else user_ids
36
36
 
37
- async def check(self, user: UserSource) -> bool:
37
+ def check(self, user: UserSource) -> bool:
38
38
  return user.id in self.user_ids
39
39
 
40
40
 
41
41
  class IsForum(ABCRule):
42
- async def check(self, chat: ChatSource) -> bool:
42
+ def check(self, chat: ChatSource) -> bool:
43
43
  return chat.is_forum.unwrap_or(False)
44
44
 
45
45
 
@@ -47,32 +47,32 @@ class IsChatId(ABCRule):
47
47
  def __init__(self, chat_ids: int | list[int], /) -> None:
48
48
  self.chat_ids = [chat_ids] if isinstance(chat_ids, int) else chat_ids
49
49
 
50
- async def check(self, chat: ChatSource) -> bool:
50
+ def check(self, chat: ChatSource) -> bool:
51
51
  return chat.id in self.chat_ids
52
52
 
53
53
 
54
54
  class IsPrivate(ABCRule):
55
- async def check(self, chat: ChatSource) -> bool:
55
+ def check(self, chat: ChatSource) -> bool:
56
56
  return chat.type == ChatType.PRIVATE
57
57
 
58
58
 
59
59
  class IsGroup(ABCRule):
60
- async def check(self, chat: ChatSource) -> bool:
60
+ def check(self, chat: ChatSource) -> bool:
61
61
  return chat.type == ChatType.GROUP
62
62
 
63
63
 
64
64
  class IsSuperGroup(ABCRule):
65
- async def check(self, chat: ChatSource) -> bool:
65
+ def check(self, chat: ChatSource) -> bool:
66
66
  return chat.type == ChatType.SUPERGROUP
67
67
 
68
68
 
69
69
  class IsChat(ABCRule):
70
- async def check(self, chat: ChatSource) -> bool:
70
+ def check(self, chat: ChatSource) -> bool:
71
71
  return chat.type in (ChatType.GROUP, ChatType.SUPERGROUP)
72
72
 
73
73
 
74
74
  class IsDice(MessageRule):
75
- async def check(self, message: Message) -> bool:
75
+ def check(self, message: Message) -> bool:
76
76
  return bool(message.dice)
77
77
 
78
78
 
@@ -80,12 +80,12 @@ class IsDiceEmoji(MessageRule, requires=[IsDice()]):
80
80
  def __init__(self, dice_emoji: DiceEmoji, /) -> None:
81
81
  self.dice_emoji = dice_emoji
82
82
 
83
- async def check(self, message: Message) -> bool:
83
+ def check(self, message: Message) -> bool:
84
84
  return message.dice.unwrap().emoji == self.dice_emoji
85
85
 
86
86
 
87
87
  class IsForward(MessageRule):
88
- async def check(self, message: Message) -> bool:
88
+ def check(self, message: Message) -> bool:
89
89
  return bool(message.forward_origin)
90
90
 
91
91
 
@@ -93,32 +93,32 @@ class IsForwardType(MessageRule, requires=[IsForward()]):
93
93
  def __init__(self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /) -> None:
94
94
  self.fwd_type = fwd_type
95
95
 
96
- async def check(self, message: Message) -> bool:
96
+ def check(self, message: Message) -> bool:
97
97
  return message.forward_origin.unwrap().v.type == self.fwd_type
98
98
 
99
99
 
100
100
  class IsReply(MessageRule):
101
- async def check(self, message: Message) -> bool:
101
+ def check(self, message: Message) -> bool:
102
102
  return bool(message.reply_to_message)
103
103
 
104
104
 
105
105
  class IsSticker(MessageRule):
106
- async def check(self, message: Message) -> bool:
106
+ def check(self, message: Message) -> bool:
107
107
  return bool(message.sticker)
108
108
 
109
109
 
110
110
  class IsVideoNote(MessageRule):
111
- async def check(self, message: Message) -> bool:
111
+ def check(self, message: Message) -> bool:
112
112
  return bool(message.video_note)
113
113
 
114
114
 
115
115
  class IsDocument(MessageRule):
116
- async def check(self, message: Message) -> bool:
116
+ def check(self, message: Message) -> bool:
117
117
  return bool(message.document)
118
118
 
119
119
 
120
120
  class IsPhoto(MessageRule):
121
- async def check(self, message: Message) -> bool:
121
+ def check(self, message: Message) -> bool:
122
122
  return bool(message.photo)
123
123
 
124
124
 
@@ -24,16 +24,19 @@ def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
24
24
 
25
25
 
26
26
  class Markup(ABCRule):
27
- """Markup Language. See [VBML docs](https://github.com/tesseradecade/vbml/blob/master/docs/index.md)"""
27
+ """Markup Language. See the [vbml documentation](https://github.com/tesseradecade/vbml/blob/master/docs/index.md)."""
28
28
 
29
29
  def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
30
30
  if not isinstance(patterns, list):
31
31
  patterns = [patterns]
32
32
  self.patterns = [
33
- vbml.Pattern(pattern) if isinstance(pattern, str) else pattern for pattern in patterns
33
+ vbml.Pattern(pattern, flags=global_ctx.vbml_pattern_flags)
34
+ if isinstance(pattern, str)
35
+ else pattern
36
+ for pattern in patterns
34
37
  ]
35
38
 
36
- async def check(self, text: Text, ctx: Context) -> bool:
39
+ def check(self, text: Text, ctx: Context) -> bool:
37
40
  return check_string(self.patterns, text, ctx)
38
41
 
39
42