Rubka 4.5.2__py3-none-any.whl → 4.5.11__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 +131 -23
- rubka/asynco.py +141 -16
- rubka/context.py +32 -8
- {rubka-4.5.2.dist-info → rubka-4.5.11.dist-info}/METADATA +1 -1
- {rubka-4.5.2.dist-info → rubka-4.5.11.dist-info}/RECORD +7 -7
- {rubka-4.5.2.dist-info → rubka-4.5.11.dist-info}/WHEEL +0 -0
- {rubka-4.5.2.dist-info → rubka-4.5.11.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:
|
|
@@ -343,7 +336,6 @@ class Robot:
|
|
|
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.
|
|
502
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}")
|
|
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
|
|
@@ -307,6 +311,7 @@ class Robot:
|
|
|
307
311
|
|
|
308
312
|
self._processed_message_ids[message_id] = now
|
|
309
313
|
return False
|
|
314
|
+
|
|
310
315
|
|
|
311
316
|
async def run(self):
|
|
312
317
|
"""
|
|
@@ -384,14 +389,40 @@ class Robot:
|
|
|
384
389
|
await self._aiohttp_session.close()
|
|
385
390
|
print("Bot stopped and session closed.")
|
|
386
391
|
|
|
387
|
-
async def send_message(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
392
|
+
async def send_message(
|
|
393
|
+
self,
|
|
394
|
+
chat_id: str,
|
|
395
|
+
text: str,
|
|
396
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
397
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
398
|
+
disable_notification: bool = False,
|
|
399
|
+
reply_to_message_id: Optional[str] = None,
|
|
400
|
+
chat_keypad_type: Optional[Literal["New", "Removed"]] = None
|
|
401
|
+
) -> Dict[str, Any]:
|
|
402
|
+
payload = {
|
|
403
|
+
"chat_id": chat_id,
|
|
404
|
+
"text": text,
|
|
405
|
+
"disable_notification": disable_notification,
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if chat_keypad:
|
|
409
|
+
payload["chat_keypad"] = chat_keypad
|
|
410
|
+
payload["chat_keypad_type"] = chat_keypad_type or "New"
|
|
411
|
+
|
|
412
|
+
if inline_keypad:
|
|
413
|
+
payload["inline_keypad"] = inline_keypad
|
|
414
|
+
|
|
415
|
+
if reply_to_message_id:
|
|
416
|
+
payload["reply_to_message_id"] = reply_to_message_id
|
|
417
|
+
|
|
393
418
|
return await self._post("sendMessage", payload)
|
|
394
419
|
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
async def get_url_file(self,file_id):
|
|
423
|
+
data = await self._post("getFile", {'file_id': file_id})
|
|
424
|
+
return data.get("data").get("download_url")
|
|
425
|
+
|
|
395
426
|
def _get_client(self) -> Client_get:
|
|
396
427
|
if self.session_name:
|
|
397
428
|
return Client_get(self.session_name, self.auth, self.Key, self.platform)
|
|
@@ -454,7 +485,7 @@ class Robot:
|
|
|
454
485
|
path = temp_file.name
|
|
455
486
|
is_temp_file = True
|
|
456
487
|
|
|
457
|
-
file_size = os.path.getsize(path)
|
|
488
|
+
file_size = os.path.getsize(path)
|
|
458
489
|
|
|
459
490
|
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
491
|
|
|
@@ -478,9 +509,100 @@ class Robot:
|
|
|
478
509
|
json_data = await response.json()
|
|
479
510
|
if is_temp_file:
|
|
480
511
|
os.remove(path)
|
|
481
|
-
|
|
512
|
+
print(json_data)
|
|
482
513
|
return json_data.get('data', {}).get('file_id')
|
|
483
514
|
|
|
515
|
+
|
|
516
|
+
def get_extension(content_type: str) -> str:
|
|
517
|
+
ext = mimetypes.guess_extension(content_type)
|
|
518
|
+
return ext if ext else ''
|
|
519
|
+
|
|
520
|
+
async def download(self, file_id: str, save_as: str = None, chunk_size: int = 1024 * 512,timeout_sec: int = 60, verbose: bool = False):
|
|
521
|
+
"""
|
|
522
|
+
Download a file from server using its file_id with chunked transfer,
|
|
523
|
+
progress bar, file extension detection, custom filename, and timeout.
|
|
524
|
+
|
|
525
|
+
If save_as is not provided, filename will be extracted from
|
|
526
|
+
Content-Disposition header or Content-Type header extension.
|
|
527
|
+
|
|
528
|
+
Parameters:
|
|
529
|
+
file_id (str): The file ID to fetch the download URL.
|
|
530
|
+
save_as (str, optional): Custom filename to save. If None, automatically detected.
|
|
531
|
+
chunk_size (int, optional): Size of each chunk in bytes. Default 512KB.
|
|
532
|
+
timeout_sec (int, optional): HTTP timeout in seconds. Default 60.
|
|
533
|
+
verbose (bool, optional): Show progress messages. Default True.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
bool: True if success, raises exceptions otherwise.
|
|
537
|
+
"""
|
|
538
|
+
|
|
539
|
+
try:
|
|
540
|
+
url = await self.get_url_file(file_id)
|
|
541
|
+
if not url:
|
|
542
|
+
raise ValueError("Download URL not found in response.")
|
|
543
|
+
except Exception as e:
|
|
544
|
+
raise ValueError(f"Failed to get download URL: {e}")
|
|
545
|
+
|
|
546
|
+
timeout = aiohttp.ClientTimeout(total=timeout_sec)
|
|
547
|
+
|
|
548
|
+
try:
|
|
549
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
550
|
+
async with session.get(url) as resp:
|
|
551
|
+
if resp.status != 200:
|
|
552
|
+
raise aiohttp.ClientResponseError(
|
|
553
|
+
request_info=resp.request_info,
|
|
554
|
+
history=resp.history,
|
|
555
|
+
status=resp.status,
|
|
556
|
+
message="Failed to download file.",
|
|
557
|
+
headers=resp.headers
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
if not save_as:
|
|
561
|
+
content_disp = resp.headers.get("Content-Disposition", "")
|
|
562
|
+
import re
|
|
563
|
+
match = re.search(r'filename="?([^\";]+)"?', content_disp)
|
|
564
|
+
if match:
|
|
565
|
+
save_as = match.group(1)
|
|
566
|
+
else:
|
|
567
|
+
content_type = resp.headers.get("Content-Type", "").split(";")[0]
|
|
568
|
+
extension = mimetypes.guess_extension(content_type) or ".bin"
|
|
569
|
+
save_as = f"{file_id}{extension}"
|
|
570
|
+
|
|
571
|
+
total_size = int(resp.headers.get("Content-Length", 0))
|
|
572
|
+
progress = tqdm(total=total_size, unit="B", unit_scale=True, disable=not verbose)
|
|
573
|
+
|
|
574
|
+
async with aiofiles.open(save_as, "wb") as f:
|
|
575
|
+
async for chunk in resp.content.iter_chunked(chunk_size):
|
|
576
|
+
await f.write(chunk)
|
|
577
|
+
progress.update(len(chunk))
|
|
578
|
+
|
|
579
|
+
progress.close()
|
|
580
|
+
if verbose:
|
|
581
|
+
print(f"✅ File saved as: {save_as}")
|
|
582
|
+
|
|
583
|
+
return True
|
|
584
|
+
|
|
585
|
+
except aiohttp.ClientError as e:
|
|
586
|
+
raise aiohttp.ClientError(f"HTTP error occurred: {e}")
|
|
587
|
+
except asyncio.TimeoutError:
|
|
588
|
+
raise asyncio.TimeoutError("Download timed out.")
|
|
589
|
+
except Exception as e:
|
|
590
|
+
raise Exception(f"Error downloading file: {e}")
|
|
591
|
+
|
|
592
|
+
except aiohttp.ClientError as e:
|
|
593
|
+
raise aiohttp.ClientError(f"HTTP error occurred: {e}")
|
|
594
|
+
except asyncio.TimeoutError:
|
|
595
|
+
raise asyncio.TimeoutError("The download operation timed out.")
|
|
596
|
+
except Exception as e:
|
|
597
|
+
raise Exception(f"An error occurred while downloading the file: {e}")
|
|
598
|
+
|
|
599
|
+
except aiohttp.ClientError as e:
|
|
600
|
+
raise aiohttp.ClientError(f"HTTP error occurred: {e}")
|
|
601
|
+
except asyncio.TimeoutError:
|
|
602
|
+
raise asyncio.TimeoutError("The download operation timed out.")
|
|
603
|
+
except Exception as e:
|
|
604
|
+
raise Exception(f"An error occurred while downloading the file: {e}")
|
|
605
|
+
|
|
484
606
|
async def get_upload_url(self, media_type: Literal['File', 'Image', 'Voice', 'Music', 'Gif', 'Video']) -> str:
|
|
485
607
|
allowed = ['File', 'Image', 'Voice', 'Music', 'Gif', 'Video']
|
|
486
608
|
if media_type not in allowed:
|
|
@@ -504,22 +626,25 @@ class Robot:
|
|
|
504
626
|
raise ValueError("Either path or file_id must be provided.")
|
|
505
627
|
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
628
|
|
|
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"]] = "
|
|
629
|
+
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
630
|
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
|
-
|
|
631
|
+
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]:
|
|
632
|
+
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)
|
|
633
|
+
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]:
|
|
634
|
+
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)
|
|
635
|
+
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
636
|
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
637
|
|
|
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"]] = "
|
|
638
|
+
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
639
|
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
640
|
|
|
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"]] = "
|
|
641
|
+
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]:
|
|
517
642
|
return await self._send_file_generic("Voice", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
518
643
|
|
|
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"]] = "
|
|
644
|
+
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
645
|
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
646
|
|
|
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"]] = "
|
|
647
|
+
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
648
|
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
649
|
|
|
525
650
|
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
|
@@ -242,6 +242,31 @@ class Message:
|
|
|
242
242
|
chat_keypad_type: Optional[str] = "None",
|
|
243
243
|
disable_notification: bool = False
|
|
244
244
|
):
|
|
245
|
+
if chat_keypad and chat_keypad_type == "none":chat_keypad_type == "New"
|
|
246
|
+
return self.bot.send_document(
|
|
247
|
+
chat_id=self.chat_id,
|
|
248
|
+
path=path,
|
|
249
|
+
file_id=file_id,
|
|
250
|
+
text=text,
|
|
251
|
+
chat_keypad=chat_keypad,
|
|
252
|
+
inline_keypad=inline_keypad,
|
|
253
|
+
chat_keypad_type=chat_keypad_type,
|
|
254
|
+
disable_notification=disable_notification,
|
|
255
|
+
reply_to_message_id=self.message_id
|
|
256
|
+
)
|
|
257
|
+
def reply_file(
|
|
258
|
+
self,
|
|
259
|
+
path: Optional[Union[str, Path]] = None,
|
|
260
|
+
file_id: Optional[str] = None,
|
|
261
|
+
text: Optional[str] = None,
|
|
262
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
263
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
264
|
+
chat_keypad_type: Optional[str] = "None",
|
|
265
|
+
disable_notification: bool = False
|
|
266
|
+
):
|
|
267
|
+
if chat_keypad and chat_keypad_type == "none":
|
|
268
|
+
chat_keypad_type == "New"
|
|
269
|
+
|
|
245
270
|
return self.bot.send_document(
|
|
246
271
|
chat_id=self.chat_id,
|
|
247
272
|
path=path,
|
|
@@ -264,6 +289,8 @@ class Message:
|
|
|
264
289
|
chat_keypad_type: Optional[str] = "None",
|
|
265
290
|
disable_notification: bool = False
|
|
266
291
|
):
|
|
292
|
+
if chat_keypad and chat_keypad_type == "none":
|
|
293
|
+
chat_keypad_type == "New"
|
|
267
294
|
return self.bot.send_image(
|
|
268
295
|
chat_id=self.chat_id,
|
|
269
296
|
path=path,
|
|
@@ -286,6 +313,8 @@ class Message:
|
|
|
286
313
|
chat_keypad_type: Optional[str] = "None",
|
|
287
314
|
disable_notification: bool = False
|
|
288
315
|
):
|
|
316
|
+
if chat_keypad and chat_keypad_type == "none":
|
|
317
|
+
chat_keypad_type == "New"
|
|
289
318
|
return self.bot.send_music(
|
|
290
319
|
chat_id=self.chat_id,
|
|
291
320
|
path=path,
|
|
@@ -308,6 +337,8 @@ class Message:
|
|
|
308
337
|
chat_keypad_type: Optional[str] = "None",
|
|
309
338
|
disable_notification: bool = False
|
|
310
339
|
):
|
|
340
|
+
if chat_keypad and chat_keypad_type == "none":
|
|
341
|
+
chat_keypad_type == "New"
|
|
311
342
|
return self.bot.send_voice(
|
|
312
343
|
chat_id=self.chat_id,
|
|
313
344
|
path=path,
|
|
@@ -330,6 +361,7 @@ class Message:
|
|
|
330
361
|
chat_keypad_type: Optional[str] = "None",
|
|
331
362
|
disable_notification: bool = False
|
|
332
363
|
):
|
|
364
|
+
if chat_keypad and chat_keypad_type == "none":chat_keypad_type == "New"
|
|
333
365
|
return self.bot.send_gif(
|
|
334
366
|
chat_id=self.chat_id,
|
|
335
367
|
path=path,
|
|
@@ -388,14 +420,6 @@ class Message:
|
|
|
388
420
|
**kwargs
|
|
389
421
|
})
|
|
390
422
|
|
|
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
423
|
def edit(self, new_text: str) -> Dict[str, Any]:
|
|
400
424
|
return self.bot.edit_message_text(
|
|
401
425
|
chat_id=self.chat_id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 4.5.
|
|
3
|
+
Version: 4.5.11
|
|
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
|
|
@@ -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=0gj52uxIGmLCVRUxnvjIHF6MVBiXnVBdVr_SRXp2I4M,39416
|
|
3
|
+
rubka/asynco.py,sha256=DDAeES9qDQ9vegHT-7X7RL72G510YObFN7qqJ948NEQ,37647
|
|
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=KXfDqn3vir6oIupF7piKD89Sqj1MjWoSerS4WyyHXBw,18062
|
|
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.5.
|
|
37
|
-
rubka-4.5.
|
|
38
|
-
rubka-4.5.
|
|
39
|
-
rubka-4.5.
|
|
36
|
+
rubka-4.5.11.dist-info/METADATA,sha256=_9gtchOWeb6ROIeF58OO4Egk7uDSL6Rc-xvcvAasyKg,33217
|
|
37
|
+
rubka-4.5.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
rubka-4.5.11.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
|
|
39
|
+
rubka-4.5.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|