telegrinder 1.0.0rc1__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.
Files changed (215) hide show
  1. telegrinder/__init__.py +258 -0
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +15 -0
  4. telegrinder/api/api.py +175 -0
  5. telegrinder/api/error.py +50 -0
  6. telegrinder/api/response.py +23 -0
  7. telegrinder/api/token.py +30 -0
  8. telegrinder/api/validators.py +30 -0
  9. telegrinder/bot/__init__.py +144 -0
  10. telegrinder/bot/bot.py +70 -0
  11. telegrinder/bot/cute_types/__init__.py +41 -0
  12. telegrinder/bot/cute_types/base.py +228 -0
  13. telegrinder/bot/cute_types/base.pyi +49 -0
  14. telegrinder/bot/cute_types/business_connection.py +9 -0
  15. telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
  16. telegrinder/bot/cute_types/callback_query.py +248 -0
  17. telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
  18. telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
  19. telegrinder/bot/cute_types/chat_join_request.py +59 -0
  20. telegrinder/bot/cute_types/chat_member_updated.py +158 -0
  21. telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
  22. telegrinder/bot/cute_types/inline_query.py +41 -0
  23. telegrinder/bot/cute_types/message.py +2809 -0
  24. telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
  25. telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
  26. telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
  27. telegrinder/bot/cute_types/poll.py +9 -0
  28. telegrinder/bot/cute_types/poll_answer.py +9 -0
  29. telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
  30. telegrinder/bot/cute_types/shipping_query.py +11 -0
  31. telegrinder/bot/cute_types/update.py +209 -0
  32. telegrinder/bot/cute_types/utils.py +141 -0
  33. telegrinder/bot/dispatch/__init__.py +99 -0
  34. telegrinder/bot/dispatch/abc.py +74 -0
  35. telegrinder/bot/dispatch/action.py +99 -0
  36. telegrinder/bot/dispatch/context.py +162 -0
  37. telegrinder/bot/dispatch/dispatch.py +362 -0
  38. telegrinder/bot/dispatch/handler/__init__.py +23 -0
  39. telegrinder/bot/dispatch/handler/abc.py +25 -0
  40. telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
  41. telegrinder/bot/dispatch/handler/base.py +34 -0
  42. telegrinder/bot/dispatch/handler/document_reply.py +43 -0
  43. telegrinder/bot/dispatch/handler/func.py +73 -0
  44. telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
  45. telegrinder/bot/dispatch/handler/message_reply.py +35 -0
  46. telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
  47. telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
  48. telegrinder/bot/dispatch/handler/video_reply.py +43 -0
  49. telegrinder/bot/dispatch/middleware/__init__.py +13 -0
  50. telegrinder/bot/dispatch/middleware/abc.py +112 -0
  51. telegrinder/bot/dispatch/middleware/box.py +32 -0
  52. telegrinder/bot/dispatch/middleware/filter.py +88 -0
  53. telegrinder/bot/dispatch/middleware/media_group.py +69 -0
  54. telegrinder/bot/dispatch/process.py +93 -0
  55. telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
  56. telegrinder/bot/dispatch/return_manager/abc.py +107 -0
  57. telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
  58. telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
  59. telegrinder/bot/dispatch/return_manager/message.py +34 -0
  60. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
  61. telegrinder/bot/dispatch/return_manager/utils.py +20 -0
  62. telegrinder/bot/dispatch/router/__init__.py +4 -0
  63. telegrinder/bot/dispatch/router/abc.py +15 -0
  64. telegrinder/bot/dispatch/router/base.py +154 -0
  65. telegrinder/bot/dispatch/view/__init__.py +15 -0
  66. telegrinder/bot/dispatch/view/abc.py +15 -0
  67. telegrinder/bot/dispatch/view/base.py +226 -0
  68. telegrinder/bot/dispatch/view/box.py +207 -0
  69. telegrinder/bot/dispatch/view/media_group.py +25 -0
  70. telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
  71. telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
  72. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
  73. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
  74. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
  75. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
  76. telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
  77. telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
  78. telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
  79. telegrinder/bot/polling/__init__.py +4 -0
  80. telegrinder/bot/polling/abc.py +25 -0
  81. telegrinder/bot/polling/error_handler.py +93 -0
  82. telegrinder/bot/polling/polling.py +167 -0
  83. telegrinder/bot/polling/utils.py +12 -0
  84. telegrinder/bot/rules/__init__.py +166 -0
  85. telegrinder/bot/rules/abc.py +150 -0
  86. telegrinder/bot/rules/button.py +20 -0
  87. telegrinder/bot/rules/callback_data.py +109 -0
  88. telegrinder/bot/rules/chat_join.py +28 -0
  89. telegrinder/bot/rules/chat_member_updated.py +145 -0
  90. telegrinder/bot/rules/command.py +137 -0
  91. telegrinder/bot/rules/enum_text.py +29 -0
  92. telegrinder/bot/rules/func.py +21 -0
  93. telegrinder/bot/rules/fuzzy.py +21 -0
  94. telegrinder/bot/rules/inline.py +45 -0
  95. telegrinder/bot/rules/integer.py +19 -0
  96. telegrinder/bot/rules/is_from.py +213 -0
  97. telegrinder/bot/rules/logic.py +22 -0
  98. telegrinder/bot/rules/magic.py +60 -0
  99. telegrinder/bot/rules/markup.py +51 -0
  100. telegrinder/bot/rules/media.py +13 -0
  101. telegrinder/bot/rules/mention.py +15 -0
  102. telegrinder/bot/rules/message_entities.py +37 -0
  103. telegrinder/bot/rules/node.py +43 -0
  104. telegrinder/bot/rules/payload.py +89 -0
  105. telegrinder/bot/rules/payment_invoice.py +14 -0
  106. telegrinder/bot/rules/regex.py +34 -0
  107. telegrinder/bot/rules/rule_enum.py +71 -0
  108. telegrinder/bot/rules/start.py +73 -0
  109. telegrinder/bot/rules/state.py +35 -0
  110. telegrinder/bot/rules/text.py +27 -0
  111. telegrinder/bot/rules/update.py +14 -0
  112. telegrinder/bot/scenario/__init__.py +5 -0
  113. telegrinder/bot/scenario/abc.py +16 -0
  114. telegrinder/bot/scenario/checkbox.py +183 -0
  115. telegrinder/bot/scenario/choice.py +44 -0
  116. telegrinder/client/__init__.py +11 -0
  117. telegrinder/client/abc.py +136 -0
  118. telegrinder/client/form_data.py +34 -0
  119. telegrinder/client/rnet.py +198 -0
  120. telegrinder/model.py +133 -0
  121. telegrinder/model.pyi +57 -0
  122. telegrinder/modules.py +1081 -0
  123. telegrinder/msgspec_utils/__init__.py +42 -0
  124. telegrinder/msgspec_utils/abc.py +16 -0
  125. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  126. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  127. telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
  128. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  129. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  130. telegrinder/msgspec_utils/decoder.py +388 -0
  131. telegrinder/msgspec_utils/encoder.py +204 -0
  132. telegrinder/msgspec_utils/json.py +15 -0
  133. telegrinder/msgspec_utils/tools.py +80 -0
  134. telegrinder/node/__init__.py +80 -0
  135. telegrinder/node/compose.py +193 -0
  136. telegrinder/node/nodes/__init__.py +96 -0
  137. telegrinder/node/nodes/attachment.py +169 -0
  138. telegrinder/node/nodes/callback_query.py +25 -0
  139. telegrinder/node/nodes/channel.py +97 -0
  140. telegrinder/node/nodes/command.py +33 -0
  141. telegrinder/node/nodes/error.py +43 -0
  142. telegrinder/node/nodes/event.py +70 -0
  143. telegrinder/node/nodes/file.py +39 -0
  144. telegrinder/node/nodes/global_node.py +66 -0
  145. telegrinder/node/nodes/i18n.py +110 -0
  146. telegrinder/node/nodes/me.py +26 -0
  147. telegrinder/node/nodes/message_entities.py +15 -0
  148. telegrinder/node/nodes/payload.py +84 -0
  149. telegrinder/node/nodes/reply_message.py +14 -0
  150. telegrinder/node/nodes/source.py +172 -0
  151. telegrinder/node/nodes/state_mutator.py +71 -0
  152. telegrinder/node/nodes/text.py +62 -0
  153. telegrinder/node/scope.py +88 -0
  154. telegrinder/node/utils.py +38 -0
  155. telegrinder/py.typed +0 -0
  156. telegrinder/rules.py +1 -0
  157. telegrinder/tools/__init__.py +183 -0
  158. telegrinder/tools/aio.py +147 -0
  159. telegrinder/tools/final.py +21 -0
  160. telegrinder/tools/formatting/__init__.py +85 -0
  161. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  162. telegrinder/tools/formatting/deep_links/links.py +468 -0
  163. telegrinder/tools/formatting/deep_links/parsing.py +88 -0
  164. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  165. telegrinder/tools/formatting/html.py +241 -0
  166. telegrinder/tools/fullname.py +82 -0
  167. telegrinder/tools/global_context/__init__.py +13 -0
  168. telegrinder/tools/global_context/abc.py +63 -0
  169. telegrinder/tools/global_context/builtin_context.py +45 -0
  170. telegrinder/tools/global_context/global_context.py +614 -0
  171. telegrinder/tools/input_file_directory.py +30 -0
  172. telegrinder/tools/keyboard/__init__.py +6 -0
  173. telegrinder/tools/keyboard/abc.py +84 -0
  174. telegrinder/tools/keyboard/base.py +108 -0
  175. telegrinder/tools/keyboard/button.py +181 -0
  176. telegrinder/tools/keyboard/data.py +31 -0
  177. telegrinder/tools/keyboard/keyboard.py +160 -0
  178. telegrinder/tools/keyboard/utils.py +95 -0
  179. telegrinder/tools/lifespan.py +188 -0
  180. telegrinder/tools/limited_dict.py +35 -0
  181. telegrinder/tools/loop_wrapper.py +271 -0
  182. telegrinder/tools/magic/__init__.py +29 -0
  183. telegrinder/tools/magic/annotations.py +172 -0
  184. telegrinder/tools/magic/descriptors.py +57 -0
  185. telegrinder/tools/magic/function.py +254 -0
  186. telegrinder/tools/magic/inspect.py +16 -0
  187. telegrinder/tools/magic/shortcut.py +107 -0
  188. telegrinder/tools/member_descriptor_proxy.py +95 -0
  189. telegrinder/tools/parse_mode.py +12 -0
  190. telegrinder/tools/serialization/__init__.py +5 -0
  191. telegrinder/tools/serialization/abc.py +34 -0
  192. telegrinder/tools/serialization/json_ser.py +60 -0
  193. telegrinder/tools/serialization/msgpack_ser.py +197 -0
  194. telegrinder/tools/serialization/utils.py +18 -0
  195. telegrinder/tools/singleton/__init__.py +4 -0
  196. telegrinder/tools/singleton/abc.py +14 -0
  197. telegrinder/tools/singleton/singleton.py +18 -0
  198. telegrinder/tools/state_mutator/__init__.py +4 -0
  199. telegrinder/tools/state_mutator/mutation.py +85 -0
  200. telegrinder/tools/state_storage/__init__.py +4 -0
  201. telegrinder/tools/state_storage/abc.py +38 -0
  202. telegrinder/tools/state_storage/memory.py +27 -0
  203. telegrinder/tools/strings.py +22 -0
  204. telegrinder/types/__init__.py +323 -0
  205. telegrinder/types/enums.py +754 -0
  206. telegrinder/types/input_file.py +51 -0
  207. telegrinder/types/methods.py +6143 -0
  208. telegrinder/types/methods_utils.py +66 -0
  209. telegrinder/types/objects.py +8184 -0
  210. telegrinder/types/webapp.py +129 -0
  211. telegrinder/verification_utils.py +35 -0
  212. telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
  213. telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
  214. telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
  215. telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,258 @@
1
+ """Modern visionary telegram bot framework.
2
+
3
+ * Type hinted & [type functional](https://github.com/timoniq/telegrinder/blob/dev/docs/tutorial/en/3_functional_bits.md)
4
+ * Customizable and extensible
5
+ * Fast models built on [msgspec](https://github.com/jcrist/msgspec)
6
+ * API client powered by fast [rnet](https://github.com/0x676e67/rnet) library
7
+ * Both low-level and high-level API
8
+ * Convenient [dependency injection](https://github.com/timoniq/telegrinder/blob/dev/docs/tutorial/en/5_nodes.md) via nodes
9
+
10
+ Basic example:
11
+
12
+ ```python
13
+ from telegrinder import API, Message, Telegrinder, Token
14
+ from telegrinder.modules import setup_logger
15
+ from telegrinder.rules import Text
16
+
17
+ setup_logger(level="INFO")
18
+ api = API(token=Token("123:token"))
19
+ bot = Telegrinder(api)
20
+
21
+
22
+ @bot.on.message(Text("/start"))
23
+ async def start(message: Message) -> None:
24
+ me = (await api.get_me()).unwrap()
25
+ await message.answer(f"Hello, {message.from_user.full_name}! I'm {me.full_name}.")
26
+
27
+
28
+ bot.run_forever()
29
+ ```
30
+ """
31
+
32
+ import typing
33
+
34
+ from .api import API, APIError, APIResponse, APIServerError, Token
35
+ from .bot import (
36
+ CALLBACK_QUERY_FOR_MESSAGE,
37
+ CALLBACK_QUERY_FROM_CHAT,
38
+ CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE,
39
+ MESSAGE_FROM_USER,
40
+ MESSAGE_FROM_USER_IN_CHAT,
41
+ MESSAGE_IN_CHAT,
42
+ ABCDispatch,
43
+ ABCHandler,
44
+ ABCMiddleware,
45
+ ABCPolling,
46
+ ABCReturnManager,
47
+ ABCRouter,
48
+ ABCRule,
49
+ ABCScenario,
50
+ ABCView,
51
+ AudioReplyHandler,
52
+ BaseCute,
53
+ BaseReturnManager,
54
+ BusinessConnectionCute,
55
+ BusinessMessagesDeletedCute,
56
+ CallbackQueryCute,
57
+ CallbackQueryReturnManager,
58
+ ChatBoostRemovedCute,
59
+ ChatBoostUpdatedCute,
60
+ ChatJoinRequestCute,
61
+ ChatMemberUpdatedCute,
62
+ Checkbox,
63
+ Choice,
64
+ ChosenInlineResultCute,
65
+ Context,
66
+ Dispatch,
67
+ DocumentReplyHandler,
68
+ ErrorView,
69
+ EventModelView,
70
+ EventView,
71
+ FilterMiddleware,
72
+ FuncHandler,
73
+ Hasher,
74
+ InlineQueryCute,
75
+ InlineQueryReturnManager,
76
+ MediaGroupMiddleware,
77
+ MediaGroupReplyHandler,
78
+ MediaGroupView,
79
+ MessageCute,
80
+ MessageReactionCountUpdatedCute,
81
+ MessageReactionUpdatedCute,
82
+ MessageReplyHandler,
83
+ MessageReturnManager,
84
+ MiddlewareBox,
85
+ PaidMediaPurchasedCute,
86
+ PhotoReplyHandler,
87
+ PollAnswerCute,
88
+ PollCute,
89
+ Polling,
90
+ PreCheckoutQueryCute,
91
+ PreCheckoutQueryReturnManager,
92
+ RawEventView,
93
+ Router,
94
+ ShippingQueryCute,
95
+ ShortState,
96
+ StickerReplyHandler,
97
+ Telegrinder,
98
+ UpdateCute,
99
+ VideoReplyHandler,
100
+ View,
101
+ ViewBox,
102
+ WaiterMachine,
103
+ action,
104
+ register_manager,
105
+ )
106
+ from .client import ABCClient, RnetClient
107
+ from .model import Model, field
108
+ from .modules import configure_dotenv, logger, setup_logger
109
+ from .tools.global_context import ABCGlobalContext, GlobalContext, TelegrinderContext
110
+ from .tools.input_file_directory import InputFileDirectory
111
+ from .tools.keyboard import (
112
+ ABCKeyboard,
113
+ Button,
114
+ InlineButton,
115
+ InlineKeyboard,
116
+ Keyboard,
117
+ RowButtons,
118
+ )
119
+ from .tools.lifespan import Lifespan
120
+ from .tools.loop_wrapper import DelayedTask, LoopWrapper
121
+ from .tools.parse_mode import ParseMode
122
+ from .tools.state_storage import ABCStateStorage, MemoryStateStorage, StateData
123
+
124
+ Update: typing.TypeAlias = UpdateCute
125
+ Message: typing.TypeAlias = MessageCute
126
+ PreCheckoutQuery: typing.TypeAlias = PreCheckoutQueryCute
127
+ ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
128
+ ChatMemberUpdated: typing.TypeAlias = ChatMemberUpdatedCute
129
+ CallbackQuery: typing.TypeAlias = CallbackQueryCute
130
+ InlineQuery: typing.TypeAlias = InlineQueryCute
131
+ ChosenInlineResult: typing.TypeAlias = ChosenInlineResultCute
132
+ ShippingQuery: typing.TypeAlias = ShippingQueryCute
133
+ Poll: typing.TypeAlias = PollCute
134
+ PollAnswer: typing.TypeAlias = PollAnswerCute
135
+ PaidMediaPurchased: typing.TypeAlias = PaidMediaPurchasedCute
136
+ ChatBoostRemoved: typing.TypeAlias = ChatBoostRemovedCute
137
+ ChatBoostUpdated: typing.TypeAlias = ChatBoostUpdatedCute
138
+ BusinessConnection: typing.TypeAlias = BusinessConnectionCute
139
+ BusinessMessagesDeleted: typing.TypeAlias = BusinessMessagesDeletedCute
140
+ MessageReactionCountUpdated: typing.TypeAlias = MessageReactionCountUpdatedCute
141
+ MessageReactionUpdated: typing.TypeAlias = MessageReactionUpdatedCute
142
+ Bot: typing.TypeAlias = Telegrinder
143
+
144
+
145
+ __all__ = (
146
+ "API",
147
+ "CALLBACK_QUERY_FOR_MESSAGE",
148
+ "CALLBACK_QUERY_FROM_CHAT",
149
+ "CALLBACK_QUERY_IN_CHAT_FOR_MESSAGE",
150
+ "MESSAGE_FROM_USER",
151
+ "MESSAGE_FROM_USER_IN_CHAT",
152
+ "MESSAGE_IN_CHAT",
153
+ "ABCClient",
154
+ "ABCDispatch",
155
+ "ABCGlobalContext",
156
+ "ABCHandler",
157
+ "ABCKeyboard",
158
+ "ABCMiddleware",
159
+ "ABCPolling",
160
+ "ABCReturnManager",
161
+ "ABCRouter",
162
+ "ABCRule",
163
+ "ABCScenario",
164
+ "ABCStateStorage",
165
+ "ABCView",
166
+ "APIError",
167
+ "APIResponse",
168
+ "APIServerError",
169
+ "AudioReplyHandler",
170
+ "BaseCute",
171
+ "BaseReturnManager",
172
+ "Bot",
173
+ "Button",
174
+ "CallbackQuery",
175
+ "CallbackQueryCute",
176
+ "CallbackQueryReturnManager",
177
+ "ChatBoostRemoved",
178
+ "ChatBoostUpdatedCute",
179
+ "ChatJoinRequest",
180
+ "ChatJoinRequestCute",
181
+ "ChatMemberUpdated",
182
+ "ChatMemberUpdatedCute",
183
+ "Checkbox",
184
+ "Choice",
185
+ "ChosenInlineResult",
186
+ "ChosenInlineResultCute",
187
+ "Context",
188
+ "DelayedTask",
189
+ "Dispatch",
190
+ "DocumentReplyHandler",
191
+ "ErrorView",
192
+ "EventModelView",
193
+ "EventView",
194
+ "FilterMiddleware",
195
+ "FuncHandler",
196
+ "GlobalContext",
197
+ "Hasher",
198
+ "InlineButton",
199
+ "InlineKeyboard",
200
+ "InlineQuery",
201
+ "InlineQueryCute",
202
+ "InlineQueryReturnManager",
203
+ "InputFileDirectory",
204
+ "Keyboard",
205
+ "Lifespan",
206
+ "LoopWrapper",
207
+ "MediaGroupMiddleware",
208
+ "MediaGroupReplyHandler",
209
+ "MediaGroupView",
210
+ "MemoryStateStorage",
211
+ "Message",
212
+ "MessageCute",
213
+ "MessageReactionCountUpdated",
214
+ "MessageReactionCountUpdatedCute",
215
+ "MessageReactionUpdated",
216
+ "MessageReactionUpdatedCute",
217
+ "MessageReplyHandler",
218
+ "MessageReturnManager",
219
+ "MiddlewareBox",
220
+ "Model",
221
+ "PaidMediaPurchased",
222
+ "PaidMediaPurchasedCute",
223
+ "ParseMode",
224
+ "PhotoReplyHandler",
225
+ "Poll",
226
+ "PollAnswer",
227
+ "PollAnswerCute",
228
+ "PollCute",
229
+ "Polling",
230
+ "PreCheckoutQuery",
231
+ "PreCheckoutQueryCute",
232
+ "PreCheckoutQueryReturnManager",
233
+ "RawEventView",
234
+ "RnetClient",
235
+ "Router",
236
+ "RowButtons",
237
+ "ShippingQuery",
238
+ "ShippingQueryCute",
239
+ "ShortState",
240
+ "StateData",
241
+ "StickerReplyHandler",
242
+ "Telegrinder",
243
+ "TelegrinderContext",
244
+ "Token",
245
+ "Update",
246
+ "UpdateCute",
247
+ "VideoReplyHandler",
248
+ "View",
249
+ "ViewBox",
250
+ "ViewBox",
251
+ "WaiterMachine",
252
+ "action",
253
+ "configure_dotenv",
254
+ "field",
255
+ "logger",
256
+ "register_manager",
257
+ "setup_logger",
258
+ )
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0rc1"
@@ -0,0 +1,15 @@
1
+ from telegrinder.api.api import API
2
+ from telegrinder.api.error import APIError, APIServerError, InvalidTokenError
3
+ from telegrinder.api.response import APIResponse
4
+ from telegrinder.api.token import Token
5
+ from telegrinder.api.validators import validate_token
6
+
7
+ __all__ = (
8
+ "API",
9
+ "APIError",
10
+ "APIResponse",
11
+ "APIServerError",
12
+ "InvalidTokenError",
13
+ "Token",
14
+ "validate_token",
15
+ )
telegrinder/api/api.py ADDED
@@ -0,0 +1,175 @@
1
+ import asyncio
2
+ import pathlib
3
+ import typing
4
+ from datetime import timedelta
5
+ from functools import cached_property, wraps
6
+ from http import HTTPStatus
7
+
8
+ import msgspec
9
+ from kungfu.library.misc import is_ok
10
+ from kungfu.library.monad.result import Error, Ok, Result
11
+
12
+ from telegrinder.api.error import APIError
13
+ from telegrinder.api.response import APIResponse
14
+ from telegrinder.api.token import Token
15
+ from telegrinder.client import ABCClient, RnetClient
16
+ from telegrinder.msgspec_utils import decoder
17
+ from telegrinder.types.methods import APIMethods
18
+
19
+ type Json = str | int | float | bool | list[Json] | dict[str, Json] | None
20
+ type Data = dict[str, typing.Any]
21
+ type Files = dict[str, tuple[str, bytes]]
22
+ type APIRequestMethod[T: API, **P, R] = typing.Callable[
23
+ typing.Concatenate[T, P],
24
+ typing.Coroutine[typing.Any, typing.Any, Result[R, APIError]],
25
+ ]
26
+
27
+
28
+ DEFAULT_MAX_RETRIES: typing.Final = 5
29
+ DEFAULT_TIMEOUT: typing.Final = timedelta(seconds=30)
30
+
31
+
32
+ def retryer[T: API, **P, R](func: APIRequestMethod[T, P, R], /) -> APIRequestMethod[T, P, R]:
33
+ @wraps(func)
34
+ async def wrapper(
35
+ self: T,
36
+ *args: P.args,
37
+ **kwargs: P.kwargs,
38
+ ) -> Result[typing.Any, APIError]:
39
+ retries_counter = 0
40
+
41
+ while True:
42
+ result = await func(self, *args, **kwargs)
43
+
44
+ if is_ok(result) or not self.retryer_is_enabled or retries_counter >= self.max_retries:
45
+ return result
46
+
47
+ if result.error.status_code == HTTPStatus.TOO_MANY_REQUESTS:
48
+ await asyncio.sleep(result.error.retry_after.unwrap_or(5.0))
49
+
50
+ elif result.error.status_code == HTTPStatus.INTERNAL_SERVER_ERROR and "restart" in result.error.error:
51
+ await asyncio.sleep(10.0)
52
+
53
+ elif result.error.migrate_to_chat_id:
54
+ kwargs["chat_id"] = result.error.migrate_to_chat_id.value
55
+
56
+ else:
57
+ return result
58
+
59
+ retries_counter += 1
60
+
61
+ return wrapper # type: ignore
62
+
63
+
64
+ class API(APIMethods):
65
+ """Bot API with available API methods and http client."""
66
+
67
+ http: ABCClient
68
+
69
+ API_URL = "https://api.telegram.org/"
70
+ API_FILE_URL = "https://api.telegram.org/file/"
71
+
72
+ def __init__(
73
+ self,
74
+ token: Token,
75
+ *,
76
+ http: ABCClient | None = None,
77
+ retryer: bool = True,
78
+ max_retries: int = DEFAULT_MAX_RETRIES,
79
+ ) -> None:
80
+ self.token = token
81
+ self.http = http or RnetClient()
82
+ self._retryer_is_enabled = retryer
83
+ self._max_retries = max_retries
84
+ super().__init__(api=self)
85
+
86
+ def __repr__(self) -> str:
87
+ return "<{}: id={}, http={!r}, max_retries={}>".format(
88
+ type(self).__name__,
89
+ self.id,
90
+ self.http,
91
+ self._max_retries,
92
+ )
93
+
94
+ @cached_property
95
+ def id(self) -> int:
96
+ return self.token.bot_id
97
+
98
+ @property
99
+ def request_url(self) -> str:
100
+ return self.API_URL + f"bot{self.token}/"
101
+
102
+ @property
103
+ def request_file_url(self) -> str:
104
+ return self.API_FILE_URL + f"bot{self.token}/"
105
+
106
+ @property
107
+ def retryer_is_enabled(self) -> bool:
108
+ return self._retryer_is_enabled
109
+
110
+ @property
111
+ def max_retries(self) -> int:
112
+ return self._max_retries
113
+
114
+ async def download_file(
115
+ self,
116
+ file_path: str | pathlib.Path,
117
+ timeout: int | float | timedelta = DEFAULT_TIMEOUT,
118
+ ) -> Result[bytes, APIError]:
119
+ response = await self.http.request(
120
+ url=f"{self.request_file_url}/{file_path}",
121
+ timeout=timeout,
122
+ )
123
+
124
+ if response.status.is_success:
125
+ return Ok(response.content)
126
+
127
+ error = decoder.decode(response.content, type=APIResponse)
128
+ return Error(APIError(code=error.error_code, error=error.description, data=error.parameters))
129
+
130
+ @retryer
131
+ async def request(
132
+ self,
133
+ method: str,
134
+ data: Data | None = None,
135
+ files: Files | None = None,
136
+ **kwargs: typing.Any,
137
+ ) -> Result[Json, APIError]:
138
+ """Request a `JSON` response using http method `POST` and passing data & files as `multipart`."""
139
+ response = await self.http.request_json(
140
+ url=self.request_url + method,
141
+ method="POST",
142
+ data=self.http.get_form(data=data, files=files),
143
+ **kwargs,
144
+ )
145
+
146
+ if response.get("ok", False) is True:
147
+ return Ok(response["result"])
148
+
149
+ return Error(
150
+ APIError(
151
+ code=response.get("error_code", 400),
152
+ error=response.get("description", "Something went wrong"),
153
+ data=response.get("parameters", {}),
154
+ ),
155
+ )
156
+
157
+ @retryer
158
+ async def request_raw(
159
+ self,
160
+ method: str,
161
+ data: Data | None = None,
162
+ files: Files | None = None,
163
+ **kwargs: typing.Any,
164
+ ) -> Result[msgspec.Raw, APIError]:
165
+ """Request a `raw` response using http method `POST` and passing data & files as `multipart`."""
166
+ response_bytes = await self.http.request_bytes(
167
+ url=self.request_url + method,
168
+ method="POST",
169
+ data=self.http.get_form(data=data, files=files),
170
+ **kwargs,
171
+ )
172
+ return decoder.decode(response_bytes, type=APIResponse).to_result()
173
+
174
+
175
+ __all__ = ("API",)
@@ -0,0 +1,50 @@
1
+ import typing
2
+ from functools import cached_property
3
+ from http import HTTPStatus
4
+
5
+ from kungfu.library.misc import from_optional
6
+ from kungfu.library.monad.option import Option
7
+
8
+
9
+ class ReprErrorMixin:
10
+ def __repr__(self) -> str:
11
+ return f"{type(self).__name__}: {self}"
12
+
13
+
14
+ class APIError(ReprErrorMixin, Exception):
15
+ def __init__(
16
+ self,
17
+ code: int,
18
+ error: str,
19
+ data: dict[str, typing.Any],
20
+ ) -> None:
21
+ self.code, self.error, self.parameters = code, error, data
22
+
23
+ @cached_property
24
+ def status_code(self) -> HTTPStatus:
25
+ return HTTPStatus(self.code)
26
+
27
+ @property
28
+ def retry_after(self) -> Option[int]:
29
+ return from_optional(self.parameters.get("retry_after"))
30
+
31
+ @property
32
+ def migrate_to_chat_id(self) -> Option[int]:
33
+ return from_optional(self.parameters.get("migrate_to_chat_id"))
34
+
35
+ def __str__(self) -> str:
36
+ return f"[{self.code}] ({self.status_code.name}) {self.error}"
37
+
38
+
39
+ class APIServerError(ReprErrorMixin, Exception):
40
+ def __init__(self, message: str, retry_after: int | None = None) -> None:
41
+ self.message = message
42
+ self.retry_after = retry_after
43
+ super().__init__(message)
44
+
45
+
46
+ class InvalidTokenError(BaseException):
47
+ pass
48
+
49
+
50
+ __all__ = ("APIError", "APIServerError", "InvalidTokenError")
@@ -0,0 +1,23 @@
1
+ import typing
2
+
3
+ import msgspec
4
+ from kungfu.library.monad.result import Error, Ok, Result
5
+
6
+ from telegrinder.api.error import APIError
7
+ from telegrinder.model import Model
8
+
9
+
10
+ class APIResponse(Model):
11
+ ok: bool = False
12
+ result: msgspec.Raw = msgspec.Raw(b"")
13
+ error_code: int = 400
14
+ description: str = "Something went wrong"
15
+ parameters: dict[str, typing.Any] = msgspec.field(default_factory=dict[str, typing.Any])
16
+
17
+ def to_result(self) -> Result[msgspec.Raw, APIError]:
18
+ if self.ok:
19
+ return Ok(self.result)
20
+ return Error(APIError(code=self.error_code, error=self.description, data=self.parameters))
21
+
22
+
23
+ __all__ = ("APIResponse",)
@@ -0,0 +1,30 @@
1
+ import typing
2
+ from functools import cached_property
3
+
4
+ from telegrinder.api.error import InvalidTokenError
5
+ from telegrinder.modules import take_env
6
+
7
+
8
+ class Token(str):
9
+ def __new__(cls, token: str, /) -> typing.Self:
10
+ if token.count(":") != 1 or not token.split(":")[0].isdigit():
11
+ raise InvalidTokenError("Invalid token format, it should look like 12345:ABCdef")
12
+ return super().__new__(cls, token)
13
+
14
+ def __repr__(self) -> str:
15
+ return f"<Token: {self.bot_id}:{self.token[:9]}...>"
16
+
17
+ @classmethod
18
+ def from_env(cls, var_name: str = "BOT_TOKEN") -> typing.Self:
19
+ return cls(take_env(var_name))
20
+
21
+ @cached_property
22
+ def token(self) -> str:
23
+ return self.split(":")[1]
24
+
25
+ @cached_property
26
+ def bot_id(self) -> int:
27
+ return int(self.split(":")[0])
28
+
29
+
30
+ __all__ = ("Token",)
@@ -0,0 +1,30 @@
1
+ from http import HTTPStatus
2
+
3
+ from kungfu import Error, Ok, Result
4
+
5
+ from telegrinder.api.api import API
6
+ from telegrinder.api.error import InvalidTokenError
7
+ from telegrinder.api.token import Token
8
+
9
+
10
+ async def validate_token(token: str, /) -> Result[Token, InvalidTokenError]:
11
+ try:
12
+ token = Token(token)
13
+ api = API(token)
14
+
15
+ match await api.get_me():
16
+ case Ok(_):
17
+ return Ok(token)
18
+ case Error(error):
19
+ return Error(
20
+ InvalidTokenError(
21
+ "Token seems to be invalid."
22
+ if error.status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.NOT_FOUND)
23
+ else f"Unknown error {error!r} while validating token, please try again later.",
24
+ ),
25
+ )
26
+ except InvalidTokenError as error:
27
+ return Error(error)
28
+
29
+
30
+ __all__ = ("validate_token",)