baleropy 0.2.0__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 (65) hide show
  1. baleropy/__init__.py +21 -0
  2. baleropy/__meta__.py +1 -0
  3. baleropy/client/__init__.py +3 -0
  4. baleropy/client/bot.py +568 -0
  5. baleropy/client/server.py +32 -0
  6. baleropy/client/session/__init__.py +2 -0
  7. baleropy/client/session/aiohttp.py +84 -0
  8. baleropy/client/session/base.py +124 -0
  9. baleropy/dispatcher/__init__.py +5 -0
  10. baleropy/dispatcher/dispatcher.py +230 -0
  11. baleropy/dispatcher/event/__init__.py +0 -0
  12. baleropy/dispatcher/event/bases.py +32 -0
  13. baleropy/dispatcher/event/handler.py +86 -0
  14. baleropy/dispatcher/event/observer.py +86 -0
  15. baleropy/dispatcher/flags.py +19 -0
  16. baleropy/dispatcher/middlewares/__init__.py +2 -0
  17. baleropy/dispatcher/middlewares/base.py +33 -0
  18. baleropy/dispatcher/middlewares/builtin.py +106 -0
  19. baleropy/dispatcher/middlewares/user_context.py +29 -0
  20. baleropy/dispatcher/router.py +129 -0
  21. baleropy/exceptions.py +88 -0
  22. baleropy/filters/__init__.py +7 -0
  23. baleropy/filters/base.py +79 -0
  24. baleropy/filters/callback_data.py +92 -0
  25. baleropy/filters/chat.py +64 -0
  26. baleropy/filters/command.py +124 -0
  27. baleropy/filters/extra.py +49 -0
  28. baleropy/filters/magic.py +155 -0
  29. baleropy/filters/state.py +37 -0
  30. baleropy/fsm/__init__.py +4 -0
  31. baleropy/fsm/context.py +39 -0
  32. baleropy/fsm/middleware.py +78 -0
  33. baleropy/fsm/state.py +108 -0
  34. baleropy/fsm/storage/__init__.py +3 -0
  35. baleropy/fsm/storage/base.py +46 -0
  36. baleropy/fsm/storage/memory.py +37 -0
  37. baleropy/fsm/storage/redis.py +68 -0
  38. baleropy/methods/__init__.py +14 -0
  39. baleropy/methods/base.py +64 -0
  40. baleropy/methods/chats.py +118 -0
  41. baleropy/methods/limits.py +32 -0
  42. baleropy/methods/media_group.py +59 -0
  43. baleropy/methods/messages.py +168 -0
  44. baleropy/methods/misc.py +137 -0
  45. baleropy/py.typed +0 -0
  46. baleropy/types/__init__.py +50 -0
  47. baleropy/types/base.py +35 -0
  48. baleropy/types/chat.py +127 -0
  49. baleropy/types/input_file.py +53 -0
  50. baleropy/types/markup.py +73 -0
  51. baleropy/types/media.py +104 -0
  52. baleropy/types/message.py +321 -0
  53. baleropy/types/payments.py +53 -0
  54. baleropy/utils/__init__.py +5 -0
  55. baleropy/utils/chat_action.py +75 -0
  56. baleropy/utils/deep_linking.py +43 -0
  57. baleropy/utils/keyboard.py +157 -0
  58. baleropy/utils/media_group.py +71 -0
  59. baleropy/utils/text.py +47 -0
  60. baleropy/webhook/__init__.py +1 -0
  61. baleropy/webhook/aiohttp_server.py +93 -0
  62. baleropy-0.2.0.dist-info/METADATA +315 -0
  63. baleropy-0.2.0.dist-info/RECORD +65 -0
  64. baleropy-0.2.0.dist-info/WHEEL +4 -0
  65. baleropy-0.2.0.dist-info/licenses/LICENSE +21 -0
baleropy/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ from . import exceptions, methods, types
2
+ from .__meta__ import __version__
3
+ from .client import BUSINESS, PRODUCTION, AiohttpSession, ApiServer, BaseSession, Bot
4
+ from .dispatcher import Dispatcher, Router
5
+ from .filters import F
6
+
7
+ __all__ = [
8
+ "BUSINESS",
9
+ "PRODUCTION",
10
+ "AiohttpSession",
11
+ "ApiServer",
12
+ "BaseSession",
13
+ "Bot",
14
+ "F",
15
+ "Dispatcher",
16
+ "Router",
17
+ "__version__",
18
+ "exceptions",
19
+ "methods",
20
+ "types",
21
+ ]
baleropy/__meta__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -0,0 +1,3 @@
1
+ from .bot import Bot
2
+ from .server import BUSINESS, PRODUCTION, ApiServer
3
+ from .session import AiohttpSession, BaseSession
baleropy/client/bot.py ADDED
@@ -0,0 +1,568 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import io
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any, BinaryIO, TypeVar
8
+
9
+ from .. import methods as m
10
+ from ..methods.base import BaleMethod
11
+ from ..methods.media_group import AnyInputMedia
12
+ from ..methods.misc import InputSticker
13
+ from ..types import (
14
+ ChatAction,
15
+ ChatFullInfo,
16
+ ChatMember,
17
+ File,
18
+ InlineKeyboardMarkup,
19
+ LabeledPrice,
20
+ Message,
21
+ MessageId,
22
+ ReplyMarkup,
23
+ StickerSet,
24
+ Transaction,
25
+ Update,
26
+ User,
27
+ WebhookInfo,
28
+ )
29
+ from ..types.input_file import InputFile
30
+ from .server import BUSINESS, PRODUCTION, ApiServer
31
+ from .session.aiohttp import AiohttpSession
32
+ from .session.base import BaseSession
33
+
34
+ T = TypeVar("T")
35
+ ChatId = int | str
36
+ FileInput = InputFile | str
37
+
38
+ _TOKEN_RE = re.compile(r"^\d+:[\w-]+$")
39
+
40
+
41
+ def validate_token(token: str) -> str:
42
+ if not isinstance(token, str) or not _TOKEN_RE.match(token.strip()):
43
+ raise ValueError("Token has an invalid format, expected '<digits>:<secret>'")
44
+ return token.strip()
45
+
46
+
47
+ def _write_file(path: Path, data: bytes) -> None:
48
+ path.parent.mkdir(parents=True, exist_ok=True)
49
+ path.write_bytes(data)
50
+
51
+
52
+ class Bot:
53
+ """Entry point to the Bale Bot API.
54
+
55
+ Use shortcuts (``await bot.send_message(...)``) or method objects
56
+ (``await bot(SendMessage(...))``).
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ token: str,
62
+ session: BaseSession | None = None,
63
+ server: ApiServer = PRODUCTION,
64
+ ) -> None:
65
+ self.token = validate_token(token)
66
+ self.session = session or AiohttpSession()
67
+ self.server = server
68
+ self._me: User | None = None
69
+
70
+ @classmethod
71
+ def business(cls, token: str, session: BaseSession | None = None) -> Bot:
72
+ """A bot for the Business API (bulk messaging with a separate, paid rate quota).
73
+
74
+ Only getMe, sendMessage, forwardMessage, sendPhoto, sendVideo, sendAudio and sendDocument
75
+ are available; ``chat_id`` must be a private chat and files can only be sent by ``file_id``.
76
+ """
77
+ return cls(token, session, server=BUSINESS)
78
+
79
+ @property
80
+ def id(self) -> int:
81
+ return int(self.token.split(":", 1)[0])
82
+
83
+ async def __call__(self, method: BaleMethod[T]) -> T:
84
+ return await self.session(self, method)
85
+
86
+ async def __aenter__(self) -> Bot:
87
+ return self
88
+
89
+ async def __aexit__(self, *exc: object) -> None:
90
+ await self.close()
91
+
92
+ async def close(self) -> None:
93
+ await self.session.close()
94
+
95
+ async def me(self) -> User:
96
+ """Cached result of ``getMe``."""
97
+ if self._me is None:
98
+ self._me = await self.get_me()
99
+ return self._me
100
+
101
+ # --- updates ---------------------------------------------------------------------------
102
+ async def get_updates(
103
+ self, offset: int | None = None, limit: int | None = None, timeout: int | None = None
104
+ ) -> list[Update]:
105
+ return await self(m.GetUpdates(offset=offset, limit=limit, timeout=timeout))
106
+
107
+ async def set_webhook(self, url: str) -> bool:
108
+ return await self(m.SetWebhook(url=url))
109
+
110
+ async def delete_webhook(self) -> bool:
111
+ return await self(m.DeleteWebhook())
112
+
113
+ async def get_webhook_info(self) -> WebhookInfo:
114
+ return await self(m.GetWebhookInfo())
115
+
116
+ # --- bot -------------------------------------------------------------------------------
117
+ async def get_me(self) -> User:
118
+ return await self(m.GetMe())
119
+
120
+ async def ask_review(self, user_id: int, delay_seconds: int) -> bool:
121
+ return await self(m.AskReview(user_id=user_id, delay_seconds=delay_seconds))
122
+
123
+ # --- sending ---------------------------------------------------------------------------
124
+ async def send_message(
125
+ self,
126
+ chat_id: ChatId,
127
+ text: str,
128
+ reply_to_message_id: int | None = None,
129
+ reply_markup: ReplyMarkup | None = None,
130
+ ) -> Message:
131
+ return await self(
132
+ m.SendMessage(
133
+ chat_id=chat_id,
134
+ text=text,
135
+ reply_to_message_id=reply_to_message_id,
136
+ reply_markup=reply_markup,
137
+ )
138
+ )
139
+
140
+ async def forward_message(
141
+ self, chat_id: ChatId, from_chat_id: ChatId, message_id: int
142
+ ) -> Message:
143
+ return await self(
144
+ m.ForwardMessage(chat_id=chat_id, from_chat_id=from_chat_id, message_id=message_id)
145
+ )
146
+
147
+ async def copy_message(
148
+ self, chat_id: ChatId, from_chat_id: ChatId, message_id: int
149
+ ) -> MessageId:
150
+ return await self(
151
+ m.CopyMessage(chat_id=chat_id, from_chat_id=from_chat_id, message_id=message_id)
152
+ )
153
+
154
+ async def send_photo(
155
+ self,
156
+ chat_id: ChatId,
157
+ photo: FileInput,
158
+ caption: str | None = None,
159
+ reply_to_message_id: int | None = None,
160
+ reply_markup: ReplyMarkup | None = None,
161
+ ) -> Message:
162
+ return await self(
163
+ m.SendPhoto(
164
+ chat_id=chat_id,
165
+ photo=photo,
166
+ caption=caption,
167
+ reply_to_message_id=reply_to_message_id,
168
+ reply_markup=reply_markup,
169
+ )
170
+ )
171
+
172
+ async def send_audio(
173
+ self,
174
+ chat_id: ChatId,
175
+ audio: FileInput,
176
+ caption: str | None = None,
177
+ reply_to_message_id: int | None = None,
178
+ reply_markup: ReplyMarkup | None = None,
179
+ ) -> Message:
180
+ return await self(
181
+ m.SendAudio(
182
+ chat_id=chat_id,
183
+ audio=audio,
184
+ caption=caption,
185
+ reply_to_message_id=reply_to_message_id,
186
+ reply_markup=reply_markup,
187
+ )
188
+ )
189
+
190
+ async def send_document(
191
+ self,
192
+ chat_id: ChatId,
193
+ document: FileInput,
194
+ caption: str | None = None,
195
+ reply_to_message_id: int | None = None,
196
+ reply_markup: ReplyMarkup | None = None,
197
+ ) -> Message:
198
+ return await self(
199
+ m.SendDocument(
200
+ chat_id=chat_id,
201
+ document=document,
202
+ caption=caption,
203
+ reply_to_message_id=reply_to_message_id,
204
+ reply_markup=reply_markup,
205
+ )
206
+ )
207
+
208
+ async def send_video(
209
+ self,
210
+ chat_id: ChatId,
211
+ video: FileInput,
212
+ caption: str | None = None,
213
+ reply_to_message_id: int | None = None,
214
+ reply_markup: ReplyMarkup | None = None,
215
+ ) -> Message:
216
+ return await self(
217
+ m.SendVideo(
218
+ chat_id=chat_id,
219
+ video=video,
220
+ caption=caption,
221
+ reply_to_message_id=reply_to_message_id,
222
+ reply_markup=reply_markup,
223
+ )
224
+ )
225
+
226
+ async def send_animation(
227
+ self,
228
+ chat_id: ChatId,
229
+ animation: FileInput,
230
+ caption: str | None = None,
231
+ reply_to_message_id: int | None = None,
232
+ reply_markup: ReplyMarkup | None = None,
233
+ ) -> Message:
234
+ return await self(
235
+ m.SendAnimation(
236
+ chat_id=chat_id,
237
+ animation=animation,
238
+ caption=caption,
239
+ reply_to_message_id=reply_to_message_id,
240
+ reply_markup=reply_markup,
241
+ )
242
+ )
243
+
244
+ async def send_voice(
245
+ self,
246
+ chat_id: ChatId,
247
+ voice: FileInput,
248
+ caption: str | None = None,
249
+ reply_to_message_id: int | None = None,
250
+ reply_markup: ReplyMarkup | None = None,
251
+ ) -> Message:
252
+ return await self(
253
+ m.SendVoice(
254
+ chat_id=chat_id,
255
+ voice=voice,
256
+ caption=caption,
257
+ reply_to_message_id=reply_to_message_id,
258
+ reply_markup=reply_markup,
259
+ )
260
+ )
261
+
262
+ async def send_sticker(
263
+ self,
264
+ chat_id: ChatId,
265
+ sticker: FileInput,
266
+ reply_to_message_id: int | None = None,
267
+ reply_markup: ReplyMarkup | None = None,
268
+ ) -> Message:
269
+ return await self(
270
+ m.SendSticker(
271
+ chat_id=chat_id,
272
+ sticker=sticker,
273
+ reply_to_message_id=reply_to_message_id,
274
+ reply_markup=reply_markup,
275
+ )
276
+ )
277
+
278
+ async def send_media_group(
279
+ self,
280
+ chat_id: ChatId,
281
+ media: list[AnyInputMedia],
282
+ reply_to_message_id: int | None = None,
283
+ ) -> list[Message]:
284
+ return await self(
285
+ m.SendMediaGroup(chat_id=chat_id, media=media, reply_to_message_id=reply_to_message_id)
286
+ )
287
+
288
+ async def send_location(
289
+ self,
290
+ chat_id: ChatId,
291
+ latitude: float,
292
+ longitude: float,
293
+ horizontal_accuracy: float | None = None,
294
+ reply_to_message_id: int | None = None,
295
+ reply_markup: ReplyMarkup | None = None,
296
+ ) -> Message:
297
+ return await self(
298
+ m.SendLocation(
299
+ chat_id=chat_id,
300
+ latitude=latitude,
301
+ longitude=longitude,
302
+ horizontal_accuracy=horizontal_accuracy,
303
+ reply_to_message_id=reply_to_message_id,
304
+ reply_markup=reply_markup,
305
+ )
306
+ )
307
+
308
+ async def send_contact(
309
+ self,
310
+ chat_id: ChatId,
311
+ phone_number: str | int,
312
+ first_name: str,
313
+ last_name: str | None = None,
314
+ reply_to_message_id: int | None = None,
315
+ reply_markup: ReplyMarkup | None = None,
316
+ ) -> Message:
317
+ return await self(
318
+ m.SendContact(
319
+ chat_id=chat_id,
320
+ phone_number=phone_number,
321
+ first_name=first_name,
322
+ last_name=last_name,
323
+ reply_to_message_id=reply_to_message_id,
324
+ reply_markup=reply_markup,
325
+ )
326
+ )
327
+
328
+ async def send_chat_action(self, chat_id: ChatId, action: ChatAction | str) -> bool:
329
+ return await self(m.SendChatAction(chat_id=chat_id, action=action))
330
+
331
+ # --- editing ---------------------------------------------------------------------------
332
+ async def edit_message_text(
333
+ self,
334
+ chat_id: ChatId,
335
+ message_id: int,
336
+ text: str,
337
+ reply_markup: InlineKeyboardMarkup | None = None,
338
+ ) -> Message | bool:
339
+ return await self(
340
+ m.EditMessageText(
341
+ chat_id=chat_id, message_id=message_id, text=text, reply_markup=reply_markup
342
+ )
343
+ )
344
+
345
+ async def edit_message_caption(
346
+ self,
347
+ chat_id: ChatId,
348
+ message_id: int,
349
+ caption: str | None = None,
350
+ reply_markup: InlineKeyboardMarkup | None = None,
351
+ ) -> Message | bool:
352
+ return await self(
353
+ m.EditMessageCaption(
354
+ chat_id=chat_id, message_id=message_id, caption=caption, reply_markup=reply_markup
355
+ )
356
+ )
357
+
358
+ async def edit_message_reply_markup(
359
+ self,
360
+ chat_id: ChatId,
361
+ message_id: int,
362
+ reply_markup: InlineKeyboardMarkup | None = None,
363
+ ) -> Message | bool:
364
+ return await self(
365
+ m.EditMessageReplyMarkup(
366
+ chat_id=chat_id, message_id=message_id, reply_markup=reply_markup
367
+ )
368
+ )
369
+
370
+ async def delete_message(self, chat_id: ChatId, message_id: int) -> bool:
371
+ return await self(m.DeleteMessage(chat_id=chat_id, message_id=message_id))
372
+
373
+ async def answer_callback_query(
374
+ self, callback_query_id: str, text: str | None = None, show_alert: bool | None = None
375
+ ) -> bool:
376
+ return await self(
377
+ m.AnswerCallbackQuery(
378
+ callback_query_id=callback_query_id, text=text, show_alert=show_alert
379
+ )
380
+ )
381
+
382
+ # --- chats -----------------------------------------------------------------------------
383
+ async def get_chat(self, chat_id: ChatId) -> ChatFullInfo:
384
+ return await self(m.GetChat(chat_id=chat_id))
385
+
386
+ async def get_chat_administrators(self, chat_id: ChatId) -> list[ChatMember]:
387
+ return await self(m.GetChatAdministrators(chat_id=chat_id))
388
+
389
+ async def get_chat_members_count(self, chat_id: ChatId) -> int:
390
+ return await self(m.GetChatMembersCount(chat_id=chat_id))
391
+
392
+ async def get_chat_member(self, chat_id: ChatId, user_id: int) -> ChatMember:
393
+ return await self(m.GetChatMember(chat_id=chat_id, user_id=user_id))
394
+
395
+ async def ban_chat_member(self, chat_id: ChatId, user_id: int) -> bool:
396
+ return await self(m.BanChatMember(chat_id=chat_id, user_id=user_id))
397
+
398
+ async def unban_chat_member(
399
+ self, chat_id: ChatId, user_id: int, only_if_banned: bool | None = None
400
+ ) -> bool:
401
+ return await self(
402
+ m.UnbanChatMember(chat_id=chat_id, user_id=user_id, only_if_banned=only_if_banned)
403
+ )
404
+
405
+ async def promote_chat_member(
406
+ self,
407
+ chat_id: ChatId,
408
+ user_id: int,
409
+ *,
410
+ can_change_info: bool | None = None,
411
+ can_post_messages: bool | None = None,
412
+ can_edit_messages: bool | None = None,
413
+ can_delete_messages: bool | None = None,
414
+ can_manage_video_chats: bool | None = None,
415
+ can_invite_users: bool | None = None,
416
+ can_restrict_members: bool | None = None,
417
+ ) -> bool:
418
+ return await self(
419
+ m.PromoteChatMember(
420
+ chat_id=chat_id,
421
+ user_id=user_id,
422
+ can_change_info=can_change_info,
423
+ can_post_messages=can_post_messages,
424
+ can_edit_messages=can_edit_messages,
425
+ can_delete_messages=can_delete_messages,
426
+ can_manage_video_chats=can_manage_video_chats,
427
+ can_invite_users=can_invite_users,
428
+ can_restrict_members=can_restrict_members,
429
+ )
430
+ )
431
+
432
+ async def leave_chat(self, chat_id: ChatId) -> bool:
433
+ return await self(m.LeaveChat(chat_id=chat_id))
434
+
435
+ async def set_chat_photo(self, chat_id: ChatId, photo: InputFile) -> bool:
436
+ return await self(m.SetChatPhoto(chat_id=chat_id, photo=photo))
437
+
438
+ async def delete_chat_photo(self, chat_id: ChatId) -> bool:
439
+ return await self(m.DeleteChatPhoto(chat_id=chat_id))
440
+
441
+ async def set_chat_title(self, chat_id: ChatId, title: str) -> bool:
442
+ return await self(m.SetChatTitle(chat_id=chat_id, title=title))
443
+
444
+ async def set_chat_description(self, chat_id: ChatId, description: str) -> bool:
445
+ return await self(m.SetChatDescription(chat_id=chat_id, description=description))
446
+
447
+ async def pin_chat_message(self, chat_id: ChatId, message_id: int) -> bool:
448
+ return await self(m.PinChatMessage(chat_id=chat_id, message_id=message_id))
449
+
450
+ async def unpin_chat_message(self, chat_id: ChatId, message_id: int) -> bool:
451
+ return await self(m.UnpinChatMessage(chat_id=chat_id, message_id=message_id))
452
+
453
+ async def unpin_all_chat_messages(self, chat_id: ChatId) -> bool:
454
+ return await self(m.UnpinAllChatMessages(chat_id=chat_id))
455
+
456
+ async def create_chat_invite_link(self, chat_id: ChatId) -> Any:
457
+ return await self(m.CreateChatInviteLink(chat_id=chat_id))
458
+
459
+ async def revoke_chat_invite_link(self, chat_id: ChatId, invite_link: str) -> Any:
460
+ return await self(m.RevokeChatInviteLink(chat_id=chat_id, invite_link=invite_link))
461
+
462
+ async def export_chat_invite_link(self, chat_id: ChatId) -> str:
463
+ return await self(m.ExportChatInviteLink(chat_id=chat_id))
464
+
465
+ # --- files -----------------------------------------------------------------------------
466
+ async def get_file(self, file_id: str) -> File:
467
+ return await self(m.GetFile(file_id=file_id))
468
+
469
+ async def download(
470
+ self,
471
+ file: str | File,
472
+ destination: BinaryIO | str | Path | None = None,
473
+ chunk_size: int = 65536,
474
+ ) -> BinaryIO | None:
475
+ """Download a file by ``file_id`` or :class:`File`.
476
+
477
+ Returns an in-memory buffer when ``destination`` is omitted, otherwise ``None``.
478
+ """
479
+ if isinstance(file, File) and file.file_path:
480
+ info = file
481
+ else:
482
+ info = await self.get_file(file.file_id if isinstance(file, File) else file)
483
+ if not info.file_path:
484
+ raise ValueError("Bale did not return file_path for this file")
485
+ url = self.server.file_url(self.token, info.file_path)
486
+ buffer = io.BytesIO()
487
+ async for chunk in self.session.stream_content(url, chunk_size):
488
+ buffer.write(chunk)
489
+ if destination is None:
490
+ buffer.seek(0)
491
+ return buffer
492
+ if isinstance(destination, (str, Path)):
493
+ await asyncio.to_thread(_write_file, Path(destination), buffer.getvalue())
494
+ else:
495
+ destination.write(buffer.getvalue()) # noqa: ASYNC240
496
+ return None
497
+
498
+ # --- stickers --------------------------------------------------------------------------
499
+ async def upload_sticker_file(self, user_id: int, sticker: InputFile) -> File:
500
+ return await self(m.UploadStickerFile(user_id=user_id, sticker=sticker))
501
+
502
+ async def create_new_sticker_set(
503
+ self, user_id: int, name: str, title: str, stickers: list[InputSticker]
504
+ ) -> bool:
505
+ return await self(
506
+ m.CreateNewStickerSet(user_id=user_id, name=name, title=title, stickers=stickers)
507
+ )
508
+
509
+ async def add_sticker_to_set(self, user_id: int, name: str, sticker: InputSticker) -> bool:
510
+ return await self(m.AddStickerToSet(user_id=user_id, name=name, sticker=sticker))
511
+
512
+ async def get_sticker_set(self, name: str) -> StickerSet:
513
+ return await self(m.GetStickerSet(name=name))
514
+
515
+ # --- payments --------------------------------------------------------------------------
516
+ async def send_invoice(
517
+ self,
518
+ chat_id: ChatId,
519
+ title: str,
520
+ description: str,
521
+ payload: str,
522
+ provider_token: str,
523
+ prices: list[LabeledPrice],
524
+ photo_url: str | None = None,
525
+ reply_to_message_id: int | None = None,
526
+ ) -> Message:
527
+ return await self(
528
+ m.SendInvoice(
529
+ chat_id=chat_id,
530
+ title=title,
531
+ description=description,
532
+ payload=payload,
533
+ provider_token=provider_token,
534
+ prices=prices,
535
+ photo_url=photo_url,
536
+ reply_to_message_id=reply_to_message_id,
537
+ )
538
+ )
539
+
540
+ async def create_invoice_link(
541
+ self,
542
+ title: str,
543
+ description: str,
544
+ payload: str,
545
+ provider_token: str,
546
+ prices: list[LabeledPrice],
547
+ ) -> str:
548
+ return await self(
549
+ m.CreateInvoiceLink(
550
+ title=title,
551
+ description=description,
552
+ payload=payload,
553
+ provider_token=provider_token,
554
+ prices=prices,
555
+ )
556
+ )
557
+
558
+ async def answer_pre_checkout_query(
559
+ self, pre_checkout_query_id: str, ok: bool, error_message: str | None = None
560
+ ) -> bool:
561
+ return await self(
562
+ m.AnswerPreCheckoutQuery(
563
+ pre_checkout_query_id=pre_checkout_query_id, ok=ok, error_message=error_message
564
+ )
565
+ )
566
+
567
+ async def inquire_transaction(self, transaction_id: str) -> Transaction:
568
+ return await self(m.InquireTransaction(transaction_id=transaction_id))
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class ApiServer:
8
+ """Describes where the Bale Bot API lives. Subclass or build your own for proxies/mirrors."""
9
+
10
+ base: str = "https://tapi.bale.ai/bot{token}/{method}"
11
+ file: str = "https://tapi.bale.ai/file/bot{token}/{path}"
12
+
13
+ def api_url(self, token: str, method: str) -> str:
14
+ return self.base.format(token=token, method=method)
15
+
16
+ def file_url(self, token: str, path: str) -> str:
17
+ return self.file.format(token=token, path=path)
18
+
19
+ @classmethod
20
+ def from_base(cls, base: str) -> ApiServer:
21
+ base = base.rstrip("/")
22
+ return cls(
23
+ base=f"{base}/bot{{token}}/{{method}}",
24
+ file=f"{base}/file/bot{{token}}/{{path}}",
25
+ )
26
+
27
+
28
+ PRODUCTION = ApiServer()
29
+ BUSINESS = ApiServer(
30
+ base="https://tapi.bale.ai/business/bot{token}/{method}",
31
+ file=PRODUCTION.file,
32
+ )
@@ -0,0 +1,2 @@
1
+ from .aiohttp import AiohttpSession
2
+ from .base import BaseSession, RequestMiddleware