telegrinder 0.1.dev164__tar.gz → 0.1.dev165__tar.gz

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 (131) hide show
  1. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/PKG-INFO +1 -1
  2. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/pyproject.toml +1 -1
  3. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/__init__.py +36 -0
  4. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/bot.py +9 -0
  5. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/cute_types/message.py +1 -1
  6. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/return_manager/abc.py +6 -0
  7. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/polling/polling.py +14 -0
  8. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/markup.py +1 -1
  9. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/model.py +10 -0
  10. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/msgspec_utils.py +13 -2
  11. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/composer.py +1 -1
  12. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/__init__.py +2 -1
  13. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/formatting/html.py +6 -12
  14. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/formatting/links.py +12 -6
  15. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/formatting/spec_html_formats.py +4 -5
  16. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/global_context/global_context.py +2 -2
  17. telegrinder-0.1.dev165/telegrinder/tools/loop_wrapper/__init__.py +4 -0
  18. telegrinder-0.1.dev165/telegrinder/tools/loop_wrapper/abc.py +15 -0
  19. telegrinder-0.1.dev165/telegrinder/tools/loop_wrapper/loop_wrapper.py +178 -0
  20. telegrinder-0.1.dev165/telegrinder/verification_utils.py +31 -0
  21. telegrinder-0.1.dev164/telegrinder/tools/loop_wrapper/__init__.py +0 -4
  22. telegrinder-0.1.dev164/telegrinder/tools/loop_wrapper/abc.py +0 -18
  23. telegrinder-0.1.dev164/telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -132
  24. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/LICENSE +0 -0
  25. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/readme.md +0 -0
  26. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/api/__init__.py +0 -0
  27. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/api/abc.py +0 -0
  28. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/api/api.py +0 -0
  29. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/api/error.py +0 -0
  30. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/api/response.py +0 -0
  31. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/__init__.py +0 -0
  32. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/cute_types/__init__.py +0 -0
  33. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/cute_types/base.py +0 -0
  34. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/cute_types/callback_query.py +0 -0
  35. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/cute_types/inline_query.py +0 -0
  36. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/cute_types/update.py +0 -0
  37. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/cute_types/utils.py +0 -0
  38. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/__init__.py +0 -0
  39. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/abc.py +0 -0
  40. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/composition.py +0 -0
  41. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/context.py +0 -0
  42. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/dispatch.py +0 -0
  43. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/handler/__init__.py +0 -0
  44. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/handler/abc.py +0 -0
  45. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/handler/func.py +0 -0
  46. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/handler/message_reply.py +0 -0
  47. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  48. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/middleware/abc.py +0 -0
  49. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/process.py +0 -0
  50. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  51. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/return_manager/callback_query.py +0 -0
  52. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/return_manager/inline_query.py +0 -0
  53. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/return_manager/message.py +0 -0
  54. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/view/__init__.py +0 -0
  55. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/view/abc.py +0 -0
  56. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/view/box.py +0 -0
  57. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/view/callback_query.py +0 -0
  58. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/view/inline_query.py +0 -0
  59. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/view/message.py +0 -0
  60. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/waiter_machine/__init__.py +0 -0
  61. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/waiter_machine/machine.py +0 -0
  62. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/waiter_machine/middleware.py +0 -0
  63. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/dispatch/waiter_machine/short_state.py +0 -0
  64. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/polling/__init__.py +0 -0
  65. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/polling/abc.py +0 -0
  66. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/__init__.py +0 -0
  67. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/abc.py +0 -0
  68. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/adapter/__init__.py +0 -0
  69. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/adapter/abc.py +0 -0
  70. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/adapter/errors.py +0 -0
  71. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/adapter/event.py +0 -0
  72. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/adapter/raw_update.py +0 -0
  73. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/callback_data.py +0 -0
  74. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/command.py +0 -0
  75. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/enum_text.py +0 -0
  76. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/func.py +0 -0
  77. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/fuzzy.py +0 -0
  78. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/inline.py +0 -0
  79. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/integer.py +0 -0
  80. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/is_from.py +0 -0
  81. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/mention.py +0 -0
  82. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/message_entities.py +0 -0
  83. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/regex.py +0 -0
  84. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/rule_enum.py +0 -0
  85. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/start.py +0 -0
  86. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/rules/text.py +0 -0
  87. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/scenario/__init__.py +0 -0
  88. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/scenario/abc.py +0 -0
  89. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/scenario/checkbox.py +0 -0
  90. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/bot/scenario/choice.py +0 -0
  91. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/client/__init__.py +0 -0
  92. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/client/abc.py +0 -0
  93. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/client/aiohttp.py +0 -0
  94. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/modules.py +0 -0
  95. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/msgspec_json.py +0 -0
  96. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/__init__.py +0 -0
  97. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/attachment.py +0 -0
  98. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/base.py +0 -0
  99. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/container.py +0 -0
  100. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/message.py +0 -0
  101. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/rule.py +0 -0
  102. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/source.py +0 -0
  103. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/text.py +0 -0
  104. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/tools/__init__.py +0 -0
  105. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/tools/generator.py +0 -0
  106. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/node/update.py +0 -0
  107. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/rules.py +0 -0
  108. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/buttons.py +0 -0
  109. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/error_handler/__init__.py +0 -0
  110. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/error_handler/abc.py +0 -0
  111. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/error_handler/error.py +0 -0
  112. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/error_handler/error_handler.py +0 -0
  113. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/formatting/__init__.py +0 -0
  114. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/global_context/__init__.py +0 -0
  115. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/global_context/abc.py +0 -0
  116. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/global_context/telegrinder_ctx.py +0 -0
  117. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/i18n/__init__.py +0 -0
  118. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/i18n/base.py +0 -0
  119. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/i18n/middleware/__init__.py +0 -0
  120. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/i18n/middleware/base.py +0 -0
  121. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/i18n/simple.py +0 -0
  122. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/kb_set/__init__.py +0 -0
  123. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/kb_set/base.py +0 -0
  124. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/kb_set/yaml.py +0 -0
  125. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/keyboard.py +0 -0
  126. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/magic.py +0 -0
  127. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/tools/parse_mode.py +0 -0
  128. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/types/__init__.py +0 -0
  129. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/types/enums.py +0 -0
  130. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/types/methods.py +0 -0
  131. {telegrinder-0.1.dev164 → telegrinder-0.1.dev165}/telegrinder/types/objects.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: telegrinder
3
- Version: 0.1.dev164
3
+ Version: 0.1.dev165
4
4
  Summary: Framework for effective and reliable async telegram bot building.
5
5
  Home-page: https://github.com/timoniq/telegrinder
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "telegrinder"
3
- version = "0.1.dev164"
3
+ version = "0.1.dev165"
4
4
  description = "Framework for effective and reliable async telegram bot building."
5
5
  authors = ["timoniq <tesseradecades@mail.ru>"]
6
6
  maintainers = ["luwqz1 <howluwqz1@gmail.com>"]
@@ -1,3 +1,37 @@
1
+ """Telegrinder
2
+
3
+ Framework for effective and reliable telegram bot building.
4
+
5
+ * Type hinted
6
+ * Customizable and extensible
7
+ * Ready to use scenarios and rules
8
+ * Fast models built on msgspec
9
+ * Both low-level and high-level API
10
+
11
+ Basic example:
12
+
13
+ ```python
14
+ from telegrinder import API, Message, Telegrinder, Token
15
+ from telegrinder.modules import logger
16
+ from telegrinder.rules import Text
17
+
18
+ api = API(token=Token("123:token"))
19
+ bot = Telegrinder(api)
20
+ logger.set_level("INFO")
21
+
22
+
23
+ @bot.on.message(Text("/start"))
24
+ async def start(message: Message):
25
+ me = (await api.get_me()).unwrap()
26
+ await message.answer(
27
+ f"Hello, {message.from_user.full_name}! I'm {me.full_name}."
28
+ )
29
+
30
+
31
+ bot.run_forever()
32
+ ```
33
+ """
34
+
1
35
  import typing
2
36
 
3
37
  from .api import ABCAPI, API, APIError, APIResponse, Token
@@ -58,6 +92,7 @@ from .tools import (
58
92
  Keyboard,
59
93
  KeyboardSetBase,
60
94
  KeyboardSetYAML,
95
+ Lifespan,
61
96
  LoopWrapper,
62
97
  ParseMode,
63
98
  RowButtons,
@@ -123,6 +158,7 @@ __all__ = (
123
158
  "Keyboard",
124
159
  "KeyboardSetBase",
125
160
  "KeyboardSetYAML",
161
+ "Lifespan",
126
162
  "LoopWrapper",
127
163
  "Message",
128
164
  "MessageCute",
@@ -24,6 +24,15 @@ class Telegrinder(typing.Generic[DispatchT, PollingT, LoopWrapperT]):
24
24
  self.dispatch = typing.cast(DispatchT, dispatch or Dispatch())
25
25
  self.polling = typing.cast(PollingT, polling or Polling(api))
26
26
  self.loop_wrapper = typing.cast(LoopWrapperT, loop_wrapper or LoopWrapper())
27
+
28
+ def __repr__(self) -> str:
29
+ return "<{}: api={!r}, dispatch={!r}, polling={!r}, loop_wrapper={!r}>".format(
30
+ self.__class__.__name__,
31
+ self.api,
32
+ self.dispatch,
33
+ self.polling,
34
+ self.loop_wrapper,
35
+ )
27
36
 
28
37
  @property
29
38
  def on(self) -> DispatchT:
@@ -80,7 +80,7 @@ async def execute_method_answer(
80
80
  params["link_preview_options"] = compose_link_preview_options(
81
81
  **link_preview_options
82
82
  )
83
-
83
+
84
84
  result = await getattr(message.ctx_api, method_name)(**params)
85
85
  return result.map(
86
86
  lambda x: (
@@ -43,6 +43,12 @@ class ABCReturnManager(ABC, typing.Generic[EventT]):
43
43
 
44
44
 
45
45
  class BaseReturnManager(ABCReturnManager[EventT]):
46
+ def __repr__(self) -> str:
47
+ return "<{}: {}>".format(
48
+ self.__class__.__name__,
49
+ ", ".join(x.callback.__name__ + "=" + repr(x) for x in self.managers),
50
+ )
51
+
46
52
  @property
47
53
  def managers(self) -> list[Manager]:
48
54
  return [
@@ -33,6 +33,20 @@ class Polling(ABCPolling):
33
33
  self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
34
34
  self.offset = offset
35
35
  self._stop = False
36
+
37
+ def __repr__(self) -> str:
38
+ return (
39
+ "<{}: with api={!r}, stopped={}, offset={}, allowed_updates={!r}, "
40
+ "max_reconnetions={}, reconnection_timeout={}>"
41
+ ).format(
42
+ self.__class__.__name__,
43
+ self.api,
44
+ self._stop,
45
+ self.offset,
46
+ self.allowed_updates,
47
+ self.max_reconnetions,
48
+ self.reconnection_timeout,
49
+ )
36
50
 
37
51
  def get_allowed_updates(
38
52
  self,
@@ -24,7 +24,7 @@ def check_string(patterns: list[vbml.Pattern], s: str, ctx: Context) -> bool:
24
24
 
25
25
 
26
26
  class Markup(TextMessageRule):
27
- def __init__(self, patterns: PatternLike | list[PatternLike]):
27
+ def __init__(self, patterns: PatternLike | list[PatternLike], /):
28
28
  if not isinstance(patterns, list):
29
29
  patterns = [patterns]
30
30
  self.patterns = [
@@ -57,6 +57,10 @@ def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
57
57
 
58
58
 
59
59
  class Model(msgspec.Struct, **MODEL_CONFIG):
60
+ @classmethod
61
+ def from_bytes(cls, data: bytes) -> typing.Self:
62
+ return decoder.decode(data, type=cls)
63
+
60
64
  def to_dict(
61
65
  self,
62
66
  *,
@@ -76,6 +80,12 @@ class Model(msgspec.Struct, **MODEL_CONFIG):
76
80
  class DataConverter:
77
81
  files: dict[str, tuple[str, bytes]] = dataclasses.field(default_factory=lambda: {})
78
82
 
83
+ def __repr__(self) -> str:
84
+ return "<{}: {}>".format(
85
+ self.__class__.__name__,
86
+ ", ".join(f"{k}={v!r}" for k, v in self.converters),
87
+ )
88
+
79
89
  @property
80
90
  def converters(self) -> dict[type[typing.Any], typing.Callable[..., typing.Any]]:
81
91
  return {
@@ -51,8 +51,7 @@ def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, msgspec.Validation
51
51
  def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
52
52
  if obj is None:
53
53
  return Nothing
54
- generic_args = typing.get_args(tp)
55
- value_type: typing.Any | type[typing.Any] = typing.Any if not generic_args else generic_args[0]
54
+ value_type, = typing.get_args(tp) or (typing.Any,)
56
55
  return msgspec_convert({"value": obj}, fntypes.option.Some[value_type]).unwrap()
57
56
 
58
57
 
@@ -123,6 +122,12 @@ class Decoder:
123
122
  Variative: variative_dec_hook,
124
123
  datetime: lambda t, obj: t.fromtimestamp(obj),
125
124
  }
125
+
126
+ def __repr__(self) -> str:
127
+ return "<{}: dec_hooks={!r}>".format(
128
+ self.__class__.__name__,
129
+ self.dec_hooks,
130
+ )
126
131
 
127
132
  def add_dec_hook(self, t: T): # type: ignore
128
133
  def decorator(func: DecHook[T]) -> DecHook[T]:
@@ -216,6 +221,12 @@ class Encoder:
216
221
  datetime: lambda date: int(date.timestamp()),
217
222
  }
218
223
 
224
+ def __repr__(self) -> str:
225
+ return "<{}: enc_hooks={!r}>".format(
226
+ self.__class__.__name__,
227
+ self.enc_hooks,
228
+ )
229
+
219
230
  def add_dec_hook(self, t: type[T]):
220
231
  def decorator(func: EncHook[T]) -> EncHook[T]:
221
232
  encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
@@ -27,7 +27,7 @@ class NodeSession:
27
27
  self.generator = None
28
28
 
29
29
  def __repr__(self) -> str:
30
- return f"<NodeSession: {self.value}" + ("ACTIVE>" if self.generator else ">")
30
+ return f"<{self.__class__.__name__}: {self.value}" + ("ACTIVE>" if self.generator else ">")
31
31
 
32
32
 
33
33
  class NodeCollection:
@@ -66,7 +66,7 @@ from .keyboard import (
66
66
  Keyboard,
67
67
  RowButtons,
68
68
  )
69
- from .loop_wrapper import ABCLoopWrapper, DelayedTask, LoopWrapper
69
+ from .loop_wrapper import ABCLoopWrapper, DelayedTask, Lifespan, LoopWrapper
70
70
  from .magic import magic_bundle, resolve_arg_names
71
71
  from .parse_mode import ParseMode
72
72
 
@@ -99,6 +99,7 @@ __all__ = (
99
99
  "KeyboardSetBase",
100
100
  "KeyboardSetYAML",
101
101
  "Link",
102
+ "Lifespan",
102
103
  "LoopWrapper",
103
104
  "Mention",
104
105
  "ParseMode",
@@ -200,11 +200,8 @@ def bold(string: str) -> TagFormat:
200
200
  return TagFormat(string, tag="b")
201
201
 
202
202
 
203
- def channel_boost_link(channel_username: str, string: str | None = None):
204
- return link(
205
- get_channel_boost_link(channel_username),
206
- string or f"t.me/{channel_username}?boost",
207
- )
203
+ def channel_boost_link(channel_id: str | int, string: str | None = None):
204
+ return link(get_channel_boost_link(channel_id), string)
208
205
 
209
206
 
210
207
  def code_inline(string: str) -> TagFormat:
@@ -234,15 +231,12 @@ def spoiler(string: str) -> TagFormat:
234
231
  return TagFormat(string, tag="tg-spoiler")
235
232
 
236
233
 
237
- def start_bot_link(bot_username: str, data: str, string: str | None = None) -> TagFormat:
238
- return link(
239
- get_start_bot_link(bot_username, data),
240
- string or f"t.me/{bot_username}?start={data}"
241
- )
234
+ def start_bot_link(bot_id: str | int, data: str, string: str | None = None) -> TagFormat:
235
+ return link(get_start_bot_link(bot_id, data), string)
242
236
 
243
237
 
244
- def start_group_link(bot_username: str, data: str, string: str | None = None) -> TagFormat:
245
- return link(get_start_group_link(bot_username, data), string)
238
+ def start_group_link(bot_id: str | int, data: str, string: str | None = None) -> TagFormat:
239
+ return link(get_start_group_link(bot_id, data), string)
246
240
 
247
241
 
248
242
  def strike(string: str) -> TagFormat:
@@ -6,16 +6,22 @@ def get_resolve_domain_link(username: str) -> str:
6
6
  return f"tg://resolve?domain={username}"
7
7
 
8
8
 
9
- def get_start_bot_link(bot_username: str, data: str) -> str:
10
- return get_resolve_domain_link(bot_username) + f"&start={data}"
9
+ def get_start_bot_link(bot_id: str | int, data: str) -> str:
10
+ if isinstance(bot_id, int):
11
+ return get_mention_link(bot_id) + f"&start={data}"
12
+ return get_resolve_domain_link(bot_id) + f"&start={data}"
11
13
 
12
14
 
13
- def get_start_group_link(bot_username: str, data: str) -> str:
14
- return get_resolve_domain_link(bot_username) + f"&startgroup={data}"
15
+ def get_start_group_link(bot_id: str | int, data: str) -> str:
16
+ if isinstance(bot_id, int):
17
+ return get_mention_link(bot_id) + f"&startgroup={data}"
18
+ return get_resolve_domain_link(bot_id) + f"&startgroup={data}"
15
19
 
16
20
 
17
- def get_channel_boost_link(channel_username: str) -> str:
18
- return get_resolve_domain_link(channel_username) + "&boost"
21
+ def get_channel_boost_link(channel_id: str | int) -> str:
22
+ if isinstance(channel_id, int):
23
+ return get_mention_link(channel_id) + "&boost"
24
+ return get_resolve_domain_link(channel_id) + "&boost"
19
25
 
20
26
 
21
27
  def get_invite_chat_link(invite_link: str) -> str:
@@ -3,14 +3,13 @@ import typing
3
3
 
4
4
  from telegrinder.types.enums import ProgrammingLanguage
5
5
 
6
- SpecialFormat = typing.Union[
6
+ SpecialFormat: typing.TypeAlias = typing.Union[
7
7
  "ChannelBoostLink",
8
8
  "InviteChatLink",
9
9
  "Link",
10
10
  "Mention",
11
11
  "PreCode",
12
12
  "ResolveDomain",
13
- "SpecialFormat",
14
13
  "StartBotLink",
15
14
  "StartGroupLink",
16
15
  "TgEmoji",
@@ -38,7 +37,7 @@ class BaseSpecFormat:
38
37
  class ChannelBoostLink(BaseSpecFormat):
39
38
  __formatter_name__ = "channel_boost_link"
40
39
 
41
- channel_username: str
40
+ channel_id: str | int
42
41
  string: str | None = None
43
42
 
44
43
 
@@ -86,7 +85,7 @@ class TgEmoji(BaseSpecFormat):
86
85
  class StartBotLink(BaseSpecFormat):
87
86
  __formatter_name__ = "start_bot_link"
88
87
 
89
- bot_username: str
88
+ bot_id: str | int
90
89
  data: str
91
90
  string: str | None
92
91
 
@@ -95,7 +94,7 @@ class StartBotLink(BaseSpecFormat):
95
94
  class StartGroupLink(BaseSpecFormat):
96
95
  __formatter_name__ = "start_group_link"
97
96
 
98
- bot_username: str
97
+ bot_id: str | int
99
98
  data: str
100
99
  string: str | None = None
101
100
 
@@ -199,8 +199,8 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
199
199
  self.set_context_variables(variables)
200
200
 
201
201
  def __repr__(self) -> str:
202
- return "<{!r} -> ({})>".format(
203
- f"{self.__class__.__name__}@{self.ctx_name}",
202
+ return "<{} -> ({})>".format(
203
+ f"{self.__class__.__name__}@{self.ctx_name!r}",
204
204
  ", ".join(repr(var) for var in self),
205
205
  )
206
206
 
@@ -0,0 +1,4 @@
1
+ from .abc import ABCLoopWrapper
2
+ from .loop_wrapper import DelayedTask, Lifespan, LoopWrapper
3
+
4
+ __all__ = ("ABCLoopWrapper", "DelayedTask", "Lifespan", "LoopWrapper")
@@ -0,0 +1,15 @@
1
+ import typing
2
+ from abc import ABC, abstractmethod
3
+
4
+
5
+ class ABCLoopWrapper(ABC):
6
+ @abstractmethod
7
+ def add_task(self, task: typing.Any) -> None:
8
+ pass
9
+
10
+ @abstractmethod
11
+ def run_event_loop(self) -> None:
12
+ pass
13
+
14
+
15
+ __all__ = ("ABCLoopWrapper",)
@@ -0,0 +1,178 @@
1
+ import asyncio
2
+ import contextlib
3
+ import dataclasses
4
+ import typing
5
+
6
+ from telegrinder.modules import logger
7
+
8
+ from .abc import ABCLoopWrapper
9
+
10
+ T = typing.TypeVar("T")
11
+ P = typing.ParamSpec("P")
12
+ CoroFunc = typing.TypeVar("CoroFunc", bound="CoroutineFunc")
13
+
14
+ CoroutineTask: typing.TypeAlias = typing.Coroutine[typing.Any, typing.Any, T]
15
+ CoroutineFunc: typing.TypeAlias = typing.Callable[P, CoroutineTask[T]]
16
+ Task: typing.TypeAlias = typing.Union[CoroutineFunc, CoroutineTask, "DelayedTask"]
17
+
18
+
19
+ def run_tasks(tasks: list[CoroutineTask[typing.Any]], loop: asyncio.AbstractEventLoop) -> None:
20
+ while len(tasks) != 0:
21
+ loop.run_until_complete(tasks.pop(0))
22
+
23
+
24
+ def to_coroutine_task(task: Task) -> CoroutineTask:
25
+ if asyncio.iscoroutinefunction(task) or isinstance(task, DelayedTask):
26
+ task = task()
27
+ elif not asyncio.iscoroutine(task):
28
+ raise TypeError("Task should be coroutine or coroutine function.")
29
+ return task
30
+
31
+
32
+ @dataclasses.dataclass
33
+ class DelayedTask(typing.Generic[CoroFunc]):
34
+ handler: CoroFunc
35
+ seconds: float
36
+ repeat: bool = dataclasses.field(default=False, kw_only=True)
37
+ _cancelled: bool = dataclasses.field(default=False, init=False, repr=False)
38
+
39
+ @property
40
+ def is_cancelled(self) -> bool:
41
+ return self._cancelled
42
+
43
+ async def __call__(self, *args, **kwargs) -> None:
44
+ while not self.is_cancelled:
45
+ await asyncio.sleep(self.seconds)
46
+ if self.is_cancelled:
47
+ break
48
+ await self.handler(*args, **kwargs)
49
+ if not self.repeat:
50
+ break
51
+
52
+ def cancel(self) -> None:
53
+ if not self._cancelled:
54
+ self._cancelled = True
55
+
56
+
57
+ @dataclasses.dataclass(kw_only=True)
58
+ class Lifespan:
59
+ startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
60
+ shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
61
+
62
+ def on_startup(self, task_or_func: Task) -> Task:
63
+ task_or_func = to_coroutine_task(task_or_func)
64
+ self.startup_tasks.append(task_or_func)
65
+ return task_or_func
66
+
67
+ def on_shutdown(self, task_or_func: Task) -> Task:
68
+ task_or_func = to_coroutine_task(task_or_func)
69
+ self.shutdown_tasks.append(task_or_func)
70
+ return task_or_func
71
+
72
+ def start(self, loop: asyncio.AbstractEventLoop) -> None:
73
+ run_tasks(self.startup_tasks, loop)
74
+
75
+ def shutdown(self, loop: asyncio.AbstractEventLoop) -> None:
76
+ run_tasks(self.shutdown_tasks, loop)
77
+
78
+
79
+ class LoopWrapper(ABCLoopWrapper):
80
+ def __init__(
81
+ self,
82
+ *,
83
+ tasks: list[CoroutineTask[typing.Any]] | None = None,
84
+ lifespan: Lifespan | None = None,
85
+ ) -> None:
86
+ self.tasks: list[CoroutineTask[typing.Any]] = tasks or []
87
+ self.lifespan = lifespan or Lifespan()
88
+ self._loop = asyncio.new_event_loop()
89
+
90
+ def __repr__(self) -> str:
91
+ return "<{}: loop={!r} with tasks={!r}, lifespan={!r}>".format(
92
+ self.__class__.__name__,
93
+ self._loop,
94
+ self.tasks,
95
+ self.lifespan,
96
+ )
97
+
98
+ def run_event_loop(self) -> None:
99
+ if not self.tasks:
100
+ logger.warning("You run loop with 0 tasks!")
101
+
102
+ self.lifespan.start(self._loop)
103
+ run_tasks(self.tasks, self._loop)
104
+ tasks = asyncio.all_tasks(self._loop)
105
+
106
+ try:
107
+ while tasks:
108
+ tasks_results, _ = self._loop.run_until_complete(
109
+ asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION),
110
+ )
111
+ for task_result in tasks_results:
112
+ try:
113
+ task_result.result()
114
+ except BaseException as ex:
115
+ logger.exception(ex)
116
+ tasks = asyncio.all_tasks(self._loop)
117
+ except KeyboardInterrupt:
118
+ print() # blank print for ^C
119
+ logger.info("Caught KeyboardInterrupt, cancellation...")
120
+ self.complete_tasks(tasks)
121
+ finally:
122
+ self.lifespan.shutdown(self._loop)
123
+ if self._loop.is_running():
124
+ self._loop.close()
125
+
126
+ def add_task(self, task: Task) -> None:
127
+ task = to_coroutine_task(task)
128
+
129
+ if self._loop and self._loop.is_running():
130
+ self._loop.create_task(task)
131
+ else:
132
+ self.tasks.append(task)
133
+
134
+ def complete_tasks(self, tasks: set[asyncio.Task[typing.Any]]) -> None:
135
+ tasks = tasks | asyncio.all_tasks(self._loop)
136
+ task_to_cancel = asyncio.gather(*tasks, return_exceptions=True)
137
+ task_to_cancel.cancel()
138
+ with contextlib.suppress(asyncio.CancelledError):
139
+ self._loop.run_until_complete(task_to_cancel)
140
+
141
+ def timer(
142
+ self,
143
+ *,
144
+ days: int = 0,
145
+ hours: int = 0,
146
+ minutes: int = 0,
147
+ seconds: float = 0,
148
+ ):
149
+ seconds += minutes * 60
150
+ seconds += hours * 60 * 60
151
+ seconds += days * 24 * 60 * 60
152
+
153
+ def decorator(func: CoroFunc) -> CoroFunc:
154
+ self.add_task(DelayedTask(func, seconds, repeat=False))
155
+ return func
156
+
157
+ return decorator
158
+
159
+ def interval(
160
+ self,
161
+ *,
162
+ days: int = 0,
163
+ hours: int = 0,
164
+ minutes: int = 0,
165
+ seconds: float = 0,
166
+ ):
167
+ seconds += minutes * 60
168
+ seconds += hours * 60 * 60
169
+ seconds += days * 24 * 60 * 60
170
+
171
+ def decorator(func: CoroFunc) -> CoroFunc:
172
+ self.add_task(DelayedTask(func, seconds, repeat=True))
173
+ return func
174
+
175
+ return decorator
176
+
177
+
178
+ __all__ = ("DelayedTask", "Lifespan", "LoopWrapper", "to_coroutine_task")
@@ -0,0 +1,31 @@
1
+ import hashlib
2
+ import hmac
3
+ import typing
4
+
5
+ from telegrinder.api.abc import Token
6
+
7
+
8
+ def verify_webapp_request(secret_token: str, request_headers: typing.Mapping[str, typing.Any]) -> bool:
9
+ """Verifies update request is from telegram."""
10
+
11
+ return request_headers.get("X-Telegram-Bot-Api-Secret-Token") == secret_token
12
+
13
+
14
+ def webapp_validate_request(
15
+ bot_token: Token,
16
+ request_query_params: typing.Mapping[str, typing.Any],
17
+ ) -> bool:
18
+ """Verifies authentity of webapp request by counting hash of its parameters."""
19
+
20
+ items = sorted(request_query_params.items(), key=lambda kv: kv[0])
21
+ data_check_string = "\n".join(f"{k}={param}" for k, param in items if k != "hash")
22
+ secret = hmac.new(
23
+ "WebAppData".encode(),
24
+ bot_token.encode(),
25
+ hashlib.sha256,
26
+ ).digest()
27
+ data_chk = hmac.new(secret, data_check_string.encode(), hashlib.sha256)
28
+ return data_chk.hexdigest() == request_query_params.get("hash")
29
+
30
+
31
+ __all__ = ("verify_webapp_request", "webapp_validate_request")
@@ -1,4 +0,0 @@
1
- from .abc import ABCLoopWrapper
2
- from .loop_wrapper import DelayedTask, LoopWrapper
3
-
4
- __all__ = ("ABCLoopWrapper", "DelayedTask", "LoopWrapper")
@@ -1,18 +0,0 @@
1
- import typing
2
- from abc import ABC, abstractmethod
3
-
4
- CoroutineTask: typing.TypeAlias = typing.Coroutine[typing.Any, typing.Any, typing.Any]
5
- CoroutineFunc: typing.TypeAlias = typing.Callable[..., CoroutineTask]
6
-
7
-
8
- class ABCLoopWrapper(ABC):
9
- @abstractmethod
10
- def add_task(self, task: CoroutineFunc | CoroutineTask) -> None:
11
- pass
12
-
13
- @abstractmethod
14
- def run_event_loop(self) -> None:
15
- pass
16
-
17
-
18
- __all__ = ("ABCLoopWrapper",)
@@ -1,132 +0,0 @@
1
- import asyncio
2
- import contextlib
3
- import dataclasses
4
- import typing
5
-
6
- from telegrinder.modules import logger
7
-
8
- from .abc import ABCLoopWrapper, CoroutineFunc, CoroutineTask
9
-
10
-
11
- @dataclasses.dataclass
12
- class DelayedTask:
13
- handler: CoroutineFunc
14
- seconds: float
15
- repeat: bool = dataclasses.field(default=False, kw_only=True)
16
- _cancelled: bool = dataclasses.field(default=False, init=False, repr=False)
17
-
18
- @property
19
- def is_cancelled(self) -> bool:
20
- return self._cancelled
21
-
22
- async def __call__(self, *args, **kwargs) -> None:
23
- while not self.is_cancelled:
24
- await asyncio.sleep(self.seconds)
25
- if self.is_cancelled:
26
- break
27
- await self.handler(*args, **kwargs)
28
- if not self.repeat:
29
- break
30
-
31
- def cancel(self) -> None:
32
- if not self._cancelled:
33
- self._cancelled = True
34
-
35
-
36
- class LoopWrapper(ABCLoopWrapper):
37
- def __init__(self, tasks: list[CoroutineTask] | None = None):
38
- self.on_startup: list[CoroutineTask] = []
39
- self.on_shutdown: list[CoroutineTask] = []
40
- self.tasks = tasks or []
41
- self._loop = asyncio.new_event_loop()
42
-
43
- def run_event_loop(self) -> None:
44
- if not self.tasks:
45
- logger.warning("You run loop with 0 tasks!")
46
-
47
- for startup_task in self.on_startup:
48
- self._loop.run_until_complete(startup_task)
49
- for task in self.tasks:
50
- self._loop.create_task(task)
51
-
52
- self.tasks.clear()
53
- tasks = asyncio.all_tasks(self._loop)
54
- try:
55
- while tasks:
56
- tasks_results, _ = self._loop.run_until_complete(
57
- asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
58
- )
59
- for task_result in tasks_results:
60
- try:
61
- task_result.result()
62
- except BaseException as ex:
63
- logger.exception(ex)
64
- tasks = asyncio.all_tasks(self._loop)
65
- except KeyboardInterrupt:
66
- print() # blank print for ^C
67
- logger.info("KeyboardInterrupt")
68
- self.complete_tasks(tasks)
69
- finally:
70
- for shutdown_task in self.on_shutdown:
71
- self._loop.run_until_complete(shutdown_task)
72
- if self._loop.is_running():
73
- self._loop.close()
74
-
75
- def add_task(self, task: CoroutineFunc | CoroutineTask | DelayedTask):
76
- if asyncio.iscoroutinefunction(task) or isinstance(task, DelayedTask):
77
- task = task()
78
- elif not asyncio.iscoroutine(task):
79
- raise TypeError("Task should be coroutine or coroutine function.")
80
-
81
- if self._loop and self._loop.is_running():
82
- self._loop.create_task(task)
83
- else:
84
- self.tasks.append(task)
85
-
86
- def complete_tasks(self, tasks: set[asyncio.Task[typing.Any]]) -> None:
87
- tasks = tasks | asyncio.all_tasks(self._loop)
88
- task_to_cancel = asyncio.gather(*tasks, return_exceptions=True)
89
- task_to_cancel.cancel()
90
- with contextlib.suppress(asyncio.CancelledError):
91
- self._loop.run_until_complete(task_to_cancel)
92
-
93
- def timer(
94
- self,
95
- *,
96
- days: int = 0,
97
- hours: int = 0,
98
- minutes: int = 0,
99
- seconds: float = 0,
100
- ) -> typing.Callable[[typing.Callable], DelayedTask]:
101
- seconds += minutes * 60
102
- seconds += hours * 60 * 60
103
- seconds += days * 24 * 60 * 60
104
-
105
- def decorator(func: typing.Callable) -> DelayedTask:
106
- delayed_task = DelayedTask(func, seconds, repeat=False)
107
- self.add_task(delayed_task)
108
- return delayed_task
109
-
110
- return decorator
111
-
112
- def interval(
113
- self,
114
- *,
115
- days: int = 0,
116
- hours: int = 0,
117
- minutes: int = 0,
118
- seconds: float = 0,
119
- ) -> typing.Callable[[typing.Callable], DelayedTask]:
120
- seconds += minutes * 60
121
- seconds += hours * 60 * 60
122
- seconds += days * 24 * 60 * 60
123
-
124
- def decorator(func: typing.Callable) -> DelayedTask:
125
- delayed_task = DelayedTask(func, seconds, repeat=True)
126
- self.add_task(delayed_task)
127
- return delayed_task
128
-
129
- return decorator
130
-
131
-
132
- __all__ = ("DelayedTask", "LoopWrapper")