pyrobale 0.3.5__py3-none-any.whl → 0.3.8__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,19 @@ 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 ..filters import Filters, equals
44
+ from ..StateMachine import StateMachine
45
+ from ..exceptions import NotFoundException, InvalidTokenException, PyroBaleException
45
46
 
46
47
 
47
48
  class Client:
48
49
  """A client for interacting with the Bale messenger API.
49
50
 
50
- token: The bot token to use for authentication.
51
+ Args:
52
+ token (str): The bot token.
53
+ base_url (str, optional): The base URL for the API. Defaults to "https://tapi.bale.ai/bot".
51
54
  """
52
55
 
53
56
  def __init__(self, token: str, base_url: str = "https://tapi.bale.ai/bot"):
@@ -56,8 +59,10 @@ class Client:
56
59
  self.requests_base = base_url + token
57
60
 
58
61
  self.handlers = []
62
+ self._waiters = []
59
63
  self.running = False
60
64
  self.last_update_id = 0
65
+ self.state_machine = StateMachine()
61
66
 
62
67
  async def get_updates(
63
68
  self,
@@ -65,29 +70,70 @@ class Client:
65
70
  limit: Optional[int] = None,
66
71
  timeout: Optional[int] = None,
67
72
  ) -> List[Dict]:
73
+ """Get updates from the Bale API.
74
+
75
+ Args:
76
+ offset (int, optional): The offset of the updates to get. Defaults to None.
77
+ limit (int, optional): The maximum number of updates to get. Defaults to None.
78
+ timeout (int, optional): The timeout for the request. Defaults to None.
79
+ Returns:
80
+ List[Dict]: The updates.
81
+ """
68
82
  data = await make_get(
69
83
  self.requests_base
70
84
  + f"/getUpdates?offset={offset}&limit={limit}&timeout={timeout}"
71
85
  )
72
- return data["result"]
86
+ if data['ok']:
87
+ if 'result' in data.keys():
88
+ return data["result"]
89
+ else:
90
+ if data['error_code'] == 403:
91
+ raise InvalidTokenException("Forbidden 403 : --ENTERED TOKEN IS NOT VALID--")
73
92
 
74
93
  async def set_webhook(self, url: str) -> bool:
94
+ """Set the webhook for the bot.
95
+
96
+ Args:
97
+ url (str): The URL to set the webhook to.
98
+ Returns:
99
+ bool: True if the webhook was set successfully, False otherwise.
100
+ """
75
101
  data = await make_post(self.requests_base + "/setWebhook", data={"url": url})
76
102
  return data["result"]
77
103
 
78
104
  async def get_webhook_info(self) -> Dict:
105
+ """Get the webhook information for the bot.
106
+
107
+ Returns:
108
+ Dict: The webhook information.
109
+ """
79
110
  data = await make_get(self.requests_base + "/getWebhookInfo")
80
111
  return data["result"]
81
112
 
82
113
  async def get_me(self) -> User:
114
+ """Get information about the bot.
115
+
116
+ Returns:
117
+ User: The information about the bot.
118
+ """
83
119
  data = await make_get(self.requests_base + "/getMe")
84
120
  return User(**data["result"])
85
121
 
86
122
  async def logout(self) -> bool:
123
+ """Log out the bot.
124
+
125
+ Returns:
126
+ bool: True if the bot was logged out successfully, False otherwise.
127
+ """
87
128
  data = await make_get(self.requests_base + "/logOut")
88
129
  return data["result"]
89
130
 
90
131
  async def close(self) -> bool:
132
+ """Close the bot.
133
+
134
+ Returns:
135
+ bool: True if the bot was closed successfully, False otherwise.
136
+ """
91
137
  data = await make_get(self.requests_base + "/close")
92
138
  return data["result"]
93
139
 
@@ -98,6 +144,16 @@ class Client:
98
144
  reply_to_message_id: Optional[int] = None,
99
145
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
100
146
  ) -> Message:
147
+ """Send a message to a chat.
148
+
149
+ Args:
150
+ chat_id (int): The ID of the chat to send the message to.
151
+ text (str): The text of the message.
152
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
153
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
154
+ Returns:
155
+ Message: The message that was sent.
156
+ """
101
157
  data = await make_post(
102
158
  self.requests_base + "/sendMessage",
103
159
  data={
@@ -112,6 +168,15 @@ class Client:
112
168
  async def forward_message(
113
169
  self, chat_id: int, from_chat_id: int, message_id: int
114
170
  ) -> Message:
171
+ """Forward a message to a chat.
172
+
173
+ Args:
174
+ chat_id (int): The ID of the chat to forward the message to.
175
+ from_chat_id (int): The ID of the chat to forward the message from.
176
+ message_id (int): The ID of the message to forward.
177
+ Returns:
178
+ Message: The message that was forwarded.
179
+ """
115
180
  data = await make_post(
116
181
  self.requests_base + "/forwardMessage",
117
182
  data={
@@ -125,6 +190,15 @@ class Client:
125
190
  async def copy_message(
126
191
  self, chat_id: int, from_chat_id: int, message_id: int
127
192
  ) -> Message:
193
+ """Copy a message to a chat.
194
+
195
+ Args:
196
+ chat_id (int): The ID of the chat to copy the message to.
197
+ from_chat_id (int): The ID of the chat to copy the message from.
198
+ message_id (int): The ID of the message to copy.
199
+ Returns:
200
+ Message: The message that was copied.
201
+ """
128
202
  data = await make_post(
129
203
  self.requests_base + "/copyMessage",
130
204
  data={
@@ -143,6 +217,17 @@ class Client:
143
217
  reply_to_message_id: Optional[int] = None,
144
218
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
145
219
  ) -> Message:
220
+ """Send a photo to a chat.
221
+
222
+ Args:
223
+ chat_id (Union[int, str]): The ID of the chat to send the photo to.
224
+ photo (Union[InputFile, str]): The photo to send.
225
+ caption (str, optional): The caption of the photo. Defaults to None.
226
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
227
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
228
+ Returns:
229
+ Message: The message that was sent.
230
+ """
146
231
  data = await make_post(
147
232
  self.requests_base + "/sendPhoto",
148
233
  data={
@@ -163,6 +248,17 @@ class Client:
163
248
  reply_to_message_id: Optional[int] = None,
164
249
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
165
250
  ) -> Message:
251
+ """Send an audio to a chat.
252
+
253
+ Args:
254
+ chat_id (int): The ID of the chat to send the audio to.
255
+ audio (Union[InputFile, str]): The audio to send.
256
+ caption (str, optional): The caption of the audio. Defaults to None.
257
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
258
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
259
+ Returns:
260
+ Message: The message that was sent.
261
+ """
166
262
  data = await make_post(
167
263
  self.requests_base + "/sendAudio",
168
264
  data={
@@ -183,6 +279,17 @@ class Client:
183
279
  reply_to_message_id: Optional[int] = None,
184
280
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
185
281
  ) -> Message:
282
+ """Send a document to a chat.
283
+
284
+ Args:
285
+ chat_id (int): The ID of the chat to send the document to.
286
+ document (Union[InputFile, str]): The document to send.
287
+ caption (str, optional): The caption of the document. Defaults to None.
288
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
289
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
290
+ Returns:
291
+ Message: The message that was sent.
292
+ """
186
293
  data = await make_post(
187
294
  self.requests_base + "/sendDocument",
188
295
  data={
@@ -203,6 +310,17 @@ class Client:
203
310
  reply_to_message_id: Optional[int] = None,
204
311
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
205
312
  ) -> Message:
313
+ """Send a video to a chat.
314
+
315
+ Args:
316
+ chat_id (int): The ID of the chat to send the video to.
317
+ video (Union[InputFile, str]): The video to send.
318
+ caption (str, optional): The caption of the video. Defaults to None.
319
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
320
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
321
+ Returns:
322
+ Message: The message that was sent.
323
+ """
206
324
  data = await make_post(
207
325
  self.requests_base + "/sendVideo",
208
326
  data={
@@ -223,6 +341,17 @@ class Client:
223
341
  reply_to_message_id: Optional[int] = None,
224
342
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
225
343
  ) -> Message:
344
+ """Send an animation to a chat.
345
+
346
+ Args:
347
+ chat_id (int): The ID of the chat to send the animation to.
348
+ animation (Union[InputFile, str]): The animation to send.
349
+ caption (str, optional): The caption of the animation. Defaults to None.
350
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
351
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
352
+ Returns:
353
+ Message: The message that was sent.
354
+ """
226
355
  data = await make_post(
227
356
  self.requests_base + "/sendAnimation",
228
357
  data={
@@ -243,6 +372,17 @@ class Client:
243
372
  reply_to_message_id: Optional[int] = None,
244
373
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
245
374
  ) -> Message:
375
+ """Send a voice message to a chat.
376
+
377
+ Args:
378
+ chat_id (int): The ID of the chat to send the voice message to.
379
+ voice (Union[InputFile, str]): The voice message to send.
380
+ caption (str, optional): The caption of the voice message. Defaults to None.
381
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
382
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
383
+ Returns:
384
+ Message: The message that was sent.
385
+ """
246
386
  data = await make_post(
247
387
  self.requests_base + "/sendVoice",
248
388
  data={
@@ -262,6 +402,16 @@ class Client:
262
402
  reply_to_message_id: Optional[int] = None,
263
403
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
264
404
  ) -> List[Message]:
405
+ """Send a media group to a chat.
406
+
407
+ Args:
408
+ chat_id (int): The ID of the chat to send the media group to.
409
+ media (List[Union[InputMediaPhoto, InputMediaVideo, InputMediaAudio]]): The media group to send.
410
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
411
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
412
+ Returns:
413
+ List[Message]: The messages that were sent.
414
+ """
265
415
  data = await make_post(
266
416
  self.requests_base + "/sendMediaGroup",
267
417
  data={
@@ -278,15 +428,28 @@ class Client:
278
428
  chat_id: int,
279
429
  latitude: float,
280
430
  longitude: float,
431
+ horizontal_accuracy: Optional[float] = None,
281
432
  reply_to_message_id: Optional[int] = None,
282
433
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
283
434
  ) -> Message:
435
+ """Send a location to a chat.
436
+
437
+ Args:
438
+ chat_id (int): The ID of the chat to send the location to.
439
+ latitude (float): The latitude of the location.
440
+ longitude (float): The longitude of the location.
441
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
442
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
443
+ Returns:
444
+ Message: The message that was sent.
445
+ """
284
446
  data = await make_post(
285
447
  self.requests_base + "/sendLocation",
286
448
  data={
287
449
  "chat_id": chat_id,
288
450
  "latitude": latitude,
289
451
  "longitude": longitude,
452
+ "horizontal_accuracy": horizontal_accuracy,
290
453
  "reply_to_message_id": reply_to_message_id,
291
454
  "reply_markup": reply_markup.to_dict() if reply_markup else None,
292
455
  },
@@ -302,6 +465,18 @@ class Client:
302
465
  reply_to_message_id: Optional[int] = None,
303
466
  reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
304
467
  ) -> Message:
468
+ """Send a contact to a chat.
469
+
470
+ Args:
471
+ chat_id (int): The ID of the chat to send the contact to.
472
+ phone_number (str): The phone number of the contact.
473
+ first_name (str): The first name of the contact.
474
+ last_name (str, optional): The last name of the contact. Defaults to None.
475
+ reply_to_message_id (int, optional): The ID of the message to reply to. Defaults to None.
476
+ reply_markup (Union[InlineKeyboardMarkup, ReplyKeyboardMarkup], optional): The reply markup to use. Defaults to None.
477
+ Returns:
478
+ Message: The message that was sent.
479
+ """
305
480
  data = await make_post(
306
481
  self.requests_base + "/sendContact",
307
482
  data={
@@ -315,7 +490,58 @@ class Client:
315
490
  )
316
491
  return Message(**pythonize(data["result"]))
317
492
 
493
+ async def send_invoice(
494
+ self,
495
+ chat_id: Union[str, int],
496
+ title: str,
497
+ description: str,
498
+ payload: str,
499
+ provider_token: str,
500
+ prices: list[LabeledPrice],
501
+ photo_url: Optional[str] = None,
502
+ reply_to_message_id: Optional[int] = None,
503
+ ) -> Message:
504
+ """Sends a message including a invoice for user to pay.
505
+
506
+ Args:
507
+ chat_id (string OR integer): unique chat id to send the invoice
508
+ title (string): the title of invoice
509
+ description (string): desciption of invoice, you can explain the invoice here
510
+ payload (string): payload of invoice, user will not see this, it'll be returned after successful payment
511
+ provider_token (string): Wallet token or card number of receiver
512
+ prices (list of LabledPrice): a list of prices that user must pay
513
+ photo_url (Optional: string): url of a photo that will be sent with invoice
514
+ reply_to_message_id (Optional: int): message id to reply that
515
+
516
+ Returns:
517
+ Message: returns the sent message with invoice
518
+ """
519
+ new_prices = []
520
+ for price in prices:
521
+ new_prices.append(price.json)
522
+ data = await make_post(
523
+ self.requests_base + "/sendInvoice",
524
+ data={
525
+ "chat_id": chat_id,
526
+ "title": title,
527
+ "description": description,
528
+ "payload": payload,
529
+ "provider_token": provider_token,
530
+ "prices": new_prices,
531
+ "photo_url": photo_url,
532
+ "reply_to_message_id": reply_to_message_id,
533
+ },
534
+ )
535
+ return Message(**pythonize(data["result"]))
536
+
318
537
  async def get_file(self, file_id: str) -> File:
538
+ """Get a file from the Bale servers.
539
+
540
+ Args:
541
+ file_id (str): The ID of the file to get.
542
+ Returns:
543
+ File: The file that was retrieved.
544
+ """
319
545
  data = await make_post(
320
546
  self.requests_base + "/getFile", data={"file_id": file_id}
321
547
  )
@@ -327,6 +553,15 @@ class Client:
327
553
  text: Optional[str] = None,
328
554
  show_alert: Optional[bool] = None,
329
555
  ):
556
+ """Answer a callback query.
557
+
558
+ Args:
559
+ callback_query_id (str): The ID of the callback query to answer.
560
+ text (str, optional): The text to show to the user. Defaults to None.
561
+ show_alert (bool, optional): Whether to show an alert to the user. Defaults to None.
562
+ Returns:
563
+ bool: Whether the callback query was answered successfully.
564
+ """
330
565
  data = await make_post(
331
566
  self.requests_base + "/answerCallbackQuery",
332
567
  data={
@@ -338,6 +573,15 @@ class Client:
338
573
  return data.get("ok", False)
339
574
 
340
575
  async def ban_chat_member(self, chat_id: int, user_id: int) -> bool:
576
+ """Ban a user from a chat.
577
+
578
+ Args:
579
+ chat_id (int): The ID of the chat to ban the user from.
580
+ user_id (int): The ID of the user to ban.
581
+ Returns:
582
+ bool: Whether the user was banned successfully
583
+ """
584
+
341
585
  data = await make_post(
342
586
  self.requests_base + "/banChatMember",
343
587
  data={"chat_id": chat_id, "user_id": user_id},
@@ -345,6 +589,14 @@ class Client:
345
589
  return data.get("ok", False)
346
590
 
347
591
  async def unban_chat_member(self, chat_id: int, user_id: int) -> bool:
592
+ """Unban a user from a chat.
593
+
594
+ Args:
595
+ chat_id (int): The ID of the chat to unban the user from.
596
+ user_id (int): The ID of the user to unban.
597
+ Returns:
598
+ bool: Whether the user was unbanned successfully.
599
+ """
348
600
  data = await make_post(
349
601
  self.requests_base + "/unbanChatMember",
350
602
  data={"chat_id": chat_id, "user_id": user_id},
@@ -352,6 +604,14 @@ class Client:
352
604
  return data.get("ok", False)
353
605
 
354
606
  async def get_chat_member(self, chat_id: int, user_id: int) -> ChatMember:
607
+ """Get a chat member.
608
+
609
+ Args:
610
+ chat_id (int): The ID of the chat to get the member from.
611
+ user_id (int): The ID of the user to get.
612
+ Returns:
613
+ ChatMember: The chat member that was retrieved.
614
+ """
355
615
  data = await make_post(
356
616
  self.requests_base + "/getChatMember",
357
617
  data={"chat_id": chat_id, "user_id": user_id},
@@ -373,6 +633,22 @@ class Client:
373
633
  can_pin_messages: Optional[bool] = None,
374
634
  can_promote_members: Optional[bool] = None,
375
635
  ):
636
+ """Promote a user in a chat.
637
+
638
+ Args:
639
+ chat_id (int): The ID of the chat to promote the user in.
640
+ user_id (int): The ID of the user to promote.
641
+ can_change_info (bool, optional): Whether the user can change the chat info. Defaults to None.
642
+ can_post_messages (bool, optional): Whether the user can post messages. Defaults to None.
643
+ can_edit_messages (bool, optional): Whether the user can edit messages. Defaults to None.
644
+ can_delete_messages (bool, optional): Whether the user can delete messages. Defaults to None.
645
+ can_invite_users (bool, optional): Whether the user can invite users. Defaults to None.
646
+ can_restrict_members (bool, optional): Whether the user can restrict members. Defaults to None.
647
+ can_pin_messages (bool, optional): Whether the user can pin messages. Defaults to None.
648
+ can_promote_members (bool, optional): Whether the user can promote members. Defaults to None.
649
+ Returns:
650
+ bool: Whether the user was promoted successfully.
651
+ """
376
652
  data = await make_post(
377
653
  self.requests_base + "/promoteChatMember",
378
654
  data={
@@ -391,6 +667,15 @@ class Client:
391
667
  return data.get("ok", False)
392
668
 
393
669
  async def set_chat_photo(self, chat_id: int, photo: InputFile) -> bool:
670
+ """Set a new profile photo for the chat.
671
+
672
+ Args:
673
+ chat_id (int): Unique identifier for the target chat
674
+ photo (InputFile): New chat photo
675
+
676
+ Returns:
677
+ bool: True on success
678
+ """
394
679
  data = await make_post(
395
680
  self.requests_base + "/setChatPhoto",
396
681
  data={"chat_id": chat_id, "photo": photo},
@@ -398,24 +683,57 @@ class Client:
398
683
  return data.get("ok", False)
399
684
 
400
685
  async def leave_chat(self, chat_id: int) -> bool:
686
+ """Leave a group, supergroup or channel.
687
+
688
+ Args:
689
+ chat_id (int): Unique identifier for the target chat
690
+
691
+ Returns:
692
+ bool: True on success
693
+ """
401
694
  data = await make_post(
402
695
  self.requests_base + "/leaveChat", data={"chat_id": chat_id}
403
696
  )
404
697
  return data.get("ok", False)
405
698
 
406
699
  async def get_chat(self, chat_id: int) -> Chat:
700
+ """Get up to date information about the chat.
701
+
702
+ Args:
703
+ chat_id (int): Unique identifier for the target chat
704
+
705
+ Returns:
706
+ Chat: Chat object with information about the chat
707
+ """
407
708
  data = await make_post(
408
709
  self.requests_base + "/getChat", data={"chat_id": chat_id}
409
710
  )
410
711
  return Chat(**pythonize(data["result"]))
411
712
 
412
713
  async def get_chat_members_count(self, chat_id: int) -> int:
714
+ """Get the number of members in a chat.
715
+
716
+ Args:
717
+ chat_id (int): Unique identifier for the target chat
718
+
719
+ Returns:
720
+ int: Number of members in the chat
721
+ """
413
722
  data = await make_post(
414
723
  self.requests_base + "/getChatMembersCount", data={"chat_id": chat_id}
415
724
  )
416
725
  return data.get("result", 0)
417
726
 
418
727
  async def pin_chat_message(self, chat_id: int, message_id: int) -> bool:
728
+ """Pin a message in a chat.
729
+
730
+ Args:
731
+ chat_id (int): Unique identifier for the target chat
732
+ message_id (int): Identifier of a message to pin
733
+
734
+ Returns:
735
+ bool: True on success
736
+ """
419
737
  data = await make_post(
420
738
  self.requests_base + "/pinChatMessage",
421
739
  data={"chat_id": chat_id, "message_id": message_id},
@@ -423,18 +741,43 @@ class Client:
423
741
  return data.get("ok", False)
424
742
 
425
743
  async def unpin_chat_message(self, chat_id: int) -> bool:
744
+ """Unpin a message in a chat.
745
+
746
+ Args:
747
+ chat_id (int): Unique identifier for the target chat
748
+
749
+ Returns:
750
+ bool: True on success
751
+ """
426
752
  data = await make_post(
427
753
  self.requests_base + "/unpinChatMessage", data={"chat_id": chat_id}
428
754
  )
429
755
  return data.get("ok", False)
430
756
 
431
757
  async def unpin_all_chat_messages(self, chat_id: int) -> bool:
758
+ """Unpin all messages in a chat.
759
+
760
+ Args:
761
+ chat_id (int): Unique identifier for the target chat
762
+
763
+ Returns:
764
+ bool: True on success
765
+ """
432
766
  data = await make_post(
433
767
  self.requests_base + "/unpinAllChatMessages", data={"chat_id": chat_id}
434
768
  )
435
769
  return data.get("ok", False)
436
770
 
437
771
  async def set_chat_title(self, chat_id: int, title: str) -> bool:
772
+ """Change the title of a chat.
773
+
774
+ Args:
775
+ chat_id (int): Unique identifier for the target chat
776
+ title (str): New chat title, 1-255 characters
777
+
778
+ Returns:
779
+ bool: True on success
780
+ """
438
781
  data = await make_post(
439
782
  self.requests_base + "/setChatTitle",
440
783
  data={"chat_id": chat_id, "title": title},
@@ -442,6 +785,15 @@ class Client:
442
785
  return data.get("ok", False)
443
786
 
444
787
  async def set_chat_description(self, chat_id: int, description: str) -> bool:
788
+ """Change the description of a chat.
789
+
790
+ Args:
791
+ chat_id (int): Unique identifier for the target chat
792
+ description (str): New chat description, 0-255 characters
793
+
794
+ Returns:
795
+ bool: True on success
796
+ """
445
797
  data = await make_post(
446
798
  self.requests_base + "/setChatDescription",
447
799
  data={"chat_id": chat_id, "description": description},
@@ -449,18 +801,73 @@ class Client:
449
801
  return data.get("ok", False)
450
802
 
451
803
  async def delete_chat_photo(self, chat_id: int) -> bool:
804
+ """Delete a chat photo.
805
+
806
+ Args:
807
+ chat_id (int): Unique identifier for the target chat
808
+
809
+ Returns:
810
+ bool: True on success
811
+ """
452
812
  data = await make_post(
453
813
  self.requests_base + "/deleteChatPhoto", data={"chat_id": chat_id}
454
814
  )
455
815
  return data.get("ok", False)
456
816
 
817
+ async def edit_message(
818
+ self,
819
+ chat_id: Union[int, str],
820
+ message_id: int,
821
+ text: str,
822
+ reply_markup: Optional[InlineKeyboardMarkup] = None,
823
+ ) -> Message:
824
+ """Edits a message in a specified chat
825
+
826
+ Args:
827
+ chat_id (int OR str): Unique identifier for the target chat
828
+ message_id (int): Unique indentifier for the message you want to edit
829
+ text (str): New text of message
830
+ reply_markup (InlineKeyboardMarkup): Inline markup you can add or change in message
831
+
832
+ Returns:
833
+ Message: The object of edited message
834
+ """
835
+
836
+ data = await make_post(
837
+ self.requests_base + "/editMessageText",
838
+ data={
839
+ "chat_id": chat_id,
840
+ "message_id": message_id,
841
+ "text": text,
842
+ "reply_markup": reply_markup.to_dict() if reply_markup else None,
843
+ },
844
+ )
845
+ return Message(**pythonize(data["result"]))
846
+
457
847
  async def create_chat_invite_link(self, chat_id: int) -> str:
848
+ """Create an additional invite link for a chat.
849
+
850
+ Args:
851
+ chat_id (int): Unique identifier for the target chat
852
+
853
+ Returns:
854
+ str: The new invite link
855
+ """
458
856
  data = await make_post(
459
857
  self.requests_base + "/createChatInviteLink", data={"chat_id": chat_id}
460
858
  )
461
859
  return data.get("result", "")
462
860
 
463
861
  async def revoke_chat_invite_link(self, chat_id: int, invite_link: str) -> str:
862
+ """Revoke an invite link created by the bot.
863
+
864
+ Args:
865
+ chat_id (int): Unique identifier for the target chat
866
+ invite_link (str): The invite link to revoke
867
+
868
+ Returns:
869
+ str: The revoked invite link
870
+ """
464
871
  data = await make_post(
465
872
  self.requests_base + "/revokeChatInviteLink",
466
873
  data={"chat_id": chat_id, "invite_link": invite_link},
@@ -468,66 +875,287 @@ class Client:
468
875
  return data.get("result", "")
469
876
 
470
877
  async def export_chat_invite_link(self, chat_id: int) -> str:
878
+ """Generate a new primary invite link for a chat.
879
+
880
+ Args:
881
+ chat_id (int): Unique identifier for the target chat
882
+
883
+ Returns:
884
+ str: The new invite link
885
+ """
471
886
  data = await make_post(
472
887
  self.requests_base + "/exportChatInviteLink", data={"chat_id": chat_id}
473
888
  )
474
889
  return data.get("result", "")
475
890
 
476
891
  async def send_chat_action(self, chat_id: int, action: ChatAction) -> bool:
892
+ """Tell the user that something is happening on the bot's side.
893
+
894
+ Args:
895
+ chat_id (int): Unique identifier for the target chat
896
+ action (ChatAction): Type of action to broadcast
897
+
898
+ Returns:
899
+ bool: True on success
900
+ """
477
901
  data = await make_post(
478
902
  self.requests_base + "/sendChatAction",
479
903
  data={"chat_id": str(chat_id), "action": action.value},
480
904
  )
481
905
  return data.get("ok", False)
482
906
 
907
+ async def wait_for(self, update_type: UpdatesTypes, check=None):
908
+ """Wait until a specified update
909
+
910
+ Args:
911
+ update_type (UpdatesTypes): The type of update you're waiting for it.
912
+ check: a condition that will be checked before passing.
913
+ """
914
+ future = asyncio.get_running_loop().create_future()
915
+ self._waiters.append((update_type, check, future))
916
+ return await future
917
+
483
918
  async def process_update(self, update: Dict[str, Any]) -> None:
484
- """Process a single update and call registered handlers."""
919
+ """Process a single update and call registered handlers.
920
+
921
+ Args:
922
+ update (Dict[str, Any]): The update to process
923
+ """
485
924
  update_id = update.get("update_id")
486
925
  if update_id:
487
926
  self.last_update_id = update_id + 1
488
927
 
928
+ for waiter in list(self._waiters):
929
+ w_type, check, future = waiter
930
+ if w_type.value in update:
931
+ event = update[w_type.value]
932
+ event = self._convert_event(w_type, event)
933
+ if check is None or check(event):
934
+ if not future.done():
935
+ future.set_result(event)
936
+ self._waiters.remove(waiter)
937
+ return
938
+
489
939
  for handler in self.handlers:
490
940
  update_type = handler["type"].value
491
941
  if update_type in update:
492
- event = update[update_type]
493
-
494
- event = self._convert_event(handler["type"], event)
942
+ raw_event = update[update_type]
943
+ event = self._convert_event(handler["type"], raw_event)
944
+
945
+ if handler["type"] == UpdatesTypes.COMMAND:
946
+ if hasattr(event, 'text') and event.text and event.text.startswith('/'):
947
+ command_text = event.text[1:]
948
+ command_parts = command_text.split()
949
+ if command_parts:
950
+ actual_command = command_parts[0]
951
+ expected_command = handler.get("command", "")
952
+
953
+ if actual_command != expected_command:
954
+ continue
495
955
 
956
+ flt = handler.get("filter")
957
+ if flt is not None:
958
+ if callable(flt):
959
+ try:
960
+ if not flt(event):
961
+ continue
962
+ except Exception as e:
963
+ print(f"[Filter Error] {e}")
964
+ continue
965
+ elif isinstance(flt, Filters):
966
+ if not hasattr(event, flt.value):
967
+ continue
968
+
496
969
  if asyncio.iscoroutinefunction(handler["callback"]):
497
970
  asyncio.create_task(handler["callback"](event))
498
971
  else:
499
972
  handler["callback"](event)
500
973
 
974
+
975
+
976
+ def base_handler_decorator(self, update_type: UpdatesTypes):
977
+ """Base decorator for handling different types of updates.
978
+
979
+ Args:
980
+ update_type (UpdatesTypes): The type of update to handle.
981
+
982
+ Returns:
983
+ Callable: A decorator function that registers the callback for the specified update type.
984
+ """
985
+ def wrapper(filter: Optional[Filters] = None):
986
+ def decorator(callback: Callable[[Any], Union[None, Awaitable[None]]]):
987
+ self.add_handler(update_type, callback, filter)
988
+ return callback
989
+ return decorator
990
+ return wrapper
991
+
992
+ def on_command(self, command: str, filter: Optional[Filters] = None):
993
+ """Decorator for handling command updates.
994
+
995
+ Args:
996
+ command (str): The command to handle.
997
+ filter (Optional[Filters]): An optional filter to apply to the command.
998
+ Returns:
999
+ Callable: A decorator function that registers the callback for the specified command.
1000
+ """
1001
+ def decorator(callback: Callable[[Any], Union[None, Awaitable[None]]]):
1002
+ self.add_handler(UpdatesTypes.COMMAND, callback, filter, command=command)
1003
+ return callback
1004
+ return decorator
1005
+
1006
+
1007
+ def on_message(self, filter: Optional[Filters] = None):
1008
+ """Decorator for handling new message updates.
1009
+
1010
+ Returns:
1011
+ Callable: A decorator function that registers the callback for message updates.
1012
+ """
1013
+ return self.base_handler_decorator(UpdatesTypes.MESSAGE)(filter)
1014
+
1015
+
1016
+ def on_edited_message(self):
1017
+ """Decorator for handling edited message updates.
1018
+
1019
+ Returns:
1020
+ Callable: A decorator function that registers the callback for edited message updates.
1021
+ """
1022
+ return self.base_handler_decorator(UpdatesTypes.MESSAGE_EDITED)
1023
+
1024
+ def on_callback_query(self):
1025
+ """Decorator for handling callback query updates.
1026
+
1027
+ Returns:
1028
+ Callable: A decorator function that registers the callback for callback query updates.
1029
+ """
1030
+ return self.base_handler_decorator(UpdatesTypes.CALLBACK_QUERY)
1031
+
1032
+ def on_new_members(self):
1033
+ """Decorator for handling new chat members updates.
1034
+
1035
+ Returns:
1036
+ Callable: A decorator function that registers the callback for new members updates.
1037
+ """
1038
+ return self.base_handler_decorator(UpdatesTypes.MEMBER_JOINED)
1039
+
1040
+ def on_members_left(self):
1041
+ return self.base_handler_decorator(UpdatesTypes.MEMBER_LEFT)
1042
+
1043
+ def on_pre_checkout_query(self):
1044
+ """Decorator for handling pre-checkout query updates.
1045
+
1046
+ Returns:
1047
+ Callable: A decorator function that registers the callback for pre-checkout query updates.
1048
+ """
1049
+ return self.base_handler_decorator(UpdatesTypes.PRE_CHECKOUT_QUERY)
1050
+
1051
+ def on_photo(self):
1052
+ """Decorator for handling photo updates.
1053
+
1054
+ Returns:
1055
+ Callable: A decorator function that registers the callback for photo updates.
1056
+ """
1057
+ return self.base_handler_decorator(UpdatesTypes.PHOTO)
1058
+
1059
+ def on_successful_payment(self):
1060
+ """Decorator for handling successful payment updates.
1061
+
1062
+ Returns:
1063
+ Callable: A decorator function that registers the callback for successful payment updates.
1064
+ """
1065
+ return self.base_handler_decorator(UpdatesTypes.SUCCESSFUL_PAYMENT)
1066
+
501
1067
  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})
1068
+ """Convert raw event data to appropriate object type.
1069
+
1070
+ Args:
1071
+ handler_type (UpdatesTypes): Type of the update
1072
+ event (Dict[str, Any]): Raw event data
1073
+
1074
+ Returns:
1075
+ Any: Converted event object
1076
+ """
1077
+ if handler_type in (
1078
+ UpdatesTypes.MESSAGE,
1079
+ UpdatesTypes.MESSAGE_EDITED,
1080
+ UpdatesTypes.MEMBER_JOINED,
1081
+ UpdatesTypes.MEMBER_LEFT,
1082
+ UpdatesTypes.SUCCESSFUL_PAYMENT,
1083
+ UpdatesTypes.COMMAND
1084
+ ):
1085
+ if (
1086
+ event.get("new_chat_member", False)
1087
+ and handler_type == UpdatesTypes.MEMBER_JOINED
1088
+ ):
1089
+ return (
1090
+ ChatMember(
1091
+ kwargs={"client": self},
1092
+ **pythonize(event.get("new_chat_member", {})),
1093
+ ),
1094
+ Chat(kwargs={"client": self}, **pythonize(event.get("chat", {}))),
1095
+ Message(
1096
+ kwargs={"client": self}, **pythonize(event.get("message", {}))
1097
+ ),
1098
+ )
1099
+ elif (
1100
+ event.get("left_chat_member", False)
1101
+ and handler_type == UpdatesTypes.MEMBER_LEFT
1102
+ ):
1103
+ return (
1104
+ ChatMember(
1105
+ kwargs={"client": self},
1106
+ **pythonize(event.get("left_chat_member", {})),
1107
+ ),
1108
+ Chat(kwargs={"client": self}, **pythonize(event.get("chat", {}))),
1109
+ Message(
1110
+ kwargs={"client": self}, **pythonize(event.get("message", {}))
1111
+ ),
1112
+ )
1113
+
1114
+ elif (
1115
+ event.get("successful_payment", False)
1116
+ and handler_type == UpdatesTypes.SUCCESSFUL_PAYMENT
1117
+ ):
1118
+ return Message(
1119
+ **pythonize(event.get("successful_payment", {})),
1120
+ kwargs={"client": self},
1121
+ )
1122
+
1123
+ else:
1124
+ return Message(kwargs={"client": self}, **pythonize(event))
1125
+
505
1126
  elif handler_type == UpdatesTypes.CALLBACK_QUERY:
506
1127
  return CallbackQuery(kwargs={"client": self}, **pythonize(event))
1128
+
507
1129
  elif handler_type == UpdatesTypes.PRE_CHECKOUT_QUERY:
508
1130
  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
- )
1131
+
517
1132
  return event
518
1133
 
519
- def add_handler(
520
- self,
521
- update_type: UpdatesTypes,
522
- callback: Callable[[Any], Union[None, Awaitable[None]]],
523
- ) -> None:
524
- """Register a handler for specific update type."""
525
- self.handlers.append({"type": update_type, "callback": callback})
1134
+
1135
+ def add_handler(self, update_type, callback, filter: Optional[Filters] = None, **kwargs):
1136
+ """Register a handler for specific update type.
1137
+
1138
+ Args:
1139
+ update_type (UpdatesTypes): Type of update to handle
1140
+ callback (Callable): Function to call when update is received
1141
+ """
1142
+ data = {
1143
+ "type": update_type,
1144
+ "callback": callback,
1145
+ "filter": filter,
1146
+ }
1147
+ data.update(kwargs)
1148
+ self.handlers.append(data)
1149
+
526
1150
 
527
1151
  def remove_handler(
528
1152
  self, callback: Callable[[Any], Union[None, Awaitable[None]]]
529
1153
  ) -> None:
530
- """Remove a handler from the list of handlers."""
1154
+ """Remove a handler from the list of handlers.
1155
+
1156
+ Args:
1157
+ callback (Callable): Handler function to remove
1158
+ """
531
1159
  self.handlers = [
532
1160
  handler for handler in self.handlers if handler["callback"] != callback
533
1161
  ]
@@ -537,30 +1165,42 @@ class Client:
537
1165
  self.handlers = []
538
1166
 
539
1167
  async def start_polling(self, timeout: int = 30, limit: int = 100) -> None:
540
- """Start polling updates from the server."""
1168
+ """Start polling updates from the server.
1169
+
1170
+ Args:
1171
+ timeout (int, optional): Timeout in seconds for long polling. Defaults to 30.
1172
+ limit (int, optional): Maximum number of updates to retrieve. Defaults to 100.
1173
+
1174
+ Raises:
1175
+ RuntimeError: If client is already running
1176
+ """
541
1177
  if self.running:
542
1178
  raise RuntimeError("Client is already running")
543
1179
 
544
1180
  self.running = True
545
1181
  while self.running:
546
- if 1:
1182
+ try:
547
1183
  updates = await self.get_updates(
548
1184
  offset=self.last_update_id, limit=limit, timeout=timeout
549
1185
  )
550
1186
 
551
1187
  for update in updates:
552
1188
  await self.process_update(update)
553
-
554
- else: # except Exception as e:
555
- # print(f"Error while polling updates: {e}")
556
- await asyncio.sleep(5)
1189
+
1190
+ except Exception as e:
1191
+ raise e
557
1192
 
558
1193
  async def stop_polling(self) -> None:
559
1194
  """Stop polling updates."""
560
1195
  self.running = False
561
1196
 
562
1197
  def run(self, timeout: int = 30, limit: int = 100) -> None:
563
- """Run the client."""
1198
+ """Run the client.
1199
+
1200
+ Args:
1201
+ timeout (int, optional): Timeout in seconds for long polling. Defaults to 30.
1202
+ limit (int, optional): Maximum number of updates to retrieve. Defaults to 100.
1203
+ """
564
1204
  loop = asyncio.get_event_loop()
565
1205
  loop.run_until_complete(self.start_polling(timeout, limit))
566
1206
 
@@ -570,5 +1210,9 @@ class Client:
570
1210
  loop.run_until_complete(self.stop_polling())
571
1211
 
572
1212
  async def handle_webhook_update(self, update_data: Dict[str, Any]) -> None:
573
- """Process an update received via webhook."""
1213
+ """Process an update received via webhook.
1214
+
1215
+ Args:
1216
+ update_data (Dict[str, Any]): Update data received from webhook
1217
+ """
574
1218
  await self.process_update(update_data)