telegrinder 0.1.dev20__py3-none-any.whl → 0.1.dev159__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 (132) hide show
  1. telegrinder/__init__.py +129 -22
  2. telegrinder/api/__init__.py +11 -2
  3. telegrinder/api/abc.py +25 -9
  4. telegrinder/api/api.py +47 -28
  5. telegrinder/api/error.py +14 -4
  6. telegrinder/api/response.py +11 -7
  7. telegrinder/bot/__init__.py +68 -7
  8. telegrinder/bot/bot.py +30 -24
  9. telegrinder/bot/cute_types/__init__.py +11 -1
  10. telegrinder/bot/cute_types/base.py +138 -0
  11. telegrinder/bot/cute_types/callback_query.py +458 -15
  12. telegrinder/bot/cute_types/inline_query.py +30 -24
  13. telegrinder/bot/cute_types/message.py +2982 -78
  14. telegrinder/bot/cute_types/update.py +30 -0
  15. telegrinder/bot/cute_types/utils.py +794 -0
  16. telegrinder/bot/dispatch/__init__.py +56 -3
  17. telegrinder/bot/dispatch/abc.py +9 -7
  18. telegrinder/bot/dispatch/composition.py +74 -0
  19. telegrinder/bot/dispatch/context.py +71 -0
  20. telegrinder/bot/dispatch/dispatch.py +86 -49
  21. telegrinder/bot/dispatch/handler/__init__.py +3 -0
  22. telegrinder/bot/dispatch/handler/abc.py +11 -5
  23. telegrinder/bot/dispatch/handler/func.py +41 -32
  24. telegrinder/bot/dispatch/handler/message_reply.py +46 -0
  25. telegrinder/bot/dispatch/middleware/__init__.py +2 -0
  26. telegrinder/bot/dispatch/middleware/abc.py +10 -4
  27. telegrinder/bot/dispatch/process.py +53 -49
  28. telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
  29. telegrinder/bot/dispatch/return_manager/abc.py +95 -0
  30. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  31. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  32. telegrinder/bot/dispatch/return_manager/message.py +25 -0
  33. telegrinder/bot/dispatch/view/__init__.py +14 -2
  34. telegrinder/bot/dispatch/view/abc.py +128 -2
  35. telegrinder/bot/dispatch/view/box.py +38 -0
  36. telegrinder/bot/dispatch/view/callback_query.py +13 -39
  37. telegrinder/bot/dispatch/view/inline_query.py +11 -39
  38. telegrinder/bot/dispatch/view/message.py +11 -47
  39. telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
  40. telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
  41. telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
  42. telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
  43. telegrinder/bot/polling/__init__.py +2 -0
  44. telegrinder/bot/polling/abc.py +11 -4
  45. telegrinder/bot/polling/polling.py +89 -40
  46. telegrinder/bot/rules/__init__.py +91 -5
  47. telegrinder/bot/rules/abc.py +81 -63
  48. telegrinder/bot/rules/adapter/__init__.py +11 -0
  49. telegrinder/bot/rules/adapter/abc.py +21 -0
  50. telegrinder/bot/rules/adapter/errors.py +5 -0
  51. telegrinder/bot/rules/adapter/event.py +49 -0
  52. telegrinder/bot/rules/adapter/raw_update.py +24 -0
  53. telegrinder/bot/rules/callback_data.py +159 -38
  54. telegrinder/bot/rules/command.py +116 -0
  55. telegrinder/bot/rules/enum_text.py +28 -0
  56. telegrinder/bot/rules/func.py +17 -17
  57. telegrinder/bot/rules/fuzzy.py +13 -10
  58. telegrinder/bot/rules/inline.py +61 -0
  59. telegrinder/bot/rules/integer.py +12 -7
  60. telegrinder/bot/rules/is_from.py +148 -7
  61. telegrinder/bot/rules/markup.py +21 -18
  62. telegrinder/bot/rules/mention.py +17 -0
  63. telegrinder/bot/rules/message_entities.py +33 -0
  64. telegrinder/bot/rules/regex.py +27 -19
  65. telegrinder/bot/rules/rule_enum.py +74 -0
  66. telegrinder/bot/rules/start.py +25 -13
  67. telegrinder/bot/rules/text.py +23 -14
  68. telegrinder/bot/scenario/__init__.py +2 -0
  69. telegrinder/bot/scenario/abc.py +12 -5
  70. telegrinder/bot/scenario/checkbox.py +48 -30
  71. telegrinder/bot/scenario/choice.py +16 -10
  72. telegrinder/client/__init__.py +3 -1
  73. telegrinder/client/abc.py +26 -16
  74. telegrinder/client/aiohttp.py +54 -32
  75. telegrinder/model.py +119 -40
  76. telegrinder/modules.py +189 -21
  77. telegrinder/msgspec_json.py +14 -0
  78. telegrinder/msgspec_utils.py +227 -0
  79. telegrinder/node/__init__.py +31 -0
  80. telegrinder/node/attachment.py +71 -0
  81. telegrinder/node/base.py +93 -0
  82. telegrinder/node/composer.py +71 -0
  83. telegrinder/node/container.py +22 -0
  84. telegrinder/node/message.py +18 -0
  85. telegrinder/node/rule.py +56 -0
  86. telegrinder/node/source.py +31 -0
  87. telegrinder/node/text.py +13 -0
  88. telegrinder/node/tools/__init__.py +3 -0
  89. telegrinder/node/tools/generator.py +40 -0
  90. telegrinder/node/update.py +12 -0
  91. telegrinder/rules.py +1 -1
  92. telegrinder/tools/__init__.py +138 -4
  93. telegrinder/tools/buttons.py +89 -51
  94. telegrinder/tools/error_handler/__init__.py +8 -0
  95. telegrinder/tools/error_handler/abc.py +30 -0
  96. telegrinder/tools/error_handler/error_handler.py +156 -0
  97. telegrinder/tools/formatting/__init__.py +81 -3
  98. telegrinder/tools/formatting/html.py +283 -37
  99. telegrinder/tools/formatting/links.py +32 -0
  100. telegrinder/tools/formatting/spec_html_formats.py +121 -0
  101. telegrinder/tools/global_context/__init__.py +12 -0
  102. telegrinder/tools/global_context/abc.py +66 -0
  103. telegrinder/tools/global_context/global_context.py +451 -0
  104. telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
  105. telegrinder/tools/i18n/__init__.py +12 -0
  106. telegrinder/tools/i18n/base.py +31 -0
  107. telegrinder/tools/i18n/middleware/__init__.py +3 -0
  108. telegrinder/tools/i18n/middleware/base.py +26 -0
  109. telegrinder/tools/i18n/simple.py +48 -0
  110. telegrinder/tools/kb_set/__init__.py +2 -0
  111. telegrinder/tools/kb_set/base.py +3 -0
  112. telegrinder/tools/kb_set/yaml.py +28 -17
  113. telegrinder/tools/keyboard.py +84 -62
  114. telegrinder/tools/loop_wrapper/__init__.py +4 -0
  115. telegrinder/tools/loop_wrapper/abc.py +18 -0
  116. telegrinder/tools/loop_wrapper/loop_wrapper.py +132 -0
  117. telegrinder/tools/magic.py +48 -23
  118. telegrinder/tools/parse_mode.py +1 -2
  119. telegrinder/types/__init__.py +1 -0
  120. telegrinder/types/enums.py +653 -0
  121. telegrinder/types/methods.py +4107 -1279
  122. telegrinder/types/objects.py +4771 -1745
  123. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev159.dist-info}/LICENSE +2 -1
  124. telegrinder-0.1.dev159.dist-info/METADATA +109 -0
  125. telegrinder-0.1.dev159.dist-info/RECORD +126 -0
  126. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev159.dist-info}/WHEEL +1 -1
  127. telegrinder/bot/dispatch/waiter.py +0 -38
  128. telegrinder/result.py +0 -38
  129. telegrinder/tools/formatting/abc.py +0 -52
  130. telegrinder/tools/formatting/markdown.py +0 -57
  131. telegrinder-0.1.dev20.dist-info/METADATA +0 -22
  132. telegrinder-0.1.dev20.dist-info/RECORD +0 -71
@@ -1,26 +1,26 @@
1
- from .abc import ABCRule, EventScheme, T
2
- from telegrinder.types import Update
1
+ import inspect
3
2
  import typing
4
3
 
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.types import Update
6
+
7
+ from .abc import ABCAdapter, ABCRule, RawUpdateAdapter, T
8
+
5
9
 
6
10
  class FuncRule(ABCRule, typing.Generic[T]):
7
11
  def __init__(
8
12
  self,
9
- func: typing.Callable[[T, dict], bool],
10
- event_scheme: typing.Optional[
11
- typing.Union[EventScheme, typing.Tuple[str, typing.Type[T]]]
12
- ] = None,
13
+ func: typing.Callable[[T, Context], typing.Awaitable[bool] | bool],
14
+ adapter: ABCAdapter[Update, T] | None = None,
13
15
  ):
14
16
  self.func = func
15
- if isinstance(event_scheme, tuple):
16
- event_scheme = EventScheme(*event_scheme)
17
- self.event_scheme = event_scheme
17
+ self.adapter = adapter or RawUpdateAdapter()
18
+
19
+ async def check(self, event: T, ctx: Context) -> bool:
20
+ result = self.func(event, ctx)
21
+ if inspect.isawaitable(result):
22
+ return await result
23
+ return result # type: ignore
24
+
18
25
 
19
- async def check(self, event: Update, ctx: dict) -> bool:
20
- if self.event_scheme:
21
- if self.event_scheme.name not in event:
22
- return False
23
- event = self.event_scheme.dataclass(
24
- **getattr(event, self.event_scheme.name).to_dict()
25
- )
26
- return self.func(event, ctx)
26
+ __all__ = ("FuncRule",)
@@ -1,24 +1,27 @@
1
- from .abc import Message
2
- from .text import ABCTextMessageRule
3
1
  import difflib
4
- import typing
2
+
3
+ from telegrinder.bot.dispatch.context import Context
4
+
5
+ from .abc import Message
6
+ from .text import TextMessageRule
5
7
 
6
8
 
7
- class FuzzyText(ABCTextMessageRule):
8
- def __init__(
9
- self, texts: typing.Union[str, typing.List[str]], min_ratio: float = 0.7
10
- ):
9
+ class FuzzyText(TextMessageRule):
10
+ def __init__(self, texts: str | list[str], min_ratio: float = 0.7):
11
11
  if isinstance(texts, str):
12
12
  texts = [texts]
13
13
  self.texts = texts
14
14
  self.min_ratio = min_ratio
15
15
 
16
- async def check(self, message: Message, ctx: dict) -> bool:
16
+ async def check(self, message: Message, ctx: Context) -> bool:
17
17
  match = max(
18
- difflib.SequenceMatcher(a=message.text, b=text).ratio()
18
+ difflib.SequenceMatcher(a=message.text.unwrap(), b=text).ratio()
19
19
  for text in self.texts
20
20
  )
21
21
  if match < self.min_ratio:
22
22
  return False
23
- ctx["fuzzy_ratio"] = match
23
+ ctx.fuzzy_ratio = match
24
24
  return True
25
+
26
+
27
+ __all__ = ("FuzzyText",)
@@ -0,0 +1,61 @@
1
+ import abc
2
+
3
+ from telegrinder.bot.cute_types import InlineQueryCute
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.bot.rules.abc import ABCRule
6
+ from telegrinder.bot.rules.adapter import EventAdapter
7
+ from telegrinder.types.enums import ChatType
8
+
9
+ from .markup import Markup, PatternLike, check_string
10
+
11
+ InlineQuery = InlineQueryCute
12
+
13
+
14
+ class InlineQueryRule(ABCRule[InlineQuery], abc.ABC):
15
+ adapter = EventAdapter("inline_query", InlineQuery)
16
+
17
+ @abc.abstractmethod
18
+ async def check(self, query: InlineQuery, ctx: Context) -> bool:
19
+ pass
20
+
21
+
22
+ class HasLocation(InlineQueryRule):
23
+ async def check(self, query: InlineQuery, ctx: Context) -> bool:
24
+ return bool(query.location)
25
+
26
+
27
+ class InlineQueryChatType(InlineQueryRule):
28
+ def __init__(self, chat_type: ChatType, /) -> None:
29
+ self.chat_type = chat_type
30
+
31
+ async def check(self, query: InlineQuery, ctx: Context) -> bool:
32
+ return query.chat_type.map(lambda x: x == self.chat_type).unwrap_or(False)
33
+
34
+
35
+ class InlineQueryText(InlineQueryRule):
36
+ def __init__(self, texts: str | list[str], *, lower_case: bool = False) -> None:
37
+ self.texts = [
38
+ text.lower() if lower_case else text
39
+ for text in ([texts] if isinstance(texts, str) else texts)
40
+ ]
41
+ self.lower_case = lower_case
42
+
43
+ async def check(self, query: InlineQuery, ctx: Context) -> bool:
44
+ return (query.query.lower() if self.lower_case else query.query) in self.texts
45
+
46
+
47
+ class InlineQueryMarkup(InlineQueryRule):
48
+ def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
49
+ self.patterns = Markup(patterns).patterns
50
+
51
+ async def check(self, query: InlineQuery, ctx: Context) -> bool:
52
+ return check_string(self.patterns, query.query, ctx)
53
+
54
+
55
+ __all__ = (
56
+ "HasLocation",
57
+ "InlineQueryRule",
58
+ "InlineQueryText",
59
+ "InlineQueryMarkup",
60
+ "InlineQueryChatType",
61
+ )
@@ -1,14 +1,19 @@
1
- from .text import ABCTextMessageRule, Message
1
+ from telegrinder.bot.dispatch.context import Context
2
2
 
3
+ from .text import Message, TextMessageRule
3
4
 
4
- class Integer(ABCTextMessageRule):
5
- async def check(self, message: Message, ctx: dict) -> bool:
6
- return message.text.isdigit()
7
5
 
6
+ class Integer(TextMessageRule):
7
+ async def check(self, message: Message, ctx: Context) -> bool:
8
+ return message.text.unwrap().isdigit()
8
9
 
9
- class IntegerInRange(ABCTextMessageRule, require=[Integer()]):
10
+
11
+ class IntegerInRange(TextMessageRule, requires=[Integer()]):
10
12
  def __init__(self, rng: range):
11
13
  self.rng = rng
12
14
 
13
- async def check(self, message: Message, ctx: dict) -> bool:
14
- return int(message.text) in self.rng
15
+ async def check(self, message: Message, ctx: Context) -> bool:
16
+ return int(message.text.unwrap()) in self.rng
17
+
18
+
19
+ __all__ = ("Integer", "IntegerInRange")
@@ -1,11 +1,152 @@
1
- from .abc import ABCMessageRule, Message
1
+ import typing
2
2
 
3
+ from telegrinder.bot.cute_types.base import BaseCute
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.msgspec_utils import Option
6
+ from telegrinder.types.enums import ChatType, DiceEmoji
7
+ from telegrinder.types.objects import User
3
8
 
4
- class IsPrivate(ABCMessageRule):
5
- async def check(self, message: Message, ctx: dict) -> bool:
6
- return message.chat.id > 0
9
+ from .abc import ABCRule, Message, MessageRule
7
10
 
11
+ T = typing.TypeVar("T", bound=BaseCute)
8
12
 
9
- class IsChat(ABCMessageRule):
10
- async def check(self, message: Message, ctx: dict) -> bool:
11
- return message.chat.id < 0
13
+
14
+ @typing.runtime_checkable
15
+ class HasFromProto(typing.Protocol):
16
+ from_: User | Option[User]
17
+
18
+
19
+ class HasFrom(ABCRule[T]):
20
+ async def check(self, event: T, ctx: Context) -> bool:
21
+ return isinstance(event, HasFromProto) and bool(event.from_)
22
+
23
+
24
+ class HasDice(MessageRule):
25
+ async def check(self, message: Message, ctx: Context) -> bool:
26
+ return bool(message.dice)
27
+
28
+
29
+ class IsReply(MessageRule):
30
+ async def check(self, message: Message, ctx: Context) -> bool:
31
+ return bool(message.reply_to_message)
32
+
33
+
34
+ class IsSticker(MessageRule):
35
+ async def check(self, message: Message, ctx: Context) -> bool:
36
+ return bool(message.sticker)
37
+
38
+
39
+ class IsBot(MessageRule, requires=[HasFrom()]):
40
+ async def check(self, message: Message, ctx: Context) -> bool:
41
+ return message.from_user.is_bot
42
+
43
+
44
+ class IsUser(MessageRule, requires=[HasFrom()]):
45
+ async def check(self, message: Message, ctx: Context) -> bool:
46
+ return not message.from_user.is_bot
47
+
48
+
49
+ class IsPremium(MessageRule, requires=[HasFrom()]):
50
+ async def check(self, message: Message, ctx: Context) -> bool:
51
+ return message.from_user.is_premium.unwrap_or(False)
52
+
53
+
54
+ class IsLanguageCode(MessageRule, requires=[HasFrom()]):
55
+ def __init__(self, lang_codes: str | list[str], /) -> None:
56
+ self.lang_codes = [lang_codes] if isinstance(lang_codes, str) else lang_codes
57
+
58
+ async def check(self, message: Message, ctx: Context) -> bool:
59
+ if not message.from_user.language_code:
60
+ return False
61
+ return message.from_user.language_code.unwrap_or_none() in self.lang_codes
62
+
63
+
64
+ class IsForum(MessageRule):
65
+ async def check(self, message: Message, ctx: Context) -> bool:
66
+ return message.chat.is_forum.unwrap_or(False)
67
+
68
+
69
+ class IsUserId(MessageRule, requires=[HasFrom()]):
70
+ def __init__(self, user_ids: int | list[int], /) -> None:
71
+ self.user_ids = [user_ids] if isinstance(user_ids, int) else user_ids
72
+
73
+ async def check(self, message: Message, ctx: Context) -> bool:
74
+ return message.from_user.id in self.user_ids
75
+
76
+
77
+ class IsChatId(MessageRule):
78
+ def __init__(self, chat_ids: int | list[int], /) -> None:
79
+ self.chat_ids = [chat_ids] if isinstance(chat_ids, int) else chat_ids
80
+
81
+ async def check(self, message: Message, ctx: Context) -> bool:
82
+ return message.chat.id in self.chat_ids
83
+
84
+
85
+ class IsPrivate(MessageRule):
86
+ async def check(self, message: Message, ctx: Context) -> bool:
87
+ return message.chat.type == ChatType.PRIVATE
88
+
89
+
90
+ class IsGroup(MessageRule):
91
+ async def check(self, message: Message, ctx: Context) -> bool:
92
+ return message.chat.type == ChatType.GROUP
93
+
94
+
95
+ class IsSuperGroup(MessageRule):
96
+ async def check(self, message: Message, ctx: Context) -> bool:
97
+ return message.chat.type == ChatType.SUPERGROUP
98
+
99
+
100
+ class IsChat(MessageRule):
101
+ async def check(self, message: Message, ctx: Context) -> bool:
102
+ return message.chat.type in (ChatType.GROUP, ChatType.SUPERGROUP)
103
+
104
+
105
+ class IsDice(MessageRule, requires=[HasDice()]):
106
+ async def check(self, message: Message, ctx: Context) -> bool:
107
+ return message.dice.unwrap().emoji == DiceEmoji.DICE
108
+
109
+
110
+ class IsDartDice(MessageRule, requires=[HasDice()]):
111
+ async def check(self, message: Message, ctx: Context) -> bool:
112
+ return message.dice.unwrap().emoji == DiceEmoji.DART
113
+
114
+
115
+ class IsBasketballDice(MessageRule, requires=[HasDice()]):
116
+ async def check(self, message: Message, ctx: Context) -> bool:
117
+ return message.dice.unwrap().emoji == DiceEmoji.BASKETBALL
118
+
119
+
120
+ class IsFootballDice(MessageRule, requires=[HasDice()]):
121
+ async def check(self, message: Message, ctx: Context) -> bool:
122
+ return message.dice.unwrap().emoji == DiceEmoji.FOOTBALL
123
+
124
+
125
+ class IsSlotMachineDice(MessageRule, requires=[HasDice()]):
126
+ async def check(self, message: Message, ctx: Context) -> bool:
127
+ return message.dice.unwrap().emoji == DiceEmoji.SLOT_MACHINE
128
+
129
+
130
+ class IsBowlingDice(MessageRule, requires=[HasDice()]):
131
+ async def check(self, message: Message, ctx: Context) -> bool:
132
+ return message.dice.unwrap().emoji == DiceEmoji.BOWLING
133
+
134
+
135
+ __all__ = (
136
+ "IsBasketballDice",
137
+ "IsBot",
138
+ "IsBowlingDice",
139
+ "IsChat",
140
+ "IsChatId",
141
+ "IsDartDice",
142
+ "IsDice",
143
+ "IsForum",
144
+ "IsGroup",
145
+ "IsLanguageCode",
146
+ "IsPremium",
147
+ "IsPrivate",
148
+ "IsReply",
149
+ "IsSuperGroup",
150
+ "IsUser",
151
+ "IsUserId",
152
+ )
@@ -1,28 +1,28 @@
1
- from .abc import Message, patcher
2
- from .text import ABCTextMessageRule
3
- import typing
4
1
  import vbml
5
2
 
6
- PatternLike = typing.Union[str, vbml.Pattern]
3
+ from telegrinder.bot.dispatch.context import Context
4
+ from telegrinder.tools.global_context import TelegrinderCtx
7
5
 
6
+ from .abc import Message
7
+ from .text import TextMessageRule
8
8
 
9
- def check_string(
10
- patterns: typing.Union[PatternLike, typing.List[PatternLike]], s: str, ctx: dict
11
- ) -> bool:
12
- if s is None:
13
- return False
9
+ PatternLike = str | vbml.Pattern
10
+ global_ctx = TelegrinderCtx()
11
+
12
+
13
+ def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
14
14
  for pattern in patterns:
15
- response = patcher.check(pattern, s)
16
- if response in (False, None):
17
- continue
18
- elif isinstance(response, dict):
19
- ctx.update(response)
15
+ match global_ctx.vbml_patcher.check(pattern, s):
16
+ case None | False:
17
+ continue
18
+ case {**response}:
19
+ ctx |= response
20
20
  return True
21
21
  return False
22
22
 
23
23
 
24
- class Markup(ABCTextMessageRule):
25
- def __init__(self, patterns: typing.Union[PatternLike, typing.List[PatternLike]]):
24
+ class Markup(TextMessageRule):
25
+ def __init__(self, patterns: PatternLike | list[PatternLike]):
26
26
  if not isinstance(patterns, list):
27
27
  patterns = [patterns]
28
28
  self.patterns = [
@@ -30,5 +30,8 @@ class Markup(ABCTextMessageRule):
30
30
  for pattern in patterns
31
31
  ]
32
32
 
33
- async def check(self, message: Message, ctx: dict) -> bool:
34
- return check_string(self.patterns, message.text, ctx)
33
+ async def check(self, message: Message, ctx: Context) -> bool:
34
+ return check_string(self.patterns, message.text.unwrap(), ctx)
35
+
36
+
37
+ __all__ = ("Markup", "check_string")
@@ -0,0 +1,17 @@
1
+ from telegrinder.bot.dispatch.context import Context
2
+ from telegrinder.types.enums import MessageEntityType
3
+
4
+ from .text import Message, TextMessageRule
5
+
6
+
7
+ class HasMention(TextMessageRule):
8
+ async def check(self, message: Message, ctx: Context) -> bool:
9
+ if not message.entities.unwrap_or_none():
10
+ return False
11
+ return any(
12
+ entity.type == MessageEntityType.MENTION
13
+ for entity in message.entities.unwrap()
14
+ )
15
+
16
+
17
+ __all__ = ("HasMention",)
@@ -0,0 +1,33 @@
1
+ from telegrinder.bot.dispatch.context import Context
2
+ from telegrinder.types.enums import MessageEntityType
3
+ from telegrinder.types.objects import MessageEntity
4
+
5
+ from .abc import Message, MessageRule
6
+
7
+ Entity = str | MessageEntityType
8
+
9
+
10
+ class HasEntities(MessageRule):
11
+ async def check(self, message: Message, ctx: Context) -> bool:
12
+ return bool(message.entities)
13
+
14
+
15
+ class MessageEntities(MessageRule, requires=[HasEntities()]):
16
+ def __init__(self, entities: Entity | list[Entity]):
17
+ self.entities = [entities] if not isinstance(entities, list) else entities
18
+
19
+ async def check(self, message: Message, ctx: Context) -> bool:
20
+ message_entities: list[MessageEntity] = []
21
+ for entity in message.entities.unwrap():
22
+ for entity_type in self.entities:
23
+ if entity_type == entity.type:
24
+ message_entities.append(entity)
25
+
26
+ if not message_entities:
27
+ return False
28
+
29
+ ctx.message_entities = message_entities
30
+ return True
31
+
32
+
33
+ __all__ = ("HasEntities", "MessageEntities")
@@ -1,30 +1,38 @@
1
1
  import re
2
2
  import typing
3
3
 
4
- from .abc import Message
5
- from .text import ABCTextMessageRule
4
+ from telegrinder.bot.dispatch.context import Context
6
5
 
7
- PatternLike = typing.Union[str, typing.Pattern]
6
+ from .abc import Message
7
+ from .text import TextMessageRule
8
8
 
9
+ PatternLike = str | typing.Pattern[str]
9
10
 
10
- class Regex(ABCTextMessageRule):
11
- def __init__(self, regexp: typing.Union[PatternLike, typing.List[PatternLike]]):
12
- if isinstance(regexp, re.Pattern):
13
- regexp = [regexp]
14
- elif isinstance(regexp, str):
15
- regexp = [re.compile(regexp)]
16
- else:
17
- regexp = [
18
- re.compile(regexp) if isinstance(regexp, str) else regexp
19
- for regexp in regexp
20
- ]
21
11
 
22
- self.regexp = regexp
12
+ class Regex(TextMessageRule):
13
+ def __init__(self, regexp: PatternLike | list[PatternLike]):
14
+ self.regexp: list[re.Pattern[str]] = []
15
+ match regexp:
16
+ case re.Pattern() as pattern:
17
+ self.regexp.append(pattern)
18
+ case str(regex):
19
+ self.regexp.append(re.compile(regex))
20
+ case _:
21
+ self.regexp.extend(
22
+ re.compile(regexp) if isinstance(regexp, str) else regexp
23
+ for regexp in regexp
24
+ )
23
25
 
24
- async def check(self, message: Message, ctx: dict) -> bool:
26
+ async def check(self, message: Message, ctx: Context) -> bool:
25
27
  for regexp in self.regexp:
26
- match = re.match(regexp, message.text)
27
- if match:
28
- ctx.update({"match": match.groups()})
28
+ response = re.match(regexp, message.text.unwrap())
29
+ if response is not None:
30
+ if matches := response.groupdict():
31
+ ctx |= matches
32
+ else:
33
+ ctx |= {"matches": response.groups() or (response.group(),)}
29
34
  return True
30
35
  return False
36
+
37
+
38
+ __all__ = ("Regex",)
@@ -0,0 +1,74 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from telegrinder.bot.dispatch.context import Context
5
+
6
+ from .abc import ABCRule, T, Update, check_rule
7
+ from .func import FuncRule
8
+
9
+
10
+ @dataclasses.dataclass
11
+ class RuleEnumState:
12
+ name: str
13
+ rule: ABCRule
14
+ cls: type["RuleEnum"]
15
+
16
+ def __eq__(self, other: typing.Self) -> bool:
17
+ return self.cls == other.cls and self.name == other.name
18
+
19
+
20
+ class RuleEnum(ABCRule[T]):
21
+ __enum__: list[RuleEnumState]
22
+
23
+ def __init_subclass__(cls, *args, **kwargs):
24
+ new_attributes = (
25
+ set(cls.__dict__) - set(RuleEnum.__dict__) - {"__enum__", "__init__"}
26
+ )
27
+ enum_lst: list[RuleEnumState] = []
28
+
29
+ self = cls.__new__(cls)
30
+ self.__init__()
31
+
32
+ for attribute_name in new_attributes:
33
+ rules = getattr(cls, attribute_name)
34
+ attribute = RuleEnumState(attribute_name, rules, cls)
35
+
36
+ setattr(
37
+ self,
38
+ attribute.name,
39
+ self & FuncRule(lambda _, ctx: self.must_be_state(ctx, attribute)),
40
+ )
41
+ enum_lst.append(attribute)
42
+
43
+ setattr(cls, "__enum__", enum_lst)
44
+
45
+ @classmethod
46
+ def save_state(cls, ctx: Context, enum: RuleEnumState) -> None:
47
+ ctx.update({cls.__class__.__name__ + "_state": enum})
48
+
49
+ @classmethod
50
+ def check_state(cls, ctx: Context) -> RuleEnumState | None:
51
+ return ctx.get(cls.__class__.__name__ + "_state")
52
+
53
+ @classmethod
54
+ def must_be_state(cls, ctx: Context, state: RuleEnumState) -> bool:
55
+ real_state = cls.check_state(ctx)
56
+ if not real_state:
57
+ return False
58
+ return real_state == state
59
+
60
+ async def check(self, event: Update, ctx: Context) -> bool:
61
+ if self.check_state(ctx):
62
+ return True
63
+
64
+ for enum in self.__enum__:
65
+ ctx_copy = ctx.copy()
66
+ if await check_rule(event.ctx_api, enum.rule, event, ctx_copy):
67
+ ctx.update(ctx_copy)
68
+ self.save_state(ctx, enum)
69
+ return True
70
+
71
+ return False
72
+
73
+
74
+ __all__ = ("RuleEnum", "RuleEnumState")
@@ -1,30 +1,42 @@
1
- from .abc import ABCMessageRule
2
- from .markup import Markup, Message
3
1
  import typing
4
2
 
3
+ from telegrinder.bot.dispatch.context import Context
4
+ from telegrinder.types.enums import MessageEntityType
5
+
6
+ from .abc import MessageRule
7
+ from .is_from import IsPrivate
8
+ from .markup import Markup, Message
9
+ from .message_entities import MessageEntities
10
+
5
11
 
6
12
  class StartCommand(
7
- ABCMessageRule,
8
- require=[Markup(["/start <param>", "/start"])]
13
+ MessageRule, requires=[
14
+ IsPrivate() & MessageEntities(MessageEntityType.BOT_COMMAND)
15
+ & Markup(["/start <param>", "/start"]),
16
+ ]
9
17
  ):
10
18
  def __init__(
11
- self,
12
- validator: typing.Callable[str, typing.Any | None] | None = None,
19
+ self,
20
+ validator: typing.Callable[[str], typing.Any | None] | None = None,
21
+ *,
13
22
  param_required: bool = False,
23
+ alias: str | None = None,
14
24
  ) -> None:
15
25
  self.param_required = param_required
16
26
  self.validator = validator
27
+ self.alias = alias
17
28
 
18
- async def check(self, message: Message, ctx: dict) -> bool:
19
- param: str | None = ctx.get("param")
29
+ async def check(self, _: Message, ctx: Context) -> bool:
30
+ param: str | None = ctx.pop("param", None)
20
31
  validated_param = (
21
- self.validator(param)
22
- if self.validator and param is not None
23
- else param
32
+ self.validator(param) if self.validator and param is not None else param
24
33
  )
25
34
 
26
35
  if self.param_required and validated_param is None:
27
36
  return False
28
-
29
- ctx["param"] = validated_param
37
+
38
+ ctx.set(self.alias or "param", validated_param)
30
39
  return True
40
+
41
+
42
+ __all__ = ("StartCommand",)
@@ -1,26 +1,35 @@
1
- from .abc import ABC, ABCMessageRule, Message
2
- import typing
1
+ from telegrinder.bot.dispatch.context import Context
2
+ from telegrinder.tools.i18n.base import ABCTranslator
3
3
 
4
+ from .abc import ABC, Message, MessageRule, with_caching_translations
4
5
 
5
- class HasText(ABCMessageRule):
6
- async def check(self, message: Message, ctx: dict) -> bool:
6
+
7
+ class HasText(MessageRule):
8
+ async def check(self, message: Message, ctx: Context) -> bool:
7
9
  return bool(message.text)
8
10
 
9
11
 
10
- class ABCTextMessageRule(ABCMessageRule, ABC, require=[HasText()]):
12
+ class TextMessageRule(MessageRule, ABC, requires=[HasText()]):
11
13
  pass
12
14
 
13
15
 
14
- class Text(ABCTextMessageRule):
15
- def __init__(
16
- self, texts: typing.Union[str, typing.List[str]], ignore_case: bool = False
17
- ):
16
+ class Text(TextMessageRule):
17
+ def __init__(self, texts: str | list[str], ignore_case: bool = False):
18
18
  if not isinstance(texts, list):
19
19
  texts = [texts]
20
- self.texts = texts
20
+ self.texts = texts if not ignore_case else list(map(str.lower, texts))
21
21
  self.ignore_case = ignore_case
22
22
 
23
- async def check(self, message: Message, ctx: dict) -> bool:
24
- if self.ignore_case:
25
- return message.text.lower() in list(map(str.lower, self.texts))
26
- return message.text in self.texts
23
+ async def check(self, message: Message, ctx: Context) -> bool:
24
+ text = message.text.unwrap()
25
+ return (text if not self.ignore_case else text.lower()) in self.texts
26
+
27
+ @with_caching_translations
28
+ async def translate(self, translator: ABCTranslator) -> "Text":
29
+ return Text(
30
+ texts=[translator.get(text) for text in self.texts],
31
+ ignore_case=self.ignore_case,
32
+ )
33
+
34
+
35
+ __all__ = ("HasText", "Text", "TextMessageRule")