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.
- telegrinder/__init__.py +258 -0
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +15 -0
- telegrinder/api/api.py +175 -0
- telegrinder/api/error.py +50 -0
- telegrinder/api/response.py +23 -0
- telegrinder/api/token.py +30 -0
- telegrinder/api/validators.py +30 -0
- telegrinder/bot/__init__.py +144 -0
- telegrinder/bot/bot.py +70 -0
- telegrinder/bot/cute_types/__init__.py +41 -0
- telegrinder/bot/cute_types/base.py +228 -0
- telegrinder/bot/cute_types/base.pyi +49 -0
- telegrinder/bot/cute_types/business_connection.py +9 -0
- telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
- telegrinder/bot/cute_types/callback_query.py +248 -0
- telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
- telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
- telegrinder/bot/cute_types/chat_join_request.py +59 -0
- telegrinder/bot/cute_types/chat_member_updated.py +158 -0
- telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
- telegrinder/bot/cute_types/inline_query.py +41 -0
- telegrinder/bot/cute_types/message.py +2809 -0
- telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
- telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
- telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
- telegrinder/bot/cute_types/poll.py +9 -0
- telegrinder/bot/cute_types/poll_answer.py +9 -0
- telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
- telegrinder/bot/cute_types/shipping_query.py +11 -0
- telegrinder/bot/cute_types/update.py +209 -0
- telegrinder/bot/cute_types/utils.py +141 -0
- telegrinder/bot/dispatch/__init__.py +99 -0
- telegrinder/bot/dispatch/abc.py +74 -0
- telegrinder/bot/dispatch/action.py +99 -0
- telegrinder/bot/dispatch/context.py +162 -0
- telegrinder/bot/dispatch/dispatch.py +362 -0
- telegrinder/bot/dispatch/handler/__init__.py +23 -0
- telegrinder/bot/dispatch/handler/abc.py +25 -0
- telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
- telegrinder/bot/dispatch/handler/base.py +34 -0
- telegrinder/bot/dispatch/handler/document_reply.py +43 -0
- telegrinder/bot/dispatch/handler/func.py +73 -0
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +35 -0
- telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
- telegrinder/bot/dispatch/handler/video_reply.py +43 -0
- telegrinder/bot/dispatch/middleware/__init__.py +13 -0
- telegrinder/bot/dispatch/middleware/abc.py +112 -0
- telegrinder/bot/dispatch/middleware/box.py +32 -0
- telegrinder/bot/dispatch/middleware/filter.py +88 -0
- telegrinder/bot/dispatch/middleware/media_group.py +69 -0
- telegrinder/bot/dispatch/process.py +93 -0
- telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
- telegrinder/bot/dispatch/return_manager/abc.py +107 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +34 -0
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/utils.py +20 -0
- telegrinder/bot/dispatch/router/__init__.py +4 -0
- telegrinder/bot/dispatch/router/abc.py +15 -0
- telegrinder/bot/dispatch/router/base.py +154 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -0
- telegrinder/bot/dispatch/view/abc.py +15 -0
- telegrinder/bot/dispatch/view/base.py +226 -0
- telegrinder/bot/dispatch/view/box.py +207 -0
- telegrinder/bot/dispatch/view/media_group.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
- telegrinder/bot/polling/__init__.py +4 -0
- telegrinder/bot/polling/abc.py +25 -0
- telegrinder/bot/polling/error_handler.py +93 -0
- telegrinder/bot/polling/polling.py +167 -0
- telegrinder/bot/polling/utils.py +12 -0
- telegrinder/bot/rules/__init__.py +166 -0
- telegrinder/bot/rules/abc.py +150 -0
- telegrinder/bot/rules/button.py +20 -0
- telegrinder/bot/rules/callback_data.py +109 -0
- telegrinder/bot/rules/chat_join.py +28 -0
- telegrinder/bot/rules/chat_member_updated.py +145 -0
- telegrinder/bot/rules/command.py +137 -0
- telegrinder/bot/rules/enum_text.py +29 -0
- telegrinder/bot/rules/func.py +21 -0
- telegrinder/bot/rules/fuzzy.py +21 -0
- telegrinder/bot/rules/inline.py +45 -0
- telegrinder/bot/rules/integer.py +19 -0
- telegrinder/bot/rules/is_from.py +213 -0
- telegrinder/bot/rules/logic.py +22 -0
- telegrinder/bot/rules/magic.py +60 -0
- telegrinder/bot/rules/markup.py +51 -0
- telegrinder/bot/rules/media.py +13 -0
- telegrinder/bot/rules/mention.py +15 -0
- telegrinder/bot/rules/message_entities.py +37 -0
- telegrinder/bot/rules/node.py +43 -0
- telegrinder/bot/rules/payload.py +89 -0
- telegrinder/bot/rules/payment_invoice.py +14 -0
- telegrinder/bot/rules/regex.py +34 -0
- telegrinder/bot/rules/rule_enum.py +71 -0
- telegrinder/bot/rules/start.py +73 -0
- telegrinder/bot/rules/state.py +35 -0
- telegrinder/bot/rules/text.py +27 -0
- telegrinder/bot/rules/update.py +14 -0
- telegrinder/bot/scenario/__init__.py +5 -0
- telegrinder/bot/scenario/abc.py +16 -0
- telegrinder/bot/scenario/checkbox.py +183 -0
- telegrinder/bot/scenario/choice.py +44 -0
- telegrinder/client/__init__.py +11 -0
- telegrinder/client/abc.py +136 -0
- telegrinder/client/form_data.py +34 -0
- telegrinder/client/rnet.py +198 -0
- telegrinder/model.py +133 -0
- telegrinder/model.pyi +57 -0
- telegrinder/modules.py +1081 -0
- telegrinder/msgspec_utils/__init__.py +42 -0
- telegrinder/msgspec_utils/abc.py +16 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +388 -0
- telegrinder/msgspec_utils/encoder.py +204 -0
- telegrinder/msgspec_utils/json.py +15 -0
- telegrinder/msgspec_utils/tools.py +80 -0
- telegrinder/node/__init__.py +80 -0
- telegrinder/node/compose.py +193 -0
- telegrinder/node/nodes/__init__.py +96 -0
- telegrinder/node/nodes/attachment.py +169 -0
- telegrinder/node/nodes/callback_query.py +25 -0
- telegrinder/node/nodes/channel.py +97 -0
- telegrinder/node/nodes/command.py +33 -0
- telegrinder/node/nodes/error.py +43 -0
- telegrinder/node/nodes/event.py +70 -0
- telegrinder/node/nodes/file.py +39 -0
- telegrinder/node/nodes/global_node.py +66 -0
- telegrinder/node/nodes/i18n.py +110 -0
- telegrinder/node/nodes/me.py +26 -0
- telegrinder/node/nodes/message_entities.py +15 -0
- telegrinder/node/nodes/payload.py +84 -0
- telegrinder/node/nodes/reply_message.py +14 -0
- telegrinder/node/nodes/source.py +172 -0
- telegrinder/node/nodes/state_mutator.py +71 -0
- telegrinder/node/nodes/text.py +62 -0
- telegrinder/node/scope.py +88 -0
- telegrinder/node/utils.py +38 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -0
- telegrinder/tools/__init__.py +183 -0
- telegrinder/tools/aio.py +147 -0
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +85 -0
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/deep_links/links.py +468 -0
- telegrinder/tools/formatting/deep_links/parsing.py +88 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html.py +241 -0
- telegrinder/tools/fullname.py +82 -0
- telegrinder/tools/global_context/__init__.py +13 -0
- telegrinder/tools/global_context/abc.py +63 -0
- telegrinder/tools/global_context/builtin_context.py +45 -0
- telegrinder/tools/global_context/global_context.py +614 -0
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard/__init__.py +6 -0
- telegrinder/tools/keyboard/abc.py +84 -0
- telegrinder/tools/keyboard/base.py +108 -0
- telegrinder/tools/keyboard/button.py +181 -0
- telegrinder/tools/keyboard/data.py +31 -0
- telegrinder/tools/keyboard/keyboard.py +160 -0
- telegrinder/tools/keyboard/utils.py +95 -0
- telegrinder/tools/lifespan.py +188 -0
- telegrinder/tools/limited_dict.py +35 -0
- telegrinder/tools/loop_wrapper.py +271 -0
- telegrinder/tools/magic/__init__.py +29 -0
- telegrinder/tools/magic/annotations.py +172 -0
- telegrinder/tools/magic/descriptors.py +57 -0
- telegrinder/tools/magic/function.py +254 -0
- telegrinder/tools/magic/inspect.py +16 -0
- telegrinder/tools/magic/shortcut.py +107 -0
- telegrinder/tools/member_descriptor_proxy.py +95 -0
- telegrinder/tools/parse_mode.py +12 -0
- telegrinder/tools/serialization/__init__.py +5 -0
- telegrinder/tools/serialization/abc.py +34 -0
- telegrinder/tools/serialization/json_ser.py +60 -0
- telegrinder/tools/serialization/msgpack_ser.py +197 -0
- telegrinder/tools/serialization/utils.py +18 -0
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_mutator/__init__.py +4 -0
- telegrinder/tools/state_mutator/mutation.py +85 -0
- telegrinder/tools/state_storage/__init__.py +4 -0
- telegrinder/tools/state_storage/abc.py +38 -0
- telegrinder/tools/state_storage/memory.py +27 -0
- telegrinder/tools/strings.py +22 -0
- telegrinder/types/__init__.py +323 -0
- telegrinder/types/enums.py +754 -0
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +6143 -0
- telegrinder/types/methods_utils.py +66 -0
- telegrinder/types/objects.py +8184 -0
- telegrinder/types/webapp.py +129 -0
- telegrinder/verification_utils.py +35 -0
- telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
- telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
- telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
- telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
telegrinder/__init__.py
ADDED
|
@@ -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
|
+
)
|
telegrinder/__meta__.py
ADDED
|
@@ -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",)
|
telegrinder/api/error.py
ADDED
|
@@ -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",)
|
telegrinder/api/token.py
ADDED
|
@@ -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",)
|