Rubka 4.5.0__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 +159 -56
- rubka/asynco.py +172 -33
- rubka/context.py +32 -8
- {rubka-4.5.0.dist-info → rubka-4.5.11.dist-info}/METADATA +1 -1
- {rubka-4.5.0.dist-info → rubka-4.5.11.dist-info}/RECORD +7 -7
- {rubka-4.5.0.dist-info → rubka-4.5.11.dist-info}/WHEEL +0 -0
- {rubka-4.5.0.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,14 +111,16 @@ 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]] = {}
|
|
115
118
|
self._callback_handler = None
|
|
116
119
|
self._message_handler = None
|
|
117
120
|
self._inline_query_handler = None
|
|
121
|
+
self._message_handlers: List[dict] = []
|
|
118
122
|
self._callback_handlers = None
|
|
119
|
-
self._callback_handlers = []
|
|
123
|
+
self._callback_handlers = []
|
|
120
124
|
if web_hook:
|
|
121
125
|
try:
|
|
122
126
|
json_url = requests.get(web_hook, timeout=self.timeout).json().get('url', web_hook)
|
|
@@ -132,7 +136,7 @@ class Robot:
|
|
|
132
136
|
except Exception as e:
|
|
133
137
|
logger.error(f"Failed to set webhook from {web_hook}: {e}")
|
|
134
138
|
else:
|
|
135
|
-
self.web_hook =
|
|
139
|
+
self.web_hook = self.hook
|
|
136
140
|
|
|
137
141
|
|
|
138
142
|
|
|
@@ -161,11 +165,11 @@ class Robot:
|
|
|
161
165
|
return self._post("getMe", {})
|
|
162
166
|
def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
163
167
|
def decorator(func: Callable[[Any, Message], None]):
|
|
164
|
-
self.
|
|
168
|
+
self._message_handlers.append({
|
|
165
169
|
"func": func,
|
|
166
170
|
"filters": filters,
|
|
167
171
|
"commands": commands
|
|
168
|
-
}
|
|
172
|
+
})
|
|
169
173
|
return func
|
|
170
174
|
return decorator
|
|
171
175
|
|
|
@@ -200,64 +204,52 @@ class Robot:
|
|
|
200
204
|
|
|
201
205
|
|
|
202
206
|
def _process_update(self, update: dict):
|
|
203
|
-
import threading
|
|
207
|
+
import threading
|
|
204
208
|
|
|
205
|
-
# هندل پیام inline (معمولاً type = ReceiveQuery)
|
|
206
209
|
if update.get("type") == "ReceiveQuery":
|
|
207
210
|
msg = update.get("inline_message", {})
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
threading.Thread(target=self._handle_inline_query, args=(context,), daemon=True).start()
|
|
212
|
-
return
|
|
211
|
+
context = InlineMessage(bot=self, raw_data=msg)
|
|
212
|
+
threading.Thread(target=self._handle_inline_query, args=(context,), daemon=True).start()
|
|
213
|
+
return
|
|
213
214
|
|
|
214
|
-
# هندل پیام جدید متنی
|
|
215
215
|
if update.get("type") == "NewMessage":
|
|
216
216
|
msg = update.get("new_message", {})
|
|
217
|
-
chat_id = update.get("chat_id")
|
|
218
|
-
message_id = msg.get("message_id")
|
|
219
|
-
sender_id = msg.get("sender_id")
|
|
220
|
-
text = msg.get("text")
|
|
221
|
-
|
|
222
|
-
# فیلتر زمان پیام (مثلاً پیامهای خیلی قدیمی)
|
|
223
217
|
try:
|
|
224
218
|
if msg.get("time") and (time.time() - float(msg["time"])) > 20:
|
|
225
219
|
return
|
|
226
220
|
except Exception:
|
|
227
221
|
return
|
|
228
222
|
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
context = Message(bot=self,
|
|
224
|
+
chat_id=update.get("chat_id"),
|
|
225
|
+
message_id=msg.get("message_id"),
|
|
226
|
+
sender_id=msg.get("sender_id"),
|
|
227
|
+
text=msg.get("text"),
|
|
228
|
+
raw_data=msg)
|
|
231
229
|
|
|
232
|
-
|
|
233
|
-
if context.aux_data and hasattr(self, "_callback_handlers"):
|
|
230
|
+
if context.aux_data and self._callback_handlers:
|
|
234
231
|
for handler in self._callback_handlers:
|
|
235
232
|
if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
|
|
236
233
|
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
237
|
-
return # فقط یک callback اجرا شود
|
|
238
|
-
|
|
239
|
-
# هندل پیامهای متنی معمولی
|
|
240
|
-
if hasattr(self, "_message_handler") and self._message_handler:
|
|
241
|
-
handler = self._message_handler
|
|
242
|
-
|
|
243
|
-
if handler["commands"]:
|
|
244
|
-
if not context.text or not context.text.startswith("/"):
|
|
245
|
-
return
|
|
246
|
-
parts = context.text.split()
|
|
247
|
-
cmd = parts[0][1:]
|
|
248
|
-
if cmd not in handler["commands"]:
|
|
249
234
|
return
|
|
250
|
-
context.args = parts[1:]
|
|
251
235
|
|
|
252
|
-
|
|
236
|
+
if self._message_handlers:
|
|
237
|
+
for handler in self._message_handlers:
|
|
238
|
+
if handler["commands"]:
|
|
239
|
+
if not context.text or not context.text.startswith("/"):
|
|
240
|
+
continue
|
|
241
|
+
parts = context.text.split()
|
|
242
|
+
cmd = parts[0][1:]
|
|
243
|
+
if cmd not in handler["commands"]:
|
|
244
|
+
continue
|
|
245
|
+
context.args = parts[1:]
|
|
246
|
+
|
|
247
|
+
if handler["filters"] and not handler["filters"](context):
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
253
251
|
return
|
|
254
252
|
|
|
255
|
-
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
253
|
def get_updates(
|
|
262
254
|
self,
|
|
263
255
|
offset_id: Optional[str] = None,
|
|
@@ -284,12 +276,10 @@ class Robot:
|
|
|
284
276
|
def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
|
|
285
277
|
now = time.time()
|
|
286
278
|
|
|
287
|
-
# حذف پیامهای قدیمیتر از max_age_sec
|
|
288
279
|
expired = [mid for mid, ts in self._processed_message_ids.items() if now - ts > max_age_sec]
|
|
289
280
|
for mid in expired:
|
|
290
281
|
del self._processed_message_ids[mid]
|
|
291
282
|
|
|
292
|
-
# بررسی تکراری بودن پیام
|
|
293
283
|
if message_id in self._processed_message_ids:
|
|
294
284
|
return True
|
|
295
285
|
|
|
@@ -303,7 +293,6 @@ class Robot:
|
|
|
303
293
|
def run(self):
|
|
304
294
|
print("Bot started running...")
|
|
305
295
|
self._processed_message_ids: Dict[str, float] = {}
|
|
306
|
-
# self._offset_id میتواند قبلاً مقداردهی شده باشد
|
|
307
296
|
|
|
308
297
|
while True:
|
|
309
298
|
try:
|
|
@@ -314,12 +303,11 @@ class Robot:
|
|
|
314
303
|
for item in updates:
|
|
315
304
|
data = item.get("data", {})
|
|
316
305
|
|
|
317
|
-
# بررسی زمان دریافت پیام و رد کردن پیامهای قدیمی (بیش از 20 ثانیه)
|
|
318
306
|
received_at_str = item.get("received_at")
|
|
319
307
|
if received_at_str:
|
|
320
308
|
received_at_ts = datetime.datetime.strptime(received_at_str, "%Y-%m-%d %H:%M:%S").timestamp()
|
|
321
309
|
if time.time() - received_at_ts > 20:
|
|
322
|
-
continue
|
|
310
|
+
continue
|
|
323
311
|
|
|
324
312
|
update = None
|
|
325
313
|
if "update" in data:
|
|
@@ -348,7 +336,6 @@ class Robot:
|
|
|
348
336
|
|
|
349
337
|
self._process_update(update)
|
|
350
338
|
|
|
351
|
-
# ثبت پیام به عنوان پردازش شده
|
|
352
339
|
if message_id:
|
|
353
340
|
self._processed_message_ids[message_id] = time.time()
|
|
354
341
|
|
|
@@ -436,6 +423,10 @@ class Robot:
|
|
|
436
423
|
|
|
437
424
|
return False
|
|
438
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
|
+
|
|
439
430
|
|
|
440
431
|
def get_all_member(
|
|
441
432
|
self,
|
|
@@ -484,7 +475,6 @@ class Robot:
|
|
|
484
475
|
"reply_to_message_id": reply_to_message_id,
|
|
485
476
|
"chat_keypad_type": chat_keypad_type
|
|
486
477
|
}
|
|
487
|
-
# Remove None values
|
|
488
478
|
payload = {k: v for k, v in payload.items() if v is not None}
|
|
489
479
|
return self._post("sendLocation", payload)
|
|
490
480
|
|
|
@@ -504,7 +494,63 @@ class Robot:
|
|
|
504
494
|
"last_name": last_name,
|
|
505
495
|
"phone_number": phone_number
|
|
506
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.
|
|
507
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}")
|
|
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}")
|
|
508
554
|
def get_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
509
555
|
"""Get chat info."""
|
|
510
556
|
return self._post("getChat", {"chat_id": chat_id})
|
|
@@ -605,7 +651,64 @@ class Robot:
|
|
|
605
651
|
payload["reply_to_message_id"] = str(reply_to_message_id)
|
|
606
652
|
|
|
607
653
|
return self._post("sendFile", payload)
|
|
608
|
-
|
|
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
|
+
)
|
|
609
712
|
def send_document(
|
|
610
713
|
self,
|
|
611
714
|
chat_id: str,
|
|
@@ -617,7 +720,7 @@ class Robot:
|
|
|
617
720
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
618
721
|
reply_to_message_id: Optional[str] = None,
|
|
619
722
|
disable_notification: bool = False,
|
|
620
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
723
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
621
724
|
) -> Dict[str, Any]:
|
|
622
725
|
if path:
|
|
623
726
|
file_name = file_name or Path(path).name
|
|
@@ -646,7 +749,7 @@ class Robot:
|
|
|
646
749
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
647
750
|
reply_to_message_id: Optional[str] = None,
|
|
648
751
|
disable_notification: bool = False,
|
|
649
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
752
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
650
753
|
) -> Dict[str, Any]:
|
|
651
754
|
if path:
|
|
652
755
|
file_name = file_name or Path(path).name
|
|
@@ -675,7 +778,7 @@ class Robot:
|
|
|
675
778
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
676
779
|
reply_to_message_id: Optional[str] = None,
|
|
677
780
|
disable_notification: bool = False,
|
|
678
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
781
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
679
782
|
) -> Dict[str, Any]:
|
|
680
783
|
if path:
|
|
681
784
|
file_name = file_name or Path(path).name
|
|
@@ -704,7 +807,7 @@ class Robot:
|
|
|
704
807
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
705
808
|
reply_to_message_id: Optional[str] = None,
|
|
706
809
|
disable_notification: bool = False,
|
|
707
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
810
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
708
811
|
) -> Dict[str, Any]:
|
|
709
812
|
if path:
|
|
710
813
|
file_name = file_name or Path(path).name
|
|
@@ -733,7 +836,7 @@ class Robot:
|
|
|
733
836
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
734
837
|
reply_to_message_id: Optional[str] = None,
|
|
735
838
|
disable_notification: bool = False,
|
|
736
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
839
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
737
840
|
) -> Dict[str, Any]:
|
|
738
841
|
if path:
|
|
739
842
|
file_name = file_name or Path(path).name
|
|
@@ -762,7 +865,7 @@ class Robot:
|
|
|
762
865
|
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
763
866
|
reply_to_message_id: Optional[str] = None,
|
|
764
867
|
disable_notification: bool = False,
|
|
765
|
-
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "
|
|
868
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
|
|
766
869
|
) -> Dict[str, Any]:
|
|
767
870
|
if path:
|
|
768
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
|
|
@@ -119,6 +123,7 @@ class Robot:
|
|
|
119
123
|
self._inline_query_handler = None
|
|
120
124
|
self._callback_handlers: List[dict] = []
|
|
121
125
|
self._processed_message_ids: Dict[str, float] = {}
|
|
126
|
+
self._message_handlers: List[dict] = []
|
|
122
127
|
|
|
123
128
|
logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
|
|
124
129
|
|
|
@@ -184,11 +189,11 @@ class Robot:
|
|
|
184
189
|
|
|
185
190
|
def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
186
191
|
def decorator(func: Callable[[Any, Message], None]):
|
|
187
|
-
self.
|
|
192
|
+
self._message_handlers.append({
|
|
188
193
|
"func": func,
|
|
189
194
|
"filters": filters,
|
|
190
195
|
"commands": commands
|
|
191
|
-
}
|
|
196
|
+
})
|
|
192
197
|
return func
|
|
193
198
|
return decorator
|
|
194
199
|
|
|
@@ -244,27 +249,40 @@ class Robot:
|
|
|
244
249
|
text=msg.get("text"),
|
|
245
250
|
raw_data=msg)
|
|
246
251
|
|
|
252
|
+
# پردازش دکمههای شیشهای (بدون تغییر)
|
|
247
253
|
if context.aux_data and self._callback_handlers:
|
|
248
254
|
for handler in self._callback_handlers:
|
|
249
255
|
if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
|
|
250
256
|
asyncio.create_task(handler["func"](self, context))
|
|
251
257
|
return
|
|
252
258
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
259
|
+
# پردازش پیامهای متنی با حلقه روی تمام هندلرها
|
|
260
|
+
if self._message_handlers:
|
|
261
|
+
for handler_info in self._message_handlers:
|
|
262
|
+
# بررسی شرط دستورات (commands)
|
|
263
|
+
if handler_info["commands"]:
|
|
264
|
+
if not context.text or not context.text.startswith("/"):
|
|
265
|
+
continue # اگر پیام کامند نبود، این هندلر را رد کن
|
|
266
|
+
parts = context.text.split()
|
|
267
|
+
cmd = parts[0][1:]
|
|
268
|
+
if cmd not in handler_info["commands"]:
|
|
269
|
+
continue # اگر کامند مطابقت نداشت، این هندلر را رد کن
|
|
270
|
+
context.args = parts[1:]
|
|
271
|
+
|
|
272
|
+
# بررسی شرط فیلترها (filters)
|
|
273
|
+
if handler_info["filters"]:
|
|
274
|
+
if not handler_info["filters"](context):
|
|
275
|
+
continue # اگر فیلتر برقرار نبود، این هندلر را رد کن
|
|
276
|
+
|
|
277
|
+
# اگر هندلری برای همه پیامها باشد (بدون کامند و فیلتر)
|
|
278
|
+
if not handler_info["commands"] and not handler_info["filters"]:
|
|
279
|
+
asyncio.create_task(handler_info["func"](self, context))
|
|
280
|
+
return # بعد از یافتن هندلر مناسب، از حلقه خارج شو
|
|
281
|
+
|
|
282
|
+
# اگر شرایط کامند یا فیلتر برقرار بود
|
|
283
|
+
if handler_info["commands"] or handler_info["filters"]:
|
|
284
|
+
asyncio.create_task(handler_info["func"](self, context))
|
|
285
|
+
return # بعد از یافتن هندلر مناسب، از حلقه خارج شو
|
|
268
286
|
|
|
269
287
|
async def get_updates(self, offset_id: Optional[str] = None, limit: Optional[int] = None) -> Dict[str, Any]:
|
|
270
288
|
data = {}
|
|
@@ -293,6 +311,7 @@ class Robot:
|
|
|
293
311
|
|
|
294
312
|
self._processed_message_ids[message_id] = now
|
|
295
313
|
return False
|
|
314
|
+
|
|
296
315
|
|
|
297
316
|
async def run(self):
|
|
298
317
|
"""
|
|
@@ -370,14 +389,40 @@ class Robot:
|
|
|
370
389
|
await self._aiohttp_session.close()
|
|
371
390
|
print("Bot stopped and session closed.")
|
|
372
391
|
|
|
373
|
-
async def send_message(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
+
|
|
379
418
|
return await self._post("sendMessage", payload)
|
|
380
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
|
+
|
|
381
426
|
def _get_client(self) -> Client_get:
|
|
382
427
|
if self.session_name:
|
|
383
428
|
return Client_get(self.session_name, self.auth, self.Key, self.platform)
|
|
@@ -440,7 +485,7 @@ class Robot:
|
|
|
440
485
|
path = temp_file.name
|
|
441
486
|
is_temp_file = True
|
|
442
487
|
|
|
443
|
-
file_size = os.path.getsize(path)
|
|
488
|
+
file_size = os.path.getsize(path)
|
|
444
489
|
|
|
445
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)
|
|
446
491
|
|
|
@@ -464,9 +509,100 @@ class Robot:
|
|
|
464
509
|
json_data = await response.json()
|
|
465
510
|
if is_temp_file:
|
|
466
511
|
os.remove(path)
|
|
467
|
-
|
|
512
|
+
print(json_data)
|
|
468
513
|
return json_data.get('data', {}).get('file_id')
|
|
469
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
|
+
|
|
470
606
|
async def get_upload_url(self, media_type: Literal['File', 'Image', 'Voice', 'Music', 'Gif', 'Video']) -> str:
|
|
471
607
|
allowed = ['File', 'Image', 'Voice', 'Music', 'Gif', 'Video']
|
|
472
608
|
if media_type not in allowed:
|
|
@@ -490,22 +626,25 @@ class Robot:
|
|
|
490
626
|
raise ValueError("Either path or file_id must be provided.")
|
|
491
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)
|
|
492
628
|
|
|
493
|
-
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]:
|
|
494
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)
|
|
495
|
-
|
|
496
|
-
|
|
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]:
|
|
497
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)
|
|
498
637
|
|
|
499
|
-
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]:
|
|
500
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)
|
|
501
640
|
|
|
502
|
-
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]:
|
|
503
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)
|
|
504
643
|
|
|
505
|
-
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]:
|
|
506
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)
|
|
507
646
|
|
|
508
|
-
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]:
|
|
509
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)
|
|
510
649
|
|
|
511
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
|