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-0.2.2.dist-info → pyrobale-0.2.8.dist-info}/METADATA +36 -29
- pyrobale-0.2.8.dist-info/RECORD +5 -0
- {pyrobale-0.2.2.dist-info → pyrobale-0.2.8.dist-info}/licenses/LICENSE +5 -1
- pyrobale.py +1518 -671
- pyrobale-0.2.2.dist-info/RECORD +0 -5
- {pyrobale-0.2.2.dist-info → pyrobale-0.2.8.dist-info}/WHEEL +0 -0
pyrobale.py
CHANGED
@@ -1,19 +1,143 @@
|
|
1
|
-
|
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
|
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(
|
165
|
+
self.cursor.execute(
|
166
|
+
"SELECT key, value, created_at, updated_at FROM key_value_store")
|
42
167
|
rows = self.cursor.fetchall()
|
43
|
-
return {
|
44
|
-
|
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
|
-
|
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(
|
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
|
-
|
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(
|
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 {
|
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(
|
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(
|
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
|
-
|
135
|
-
|
136
|
-
|
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__(
|
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__(
|
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(
|
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,
|
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,
|
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
|
-
|
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
|
333
|
-
raise BaleException(
|
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(
|
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 =
|
356
|
-
|
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(
|
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,
|
378
|
-
|
379
|
-
|
380
|
-
|
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(
|
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 = {
|
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
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
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
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
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(
|
875
|
+
response = self.client._make_request(
|
876
|
+
'POST', 'banChatMember', data=data)
|
494
877
|
return response.get('ok', False)
|
495
878
|
|
496
|
-
def unbanChatMember(
|
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(
|
889
|
+
response = self.client._make_request(
|
890
|
+
'POST', 'unbanChatMember', data=data)
|
504
891
|
return response.get('ok', False)
|
505
892
|
|
506
|
-
def promoteChatMember(
|
507
|
-
|
508
|
-
|
509
|
-
|
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(
|
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 = {
|
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(
|
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(
|
950
|
+
response = self.client._make_request(
|
951
|
+
'POST', 'getChatMembersCount', data=data)
|
554
952
|
return response.get('result', 0)
|
555
953
|
|
556
|
-
def pinChatMessage(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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,
|
657
|
-
|
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(
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
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(
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
self
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
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,
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
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(
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
def
|
728
|
-
|
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(
|
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
|
-
|
740
|
-
self.
|
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(
|
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(
|
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,
|
759
|
-
|
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(
|
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,
|
768
|
-
|
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(
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
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(
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
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,
|
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(
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
def
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
def
|
831
|
-
|
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
|
-
|
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,
|
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,
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
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,
|
931
|
-
|
932
|
-
|
933
|
-
|
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
|
-
|
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(
|
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
|
-
|
951
|
-
|
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,
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
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(
|
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 = {
|
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(
|
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
|
-
|
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,
|
999
|
-
|
1000
|
-
|
1001
|
-
|
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,
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
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(
|
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 = {
|
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(
|
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,
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
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(
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
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(
|
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,
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
else
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
data =
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
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,
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
if isinstance(
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
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(
|
1191
|
-
|
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
|
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':
|
1199
|
-
'
|
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
|
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(
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
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
|
-
|
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
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
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
|
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
|
-
|
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
|
2229
|
+
if update_id not in past_updates:
|
1346
2230
|
self._handle_update(update)
|
1347
2231
|
offset = update_id + 1
|
1348
|
-
past_updates.
|
1349
|
-
if len(past_updates) >
|
1350
|
-
past_updates
|
1351
|
-
|
1352
|
-
|
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.
|
1355
|
-
except Exception
|
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
|
1361
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
1416
|
-
|
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}"
|