pyrobale 0.2.2__py3-none-any.whl → 0.2.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.
pyrobale.py CHANGED
@@ -1,19 +1,143 @@
1
- import requests, json, time, threading
1
+ """
2
+ PyRobale - A Python library for developing bale bots.
3
+
4
+ Features:
5
+ - Simple and fast
6
+ - Customizable and Customizes
7
+ - Eazy to learn
8
+ - New and Up to date
9
+ - Internal database management
10
+ """
11
+ import json
12
+ import time
2
13
  from typing import Optional, Dict, Any, List, Union
14
+ import threading
3
15
  import traceback
4
16
  import sqlite3
5
17
  import inspect
6
- import asyncio
18
+ import requests
19
+ import re
20
+ import sys
21
+ import os
22
+ from urllib.parse import urlparse, unquote
23
+
24
+ __version__ = '0.2.8'
25
+
26
+ class ChatActions:
27
+ """Represents different chat action states that can be sent to Bale"""
28
+ TYPING: str = 'typing'
29
+ PHOTO: str = 'upload_photo'
30
+ VIDEO: str = 'record_video'
31
+ CHOOSE_STICKER: str = 'choose_sticker'
32
+ class conditions:
33
+
34
+ """
35
+ A class for defining conditions for message handling.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ client: 'Client' = None,
41
+ user: 'User' = None,
42
+ message: 'Message' = None,
43
+ callback: 'CallbackQuery' = None,
44
+ chat: 'Chat' = None):
45
+ self.client = client
46
+ self.user = user
47
+ self.message = message
48
+ self.callback = callback
49
+ self.chat = chat
50
+
51
+ def is_joined(self, *chats) -> bool:
52
+ """Check if user is member of all specified chats"""
53
+ return all(self.client.is_joined(chat_id, self.user.id)
54
+ for chat_id in chats)
55
+
56
+ def is_admin(self, chat: 'Chat') -> bool:
57
+ """Check if user is an admin"""
58
+ member = self.client.get_chat_member(chat.id, self.user.id)
59
+ return member.status in ['administrator', 'owner']
60
+
61
+ def is_owner(self, chat: 'Chat') -> bool:
62
+ """Check if user is the owner"""
63
+ member = self.client.get_chat_member(chat.id, self.user.id)
64
+ return member.is_creator
65
+
66
+ def is_private_chat(self) -> bool:
67
+ """Check if message is in private chat"""
68
+ return self.chat.type == 'private'
69
+
70
+ def is_group_chat(self) -> bool:
71
+ """Check if message is in group chat"""
72
+ return self.chat.type in ['group', 'supergroup']
73
+
74
+ def is_channel(self) -> bool:
75
+ """Check if message is in channel"""
76
+ return self.chat.type == 'channel'
77
+
78
+ def has_text(self) -> bool:
79
+ """Check if message contains text"""
80
+ return bool(self.message.text)
81
+
82
+ def has_photo(self) -> bool:
83
+ """Check if message contains photo"""
84
+ return bool(self.message.photo)
85
+
86
+ def has_document(self) -> bool:
87
+ """Check if message contains document"""
88
+ return bool(self.message.document)
89
+
90
+ def is_reply(self) -> bool:
91
+ """Check if message is a reply"""
92
+ return bool(self.message.reply_to_message)
93
+
94
+ def is_forwarded(self) -> bool:
95
+ """Check if message is forwarded"""
96
+ return bool(self.message.forward_from)
97
+
98
+ def at_state(self, state: str) -> bool:
99
+ """Check if user is in the specified state"""
100
+ return self.client.get_state(self.chat.id) == state
101
+
102
+ def is_bot(self) -> bool:
103
+ """Check if user is a bot"""
104
+ return self.user.is_bot
105
+
106
+ def is_user(self) -> bool:
107
+ """Check if user is a user"""
108
+ return not self.user.is_bot
109
+
110
+ def is_admin_or_owner(self, chat: 'Chat') -> bool:
111
+ """Check if user is an admin or owner"""
112
+ return self.is_admin(chat) or self.is_owner(chat)
113
+
114
+ def matches_regex(self, pattern: str) -> bool:
115
+ """Check if message text matches regex pattern"""
116
+ if not self.message.text:
117
+ return False
118
+ return bool(re.match(pattern, self.message.text))
119
+
120
+ def contains_url(self) -> bool:
121
+ """Check if message contains url"""
122
+ if not self.message.text:
123
+ return False
124
+ return bool(
125
+ re.search(
126
+ r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',
127
+ self.message.text))
7
128
 
8
- __version__ = '0.2.2'
9
129
 
10
130
  class DataBase:
131
+ """
132
+ Database class for managing key-value pairs in a SQLite database.
133
+ """
134
+
11
135
  def __init__(self, name):
12
136
  self.name = name
13
137
  self.conn = None
14
138
  self.cursor = None
15
139
  self._initialize_db()
16
-
140
+
17
141
  def _initialize_db(self):
18
142
  self.conn = sqlite3.connect(self.name)
19
143
  self.cursor = self.conn.cursor()
@@ -21,94 +145,106 @@ class DataBase:
21
145
  (key TEXT PRIMARY KEY, value TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
22
146
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
23
147
  self.conn.commit()
24
-
148
+
25
149
  def __enter__(self):
26
150
  return self
27
-
151
+
28
152
  def __exit__(self, exc_type, exc_val, exc_tb):
29
153
  self.close()
30
-
154
+
31
155
  def close(self):
32
156
  if self.conn:
33
157
  self.conn.close()
34
158
  self.conn = None
35
159
  self.cursor = None
36
-
160
+
37
161
  def read_database(self, include_timestamps=False):
38
162
  if not self.conn:
39
163
  self._initialize_db()
40
164
  if include_timestamps:
41
- self.cursor.execute("SELECT key, value, created_at, updated_at FROM key_value_store")
165
+ self.cursor.execute(
166
+ "SELECT key, value, created_at, updated_at FROM key_value_store")
42
167
  rows = self.cursor.fetchall()
43
- return {key: {'value': json.loads(value), 'created_at': created, 'updated_at': updated}
44
- for key, value, created, updated in rows}
168
+ return {
169
+ key: {
170
+ 'value': json.loads(value),
171
+ 'created_at': created,
172
+ 'updated_at': updated} for key,
173
+ value,
174
+ created,
175
+ updated in rows}
45
176
  else:
46
177
  self.cursor.execute("SELECT key, value FROM key_value_store")
47
178
  rows = self.cursor.fetchall()
48
179
  return {key: json.loads(value) for key, value in rows}
49
-
180
+
50
181
  def write_database(self, data_dict):
51
182
  if not self.conn:
52
183
  self._initialize_db()
53
184
  for key, value in data_dict.items():
54
185
  self.cursor.execute("""
55
- INSERT INTO key_value_store (key, value, updated_at)
186
+ INSERT INTO key_value_store (key, value, updated_at)
56
187
  VALUES (?, ?, CURRENT_TIMESTAMP)
57
- ON CONFLICT(key) DO UPDATE SET
188
+ ON CONFLICT(key) DO UPDATE SET
58
189
  value=excluded.value, updated_at=CURRENT_TIMESTAMP""",
59
- (key, json.dumps(value, default=str)))
190
+ (key, json.dumps(value, default=str)))
60
191
  self.conn.commit()
61
-
192
+
62
193
  def read_key(self, key: str, default=None):
63
194
  if not self.conn:
64
195
  self._initialize_db()
65
- self.cursor.execute("SELECT value FROM key_value_store WHERE key = ?", (key,))
196
+ self.cursor.execute(
197
+ "SELECT value FROM key_value_store WHERE key = ?", (key,))
66
198
  result = self.cursor.fetchone()
67
199
  return json.loads(result[0]) if result else default
68
-
200
+
69
201
  def write_key(self, key: str, value):
70
202
  if not self.conn:
71
203
  self._initialize_db()
72
204
  self.cursor.execute("""
73
- INSERT INTO key_value_store (key, value, updated_at)
205
+ INSERT INTO key_value_store (key, value, updated_at)
74
206
  VALUES (?, ?, CURRENT_TIMESTAMP)
75
- ON CONFLICT(key) DO UPDATE SET
207
+ ON CONFLICT(key) DO UPDATE SET
76
208
  value=excluded.value, updated_at=CURRENT_TIMESTAMP""",
77
- (key, json.dumps(value, default=str)))
209
+ (key, json.dumps(value, default=str)))
78
210
  self.conn.commit()
79
-
211
+
80
212
  def delete_key(self, key: str):
81
213
  if not self.conn:
82
214
  self._initialize_db()
83
- self.cursor.execute("DELETE FROM key_value_store WHERE key = ?", (key,))
215
+ self.cursor.execute(
216
+ "DELETE FROM key_value_store WHERE key = ?", (key,))
84
217
  self.conn.commit()
85
-
218
+
86
219
  def keys(self):
87
220
  if not self.conn:
88
221
  self._initialize_db()
89
222
  self.cursor.execute("SELECT key FROM key_value_store")
90
223
  return [row[0] for row in self.cursor.fetchall()]
91
-
224
+
92
225
  def clear(self):
93
226
  if not self.conn:
94
227
  self._initialize_db()
95
228
  self.cursor.execute("DELETE FROM key_value_store")
96
229
  self.conn.commit()
97
-
230
+
98
231
  def get_metadata(self, key: str):
99
232
  if not self.conn:
100
233
  self._initialize_db()
101
234
  self.cursor.execute("""
102
- SELECT created_at, updated_at
103
- FROM key_value_store
235
+ SELECT created_at, updated_at
236
+ FROM key_value_store
104
237
  WHERE key = ?""", (key,))
105
238
  result = self.cursor.fetchone()
106
- return {'created_at': result[0], 'updated_at': result[1]} if result else None
107
-
239
+ return {
240
+ 'created_at': result[0],
241
+ 'updated_at': result[1]} if result else None
242
+
108
243
  def exists(self, key: str) -> bool:
109
244
  if not self.conn:
110
245
  self._initialize_db()
111
- self.cursor.execute("SELECT 1 FROM key_value_store WHERE key = ?", (key,))
246
+ self.cursor.execute(
247
+ "SELECT 1 FROM key_value_store WHERE key = ?", (key,))
112
248
  return bool(self.cursor.fetchone())
113
249
 
114
250
 
@@ -116,7 +252,10 @@ class ChatMember:
116
252
  def __init__(self, client: 'Client', data: Dict[str, Any]):
117
253
  if data:
118
254
  self.status = data.get('status')
119
- self.user = User(client, {'ok': True, 'result': data.get('user', {})})
255
+ self.user = User(
256
+ client, {
257
+ 'ok': True, 'result': data.get(
258
+ 'user', {})})
120
259
  self.is_anonymous = data.get('is_anonymous')
121
260
  self.can_be_edited = data.get('can_be_edited')
122
261
  self.can_manage_chat = data.get('can_manage_chat')
@@ -129,12 +268,78 @@ class ChatMember:
129
268
  self.can_pin_messages = data.get('can_pin_messages')
130
269
  self.can_manage_topics = data.get('can_manage_topics')
131
270
  self.is_creator = data.get('status') == 'creator'
271
+
272
+
132
273
  class BaleException(Exception):
133
274
  """Base exception for Bale API errors"""
134
- def __init__(self, *args):
135
- super().__init__(*args)
136
- print(traceback.format_exc())
137
-
275
+
276
+ def __init__(self, message=None, error_code=None, response=None):
277
+ self.message = message
278
+ self.error_code = error_code
279
+ self.response = response
280
+
281
+ error_text = f"Error {error_code}: {message}" if error_code else message
282
+ super().__init__(error_text)
283
+
284
+ def __str__(self):
285
+ return f"{self.__class__.__name__}: {self.message}"
286
+
287
+
288
+ class BaleAPIError(BaleException):
289
+ """Exception for API-specific errors"""
290
+ pass
291
+
292
+
293
+ class BaleNetworkError(BaleException):
294
+ """Exception for network-related errors"""
295
+ pass
296
+
297
+
298
+ class BaleAuthError(BaleException):
299
+ """Exception for authentication errors"""
300
+ pass
301
+
302
+
303
+ class BaleValidationError(BaleException):
304
+ """Exception for data validation errors"""
305
+ pass
306
+
307
+
308
+ class BaleTimeoutError(BaleException):
309
+ """Exception for timeout errors"""
310
+ pass
311
+
312
+
313
+ class BaleNotFoundError(BaleException):
314
+ """Exception for when a resource is not found"""
315
+ pass
316
+
317
+
318
+ class BaleForbiddenError(BaleException):
319
+ """Exception for forbidden access errors"""
320
+ pass
321
+
322
+
323
+ class BaleServerError(BaleException):
324
+ """Exception for server-side errors"""
325
+ pass
326
+
327
+
328
+ class BaleRateLimitError(BaleException):
329
+ """Exception for rate limit errors"""
330
+ pass
331
+
332
+
333
+ class BaleTokenNotFoundError(BaleException):
334
+ """Exception for when a token is not found"""
335
+ pass
336
+
337
+
338
+ class BaleUnknownError(BaleException):
339
+ """Exception for unknown errors"""
340
+ pass
341
+
342
+
138
343
  class LabeledPrice:
139
344
  def __init__(self, label: str, amount: int):
140
345
  self.label = label
@@ -144,6 +349,7 @@ class LabeledPrice:
144
349
  "amount": self.amount
145
350
  }
146
351
 
352
+
147
353
  class Document:
148
354
  def __init__(self, data: dict):
149
355
  if data:
@@ -159,6 +365,7 @@ class Document:
159
365
  self.mime_type = None
160
366
  self.file_size = None
161
367
 
368
+
162
369
  class Invoice:
163
370
  def __init__(self, data: dict):
164
371
  if data:
@@ -174,8 +381,9 @@ class Invoice:
174
381
  self.currency = None
175
382
  self.total_amount = None
176
383
 
384
+
177
385
  class Photo:
178
- def __init__(self,data):
386
+ def __init__(self, data):
179
387
  if data:
180
388
  self.file_id = data.get('file_id')
181
389
  self.file_unique_id = data.get('file_unique_id')
@@ -189,6 +397,7 @@ class Photo:
189
397
  self.height = None
190
398
  self.file_size = None
191
399
 
400
+
192
401
  class Voice:
193
402
  def __init__(self, data: dict):
194
403
  if data:
@@ -204,8 +413,9 @@ class Voice:
204
413
  self.mime_type = None
205
414
  self.file_size = None
206
415
 
416
+
207
417
  class Location:
208
- def __init__(self,data: dict):
418
+ def __init__(self, data: dict):
209
419
  if data:
210
420
  self.long = self.longitude = data.get('longitude')
211
421
  self.lat = self.latitude = data.get('latitude')
@@ -213,6 +423,7 @@ class Location:
213
423
  self.longitude = self.long = None
214
424
  self.latitude = self.lat = None
215
425
 
426
+
216
427
  class Contact:
217
428
  def __init__(self, data: dict):
218
429
  if data:
@@ -226,24 +437,36 @@ class Contact:
226
437
  self.last_name = None
227
438
  self.user_id = None
228
439
 
440
+
229
441
  class MenuKeyboardButton:
230
- def __init__(self, text: str, request_contact: bool = False, request_location: bool = False):
442
+ def __init__(
443
+ self,
444
+ text: str,
445
+ request_contact: bool = False,
446
+ request_location: bool = False):
231
447
  if not text:
232
448
  raise ValueError("Text cannot be empty")
233
449
  if request_contact and request_location:
234
450
  raise ValueError("Cannot request both contact and location")
235
-
451
+
236
452
  self.button = {"text": text}
237
453
  if request_contact:
238
454
  self.button["request_contact"] = True
239
455
  if request_location:
240
456
  self.button["request_location"] = True
241
457
 
458
+
242
459
  class InlineKeyboardButton:
243
- def __init__(self, text: str, callback_data: Optional[str] = None, url: Optional[str] = None, web_app: Optional[str] = None):
460
+ def __init__(
461
+ self,
462
+ text: str,
463
+ callback_data: Optional[str] = None,
464
+ url: Optional[str] = None,
465
+ web_app: Optional[str] = None):
244
466
  self.button = {"text": text}
245
467
  if sum(bool(x) for x in [callback_data, url, web_app]) != 1:
246
- raise ValueError("Exactly one of callback_data, url, or web_app must be provided")
468
+ raise ValueError(
469
+ "Exactly one of callback_data, url, or web_app must be provided")
247
470
  if callback_data:
248
471
  self.button["callback_data"] = callback_data
249
472
  elif url:
@@ -251,11 +474,13 @@ class InlineKeyboardButton:
251
474
  elif web_app:
252
475
  self.button["web_app"] = {"url": web_app}
253
476
 
477
+
254
478
  class MenuKeyboardMarkup:
255
479
  def __init__(self):
256
480
  self.menu_keyboard = []
257
481
 
258
- def add(self, button: MenuKeyboardButton, row: int = 0) -> 'MenuKeyboardMarkup':
482
+ def add(self, button: MenuKeyboardButton,
483
+ row: int = 0) -> 'MenuKeyboardMarkup':
259
484
  if row < 0:
260
485
  raise ValueError("Row index cannot be negative")
261
486
  while len(self.menu_keyboard) <= row:
@@ -263,13 +488,13 @@ class MenuKeyboardMarkup:
263
488
  self.menu_keyboard[row].append(button.button)
264
489
  self.cleanup_empty_rows()
265
490
  return self
266
-
491
+
267
492
  def cleanup_empty_rows(self) -> None:
268
493
  self.menu_keyboard = [row for row in self.menu_keyboard if row]
269
-
494
+
270
495
  def clear(self) -> None:
271
496
  self.menu_keyboard = []
272
-
497
+
273
498
  def remove_button(self, text: str) -> bool:
274
499
  found = False
275
500
  for row in self.menu_keyboard:
@@ -279,16 +504,18 @@ class MenuKeyboardMarkup:
279
504
  found = True
280
505
  self.cleanup_empty_rows()
281
506
  return found
282
-
507
+
283
508
  @property
284
509
  def keyboard(self):
285
510
  return {"keyboard": self.menu_keyboard}
286
-
511
+
512
+
287
513
  class InlineKeyboardMarkup:
288
514
  def __init__(self):
289
515
  self.inline_keyboard = []
290
516
 
291
- def add(self, button: InlineKeyboardButton, row: int = 0) -> 'InlineKeyboardMarkup':
517
+ def add(self, button: InlineKeyboardButton,
518
+ row: int = 0) -> 'InlineKeyboardMarkup':
292
519
  if row < 0:
293
520
  raise ValueError("Row index cannot be negative")
294
521
  while len(self.inline_keyboard) <= row:
@@ -296,13 +523,13 @@ class InlineKeyboardMarkup:
296
523
  self.inline_keyboard[row].append(button.button)
297
524
  self.cleanup_empty_rows()
298
525
  return self
299
-
526
+
300
527
  def cleanup_empty_rows(self) -> None:
301
528
  self.inline_keyboard = [row for row in self.inline_keyboard if row]
302
-
529
+
303
530
  def clear(self) -> None:
304
531
  self.inline_keyboard = []
305
-
532
+
306
533
  def remove_button(self, text: str) -> bool:
307
534
  found = False
308
535
  for row in self.inline_keyboard:
@@ -312,13 +539,20 @@ class InlineKeyboardMarkup:
312
539
  found = True
313
540
  self.cleanup_empty_rows()
314
541
  return found
315
-
542
+
316
543
  @property
317
544
  def keyboard(self) -> dict:
318
545
  return {"inline_keyboard": self.inline_keyboard}
546
+
547
+
319
548
  class InputFile:
320
549
  """Represents a file to be uploaded"""
321
- def __init__(self, client: 'Client', file: Union[str, bytes], filename: Optional[str] = None):
550
+
551
+ def __init__(self,
552
+ client: 'Client',
553
+ file: Union[str,
554
+ bytes],
555
+ filename: Optional[str] = None):
322
556
  self.client = client
323
557
  self.filename = filename
324
558
  if isinstance(file, str):
@@ -329,40 +563,70 @@ class InputFile:
329
563
  else:
330
564
  try:
331
565
  self.file = open(file, 'rb')
332
- except IOError as e:
333
- raise BaleException(f"Failed to open file: {traceback.format_exc()}")
566
+ except IOError:
567
+ raise BaleException(
568
+ f"Failed to open file: {traceback.format_exc()}")
334
569
  else:
335
- self.file = file
336
-
570
+ self.file = file
571
+
337
572
  def __del__(self):
338
573
  if hasattr(self, 'file') and hasattr(self.file, 'close'):
339
574
  self.file.close()
340
-
575
+
341
576
  @property
342
577
  def to_bytes(self):
343
578
  if hasattr(self.file, 'read'):
344
579
  return self.file.read()
345
580
  return self.file
346
581
 
582
+
347
583
  class CallbackQuery:
348
584
  """Represents a callback query from a callback button"""
585
+
349
586
  def __init__(self, client: 'Client', data: Dict[str, Any]):
350
587
  if not data.get('ok'):
351
- raise BaleException(f"API request failed: {traceback.format_exc()}")
588
+ raise BaleException(
589
+ f"API request failed: {traceback.format_exc()}")
352
590
  self.client = client
353
591
  result = data.get('result', {})
354
592
  self.id = result.get('id')
355
- self.from_user = User(client, {'ok': True, 'result': result.get('from', {})})
356
- self.message = Message(client, {'ok': True, 'result': result.get('message', {})})
593
+ self.from_user = self.user = self.author = User(
594
+ client, {'ok': True, 'result': result.get('from', {})})
595
+ self.message = Message(
596
+ client, {
597
+ 'ok': True, 'result': result.get(
598
+ 'message', {})})
357
599
  self.inline_message_id = result.get('inline_message_id')
358
600
  self.chat_instance = result.get('chat_instance')
359
- self.data = result.get('data')
601
+ self.data = result.get('data')
602
+
603
+ def answer(self,
604
+ text: str,
605
+ reply_markup: Optional[Union[MenuKeyboardMarkup,
606
+ InlineKeyboardMarkup]] = None) -> 'Message':
607
+ return self.client.send_message(
608
+ chat_id=self.message.chat.id,
609
+ text=text,
610
+ reply_markup=reply_markup)
611
+
612
+ def reply(self,
613
+ text: str,
614
+ reply_markup: Optional[Union[MenuKeyboardMarkup,
615
+ InlineKeyboardMarkup]] = None) -> 'Message':
616
+ return self.client.send_message(
617
+ chat_id=self.message.chat.id,
618
+ text=text,
619
+ reply_markup=reply_markup,
620
+ reply_to_message=self.message.id)
621
+
360
622
 
361
623
  class Chat:
362
624
  """Represents a chat conversation"""
625
+
363
626
  def __init__(self, client: 'Client', data: Dict[str, Any]):
364
627
  if not data.get('ok'):
365
- raise BaleException(f"API request failed: {traceback.format_exc()}")
628
+ raise BaleException(
629
+ f"API request failed: {traceback.format_exc()}")
366
630
  self.client = client
367
631
  result = data.get('result', {})
368
632
  self.data = data
@@ -374,139 +638,268 @@ class Chat:
374
638
  self.invite_link = result.get('invite_link')
375
639
  self.photo = result.get('photo')
376
640
 
377
- def send_photo(self, photo: Union[str, bytes, InputFile],
378
- caption: Optional[str] = None,
379
- parse_mode: Optional[str] = None,
380
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
641
+ def send_photo(self,
642
+ photo: Union[str,
643
+ bytes,
644
+ InputFile],
645
+ caption: Optional[str] = None,
646
+ parse_mode: Optional[str] = None,
647
+ reply_markup: Union[MenuKeyboardMarkup,
648
+ InlineKeyboardMarkup] = None) -> 'Message':
381
649
  """Send a photo to a chat"""
382
650
  files = None
383
651
  data = {
384
652
  'chat_id': self.id,
385
653
  'caption': caption,
386
654
  'parse_mode': parse_mode,
387
- 'reply_markup': reply_markup.keyboard if isinstance(reply_markup, MenuKeyboardMarkup) else reply_markup.keyboard if isinstance(reply_markup, InlineKeyboardMarkup) else None
388
- }
389
-
655
+ 'reply_markup': reply_markup.keyboard if isinstance(
656
+ reply_markup,
657
+ MenuKeyboardMarkup) else reply_markup.keyboard if isinstance(
658
+ reply_markup,
659
+ InlineKeyboardMarkup) else None}
660
+
390
661
  if isinstance(photo, (bytes, InputFile)) or hasattr(photo, 'read'):
391
- files = {'photo': photo if not isinstance(photo, InputFile) else photo.file}
662
+ files = {
663
+ 'photo': photo if not isinstance(
664
+ photo, InputFile) else photo.file}
392
665
  else:
393
666
  data['photo'] = photo
394
-
395
- response = self.client._make_request('POST', 'sendPhoto', data=data, files=files)
396
- return Message(self.client, response)
397
667
 
398
- def send_message(self, text: str,
399
- parse_mode: Optional[str] = None,
400
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
401
- return self.client.send_message(self.id, text, parse_mode, reply_markup)
402
-
403
- def forward_message(self, from_chat_id: Union[int, str], message_id: int) -> 'Message':
404
- """Forward a message from another chat"""
405
- return self.client.forward_message(self.id, from_chat_id, message_id)
406
-
407
- def copy_message(self, from_chat_id: Union[int, str], message_id: int,
408
- caption: Optional[str] = None,
409
- parse_mode: Optional[str] = None,
410
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
411
- """Copy a message from another chat"""
412
- return self.client.copy_message(self.id, from_chat_id, message_id, caption, parse_mode, reply_markup)
413
-
414
- def send_audio(self, audio: Union[str, bytes, InputFile],
415
- caption: Optional[str] = None,
416
- parse_mode: Optional[str] = None,
417
- duration: Optional[int] = None,
418
- performer: Optional[str] = None,
419
- title: Optional[str] = None,
420
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
421
- """Send an audio file"""
422
- return self.client.send_audio(self.id, audio, caption, parse_mode, duration, performer, title, reply_markup)
423
-
424
- def send_document(self, document: Union[str, bytes, InputFile],
425
- caption: Optional[str] = None,
426
- parse_mode: Optional[str] = None,
427
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
428
- """Send a document"""
429
- return self.client.send_document(self.id, document, caption, parse_mode, reply_markup)
430
-
431
- def send_video(self, video: Union[str, bytes, InputFile],
432
- caption: Optional[str] = None,
433
- parse_mode: Optional[str] = None,
434
- duration: Optional[int] = None,
435
- width: Optional[int] = None,
436
- height: Optional[int] = None,
437
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
438
- """Send a video"""
439
- return self.client.send_video(self.id, video, caption, parse_mode, duration, width, height, reply_markup)
440
-
441
- def send_animation(self, animation: Union[str, bytes, InputFile],
442
- caption: Optional[str] = None,
443
- parse_mode: Optional[str] = None,
444
- duration: Optional[int] = None,
445
- width: Optional[int] = None,
446
- height: Optional[int] = None,
447
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
448
- """Send an animation"""
449
- return self.client.send_animation(self.id, animation, caption, parse_mode, duration, width, height, reply_markup)
450
-
451
- def send_voice(self, voice: Union[str, bytes, InputFile],
668
+ response = self.client._make_request(
669
+ 'POST', 'sendPhoto', data=data, files=files)
670
+ return Message(self.client, response)
671
+
672
+ def send_message(self,
673
+ text: str,
674
+ parse_mode: Optional[str] = None,
675
+ reply_markup: Union[MenuKeyboardMarkup,
676
+ InlineKeyboardMarkup] = None) -> 'Message':
677
+ return self.client.send_message(
678
+ self.id, text, parse_mode, reply_markup)
679
+
680
+ def forward_message(
681
+ self, from_chat_id: Union[int, str], message_id: int) -> 'Message':
682
+ """Forward a message from another chat"""
683
+ return self.client.forward_message(self.id, from_chat_id, message_id)
684
+
685
+ def copy_message(self,
686
+ from_chat_id: Union[int,
687
+ str],
688
+ message_id: int,
689
+ caption: Optional[str] = None,
690
+ parse_mode: Optional[str] = None,
691
+ reply_markup: Union[MenuKeyboardMarkup,
692
+ InlineKeyboardMarkup] = None) -> 'Message':
693
+ """Copy a message from another chat"""
694
+ return self.client.copy_message(
695
+ self.id,
696
+ from_chat_id,
697
+ message_id,
698
+ caption,
699
+ parse_mode,
700
+ reply_markup)
701
+
702
+ def send_audio(self,
703
+ audio: Union[str,
704
+ bytes,
705
+ InputFile],
706
+ caption: Optional[str] = None,
707
+ parse_mode: Optional[str] = None,
708
+ duration: Optional[int] = None,
709
+ performer: Optional[str] = None,
710
+ title: Optional[str] = None,
711
+ reply_markup: Union[MenuKeyboardMarkup,
712
+ InlineKeyboardMarkup] = None) -> 'Message':
713
+ """Send an audio file"""
714
+ return self.client.send_audio(
715
+ self.id,
716
+ audio,
717
+ caption,
718
+ parse_mode,
719
+ duration,
720
+ performer,
721
+ title,
722
+ reply_markup)
723
+
724
+ def send_document(self,
725
+ document: Union[str,
726
+ bytes,
727
+ InputFile],
452
728
  caption: Optional[str] = None,
453
729
  parse_mode: Optional[str] = None,
454
- duration: Optional[int] = None,
455
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
456
- """Send a voice message"""
457
- return self.client.send_voice(self.id, voice, caption, parse_mode, duration, reply_markup)
458
-
459
- def send_media_group(self, chat_id: Union[int, str],
460
- media: List[Dict],
461
- reply_to_message: Union['Message', int, str] = None) -> List['Message']:
462
- """Send a group of photos, videos, documents or audios as an album"""
463
- data = {
464
- 'chat_id': chat_id,
465
- 'media': media,
466
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
467
- }
468
- response = self._make_request('POST', 'sendMediaGroup', json=data)
469
- return [Message(self, msg) for msg in response]
470
-
471
- def send_location(self, latitude: float, longitude: float,
472
- live_period: Optional[int] = None,
473
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
474
- """Send a location"""
475
- return self.client.send_location(self.id, latitude, longitude, live_period, reply_markup)
476
-
477
- def send_contact(self, phone_number: str, first_name: str,
478
- last_name: Optional[str] = None,
479
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
480
- """Send a contact"""
481
- return self.client.send_contact(self.id, phone_number, first_name, last_name, reply_markup)
482
-
483
- def send_invoice(self,title:str,description:str,payload:str,provider_token:str,prices:list,photo_url: Optional[str] = None,reply_to_message: Union[int,str,'Message'] = None, reply_markup: Union[MenuKeyboardMarkup|InlineKeyboardMarkup] = None):
484
- return self.client.send_invoice(self.id,title,description,payload,provider_token,prices,photo_url,reply_to_message,reply_markup)
485
-
486
- def banChatMember(self, user_id: int, until_date: Optional[int] = None) -> bool:
730
+ reply_markup: Union[MenuKeyboardMarkup,
731
+ InlineKeyboardMarkup] = None) -> 'Message':
732
+ """Send a document"""
733
+ return self.client.send_document(
734
+ self.id, document, caption, parse_mode, reply_markup)
735
+
736
+ def send_video(self,
737
+ video: Union[str,
738
+ bytes,
739
+ InputFile],
740
+ caption: Optional[str] = None,
741
+ parse_mode: Optional[str] = None,
742
+ duration: Optional[int] = None,
743
+ width: Optional[int] = None,
744
+ height: Optional[int] = None,
745
+ reply_markup: Union[MenuKeyboardMarkup,
746
+ InlineKeyboardMarkup] = None) -> 'Message':
747
+ """Send a video"""
748
+ return self.client.send_video(
749
+ self.id,
750
+ video,
751
+ caption,
752
+ parse_mode,
753
+ duration,
754
+ width,
755
+ height,
756
+ reply_markup)
757
+
758
+ def send_animation(self,
759
+ animation: Union[str,
760
+ bytes,
761
+ InputFile],
762
+ caption: Optional[str] = None,
763
+ parse_mode: Optional[str] = None,
764
+ duration: Optional[int] = None,
765
+ width: Optional[int] = None,
766
+ height: Optional[int] = None,
767
+ reply_markup: Union[MenuKeyboardMarkup,
768
+ InlineKeyboardMarkup] = None) -> 'Message':
769
+ """Send an animation"""
770
+ return self.client.send_animation(
771
+ self.id,
772
+ animation,
773
+ caption,
774
+ parse_mode,
775
+ duration,
776
+ width,
777
+ height,
778
+ reply_markup)
779
+
780
+ def send_voice(self,
781
+ voice: Union[str,
782
+ bytes,
783
+ InputFile],
784
+ caption: Optional[str] = None,
785
+ parse_mode: Optional[str] = None,
786
+ duration: Optional[int] = None,
787
+ reply_markup: Union[MenuKeyboardMarkup,
788
+ InlineKeyboardMarkup] = None) -> 'Message':
789
+ """Send a voice message"""
790
+ return self.client.send_voice(
791
+ self.id,
792
+ voice,
793
+ caption,
794
+ parse_mode,
795
+ duration,
796
+ reply_markup)
797
+
798
+ def send_media_group(self,
799
+ chat_id: Union[int,
800
+ str],
801
+ media: List[Dict],
802
+ reply_to_message: Union['Message',
803
+ int,
804
+ str] = None) -> List['Message']:
805
+ """Send a group of photos, videos, documents or audios as an album"""
806
+ data = {
807
+ 'chat_id': chat_id,
808
+ 'media': media,
809
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
810
+ reply_to_message,
811
+ Message) else reply_to_message}
812
+ response = self._make_request('POST', 'sendMediaGroup', json=data)
813
+ return [Message(self, msg) for msg in response]
814
+
815
+ def send_location(self,
816
+ latitude: float,
817
+ longitude: float,
818
+ live_period: Optional[int] = None,
819
+ reply_markup: Union[MenuKeyboardMarkup,
820
+ InlineKeyboardMarkup] = None) -> 'Message':
821
+ """Send a location"""
822
+ return self.client.send_location(
823
+ self.id, latitude, longitude, live_period, reply_markup)
824
+
825
+ def send_contact(self,
826
+ phone_number: str,
827
+ first_name: str,
828
+ last_name: Optional[str] = None,
829
+ reply_markup: Union[MenuKeyboardMarkup,
830
+ InlineKeyboardMarkup] = None) -> 'Message':
831
+ """Send a contact"""
832
+ return self.client.send_contact(
833
+ self.id,
834
+ phone_number,
835
+ first_name,
836
+ last_name,
837
+ reply_markup)
838
+
839
+ def send_invoice(self,
840
+ title: str,
841
+ description: str,
842
+ payload: str,
843
+ provider_token: str,
844
+ prices: list,
845
+ photo_url: Optional[str] = None,
846
+ reply_to_message: Union[int,
847
+ str,
848
+ 'Message'] = None,
849
+ reply_markup: Union[MenuKeyboardMarkup | InlineKeyboardMarkup] = None):
850
+ return self.client.send_invoice(
851
+ self.id,
852
+ title,
853
+ description,
854
+ payload,
855
+ provider_token,
856
+ prices,
857
+ photo_url,
858
+ reply_to_message,
859
+ reply_markup)
860
+
861
+ def send_action(self, action: str, how_many_times = 1) -> bool:
862
+ """Send a chat action"""
863
+ return self.client.send_chat_action(self.id, action, how_many_times)
864
+
865
+ def banChatMember(
866
+ self,
867
+ user_id: int,
868
+ until_date: Optional[int] = None) -> bool:
487
869
  """Ban a user from the chat"""
488
870
  data = {
489
871
  'chat_id': self.id,
490
872
  'user_id': user_id,
491
873
  'until_date': until_date
492
874
  }
493
- response = self.client._make_request('POST', 'banChatMember', data=data)
875
+ response = self.client._make_request(
876
+ 'POST', 'banChatMember', data=data)
494
877
  return response.get('ok', False)
495
878
 
496
- def unbanChatMember(self, user_id: int, only_if_banned: bool = False) -> bool:
879
+ def unbanChatMember(
880
+ self,
881
+ user_id: int,
882
+ only_if_banned: bool = False) -> bool:
497
883
  """Unban a previously banned user from the chat"""
498
884
  data = {
499
885
  'chat_id': self.id,
500
886
  'user_id': user_id,
501
887
  'only_if_banned': only_if_banned
502
888
  }
503
- response = self.client._make_request('POST', 'unbanChatMember', data=data)
889
+ response = self.client._make_request(
890
+ 'POST', 'unbanChatMember', data=data)
504
891
  return response.get('ok', False)
505
892
 
506
- def promoteChatMember(self, user_id: int, can_change_info: bool = None,
507
- can_post_messages: bool = None, can_edit_messages: bool = None,
508
- can_delete_messages: bool = None, can_manage_video_chats: bool = None,
509
- can_invite_users: bool = None, can_restrict_members: bool = None) -> bool:
893
+ def promoteChatMember(
894
+ self,
895
+ user_id: int,
896
+ can_change_info: bool = None,
897
+ can_post_messages: bool = None,
898
+ can_edit_messages: bool = None,
899
+ can_delete_messages: bool = None,
900
+ can_manage_video_chats: bool = None,
901
+ can_invite_users: bool = None,
902
+ can_restrict_members: bool = None) -> bool:
510
903
  """Promote or demote a chat member"""
511
904
  data = {
512
905
  'chat_id': self.id,
@@ -519,20 +912,24 @@ class Chat:
519
912
  'can_invite_users': can_invite_users,
520
913
  'can_restrict_members': can_restrict_members
521
914
  }
522
- response = self.client._make_request('POST', 'promoteChatMember', data=data)
915
+ response = self.client._make_request(
916
+ 'POST', 'promoteChatMember', data=data)
523
917
  return response.get('ok', False)
524
-
918
+
525
919
  def setChatPhoto(self, photo: Union[str, bytes, InputFile]) -> bool:
526
920
  """Set a new chat photo"""
527
921
  files = None
528
922
  data = {'chat_id': self.id}
529
-
923
+
530
924
  if isinstance(photo, (bytes, InputFile)) or hasattr(photo, 'read'):
531
- files = {'photo': photo if not isinstance(photo, InputFile) else photo.file}
925
+ files = {
926
+ 'photo': photo if not isinstance(
927
+ photo, InputFile) else photo.file}
532
928
  else:
533
929
  data['photo'] = photo
534
-
535
- response = self.client._make_request('POST', 'setChatPhoto', data=data, files=files)
930
+
931
+ response = self.client._make_request(
932
+ 'POST', 'setChatPhoto', data=data, files=files)
536
933
  return response.get('ok', False)
537
934
 
538
935
  def leaveChat(self) -> bool:
@@ -550,17 +947,22 @@ class Chat:
550
947
  def getChatMembersCount(self) -> int:
551
948
  """Get the number of members in the chat"""
552
949
  data = {'chat_id': self.id}
553
- response = self.client._make_request('POST', 'getChatMembersCount', data=data)
950
+ response = self.client._make_request(
951
+ 'POST', 'getChatMembersCount', data=data)
554
952
  return response.get('result', 0)
555
953
 
556
- def pinChatMessage(self, message_id: int, disable_notification: bool = False) -> bool:
954
+ def pinChatMessage(
955
+ self,
956
+ message_id: int,
957
+ disable_notification: bool = False) -> bool:
557
958
  """Pin a message in the chat"""
558
959
  data = {
559
960
  'chat_id': self.id,
560
961
  'message_id': message_id,
561
962
  'disable_notification': disable_notification
562
963
  }
563
- response = self.client._make_request('POST', 'pinChatMessage', data=data)
964
+ response = self.client._make_request(
965
+ 'POST', 'pinChatMessage', data=data)
564
966
  return response.get('ok', False)
565
967
 
566
968
  def unPinChatMessage(self, message_id: int) -> bool:
@@ -569,13 +971,15 @@ class Chat:
569
971
  'chat_id': self.id,
570
972
  'message_id': message_id
571
973
  }
572
- response = self.client._make_request('POST', 'unpinChatMessage', data=data)
974
+ response = self.client._make_request(
975
+ 'POST', 'unpinChatMessage', data=data)
573
976
  return response.get('ok', False)
574
977
 
575
978
  def unpinAllChatMessages(self) -> bool:
576
979
  """Unpin all messages in the chat"""
577
980
  data = {'chat_id': self.id}
578
- response = self.client._make_request('POST', 'unpinAllChatMessages', data=data)
981
+ response = self.client._make_request(
982
+ 'POST', 'unpinAllChatMessages', data=data)
579
983
  return response.get('ok', False)
580
984
 
581
985
  def setChatTitle(self, title: str) -> bool:
@@ -593,19 +997,22 @@ class Chat:
593
997
  'chat_id': self.id,
594
998
  'description': description
595
999
  }
596
- response = self.client._make_request('POST', 'setChatDescription', data=data)
1000
+ response = self.client._make_request(
1001
+ 'POST', 'setChatDescription', data=data)
597
1002
  return response.get('ok', False)
598
1003
 
599
1004
  def deleteChatPhoto(self) -> bool:
600
1005
  """Delete the chat photo"""
601
1006
  data = {'chat_id': self.id}
602
- response = self.client._make_request('POST', 'deleteChatPhoto', data=data)
1007
+ response = self.client._make_request(
1008
+ 'POST', 'deleteChatPhoto', data=data)
603
1009
  return response.get('ok', False)
604
1010
 
605
1011
  def createChatInviteLink(self) -> str:
606
1012
  """Create an invite link for the chat"""
607
1013
  data = {'chat_id': self.id}
608
- response = self.client._make_request('POST', 'createChatInviteLink', data=data)
1014
+ response = self.client._make_request(
1015
+ 'POST', 'createChatInviteLink', data=data)
609
1016
  return response.get('result', {}).get('invite_link')
610
1017
 
611
1018
  def revokeChatInviteLink(self, invite_link: str) -> bool:
@@ -614,22 +1021,25 @@ class Chat:
614
1021
  'chat_id': self.id,
615
1022
  'invite_link': invite_link
616
1023
  }
617
- response = self.client._make_request('POST', 'revokeChatInviteLink', data=data)
1024
+ response = self.client._make_request(
1025
+ 'POST', 'revokeChatInviteLink', data=data)
618
1026
  return response.get('ok', False)
619
1027
 
620
1028
  def exportChatInviteLink(self) -> str:
621
1029
  """Generate a new invite link for the chat"""
622
1030
  data = {'chat_id': self.id}
623
- response = self.client._make_request('POST', 'exportChatInviteLink', data=data)
1031
+ response = self.client._make_request(
1032
+ 'POST', 'exportChatInviteLink', data=data)
624
1033
  return response.get('result')
625
1034
 
626
-
627
1035
 
628
1036
  class User:
629
1037
  """Represents a Bale user"""
1038
+
630
1039
  def __init__(self, client: 'Client', data: Dict[str, Any]):
631
1040
  if not data.get('ok'):
632
- raise BaleException(f"API request failed: {traceback.format_exc()}")
1041
+ raise BaleException(
1042
+ f"API request failed: {traceback.format_exc()}")
633
1043
  self.client = client
634
1044
  result = data.get('result', {})
635
1045
  self.data = data
@@ -639,233 +1049,521 @@ class User:
639
1049
  self.first_name = result.get('first_name')
640
1050
  self.last_name = result.get('last_name')
641
1051
  self.username = result.get('username')
642
-
1052
+
643
1053
  def set_state(self, state: str) -> None:
644
1054
  """Set the state for a chat or user"""
645
1055
  self.client.states[str(self.id)] = state
646
-
647
- def get_state(self) -> str|None:
1056
+
1057
+ def get_state(self) -> str | None:
648
1058
  """Get the state for a chat or user"""
649
1059
  return self.client.states.get(str(self.id))
650
-
1060
+
651
1061
  def del_state(self) -> None:
652
1062
  """Delete the state for a chat or user"""
653
1063
  self.client.states.pop(str(self.id), None)
654
-
655
1064
 
656
- def send_message(self, text: str, parse_mode: Optional[str] = None,
657
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
1065
+ def send_message(self,
1066
+ text: str,
1067
+ parse_mode: Optional[str] = None,
1068
+ reply_markup: Union[MenuKeyboardMarkup,
1069
+ InlineKeyboardMarkup] = None) -> 'Message':
658
1070
  """Send a message to this user"""
659
- return self.client.send_message(self.id, text, parse_mode, reply_markup)
660
-
661
- def send_photo(self, photo: Union[str, bytes, InputFile],
662
- caption: Optional[str] = None,
663
- parse_mode: Optional[str] = None,
664
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
1071
+ return self.client.send_message(
1072
+ self.id, text, parse_mode, reply_markup)
1073
+
1074
+ def send_photo(self,
1075
+ photo: Union[str,
1076
+ bytes,
1077
+ InputFile],
1078
+ caption: Optional[str] = None,
1079
+ parse_mode: Optional[str] = None,
1080
+ reply_markup: Union[MenuKeyboardMarkup,
1081
+ InlineKeyboardMarkup] = None) -> 'Message':
665
1082
  """Send a photo to a chat"""
666
- return self.client.send_photo(self.id, photo, caption, parse_mode, reply_markup)
667
-
668
- def forward_message(self, from_chat_id: Union[int, str], message_id: int) -> 'Message':
669
- """Forward a message to this user"""
670
- self.client.forward_message(self.id,from_chat_id,message_id)
671
-
672
- def copy_message(self, from_chat_id: Union[int, str], message_id: int) -> 'Message':
673
- """Copy a message to this user"""
674
- self.client.copy_message(self.id,from_chat_id,message_id)
675
-
676
- def send_audio(self, audio: Union[str, bytes, InputFile],
677
- caption: Optional[str] = None,
678
- parse_mode: Optional[str] = None,
679
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None, reply_to_message: Union[str,int,'Message'] = None) -> 'Message':
680
- """Send an audio file to this user"""
681
- self.client.send_audio(self.id, audio, caption, parse_mode, reply_markup, reply_to_message)
682
-
683
- def send_document(self, document: Union[str, bytes, InputFile],
684
- caption: Optional[str] = None,
685
- parse_mode: Optional[str] = None,
686
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None, reply_to_message: Union[str,int,'Message'] = None) -> 'Message':
687
- """Send a document to this user"""
688
- self.client.send_document(self.id, document, caption, parse_mode, reply_markup, reply_markup)
689
-
690
- def send_video(self, video: Union[str, bytes, InputFile],
1083
+ return self.client.send_photo(
1084
+ self.id, photo, caption, parse_mode, reply_markup)
1085
+
1086
+ def forward_message(
1087
+ self, from_chat_id: Union[int, str], message_id: int) -> 'Message':
1088
+ """Forward a message to this user"""
1089
+ self.client.forward_message(self.id, from_chat_id, message_id)
1090
+
1091
+ def copy_message(self,
1092
+ from_chat_id: Union[int,
1093
+ str],
1094
+ message_id: int) -> 'Message':
1095
+ """Copy a message to this user"""
1096
+ self.client.copy_message(self.id, from_chat_id, message_id)
1097
+
1098
+ def send_audio(self,
1099
+ audio: Union[str,
1100
+ bytes,
1101
+ InputFile],
1102
+ caption: Optional[str] = None,
1103
+ parse_mode: Optional[str] = None,
1104
+ reply_markup: Union[MenuKeyboardMarkup,
1105
+ InlineKeyboardMarkup] = None,
1106
+ reply_to_message: Union[str,
1107
+ int,
1108
+ 'Message'] = None) -> 'Message':
1109
+ """Send an audio file to this user"""
1110
+ self.client.send_audio(
1111
+ self.id,
1112
+ audio,
1113
+ caption,
1114
+ parse_mode,
1115
+ reply_markup,
1116
+ reply_to_message)
1117
+
1118
+ def send_document(self,
1119
+ document: Union[str,
1120
+ bytes,
1121
+ InputFile],
691
1122
  caption: Optional[str] = None,
692
1123
  parse_mode: Optional[str] = None,
693
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None, reply_to_message: Union[str,int,'Message'] = None) -> 'Message':
694
- """Send a video to this user"""
695
- self.client.send_video(self.id,video,caption,parse_mode,reply_markup,reply_to_message)
696
-
697
- def send_animation(self, animation: Union[str, bytes, InputFile],
698
- caption: Optional[str] = None,
699
- parse_mode: Optional[str] = None,
700
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None, reply_to_message: Union[int,str,'Message'] = None) -> 'Message':
701
- """Send an animation to this user"""
702
- return self.client.send_animation(self.id,animation,caption,parse_mode,reply_markup,reply_to_message)
703
-
704
- def send_voice(self, voice: Union[str, bytes, InputFile],
705
- caption: Optional[str] = None,
706
- parse_mode: Optional[str] = None,
707
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,reply_to_message: Union[int,str,'Message'] = None) -> 'Message':
1124
+ reply_markup: Union[MenuKeyboardMarkup,
1125
+ InlineKeyboardMarkup] = None,
1126
+ reply_to_message: Union[str,
1127
+ int,
1128
+ 'Message'] = None) -> 'Message':
1129
+ """Send a document to this user"""
1130
+ self.client.send_document(
1131
+ self.id,
1132
+ document,
1133
+ caption,
1134
+ parse_mode,
1135
+ reply_markup,
1136
+ reply_markup)
1137
+
1138
+ def send_video(self,
1139
+ video: Union[str,
1140
+ bytes,
1141
+ InputFile],
1142
+ caption: Optional[str] = None,
1143
+ parse_mode: Optional[str] = None,
1144
+ reply_markup: Union[MenuKeyboardMarkup,
1145
+ InlineKeyboardMarkup] = None,
1146
+ reply_to_message: Union[str,
1147
+ int,
1148
+ 'Message'] = None) -> 'Message':
1149
+ """Send a video to this user"""
1150
+ self.client.send_video(
1151
+ self.id,
1152
+ video,
1153
+ caption,
1154
+ parse_mode,
1155
+ reply_markup,
1156
+ reply_to_message)
1157
+
1158
+ def send_animation(self,
1159
+ animation: Union[str,
1160
+ bytes,
1161
+ InputFile],
1162
+ caption: Optional[str] = None,
1163
+ parse_mode: Optional[str] = None,
1164
+ reply_markup: Union[MenuKeyboardMarkup,
1165
+ InlineKeyboardMarkup] = None,
1166
+ reply_to_message: Union[int,
1167
+ str,
1168
+ 'Message'] = None) -> 'Message':
1169
+ """Send an animation to this user"""
1170
+ return self.client.send_animation(
1171
+ self.id,
1172
+ animation,
1173
+ caption,
1174
+ parse_mode,
1175
+ reply_markup,
1176
+ reply_to_message)
1177
+
1178
+ def send_voice(self,
1179
+ voice: Union[str,
1180
+ bytes,
1181
+ InputFile],
1182
+ caption: Optional[str] = None,
1183
+ parse_mode: Optional[str] = None,
1184
+ reply_markup: Union[MenuKeyboardMarkup,
1185
+ InlineKeyboardMarkup] = None,
1186
+ reply_to_message: Union[int,
1187
+ str,
1188
+ 'Message'] = None) -> 'Message':
708
1189
  """Send a voice message to this user"""
709
- return self.client.send_voice(self.id,voice,caption,parse_mode,reply_markup,reply_to_message)
710
-
711
- def send_media_group(self, chat_id: Union[int, str],
712
- media: List[Dict],
713
- reply_to_message: Union['Message', int, str] = None) -> List['Message']:
714
- """Send a group of photos, videos, documents or audios as an album"""
715
- return self.client.send_media_group(chat_id, media, reply_to_message)
716
-
717
-
718
- def send_location(self, latitude: float, longitude: float,
719
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,reply_to_message: Union[str,int,'Message'] = None) -> 'Message':
720
- """Send a location to this user"""
721
- return self.send_location(latitude,longitude,reply_markup,reply_to_message)
722
-
723
- def send_contact(self, phone_number: str, first_name: str,last_name: Optional[str] = None,reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None, reply_to_message: Union[str,int,'Message'] = None) -> 'Message':
724
- """Send a contact to this user"""
725
- return self.client.send_contact(self.id,phone_number,first_name,last_name,reply_markup,reply_to_message)
726
-
727
- def send_invoice(self,title:str,description:str,payload:str,provider_token:str,prices:list,photo_url: Optional[str] = None,reply_to_message: Union[int,str,'Message'] = None, reply_markup: Union[MenuKeyboardMarkup|InlineKeyboardMarkup] = None):
728
- return self.client.send_invoice(self.id,title,description,payload,provider_token,prices,photo_url,reply_to_message,reply_markup)
1190
+ return self.client.send_voice(
1191
+ self.id,
1192
+ voice,
1193
+ caption,
1194
+ parse_mode,
1195
+ reply_markup,
1196
+ reply_to_message)
1197
+
1198
+ def send_media_group(self,
1199
+ chat_id: Union[int,
1200
+ str],
1201
+ media: List[Dict],
1202
+ reply_to_message: Union['Message',
1203
+ int,
1204
+ str] = None) -> List['Message']:
1205
+ """Send a group of photos, videos, documents or audios as an album"""
1206
+ return self.client.send_media_group(chat_id, media, reply_to_message)
1207
+
1208
+ def send_location(self,
1209
+ latitude: float,
1210
+ longitude: float,
1211
+ reply_markup: Union[MenuKeyboardMarkup,
1212
+ InlineKeyboardMarkup] = None,
1213
+ reply_to_message: Union[str,
1214
+ int,
1215
+ 'Message'] = None) -> 'Message':
1216
+ """Send a location to this user"""
1217
+ return self.send_location(
1218
+ latitude,
1219
+ longitude,
1220
+ reply_markup,
1221
+ reply_to_message)
1222
+
1223
+ def send_contact(self,
1224
+ phone_number: str,
1225
+ first_name: str,
1226
+ last_name: Optional[str] = None,
1227
+ reply_markup: Union[MenuKeyboardMarkup,
1228
+ InlineKeyboardMarkup] = None,
1229
+ reply_to_message: Union[str,
1230
+ int,
1231
+ 'Message'] = None) -> 'Message':
1232
+ """Send a contact to this user"""
1233
+ return self.client.send_contact(
1234
+ self.id,
1235
+ phone_number,
1236
+ first_name,
1237
+ last_name,
1238
+ reply_markup,
1239
+ reply_to_message)
1240
+
1241
+ def send_invoice(self,
1242
+ title: str,
1243
+ description: str,
1244
+ payload: str,
1245
+ provider_token: str,
1246
+ prices: list,
1247
+ photo_url: Optional[str] = None,
1248
+ reply_to_message: Union[int,
1249
+ str,
1250
+ 'Message'] = None,
1251
+ reply_markup: Union[MenuKeyboardMarkup | InlineKeyboardMarkup] = None):
1252
+ return self.client.send_invoice(
1253
+ self.id,
1254
+ title,
1255
+ description,
1256
+ payload,
1257
+ provider_token,
1258
+ prices,
1259
+ photo_url,
1260
+ reply_to_message,
1261
+ reply_markup)
1262
+ def send_action(self, action: str, how_many_times: int = 1):
1263
+ """Send a chat action to this user"""
1264
+ return self.client.send_chat_action(self.id, action, how_many_times)
1265
+
729
1266
 
730
1267
  class Message:
731
1268
  """Represents a message in Bale"""
1269
+
732
1270
  def __init__(self, client: 'Client', data: Dict[str, Any]):
733
1271
  if not data.get('ok'):
734
- raise BaleException(f"API request failed: {traceback.format_exc()}")
1272
+ raise BaleException(
1273
+ f"API request failed: {traceback.format_exc()}")
735
1274
  self.client = client
736
1275
  self.ok = data.get('ok')
737
1276
  result = data.get('result', {})
738
-
739
- self.message_id = result.get('message_id')
740
- self.from_user = User(client,{'ok': True, 'result': result.get('from', {})})
1277
+ self.json_result = result
1278
+
1279
+ self.message_id = self.id = result.get('message_id')
1280
+ self.from_user = self.author = User(
1281
+ client, {'ok': True, 'result': result.get('from', {})})
741
1282
  self.date = result.get('date')
742
- self.chat = Chat(client, {'ok': True, 'result': result.get('chat', {})})
1283
+ self.chat = Chat(
1284
+ client, {
1285
+ 'ok': True, 'result': result.get(
1286
+ 'chat', {})})
743
1287
  self.text = result.get('text')
744
1288
  self.caption = result.get('caption')
745
1289
  self.document = Document(result.get('document'))
746
- self.photo = result.get('photo')
747
- self.video = result.get('video')
748
- self.audio = result.get('audio')
1290
+ self.photo = Document(result.get('photo'))
1291
+ self.video = Document(result.get('video'))
1292
+ self.audio = Document(result.get('audio'))
749
1293
  self.voice = Voice(result.get('voice'))
750
1294
  self.animation = result.get('animation')
751
1295
  self.contact = Contact(result.get('contact'))
752
1296
  self.location = Location(result.get('location'))
753
- self.forward_from = User(client, {'ok': True, 'result': result.get('forward_from', {})})
1297
+ self.forward_from = User(client, {'ok': True, 'result': result.get(
1298
+ 'forward_from', {})}) if result.get('forward_from') else None
754
1299
  self.forward_from_message_id = result.get('forward_from_message_id')
755
1300
  self.invoice = Invoice(result.get('invoice'))
1301
+ self.reply_to_message = Message(client, {'ok': True, 'result': result.get(
1302
+ 'reply_to_message', {})}) if result.get('reply_to_message') else None
1303
+ self.reply = self.reply_message
1304
+ self.send = lambda text, parse_mode=None, reply_markup=None: self.client.send_message(
1305
+ self.chat.id, text, parse_mode, reply_markup, reply_to_message=self)
1306
+
1307
+ self.command = None
1308
+ self.args = None
1309
+ txt = self.text.split(' ')
756
1310
 
1311
+ self.command = txt[0]
1312
+ self.has_slash_command = self.command.startswith('/')
1313
+ self.args = txt[1:]
1314
+
1315
+ self.start = self.command == '/start'
757
1316
 
758
- def edit(self, text: str, parse_mode: Optional[str] = None,
759
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
1317
+ def edit(self,
1318
+ text: str,
1319
+ parse_mode: Optional[str] = None,
1320
+ reply_markup: Union[MenuKeyboardMarkup,
1321
+ InlineKeyboardMarkup] = None) -> 'Message':
760
1322
  """Edit this message"""
761
- return self.client.edit_message(self.chat.id, self.message_id, text, parse_mode, reply_markup)
762
-
1323
+ return self.client.edit_message(
1324
+ self.chat.id,
1325
+ self.message_id,
1326
+ text,
1327
+ parse_mode,
1328
+ reply_markup)
1329
+
763
1330
  def delete(self) -> bool:
764
1331
  """Delete this message"""
765
1332
  return self.client.delete_message(self.chat.id, self.message_id)
766
-
767
- def reply_message(self, text: str, parse_mode: Optional[str] = None,
768
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
1333
+
1334
+ def reply_message(self,
1335
+ text: str,
1336
+ parse_mode: Optional[str] = None,
1337
+ reply_markup: Union[MenuKeyboardMarkup,
1338
+ InlineKeyboardMarkup] = None) -> 'Message':
769
1339
  """Send a message to this user"""
770
- return self.client.send_message(self.chat.id, text, parse_mode, reply_markup,reply_to_message=self)
771
-
772
- def reply_photo(self, photo: Union[str, bytes, InputFile],
773
- caption: Optional[str] = None,
774
- parse_mode: Optional[str] = None,
775
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
1340
+ return self.client.send_message(
1341
+ self.chat.id,
1342
+ text,
1343
+ parse_mode,
1344
+ reply_markup,
1345
+ reply_to_message=self)
1346
+
1347
+ def reply_photo(self,
1348
+ photo: Union[str,
1349
+ bytes,
1350
+ InputFile],
1351
+ caption: Optional[str] = None,
1352
+ parse_mode: Optional[str] = None,
1353
+ reply_markup: Union[MenuKeyboardMarkup,
1354
+ InlineKeyboardMarkup] = None) -> 'Message':
776
1355
  """Send a photo to a chat"""
777
- return self.client.send_photo(self.chat.id, photo, caption, parse_mode, reply_markup,reply_to_message=self)
778
-
779
-
780
- def reply_audio(self, audio: Union[str, bytes, InputFile],
781
- caption: Optional[str] = None,
782
- parse_mode: Optional[str] = None,
783
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
784
- """Send an audio file to this user"""
785
- self.client.send_audio(self.chat.id, audio, caption, parse_mode, reply_markup, reply_to_message=self)
786
-
787
- def reply_document(self, document: Union[str, bytes, InputFile],
788
- caption: Optional[str] = None,
789
- parse_mode: Optional[str] = None,
790
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
791
- """Send a document to this user"""
792
- self.client.send_document(self.chat.id, document, caption, parse_mode, reply_markup, reply_markup, reply_to_message=self)
793
-
794
- def reply_video(self, video: Union[str, bytes, InputFile],
795
- caption: Optional[str] = None,
796
- parse_mode: Optional[str] = None,
797
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
798
- """Send a video to this user"""
799
- self.client.send_video(self.chat.id,video,caption,parse_mode,reply_markup, reply_to_message=self)
800
-
801
- def reply_animation(self, animation: Union[str, bytes, InputFile],
802
- caption: Optional[str] = None,
803
- parse_mode: Optional[str] = None,
804
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
805
- """Send an animation to this user"""
806
- return self.client.send_animation(self.chat.id,animation,caption,parse_mode,reply_markup, reply_to_message=self)
807
-
808
- def reply_voice(self, voice: Union[str, bytes, InputFile],
1356
+ return self.client.send_photo(
1357
+ self.chat.id,
1358
+ photo,
1359
+ caption,
1360
+ parse_mode,
1361
+ reply_markup,
1362
+ reply_to_message=self)
1363
+
1364
+ def reply_audio(self,
1365
+ audio: Union[str,
1366
+ bytes,
1367
+ InputFile],
1368
+ caption: Optional[str] = None,
1369
+ parse_mode: Optional[str] = None,
1370
+ reply_markup: Union[MenuKeyboardMarkup,
1371
+ InlineKeyboardMarkup] = None) -> 'Message':
1372
+ """Send an audio file to this user"""
1373
+ self.client.send_audio(
1374
+ self.chat.id,
1375
+ audio,
1376
+ caption,
1377
+ parse_mode,
1378
+ reply_markup,
1379
+ reply_to_message=self)
1380
+
1381
+ def reply_document(self,
1382
+ document: Union[str,
1383
+ bytes,
1384
+ InputFile],
1385
+ caption: Optional[str] = None,
1386
+ parse_mode: Optional[str] = None,
1387
+ reply_markup: Union[MenuKeyboardMarkup,
1388
+ InlineKeyboardMarkup] = None) -> 'Message':
1389
+ """Send a document to this user"""
1390
+ self.client.send_document(
1391
+ self.chat.id,
1392
+ document,
1393
+ caption,
1394
+ parse_mode,
1395
+ reply_markup,
1396
+ reply_markup,
1397
+ reply_to_message=self)
1398
+
1399
+ def reply_video(self,
1400
+ video: Union[str,
1401
+ bytes,
1402
+ InputFile],
809
1403
  caption: Optional[str] = None,
810
1404
  parse_mode: Optional[str] = None,
811
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
1405
+ reply_markup: Union[MenuKeyboardMarkup,
1406
+ InlineKeyboardMarkup] = None) -> 'Message':
1407
+ """Send a video to this user"""
1408
+ self.client.send_video(
1409
+ self.chat.id,
1410
+ video,
1411
+ caption,
1412
+ parse_mode,
1413
+ reply_markup,
1414
+ reply_to_message=self)
1415
+
1416
+ def reply_animation(self,
1417
+ animation: Union[str,
1418
+ bytes,
1419
+ InputFile],
1420
+ caption: Optional[str] = None,
1421
+ parse_mode: Optional[str] = None,
1422
+ reply_markup: Union[MenuKeyboardMarkup,
1423
+ InlineKeyboardMarkup] = None) -> 'Message':
1424
+ """Send an animation to this user"""
1425
+ return self.client.send_animation(
1426
+ self.chat.id,
1427
+ animation,
1428
+ caption,
1429
+ parse_mode,
1430
+ reply_markup,
1431
+ reply_to_message=self)
1432
+
1433
+ def reply_voice(self,
1434
+ voice: Union[str,
1435
+ bytes,
1436
+ InputFile],
1437
+ caption: Optional[str] = None,
1438
+ parse_mode: Optional[str] = None,
1439
+ reply_markup: Union[MenuKeyboardMarkup,
1440
+ InlineKeyboardMarkup] = None) -> 'Message':
812
1441
  """Send a voice message to this user"""
813
- return self.client.send_voice(self.chat.id,voice,caption,parse_mode,reply_markup, reply_to_message=self)
814
-
815
- def reply_media_group(self, media: List[Dict],
816
- reply_to_message: Union['Message', int, str] = None) -> List['Message']:
817
- """Send a group of photos, videos, documents or audios as an album"""
818
- return self.client.send_media_group(self.chat.id, media, reply_to_message=self)
819
-
820
-
821
- def reply_location(self, latitude: float, longitude: float,
822
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> 'Message':
823
- """Send a location to this user"""
824
- return self.client.send_location(self.chat.id,latitude,longitude,reply_markup, reply_to_message=self)
825
-
826
- def reply_contact(self, phone_number: str, first_name: str,last_name: Optional[str] = None,reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None, reply_to_message: Union[str,int,'Message'] = None) -> 'Message':
827
- """Send a contact to this user"""
828
- return self.client.send_contact(self.chat.id,phone_number,first_name,last_name,reply_markup,reply_to_message)
829
-
830
- def reply_invoice(self,title:str,description:str,payload:str,provider_token:str,prices:list,photo_url: Optional[str] = None, reply_markup: Union[MenuKeyboardMarkup|InlineKeyboardMarkup] = None):
831
- return self.client.send_invoice(self.chat.id,title,description,payload,provider_token,prices,photo_url,self,reply_markup)
1442
+ return self.client.send_voice(
1443
+ self.chat.id,
1444
+ voice,
1445
+ caption,
1446
+ parse_mode,
1447
+ reply_markup,
1448
+ reply_to_message=self)
1449
+
1450
+ def reply_media_group(self,
1451
+ media: List[Dict],
1452
+ reply_to_message: Union['Message',
1453
+ int,
1454
+ str] = None) -> List['Message']:
1455
+ """Send a group of photos, videos, documents or audios as an album"""
1456
+ return self.client.send_media_group(
1457
+ self.chat.id, media, reply_to_message=self)
1458
+
1459
+ def reply_location(self,
1460
+ latitude: float,
1461
+ longitude: float,
1462
+ reply_markup: Union[MenuKeyboardMarkup,
1463
+ InlineKeyboardMarkup] = None) -> 'Message':
1464
+ """Send a location to this user"""
1465
+ return self.client.send_location(
1466
+ self.chat.id,
1467
+ latitude,
1468
+ longitude,
1469
+ reply_markup,
1470
+ reply_to_message=self)
1471
+
1472
+ def reply_contact(self,
1473
+ phone_number: str,
1474
+ first_name: str,
1475
+ last_name: Optional[str] = None,
1476
+ reply_markup: Union[MenuKeyboardMarkup,
1477
+ InlineKeyboardMarkup] = None,
1478
+ reply_to_message: Union[str,
1479
+ int,
1480
+ 'Message'] = None) -> 'Message':
1481
+ """Send a contact to this user"""
1482
+ return self.client.send_contact(
1483
+ self.chat.id,
1484
+ phone_number,
1485
+ first_name,
1486
+ last_name,
1487
+ reply_markup,
1488
+ reply_to_message)
1489
+
1490
+ def reply_invoice(self,
1491
+ title: str,
1492
+ description: str,
1493
+ payload: str,
1494
+ provider_token: str,
1495
+ prices: list,
1496
+ photo_url: Optional[str] = None,
1497
+ reply_markup: Union[MenuKeyboardMarkup | InlineKeyboardMarkup] = None):
1498
+ return self.client.send_invoice(
1499
+ self.chat.id,
1500
+ title,
1501
+ description,
1502
+ payload,
1503
+ provider_token,
1504
+ prices,
1505
+ photo_url,
1506
+ self,
1507
+ reply_markup)
1508
+
832
1509
 
833
1510
  class Client:
834
1511
  """Main client class for interacting with Bale API"""
835
- def __init__(self, token: str, session: str = 'https://tapi.bale.ai', database_name='database.db'):
1512
+
1513
+ def __init__(
1514
+ self,
1515
+ token: str,
1516
+ session: str = 'https://tapi.bale.ai',
1517
+ database_name='database.db',
1518
+ auto_log_start_message: bool = True,
1519
+ ):
836
1520
  self.token = token
837
1521
  self.session = session
838
1522
  self.states = {}
839
1523
  self.database_name = database_name
1524
+ self.auto_log_start_message = auto_log_start_message
840
1525
  self._base_url = f"{session}/bot{token}"
841
1526
  self._session = requests.Session()
842
1527
  self._message_handler = None
1528
+ self._message_edit_handler = None
1529
+ self._callback_handler = None
1530
+ self._member_leave_handler = None
1531
+ self._member_join_handler = None
843
1532
  self._threads = []
844
-
845
- def set_state(self, chat_or_user_id: Union[Chat,User,int,str], state: str) -> None:
1533
+
1534
+ def set_state(self,
1535
+ chat_or_user_id: Union[Chat,
1536
+ User,
1537
+ int,
1538
+ str],
1539
+ state: str) -> None:
846
1540
  """Set the state for a chat or user"""
847
1541
  if isinstance(chat_or_user_id, (Chat, User)):
848
1542
  chat_or_user_id = chat_or_user_id.id
849
1543
  self.states[str(chat_or_user_id)] = state
850
-
851
- def get_state(self, chat_or_user_id: Union[Chat,User,int,str]) -> str|None:
1544
+
1545
+ def get_state(self,
1546
+ chat_or_user_id: Union[Chat,
1547
+ User,
1548
+ int,
1549
+ str]) -> str | None:
852
1550
  """Get the state for a chat or user"""
853
1551
  if isinstance(chat_or_user_id, (Chat, User)):
854
1552
  chat_or_user_id = chat_or_user_id.id
855
1553
  return self.states.get(str(chat_or_user_id))
856
-
857
- def del_state(self, chat_or_user_id: Union[Chat,User,int,str]) -> None:
1554
+
1555
+ def del_state(self, chat_or_user_id: Union[Chat, User, int, str]) -> None:
858
1556
  """Delete the state for a chat or user"""
859
1557
  if isinstance(chat_or_user_id, (Chat, User)):
860
1558
  chat_or_user_id = chat_or_user_id.id
861
1559
  self.states.pop(str(chat_or_user_id), None)
862
-
1560
+
863
1561
  @property
864
1562
  def database(self) -> DataBase:
865
1563
  """Get the database name"""
866
- return DataBase(self.database_name)
867
-
868
-
1564
+ db = DataBase(self.database_name)
1565
+ return db
1566
+
869
1567
  def get_chat(self, chat_id: int) -> Optional[Dict]:
870
1568
  """Get chat information from database"""
871
1569
  conn = sqlite3.connect(self.database)
@@ -882,39 +1580,29 @@ class Client:
882
1580
  }
883
1581
  return None
884
1582
 
885
-
886
- def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
1583
+ def _make_request(self, method: str, endpoint: str,
1584
+ **kwargs) -> Dict[str, Any]:
887
1585
  """Make an HTTP request to Bale API"""
888
1586
  url = f"{self._base_url}/{endpoint}"
889
1587
  response = self._session.request(method, url, **kwargs)
890
1588
  response_data = response.json()
891
1589
  if not response_data.get('ok'):
892
- raise BaleException(response_data['error_code'], response_data['description'])
1590
+ raise BaleException(
1591
+ response_data['error_code'],
1592
+ response_data['description'])
893
1593
  return response_data
894
-
1594
+
895
1595
  def __del__(self):
896
1596
  if hasattr(self, '_session'):
897
1597
  self._session.close()
898
-
1598
+
899
1599
  def get_me(self) -> User:
900
1600
  """Get information about the bot"""
901
1601
  data = self._make_request('GET', 'getMe')
902
1602
  return User(self, data)
903
-
904
- def get_updates(self, offset: Optional[int] = None,
905
- limit: Optional[int] = None,
906
- timeout: Optional[int] = None) -> List[Dict[str, Any]]:
907
- """Get latest updates/messages"""
908
- params = {
909
- 'offset': offset,
910
- 'limit': limit,
911
- 'timeout': timeout
912
- }
913
- response = self._make_request('GET', 'getUpdates', params=params)
914
- return response.get('result', [])
915
-
1603
+
916
1604
  def set_webhook(self, url: str, certificate: Optional[str] = None,
917
- max_connections: Optional[int] = None) -> bool:
1605
+ max_connections: Optional[int] = None) -> bool:
918
1606
  """Set webhook for getting updates"""
919
1607
  data = {
920
1608
  'url': url,
@@ -922,33 +1610,39 @@ class Client:
922
1610
  'max_connections': max_connections
923
1611
  }
924
1612
  return self._make_request('POST', 'setWebhook', json=data)
925
-
1613
+
926
1614
  def get_webhook_info(self) -> Dict[str, Any]:
927
1615
  """Get current webhook status"""
928
1616
  return self._make_request('GET', 'getWebhookInfo')
929
-
930
- def send_message(self, chat_id: Union[int, str], text: str,
931
- parse_mode: Optional[str] = None,
932
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
933
- reply_to_message: Union[Message,int,str] = None) -> Message:
1617
+
1618
+ def send_message(self,
1619
+ chat_id: Union[int,
1620
+ str],
1621
+ text: str,
1622
+ parse_mode: Optional[str] = None,
1623
+ reply_markup: Union[MenuKeyboardMarkup,
1624
+ InlineKeyboardMarkup] = None,
1625
+ reply_to_message: Union[Message,
1626
+ int,
1627
+ str] = None) -> Message:
934
1628
  """Send a message to a chat"""
935
- # Convert text to string if it isn't already
1629
+
936
1630
  text = str(text)
937
-
1631
+
938
1632
  data = {
939
1633
  'chat_id': chat_id,
940
1634
  'text': text,
941
1635
  'parse_mode': parse_mode,
942
1636
  'reply_markup': reply_markup.keyboard if reply_markup else None,
943
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
944
- }
1637
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1638
+ reply_to_message,
1639
+ Message) else reply_to_message}
945
1640
  response = self._make_request('POST', 'sendMessage', json=data)
946
1641
  return Message(self, response)
947
1642
 
948
-
949
1643
  def forward_message(self, chat_id: Union[int, str],
950
- from_chat_id: Union[int, str],
951
- message_id: int) -> Message:
1644
+ from_chat_id: Union[int, str],
1645
+ message_id: int) -> Message:
952
1646
  """Forward a message from one chat to another"""
953
1647
  data = {
954
1648
  'chat_id': chat_id,
@@ -957,12 +1651,20 @@ class Client:
957
1651
  }
958
1652
  response = self._make_request('POST', 'forwardMessage', json=data)
959
1653
  return Message(self, response)
960
-
961
- def send_photo(self, chat_id: Union[int, str], photo: Union[str, bytes, InputFile],
962
- caption: Optional[str] = None,
963
- parse_mode: Optional[str] = None,
964
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
965
- reply_to_message: Union[Message, int, str] = None) -> Message:
1654
+
1655
+ def send_photo(self,
1656
+ chat_id: Union[int,
1657
+ str],
1658
+ photo: Union[str,
1659
+ bytes,
1660
+ InputFile],
1661
+ caption: Optional[str] = None,
1662
+ parse_mode: Optional[str] = None,
1663
+ reply_markup: Union[MenuKeyboardMarkup,
1664
+ InlineKeyboardMarkup] = None,
1665
+ reply_to_message: Union[Message,
1666
+ int,
1667
+ str] = None) -> Message:
966
1668
  """Send a photo to a chat"""
967
1669
  files = None
968
1670
  data = {
@@ -970,35 +1672,43 @@ class Client:
970
1672
  'caption': caption,
971
1673
  'parse_mode': parse_mode,
972
1674
  'reply_markup': reply_markup.keyboard if reply_markup else None,
973
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
974
- }
975
-
1675
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1676
+ reply_to_message,
1677
+ Message) else reply_to_message}
1678
+
976
1679
  if isinstance(photo, (bytes, InputFile)) or hasattr(photo, 'read'):
977
- files = {'photo': photo if not isinstance(photo, InputFile) else photo.file}
1680
+ files = {
1681
+ 'photo': photo if not isinstance(
1682
+ photo, InputFile) else photo.file}
978
1683
  else:
979
1684
  data['photo'] = photo
980
-
981
- response = self._make_request('POST', 'sendPhoto', data=data, files=files)
1685
+
1686
+ response = self._make_request(
1687
+ 'POST', 'sendPhoto', data=data, files=files)
982
1688
  return Message(self, response)
983
-
1689
+
984
1690
  def delete_message(self, chat_id: Union[int, str],
985
- message_id: int) -> bool:
1691
+ message_id: int) -> bool:
986
1692
  """Delete a message from a chat"""
987
1693
  data = {
988
1694
  'chat_id': chat_id,
989
1695
  'message_id': message_id
990
1696
  }
991
- return self._make_request('POST', 'deleteMessage', json=data)
992
-
1697
+ return self._make_request('POST', 'deleteMessage', json=data)
1698
+
993
1699
  def get_user(self, user_id: Union[int, str]) -> User:
994
1700
  """Get information about a user"""
995
1701
  data = self._make_request('POST', 'getChat', json={'chat_id': user_id})
996
1702
  return User(self, data)
997
-
998
- def edit_message(self, chat_id: Union[int, str],
999
- message_id: int, text: str,
1000
- parse_mode: Optional[str] = None,
1001
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None) -> Message:
1703
+
1704
+ def edit_message(self,
1705
+ chat_id: Union[int,
1706
+ str],
1707
+ message_id: int,
1708
+ text: str,
1709
+ parse_mode: Optional[str] = None,
1710
+ reply_markup: Union[MenuKeyboardMarkup,
1711
+ InlineKeyboardMarkup] = None) -> Message:
1002
1712
  """Edit a message in a chat"""
1003
1713
  data = {
1004
1714
  'chat_id': chat_id,
@@ -1009,17 +1719,23 @@ class Client:
1009
1719
  }
1010
1720
  response = self._make_request('POST', 'editMessageText', json=data)
1011
1721
  return Message(self, response)
1012
-
1722
+
1013
1723
  def get_chat(self, chat_id: Union[int, str]) -> Chat:
1014
1724
  """Get information about a chat"""
1015
1725
  data = self._make_request('POST', 'getChat', json={'chat_id': chat_id})
1016
1726
  return Chat(self, data)
1017
-
1018
- def send_audio(self, chat_id: Union[int, str], audio,
1019
- caption: Optional[str] = None,
1020
- parse_mode: Optional[str] = None,
1021
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
1022
- reply_to_message: Union[Message, int, str] = None) -> Message:
1727
+
1728
+ def send_audio(self,
1729
+ chat_id: Union[int,
1730
+ str],
1731
+ audio,
1732
+ caption: Optional[str] = None,
1733
+ parse_mode: Optional[str] = None,
1734
+ reply_markup: Union[MenuKeyboardMarkup,
1735
+ InlineKeyboardMarkup] = None,
1736
+ reply_to_message: Union[Message,
1737
+ int,
1738
+ str] = None) -> Message:
1023
1739
  """Send an audio file"""
1024
1740
  files = None
1025
1741
  data = {
@@ -1027,21 +1743,31 @@ class Client:
1027
1743
  'caption': caption,
1028
1744
  'parse_mode': parse_mode,
1029
1745
  'reply_markup': reply_markup.keyboard if reply_markup else None,
1030
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
1031
- }
1746
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1747
+ reply_to_message,
1748
+ Message) else reply_to_message}
1032
1749
  if isinstance(audio, (bytes, InputFile)) or hasattr(audio, 'read'):
1033
- files = {'audio': audio if not isinstance(audio, InputFile) else audio.file}
1750
+ files = {
1751
+ 'audio': audio if not isinstance(
1752
+ audio, InputFile) else audio.file}
1034
1753
  else:
1035
1754
  data['audio'] = audio
1036
-
1037
- response = self._make_request('POST', 'sendAudio', data=data, files=files)
1755
+
1756
+ response = self._make_request(
1757
+ 'POST', 'sendAudio', data=data, files=files)
1038
1758
  return Message(self, response)
1039
-
1040
- def send_document(self, chat_id: Union[int, str], document,
1041
- caption: Optional[str] = None,
1042
- parse_mode: Optional[str] = None,
1043
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
1044
- reply_to_message: Union[Message, int, str] = None) -> Message:
1759
+
1760
+ def send_document(self,
1761
+ chat_id: Union[int,
1762
+ str],
1763
+ document,
1764
+ caption: Optional[str] = None,
1765
+ parse_mode: Optional[str] = None,
1766
+ reply_markup: Union[MenuKeyboardMarkup,
1767
+ InlineKeyboardMarkup] = None,
1768
+ reply_to_message: Union[Message,
1769
+ int,
1770
+ str] = None) -> Message:
1045
1771
  """Send a document"""
1046
1772
  files = None
1047
1773
  data = {
@@ -1049,131 +1775,196 @@ class Client:
1049
1775
  'caption': caption,
1050
1776
  'parse_mode': parse_mode,
1051
1777
  'reply_markup': reply_markup.keyboard if reply_markup else None,
1052
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
1053
- }
1054
-
1055
- if isinstance(document, (bytes, InputFile)) or hasattr(document, 'read'):
1056
- files = {'document': document if not isinstance(document, InputFile) else document.file}
1778
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1779
+ reply_to_message,
1780
+ Message) else reply_to_message}
1781
+
1782
+ if isinstance(
1783
+ document, (bytes, InputFile)) or hasattr(
1784
+ document, 'read'):
1785
+ files = {'document': document if not isinstance(
1786
+ document, InputFile) else document.file}
1057
1787
  else:
1058
1788
  data['document'] = document
1059
-
1060
- response = self._make_request('POST', 'sendDocument', data=data, files=files)
1789
+
1790
+ response = self._make_request(
1791
+ 'POST', 'sendDocument', data=data, files=files)
1061
1792
  return Message(self, response)
1062
-
1063
- def send_video(self, chat_id: Union[int, str], video,
1064
- caption: Optional[str] = None,
1065
- parse_mode: Optional[str] = None,
1066
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
1067
- reply_to_message: Union[Message, int, str] = None) -> Message:
1068
- """Send a video"""
1069
- files = None
1070
- data = {
1071
- 'chat_id': chat_id,
1072
- 'caption': caption,
1073
- 'parse_mode': parse_mode,
1074
- 'reply_markup': reply_markup.keyboard if reply_markup else None,
1075
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
1076
- }
1077
-
1078
- if isinstance(video, (bytes, InputFile)) or hasattr(video, 'read'):
1079
- files = {'video': video if not isinstance(video, InputFile) else video.file}
1080
- else:
1081
- data['video'] = video
1082
-
1083
- response = self._make_request('POST', 'sendVideo', data=data, files=files)
1084
- return Message(self, response)
1085
-
1086
- def send_animation(self, chat_id: Union[int, str], animation,
1087
- caption: Optional[str] = None,
1088
- parse_mode: Optional[str] = None,
1089
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
1090
- reply_to_message: Union[Message, int, str] = None) -> Message:
1091
- """Send an animation (GIF or H.264/MPEG-4 AVC video without sound)"""
1092
- files = None
1093
- data = {
1094
- 'chat_id': chat_id,
1095
- 'caption': caption,
1096
- 'parse_mode': parse_mode,
1097
- 'reply_markup': reply_markup.keyboard if reply_markup else None,
1098
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
1099
- }
1100
-
1101
- if isinstance(animation, (bytes, InputFile)) or hasattr(animation, 'read'):
1102
- files = {'animation': animation if not isinstance(animation, InputFile) else animation.file}
1103
- else:
1104
- data['animation'] = animation
1105
-
1106
- response = self._make_request('POST', 'sendAnimation', data=data, files=files)
1107
- return Message(self, response)
1108
-
1109
- def send_voice(self, chat_id: Union[int, str], voice,
1793
+
1794
+ def send_video(self,
1795
+ chat_id: Union[int,
1796
+ str],
1797
+ video,
1798
+ caption: Optional[str] = None,
1799
+ parse_mode: Optional[str] = None,
1800
+ reply_markup: Union[MenuKeyboardMarkup,
1801
+ InlineKeyboardMarkup] = None,
1802
+ reply_to_message: Union[Message,
1803
+ int,
1804
+ str] = None) -> Message:
1805
+ """Send a video"""
1806
+ files = None
1807
+ data = {
1808
+ 'chat_id': chat_id,
1809
+ 'caption': caption,
1810
+ 'parse_mode': parse_mode,
1811
+ 'reply_markup': reply_markup.keyboard if reply_markup else None,
1812
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1813
+ reply_to_message,
1814
+ Message) else reply_to_message}
1815
+
1816
+ if isinstance(video, (bytes, InputFile)) or hasattr(video, 'read'):
1817
+ files = {
1818
+ 'video': video if not isinstance(
1819
+ video, InputFile) else video.file}
1820
+ else:
1821
+ data['video'] = video
1822
+
1823
+ response = self._make_request(
1824
+ 'POST', 'sendVideo', data=data, files=files)
1825
+ return Message(self, response)
1826
+
1827
+ def send_animation(self,
1828
+ chat_id: Union[int,
1829
+ str],
1830
+ animation,
1110
1831
  caption: Optional[str] = None,
1111
1832
  parse_mode: Optional[str] = None,
1112
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
1113
- reply_to_message: Union[Message, int, str] = None) -> Message:
1114
- """Send a voice message"""
1115
- files = None
1116
- data = {
1117
- 'chat_id': chat_id,
1118
- 'caption': caption,
1119
- 'parse_mode': parse_mode,
1120
- 'reply_markup': reply_markup.keyboard if reply_markup else None,
1121
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
1122
- }
1123
-
1124
- if isinstance(voice, (bytes, InputFile)) or hasattr(voice, 'read'):
1125
- files = {'voice': voice if not isinstance(voice, InputFile) else voice.file}
1126
- else:
1127
- data['voice'] = voice
1128
-
1129
- response = self._make_request('POST', 'sendVoice', data=data, files=files)
1130
- return Message(self, response)
1131
-
1132
- def send_media_group(self, chat_id: Union[int, str],
1133
- media: List[Dict],
1134
- reply_to_message: Union[Message, int, str] = None) -> List[Message]:
1135
- """Send a group of photos, videos, documents or audios as an album"""
1136
- data = {
1137
- 'chat_id': chat_id,
1138
- 'media': media,
1139
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
1140
- }
1141
- response = self._make_request('POST', 'sendMediaGroup', json=data)
1142
- return [Message(self, msg) for msg in response]
1143
-
1144
- def send_location(self, chat_id: Union[int, str],
1145
- latitude: float, longitude: float,
1146
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
1147
- reply_to_message: Union[Message, int, str] = None) -> Message:
1148
- """Send a point on the map"""
1149
- data = {
1150
- 'chat_id': chat_id,
1151
- 'latitude': latitude,
1152
- 'longitude': longitude,
1153
- 'reply_markup': reply_markup.keyboard if reply_markup else None,
1154
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
1155
- }
1156
- response = self._make_request('POST', 'sendLocation', json=data)
1157
- return Message(self, response)
1158
-
1159
- def send_contact(self, chat_id: Union[int, str],
1160
- phone_number: str, first_name: str,
1161
- last_name: Optional[str] = None,
1162
- reply_markup: Union[MenuKeyboardMarkup,InlineKeyboardMarkup] = None,
1163
- reply_to_message: Union[Message, int, str] = None) -> Message:
1164
- """Send a phone contact"""
1165
- data = {
1166
- 'chat_id': chat_id,
1167
- 'phone_number': phone_number,
1168
- 'first_name': first_name,
1169
- 'last_name': last_name,
1170
- 'reply_markup': reply_markup.keyboard if reply_markup else None,
1171
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message
1172
- }
1173
- response = self._make_request('POST', 'sendContact', json=data)
1174
- return Message(self, response)
1175
-
1176
- def send_invoice(self,chat_id: Union[int,str],title:str,description:str,payload:str,provider_token:str,prices:list,photo_url: Optional[str] = None,reply_to_message: Union[int|str|Message] = None, reply_markup: Union[MenuKeyboardMarkup|InlineKeyboardMarkup] = None) -> Message:
1833
+ reply_markup: Union[MenuKeyboardMarkup,
1834
+ InlineKeyboardMarkup] = None,
1835
+ reply_to_message: Union[Message,
1836
+ int,
1837
+ str] = None) -> Message:
1838
+ """Send an animation (GIF or H.264/MPEG-4 AVC video without sound)"""
1839
+ files = None
1840
+ data = {
1841
+ 'chat_id': chat_id,
1842
+ 'caption': caption,
1843
+ 'parse_mode': parse_mode,
1844
+ 'reply_markup': reply_markup.keyboard if reply_markup else None,
1845
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1846
+ reply_to_message,
1847
+ Message) else reply_to_message}
1848
+
1849
+ if isinstance(
1850
+ animation, (bytes, InputFile)) or hasattr(
1851
+ animation, 'read'):
1852
+ files = {'animation': animation if not isinstance(
1853
+ animation, InputFile) else animation.file}
1854
+ else:
1855
+ data['animation'] = animation
1856
+
1857
+ response = self._make_request(
1858
+ 'POST', 'sendAnimation', data=data, files=files)
1859
+ return Message(self, response)
1860
+
1861
+ def send_voice(self,
1862
+ chat_id: Union[int,
1863
+ str],
1864
+ voice,
1865
+ caption: Optional[str] = None,
1866
+ parse_mode: Optional[str] = None,
1867
+ reply_markup: Union[MenuKeyboardMarkup,
1868
+ InlineKeyboardMarkup] = None,
1869
+ reply_to_message: Union[Message,
1870
+ int,
1871
+ str] = None) -> Message:
1872
+ """Send a voice message"""
1873
+ files = None
1874
+ data = {
1875
+ 'chat_id': chat_id,
1876
+ 'caption': caption,
1877
+ 'parse_mode': parse_mode,
1878
+ 'reply_markup': reply_markup.keyboard if reply_markup else None,
1879
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1880
+ reply_to_message,
1881
+ Message) else reply_to_message}
1882
+
1883
+ if isinstance(voice, (bytes, InputFile)) or hasattr(voice, 'read'):
1884
+ files = {
1885
+ 'voice': voice if not isinstance(
1886
+ voice, InputFile) else voice.file}
1887
+ else:
1888
+ data['voice'] = voice
1889
+
1890
+ response = self._make_request(
1891
+ 'POST', 'sendVoice', data=data, files=files)
1892
+ return Message(self, response)
1893
+
1894
+ def send_media_group(self,
1895
+ chat_id: Union[int,
1896
+ str],
1897
+ media: List[Dict],
1898
+ reply_to_message: Union[Message,
1899
+ int,
1900
+ str] = None) -> List[Message]:
1901
+ """Send a group of photos, videos, documents or audios as an album"""
1902
+ data = {
1903
+ 'chat_id': chat_id,
1904
+ 'media': media,
1905
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1906
+ reply_to_message,
1907
+ Message) else reply_to_message}
1908
+ response = self._make_request('POST', 'sendMediaGroup', json=data)
1909
+ return [Message(self, msg) for msg in response]
1910
+
1911
+ def send_location(self,
1912
+ chat_id: Union[int,
1913
+ str],
1914
+ latitude: float,
1915
+ longitude: float,
1916
+ reply_markup: Union[MenuKeyboardMarkup,
1917
+ InlineKeyboardMarkup] = None,
1918
+ reply_to_message: Union[Message,
1919
+ int,
1920
+ str] = None) -> Message:
1921
+ """Send a point on the map"""
1922
+ data = {
1923
+ 'chat_id': chat_id,
1924
+ 'latitude': latitude,
1925
+ 'longitude': longitude,
1926
+ 'reply_markup': reply_markup.keyboard if reply_markup else None,
1927
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1928
+ reply_to_message,
1929
+ Message) else reply_to_message}
1930
+ response = self._make_request('POST', 'sendLocation', json=data)
1931
+ return Message(self, response)
1932
+
1933
+ def send_contact(self,
1934
+ chat_id: Union[int,
1935
+ str],
1936
+ phone_number: str,
1937
+ first_name: str,
1938
+ last_name: Optional[str] = None,
1939
+ reply_markup: Union[MenuKeyboardMarkup,
1940
+ InlineKeyboardMarkup] = None,
1941
+ reply_to_message: Union[Message,
1942
+ int,
1943
+ str] = None) -> Message:
1944
+ """Send a phone contact"""
1945
+ data = {
1946
+ 'chat_id': chat_id,
1947
+ 'phone_number': phone_number,
1948
+ 'first_name': first_name,
1949
+ 'last_name': last_name,
1950
+ 'reply_markup': reply_markup.keyboard if reply_markup else None,
1951
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1952
+ reply_to_message,
1953
+ Message) else reply_to_message}
1954
+ response = self._make_request('POST', 'sendContact', json=data)
1955
+ return Message(self, response)
1956
+
1957
+ def send_invoice(self,
1958
+ chat_id: Union[int,
1959
+ str],
1960
+ title: str,
1961
+ description: str,
1962
+ payload: str,
1963
+ provider_token: str,
1964
+ prices: list,
1965
+ photo_url: Optional[str] = None,
1966
+ reply_to_message: Union[int | str | Message] = None,
1967
+ reply_markup: Union[MenuKeyboardMarkup | InlineKeyboardMarkup] = None) -> Message:
1177
1968
  """Send a invoice"""
1178
1969
  r = []
1179
1970
  for x in prices:
@@ -1187,54 +1978,88 @@ class Client:
1187
1978
  'provider_token': provider_token,
1188
1979
  'prices': prices,
1189
1980
  'photo_url': photo_url,
1190
- 'reply_to_message_id': reply_to_message.message_id if isinstance(reply_to_message, Message) else reply_to_message,
1191
- 'reply_markup': reply_markup.keyboard if reply_markup else None
1192
- }
1981
+ 'reply_to_message_id': reply_to_message.message_id if isinstance(
1982
+ reply_to_message,
1983
+ Message) else reply_to_message,
1984
+ 'reply_markup': reply_markup.keyboard if reply_markup else None}
1193
1985
  response = self._make_request('POST', 'sendInvoice', json=data)
1194
1986
  return Message(self, response)
1195
1987
 
1196
- def copy_message(self, chat_id: Union[int,str,'Chat'], from_chat_id: Union[int,str,'Chat'], message_id: Union[int,str,'Chat']):
1988
+ def send_chat_action(self, chat: Union[int, str, 'Chat'], action: str, how_many_times: int = 1) -> bool:
1989
+ """Send a chat action"""
1990
+ if not chat:
1991
+ raise ValueError("Chat ID cannot be empty")
1992
+
1197
1993
  data = {
1198
- 'chat_id': chat_id if isinstance(chat_id, (int, str)) else chat_id.id,
1199
- 'from_chat_id': from_chat_id if isinstance(from_chat_id, (int, str)) else from_chat_id.id,
1200
- 'message_id': message_id if isinstance(message_id, (int, str)) else message_id.id
1994
+ 'chat_id': str(chat) if isinstance(chat, (int, str)) else str(chat.id),
1995
+ 'action': action
1201
1996
  }
1997
+ res = []
1998
+ for _ in range(how_many_times):
1999
+ response = self._make_request('POST', 'sendChatAction', json=data)
2000
+ res.append(response.get('ok', False))
2001
+ return all(res)
2002
+
2003
+ def copy_message(self,
2004
+ chat_id: Union[int,
2005
+ str,
2006
+ 'Chat'],
2007
+ from_chat_id: Union[int,
2008
+ str,
2009
+ 'Chat'],
2010
+ message_id: Union[int,
2011
+ str,
2012
+ 'Chat']):
2013
+ data = {
2014
+ 'chat_id': chat_id if isinstance(
2015
+ chat_id, (int, str)) else chat_id.id, 'from_chat_id': from_chat_id if isinstance(
2016
+ from_chat_id, (int, str)) else from_chat_id.id, 'message_id': message_id if isinstance(
2017
+ message_id, (int, str)) else message_id.id}
1202
2018
  response = self._make_request('POST', 'copyMessage', json=data)
1203
2019
  return Message(self, response)
1204
-
1205
- def get_chat_member(self, chat: Union[int,str,'Chat'], user: Union[int,str,'User']) -> ChatMember:
1206
- """Get information about a member of a chat including their permissions"""
1207
- data = {
1208
- 'chat_id': chat if isinstance(chat, (int, str)) else chat.id,
1209
- 'user_id': user if isinstance(user, (int, str)) else user.id
1210
- }
1211
- response = self._make_request('POST', 'getChatMember', json=data)
1212
- return ChatMember(self, response['result'])
1213
2020
 
1214
- def get_chat_administrators(self, chat: Union[int,str,'Chat']) -> List[ChatMember]:
2021
+ def get_chat_member(self,
2022
+ chat: Union[int,
2023
+ str,
2024
+ 'Chat'],
2025
+ user: Union[int,
2026
+ str,
2027
+ 'User']) -> ChatMember:
2028
+ """Get information about a member of a chat including their permissions"""
2029
+ data = {
2030
+ 'chat_id': chat if isinstance(chat, (int, str)) else chat.id,
2031
+ 'user_id': user if isinstance(user, (int, str)) else user.id
2032
+ }
2033
+ response = self._make_request('POST', 'getChatMember', json=data)
2034
+ return ChatMember(self, response['result'])
2035
+
2036
+ def get_chat_administrators(
2037
+ self, chat: Union[int, str, 'Chat']) -> List[ChatMember]:
1215
2038
  """Get a list of administrators in a chat"""
1216
2039
  data = {'chat_id': getattr(chat, 'id', chat)}
1217
- response = self._make_request('POST', 'getChatAdministrators', json=data)
1218
- return [ChatMember(self, member) for member in response.get('result', [])]
1219
-
1220
- def get_chat_members_count(self, chat: Union[int,str,'Chat']) -> int:
1221
- """Get the number of members in a chat"""
1222
- data = {
1223
- 'chat_id': chat if isinstance(chat, (int, str)) else chat.id
1224
- }
1225
- response = self._make_request('GET', 'getChatMembersCount', json=data)
1226
- return response['result']
1227
-
1228
- def is_joined(self, user: Union[User,int,str], chat: Union[Chat,int,str]) -> bool:
1229
- """Check if user is a member of the chat"""
1230
- data = {
1231
- 'chat_id': chat if isinstance(chat, (int, str)) else chat.id,
1232
- 'user_id': user if isinstance(user, (int, str)) else user.id
1233
- }
1234
- response = self._make_request('GET', 'getChatMember', json=data)
1235
- return response.get('status') not in ['left', 'kicked']
1236
-
1237
-
2040
+ response = self._make_request(
2041
+ 'POST', 'getChatAdministrators', json=data)
2042
+ return [ChatMember(self, member)
2043
+ for member in response.get('result', [])]
2044
+
2045
+ def get_chat_members_count(self, chat: Union[int, str, 'Chat']) -> int:
2046
+ """Get the number of members in a chat"""
2047
+ data = {
2048
+ 'chat_id': chat if isinstance(chat, (int, str)) else chat.id
2049
+ }
2050
+ response = self._make_request('GET', 'getChatMembersCount', json=data)
2051
+ return response['result']
2052
+
2053
+ def is_joined(self, user: Union[User, int, str],
2054
+ chat: Union[Chat, int, str]) -> bool:
2055
+ """Check if user is a member of the chat"""
2056
+ data = {
2057
+ 'chat_id': chat if isinstance(chat, (int, str)) else chat.id,
2058
+ 'user_id': user if isinstance(user, (int, str)) else user.id
2059
+ }
2060
+ response = self._make_request('GET', 'getChatMember', json=data)
2061
+ return response.get('status') not in ['left', 'kicked']
2062
+
1238
2063
  def on_message(self, func):
1239
2064
  """Decorator for handling new messages"""
1240
2065
  self._message_handler = func
@@ -1244,16 +2069,15 @@ class Client:
1244
2069
  """Decorator for handling callback queries"""
1245
2070
  self._callback_handler = func
1246
2071
  return func
1247
-
2072
+
1248
2073
  def on_tick(self, seconds: int):
1249
2074
  """Decorator for handling periodic events"""
1250
2075
  def decorator(func):
1251
- if not hasattr(self, '_tick_handlers'):
1252
- self._tick_handlers = {}
2076
+ self._tick_handlers = getattr(self, '_tick_handlers', {})
1253
2077
  self._tick_handlers[func] = {'interval': seconds, 'last_run': 0}
1254
2078
  return func
1255
2079
  return decorator
1256
-
2080
+
1257
2081
  def on_close(self, func):
1258
2082
  """Decorator for handling close event"""
1259
2083
  self._close_handler = func
@@ -1283,43 +2107,86 @@ class Client:
1283
2107
  """Decorator for handling edited messages"""
1284
2108
  self._message_edit_handler = func
1285
2109
  return func
1286
-
2110
+
2111
+ def on_command(self, command: str = None):
2112
+ """Decorator for handling specific text commands"""
2113
+ def decorator(func):
2114
+ self._text_handlers = getattr(self, '_text_handlers', {})
2115
+ cmd = f"/{func.__name__}" if command is None else f"/{command.lstrip('/')}"
2116
+ self._text_handlers[cmd] = func
2117
+ return func
2118
+ return decorator
2119
+
1287
2120
  def _create_thread(self, handler, *args):
1288
2121
  """Helper method to create and start a thread"""
1289
- thread = threading.Thread(target=handler, args=args)
2122
+ thread = threading.Thread(target=handler, args=args, daemon=True)
1290
2123
  thread.start()
1291
2124
  self._threads.append(thread)
1292
-
2125
+
1293
2126
  def _handle_message(self, message, update):
1294
2127
  """Handle different types of messages"""
1295
- if 'message' in update and 'new_chat_members' in update['message'] and hasattr(self, '_member_join_handler'):
1296
- chat = update['message']['chat']
1297
- user = update['message']['new_chat_members'][0]
1298
- self._create_thread(self._member_join_handler, message, Chat(self,{"ok":True,"result":chat}), User(self,{"ok":True,"result":user}))
1299
- elif 'message' in update and 'left_chat_member' in update['message'] and hasattr(self, '_member_leave_handler'):
1300
- chat = update['message']['chat']
1301
- user = update['message']['left_chat_member']
1302
- self._create_thread(self._member_leave_handler, message, Chat(self,{"ok":True,"result":chat}), User(self,{"ok":True,"result":user}))
1303
- elif self._message_handler:
1304
- args = (message, update) if len(inspect.signature(self._message_handler).parameters) > 1 else (message,)
2128
+ msg_data = update.get('message', {})
2129
+
2130
+ if 'new_chat_members' in msg_data and hasattr(self, '_member_join_handler'):
2131
+ chat, user = msg_data['chat'], msg_data['new_chat_members'][0]
2132
+ self._create_thread(
2133
+ self._member_join_handler,
2134
+ message,
2135
+ Chat(self, {"ok": True, "result": chat}),
2136
+ User(self, {"ok": True, "result": user})
2137
+ )
2138
+ return
2139
+
2140
+ if 'left_chat_member' in msg_data and hasattr(self, '_member_leave_handler'):
2141
+ chat, user = msg_data['chat'], msg_data['left_chat_member']
2142
+ self._create_thread(
2143
+ self._member_leave_handler,
2144
+ message,
2145
+ Chat(self, {"ok": True, "result": chat}),
2146
+ User(self, {"ok": True, "result": user})
2147
+ )
2148
+ return
2149
+
2150
+ if 'text' in msg_data and hasattr(self, '_text_handlers'):
2151
+ text = msg_data['text']
2152
+ for command, handler in self._text_handlers.items():
2153
+ if text.startswith(command):
2154
+ conds = conditions(self, message.author, message, None, message.chat)
2155
+ params = inspect.signature(handler).parameters
2156
+ args = (message, conds) if len(params) > 1 else (message,)
2157
+ self._create_thread(handler, *args)
2158
+ return
2159
+
2160
+ if self._message_handler:
2161
+ conds = conditions(self, message.author, message, None, message.chat)
2162
+ params = inspect.signature(self._message_handler).parameters
2163
+ args = ((message, update, conds) if len(params) > 2 else
2164
+ (message, update) if len(params) > 1 else (message,))
1305
2165
  self._create_thread(self._message_handler, *args)
1306
-
2166
+
1307
2167
  def _handle_update(self, update):
1308
2168
  if hasattr(self, '_update_handler'):
1309
2169
  self._create_thread(self._update_handler, update)
1310
-
1311
- if 'message' in update:
1312
- message = Message(self, {'ok': True, 'result': update['message']})
1313
- self._handle_message(message, update)
1314
- elif 'edited_message' in update and hasattr(self, '_message_edit_handler'):
1315
- edited_message = Message(self, {'ok': True, 'result': update['edited_message']})
1316
- args = (edited_message, update) if len(inspect.signature(self._message_edit_handler).parameters) > 1 else (edited_message,)
1317
- self._create_thread(self._message_edit_handler, *args)
1318
- elif 'callback_query' in update and self._callback_handler:
1319
- callback_query = CallbackQuery(self, {'ok': True, 'result': update['callback_query']})
1320
- args = (callback_query, update) if len(inspect.signature(self._callback_handler).parameters) > 1 else (callback_query,)
1321
- self._create_thread(self._callback_handler, *args)
1322
2170
 
2171
+ # Handle message and edited message
2172
+ message_types = {
2173
+ 'message': (Message, self._handle_message),
2174
+ 'edited_message': (Message, self._message_edit_handler)
2175
+ }
2176
+
2177
+ for update_type, (cls, handler) in message_types.items():
2178
+ if update_type in update:
2179
+ message = cls(self, {'ok': True, 'result': update[update_type]})
2180
+ handler(message, update)
2181
+ return
2182
+
2183
+ if 'callback_query' in update and self._callback_handler:
2184
+ obj = CallbackQuery(self, {'ok': True, 'result': update['callback_query']})
2185
+ params = inspect.signature(self._callback_handler).parameters
2186
+ conds = conditions(self, obj.author, None, obj, obj.chat)
2187
+ args = (obj, update, conds) if len(params) > 2 else (obj, update)
2188
+ self._create_thread(self._callback_handler, *args)
2189
+
1323
2190
  def _handle_tick_events(self, current_time):
1324
2191
  """Handle periodic tick events"""
1325
2192
  if hasattr(self, '_tick_handlers'):
@@ -1327,45 +2194,65 @@ class Client:
1327
2194
  if current_time - info['last_run'] >= info['interval']:
1328
2195
  self._create_thread(handler)
1329
2196
  info['last_run'] = current_time
1330
-
1331
- def run(self):
1332
- """Start polling for new messages"""
2197
+
2198
+ def run(self, debug=False):
2199
+ """Start p-olling for new messages"""
2200
+ try:
2201
+ self.get_me()
2202
+ except BaseException:
2203
+ raise BaleTokenNotFoundError("token not found")
2204
+
1333
2205
  self._polling = True
1334
2206
  offset = 0
1335
- past_updates = []
1336
-
2207
+ past_updates = set()
2208
+ source_file = inspect.getfile(self.__class__)
2209
+ last_modified = os.path.getmtime(source_file)
2210
+
2211
+ if self.auto_log_start_message:
2212
+ print(f"-+-+-+ [logged in as @{self.get_me().username}] +-+-+-")
1337
2213
  if hasattr(self, '_ready_handler'):
1338
2214
  self._ready_handler()
1339
-
2215
+
1340
2216
  while self._polling:
1341
2217
  try:
1342
- updates = self.get_updates(offset=offset)
2218
+ if debug:
2219
+ current_modified = os.path.getmtime(source_file)
2220
+ if current_modified > last_modified:
2221
+ last_modified = current_modified
2222
+ print("Source file changed, restarting...")
2223
+ python = sys.executable
2224
+ os.execl(python, python, *sys.argv)
2225
+
2226
+ updates = self.get_updates(offset=offset, timeout=30)
1343
2227
  for update in updates:
1344
2228
  update_id = update['update_id']
1345
- if update_id not in [u['update_id'] for u in past_updates]:
2229
+ if update_id not in past_updates:
1346
2230
  self._handle_update(update)
1347
2231
  offset = update_id + 1
1348
- past_updates.append(update)
1349
- if len(past_updates) > 30:
1350
- past_updates.pop(0)
1351
-
1352
- self._handle_tick_events(time.time())
2232
+ past_updates.add(update_id)
2233
+ if len(past_updates) > 100:
2234
+ past_updates = set(sorted(list(past_updates))[-50:])
2235
+
2236
+ current_time = time.time()
2237
+ self._handle_tick_events(current_time)
1353
2238
  self._threads = [t for t in self._threads if t.is_alive()]
1354
- time.sleep(0.5)
1355
- except Exception as e:
2239
+ time.sleep(0.1)
2240
+ except Exception:
1356
2241
  print(f"Error in polling: {traceback.format_exc()}")
1357
2242
  time.sleep(1)
1358
- continue
1359
2243
 
1360
- def get_updates(self, offset: Optional[int] = None,
1361
- limit: Optional[int] = None,
2244
+ def _check_source_file_changed(self, source_file, last_modified):
2245
+ try:
2246
+ current_mtime = os.path.getmtime(source_file)
2247
+ return current_mtime != last_modified
2248
+ except (FileNotFoundError, OSError) as e:
2249
+ print(f"Error checking file modification time: {e}")
2250
+ return False
2251
+
2252
+ def get_updates(self, offset: Optional[int] = None,
2253
+ limit: Optional[int] = None,
1362
2254
  timeout: Optional[int] = None) -> List[Dict[str, Any]]:
1363
- """Get latest updates/messages"""
1364
- params = {
1365
- 'offset': offset,
1366
- 'limit': limit,
1367
- 'timeout': timeout
1368
- }
2255
+ params = {k: v for k, v in locals().items() if k != 'self' and v is not None}
1369
2256
  response = self._make_request('GET', 'getUpdates', params=params)
1370
2257
  return response.get('result', [])
1371
2258
 
@@ -1373,50 +2260,10 @@ class Client:
1373
2260
  """Close the client and stop polling"""
1374
2261
  self._polling = False
1375
2262
  for thread in self._threads:
1376
- try:
1377
- thread.join()
1378
- except:
1379
- pass
2263
+ thread.join(timeout=1.0)
1380
2264
  self._threads.clear()
1381
2265
  if hasattr(self, '_close_handler'):
1382
2266
  self._close_handler()
1383
-
1384
- def run_multiple_clients(*clients):
1385
- """Run multiple clients concurrently using asyncio"""
1386
- async def run_client(client):
1387
- if hasattr(client, '_ready_handler'):
1388
- client._ready_handler()
1389
-
1390
- offset = None
1391
- past_updates = []
1392
-
1393
- while client._polling:
1394
- try:
1395
- updates = await client.get_updates(offset=offset)
1396
- for update in updates:
1397
- update_id = update['update_id']
1398
- if update_id not in [u['update_id'] for u in past_updates]:
1399
- await client._handle_update(update)
1400
- offset = update_id + 1
1401
- past_updates.append(update)
1402
- if len(past_updates) > 30:
1403
- past_updates.pop(0)
1404
-
1405
- await client._handle_tick_events(time.time())
1406
- await asyncio.sleep(0.5)
1407
- except Exception as e:
1408
- print(f"Error in polling: {traceback.format_exc()}")
1409
- await asyncio.sleep(1)
1410
- continue
1411
-
1412
- loop = asyncio.get_event_loop()
1413
- tasks = [loop.create_task(run_client(client)) for client in clients]
1414
2267
 
1415
- try:
1416
- loop.run_until_complete(asyncio.gather(*tasks))
1417
- except KeyboardInterrupt:
1418
- for client in clients:
1419
- client.safe_close()
1420
- loop.run_until_complete(asyncio.gather(*[task.cancel() for task in tasks]))
1421
- finally:
1422
- loop.close()
2268
+ def create_ref_link(self, data: str):
2269
+ return f"https://ble.ir/{self.get_me().username}?start={data}"