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,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")
@@ -1,3 +1,5 @@
1
1
  from .abc import ABCScenario
2
2
  from .checkbox import Checkbox
3
3
  from .choice import SingleChoice
4
+
5
+ __all__ = ("ABCScenario", "Checkbox", "SingleChoice")
@@ -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.bot.dispatch import Dispatch
6
- from telegrinder.api import API
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
- class ABCScenario(ABC):
12
+
13
+ class ABCScenario(ABC, typing.Generic[EventT]):
10
14
  @abstractmethod
11
- def wait(self, api: "API", dispatch: "Dispatch") -> typing.Any:
15
+ def wait(self, api: "ABCAPI", view: "ABCStateView[EventT]") -> typing.Any:
12
16
  pass
17
+
18
+
19
+ __all__ = ("ABCScenario",)
@@ -1,22 +1,25 @@
1
- from .abc import ABCScenario
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 = "MarkdownV2"
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: typing.List[Choice] = []
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(resize_keyboard=True)
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, name: str, default_text: str, picked_text: str, is_picked: bool = False
68
- ) -> "Checkbox":
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.ctx_api.edit_message_text(
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, api: "API", dispatch: "Dispatch"
97
- ) -> typing.Tuple[typing.Dict[str, bool], int]:
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, text=self.msg, parse_mode=self.PARSE_MODE, reply_markup=self.get_markup()
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 dispatch.callback_query.wait_for_answer(message.message_id)
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
- return {
112
- choice.name: choice.is_picked for choice in self.choices
113
- }, message.message_id
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 Dispatch
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, api: "API", dispatch: "Dispatch"
35
- ) -> typing.Tuple[str, int]:
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, dispatch)
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",)
@@ -1,2 +1,4 @@
1
1
  from .abc import ABCClient, ClientData
2
2
  from .aiohttp import AiohttpClient
3
+
4
+ __all__ = ("ABCClient", "AiohttpClient", "ClientData")
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")