pyrobale 0.3.5__py3-none-any.whl → 0.3.7__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.
@@ -38,16 +38,17 @@ from ..objects.webappdata import WebAppData
38
38
  from ..objects.webappinfo import WebAppInfo
39
39
  from ..objects.utils import *
40
40
  import asyncio
41
- import aiohttp
42
- import bale
43
41
  from enum import Enum
44
42
  from ..objects.enums import UpdatesTypes, ChatAction, ChatType
43
+ from ..StateMachine import StateMachine
45
44
 
46
45
 
47
46
  class Client:
48
47
  """A client for interacting with the Bale messenger API.
49
48
 
50
- token: The bot token to use for authentication.
49
+ Args:
50
+ token (str): The bot token.
51
+ base_url (str, optional): The base URL for the API. Defaults to "https://tapi.bale.ai/bot".
51
52
  """
52
53
 
53
54
  def __init__(self, token: str, base_url: str = "https://tapi.bale.ai/bot"):
@@ -56,8 +57,10 @@ class Client:
56
57
  self.requests_base = base_url + token
57
58
 
58
59
  self.handlers = []
60
+ self._waiters = []
59
61
  self.running = False
60
62
  self.last_update_id = 0
63
+ self.state_machine = StateMachine()
61
64
 
62
65
  async def get_updates(
63
66
  self,
@@ -65,6 +68,15 @@ class Client:
65
68
  limit: Optional[int] = None,
66
69
  timeout: Optional[int] = None,
67
70
  ) -> List[Dict]:
71
+ """Get updates from the Bale API.
72
+
73
+ Args:
74
+ offset (int, optional): The offset of the updates to get. Defaults to None.
75
+ limit (int, optional): The maximum number of updates to get. Defaults to None.
76
+ timeout (int, optional): The timeout for the request. Defaults to None.
77
+ Returns:
78
+ List[Dict]: The updates.
79
+ """
68
80
  data = await make_get(
69
81
  self.requests_base
70
82
  + f"/getUpdates?offset={offset}&limit={limit}&timeout={timeout}"
@@ -72,22 +84,49 @@ class Client:
72
84
  return data["result"]
73
85
 
74
86
  async def set_webhook(self, url: str) -> bool:
87
+ """Set the webhook for the bot.
88
+
89
+ Args:
90
+ url (str): The URL to set the webhook to.
91
+ Returns:
92
+ bool: True if the webhook was set successfully, False otherwise.
93
+ """
75
94
  data = await make_post(self.requests_base + "/setWebhook", data={"url": url})
76
95
  return data["result"]
77
96
 
78
97
  async def get_webhook_info(self) -> Dict:
98
+ """Get the webhook information for the bot.
99
+
100
+ Returns:
101
+ Dict: The webhook information.
102
+ """
79
103
  data = await make_get(self.requests_base + "/getWebhookInfo")
80
104
  return data["result"]
81
105
 
82
106
  async def get_me(self) -> User:
107
+ """Get information about the bot.
108
+
109
+ Returns:
110
+ User: The information about the bot.
111
+ """
83
112
  data = await make_get(self.requests_base + "/getMe")
84
113
  return User(**data["result"])
85
114
 
86
115
  async def logout(self) -> bool:
116
+ """Log out the bot.
117
+
118
+ Returns:
119
+ bool: True if the bot was logged out successfully, False otherwise.
120
+ """
87
121
  data = await make_get(self.requests_base + "/logOut")
88
122
  return data["result"]
89
123
 
90
124
  async def close(self) -> bool:
125
+ """Close the bot.
126
+
127
+ Returns:
128
+ bool: True if the bot was closed successfully, False otherwise.
129
+ """
91
130
  data = await make_get(self.requests_base + "/close")
92
131
  return data["result"]
93
132
 
@@ -98,6 +137,16 @@ class Client:
98
137
  reply_to_message_id: Optional[int] = None,
99
138
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
100
139
  ) -> Message:
140
+ """Send a message to a chat.
141
+
142
+ Args:
143
+ chat_id (int): The ID of the chat to send the message to.
144
+ text (str): The text of the message.
145
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
146
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
147
+ Returns:
148
+ Message: The message that was sent.
149
+ """
101
150
  data = await make_post(
102
151
  self.requests_base + "/sendMessage",
103
152
  data={
@@ -112,6 +161,15 @@ class Client:
112
161
  async def forward_message(
113
162
  self, chat_id: int, from_chat_id: int, message_id: int
114
163
  ) -> Message:
164
+ """Forward a message to a chat.
165
+
166
+ Args:
167
+ chat_id (int): The ID of the chat to forward the message to.
168
+ from_chat_id (int): The ID of the chat to forward the message from.
169
+ message_id (int): The ID of the message to forward.
170
+ Returns:
171
+ Message: The message that was forwarded.
172
+ """
115
173
  data = await make_post(
116
174
  self.requests_base + "/forwardMessage",
117
175
  data={
@@ -125,6 +183,15 @@ class Client:
125
183
  async def copy_message(
126
184
  self, chat_id: int, from_chat_id: int, message_id: int
127
185
  ) -> Message:
186
+ """Copy a message to a chat.
187
+
188
+ Args:
189
+ chat_id (int): The ID of the chat to copy the message to.
190
+ from_chat_id (int): The ID of the chat to copy the message from.
191
+ message_id (int): The ID of the message to copy.
192
+ Returns:
193
+ Message: The message that was copied.
194
+ """
128
195
  data = await make_post(
129
196
  self.requests_base + "/copyMessage",
130
197
  data={
@@ -143,6 +210,17 @@ class Client:
143
210
  reply_to_message_id: Optional[int] = None,
144
211
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
145
212
  ) -> Message:
213
+ """Send a photo to a chat.
214
+
215
+ Args:
216
+ chat_id (Union[int, str]): The ID of the chat to send the photo to.
217
+ photo (Union[InputFile, str]): The photo to send.
218
+ caption (str, optional): The caption of the photo. Defaults to None.
219
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
220
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
221
+ Returns:
222
+ Message: The message that was sent.
223
+ """
146
224
  data = await make_post(
147
225
  self.requests_base + "/sendPhoto",
148
226
  data={
@@ -163,6 +241,17 @@ class Client:
163
241
  reply_to_message_id: Optional[int] = None,
164
242
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
165
243
  ) -> Message:
244
+ """Send an audio to a chat.
245
+
246
+ Args:
247
+ chat_id (int): The ID of the chat to send the audio to.
248
+ audio (Union[InputFile, str]): The audio to send.
249
+ caption (str, optional): The caption of the audio. Defaults to None.
250
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
251
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
252
+ Returns:
253
+ Message: The message that was sent.
254
+ """
166
255
  data = await make_post(
167
256
  self.requests_base + "/sendAudio",
168
257
  data={
@@ -183,6 +272,17 @@ class Client:
183
272
  reply_to_message_id: Optional[int] = None,
184
273
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
185
274
  ) -> Message:
275
+ """Send a document to a chat.
276
+
277
+ Args:
278
+ chat_id (int): The ID of the chat to send the document to.
279
+ document (Union[InputFile, str]): The document to send.
280
+ caption (str, optional): The caption of the document. Defaults to None.
281
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
282
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
283
+ Returns:
284
+ Message: The message that was sent.
285
+ """
186
286
  data = await make_post(
187
287
  self.requests_base + "/sendDocument",
188
288
  data={
@@ -203,6 +303,17 @@ class Client:
203
303
  reply_to_message_id: Optional[int] = None,
204
304
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
205
305
  ) -> Message:
306
+ """Send a video to a chat.
307
+
308
+ Args:
309
+ chat_id (int): The ID of the chat to send the video to.
310
+ video (Union[InputFile, str]): The video to send.
311
+ caption (str, optional): The caption of the video. Defaults to None.
312
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
313
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
314
+ Returns:
315
+ Message: The message that was sent.
316
+ """
206
317
  data = await make_post(
207
318
  self.requests_base + "/sendVideo",
208
319
  data={
@@ -223,6 +334,17 @@ class Client:
223
334
  reply_to_message_id: Optional[int] = None,
224
335
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
225
336
  ) -> Message:
337
+ """Send an animation to a chat.
338
+
339
+ Args:
340
+ chat_id (int): The ID of the chat to send the animation to.
341
+ animation (Union[InputFile, str]): The animation to send.
342
+ caption (str, optional): The caption of the animation. Defaults to None.
343
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
344
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
345
+ Returns:
346
+ Message: The message that was sent.
347
+ """
226
348
  data = await make_post(
227
349
  self.requests_base + "/sendAnimation",
228
350
  data={
@@ -243,6 +365,17 @@ class Client:
243
365
  reply_to_message_id: Optional[int] = None,
244
366
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
245
367
  ) -> Message:
368
+ """Send a voice message to a chat.
369
+
370
+ Args:
371
+ chat_id (int): The ID of the chat to send the voice message to.
372
+ voice (Union[InputFile, str]): The voice message to send.
373
+ caption (str, optional): The caption of the voice message. Defaults to None.
374
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
375
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
376
+ Returns:
377
+ Message: The message that was sent.
378
+ """
246
379
  data = await make_post(
247
380
  self.requests_base + "/sendVoice",
248
381
  data={
@@ -262,6 +395,16 @@ class Client:
262
395
  reply_to_message_id: Optional[int] = None,
263
396
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
264
397
  ) -> List[Message]:
398
+ """Send a media group to a chat.
399
+
400
+ Args:
401
+ chat_id (int): The ID of the chat to send the media group to.
402
+ media (List[Union[InputMediaPhoto, InputMediaVideo, InputMediaAudio]]): The media group to send.
403
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
404
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
405
+ Returns:
406
+ List[Message]: The messages that were sent.
407
+ """
265
408
  data = await make_post(
266
409
  self.requests_base + "/sendMediaGroup",
267
410
  data={
@@ -281,6 +424,17 @@ class Client:
281
424
  reply_to_message_id: Optional[int] = None,
282
425
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
283
426
  ) -> Message:
427
+ """Send a location to a chat.
428
+
429
+ Args:
430
+ chat_id (int): The ID of the chat to send the location to.
431
+ latitude (float): The latitude of the location.
432
+ longitude (float): The longitude of the location.
433
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
434
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
435
+ Returns:
436
+ Message: The message that was sent.
437
+ """
284
438
  data = await make_post(
285
439
  self.requests_base + "/sendLocation",
286
440
  data={
@@ -302,6 +456,18 @@ class Client:
302
456
  reply_to_message_id: Optional[int] = None,
303
457
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
304
458
  ) -> Message:
459
+ """Send a contact to a chat.
460
+
461
+ Args:
462
+ chat_id (int): The ID of the chat to send the contact to.
463
+ phone_number (str): The phone number of the contact.
464
+ first_name (str): The first name of the contact.
465
+ last_name (str, optional): The last name of the contact. Defaults to None.
466
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
467
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
468
+ Returns:
469
+ Message: The message that was sent.
470
+ """
305
471
  data = await make_post(
306
472
  self.requests_base + "/sendContact",
307
473
  data={
@@ -315,7 +481,55 @@ class Client:
315
481
  )
316
482
  return Message(**pythonize(data["result"]))
317
483
 
484
+ async def send_invoice(
485
+ self,
486
+ chat_id: Union[str, int],
487
+ title: str,
488
+ description: str,
489
+ payload: str,
490
+ provider_token: str,
491
+ prices: list[LabeledPrice],
492
+ photo_url: Optional[str] = None,
493
+ reply_to_message_id: Optional[int] = None,
494
+ ) -> Message:
495
+ """Sends a message including a invoice for user to pay.
496
+
497
+ Args:
498
+ chat_id (string OR integer): unique chat id to send the invoice
499
+ title (string): the title of invoice
500
+ description (string): desciption of invoice, you can explain the invoice here
501
+ payload (string): payload of invoice, user will not see this, it'll be returned after successful payment
502
+ provider_token (string): Wallet token or card number of receiver
503
+ prices (list of LabledPrice): a list of prices that user must pay
504
+ photo_url (Optional: string): url of a photo that will be sent with invoice
505
+ reply_to_message_id (Optional: int): message id to reply that
506
+
507
+ Returns:
508
+ Message: returns the sent message with invoice
509
+ """
510
+ data = await make_post(
511
+ self.requests_base + "/sendInvoice",
512
+ data={
513
+ "chat_id": chat_id,
514
+ "title": title,
515
+ "description": description,
516
+ "payload": payload,
517
+ "provider_token": provider_token,
518
+ "prices": prices,
519
+ "photo_url": photo_url,
520
+ "reply_to_message_id": reply_to_message_id,
521
+ },
522
+ )
523
+ return Message(**pythonize(data["result"]))
524
+
318
525
  async def get_file(self, file_id: str) -> File:
526
+ """Get a file from the Bale servers.
527
+
528
+ Args:
529
+ file_id (str): The ID of the file to get.
530
+ Returns:
531
+ File: The file that was retrieved.
532
+ """
319
533
  data = await make_post(
320
534
  self.requests_base + "/getFile", data={"file_id": file_id}
321
535
  )
@@ -327,6 +541,15 @@ class Client:
327
541
  text: Optional[str] = None,
328
542
  show_alert: Optional[bool] = None,
329
543
  ):
544
+ """Answer a callback query.
545
+
546
+ Args:
547
+ callback_query_id (str): The ID of the callback query to answer.
548
+ text (str, optional): The text to show to the user. Defaults to None.
549
+ show_alert (bool, optional): Whether to show an alert to the user. Defaults to None.
550
+ Returns:
551
+ bool: Whether the callback query was answered successfully.
552
+ """
330
553
  data = await make_post(
331
554
  self.requests_base + "/answerCallbackQuery",
332
555
  data={
@@ -338,6 +561,15 @@ class Client:
338
561
  return data.get("ok", False)
339
562
 
340
563
  async def ban_chat_member(self, chat_id: int, user_id: int) -> bool:
564
+ """Ban a user from a chat.
565
+
566
+ Args:
567
+ chat_id (int): The ID of the chat to ban the user from.
568
+ user_id (int): The ID of the user to ban.
569
+ Returns:
570
+ bool: Whether the user was banned successfully
571
+ """
572
+
341
573
  data = await make_post(
342
574
  self.requests_base + "/banChatMember",
343
575
  data={"chat_id": chat_id, "user_id": user_id},
@@ -345,6 +577,14 @@ class Client:
345
577
  return data.get("ok", False)
346
578
 
347
579
  async def unban_chat_member(self, chat_id: int, user_id: int) -> bool:
580
+ """Unban a user from a chat.
581
+
582
+ Args:
583
+ chat_id (int): The ID of the chat to unban the user from.
584
+ user_id (int): The ID of the user to unban.
585
+ Returns:
586
+ bool: Whether the user was unbanned successfully.
587
+ """
348
588
  data = await make_post(
349
589
  self.requests_base + "/unbanChatMember",
350
590
  data={"chat_id": chat_id, "user_id": user_id},
@@ -352,6 +592,14 @@ class Client:
352
592
  return data.get("ok", False)
353
593
 
354
594
  async def get_chat_member(self, chat_id: int, user_id: int) -> ChatMember:
595
+ """Get a chat member.
596
+
597
+ Args:
598
+ chat_id (int): The ID of the chat to get the member from.
599
+ user_id (int): The ID of the user to get.
600
+ Returns:
601
+ ChatMember: The chat member that was retrieved.
602
+ """
355
603
  data = await make_post(
356
604
  self.requests_base + "/getChatMember",
357
605
  data={"chat_id": chat_id, "user_id": user_id},
@@ -373,6 +621,22 @@ class Client:
373
621
  can_pin_messages: Optional[bool] = None,
374
622
  can_promote_members: Optional[bool] = None,
375
623
  ):
624
+ """Promote a user in a chat.
625
+
626
+ Args:
627
+ chat_id (int): The ID of the chat to promote the user in.
628
+ user_id (int): The ID of the user to promote.
629
+ can_change_info (bool, optional): Whether the user can change the chat info. Defaults to None.
630
+ can_post_messages (bool, optional): Whether the user can post messages. Defaults to None.
631
+ can_edit_messages (bool, optional): Whether the user can edit messages. Defaults to None.
632
+ can_delete_messages (bool, optional): Whether the user can delete messages. Defaults to None.
633
+ can_invite_users (bool, optional): Whether the user can invite users. Defaults to None.
634
+ can_restrict_members (bool, optional): Whether the user can restrict members. Defaults to None.
635
+ can_pin_messages (bool, optional): Whether the user can pin messages. Defaults to None.
636
+ can_promote_members (bool, optional): Whether the user can promote members. Defaults to None.
637
+ Returns:
638
+ bool: Whether the user was promoted successfully.
639
+ """
376
640
  data = await make_post(
377
641
  self.requests_base + "/promoteChatMember",
378
642
  data={
@@ -391,6 +655,15 @@ class Client:
391
655
  return data.get("ok", False)
392
656
 
393
657
  async def set_chat_photo(self, chat_id: int, photo: InputFile) -> bool:
658
+ """Set a new profile photo for the chat.
659
+
660
+ Args:
661
+ chat_id (int): Unique identifier for the target chat
662
+ photo (InputFile): New chat photo
663
+
664
+ Returns:
665
+ bool: True on success
666
+ """
394
667
  data = await make_post(
395
668
  self.requests_base + "/setChatPhoto",
396
669
  data={"chat_id": chat_id, "photo": photo},
@@ -398,24 +671,57 @@ class Client:
398
671
  return data.get("ok", False)
399
672
 
400
673
  async def leave_chat(self, chat_id: int) -> bool:
674
+ """Leave a group, supergroup or channel.
675
+
676
+ Args:
677
+ chat_id (int): Unique identifier for the target chat
678
+
679
+ Returns:
680
+ bool: True on success
681
+ """
401
682
  data = await make_post(
402
683
  self.requests_base + "/leaveChat", data={"chat_id": chat_id}
403
684
  )
404
685
  return data.get("ok", False)
405
686
 
406
687
  async def get_chat(self, chat_id: int) -> Chat:
688
+ """Get up to date information about the chat.
689
+
690
+ Args:
691
+ chat_id (int): Unique identifier for the target chat
692
+
693
+ Returns:
694
+ Chat: Chat object with information about the chat
695
+ """
407
696
  data = await make_post(
408
697
  self.requests_base + "/getChat", data={"chat_id": chat_id}
409
698
  )
410
699
  return Chat(**pythonize(data["result"]))
411
700
 
412
701
  async def get_chat_members_count(self, chat_id: int) -> int:
702
+ """Get the number of members in a chat.
703
+
704
+ Args:
705
+ chat_id (int): Unique identifier for the target chat
706
+
707
+ Returns:
708
+ int: Number of members in the chat
709
+ """
413
710
  data = await make_post(
414
711
  self.requests_base + "/getChatMembersCount", data={"chat_id": chat_id}
415
712
  )
416
713
  return data.get("result", 0)
417
714
 
418
715
  async def pin_chat_message(self, chat_id: int, message_id: int) -> bool:
716
+ """Pin a message in a chat.
717
+
718
+ Args:
719
+ chat_id (int): Unique identifier for the target chat
720
+ message_id (int): Identifier of a message to pin
721
+
722
+ Returns:
723
+ bool: True on success
724
+ """
419
725
  data = await make_post(
420
726
  self.requests_base + "/pinChatMessage",
421
727
  data={"chat_id": chat_id, "message_id": message_id},
@@ -423,18 +729,43 @@ class Client:
423
729
  return data.get("ok", False)
424
730
 
425
731
  async def unpin_chat_message(self, chat_id: int) -> bool:
732
+ """Unpin a message in a chat.
733
+
734
+ Args:
735
+ chat_id (int): Unique identifier for the target chat
736
+
737
+ Returns:
738
+ bool: True on success
739
+ """
426
740
  data = await make_post(
427
741
  self.requests_base + "/unpinChatMessage", data={"chat_id": chat_id}
428
742
  )
429
743
  return data.get("ok", False)
430
744
 
431
745
  async def unpin_all_chat_messages(self, chat_id: int) -> bool:
746
+ """Unpin all messages in a chat.
747
+
748
+ Args:
749
+ chat_id (int): Unique identifier for the target chat
750
+
751
+ Returns:
752
+ bool: True on success
753
+ """
432
754
  data = await make_post(
433
755
  self.requests_base + "/unpinAllChatMessages", data={"chat_id": chat_id}
434
756
  )
435
757
  return data.get("ok", False)
436
758
 
437
759
  async def set_chat_title(self, chat_id: int, title: str) -> bool:
760
+ """Change the title of a chat.
761
+
762
+ Args:
763
+ chat_id (int): Unique identifier for the target chat
764
+ title (str): New chat title, 1-255 characters
765
+
766
+ Returns:
767
+ bool: True on success
768
+ """
438
769
  data = await make_post(
439
770
  self.requests_base + "/setChatTitle",
440
771
  data={"chat_id": chat_id, "title": title},
@@ -442,6 +773,15 @@ class Client:
442
773
  return data.get("ok", False)
443
774
 
444
775
  async def set_chat_description(self, chat_id: int, description: str) -> bool:
776
+ """Change the description of a chat.
777
+
778
+ Args:
779
+ chat_id (int): Unique identifier for the target chat
780
+ description (str): New chat description, 0-255 characters
781
+
782
+ Returns:
783
+ bool: True on success
784
+ """
445
785
  data = await make_post(
446
786
  self.requests_base + "/setChatDescription",
447
787
  data={"chat_id": chat_id, "description": description},
@@ -449,18 +789,73 @@ class Client:
449
789
  return data.get("ok", False)
450
790
 
451
791
  async def delete_chat_photo(self, chat_id: int) -> bool:
792
+ """Delete a chat photo.
793
+
794
+ Args:
795
+ chat_id (int): Unique identifier for the target chat
796
+
797
+ Returns:
798
+ bool: True on success
799
+ """
452
800
  data = await make_post(
453
801
  self.requests_base + "/deleteChatPhoto", data={"chat_id": chat_id}
454
802
  )
455
803
  return data.get("ok", False)
456
804
 
805
+ async def edit_message(
806
+ self,
807
+ chat_id: Union[int, str],
808
+ message_id: int,
809
+ text: str,
810
+ reply_markup: Optional[InlineKeyboardMarkup] = None,
811
+ ) -> Message:
812
+ """Edits a message in a specified chat
813
+
814
+ Args:
815
+ chat_id (int OR str): Unique identifier for the target chat
816
+ message_id (int): Unique indentifier for the message you want to edit
817
+ text (str): New text of message
818
+ reply_markup (InlineKeyboardMarkup): Inline markup you can add or change in message
819
+
820
+ Returns:
821
+ Message: The object of edited message
822
+ """
823
+
824
+ data = await make_post(
825
+ self.requests_base + "/editMessageText",
826
+ data={
827
+ "chat_id": chat_id,
828
+ "message_id": message_id,
829
+ "text": text,
830
+ "reply_markup": reply_markup.to_dict() if reply_markup else None,
831
+ },
832
+ )
833
+ return Message(**pythonize(data["result"]))
834
+
457
835
  async def create_chat_invite_link(self, chat_id: int) -> str:
836
+ """Create an additional invite link for a chat.
837
+
838
+ Args:
839
+ chat_id (int): Unique identifier for the target chat
840
+
841
+ Returns:
842
+ str: The new invite link
843
+ """
458
844
  data = await make_post(
459
845
  self.requests_base + "/createChatInviteLink", data={"chat_id": chat_id}
460
846
  )
461
847
  return data.get("result", "")
462
848
 
463
849
  async def revoke_chat_invite_link(self, chat_id: int, invite_link: str) -> str:
850
+ """Revoke an invite link created by the bot.
851
+
852
+ Args:
853
+ chat_id (int): Unique identifier for the target chat
854
+ invite_link (str): The invite link to revoke
855
+
856
+ Returns:
857
+ str: The revoked invite link
858
+ """
464
859
  data = await make_post(
465
860
  self.requests_base + "/revokeChatInviteLink",
466
861
  data={"chat_id": chat_id, "invite_link": invite_link},
@@ -468,24 +863,67 @@ class Client:
468
863
  return data.get("result", "")
469
864
 
470
865
  async def export_chat_invite_link(self, chat_id: int) -> str:
866
+ """Generate a new primary invite link for a chat.
867
+
868
+ Args:
869
+ chat_id (int): Unique identifier for the target chat
870
+
871
+ Returns:
872
+ str: The new invite link
873
+ """
471
874
  data = await make_post(
472
875
  self.requests_base + "/exportChatInviteLink", data={"chat_id": chat_id}
473
876
  )
474
877
  return data.get("result", "")
475
878
 
476
879
  async def send_chat_action(self, chat_id: int, action: ChatAction) -> bool:
880
+ """Tell the user that something is happening on the bot's side.
881
+
882
+ Args:
883
+ chat_id (int): Unique identifier for the target chat
884
+ action (ChatAction): Type of action to broadcast
885
+
886
+ Returns:
887
+ bool: True on success
888
+ """
477
889
  data = await make_post(
478
890
  self.requests_base + "/sendChatAction",
479
891
  data={"chat_id": str(chat_id), "action": action.value},
480
892
  )
481
893
  return data.get("ok", False)
482
894
 
895
+ async def wait_for(self, update_type: UpdatesTypes, check=None):
896
+ """Wait until a specified update
897
+
898
+ Args:
899
+ update_type (UpdatesTypes): The type of update you're waiting for it.
900
+ check: a condition that will be checked before passing.
901
+ """
902
+ future = asyncio.get_running_loop().create_future()
903
+ self._waiters.append((update_type, check, future))
904
+ return await future
905
+
483
906
  async def process_update(self, update: Dict[str, Any]) -> None:
484
- """Process a single update and call registered handlers."""
907
+ """Process a single update and call registered handlers.
908
+
909
+ Args:
910
+ update (Dict[str, Any]): The update to process
911
+ """
485
912
  update_id = update.get("update_id")
486
913
  if update_id:
487
914
  self.last_update_id = update_id + 1
488
915
 
916
+ for waiter in list(self._waiters):
917
+ w_type, check, future = waiter
918
+ if w_type.value in update:
919
+ event = update[w_type.value]
920
+ event = self._convert_event(w_type, event)
921
+ if check is None or check(event):
922
+ if not future.done():
923
+ future.set_result(event)
924
+ self._waiters.remove(waiter)
925
+ return update
926
+
489
927
  for handler in self.handlers:
490
928
  update_type = handler["type"].value
491
929
  if update_type in update:
@@ -498,22 +936,101 @@ class Client:
498
936
  else:
499
937
  handler["callback"](event)
500
938
 
939
+ def base_handler_decorator(self, update_type: UpdatesTypes):
940
+ def decorator(callback: Callable[[Any], Union[None, Awaitable[None]]]):
941
+ self.add_handler(update_type, callback)
942
+ return callback
943
+
944
+ return decorator
945
+
946
+ def on_message(self):
947
+ return self.base_handler_decorator(UpdatesTypes.MESSAGE)
948
+
949
+ def on_edited_message(self):
950
+ return self.base_handler_decorator(UpdatesTypes.MESSAGE_EDITED)
951
+
952
+ def on_callback_query(self):
953
+ return self.base_handler_decorator(UpdatesTypes.CALLBACK_QUERY)
954
+
955
+ def on_new_members(self):
956
+ return self.base_handler_decorator(UpdatesTypes.MEMBER_JOINED)
957
+
958
+ def on_memebers_left(self):
959
+ return self.base_handler_decorator(UpdatesTypes.MEMBER_LEFT)
960
+
961
+ def on_pre_checkout_query(self):
962
+ return self.base_handler_decorator(UpdatesTypes.PRE_CHECKOUT_QUERY)
963
+
964
+ def on_photo(self):
965
+ return self.base_handler_decorator(UpdatesTypes.PHOTO)
966
+
967
+ def on_successful_payment(self):
968
+ return self.base_handler_decorator(UpdatesTypes.SUCCESSFUL_PAYMENT)
969
+
501
970
  def _convert_event(self, handler_type: UpdatesTypes, event: Dict[str, Any]) -> Any:
502
- """Convert raw event data to appropriate object type."""
503
- if handler_type in (UpdatesTypes.MESSAGE, UpdatesTypes.MESSAGE_EDITED):
504
- return Message(**pythonize(event), kwargs={"client": self})
971
+ """Convert raw event data to appropriate object type.
972
+
973
+ Args:
974
+ handler_type (UpdatesTypes): Type of the update
975
+ event (Dict[str, Any]): Raw event data
976
+
977
+ Returns:
978
+ Any: Converted event object
979
+ """
980
+ if handler_type in (
981
+ UpdatesTypes.MESSAGE,
982
+ UpdatesTypes.MESSAGE_EDITED,
983
+ UpdatesTypes.MEMBER_JOINED,
984
+ UpdatesTypes.MEMBER_LEFT,
985
+ UpdatesTypes.SUCCESSFUL_PAYMENT,
986
+ ):
987
+ if (
988
+ event.get("new_chat_member", False)
989
+ and handler_type == UpdatesTypes.MEMBER_JOINED
990
+ ):
991
+ return (
992
+ ChatMember(
993
+ kwargs={"client": self},
994
+ **pythonize(event.get("new_chat_member", {})),
995
+ ),
996
+ Chat(kwargs={"client": self}, **pythonize(event.get("chat", {}))),
997
+ Message(
998
+ kwargs={"client": self}, **pythonize(event.get("message", {}))
999
+ ),
1000
+ )
1001
+ elif (
1002
+ event.get("left_chat_member", False)
1003
+ and handler_type == UpdatesTypes.MEMBER_LEFT
1004
+ ):
1005
+ return (
1006
+ ChatMember(
1007
+ kwargs={"client": self},
1008
+ **pythonize(event.get("left_chat_member", {})),
1009
+ ),
1010
+ Chat(kwargs={"client": self}, **pythonize(event.get("chat", {}))),
1011
+ Message(
1012
+ kwargs={"client": self}, **pythonize(event.get("message", {}))
1013
+ ),
1014
+ )
1015
+
1016
+ elif (
1017
+ event.get("successful_payment", False)
1018
+ and handler_type == UpdatesTypes.SUCCESSFUL_PAYMENT
1019
+ ):
1020
+ return Message(
1021
+ **pythonize(event.get("successful_payment", {})),
1022
+ kwargs={"client": self},
1023
+ )
1024
+
1025
+ else:
1026
+ return Message(kwargs={"client": self}, **pythonize(event))
1027
+
505
1028
  elif handler_type == UpdatesTypes.CALLBACK_QUERY:
506
1029
  return CallbackQuery(kwargs={"client": self}, **pythonize(event))
1030
+
507
1031
  elif handler_type == UpdatesTypes.PRE_CHECKOUT_QUERY:
508
1032
  return PreCheckoutQuery(kwargs={"client": self}, **pythonize(event))
509
- elif handler_type == UpdatesTypes.MEMBER_JOINED:
510
- return ChatMember(
511
- kwargs={"client": self}, **pythonize(event.get("new_chat_member", {}))
512
- )
513
- elif handler_type == UpdatesTypes.MEMBER_LEFT:
514
- return ChatMember(
515
- kwargs={"client": self}, **pythonize(event.get("left_chat_member", {}))
516
- )
1033
+
517
1034
  return event
518
1035
 
519
1036
  def add_handler(
@@ -521,13 +1038,22 @@ class Client:
521
1038
  update_type: UpdatesTypes,
522
1039
  callback: Callable[[Any], Union[None, Awaitable[None]]],
523
1040
  ) -> None:
524
- """Register a handler for specific update type."""
1041
+ """Register a handler for specific update type.
1042
+
1043
+ Args:
1044
+ update_type (UpdatesTypes): Type of update to handle
1045
+ callback (Callable): Function to call when update is received
1046
+ """
525
1047
  self.handlers.append({"type": update_type, "callback": callback})
526
1048
 
527
1049
  def remove_handler(
528
1050
  self, callback: Callable[[Any], Union[None, Awaitable[None]]]
529
1051
  ) -> None:
530
- """Remove a handler from the list of handlers."""
1052
+ """Remove a handler from the list of handlers.
1053
+
1054
+ Args:
1055
+ callback (Callable): Handler function to remove
1056
+ """
531
1057
  self.handlers = [
532
1058
  handler for handler in self.handlers if handler["callback"] != callback
533
1059
  ]
@@ -537,7 +1063,15 @@ class Client:
537
1063
  self.handlers = []
538
1064
 
539
1065
  async def start_polling(self, timeout: int = 30, limit: int = 100) -> None:
540
- """Start polling updates from the server."""
1066
+ """Start polling updates from the server.
1067
+
1068
+ Args:
1069
+ timeout (int, optional): Timeout in seconds for long polling. Defaults to 30.
1070
+ limit (int, optional): Maximum number of updates to retrieve. Defaults to 100.
1071
+
1072
+ Raises:
1073
+ RuntimeError: If client is already running
1074
+ """
541
1075
  if self.running:
542
1076
  raise RuntimeError("Client is already running")
543
1077
 
@@ -560,7 +1094,12 @@ class Client:
560
1094
  self.running = False
561
1095
 
562
1096
  def run(self, timeout: int = 30, limit: int = 100) -> None:
563
- """Run the client."""
1097
+ """Run the client.
1098
+
1099
+ Args:
1100
+ timeout (int, optional): Timeout in seconds for long polling. Defaults to 30.
1101
+ limit (int, optional): Maximum number of updates to retrieve. Defaults to 100.
1102
+ """
564
1103
  loop = asyncio.get_event_loop()
565
1104
  loop.run_until_complete(self.start_polling(timeout, limit))
566
1105
 
@@ -570,5 +1109,9 @@ class Client:
570
1109
  loop.run_until_complete(self.stop_polling())
571
1110
 
572
1111
  async def handle_webhook_update(self, update_data: Dict[str, Any]) -> None:
573
- """Process an update received via webhook."""
1112
+ """Process an update received via webhook.
1113
+
1114
+ Args:
1115
+ update_data (Dict[str, Any]): Update data received from webhook
1116
+ """
574
1117
  await self.process_update(update_data)