telegrinder 0.1.dev165__py3-none-any.whl → 0.1.dev166__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of telegrinder might be problematic. Click here for more details.

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