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,71 +1,120 @@
1
- import traceback
2
1
  import asyncio
2
+ import typing
3
3
 
4
- import msgspec.json
4
+ import aiohttp
5
+ import msgspec
6
+ from fntypes.result import Error, Ok
5
7
 
6
- from .abc import ABCPolling
7
8
  from telegrinder.api.abc import ABCAPI
8
- import typing
9
+ from telegrinder.api.error import InvalidTokenError
10
+ from telegrinder.bot.polling.abc import ABCPolling
9
11
  from telegrinder.modules import logger
10
- from telegrinder.model import Raw
11
- from telegrinder.types import Update
12
-
13
- ALLOWED_UPDATES = [
14
- "update_id",
15
- "message",
16
- "edited_message",
17
- "channel_post",
18
- "edited_channel_post",
19
- "inline_query",
20
- "chosen_inline_result",
21
- "callback_query",
22
- "shipping_query",
23
- "pre_checkout_query",
24
- "poll",
25
- "poll_answer",
26
- "my_chat_member",
27
- "chat_member",
28
- ]
12
+ from telegrinder.msgspec_utils import decoder
13
+ from telegrinder.types import Update, UpdateType
29
14
 
30
15
 
31
16
  class Polling(ABCPolling):
32
17
  def __init__(
33
18
  self,
34
- api: typing.Optional[ABCAPI] = None,
35
- offset: typing.Optional[int] = None,
19
+ api: ABCAPI,
20
+ *,
21
+ offset: int = 0,
22
+ reconnection_timeout: float = 5,
23
+ max_reconnetions: int = 10,
24
+ include_updates: set[str | UpdateType] | None = None,
25
+ exclude_updates: set[str | UpdateType] | None = None,
36
26
  ):
37
27
  self.api = api
38
- self.offset = offset or 0
28
+ self.allowed_updates = self.get_allowed_updates(
29
+ include_updates=include_updates,
30
+ exclude_updates=exclude_updates,
31
+ )
32
+ self.reconnection_timeout = 5 if reconnection_timeout < 0 else reconnection_timeout
33
+ self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
34
+ self.offset = offset
39
35
  self._stop = False
40
- self.allowed_updates = ALLOWED_UPDATES
41
36
 
42
- async def get_updates(self) -> typing.Optional[Raw]:
37
+ def get_allowed_updates(
38
+ self,
39
+ *,
40
+ include_updates: set[str | UpdateType] | None = None,
41
+ exclude_updates: set[str | UpdateType] | None = None,
42
+ ) -> list[str]:
43
+ allowed_updates: list[str] = list(x.value for x in UpdateType)
44
+ if not include_updates and not exclude_updates:
45
+ return allowed_updates
46
+
47
+ if include_updates and exclude_updates:
48
+ allowed_updates = [
49
+ x
50
+ for x in allowed_updates
51
+ if x in include_updates and x not in exclude_updates
52
+ ]
53
+ elif exclude_updates:
54
+ allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
55
+ elif include_updates:
56
+ allowed_updates = [x for x in allowed_updates if x in include_updates]
57
+
58
+ return [x.value if isinstance(x, UpdateType) else x for x in allowed_updates]
59
+
60
+ async def get_updates(self) -> msgspec.Raw | None:
43
61
  raw_updates = await self.api.request_raw(
44
62
  "getUpdates",
45
63
  {"offset": self.offset, "allowed_updates": self.allowed_updates},
46
64
  )
47
- if not raw_updates.is_ok and raw_updates.error.code == 404:
48
- logger.fatal("Token seems to be invalid")
49
- exit(6)
50
- return raw_updates.unwrap()
65
+ match raw_updates:
66
+ case Ok(value):
67
+ return value
68
+ case Error(err) if err.code in (401, 404):
69
+ raise InvalidTokenError("Token seems to be invalid")
51
70
 
52
- async def listen(self) -> typing.AsyncIterator[typing.List[Update]]:
53
- logger.debug("listening polling")
71
+ async def listen(self) -> typing.AsyncGenerator[list[Update], None]:
72
+ logger.debug("Listening polling")
73
+ reconn_counter = 0
74
+
54
75
  while not self._stop:
55
76
  try:
56
77
  updates = await self.get_updates()
57
- updates_list: typing.List[Update] = msgspec.json.decode(
58
- updates, type=typing.List[Update]
78
+ reconn_counter = 0
79
+ if not updates:
80
+ continue
81
+ updates_list: list[Update] = decoder.decode(
82
+ updates, type=list[Update]
59
83
  )
60
84
  if updates_list:
61
85
  yield updates_list
62
86
  self.offset = updates_list[-1].update_id + 1
87
+ except InvalidTokenError as e:
88
+ logger.error(e)
89
+ self.stop()
90
+ exit(6)
63
91
  except asyncio.CancelledError:
64
- logger.info("caught cancel, stopping")
65
- self._stop = True
92
+ logger.info("Caught cancel, polling stopping...")
93
+ self.stop()
94
+ except (aiohttp.client.ServerConnectionError, TimeoutError):
95
+ if reconn_counter > self.max_reconnetions:
96
+ logger.error(
97
+ "Failed to reconnect to the server after {} attempts, polling stopping.",
98
+ self.max_reconnetions,
99
+ )
100
+ self.stop()
101
+ exit(9)
102
+ else:
103
+ logger.warning("Server disconnected, waiting 5 seconds to reconnetion...")
104
+ reconn_counter += 1
105
+ await asyncio.sleep(self.reconnection_timeout)
106
+ except aiohttp.ClientConnectorError:
107
+ logger.error(
108
+ "Client connection failed, polling stopping! "
109
+ "Please, check your internet connection."
110
+ )
111
+ self.stop()
112
+ exit(3)
66
113
  except BaseException as e:
67
- traceback.print_exc()
68
- logger.error(e)
114
+ logger.exception(e)
69
115
 
70
116
  def stop(self) -> None:
71
117
  self._stop = True
118
+
119
+
120
+ __all__ = ("Polling",)
@@ -1,15 +1,101 @@
1
- from .abc import ABCRule, ABCMessageRule, AndRule, OrRule
1
+ from .abc import ABCRule, AndRule, MessageRule, OrRule
2
2
  from .callback_data import (
3
3
  CallbackDataEq,
4
4
  CallbackDataJsonEq,
5
5
  CallbackDataJsonModel,
6
+ CallbackDataMap,
6
7
  CallbackDataMarkup,
8
+ CallbackQueryDataRule,
9
+ CallbackQueryRule,
10
+ HasData,
7
11
  )
12
+ from .command import Argument, Command
13
+ from .enum_text import EnumTextRule
8
14
  from .func import FuncRule
9
- from .is_from import IsPrivate, IsChat
10
- from .markup import Markup
11
- from .regex import Regex
12
- from .text import Text, HasText, ABCTextMessageRule
13
15
  from .fuzzy import FuzzyText
16
+ from .inline import (
17
+ HasLocation,
18
+ InlineQueryChatType,
19
+ InlineQueryMarkup,
20
+ InlineQueryRule,
21
+ InlineQueryText,
22
+ )
14
23
  from .integer import Integer, IntegerInRange
24
+ from .is_from import (
25
+ IsBasketballDice,
26
+ IsBot,
27
+ IsBowlingDice,
28
+ IsChat,
29
+ IsChatId,
30
+ IsDartDice,
31
+ IsDice,
32
+ IsForum,
33
+ IsGroup,
34
+ IsLanguageCode,
35
+ IsPremium,
36
+ IsPrivate,
37
+ IsReply,
38
+ IsSuperGroup,
39
+ IsUser,
40
+ IsUserId,
41
+ )
42
+ from .markup import Markup
43
+ from .mention import HasMention
44
+ from .message_entities import HasEntities, MessageEntities
45
+ from .regex import Regex
46
+ from .rule_enum import RuleEnum
15
47
  from .start import StartCommand
48
+ from .text import HasText, Text, TextMessageRule
49
+
50
+ __all__ = (
51
+ "ABCRule",
52
+ "AndRule",
53
+ "Argument",
54
+ "CallbackDataEq",
55
+ "CallbackDataJsonEq",
56
+ "CallbackDataJsonModel",
57
+ "CallbackDataMap",
58
+ "CallbackDataMarkup",
59
+ "CallbackQueryDataRule",
60
+ "CallbackQueryRule",
61
+ "Command",
62
+ "EnumTextRule",
63
+ "FuncRule",
64
+ "FuzzyText",
65
+ "HasData",
66
+ "HasEntities",
67
+ "HasMention",
68
+ "HasText",
69
+ "InlineQueryRule",
70
+ "InlineQueryText",
71
+ "Integer",
72
+ "IntegerInRange",
73
+ "IsBasketballDice",
74
+ "IsBot",
75
+ "IsBowlingDice",
76
+ "IsChat",
77
+ "IsChatId",
78
+ "IsDartDice",
79
+ "IsDice",
80
+ "IsForum",
81
+ "IsGroup",
82
+ "IsLanguageCode",
83
+ "IsPremium",
84
+ "IsPrivate",
85
+ "IsReply",
86
+ "IsSuperGroup",
87
+ "IsUser",
88
+ "IsUserId",
89
+ "HasLocation",
90
+ "InlineQueryChatType",
91
+ "InlineQueryMarkup",
92
+ "Markup",
93
+ "MessageEntities",
94
+ "MessageRule",
95
+ "OrRule",
96
+ "Regex",
97
+ "RuleEnum",
98
+ "StartCommand",
99
+ "Text",
100
+ "TextMessageRule",
101
+ )
@@ -1,102 +1,120 @@
1
- from abc import ABC, abstractmethod
2
- from telegrinder.bot.cute_types import MessageCute
3
- from telegrinder.types import Update
4
- import typing
5
- import collections
6
1
  import inspect
7
- import vbml
2
+ import typing
3
+ from abc import ABC, abstractmethod
8
4
 
9
- T = typing.TypeVar("T")
10
- patcher = vbml.Patcher()
5
+ from telegrinder.bot.cute_types import BaseCute, MessageCute, UpdateCute
6
+ from telegrinder.bot.dispatch.context import Context
7
+ from telegrinder.bot.dispatch.process import check_rule
8
+ from telegrinder.bot.rules.adapter import ABCAdapter, EventAdapter, RawUpdateAdapter
9
+ from telegrinder.tools.i18n.base import ABCTranslator
10
+ from telegrinder.tools.magic import cache_translation, get_cached_translation
11
+ from telegrinder.types.objects import Update as UpdateObject
11
12
 
12
- Message = MessageCute
13
- EventScheme = collections.namedtuple("EventScheme", ["name", "dataclass"])
13
+ T = typing.TypeVar("T", bound=BaseCute)
14
14
 
15
+ Message: typing.TypeAlias = MessageCute
16
+ Update: typing.TypeAlias = UpdateCute
15
17
 
16
- class ABCRule(ABC, typing.Generic[T]):
17
- __event__: typing.Optional[EventScheme] = None
18
- require: typing.List["ABCRule[T]"] = []
19
18
 
20
- async def run_check(self, event: T, ctx: dict) -> bool:
21
- ctx_copy = ctx.copy()
22
- for required in self.require:
23
- if not await required.run_check(event, ctx_copy):
24
- return False
25
- ctx.update(ctx_copy)
26
- return await self.check(event, ctx)
19
+ def with_caching_translations(func):
20
+ """Should be used as decorator for .translate method. Caches rule translations."""
21
+
22
+ async def wrapper(self: "ABCRule", translator: ABCTranslator):
23
+ if translation := get_cached_translation(self, translator.locale):
24
+ return translation
25
+ translation = await func(self, translator)
26
+ cache_translation(self, translator.locale, translation)
27
+ return translation
28
+
29
+ return wrapper
30
+
31
+
32
+ class ABCRule(ABC, typing.Generic[T]):
33
+ adapter: ABCAdapter[UpdateObject, T] = RawUpdateAdapter() # type: ignore
34
+ requires: list["ABCRule[T]"] = []
27
35
 
28
36
  @abstractmethod
29
- async def check(self, event: T, ctx: dict) -> bool:
37
+ async def check(self, event: T, ctx: Context) -> bool:
30
38
  pass
31
39
 
32
- def __init_subclass__(cls, require: typing.Optional[typing.List["ABCRule[T]"]] = None):
33
- """Merges requirements from inherited classes and rule-specific requirements"""
40
+ def __init_subclass__(cls, requires: list["ABCRule[T]"] | None = None):
41
+ """Merges requirements from inherited classes and rule-specific requirements."""
42
+
34
43
  requirements = []
35
44
  for base in inspect.getmro(cls):
36
45
  if issubclass(base, ABCRule) and base != cls:
37
- requirements.extend(base.require or ())
46
+ requirements.extend(base.requires or ()) # type: ignore
38
47
 
39
- requirements.extend(require or ())
40
- cls.require = list(dict.fromkeys(requirements))
48
+ requirements.extend(requires or ())
49
+ cls.requires = list(dict.fromkeys(requirements))
41
50
 
42
- def __and__(self, other: "ABCRule"):
51
+ def __and__(self, other: "ABCRule[T]"):
43
52
  return AndRule(self, other)
44
53
 
45
- def __or__(self, other: "ABCRule"):
54
+ def __or__(self, other: "ABCRule[T]"):
46
55
  return OrRule(self, other)
47
56
 
57
+ def __neg__(self) -> "ABCRule[T]":
58
+ return NotRule(self)
59
+
48
60
  def __repr__(self) -> str:
49
- return f"(rule {self.__class__.__name__})"
61
+ return "<rule: {!r}, adapter: {!r}>".format(
62
+ self.__class__.__name__,
63
+ self.adapter,
64
+ )
50
65
 
66
+ async def translate(self, translator: ABCTranslator) -> typing.Self:
67
+ return self
51
68
 
52
- class AndRule(ABCRule):
53
- def __init__(self, *rules: ABCRule):
69
+
70
+ class AndRule(ABCRule[T]):
71
+ def __init__(self, *rules: ABCRule[T]):
54
72
  self.rules = rules
55
73
 
56
- async def check(self, event: Update, ctx: dict) -> bool:
74
+ async def check(self, event: Update, ctx: Context) -> bool:
57
75
  ctx_copy = ctx.copy()
58
76
  for rule in self.rules:
59
- e = event
60
- if rule.__event__:
61
- event_dict = event.to_dict()
62
- if rule.__event__.name not in event:
63
- return False
64
- e = rule.__event__.dataclass(
65
- **event_dict[rule.__event__.name].to_dict()
66
- )
67
- if not await rule.run_check(e, ctx_copy):
77
+ if not await check_rule(event.ctx_api, rule, event, ctx_copy):
68
78
  return False
69
- ctx.clear()
70
- ctx.update(ctx_copy)
79
+ ctx |= ctx_copy
71
80
  return True
72
81
 
73
82
 
74
- class OrRule(ABCRule):
75
- def __init__(self, *rules: ABCRule):
83
+ class OrRule(ABCRule[T]):
84
+ def __init__(self, *rules: ABCRule[T]):
76
85
  self.rules = rules
77
86
 
78
- async def check(self, event: T, ctx: dict) -> bool:
79
- ctx_copy = ctx.copy()
80
- found = False
81
-
87
+ async def check(self, event: Update, ctx: Context) -> bool:
82
88
  for rule in self.rules:
83
- e = event
84
- if rule.__event__:
85
- if rule.__event__.name not in event:
86
- continue
87
- e = rule.__event__.dataclass(**event[rule.__event__.name])
88
- if await rule.run_check(e, ctx_copy):
89
- found = True
90
- break
89
+ ctx_copy = ctx.copy()
90
+ if await check_rule(event.ctx_api, rule, event, ctx_copy):
91
+ ctx |= ctx_copy
92
+ return True
93
+ return False
91
94
 
92
- ctx.clear()
93
- ctx.update(ctx_copy)
94
- return found
95
95
 
96
+ class NotRule(ABCRule[T]):
97
+ def __init__(self, rule: ABCRule[T]):
98
+ self.rule = rule
96
99
 
97
- class ABCMessageRule(ABCRule, ABC, require=[]):
98
- __event__ = EventScheme("message", Message)
100
+ async def check(self, event: Update, ctx: Context) -> bool:
101
+ ctx_copy = ctx.copy()
102
+ return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
103
+
104
+
105
+ class MessageRule(ABCRule[Message], ABC, requires=[]):
106
+ adapter = EventAdapter("message", Message)
99
107
 
100
108
  @abstractmethod
101
- async def check(self, message: Message, ctx: dict) -> bool:
109
+ async def check(self, message: Message, ctx: Context) -> bool:
102
110
  ...
111
+
112
+
113
+ __all__ = (
114
+ "ABCRule",
115
+ "AndRule",
116
+ "MessageRule",
117
+ "NotRule",
118
+ "OrRule",
119
+ "with_caching_translations",
120
+ )
@@ -0,0 +1,11 @@
1
+ from .abc import ABCAdapter
2
+ from .errors import AdapterError
3
+ from .event import EventAdapter
4
+ from .raw_update import RawUpdateAdapter
5
+
6
+ __all__ = (
7
+ "ABCAdapter",
8
+ "AdapterError",
9
+ "EventAdapter",
10
+ "RawUpdateAdapter",
11
+ )
@@ -0,0 +1,21 @@
1
+ import abc
2
+ import typing
3
+
4
+ from fntypes.result import Result
5
+
6
+ from telegrinder.api.abc import ABCAPI
7
+ from telegrinder.bot.cute_types import BaseCute
8
+ from telegrinder.bot.rules.adapter.errors import AdapterError
9
+ from telegrinder.model import Model
10
+
11
+ UpdateT = typing.TypeVar("UpdateT", bound=Model)
12
+ CuteT = typing.TypeVar("CuteT", bound=BaseCute)
13
+
14
+
15
+ class ABCAdapter(abc.ABC, typing.Generic[UpdateT, CuteT]):
16
+ @abc.abstractmethod
17
+ async def adapt(self, api: ABCAPI, update: UpdateT) -> Result[CuteT, AdapterError]:
18
+ pass
19
+
20
+
21
+ __all__ = ("ABCAdapter",)
@@ -0,0 +1,5 @@
1
+ class AdapterError(RuntimeError):
2
+ pass
3
+
4
+
5
+ __all__ = ("AdapterError",)
@@ -0,0 +1,43 @@
1
+ import typing
2
+
3
+ from fntypes.result import Error, Ok, Result
4
+
5
+ from telegrinder.api.abc import ABCAPI
6
+ from telegrinder.bot.cute_types import BaseCute
7
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
8
+ from telegrinder.bot.rules.adapter.errors import AdapterError
9
+ from telegrinder.msgspec_utils import Nothing
10
+ from telegrinder.types.objects import Model, Update
11
+
12
+ EventT = typing.TypeVar("EventT", bound=Model)
13
+ CuteT = typing.TypeVar("CuteT", bound=BaseCute)
14
+
15
+
16
+ class EventAdapter(ABCAdapter[Update, CuteT]):
17
+ def __init__(self, event_name: str, model: type[CuteT]):
18
+ self.event_name = event_name
19
+ self.model = model
20
+
21
+ def __repr__(self) -> str:
22
+ return "<{}: adapt Update.{} -> {}>".format(
23
+ self.__class__.__name__,
24
+ self.event_name,
25
+ self.model.__name__,
26
+ )
27
+
28
+ async def adapt(self, api: ABCAPI, update: Update) -> Result[CuteT, AdapterError]:
29
+ update_dct = update.to_dict()
30
+ if self.event_name not in update_dct:
31
+ return Error(
32
+ AdapterError(f"Update is not of event type {self.event_name!r}.")
33
+ )
34
+ if update_dct[self.event_name] is Nothing:
35
+ return Error(
36
+ AdapterError(f"Update is not an {self.event_name!r}.")
37
+ )
38
+ return Ok(
39
+ self.model.from_update(update_dct[self.event_name].unwrap(), bound_api=api)
40
+ )
41
+
42
+
43
+ __all__ = ("EventAdapter",)
@@ -0,0 +1,24 @@
1
+ from fntypes.result import Ok, Result
2
+
3
+ from telegrinder.api.abc import ABCAPI
4
+ from telegrinder.bot.cute_types.update import UpdateCute
5
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
6
+ from telegrinder.bot.rules.adapter.errors import AdapterError
7
+ from telegrinder.types.objects import Update
8
+
9
+
10
+ class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
11
+ def __repr__(self) -> str:
12
+ return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
13
+
14
+ async def adapt(
15
+ self,
16
+ api: ABCAPI,
17
+ update: Update,
18
+ ) -> Result[UpdateCute, AdapterError]:
19
+ if not isinstance(update, UpdateCute):
20
+ return Ok(UpdateCute.from_update(update, api))
21
+ return Ok(update)
22
+
23
+
24
+ __all__ = ("RawUpdateAdapter",)