telegrinder 0.1.dev19__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.
- telegrinder/__init__.py +129 -22
- telegrinder/api/__init__.py +11 -2
- telegrinder/api/abc.py +25 -9
- telegrinder/api/api.py +29 -24
- telegrinder/api/error.py +14 -4
- telegrinder/api/response.py +11 -7
- telegrinder/bot/__init__.py +68 -7
- telegrinder/bot/bot.py +30 -24
- telegrinder/bot/cute_types/__init__.py +11 -1
- telegrinder/bot/cute_types/base.py +47 -0
- telegrinder/bot/cute_types/callback_query.py +64 -14
- telegrinder/bot/cute_types/inline_query.py +22 -16
- telegrinder/bot/cute_types/message.py +163 -43
- telegrinder/bot/cute_types/update.py +23 -0
- telegrinder/bot/dispatch/__init__.py +56 -3
- telegrinder/bot/dispatch/abc.py +9 -7
- telegrinder/bot/dispatch/composition.py +74 -0
- telegrinder/bot/dispatch/context.py +71 -0
- telegrinder/bot/dispatch/dispatch.py +86 -49
- telegrinder/bot/dispatch/handler/__init__.py +3 -0
- telegrinder/bot/dispatch/handler/abc.py +11 -5
- telegrinder/bot/dispatch/handler/func.py +41 -32
- telegrinder/bot/dispatch/handler/message_reply.py +46 -0
- telegrinder/bot/dispatch/middleware/__init__.py +2 -0
- telegrinder/bot/dispatch/middleware/abc.py +10 -4
- telegrinder/bot/dispatch/process.py +53 -49
- telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
- telegrinder/bot/dispatch/return_manager/abc.py +95 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +25 -0
- telegrinder/bot/dispatch/view/__init__.py +14 -2
- telegrinder/bot/dispatch/view/abc.py +121 -2
- telegrinder/bot/dispatch/view/box.py +38 -0
- telegrinder/bot/dispatch/view/callback_query.py +13 -38
- telegrinder/bot/dispatch/view/inline_query.py +11 -38
- telegrinder/bot/dispatch/view/message.py +11 -46
- telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
- telegrinder/bot/polling/__init__.py +2 -0
- telegrinder/bot/polling/abc.py +11 -4
- telegrinder/bot/polling/polling.py +89 -40
- telegrinder/bot/rules/__init__.py +92 -5
- telegrinder/bot/rules/abc.py +81 -63
- telegrinder/bot/rules/adapter/__init__.py +11 -0
- telegrinder/bot/rules/adapter/abc.py +21 -0
- telegrinder/bot/rules/adapter/errors.py +5 -0
- telegrinder/bot/rules/adapter/event.py +43 -0
- telegrinder/bot/rules/adapter/raw_update.py +24 -0
- telegrinder/bot/rules/callback_data.py +159 -38
- telegrinder/bot/rules/command.py +116 -0
- telegrinder/bot/rules/enum_text.py +28 -0
- telegrinder/bot/rules/func.py +17 -17
- telegrinder/bot/rules/fuzzy.py +13 -10
- telegrinder/bot/rules/inline.py +61 -0
- telegrinder/bot/rules/integer.py +12 -7
- telegrinder/bot/rules/is_from.py +148 -7
- telegrinder/bot/rules/markup.py +21 -18
- telegrinder/bot/rules/mention.py +17 -0
- telegrinder/bot/rules/message_entities.py +33 -0
- telegrinder/bot/rules/regex.py +27 -19
- telegrinder/bot/rules/rule_enum.py +74 -0
- telegrinder/bot/rules/start.py +42 -0
- telegrinder/bot/rules/text.py +23 -14
- telegrinder/bot/scenario/__init__.py +2 -0
- telegrinder/bot/scenario/abc.py +12 -5
- telegrinder/bot/scenario/checkbox.py +48 -30
- telegrinder/bot/scenario/choice.py +16 -10
- telegrinder/client/__init__.py +2 -0
- telegrinder/client/abc.py +8 -21
- telegrinder/client/aiohttp.py +30 -21
- telegrinder/model.py +68 -37
- telegrinder/modules.py +189 -21
- telegrinder/msgspec_json.py +14 -0
- telegrinder/msgspec_utils.py +207 -0
- telegrinder/node/__init__.py +31 -0
- telegrinder/node/attachment.py +71 -0
- telegrinder/node/base.py +93 -0
- telegrinder/node/composer.py +71 -0
- telegrinder/node/container.py +22 -0
- telegrinder/node/message.py +18 -0
- telegrinder/node/rule.py +56 -0
- telegrinder/node/source.py +31 -0
- telegrinder/node/text.py +13 -0
- telegrinder/node/tools/__init__.py +3 -0
- telegrinder/node/tools/generator.py +40 -0
- telegrinder/node/update.py +12 -0
- telegrinder/rules.py +1 -1
- telegrinder/tools/__init__.py +165 -4
- telegrinder/tools/buttons.py +75 -51
- telegrinder/tools/error_handler/__init__.py +8 -0
- telegrinder/tools/error_handler/abc.py +30 -0
- telegrinder/tools/error_handler/error_handler.py +156 -0
- telegrinder/tools/formatting/__init__.py +81 -3
- telegrinder/tools/formatting/html.py +283 -37
- telegrinder/tools/formatting/links.py +32 -0
- telegrinder/tools/formatting/spec_html_formats.py +121 -0
- telegrinder/tools/global_context/__init__.py +12 -0
- telegrinder/tools/global_context/abc.py +66 -0
- telegrinder/tools/global_context/global_context.py +451 -0
- telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
- telegrinder/tools/i18n/__init__.py +12 -0
- telegrinder/tools/i18n/base.py +31 -0
- telegrinder/tools/i18n/middleware/__init__.py +3 -0
- telegrinder/tools/i18n/middleware/base.py +26 -0
- telegrinder/tools/i18n/simple.py +48 -0
- telegrinder/tools/inline_query.py +684 -0
- telegrinder/tools/kb_set/__init__.py +2 -0
- telegrinder/tools/kb_set/base.py +3 -0
- telegrinder/tools/kb_set/yaml.py +28 -17
- telegrinder/tools/keyboard.py +84 -62
- telegrinder/tools/loop_wrapper/__init__.py +4 -0
- telegrinder/tools/loop_wrapper/abc.py +18 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +132 -0
- telegrinder/tools/magic.py +48 -23
- telegrinder/tools/parse_mode.py +1 -2
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/enums.py +651 -0
- telegrinder/types/methods.py +3933 -1128
- telegrinder/types/objects.py +4755 -1633
- {telegrinder-0.1.dev19.dist-info → telegrinder-0.1.dev158.dist-info}/LICENSE +2 -1
- telegrinder-0.1.dev158.dist-info/METADATA +108 -0
- telegrinder-0.1.dev158.dist-info/RECORD +126 -0
- {telegrinder-0.1.dev19.dist-info → telegrinder-0.1.dev158.dist-info}/WHEEL +1 -1
- telegrinder/bot/dispatch/waiter.py +0 -37
- telegrinder/result.py +0 -38
- telegrinder/tools/formatting/abc.py +0 -52
- telegrinder/tools/formatting/markdown.py +0 -57
- telegrinder/typegen/__init__.py +0 -1
- telegrinder/typegen/__main__.py +0 -3
- telegrinder/typegen/nicification.py +0 -20
- telegrinder/typegen/schema_generator.py +0 -259
- telegrinder-0.1.dev19.dist-info/METADATA +0 -22
- telegrinder-0.1.dev19.dist-info/RECORD +0 -74
|
@@ -1,57 +1,178 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
|
|
16
|
-
self.value = value
|
|
37
|
+
class CallbackQueryRule(ABCRule[CallbackQuery], abc.ABC):
|
|
38
|
+
adapter = EventAdapter("callback_query", CallbackQuery)
|
|
17
39
|
|
|
18
|
-
|
|
19
|
-
|
|
40
|
+
@abc.abstractmethod
|
|
41
|
+
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
42
|
+
pass
|
|
20
43
|
|
|
21
44
|
|
|
22
|
-
class
|
|
23
|
-
def
|
|
24
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
telegrinder/bot/rules/func.py
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
|
|
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,
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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",)
|
telegrinder/bot/rules/fuzzy.py
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
from .abc import Message
|
|
2
|
-
from .text import ABCTextMessageRule
|
|
3
1
|
import difflib
|
|
4
|
-
|
|
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(
|
|
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:
|
|
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
|
|
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
|
+
)
|
telegrinder/bot/rules/integer.py
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
from .
|
|
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
|
-
|
|
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:
|
|
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")
|
telegrinder/bot/rules/is_from.py
CHANGED
|
@@ -1,11 +1,152 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
)
|