Rubka 4.5.2__py3-none-any.whl → 4.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rubka/api.py +132 -24
- rubka/asynco.py +197 -40
- rubka/context.py +73 -22
- {rubka-4.5.2.dist-info → rubka-4.6.0.dist-info}/METADATA +6 -1
- {rubka-4.5.2.dist-info → rubka-4.6.0.dist-info}/RECORD +7 -7
- {rubka-4.5.2.dist-info → rubka-4.6.0.dist-info}/WHEEL +0 -0
- {rubka-4.5.2.dist-info → rubka-4.6.0.dist-info}/top_level.txt +0 -0
rubka/api.py
CHANGED
|
@@ -13,6 +13,8 @@ import tempfile
|
|
|
13
13
|
from tqdm import tqdm
|
|
14
14
|
import os
|
|
15
15
|
API_URL = "https://botapi.rubika.ir/v3"
|
|
16
|
+
import mimetypes
|
|
17
|
+
import re
|
|
16
18
|
import sys
|
|
17
19
|
import subprocess
|
|
18
20
|
def install_package(package_name):
|
|
@@ -109,6 +111,7 @@ class Robot:
|
|
|
109
111
|
self.Key = Key
|
|
110
112
|
self.platform = platform
|
|
111
113
|
self.web_hook = web_hook
|
|
114
|
+
self.hook = web_hook
|
|
112
115
|
self._offset_id = None
|
|
113
116
|
self.session = requests.Session()
|
|
114
117
|
self.sessions: Dict[str, Dict[str, Any]] = {}
|
|
@@ -117,7 +120,7 @@ class Robot:
|
|
|
117
120
|
self._inline_query_handler = None
|
|
118
121
|
self._message_handlers: List[dict] = []
|
|
119
122
|
self._callback_handlers = None
|
|
120
|
-
self._callback_handlers = []
|
|
123
|
+
self._callback_handlers = []
|
|
121
124
|
if web_hook:
|
|
122
125
|
try:
|
|
123
126
|
json_url = requests.get(web_hook, timeout=self.timeout).json().get('url', web_hook)
|
|
@@ -133,7 +136,7 @@ class Robot:
|
|
|
133
136
|
except Exception as e:
|
|
134
137
|
logger.error(f"Failed to set webhook from {web_hook}: {e}")
|
|
135
138
|
else:
|
|
136
|
-
self.web_hook =
|
|
139
|
+
self.web_hook = self.hook
|
|
137
140
|
|
|
138
141
|
|
|
139
142
|
|
|
@@ -202,14 +205,13 @@ class Robot:
|
|
|
202
205
|
|
|
203
206
|
def _process_update(self, update: dict):
|
|
204
207
|
import threading
|
|
205
|
-
|
|
208
|
+
|
|
206
209
|
if update.get("type") == "ReceiveQuery":
|
|
207
210
|
msg = update.get("inline_message", {})
|
|
208
211
|
context = InlineMessage(bot=self, raw_data=msg)
|
|
209
212
|
threading.Thread(target=self._handle_inline_query, args=(context,), daemon=True).start()
|
|
210
213
|
return
|
|
211
214
|
|
|
212
|
-
# هندل پیام جدید متنی
|
|
213
215
|
if update.get("type") == "NewMessage":
|
|
214
216
|
msg = update.get("new_message", {})
|
|
215
217
|
try:
|
|
@@ -225,17 +227,14 @@ class Robot:
|
|
|
225
227
|
text=msg.get("text"),
|
|
226
228
|
raw_data=msg)
|
|
227
229
|
|
|
228
|
-
# هندل callback ها (دکمهها) - بدون تغییر
|
|
229
230
|
if context.aux_data and self._callback_handlers:
|
|
230
231
|
for handler in self._callback_handlers:
|
|
231
232
|
if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
|
|
232
233
|
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
233
234
|
return
|
|
234
235
|
|
|
235
|
-
# هندل پیامهای متنی با حلقه روی تمام هندلرها
|
|
236
236
|
if self._message_handlers:
|
|
237
237
|
for handler in self._message_handlers:
|
|
238
|
-
# بررسی شرط دستورات (commands)
|
|
239
238
|
if handler["commands"]:
|
|
240
239
|
if not context.text or not context.text.startswith("/"):
|
|
241
240
|
continue
|
|
@@ -245,11 +244,9 @@ class Robot:
|
|
|
245
244
|
continue
|
|
246
245
|
context.args = parts[1:]
|
|
247
246
|
|
|
248
|
-
# بررسی شرط فیلترها (filters)
|
|
249
247
|
if handler["filters"] and not handler["filters"](context):
|
|
250
248
|
continue
|
|
251
249
|
|
|
252
|
-
# اگر شرایط بالا برقرار بود یا هندلر عمومی بود، اجرا کن و خارج شو
|
|
253
250
|
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
254
251
|
return
|
|
255
252
|
|
|
@@ -279,12 +276,10 @@ class Robot:
|
|
|
279
276
|
def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
|
|
280
277
|
now = time.time()
|
|
281
278
|
|
|
282
|
-
# حذف پیامهای قدیمیتر از max_age_sec
|
|
283
279
|
expired = [mid for mid, ts in self._processed_message_ids.items() if now - ts > max_age_sec]
|
|
284
280
|
for mid in expired:
|
|
285
281
|
del self._processed_message_ids[mid]
|
|
286
282
|
|
|
287
|
-
# بررسی تکراری بودن پیام
|
|
288
283
|
if message_id in self._processed_message_ids:
|
|
289
284
|
return True
|
|
290
285
|
|
|
@@ -298,7 +293,6 @@ class Robot:
|
|
|
298
293
|
def run(self):
|
|
299
294
|
print("Bot started running...")
|
|
300
295
|
self._processed_message_ids: Dict[str, float] = {}
|
|
301
|
-
# self._offset_id میتواند قبلاً مقداردهی شده باشد
|
|
302
296
|
|
|
303
297
|
while True:
|
|
304
298
|
try:
|
|
@@ -309,12 +303,11 @@ class Robot:
|
|
|
309
303
|
for item in updates:
|
|
310
304
|
data = item.get("data", {})
|
|
311
305
|
|
|
312
|
-
# بررسی زمان دریافت پیام و رد کردن پیامهای قدیمی (بیش از 20 ثانیه)
|
|
313
306
|
received_at_str = item.get("received_at")
|
|
314
307
|
if received_at_str:
|
|
315
308
|
received_at_ts = datetime.datetime.strptime(received_at_str, "%Y-%m-%d %H:%M:%S").timestamp()
|
|
316
309
|
if time.time() - received_at_ts > 20:
|
|
317
|
-
continue
|
|
310
|
+
continue
|
|
318
311
|
|
|
319
312
|
update = None
|
|
320
313
|
if "update" in data:
|
|
@@ -338,12 +331,11 @@ class Robot:
|
|
|
338
331
|
if message_id is not None:
|
|
339
332
|
message_id = str(message_id)
|
|
340
333
|
|
|
341
|
-
if message_id and self._is_duplicate(
|
|
334
|
+
if message_id and self._is_duplicate(received_at_str):
|
|
342
335
|
continue
|
|
343
336
|
|
|
344
337
|
self._process_update(update)
|
|
345
338
|
|
|
346
|
-
# ثبت پیام به عنوان پردازش شده
|
|
347
339
|
if message_id:
|
|
348
340
|
self._processed_message_ids[message_id] = time.time()
|
|
349
341
|
|
|
@@ -431,6 +423,10 @@ class Robot:
|
|
|
431
423
|
|
|
432
424
|
return False
|
|
433
425
|
|
|
426
|
+
def get_url_file(self,file_id):
|
|
427
|
+
data = self._post("getFile", {'file_id': file_id})
|
|
428
|
+
return data.get("data").get("download_url")
|
|
429
|
+
|
|
434
430
|
|
|
435
431
|
def get_all_member(
|
|
436
432
|
self,
|
|
@@ -479,7 +475,6 @@ class Robot:
|
|
|
479
475
|
"reply_to_message_id": reply_to_message_id,
|
|
480
476
|
"chat_keypad_type": chat_keypad_type
|
|
481
477
|
}
|
|
482
|
-
# Remove None values
|
|
483
478
|
payload = {k: v for k, v in payload.items() if v is not None}
|
|
484
479
|
return self._post("sendLocation", payload)
|
|
485
480
|
|
|
@@ -499,7 +494,63 @@ class Robot:
|
|
|
499
494
|
"last_name": last_name,
|
|
500
495
|
"phone_number": phone_number
|
|
501
496
|
})
|
|
497
|
+
def download(self,file_id: str, save_as: str = None, chunk_size: int = 1024 * 512, timeout_sec: int = 60, verbose: bool = False):
|
|
498
|
+
"""
|
|
499
|
+
Download a file from server using its file_id with chunked transfer,
|
|
500
|
+
progress bar, file extension detection, custom filename, and timeout.
|
|
501
|
+
|
|
502
|
+
If save_as is not provided, filename will be extracted from
|
|
503
|
+
Content-Disposition header or Content-Type header extension.
|
|
504
|
+
|
|
505
|
+
Parameters:
|
|
506
|
+
file_id (str): The file ID to fetch the download URL.
|
|
507
|
+
save_as (str, optional): Custom filename to save. If None, automatically detected.
|
|
508
|
+
chunk_size (int, optional): Size of each chunk in bytes. Default 512KB.
|
|
509
|
+
timeout_sec (int, optional): HTTP timeout in seconds. Default 60.
|
|
510
|
+
verbose (bool, optional): Show progress messages. Default True.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
bool: True if success, raises exceptions otherwise.
|
|
514
|
+
"""
|
|
515
|
+
try:
|
|
516
|
+
url = self.get_url_file(file_id)
|
|
517
|
+
if not url:
|
|
518
|
+
raise ValueError("Download URL not found in response.")
|
|
519
|
+
except Exception as e:
|
|
520
|
+
raise ValueError(f"Failed to get download URL: {e}")
|
|
502
521
|
|
|
522
|
+
try:
|
|
523
|
+
with requests.get(url, stream=True, timeout=timeout_sec) as resp:
|
|
524
|
+
if resp.status_code != 200:
|
|
525
|
+
raise requests.HTTPError(f"Failed to download file. Status code: {resp.status_code}")
|
|
526
|
+
|
|
527
|
+
if not save_as:
|
|
528
|
+
content_disp = resp.headers.get("Content-Disposition", "")
|
|
529
|
+
match = re.search(r'filename="?([^\";]+)"?', content_disp)
|
|
530
|
+
if match:
|
|
531
|
+
save_as = match.group(1)
|
|
532
|
+
else:
|
|
533
|
+
content_type = resp.headers.get("Content-Type", "").split(";")[0]
|
|
534
|
+
extension = mimetypes.guess_extension(content_type) or ".bin"
|
|
535
|
+
save_as = f"{file_id}{extension}"
|
|
536
|
+
|
|
537
|
+
total_size = int(resp.headers.get("Content-Length", 0))
|
|
538
|
+
progress = tqdm(total=total_size, unit="B", unit_scale=True)
|
|
539
|
+
|
|
540
|
+
with open(save_as, "wb") as f:
|
|
541
|
+
for chunk in resp.iter_content(chunk_size=chunk_size):
|
|
542
|
+
if chunk:
|
|
543
|
+
f.write(chunk)
|
|
544
|
+
progress.update(len(chunk))
|
|
545
|
+
|
|
546
|
+
progress.close()
|
|
547
|
+
if verbose:
|
|
548
|
+
print(f"File saved as: {save_as}")
|
|
549
|
+
|
|
550
|
+
return True
|
|
551
|
+
|
|
552
|
+
except Exception as e:
|
|
553
|
+
raise RuntimeError(f"Download failed: {e}")
|
|
503
554
|
def get_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
504
555
|
"""Get chat info."""
|
|
505
556
|
return self._post("getChat", {"chat_id": chat_id})
|
|
@@ -600,7 +651,64 @@ class Robot:
|
|
|
600
651
|
payload["reply_to_message_id"] = str(reply_to_message_id)
|
|
601
652
|
|
|
602
653
|
return self._post("sendFile", payload)
|
|
603
|
-
|
|
654
|
+
def send_file(
|
|
655
|
+
self,
|
|
656
|
+
chat_id: str,
|
|
657
|
+
path: Optional[Union[str, Path]] = None,
|
|
658
|
+
file_id: Optional[str] = None,
|
|
659
|
+
caption: Optional[str] = None,
|
|
660
|
+
file_name: Optional[str] = None,
|
|
661
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
662
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
663
|
+
reply_to_message_id: Optional[str] = None,
|
|
664
|
+
disable_notification: bool = False,
|
|
665
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
666
|
+
) -> Dict[str, Any]:
|
|
667
|
+
if path:
|
|
668
|
+
file_name = file_name or Path(path).name
|
|
669
|
+
upload_url = self.get_upload_url("File")
|
|
670
|
+
file_id = self.upload_media_file(upload_url, file_name, path)
|
|
671
|
+
if not file_id:
|
|
672
|
+
raise ValueError("Either path or file_id must be provided.")
|
|
673
|
+
return self._send_uploaded_file(
|
|
674
|
+
chat_id=chat_id,
|
|
675
|
+
file_id=file_id,
|
|
676
|
+
text=caption,
|
|
677
|
+
inline_keypad=inline_keypad,
|
|
678
|
+
chat_keypad=chat_keypad,
|
|
679
|
+
reply_to_message_id=reply_to_message_id,
|
|
680
|
+
disable_notification=disable_notification,
|
|
681
|
+
chat_keypad_type=chat_keypad_type
|
|
682
|
+
)
|
|
683
|
+
def re_send_file(
|
|
684
|
+
self,
|
|
685
|
+
chat_id: str,
|
|
686
|
+
path: Optional[Union[str, Path]] = None,
|
|
687
|
+
file_id: Optional[str] = None,
|
|
688
|
+
caption: Optional[str] = None,
|
|
689
|
+
file_name: Optional[str] = None,
|
|
690
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
691
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
692
|
+
reply_to_message_id: Optional[str] = None,
|
|
693
|
+
disable_notification: bool = False,
|
|
694
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
695
|
+
) -> Dict[str, Any]:
|
|
696
|
+
if path:
|
|
697
|
+
file_name = file_name or Path(path).name
|
|
698
|
+
upload_url = self.get_upload_url("File")
|
|
699
|
+
file_id = self.upload_media_file(upload_url, file_name, path)
|
|
700
|
+
if not file_id:
|
|
701
|
+
raise ValueError("Either path or file_id must be provided.")
|
|
702
|
+
return self._send_uploaded_file(
|
|
703
|
+
chat_id=chat_id,
|
|
704
|
+
file_id=file_id,
|
|
705
|
+
text=caption,
|
|
706
|
+
inline_keypad=inline_keypad,
|
|
707
|
+
chat_keypad=chat_keypad,
|
|
708
|
+
reply_to_message_id=reply_to_message_id,
|
|
709
|
+
disable_notification=disable_notification,
|
|
710
|
+
chat_keypad_type=chat_keypad_type
|
|
711
|
+
)
|
|
604
712
|
def send_document(
|
|
605
713
|
self,
|
|
606
714
|
chat_id: str,
|
|
@@ -612,7 +720,7 @@ class Robot:
|
|
|
612
720
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
613
721
|
reply_to_message_id: Optional[str] = None,
|
|
614
722
|
disable_notification: bool = False,
|
|
615
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
723
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
616
724
|
) -> Dict[str, Any]:
|
|
617
725
|
if path:
|
|
618
726
|
file_name = file_name or Path(path).name
|
|
@@ -641,7 +749,7 @@ class Robot:
|
|
|
641
749
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
642
750
|
reply_to_message_id: Optional[str] = None,
|
|
643
751
|
disable_notification: bool = False,
|
|
644
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
752
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
645
753
|
) -> Dict[str, Any]:
|
|
646
754
|
if path:
|
|
647
755
|
file_name = file_name or Path(path).name
|
|
@@ -670,7 +778,7 @@ class Robot:
|
|
|
670
778
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
671
779
|
reply_to_message_id: Optional[str] = None,
|
|
672
780
|
disable_notification: bool = False,
|
|
673
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
781
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
674
782
|
) -> Dict[str, Any]:
|
|
675
783
|
if path:
|
|
676
784
|
file_name = file_name or Path(path).name
|
|
@@ -699,7 +807,7 @@ class Robot:
|
|
|
699
807
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
700
808
|
reply_to_message_id: Optional[str] = None,
|
|
701
809
|
disable_notification: bool = False,
|
|
702
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
810
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
703
811
|
) -> Dict[str, Any]:
|
|
704
812
|
if path:
|
|
705
813
|
file_name = file_name or Path(path).name
|
|
@@ -728,7 +836,7 @@ class Robot:
|
|
|
728
836
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
729
837
|
reply_to_message_id: Optional[str] = None,
|
|
730
838
|
disable_notification: bool = False,
|
|
731
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
839
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
732
840
|
) -> Dict[str, Any]:
|
|
733
841
|
if path:
|
|
734
842
|
file_name = file_name or Path(path).name
|
|
@@ -757,7 +865,7 @@ class Robot:
|
|
|
757
865
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
758
866
|
reply_to_message_id: Optional[str] = None,
|
|
759
867
|
disable_notification: bool = False,
|
|
760
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
868
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
761
869
|
) -> Dict[str, Any]:
|
|
762
870
|
if path:
|
|
763
871
|
file_name = file_name or Path(path).name
|
rubka/asynco.py
CHANGED
|
@@ -8,8 +8,12 @@ from .logger import logger
|
|
|
8
8
|
try:
|
|
9
9
|
from .context import Message, InlineMessage
|
|
10
10
|
except (ImportError, ModuleNotFoundError):
|
|
11
|
-
# اگر به صورت مستقیم اجرا شود، از این حالت استفاده میکند
|
|
12
11
|
from context import Message, InlineMessage
|
|
12
|
+
|
|
13
|
+
from tqdm.asyncio import tqdm
|
|
14
|
+
from urllib.parse import urlparse, parse_qs
|
|
15
|
+
|
|
16
|
+
import mimetypes
|
|
13
17
|
from pathlib import Path
|
|
14
18
|
import time
|
|
15
19
|
import datetime
|
|
@@ -86,8 +90,8 @@ async def check_rubka_version():
|
|
|
86
90
|
print("Not updating may lead to malfunctions or incompatibility.")
|
|
87
91
|
print("To see new methods : @rubka_library\n\n")
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
|
|
94
|
+
|
|
91
95
|
|
|
92
96
|
def show_last_six_words(text: str) -> str:
|
|
93
97
|
"""Returns the last 6 characters of a stripped string."""
|
|
@@ -222,7 +226,39 @@ class Robot:
|
|
|
222
226
|
})
|
|
223
227
|
return func
|
|
224
228
|
return decorator
|
|
229
|
+
def on_inline_query_prefix(self, prefix: str, button_id: Optional[str] = None):
|
|
230
|
+
if not prefix.startswith('/'):
|
|
231
|
+
prefix = '/' + prefix
|
|
232
|
+
|
|
233
|
+
def decorator(func: Callable[[Any, InlineMessage], None]):
|
|
234
|
+
|
|
235
|
+
async def handler_wrapper(bot_instance, inline_message: InlineMessage):
|
|
236
|
+
|
|
237
|
+
if not inline_message.raw_data or 'text' not in inline_message.raw_data:
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
query_text = inline_message.raw_data['text']
|
|
225
241
|
|
|
242
|
+
|
|
243
|
+
if query_text.startswith(prefix):
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
await func(bot_instance, inline_message)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
print(f"Error in inline query prefix handler '{prefix}': {e}")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
self._inline_query_handlers.append({
|
|
254
|
+
"func": handler_wrapper,
|
|
255
|
+
"button_id": button_id
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
})
|
|
260
|
+
return func
|
|
261
|
+
return decorator
|
|
226
262
|
async def _process_update(self, update: dict):
|
|
227
263
|
if update.get("type") == "ReceiveQuery":
|
|
228
264
|
msg = update.get("inline_message", {})
|
|
@@ -245,40 +281,40 @@ class Robot:
|
|
|
245
281
|
text=msg.get("text"),
|
|
246
282
|
raw_data=msg)
|
|
247
283
|
|
|
248
|
-
|
|
284
|
+
|
|
249
285
|
if context.aux_data and self._callback_handlers:
|
|
250
286
|
for handler in self._callback_handlers:
|
|
251
287
|
if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
|
|
252
288
|
asyncio.create_task(handler["func"](self, context))
|
|
253
289
|
return
|
|
254
290
|
|
|
255
|
-
|
|
291
|
+
|
|
256
292
|
if self._message_handlers:
|
|
257
293
|
for handler_info in self._message_handlers:
|
|
258
|
-
|
|
294
|
+
|
|
259
295
|
if handler_info["commands"]:
|
|
260
296
|
if not context.text or not context.text.startswith("/"):
|
|
261
|
-
continue
|
|
297
|
+
continue
|
|
262
298
|
parts = context.text.split()
|
|
263
299
|
cmd = parts[0][1:]
|
|
264
300
|
if cmd not in handler_info["commands"]:
|
|
265
|
-
continue
|
|
301
|
+
continue
|
|
266
302
|
context.args = parts[1:]
|
|
267
303
|
|
|
268
|
-
|
|
304
|
+
|
|
269
305
|
if handler_info["filters"]:
|
|
270
306
|
if not handler_info["filters"](context):
|
|
271
|
-
continue
|
|
307
|
+
continue
|
|
308
|
+
|
|
272
309
|
|
|
273
|
-
# اگر هندلری برای همه پیامها باشد (بدون کامند و فیلتر)
|
|
274
310
|
if not handler_info["commands"] and not handler_info["filters"]:
|
|
275
311
|
asyncio.create_task(handler_info["func"](self, context))
|
|
276
|
-
return
|
|
312
|
+
return
|
|
313
|
+
|
|
277
314
|
|
|
278
|
-
# اگر شرایط کامند یا فیلتر برقرار بود
|
|
279
315
|
if handler_info["commands"] or handler_info["filters"]:
|
|
280
316
|
asyncio.create_task(handler_info["func"](self, context))
|
|
281
|
-
return
|
|
317
|
+
return
|
|
282
318
|
|
|
283
319
|
async def get_updates(self, offset_id: Optional[str] = None, limit: Optional[int] = None) -> Dict[str, Any]:
|
|
284
320
|
data = {}
|
|
@@ -293,7 +329,7 @@ class Robot:
|
|
|
293
329
|
if limit: params['limit'] = limit
|
|
294
330
|
async with session.get(self.web_hook, params=params) as response:
|
|
295
331
|
response.raise_for_status()
|
|
296
|
-
|
|
332
|
+
|
|
297
333
|
return await response.json()
|
|
298
334
|
|
|
299
335
|
def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
|
|
@@ -307,6 +343,7 @@ class Robot:
|
|
|
307
343
|
|
|
308
344
|
self._processed_message_ids[message_id] = now
|
|
309
345
|
return False
|
|
346
|
+
|
|
310
347
|
|
|
311
348
|
async def run(self):
|
|
312
349
|
"""
|
|
@@ -321,8 +358,8 @@ class Robot:
|
|
|
321
358
|
while True:
|
|
322
359
|
try:
|
|
323
360
|
if self.web_hook:
|
|
324
|
-
|
|
325
|
-
|
|
361
|
+
|
|
362
|
+
|
|
326
363
|
webhook_data = await self.update_webhook()
|
|
327
364
|
if isinstance(webhook_data, list):
|
|
328
365
|
for item in webhook_data:
|
|
@@ -335,7 +372,7 @@ class Robot:
|
|
|
335
372
|
if time.time() - received_at_ts > 20:
|
|
336
373
|
continue
|
|
337
374
|
except (ValueError, TypeError):
|
|
338
|
-
pass
|
|
375
|
+
pass
|
|
339
376
|
|
|
340
377
|
update = None
|
|
341
378
|
if "update" in data:
|
|
@@ -353,11 +390,11 @@ class Robot:
|
|
|
353
390
|
elif "message_id" in update:
|
|
354
391
|
message_id = update.get("message_id")
|
|
355
392
|
|
|
356
|
-
if message_id and not self._is_duplicate(str(
|
|
393
|
+
if message_id and not self._is_duplicate(str(received_at_str)):
|
|
357
394
|
await self._process_update(update)
|
|
358
395
|
|
|
359
396
|
else:
|
|
360
|
-
|
|
397
|
+
|
|
361
398
|
get_updates_response = await self.get_updates(offset_id=self._offset_id, limit=100)
|
|
362
399
|
if get_updates_response and get_updates_response.get("data"):
|
|
363
400
|
updates = get_updates_response["data"].get("updates", [])
|
|
@@ -375,23 +412,49 @@ class Robot:
|
|
|
375
412
|
if message_id and not self._is_duplicate(str(message_id)):
|
|
376
413
|
await self._process_update(update)
|
|
377
414
|
|
|
378
|
-
await asyncio.sleep(0
|
|
415
|
+
await asyncio.sleep(0)
|
|
379
416
|
except Exception as e:
|
|
380
417
|
print(f"❌ Error in run loop: {e}")
|
|
381
|
-
await asyncio.sleep(5)
|
|
418
|
+
await asyncio.sleep(5)
|
|
382
419
|
finally:
|
|
383
420
|
if self._aiohttp_session:
|
|
384
421
|
await self._aiohttp_session.close()
|
|
385
422
|
print("Bot stopped and session closed.")
|
|
386
423
|
|
|
387
|
-
async def send_message(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
424
|
+
async def send_message(
|
|
425
|
+
self,
|
|
426
|
+
chat_id: str,
|
|
427
|
+
text: str,
|
|
428
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
429
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
430
|
+
disable_notification: bool = False,
|
|
431
|
+
reply_to_message_id: Optional[str] = None,
|
|
432
|
+
chat_keypad_type: Optional[Literal["New", "Removed"]] = None
|
|
433
|
+
) -> Dict[str, Any]:
|
|
434
|
+
payload = {
|
|
435
|
+
"chat_id": chat_id,
|
|
436
|
+
"text": text,
|
|
437
|
+
"disable_notification": disable_notification,
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if chat_keypad:
|
|
441
|
+
payload["chat_keypad"] = chat_keypad
|
|
442
|
+
payload["chat_keypad_type"] = chat_keypad_type or "New"
|
|
443
|
+
|
|
444
|
+
if inline_keypad:
|
|
445
|
+
payload["inline_keypad"] = inline_keypad
|
|
446
|
+
|
|
447
|
+
if reply_to_message_id:
|
|
448
|
+
payload["reply_to_message_id"] = reply_to_message_id
|
|
449
|
+
|
|
393
450
|
return await self._post("sendMessage", payload)
|
|
394
451
|
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
async def get_url_file(self,file_id):
|
|
455
|
+
data = await self._post("getFile", {'file_id': file_id})
|
|
456
|
+
return data.get("data").get("download_url")
|
|
457
|
+
|
|
395
458
|
def _get_client(self) -> Client_get:
|
|
396
459
|
if self.session_name:
|
|
397
460
|
return Client_get(self.session_name, self.auth, self.Key, self.platform)
|
|
@@ -407,7 +470,7 @@ class Robot:
|
|
|
407
470
|
username = chat_info.get('username')
|
|
408
471
|
user_id = chat_info.get('user_id')
|
|
409
472
|
|
|
410
|
-
|
|
473
|
+
|
|
411
474
|
if username:
|
|
412
475
|
result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
|
|
413
476
|
members = result.get('in_chat_members', [])
|
|
@@ -418,7 +481,7 @@ class Robot:
|
|
|
418
481
|
return False
|
|
419
482
|
|
|
420
483
|
def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
|
|
421
|
-
|
|
484
|
+
|
|
422
485
|
client = self._get_client()
|
|
423
486
|
return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
|
|
424
487
|
|
|
@@ -454,7 +517,7 @@ class Robot:
|
|
|
454
517
|
path = temp_file.name
|
|
455
518
|
is_temp_file = True
|
|
456
519
|
|
|
457
|
-
file_size = os.path.getsize(path)
|
|
520
|
+
file_size = os.path.getsize(path)
|
|
458
521
|
|
|
459
522
|
progress_bar = tqdm(total=file_size, unit='B', unit_scale=True, unit_divisor=1024, desc=f'Uploading : {name}', bar_format='{l_bar}{bar:100}{r_bar}', colour='cyan', disable=not self.show_progress)
|
|
460
523
|
|
|
@@ -478,9 +541,100 @@ class Robot:
|
|
|
478
541
|
json_data = await response.json()
|
|
479
542
|
if is_temp_file:
|
|
480
543
|
os.remove(path)
|
|
481
|
-
|
|
544
|
+
print(json_data)
|
|
482
545
|
return json_data.get('data', {}).get('file_id')
|
|
483
546
|
|
|
547
|
+
|
|
548
|
+
def get_extension(content_type: str) -> str:
|
|
549
|
+
ext = mimetypes.guess_extension(content_type)
|
|
550
|
+
return ext if ext else ''
|
|
551
|
+
|
|
552
|
+
async def download(self, file_id: str, save_as: str = None, chunk_size: int = 1024 * 512,timeout_sec: int = 60, verbose: bool = False):
|
|
553
|
+
"""
|
|
554
|
+
Download a file from server using its file_id with chunked transfer,
|
|
555
|
+
progress bar, file extension detection, custom filename, and timeout.
|
|
556
|
+
|
|
557
|
+
If save_as is not provided, filename will be extracted from
|
|
558
|
+
Content-Disposition header or Content-Type header extension.
|
|
559
|
+
|
|
560
|
+
Parameters:
|
|
561
|
+
file_id (str): The file ID to fetch the download URL.
|
|
562
|
+
save_as (str, optional): Custom filename to save. If None, automatically detected.
|
|
563
|
+
chunk_size (int, optional): Size of each chunk in bytes. Default 512KB.
|
|
564
|
+
timeout_sec (int, optional): HTTP timeout in seconds. Default 60.
|
|
565
|
+
verbose (bool, optional): Show progress messages. Default True.
|
|
566
|
+
|
|
567
|
+
Returns:
|
|
568
|
+
bool: True if success, raises exceptions otherwise.
|
|
569
|
+
"""
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
url = await self.get_url_file(file_id)
|
|
573
|
+
if not url:
|
|
574
|
+
raise ValueError("Download URL not found in response.")
|
|
575
|
+
except Exception as e:
|
|
576
|
+
raise ValueError(f"Failed to get download URL: {e}")
|
|
577
|
+
|
|
578
|
+
timeout = aiohttp.ClientTimeout(total=timeout_sec)
|
|
579
|
+
|
|
580
|
+
try:
|
|
581
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
582
|
+
async with session.get(url) as resp:
|
|
583
|
+
if resp.status != 200:
|
|
584
|
+
raise aiohttp.ClientResponseError(
|
|
585
|
+
request_info=resp.request_info,
|
|
586
|
+
history=resp.history,
|
|
587
|
+
status=resp.status,
|
|
588
|
+
message="Failed to download file.",
|
|
589
|
+
headers=resp.headers
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
if not save_as:
|
|
593
|
+
content_disp = resp.headers.get("Content-Disposition", "")
|
|
594
|
+
import re
|
|
595
|
+
match = re.search(r'filename="?([^\";]+)"?', content_disp)
|
|
596
|
+
if match:
|
|
597
|
+
save_as = match.group(1)
|
|
598
|
+
else:
|
|
599
|
+
content_type = resp.headers.get("Content-Type", "").split(";")[0]
|
|
600
|
+
extension = mimetypes.guess_extension(content_type) or ".bin"
|
|
601
|
+
save_as = f"{file_id}{extension}"
|
|
602
|
+
|
|
603
|
+
total_size = int(resp.headers.get("Content-Length", 0))
|
|
604
|
+
progress = tqdm(total=total_size, unit="B", unit_scale=True, disable=not verbose)
|
|
605
|
+
|
|
606
|
+
async with aiofiles.open(save_as, "wb") as f:
|
|
607
|
+
async for chunk in resp.content.iter_chunked(chunk_size):
|
|
608
|
+
await f.write(chunk)
|
|
609
|
+
progress.update(len(chunk))
|
|
610
|
+
|
|
611
|
+
progress.close()
|
|
612
|
+
if verbose:
|
|
613
|
+
print(f"✅ File saved as: {save_as}")
|
|
614
|
+
|
|
615
|
+
return True
|
|
616
|
+
|
|
617
|
+
except aiohttp.ClientError as e:
|
|
618
|
+
raise aiohttp.ClientError(f"HTTP error occurred: {e}")
|
|
619
|
+
except asyncio.TimeoutError:
|
|
620
|
+
raise asyncio.TimeoutError("Download timed out.")
|
|
621
|
+
except Exception as e:
|
|
622
|
+
raise Exception(f"Error downloading file: {e}")
|
|
623
|
+
|
|
624
|
+
except aiohttp.ClientError as e:
|
|
625
|
+
raise aiohttp.ClientError(f"HTTP error occurred: {e}")
|
|
626
|
+
except asyncio.TimeoutError:
|
|
627
|
+
raise asyncio.TimeoutError("The download operation timed out.")
|
|
628
|
+
except Exception as e:
|
|
629
|
+
raise Exception(f"An error occurred while downloading the file: {e}")
|
|
630
|
+
|
|
631
|
+
except aiohttp.ClientError as e:
|
|
632
|
+
raise aiohttp.ClientError(f"HTTP error occurred: {e}")
|
|
633
|
+
except asyncio.TimeoutError:
|
|
634
|
+
raise asyncio.TimeoutError("The download operation timed out.")
|
|
635
|
+
except Exception as e:
|
|
636
|
+
raise Exception(f"An error occurred while downloading the file: {e}")
|
|
637
|
+
|
|
484
638
|
async def get_upload_url(self, media_type: Literal['File', 'Image', 'Voice', 'Music', 'Gif', 'Video']) -> str:
|
|
485
639
|
allowed = ['File', 'Image', 'Voice', 'Music', 'Gif', 'Video']
|
|
486
640
|
if media_type not in allowed:
|
|
@@ -504,22 +658,25 @@ class Robot:
|
|
|
504
658
|
raise ValueError("Either path or file_id must be provided.")
|
|
505
659
|
return await self._send_uploaded_file(chat_id=chat_id, file_id=file_id, text=text, inline_keypad=inline_keypad, chat_keypad=chat_keypad, reply_to_message_id=reply_to_message_id, disable_notification=disable_notification, chat_keypad_type=chat_keypad_type)
|
|
506
660
|
|
|
507
|
-
async def send_document(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
661
|
+
async def send_document(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
508
662
|
return await self._send_file_generic("File", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
509
|
-
|
|
510
|
-
|
|
663
|
+
async def send_file(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, caption: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
664
|
+
return await self._send_file_generic("File", chat_id, path, file_id, caption, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
665
|
+
async def re_send(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, caption: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
666
|
+
return await self._send_file_generic("File", chat_id, path, file_id, caption, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
667
|
+
async def send_music(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
511
668
|
return await self._send_file_generic("Music", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
512
669
|
|
|
513
|
-
async def send_video(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
670
|
+
async def send_video(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
514
671
|
return await self._send_file_generic("Video", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
515
672
|
|
|
516
|
-
async def send_voice(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
517
|
-
return await self._send_file_generic("
|
|
673
|
+
async def send_voice(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
674
|
+
return await self._send_file_generic("Video", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
518
675
|
|
|
519
|
-
async def send_image(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
676
|
+
async def send_image(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
520
677
|
return await self._send_file_generic("Image", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
521
678
|
|
|
522
|
-
async def send_gif(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
679
|
+
async def send_gif(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
523
680
|
return await self._send_file_generic("Gif", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
524
681
|
|
|
525
682
|
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> Dict[str, Any]:
|
rubka/context.py
CHANGED
|
@@ -14,9 +14,9 @@ class Sticker:
|
|
|
14
14
|
self.file = File(data.get("file", {}))
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
20
|
class PollStatus:
|
|
21
21
|
def __init__(self, data: dict):
|
|
22
22
|
self.state: str = data.get("state")
|
|
@@ -33,9 +33,9 @@ class Poll:
|
|
|
33
33
|
self.poll_status = PollStatus(data.get("poll_status", {}))
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
39
|
class Location:
|
|
40
40
|
def __init__(self, data: dict):
|
|
41
41
|
self.latitude: str = data.get("latitude")
|
|
@@ -67,18 +67,18 @@ class ForwardedFrom:
|
|
|
67
67
|
self.from_sender_id: str = data.get("from_sender_id")
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
73
|
class AuxData:
|
|
74
74
|
def __init__(self, data: dict):
|
|
75
75
|
self.start_id: str = data.get("start_id")
|
|
76
76
|
self.button_id: str = data.get("button_id")
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
82
|
class ButtonTextbox:
|
|
83
83
|
def __init__(self, data: dict):
|
|
84
84
|
self.type_line: str = data.get("type_line")
|
|
@@ -221,6 +221,13 @@ class Message:
|
|
|
221
221
|
reply_to_message_id=self.message_id,
|
|
222
222
|
**kwargs
|
|
223
223
|
)
|
|
224
|
+
def answer(self, text: str, **kwargs):
|
|
225
|
+
return self.bot.send_message(
|
|
226
|
+
self.chat_id,
|
|
227
|
+
text,
|
|
228
|
+
reply_to_message_id=self.message_id,
|
|
229
|
+
**kwargs
|
|
230
|
+
)
|
|
224
231
|
|
|
225
232
|
def reply_poll(self, question: str, options: List[str], **kwargs) -> Dict[str, Any]:
|
|
226
233
|
return self.bot._post("sendPoll", {
|
|
@@ -242,6 +249,31 @@ class Message:
|
|
|
242
249
|
chat_keypad_type: Optional[str] = "None",
|
|
243
250
|
disable_notification: bool = False
|
|
244
251
|
):
|
|
252
|
+
if chat_keypad and chat_keypad_type == "none":chat_keypad_type == "New"
|
|
253
|
+
return self.bot.send_document(
|
|
254
|
+
chat_id=self.chat_id,
|
|
255
|
+
path=path,
|
|
256
|
+
file_id=file_id,
|
|
257
|
+
text=text,
|
|
258
|
+
chat_keypad=chat_keypad,
|
|
259
|
+
inline_keypad=inline_keypad,
|
|
260
|
+
chat_keypad_type=chat_keypad_type,
|
|
261
|
+
disable_notification=disable_notification,
|
|
262
|
+
reply_to_message_id=self.message_id
|
|
263
|
+
)
|
|
264
|
+
def reply_file(
|
|
265
|
+
self,
|
|
266
|
+
path: Optional[Union[str, Path]] = None,
|
|
267
|
+
file_id: Optional[str] = None,
|
|
268
|
+
text: Optional[str] = None,
|
|
269
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
270
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
271
|
+
chat_keypad_type: Optional[str] = "None",
|
|
272
|
+
disable_notification: bool = False
|
|
273
|
+
):
|
|
274
|
+
if chat_keypad and chat_keypad_type == "none":
|
|
275
|
+
chat_keypad_type == "New"
|
|
276
|
+
|
|
245
277
|
return self.bot.send_document(
|
|
246
278
|
chat_id=self.chat_id,
|
|
247
279
|
path=path,
|
|
@@ -264,6 +296,8 @@ class Message:
|
|
|
264
296
|
chat_keypad_type: Optional[str] = "None",
|
|
265
297
|
disable_notification: bool = False
|
|
266
298
|
):
|
|
299
|
+
if chat_keypad and chat_keypad_type == "none":
|
|
300
|
+
chat_keypad_type == "New"
|
|
267
301
|
return self.bot.send_image(
|
|
268
302
|
chat_id=self.chat_id,
|
|
269
303
|
path=path,
|
|
@@ -286,6 +320,8 @@ class Message:
|
|
|
286
320
|
chat_keypad_type: Optional[str] = "None",
|
|
287
321
|
disable_notification: bool = False
|
|
288
322
|
):
|
|
323
|
+
if chat_keypad and chat_keypad_type == "none":
|
|
324
|
+
chat_keypad_type == "New"
|
|
289
325
|
return self.bot.send_music(
|
|
290
326
|
chat_id=self.chat_id,
|
|
291
327
|
path=path,
|
|
@@ -308,6 +344,8 @@ class Message:
|
|
|
308
344
|
chat_keypad_type: Optional[str] = "None",
|
|
309
345
|
disable_notification: bool = False
|
|
310
346
|
):
|
|
347
|
+
if chat_keypad and chat_keypad_type == "none":
|
|
348
|
+
chat_keypad_type == "New"
|
|
311
349
|
return self.bot.send_voice(
|
|
312
350
|
chat_id=self.chat_id,
|
|
313
351
|
path=path,
|
|
@@ -330,6 +368,7 @@ class Message:
|
|
|
330
368
|
chat_keypad_type: Optional[str] = "None",
|
|
331
369
|
disable_notification: bool = False
|
|
332
370
|
):
|
|
371
|
+
if chat_keypad and chat_keypad_type == "none":chat_keypad_type == "New"
|
|
333
372
|
return self.bot.send_gif(
|
|
334
373
|
chat_id=self.chat_id,
|
|
335
374
|
path=path,
|
|
@@ -388,14 +427,6 @@ class Message:
|
|
|
388
427
|
**kwargs
|
|
389
428
|
})
|
|
390
429
|
|
|
391
|
-
def reply_file(self, file_id: str, **kwargs) -> Dict[str, Any]:
|
|
392
|
-
return self.bot._post("sendFile", {
|
|
393
|
-
"chat_id": self.chat_id,
|
|
394
|
-
"file_id": file_id,
|
|
395
|
-
"reply_to_message_id": self.message_id,
|
|
396
|
-
**kwargs
|
|
397
|
-
})
|
|
398
|
-
|
|
399
430
|
def edit(self, new_text: str) -> Dict[str, Any]:
|
|
400
431
|
return self.bot.edit_message_text(
|
|
401
432
|
chat_id=self.chat_id,
|
|
@@ -413,12 +444,25 @@ class AuxData:
|
|
|
413
444
|
self.start_id = data.get("start_id")
|
|
414
445
|
self.button_id = data.get("button_id")
|
|
415
446
|
|
|
416
|
-
|
|
447
|
+
|
|
417
448
|
class InlineMessage:
|
|
418
449
|
def __init__(self, bot, raw_data: dict):
|
|
419
450
|
self.bot = bot
|
|
420
451
|
self.raw_data = raw_data
|
|
421
|
-
|
|
452
|
+
self.chat_id = self.raw_data.get("chat_id")
|
|
453
|
+
self.time: str = self.raw_data.get("time")
|
|
454
|
+
self.is_edited: bool = self.raw_data.get("is_edited", False)
|
|
455
|
+
self.sender_type: str = self.raw_data.get("sender_type")
|
|
456
|
+
self.args = []
|
|
457
|
+
self.reply_to_message_id: Optional[str] = self.raw_data.get("reply_to_message_id")
|
|
458
|
+
self.forwarded_from = ForwardedFrom(self.raw_data["forwarded_from"]) if "forwarded_from" in self.raw_data else None
|
|
459
|
+
self.file = File(self.raw_data["file"]) if "file" in self.raw_data else None
|
|
460
|
+
self.sticker = Sticker(self.raw_data["sticker"]) if "sticker" in self.raw_data else None
|
|
461
|
+
self.contact_message = ContactMessage(self.raw_data["contact_message"]) if "contact_message" in self.raw_data else None
|
|
462
|
+
self.poll = Poll(self.raw_data["poll"]) if "poll" in self.raw_data else None
|
|
463
|
+
self.location = Location(self.raw_data["location"]) if "location" in self.raw_data else None
|
|
464
|
+
self.live_location = LiveLocation(self.raw_data["live_location"]) if "live_location" in self.raw_data else None
|
|
465
|
+
self.aux_data = AuxData(self.raw_data["aux_data"]) if "aux_data" in self.raw_data else None
|
|
422
466
|
self.chat_id: str = raw_data.get("chat_id")
|
|
423
467
|
self.message_id: str = raw_data.get("message_id")
|
|
424
468
|
self.sender_id: str = raw_data.get("sender_id")
|
|
@@ -432,6 +476,13 @@ class InlineMessage:
|
|
|
432
476
|
reply_to_message_id=self.message_id,
|
|
433
477
|
**kwargs
|
|
434
478
|
)
|
|
479
|
+
def answer(self, text: str, **kwargs):
|
|
480
|
+
return self.bot.send_message(
|
|
481
|
+
self.chat_id,
|
|
482
|
+
text,
|
|
483
|
+
reply_to_message_id=self.message_id,
|
|
484
|
+
**kwargs
|
|
485
|
+
)
|
|
435
486
|
|
|
436
487
|
def edit(self, new_text: str):
|
|
437
488
|
return self.bot.edit_message_text(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6.0
|
|
4
4
|
Summary: A Python library for interacting with Rubika Bot API.
|
|
5
5
|
Home-page: https://github.com/Mahdy-Ahmadi/Rubka
|
|
6
6
|
Download-URL: https://github.com/Mahdy-Ahmadi/rubka/blob/main/project_library.zip
|
|
@@ -19,6 +19,11 @@ Requires-Dist: requests
|
|
|
19
19
|
Requires-Dist: Pillow
|
|
20
20
|
Requires-Dist: websocket-client
|
|
21
21
|
Requires-Dist: pycryptodome
|
|
22
|
+
Requires-Dist: aiohttp
|
|
23
|
+
Requires-Dist: tqdm
|
|
24
|
+
Requires-Dist: mutagen
|
|
25
|
+
Requires-Dist: filetype
|
|
26
|
+
Requires-Dist: aiofiles
|
|
22
27
|
Dynamic: author
|
|
23
28
|
Dynamic: author-email
|
|
24
29
|
Dynamic: classifier
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
rubka/__init__.py,sha256=TR1DABU5Maz2eO62ZEFiwOqNU0dH6l6HZfqRUxeo4eY,194
|
|
2
|
-
rubka/api.py,sha256
|
|
3
|
-
rubka/asynco.py,sha256=
|
|
2
|
+
rubka/api.py,sha256=-ayu17rFI35hU1O7ePxLQNTK-upj2uJxOflA0JUdWdI,39421
|
|
3
|
+
rubka/asynco.py,sha256=f2OH2JDdB98x7T4c6B1n-dTWpCe8R5KkkHUdwr9IP9g,37433
|
|
4
4
|
rubka/button.py,sha256=4fMSZR7vUADxSmw1R3_pZ4dw5uMLZX5sOkwPPyNTBDE,8437
|
|
5
5
|
rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
|
|
6
|
-
rubka/context.py,sha256=
|
|
6
|
+
rubka/context.py,sha256=jtdMYf2slFxK2kmkVO6MMSTl-yu5NH52UvzAOyB79hw,19317
|
|
7
7
|
rubka/decorators.py,sha256=hGwUoE4q2ImrunJIGJ_kzGYYxQf1ueE0isadqraKEts,1157
|
|
8
8
|
rubka/exceptions.py,sha256=tujZt1XrhWaw-lmdeVadVceUptpw4XzNgE44sAAY0gs,90
|
|
9
9
|
rubka/jobs.py,sha256=GvLMLsVhcSEzRTgkvnPISPEBN71suW2xXI0hUaUZPTo,378
|
|
@@ -33,7 +33,7 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
|
|
|
33
33
|
rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
|
|
34
34
|
rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
|
|
35
35
|
rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
|
|
36
|
-
rubka-4.
|
|
37
|
-
rubka-4.
|
|
38
|
-
rubka-4.
|
|
39
|
-
rubka-4.
|
|
36
|
+
rubka-4.6.0.dist-info/METADATA,sha256=ZSH799BnofV3TFaXKdi_unkoru9Yki1Sbv3L1upObMI,33335
|
|
37
|
+
rubka-4.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
rubka-4.6.0.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
|
|
39
|
+
rubka-4.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|