telegrinder 0.1.dev20__py3-none-any.whl → 0.1.dev158__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 +29 -24
  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 +47 -0
  11. telegrinder/bot/cute_types/callback_query.py +64 -14
  12. telegrinder/bot/cute_types/inline_query.py +22 -16
  13. telegrinder/bot/cute_types/message.py +145 -53
  14. telegrinder/bot/cute_types/update.py +23 -0
  15. telegrinder/bot/dispatch/__init__.py +56 -3
  16. telegrinder/bot/dispatch/abc.py +9 -7
  17. telegrinder/bot/dispatch/composition.py +74 -0
  18. telegrinder/bot/dispatch/context.py +71 -0
  19. telegrinder/bot/dispatch/dispatch.py +86 -49
  20. telegrinder/bot/dispatch/handler/__init__.py +3 -0
  21. telegrinder/bot/dispatch/handler/abc.py +11 -5
  22. telegrinder/bot/dispatch/handler/func.py +41 -32
  23. telegrinder/bot/dispatch/handler/message_reply.py +46 -0
  24. telegrinder/bot/dispatch/middleware/__init__.py +2 -0
  25. telegrinder/bot/dispatch/middleware/abc.py +10 -4
  26. telegrinder/bot/dispatch/process.py +53 -49
  27. telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
  28. telegrinder/bot/dispatch/return_manager/abc.py +95 -0
  29. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  30. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  31. telegrinder/bot/dispatch/return_manager/message.py +25 -0
  32. telegrinder/bot/dispatch/view/__init__.py +14 -2
  33. telegrinder/bot/dispatch/view/abc.py +121 -2
  34. telegrinder/bot/dispatch/view/box.py +38 -0
  35. telegrinder/bot/dispatch/view/callback_query.py +13 -39
  36. telegrinder/bot/dispatch/view/inline_query.py +11 -39
  37. telegrinder/bot/dispatch/view/message.py +11 -47
  38. telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
  39. telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
  40. telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
  41. telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
  42. telegrinder/bot/polling/__init__.py +2 -0
  43. telegrinder/bot/polling/abc.py +11 -4
  44. telegrinder/bot/polling/polling.py +89 -40
  45. telegrinder/bot/rules/__init__.py +91 -5
  46. telegrinder/bot/rules/abc.py +81 -63
  47. telegrinder/bot/rules/adapter/__init__.py +11 -0
  48. telegrinder/bot/rules/adapter/abc.py +21 -0
  49. telegrinder/bot/rules/adapter/errors.py +5 -0
  50. telegrinder/bot/rules/adapter/event.py +43 -0
  51. telegrinder/bot/rules/adapter/raw_update.py +24 -0
  52. telegrinder/bot/rules/callback_data.py +159 -38
  53. telegrinder/bot/rules/command.py +116 -0
  54. telegrinder/bot/rules/enum_text.py +28 -0
  55. telegrinder/bot/rules/func.py +17 -17
  56. telegrinder/bot/rules/fuzzy.py +13 -10
  57. telegrinder/bot/rules/inline.py +61 -0
  58. telegrinder/bot/rules/integer.py +12 -7
  59. telegrinder/bot/rules/is_from.py +148 -7
  60. telegrinder/bot/rules/markup.py +21 -18
  61. telegrinder/bot/rules/mention.py +17 -0
  62. telegrinder/bot/rules/message_entities.py +33 -0
  63. telegrinder/bot/rules/regex.py +27 -19
  64. telegrinder/bot/rules/rule_enum.py +74 -0
  65. telegrinder/bot/rules/start.py +25 -13
  66. telegrinder/bot/rules/text.py +23 -14
  67. telegrinder/bot/scenario/__init__.py +2 -0
  68. telegrinder/bot/scenario/abc.py +12 -5
  69. telegrinder/bot/scenario/checkbox.py +48 -30
  70. telegrinder/bot/scenario/choice.py +16 -10
  71. telegrinder/client/__init__.py +2 -0
  72. telegrinder/client/abc.py +8 -21
  73. telegrinder/client/aiohttp.py +30 -21
  74. telegrinder/model.py +68 -37
  75. telegrinder/modules.py +189 -21
  76. telegrinder/msgspec_json.py +14 -0
  77. telegrinder/msgspec_utils.py +207 -0
  78. telegrinder/node/__init__.py +31 -0
  79. telegrinder/node/attachment.py +71 -0
  80. telegrinder/node/base.py +93 -0
  81. telegrinder/node/composer.py +71 -0
  82. telegrinder/node/container.py +22 -0
  83. telegrinder/node/message.py +18 -0
  84. telegrinder/node/rule.py +56 -0
  85. telegrinder/node/source.py +31 -0
  86. telegrinder/node/text.py +13 -0
  87. telegrinder/node/tools/__init__.py +3 -0
  88. telegrinder/node/tools/generator.py +40 -0
  89. telegrinder/node/update.py +12 -0
  90. telegrinder/rules.py +1 -1
  91. telegrinder/tools/__init__.py +165 -4
  92. telegrinder/tools/buttons.py +75 -51
  93. telegrinder/tools/error_handler/__init__.py +8 -0
  94. telegrinder/tools/error_handler/abc.py +30 -0
  95. telegrinder/tools/error_handler/error_handler.py +156 -0
  96. telegrinder/tools/formatting/__init__.py +81 -3
  97. telegrinder/tools/formatting/html.py +283 -37
  98. telegrinder/tools/formatting/links.py +32 -0
  99. telegrinder/tools/formatting/spec_html_formats.py +121 -0
  100. telegrinder/tools/global_context/__init__.py +12 -0
  101. telegrinder/tools/global_context/abc.py +66 -0
  102. telegrinder/tools/global_context/global_context.py +451 -0
  103. telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
  104. telegrinder/tools/i18n/__init__.py +12 -0
  105. telegrinder/tools/i18n/base.py +31 -0
  106. telegrinder/tools/i18n/middleware/__init__.py +3 -0
  107. telegrinder/tools/i18n/middleware/base.py +26 -0
  108. telegrinder/tools/i18n/simple.py +48 -0
  109. telegrinder/tools/inline_query.py +684 -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 +651 -0
  121. telegrinder/types/methods.py +3920 -1251
  122. telegrinder/types/objects.py +4702 -1718
  123. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev158.dist-info}/LICENSE +2 -1
  124. telegrinder-0.1.dev158.dist-info/METADATA +108 -0
  125. telegrinder-0.1.dev158.dist-info/RECORD +126 -0
  126. {telegrinder-0.1.dev20.dist-info → telegrinder-0.1.dev158.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,57 +1,178 @@
1
- from .abc import ABCRule, EventScheme
2
- from telegrinder.modules import json
3
- from telegrinder.types import Update
4
- from telegrinder.bot.cute_types import CallbackQueryCute
5
- from .markup import Markup, check_string
6
- import msgspec
7
- import vbml
1
+ import abc
2
+ import inspect
8
3
  import typing
4
+ from contextlib import suppress
5
+
6
+ import msgspec
7
+
8
+ from telegrinder.bot.cute_types import CallbackQueryCute
9
+ from telegrinder.bot.dispatch.context import Context
10
+ from telegrinder.bot.rules.adapter import EventAdapter
11
+ from telegrinder.model import decoder
12
+ from telegrinder.tools.buttons import DataclassInstance
13
+
14
+ from .abc import ABCRule
15
+ from .markup import Markup, PatternLike, check_string
16
+
17
+ if typing.TYPE_CHECKING:
18
+
19
+ T = typing.TypeVar("T")
20
+ Ref: typing.TypeAlias = typing.Annotated[T, ...]
21
+ else:
22
+
23
+ class Ref:
24
+ def __class_getitem__(cls, code: str) -> typing.ForwardRef:
25
+ return typing.ForwardRef(code)
26
+
9
27
 
10
28
  CallbackQuery = CallbackQueryCute
11
- PatternLike = typing.Union[str, vbml.Pattern]
29
+ Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
30
+ MapDict: typing.TypeAlias = dict[
31
+ str, typing.Any | type[typing.Any] | Validator | list[Ref["MapDict"]] | Ref["MapDict"]
32
+ ]
33
+ CallbackMap: typing.TypeAlias = list[tuple[str, typing.Any | type | Validator | Ref["CallbackMap"]]]
34
+ CallbackMapStrict: typing.TypeAlias = list[tuple[str, Validator | Ref["CallbackMapStrict"]]]
12
35
 
13
36
 
14
- class CallbackDataEq(ABCRule):
15
- def __init__(self, value: str):
16
- self.value = value
37
+ class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
38
+ adapter = EventAdapter("callback_query", CallbackQuery)
17
39
 
18
- async def check(self, event: Update, ctx: dict) -> bool:
19
- return event.callback_query.data == self.value
40
+ @abc.abstractmethod
41
+ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
42
+ pass
20
43
 
21
44
 
22
- class CallbackDataJsonEq(ABCRule):
23
- def __init__(self, d: dict):
24
- self.d = d
45
+ class HasData(CallbackQueryRule):
46
+ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
47
+ return bool(event.data or event.data.unwrap())
25
48
 
26
- async def check(self, event: Update, ctx: dict) -> bool:
27
- if not event.callback_query.data:
28
- return False
29
- try:
30
- # todo: use msgspec
31
- return json.loads(event.callback_query.data) == self.d
32
- except:
33
- return False
34
49
 
50
+ class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
51
+ pass
35
52
 
36
- class CallbackDataJsonModel(ABCRule[CallbackQuery]):
37
- __event__ = EventScheme("callback_query", CallbackQuery)
38
53
 
39
- def __init__(self, model: typing.Type[msgspec.Struct]):
40
- self.decoder = msgspec.json.Decoder(type=model)
54
+ class CallbackDataMap(CallbackQueryDataRule):
55
+ def __init__(self, mapping: MapDict) -> None:
56
+ self.mapping = self.transform_to_callbacks(
57
+ self.transform_to_map(mapping),
58
+ )
41
59
 
42
- async def check(self, event: CallbackQuery, ctx: dict) -> bool:
43
- try:
44
- ctx["data"] = self.decoder.decode(event.data.encode())
45
- return True
46
- except msgspec.DecodeError:
60
+ @classmethod
61
+ def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
62
+ """Transforms MapDict to CallbackMap."""
63
+
64
+ callback_map = []
65
+
66
+ for k, v in mapping.items():
67
+ if isinstance(v, dict):
68
+ v = cls.transform_to_map(v)
69
+ callback_map.append((k, v))
70
+
71
+ return callback_map
72
+
73
+ @classmethod
74
+ def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
75
+ """Transforms `CallbackMap` to `CallbackMapStrict`."""
76
+
77
+ callback_map_result = []
78
+
79
+ for key, value in callback_map:
80
+ if isinstance(value, type):
81
+ validator = (lambda tp: lambda v: isinstance(v, tp))(value)
82
+ elif isinstance(value, list):
83
+ validator = cls.transform_to_callbacks(value)
84
+ elif not callable(value):
85
+ validator = (lambda val: lambda v: val == v)(value)
86
+ else:
87
+ validator = value
88
+ callback_map_result.append((key, validator))
89
+
90
+ return callback_map_result
91
+
92
+ @staticmethod
93
+ async def run_validator(value: typing.Any, validator: Validator) -> bool:
94
+ """Run async or sync validator."""
95
+
96
+ with suppress(BaseException):
97
+ result = validator(value)
98
+ if inspect.isawaitable(result):
99
+ result = await result
100
+ return result # type: ignore
101
+
102
+ return False
103
+
104
+ @classmethod
105
+ async def match(cls, callback_data: dict, callback_map: CallbackMapStrict) -> bool:
106
+ """Matches callback_data with callback_map recursively."""
107
+
108
+ for key, validator in callback_map:
109
+ if key not in callback_data:
110
+ return False
111
+
112
+ if isinstance(validator, list):
113
+ if not (
114
+ isinstance(callback_data[key], dict)
115
+ and await cls.match(callback_data[key], validator)
116
+ ):
117
+ return False
118
+
119
+ elif not await cls.run_validator(callback_data[key], validator):
120
+ return False
121
+
122
+ return True
123
+
124
+ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
125
+ callback_data = event.decode_callback_data().unwrap_or_none()
126
+ if callback_data is None:
47
127
  return False
128
+ if await self.match(callback_data, self.mapping):
129
+ ctx.update(callback_data)
130
+ return True
131
+ return False
132
+
133
+
134
+ class CallbackDataEq(CallbackQueryDataRule):
135
+ def __init__(self, value: str):
136
+ self.value = value
48
137
 
138
+ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
139
+ return event.data.unwrap() == self.value
49
140
 
50
- class CallbackDataMarkup(ABCRule[CallbackQuery]):
51
- __event__ = EventScheme("callback_query", CallbackQuery)
52
141
 
53
- def __init__(self, patterns: typing.Union[PatternLike, typing.List[PatternLike]]):
142
+ class CallbackDataJsonEq(CallbackQueryDataRule):
143
+ def __init__(self, d: dict[str, typing.Any]):
144
+ self.d = d
145
+
146
+ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
147
+ return event.decode_callback_data().unwrap_or_none() == self.d
148
+
149
+
150
+ class CallbackDataJsonModel(CallbackQueryDataRule):
151
+ def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance]):
152
+ self.model = model
153
+
154
+ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
155
+ with suppress(BaseException):
156
+ ctx.data = decoder.decode(event.data.unwrap().encode(), type=self.model)
157
+ return True
158
+ return False
159
+
160
+
161
+ class CallbackDataMarkup(CallbackQueryDataRule):
162
+ def __init__(self, patterns: PatternLike | list[PatternLike]):
54
163
  self.patterns = Markup(patterns).patterns
55
164
 
56
- async def check(self, event: CallbackQuery, ctx: dict) -> bool:
57
- return check_string(self.patterns, event.data, ctx)
165
+ async def check(self, event: CallbackQuery, ctx: Context) -> bool:
166
+ return check_string(self.patterns, event.data.unwrap(), ctx)
167
+
168
+
169
+ __all__ = (
170
+ "CallbackDataEq",
171
+ "CallbackDataJsonEq",
172
+ "CallbackDataJsonModel",
173
+ "CallbackDataMap",
174
+ "CallbackDataMarkup",
175
+ "CallbackQueryDataRule",
176
+ "CallbackQueryRule",
177
+ "HasData",
178
+ )
@@ -0,0 +1,116 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from telegrinder.bot.dispatch.context import Context
5
+
6
+ from .abc import Message
7
+ from .text import TextMessageRule
8
+
9
+ Validator = typing.Callable[[str], typing.Any | None]
10
+
11
+
12
+ def single_split(s: str, separator: str) -> tuple[str, str]:
13
+ left, *right = s.split(separator, 1)
14
+ return left, (right[0] if right else "")
15
+
16
+
17
+ @dataclasses.dataclass(frozen=True)
18
+ class Argument:
19
+ name: str
20
+ validators: list[Validator] = dataclasses.field(default_factory=lambda: [])
21
+ optional: bool = dataclasses.field(default=False, kw_only=True)
22
+
23
+ def check(self, data: str) -> typing.Any | None:
24
+ for validator in self.validators:
25
+ data = validator(data) # type: ignore
26
+ if data is None:
27
+ return None
28
+ return data
29
+
30
+
31
+ class Command(TextMessageRule):
32
+ def __init__(
33
+ self,
34
+ names: str | typing.Iterable[str],
35
+ *arguments: Argument,
36
+ prefixes: tuple[str, ...] = ("/",),
37
+ separator: str = " ",
38
+ lazy: bool = False,
39
+ ) -> None:
40
+ self.names = [names] if isinstance(names, str) else names
41
+ self.arguments = arguments
42
+ self.prefixes = prefixes
43
+ self.separator = separator
44
+ self.lazy = lazy
45
+
46
+ def remove_prefix(self, text: str) -> str | None:
47
+ for prefix in self.prefixes:
48
+ if text.startswith(prefix):
49
+ return text.removeprefix(prefix)
50
+ return None
51
+
52
+ def parse_argument(
53
+ self,
54
+ arguments: list[Argument],
55
+ data_s: str,
56
+ new_s: str,
57
+ s: str,
58
+ ) -> dict | None:
59
+ argument = arguments[0]
60
+ data = argument.check(data_s)
61
+ if data is None and not argument.optional:
62
+ return None
63
+
64
+ if data is None:
65
+ return self.parse_arguments(arguments[1:], s)
66
+
67
+ with_argument = self.parse_arguments(arguments[1:], new_s)
68
+ if with_argument is not None:
69
+ return {argument.name: data, **with_argument}
70
+
71
+ if not argument.optional:
72
+ return None
73
+
74
+ return self.parse_arguments(arguments[1:], s)
75
+
76
+ def parse_arguments(self, arguments: list[Argument], s: str) -> dict | None:
77
+ if not arguments:
78
+ return {} if not s else None
79
+
80
+ if self.lazy:
81
+ return self.parse_argument(arguments, *single_split(s, self.separator), s)
82
+
83
+ all_split = s.split(self.separator)
84
+ for i in range(1, len(all_split) + 1):
85
+ ctx = self.parse_argument(
86
+ arguments,
87
+ self.separator.join(all_split[:i]),
88
+ self.separator.join(all_split[i:]),
89
+ s,
90
+ )
91
+ if ctx is not None:
92
+ return ctx
93
+
94
+ return None
95
+
96
+ async def check(self, message: Message, ctx: Context) -> bool:
97
+ text = self.remove_prefix(message.text.unwrap())
98
+ if text is None:
99
+ return False
100
+
101
+ name, arguments = single_split(text, self.separator)
102
+ if name not in self.names:
103
+ return False
104
+
105
+ if not self.arguments:
106
+ return not arguments
107
+
108
+ result = self.parse_arguments(list(self.arguments), arguments)
109
+ if result is None:
110
+ return False
111
+
112
+ ctx.update(result)
113
+ return True
114
+
115
+
116
+ __all__ = ("Argument", "Command", "single_split")
@@ -0,0 +1,28 @@
1
+ import enum
2
+ import typing
3
+
4
+ from telegrinder.bot.dispatch.context import Context
5
+
6
+ from .abc import Message
7
+ from .text import TextMessageRule
8
+
9
+ T = typing.TypeVar("T", bound=enum.Enum, covariant=True)
10
+
11
+
12
+ class EnumTextRule(TextMessageRule, typing.Generic[T]):
13
+ def __init__(self, enum_t: type[T], *, lower_case: bool = True) -> None:
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))
16
+
17
+ def find(self, s: str) -> T:
18
+ for enumeration in self.enum_t:
19
+ if enumeration.value.lower() == s:
20
+ return enumeration
21
+ raise KeyError("Enumeration is undefined.")
22
+
23
+ async def check(self, message: Message, ctx: Context) -> bool:
24
+ text = message.text.unwrap().lower()
25
+ if text not in self.texts:
26
+ return False
27
+ ctx.enum_text = self.find(text)
28
+ return True
@@ -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
+ )