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.
- 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 +145 -53
- 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 -39
- telegrinder/bot/dispatch/view/inline_query.py +11 -39
- telegrinder/bot/dispatch/view/message.py +11 -47
- 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 +91 -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 +25 -13
- 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 +3920 -1251
- telegrinder/types/objects.py +4702 -1718
- {telegrinder-0.1.dev20.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.dev20.dist-info → telegrinder-0.1.dev158.dist-info}/WHEEL +1 -1
- telegrinder/bot/dispatch/waiter.py +0 -38
- telegrinder/result.py +0 -38
- telegrinder/tools/formatting/abc.py +0 -52
- telegrinder/tools/formatting/markdown.py +0 -57
- telegrinder-0.1.dev20.dist-info/METADATA +0 -22
- telegrinder-0.1.dev20.dist-info/RECORD +0 -71
telegrinder/bot/rules/markup.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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(
|
|
25
|
-
def __init__(self, patterns:
|
|
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:
|
|
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")
|
telegrinder/bot/rules/regex.py
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
|
-
from .
|
|
5
|
-
from .text import ABCTextMessageRule
|
|
4
|
+
from telegrinder.bot.dispatch.context import Context
|
|
6
5
|
|
|
7
|
-
|
|
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
|
-
|
|
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:
|
|
26
|
+
async def check(self, message: Message, ctx: Context) -> bool:
|
|
25
27
|
for regexp in self.regexp:
|
|
26
|
-
|
|
27
|
-
if
|
|
28
|
-
|
|
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")
|
telegrinder/bot/rules/start.py
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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,
|
|
19
|
-
param: str | None = ctx.
|
|
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
|
|
37
|
+
|
|
38
|
+
ctx.set(self.alias or "param", validated_param)
|
|
30
39
|
return True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = ("StartCommand",)
|
telegrinder/bot/rules/text.py
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
from .
|
|
2
|
-
import
|
|
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
|
-
|
|
6
|
-
|
|
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
|
|
12
|
+
class TextMessageRule(MessageRule, ABC, requires=[HasText()]):
|
|
11
13
|
pass
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
class Text(
|
|
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:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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")
|
telegrinder/bot/scenario/abc.py
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
1
|
import typing
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
from telegrinder.bot.cute_types.base import BaseCute
|
|
3
5
|
|
|
4
6
|
if typing.TYPE_CHECKING:
|
|
5
|
-
from telegrinder.
|
|
6
|
-
from telegrinder.
|
|
7
|
+
from telegrinder.api import ABCAPI
|
|
8
|
+
from telegrinder.bot.dispatch.view.abc import ABCStateView
|
|
7
9
|
|
|
10
|
+
EventT = typing.TypeVar("EventT", bound=BaseCute)
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
|
|
13
|
+
class ABCScenario(ABC, typing.Generic[EventT]):
|
|
10
14
|
@abstractmethod
|
|
11
|
-
def wait(self, api: "
|
|
15
|
+
def wait(self, api: "ABCAPI", view: "ABCStateView[EventT]") -> typing.Any:
|
|
12
16
|
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ("ABCScenario",)
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from telegrinder.tools import InlineKeyboard, InlineButton
|
|
4
|
-
from telegrinder.types.objects import InlineKeyboardMarkup
|
|
5
|
-
from telegrinder.bot.cute_types import CallbackQueryCute
|
|
6
|
-
import typing
|
|
1
|
+
import dataclasses
|
|
7
2
|
import random
|
|
8
3
|
import string
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.bot.cute_types import CallbackQueryCute
|
|
7
|
+
from telegrinder.bot.dispatch.waiter_machine import WaiterMachine
|
|
8
|
+
from telegrinder.tools import InlineButton, InlineKeyboard
|
|
9
|
+
from telegrinder.tools.parse_mode import ParseMode
|
|
10
|
+
from telegrinder.types.objects import InlineKeyboardMarkup
|
|
11
|
+
|
|
12
|
+
from .abc import ABCScenario
|
|
9
13
|
|
|
10
14
|
if typing.TYPE_CHECKING:
|
|
11
|
-
from telegrinder.bot.dispatch import Dispatch
|
|
12
15
|
from telegrinder.api import API
|
|
16
|
+
from telegrinder.bot.dispatch.view.abc import BaseStateView
|
|
13
17
|
|
|
14
18
|
|
|
15
|
-
@dataclass
|
|
19
|
+
@dataclasses.dataclass
|
|
16
20
|
class Choice:
|
|
17
21
|
name: str
|
|
18
22
|
is_picked: bool
|
|
19
|
-
|
|
20
23
|
default_text: str
|
|
21
24
|
picked_text: str
|
|
22
25
|
code: str
|
|
@@ -26,13 +29,14 @@ def random_code(length: int) -> str:
|
|
|
26
29
|
return "".join(random.choices(string.ascii_letters + string.digits, k=length))
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
class Checkbox(ABCScenario):
|
|
30
|
-
INVALID_CODE: str = "Invalid code"
|
|
31
|
-
CALLBACK_ANSWER: str = "Done"
|
|
32
|
-
PARSE_MODE: str =
|
|
32
|
+
class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
33
|
+
INVALID_CODE: typing.ClassVar[str] = "Invalid code"
|
|
34
|
+
CALLBACK_ANSWER: typing.ClassVar[str] = "Done"
|
|
35
|
+
PARSE_MODE: typing.ClassVar[str] = ParseMode.MARKDOWNV2
|
|
33
36
|
|
|
34
37
|
def __init__(
|
|
35
38
|
self,
|
|
39
|
+
waiter_machine: WaiterMachine,
|
|
36
40
|
chat_id: int,
|
|
37
41
|
msg: str,
|
|
38
42
|
ready_text: str = "Ready",
|
|
@@ -40,13 +44,14 @@ class Checkbox(ABCScenario):
|
|
|
40
44
|
):
|
|
41
45
|
self.chat_id = chat_id
|
|
42
46
|
self.msg = msg
|
|
43
|
-
self.choices:
|
|
47
|
+
self.choices: list[Choice] = []
|
|
44
48
|
self.ready = ready_text
|
|
45
49
|
self.max_in_row = max_in_row
|
|
46
50
|
self.random_code = random_code(16)
|
|
51
|
+
self.waiter_machine = waiter_machine
|
|
47
52
|
|
|
48
53
|
def get_markup(self) -> InlineKeyboardMarkup:
|
|
49
|
-
kb = InlineKeyboard(
|
|
54
|
+
kb = InlineKeyboard()
|
|
50
55
|
choices = self.choices.copy()
|
|
51
56
|
while choices:
|
|
52
57
|
while len(kb.keyboard[-1]) < self.max_in_row and choices:
|
|
@@ -60,20 +65,24 @@ class Checkbox(ABCScenario):
|
|
|
60
65
|
)
|
|
61
66
|
)
|
|
62
67
|
kb.row()
|
|
68
|
+
|
|
63
69
|
kb.add(InlineButton(self.ready, callback_data=self.random_code + "/ready"))
|
|
64
70
|
return kb.get_markup()
|
|
65
71
|
|
|
66
72
|
def add_option(
|
|
67
|
-
self,
|
|
68
|
-
|
|
73
|
+
self,
|
|
74
|
+
name: str,
|
|
75
|
+
default_text: str,
|
|
76
|
+
picked_text: str,
|
|
77
|
+
is_picked: bool = False,
|
|
78
|
+
) -> typing.Self:
|
|
69
79
|
self.choices.append(
|
|
70
|
-
Choice(name, is_picked, default_text, picked_text, random_code(16))
|
|
80
|
+
Choice(name, is_picked, default_text, picked_text, random_code(16)),
|
|
71
81
|
)
|
|
72
82
|
return self
|
|
73
83
|
|
|
74
84
|
async def handle(self, cb: CallbackQueryCute) -> bool:
|
|
75
|
-
code = cb.data.replace(self.random_code + "/", "", 1)
|
|
76
|
-
|
|
85
|
+
code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
|
|
77
86
|
if code == "ready":
|
|
78
87
|
return False
|
|
79
88
|
|
|
@@ -81,9 +90,7 @@ class Checkbox(ABCScenario):
|
|
|
81
90
|
if choice.code == code:
|
|
82
91
|
# Toggle choice
|
|
83
92
|
self.choices[i].is_picked = not self.choices[i].is_picked
|
|
84
|
-
await cb.
|
|
85
|
-
cb.message.chat.id,
|
|
86
|
-
cb.message.message_id,
|
|
93
|
+
await cb.edit_text(
|
|
87
94
|
text=self.msg,
|
|
88
95
|
parse_mode=self.PARSE_MODE,
|
|
89
96
|
reply_markup=self.get_markup(),
|
|
@@ -93,21 +100,32 @@ class Checkbox(ABCScenario):
|
|
|
93
100
|
return True
|
|
94
101
|
|
|
95
102
|
async def wait(
|
|
96
|
-
self,
|
|
97
|
-
|
|
103
|
+
self,
|
|
104
|
+
api: "API",
|
|
105
|
+
view: "BaseStateView[CallbackQueryCute]",
|
|
106
|
+
) -> tuple[dict[str, bool], int]:
|
|
98
107
|
assert len(self.choices) > 1
|
|
99
108
|
message = (
|
|
100
109
|
await api.send_message(
|
|
101
|
-
self.chat_id,
|
|
110
|
+
self.chat_id,
|
|
111
|
+
text=self.msg,
|
|
112
|
+
parse_mode=self.PARSE_MODE,
|
|
113
|
+
reply_markup=self.get_markup(),
|
|
102
114
|
)
|
|
103
115
|
).unwrap()
|
|
116
|
+
|
|
104
117
|
while True:
|
|
105
118
|
q: CallbackQueryCute
|
|
106
|
-
q, _ = await
|
|
119
|
+
q, _ = await self.waiter_machine.wait(view, (api, message.message_id))
|
|
107
120
|
should_continue = await self.handle(q)
|
|
108
121
|
await q.answer(self.CALLBACK_ANSWER)
|
|
109
122
|
if not should_continue:
|
|
110
123
|
break
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
{choice.name: choice.is_picked for choice in self.choices},
|
|
127
|
+
message.message_id,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
__all__ = ("Checkbox", "Choice", "random_code")
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
from .checkbox import Checkbox
|
|
2
|
-
from telegrinder.bot.cute_types import CallbackQueryCute
|
|
3
1
|
import typing
|
|
4
2
|
|
|
3
|
+
from telegrinder.bot.cute_types import CallbackQueryCute
|
|
4
|
+
|
|
5
|
+
from .checkbox import Checkbox
|
|
6
|
+
|
|
5
7
|
if typing.TYPE_CHECKING:
|
|
6
8
|
from telegrinder.api import API
|
|
7
|
-
from telegrinder.bot.dispatch import
|
|
9
|
+
from telegrinder.bot.dispatch.view.abc import BaseStateView
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class SingleChoice(Checkbox):
|
|
11
13
|
async def handle(self, cb: CallbackQueryCute) -> bool:
|
|
12
|
-
code = cb.data.replace(self.random_code + "/", "", 1)
|
|
13
|
-
|
|
14
|
+
code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
|
|
14
15
|
if code == "ready":
|
|
15
16
|
return False
|
|
16
17
|
|
|
@@ -21,8 +22,8 @@ class SingleChoice(Checkbox):
|
|
|
21
22
|
if choice.code == code:
|
|
22
23
|
self.choices[i].is_picked = True
|
|
23
24
|
await cb.ctx_api.edit_message_text(
|
|
24
|
-
cb.message.chat.id,
|
|
25
|
-
cb.message.message_id,
|
|
25
|
+
chat_id=cb.message.unwrap().v.chat.id,
|
|
26
|
+
message_id=cb.message.unwrap().v.message_id,
|
|
26
27
|
text=self.msg,
|
|
27
28
|
parse_mode=self.PARSE_MODE,
|
|
28
29
|
reply_markup=self.get_markup(),
|
|
@@ -31,9 +32,14 @@ class SingleChoice(Checkbox):
|
|
|
31
32
|
return True
|
|
32
33
|
|
|
33
34
|
async def wait(
|
|
34
|
-
self,
|
|
35
|
-
|
|
35
|
+
self,
|
|
36
|
+
api: "API",
|
|
37
|
+
cb_view: "BaseStateView[CallbackQueryCute]",
|
|
38
|
+
) -> tuple[str, int]:
|
|
36
39
|
if len([choice for choice in self.choices if choice.is_picked]) != 1:
|
|
37
40
|
raise ValueError("Exactly one choice must be picked")
|
|
38
|
-
choices, m_id = await super().wait(api,
|
|
41
|
+
choices, m_id = await super().wait(api, cb_view)
|
|
39
42
|
return list(choices.keys())[list(choices.values()).index(True)], m_id
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = ("SingleChoice",)
|
telegrinder/client/__init__.py
CHANGED
telegrinder/client/abc.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
1
|
import typing
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
3
|
|
|
4
4
|
ClientData = typing.Any
|
|
5
5
|
|
|
@@ -11,41 +11,25 @@ class ABCClient(ABC):
|
|
|
11
11
|
|
|
12
12
|
@abstractmethod
|
|
13
13
|
async def request_text(
|
|
14
|
-
self,
|
|
15
|
-
url: str,
|
|
16
|
-
method: str = "GET",
|
|
17
|
-
data: typing.Optional[dict] = None,
|
|
18
|
-
**kwargs
|
|
14
|
+
self, url: str, method: str = "GET", data: dict | None = None, **kwargs
|
|
19
15
|
) -> str:
|
|
20
16
|
pass
|
|
21
17
|
|
|
22
18
|
@abstractmethod
|
|
23
19
|
async def request_json(
|
|
24
|
-
self,
|
|
25
|
-
url: str,
|
|
26
|
-
method: str = "GET",
|
|
27
|
-
data: typing.Optional[dict] = None,
|
|
28
|
-
**kwargs
|
|
20
|
+
self, url: str, method: str = "GET", data: dict | None = None, **kwargs
|
|
29
21
|
) -> dict:
|
|
30
22
|
pass
|
|
31
23
|
|
|
32
24
|
@abstractmethod
|
|
33
25
|
async def request_content(
|
|
34
|
-
self,
|
|
35
|
-
url: str,
|
|
36
|
-
method: str = "GET",
|
|
37
|
-
data: typing.Optional[dict] = None,
|
|
38
|
-
**kwargs
|
|
26
|
+
self, url: str, method: str = "GET", data: dict | None = None, **kwargs
|
|
39
27
|
) -> bytes:
|
|
40
28
|
pass
|
|
41
29
|
|
|
42
30
|
@abstractmethod
|
|
43
31
|
async def request_bytes(
|
|
44
|
-
self,
|
|
45
|
-
url: str,
|
|
46
|
-
method: str = "GET",
|
|
47
|
-
data: typing.Optional[dict] = None,
|
|
48
|
-
**kwargs
|
|
32
|
+
self, url: str, method: str = "GET", data: dict | None = None, **kwargs
|
|
49
33
|
) -> bytes:
|
|
50
34
|
pass
|
|
51
35
|
|
|
@@ -63,3 +47,6 @@ class ABCClient(ABC):
|
|
|
63
47
|
|
|
64
48
|
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
65
49
|
await self.close()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = ("ABCClient", "ClientData")
|