telegrinder 0.1.dev165__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.

Files changed (101) hide show
  1. telegrinder/__init__.py +20 -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 +12 -0
  6. telegrinder/bot/bot.py +2 -2
  7. telegrinder/bot/cute_types/__init__.py +4 -0
  8. telegrinder/bot/cute_types/base.py +10 -10
  9. telegrinder/bot/cute_types/callback_query.py +1 -3
  10. telegrinder/bot/cute_types/chat_join_request.py +65 -0
  11. telegrinder/bot/cute_types/chat_member_updated.py +246 -0
  12. telegrinder/bot/cute_types/message.py +44 -38
  13. telegrinder/bot/cute_types/update.py +5 -4
  14. telegrinder/bot/cute_types/utils.py +40 -20
  15. telegrinder/bot/dispatch/__init__.py +8 -1
  16. telegrinder/bot/dispatch/composition.py +7 -7
  17. telegrinder/bot/dispatch/context.py +9 -10
  18. telegrinder/bot/dispatch/dispatch.py +30 -22
  19. telegrinder/bot/dispatch/handler/abc.py +1 -0
  20. telegrinder/bot/dispatch/handler/func.py +21 -5
  21. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  22. telegrinder/bot/dispatch/middleware/abc.py +2 -4
  23. telegrinder/bot/dispatch/process.py +4 -3
  24. telegrinder/bot/dispatch/return_manager/__init__.py +1 -1
  25. telegrinder/bot/dispatch/return_manager/abc.py +28 -20
  26. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -2
  27. telegrinder/bot/dispatch/return_manager/inline_query.py +4 -2
  28. telegrinder/bot/dispatch/return_manager/message.py +8 -4
  29. telegrinder/bot/dispatch/view/__init__.py +8 -2
  30. telegrinder/bot/dispatch/view/abc.py +27 -23
  31. telegrinder/bot/dispatch/view/box.py +74 -11
  32. telegrinder/bot/dispatch/view/callback_query.py +1 -3
  33. telegrinder/bot/dispatch/view/chat_join_request.py +17 -0
  34. telegrinder/bot/dispatch/view/chat_member.py +26 -0
  35. telegrinder/bot/dispatch/view/message.py +23 -1
  36. telegrinder/bot/dispatch/view/raw.py +112 -0
  37. telegrinder/bot/dispatch/waiter_machine/machine.py +41 -26
  38. telegrinder/bot/dispatch/waiter_machine/middleware.py +14 -7
  39. telegrinder/bot/dispatch/waiter_machine/short_state.py +10 -7
  40. telegrinder/bot/polling/polling.py +2 -4
  41. telegrinder/bot/rules/__init__.py +20 -12
  42. telegrinder/bot/rules/abc.py +0 -9
  43. telegrinder/bot/rules/adapter/event.py +29 -22
  44. telegrinder/bot/rules/callback_data.py +15 -18
  45. telegrinder/bot/rules/chat_join.py +47 -0
  46. telegrinder/bot/rules/enum_text.py +7 -2
  47. telegrinder/bot/rules/fuzzy.py +1 -2
  48. telegrinder/bot/rules/inline.py +3 -3
  49. telegrinder/bot/rules/is_from.py +39 -51
  50. telegrinder/bot/rules/markup.py +1 -2
  51. telegrinder/bot/rules/mention.py +1 -4
  52. telegrinder/bot/rules/message.py +17 -0
  53. telegrinder/bot/rules/message_entities.py +1 -1
  54. telegrinder/bot/rules/regex.py +1 -2
  55. telegrinder/bot/rules/rule_enum.py +1 -3
  56. telegrinder/bot/rules/start.py +7 -7
  57. telegrinder/bot/rules/text.py +2 -1
  58. telegrinder/bot/rules/update.py +16 -0
  59. telegrinder/bot/scenario/checkbox.py +5 -7
  60. telegrinder/client/aiohttp.py +5 -7
  61. telegrinder/model.py +37 -22
  62. telegrinder/modules.py +15 -33
  63. telegrinder/msgspec_utils.py +34 -35
  64. telegrinder/node/__init__.py +12 -12
  65. telegrinder/node/attachment.py +21 -7
  66. telegrinder/node/base.py +14 -13
  67. telegrinder/node/composer.py +5 -5
  68. telegrinder/node/container.py +1 -1
  69. telegrinder/node/message.py +3 -1
  70. telegrinder/node/rule.py +4 -4
  71. telegrinder/node/source.py +6 -2
  72. telegrinder/node/text.py +3 -1
  73. telegrinder/node/tools/generator.py +1 -1
  74. telegrinder/tools/__init__.py +3 -1
  75. telegrinder/tools/buttons.py +4 -6
  76. telegrinder/tools/error_handler/abc.py +1 -2
  77. telegrinder/tools/error_handler/error.py +3 -6
  78. telegrinder/tools/error_handler/error_handler.py +34 -24
  79. telegrinder/tools/formatting/html.py +9 -5
  80. telegrinder/tools/formatting/links.py +1 -3
  81. telegrinder/tools/formatting/spec_html_formats.py +1 -1
  82. telegrinder/tools/global_context/abc.py +3 -1
  83. telegrinder/tools/global_context/global_context.py +13 -31
  84. telegrinder/tools/global_context/telegrinder_ctx.py +1 -1
  85. telegrinder/tools/i18n/base.py +4 -3
  86. telegrinder/tools/i18n/middleware/base.py +1 -3
  87. telegrinder/tools/i18n/simple.py +1 -3
  88. telegrinder/tools/keyboard.py +1 -1
  89. telegrinder/tools/limited_dict.py +27 -0
  90. telegrinder/tools/loop_wrapper/loop_wrapper.py +18 -14
  91. telegrinder/tools/magic.py +1 -1
  92. telegrinder/types/__init__.py +236 -0
  93. telegrinder/types/enums.py +34 -0
  94. telegrinder/types/methods.py +52 -47
  95. telegrinder/types/objects.py +533 -90
  96. telegrinder/verification_utils.py +4 -1
  97. {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/METADATA +1 -1
  98. telegrinder-0.1.dev167.dist-info/RECORD +137 -0
  99. telegrinder-0.1.dev165.dist-info/RECORD +0 -128
  100. {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/LICENSE +0 -0
  101. {telegrinder-0.1.dev165.dist-info → telegrinder-0.1.dev167.dist-info}/WHEEL +0 -0
@@ -5,21 +5,25 @@ import typing
5
5
  from telegrinder.api.abc import ABCAPI
6
6
  from telegrinder.bot.dispatch.context import Context
7
7
  from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.tools.limited_dict import LimitedDict
9
+ from telegrinder.types import Update
8
10
 
9
11
  from .middleware import WaiterMiddleware
10
12
  from .short_state import Behaviour, EventModel, ShortState
11
13
 
12
- Identificator: typing.TypeAlias = str | int
13
- Storage: typing.TypeAlias = dict[str, dict[Identificator, "ShortState"]]
14
-
15
14
  if typing.TYPE_CHECKING:
16
15
  from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
17
16
 
17
+ T = typing.TypeVar("T")
18
+
19
+ Identificator: typing.TypeAlias = str | int
20
+ Storage: typing.TypeAlias = dict[str, LimitedDict[Identificator, ShortState[EventModel]]]
21
+
18
22
 
19
23
  class WaiterMachine:
20
24
  def __init__(self) -> None:
21
25
  self.storage: Storage = {}
22
-
26
+
23
27
  def __repr__(self) -> str:
24
28
  return "<{}: storage={!r}>".format(
25
29
  self.__class__.__name__,
@@ -30,6 +34,7 @@ class WaiterMachine:
30
34
  self,
31
35
  state_view: "ABCStateView[EventModel]",
32
36
  id: Identificator,
37
+ update: Update,
33
38
  **context: typing.Any,
34
39
  ) -> None:
35
40
  view_name = state_view.__class__.__name__
@@ -37,25 +42,25 @@ class WaiterMachine:
37
42
  raise LookupError("No record of view {!r} found".format(view_name))
38
43
 
39
44
  short_state = self.storage[view_name].pop(id, None)
40
- if not short_state:
45
+ if short_state is None:
41
46
  raise LookupError(
42
47
  "Waiter with identificator {} is not found for view {!r}".format(
43
- id,
44
- view_name,
48
+ id, view_name
45
49
  )
46
50
  )
47
51
 
48
52
  waiters = typing.cast(
49
53
  typing.Iterable[asyncio.Future[typing.Any]],
50
- short_state.event._waiters # type: ignore
54
+ short_state.event._waiters, # type: ignore
51
55
  )
52
56
  for future in waiters:
53
57
  future.cancel()
54
58
 
55
59
  await self.call_behaviour(
56
60
  state_view,
57
- short_state.on_drop_behaviour,
58
61
  short_state.event,
62
+ update,
63
+ behaviour=short_state.on_drop_behaviour,
59
64
  **context,
60
65
  )
61
66
 
@@ -66,21 +71,22 @@ class WaiterMachine:
66
71
  *rules: ABCRule[EventModel],
67
72
  default: Behaviour = None,
68
73
  on_drop: Behaviour = None,
69
- expiration: datetime.timedelta | int | float | None = None,
74
+ expiration: datetime.timedelta | int | None = None,
70
75
  ) -> tuple[EventModel, Context]:
71
76
  if isinstance(expiration, int | float):
72
77
  expiration = datetime.timedelta(seconds=expiration)
73
78
 
74
79
  api: ABCAPI
75
80
  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.")
81
+ api, key = (
82
+ linked
83
+ if isinstance(linked, tuple)
84
+ else (linked.ctx_api, state_view.get_state_key(linked))
85
+ ) # type: ignore
86
+ if not key:
87
+ raise RuntimeError("Unable to get state key.")
83
88
 
89
+ event = asyncio.Event()
84
90
  short_state = ShortState(
85
91
  key,
86
92
  api,
@@ -90,33 +96,42 @@ class WaiterMachine:
90
96
  default_behaviour=default,
91
97
  on_drop_behaviour=on_drop,
92
98
  )
93
-
94
99
  view_name = state_view.__class__.__name__
95
100
  if view_name not in self.storage:
96
101
  state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
97
- self.storage[view_name] = {}
102
+ self.storage[view_name] = LimitedDict()
98
103
 
99
104
  self.storage[view_name][key] = short_state
100
-
101
105
  await event.wait()
102
106
 
103
107
  e, ctx = getattr(event, "context")
104
- self.storage[view_name].pop(key)
105
-
108
+ self.storage[view_name].pop(key, None)
106
109
  return e, ctx
107
110
 
108
111
  async def call_behaviour(
109
112
  self,
110
113
  view: "ABCStateView[EventModel]",
111
- behaviour: Behaviour,
112
114
  event: asyncio.Event | EventModel,
115
+ update: Update,
116
+ behaviour: Behaviour[EventModel] | None = None,
113
117
  **context: typing.Any,
114
118
  ) -> None:
119
+ # TODO: support param view as a behaviour
120
+
121
+ ctx = Context(**context)
122
+
123
+ if isinstance(event, asyncio.Event):
124
+ event, preset_ctx = typing.cast(
125
+ tuple[EventModel, Context],
126
+ getattr(event, "context"),
127
+ )
128
+ ctx.update(preset_ctx)
129
+
115
130
  if behaviour is None:
116
131
  return
117
- # TODO: add behaviour check
118
- # TODO: support view as a behaviour
119
- await behaviour.run(event, context) # type: ignore
120
132
 
133
+ if await behaviour.check(event.api, update, ctx):
134
+ await behaviour.run(event, ctx)
135
+
121
136
 
122
137
  __all__ = ("WaiterMachine",)
@@ -38,23 +38,24 @@ 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
+
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,
57
+ preset_context=preset_context,
56
58
  )
57
- handler.preset_context.set("short_state", short_state)
58
59
  result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
59
60
 
60
61
  if result is True:
@@ -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(self, event: EventType, short_state: "ShortState[EventType]", ctx: Context) -> None:
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,12 +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
- Behaviour: typing.TypeAlias = ABCHandler | None
17
+
18
+ Behaviour: typing.TypeAlias = ABCHandler[T] | None
16
19
 
17
20
 
18
21
  @dataclasses.dataclass
@@ -22,15 +25,15 @@ class ShortState(typing.Generic[EventModel]):
22
25
  event: asyncio.Event
23
26
  rules: tuple[ABCRule[EventModel], ...]
24
27
  _: dataclasses.KW_ONLY
25
- expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(default=None)
26
- default_behaviour: Behaviour | None = dataclasses.field(default=None)
27
- on_drop_behaviour: Behaviour | None = dataclasses.field(default=None)
28
+ expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
29
+ default=None,
30
+ )
31
+ default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
32
+ on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
28
33
  expiration_date: datetime.datetime | None = dataclasses.field(init=False)
29
34
 
30
35
  def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
31
- self.expiration_date = (
32
- datetime.datetime.now() - expiration
33
- ) if expiration is not None else None
36
+ self.expiration_date = (datetime.datetime.now() - expiration) if expiration is not None else None
34
37
 
35
38
 
36
39
  __all__ = ("ShortState",)
@@ -33,7 +33,7 @@ class Polling(ABCPolling):
33
33
  self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
34
34
  self.offset = offset
35
35
  self._stop = False
36
-
36
+
37
37
  def __repr__(self) -> str:
38
38
  return (
39
39
  "<{}: with api={!r}, stopped={}, offset={}, allowed_updates={!r}, "
@@ -60,9 +60,7 @@ class Polling(ABCPolling):
60
60
 
61
61
  if include_updates and exclude_updates:
62
62
  allowed_updates = [
63
- x
64
- for x in allowed_updates
65
- 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
66
64
  ]
67
65
  elif exclude_updates:
68
66
  allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
@@ -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,48 @@ 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(update_dct[self.event].unwrap(), bound_api=api),
39
49
  )
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
- )
50
+ event = update_dct[update.update_type.unwrap()].unwrap()
51
+ if not update.update_type or not issubclass(event.__class__, self.event):
52
+ return Error(AdapterError(f"Update is not an {self.event.__name__!r}."))
53
+ return Ok(self.cute_model.from_update(event, bound_api=api))
47
54
 
48
55
 
49
56
  __all__ = ("EventAdapter",)
@@ -14,16 +14,13 @@ 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
19
  MapDict: typing.TypeAlias = dict[
23
- str, typing.Any | type[typing.Any] | Validator | list[Ref["MapDict"]] | Ref["MapDict"]
20
+ str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"
24
21
  ]
25
- CallbackMap: typing.TypeAlias = list[tuple[str, typing.Any | type | Validator | Ref["CallbackMap"]]]
26
- CallbackMapStrict: typing.TypeAlias = list[tuple[str, Validator | Ref["CallbackMapStrict"]]]
22
+ CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type | Validator | CallbackMap"]]
23
+ CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
27
24
 
28
25
 
29
26
  class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
@@ -52,20 +49,20 @@ class CallbackDataMap(CallbackQueryDataRule):
52
49
  @classmethod
53
50
  def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
54
51
  """Transforms MapDict to CallbackMap."""
55
-
52
+
56
53
  callback_map = []
57
-
54
+
58
55
  for k, v in mapping.items():
59
56
  if isinstance(v, dict):
60
57
  v = cls.transform_to_map(v)
61
58
  callback_map.append((k, v))
62
-
59
+
63
60
  return callback_map
64
61
 
65
62
  @classmethod
66
63
  def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
67
64
  """Transforms `CallbackMap` to `CallbackMapStrict`."""
68
-
65
+
69
66
  callback_map_result = []
70
67
 
71
68
  for key, value in callback_map:
@@ -78,21 +75,21 @@ class CallbackDataMap(CallbackQueryDataRule):
78
75
  else:
79
76
  validator = value
80
77
  callback_map_result.append((key, validator))
81
-
78
+
82
79
  return callback_map_result
83
80
 
84
81
  @staticmethod
85
82
  async def run_validator(value: typing.Any, validator: Validator) -> bool:
86
83
  """Run async or sync validator."""
87
-
84
+
88
85
  with suppress(BaseException):
89
86
  result = validator(value)
90
87
  if inspect.isawaitable(result):
91
88
  result = await result
92
89
  return result # type: ignore
93
-
90
+
94
91
  return False
95
-
92
+
96
93
  @classmethod
97
94
  async def match(cls, callback_data: dict, callback_map: CallbackMapStrict) -> bool:
98
95
  """Matches callback_data with callback_map recursively."""
@@ -100,19 +97,19 @@ class CallbackDataMap(CallbackQueryDataRule):
100
97
  for key, validator in callback_map:
101
98
  if key not in callback_data:
102
99
  return False
103
-
100
+
104
101
  if isinstance(validator, list):
105
102
  if not (
106
103
  isinstance(callback_data[key], dict)
107
104
  and await cls.match(callback_data[key], validator)
108
105
  ):
109
106
  return False
110
-
107
+
111
108
  elif not await cls.run_validator(callback_data[key], validator):
112
109
  return False
113
110
 
114
111
  return True
115
-
112
+
116
113
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
117
114
  callback_data = event.decode_callback_data().unwrap_or_none()
118
115
  if callback_data is None:
@@ -142,7 +139,7 @@ class CallbackDataJsonEq(CallbackQueryDataRule):
142
139
  class CallbackDataJsonModel(CallbackQueryDataRule):
143
140
  def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance]):
144
141
  self.model = model
145
-
142
+
146
143
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
147
144
  with suppress(BaseException):
148
145
  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:
@@ -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
@@ -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
  )