Rubka 4.4.4__py3-none-any.whl → 4.4.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rubka/__init__.py CHANGED
@@ -1,11 +1,10 @@
1
1
  from .api import Robot
2
- from .decorators import on_message
2
+ from .rubino import Bot
3
3
  from .exceptions import APIRequestError
4
- from .keyboards import create_simple_keyboard
5
4
 
6
5
  __all__ = [
7
6
  "Robot",
8
7
  "on_message",
9
8
  "APIRequestError",
10
9
  "create_simple_keyboard",
11
- ]
10
+ ]
@@ -1,4 +1,19 @@
1
+ import sys
2
+ import subprocess
3
+
4
+ def install_and_import(package_name):
5
+ try:
6
+ __import__(package_name)
7
+ except ModuleNotFoundError:
8
+ print(f"Module '{package_name}' not found. Installing...")
9
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
10
+ finally:
11
+ globals()[package_name] = __import__(package_name)
12
+
13
+ install_and_import("websocket")
14
+
1
15
  from websocket import WebSocketApp
16
+
2
17
  from .helper import Helper
3
18
  from json import dumps, loads
4
19
  from threading import Thread
@@ -13,4 +28,4 @@ class Socket:
13
28
  self.methods = methods
14
29
  self.handlers = {}
15
30
 
16
- ...
31
+ ...
rubka/api.py CHANGED
@@ -5,6 +5,10 @@ from .adaptorrubka import Client as Client_get
5
5
  from .logger import logger
6
6
  from typing import Callable
7
7
  from .context import Message,InlineMessage
8
+ from typing import Optional, Union, Literal, Dict, Any
9
+ from pathlib import Path
10
+ import requests
11
+
8
12
  API_URL = "https://botapi.rubika.ir/v3"
9
13
  import sys
10
14
  import subprocess
@@ -50,23 +54,55 @@ def check_rubka_version():
50
54
  latest_version = get_latest_version(package_name)
51
55
  if latest_version is None:return
52
56
  if installed_version != latest_version:
53
- print(f"\n\n⚠️ WARNING: Your installed version of '{package_name}' is outdated!")
54
- print(f"Installed version: {installed_version}")
55
- print(f"Latest version: {latest_version}")
56
- print(f"Please update it using:\n\npip install --upgrade {package_name}\n")
57
+ print(f"\n\nWARNING: Your installed version of '{package_name}' is OUTDATED and may cause errors or security risks!")
58
+ print(f"Installed version : {installed_version}")
59
+ print(f"Latest available version : {latest_version}")
60
+ print(f"Please update IMMEDIATELY by running:")
61
+ print(f"\npip install {package_name}=={latest_version}\n")
62
+ print(f"Not updating may lead to malfunctions or incompatibility.")
63
+ print(f"To see new methods : @rubka_library\n\n")
57
64
 
58
65
  check_rubka_version()
59
66
  def show_last_six_words(text):
60
67
  text = text.strip()
61
68
  return text[-6:]
69
+ import requests
70
+ from pathlib import Path
71
+ from typing import Union, Optional, Dict, Any, Literal
72
+ import tempfile
73
+ import os
62
74
  class Robot:
63
75
  """
64
76
  Main class to interact with Rubika Bot API.
65
77
  Initialized with bot token.
66
78
  """
67
79
 
68
- def __init__(self, token: str):
80
+ def __init__(self, token: str,session_name : str = None,auth : str = None , Key : str = None,platform : str ="web",timeout : int =10):
81
+ """
82
+ راه‌اندازی اولیه ربات روبیکا و پیکربندی پارامترهای پایه.
83
+
84
+ Parameters:
85
+ token (str): توکن اصلی ربات برای احراز هویت با Rubika Bot API. این مقدار الزامی است.
86
+ session_name (str, optional): نام نشست دلخواه برای شناسایی یا جداسازی کلاینت‌ها. پیش‌فرض None.
87
+ auth (str, optional): مقدار احراز هویت اضافی برای اتصال به کلاینت سفارشی. پیش‌فرض None.
88
+ Key (str, optional): کلید اضافی برای رمزنگاری یا تأیید. در صورت نیاز استفاده می‌شود. پیش‌فرض None.
89
+ platform (str, optional): پلتفرم اجرایی مورد نظر (مثل "web" یا "android"). پیش‌فرض "web".
90
+ timeout (int, optional): مدت‌زمان تایم‌اوت برای درخواست‌های HTTP بر حسب ثانیه. پیش‌فرض 10 ثانیه.
91
+
92
+ توضیحات:
93
+ - این تابع یک شیء Session از requests می‌سازد برای استفاده در تمام درخواست‌ها.
94
+ - هندلرهای مختلف مانند پیام، دکمه، کوئری اینلاین و ... در این مرحله مقداردهی اولیه می‌شوند.
95
+ - لیست `self._callback_handlers` برای مدیریت چندین callback مختلف الزامی است.
96
+
97
+ مثال:
98
+ >>> bot = Robot(token="BOT_TOKEN", platform="android", timeout=15)
99
+ """
69
100
  self.token = token
101
+ self.timeout = timeout
102
+ self.auth = auth
103
+ self.session_name = session_name
104
+ self.Key = Key
105
+ self.platform = platform
70
106
  self._offset_id = None
71
107
  self.session = requests.Session()
72
108
  self.sessions: Dict[str, Dict[str, Any]] = {}
@@ -82,7 +118,7 @@ class Robot:
82
118
  def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
83
119
  url = f"{API_URL}/{self.token}/{method}"
84
120
  try:
85
- response = self.session.post(url, json=data, timeout=10)
121
+ response = self.session.post(url, json=data, timeout=self.timeout)
86
122
  response.raise_for_status()
87
123
  try:
88
124
  json_resp = response.json()
@@ -236,8 +272,10 @@ class Robot:
236
272
  return self._post("sendMessage", payload)
237
273
 
238
274
  def _get_client(self):
239
- return Client_get(show_last_six_words(self.token))
240
-
275
+ if self.session_name:
276
+ return Client_get(self.session_name,self.auth,self.Key,self.platform)
277
+ else :
278
+ return Client_get(show_last_six_words(self.token),self.auth,self.Key,self.platform)
241
279
  from typing import Union
242
280
 
243
281
  def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
@@ -332,6 +370,211 @@ class Robot:
332
370
  def get_chat(self, chat_id: str) -> Dict[str, Any]:
333
371
  """Get chat info."""
334
372
  return self._post("getChat", {"chat_id": chat_id})
373
+ def upload_media_file(self, upload_url: str, name: str, path: Union[str, Path]) -> str:
374
+ # بررسی اینکه ورودی URL است یا مسیر محلی
375
+ is_temp_file = False
376
+ if isinstance(path, str) and path.startswith("http"):
377
+ response = requests.get(path)
378
+ if response.status_code != 200:
379
+ raise Exception(f"Failed to download file from URL ({response.status_code})")
380
+ temp_file = tempfile.NamedTemporaryFile(delete=False)
381
+ temp_file.write(response.content)
382
+ temp_file.close()
383
+ path = temp_file.name
384
+ is_temp_file = True
385
+
386
+ with open(path, 'rb') as file:
387
+ files = {
388
+ 'file': (name, file, 'application/octet-stream')
389
+ }
390
+ response = requests.post(upload_url, files=files)
391
+ if response.status_code != 200:
392
+ raise Exception(f"Upload failed ({response.status_code}): {response.text}")
393
+ data = response.json()
394
+
395
+ if is_temp_file:
396
+ os.unlink(path) # حذف فایل موقت
397
+
398
+ return data.get('data', {}).get('file_id')
399
+
400
+ def get_upload_url(self, media_type: Literal['File', 'Image', 'Voice', 'Music', 'Gif']) -> str:
401
+ allowed = ['File', 'Image', 'Voice', 'Music', 'Gif']
402
+ if media_type not in allowed:
403
+ raise ValueError(f"Invalid media type. Must be one of {allowed}")
404
+ result = self._post("requestSendFile", {"type": media_type})
405
+ return result.get("data", {}).get("upload_url")
406
+ def _send_uploaded_file(
407
+ self,
408
+ chat_id: str,
409
+ file_id: str,
410
+ text: Optional[str] = None,
411
+ chat_keypad: Optional[Dict[str, Any]] = None,
412
+ inline_keypad: Optional[Dict[str, Any]] = None,
413
+ disable_notification: bool = False,
414
+ reply_to_message_id: Optional[str] = None,
415
+ chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None"
416
+ ) -> Dict[str, Any]:
417
+ payload = {
418
+ "chat_id": chat_id,
419
+ "file_id": file_id,
420
+ "text": text,
421
+ "disable_notification": disable_notification,
422
+ "chat_keypad_type": chat_keypad_type,
423
+ }
424
+ if chat_keypad:
425
+ payload["chat_keypad"] = chat_keypad
426
+ if inline_keypad:
427
+ payload["inline_keypad"] = inline_keypad
428
+ if reply_to_message_id:
429
+ payload["reply_to_message_id"] = str(reply_to_message_id)
430
+
431
+ return self._post("sendFile", payload)
432
+
433
+ def send_document(
434
+ self,
435
+ chat_id: str,
436
+ path: Optional[Union[str, Path]] = None,
437
+ file_id: Optional[str] = None,
438
+ text: Optional[str] = None,
439
+ file_name: Optional[str] = None,
440
+ inline_keypad: Optional[Dict[str, Any]] = None,
441
+ chat_keypad: Optional[Dict[str, Any]] = None,
442
+ reply_to_message_id: Optional[str] = None,
443
+ disable_notification: bool = False,
444
+ chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "New"
445
+ ) -> Dict[str, Any]:
446
+ if path:
447
+ file_name = file_name or Path(path).name
448
+ upload_url = self.get_upload_url("File")
449
+ file_id = self.upload_media_file(upload_url, file_name, path)
450
+ if not file_id:
451
+ raise ValueError("Either path or file_id must be provided.")
452
+ return self._send_uploaded_file(
453
+ chat_id=chat_id,
454
+ file_id=file_id,
455
+ text=text,
456
+ inline_keypad=inline_keypad,
457
+ chat_keypad=chat_keypad,
458
+ reply_to_message_id=reply_to_message_id,
459
+ disable_notification=disable_notification,
460
+ chat_keypad_type=chat_keypad_type
461
+ )
462
+ def send_music(
463
+ self,
464
+ chat_id: str,
465
+ path: Optional[Union[str, Path]] = None,
466
+ file_id: Optional[str] = None,
467
+ text: Optional[str] = None,
468
+ file_name: Optional[str] = None,
469
+ inline_keypad: Optional[Dict[str, Any]] = None,
470
+ chat_keypad: Optional[Dict[str, Any]] = None,
471
+ reply_to_message_id: Optional[str] = None,
472
+ disable_notification: bool = False,
473
+ chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "New"
474
+ ) -> Dict[str, Any]:
475
+ if path:
476
+ file_name = file_name or Path(path).name
477
+ upload_url = self.get_upload_url("Music")
478
+ file_id = self.upload_media_file(upload_url, file_name, path)
479
+ if not file_id:
480
+ raise ValueError("Either path or file_id must be provided.")
481
+ return self._send_uploaded_file(
482
+ chat_id=chat_id,
483
+ file_id=file_id,
484
+ text=text,
485
+ inline_keypad=inline_keypad,
486
+ chat_keypad=chat_keypad,
487
+ reply_to_message_id=reply_to_message_id,
488
+ disable_notification=disable_notification,
489
+ chat_keypad_type=chat_keypad_type
490
+ )
491
+ def send_voice(
492
+ self,
493
+ chat_id: str,
494
+ path: Optional[Union[str, Path]] = None,
495
+ file_id: Optional[str] = None,
496
+ text: Optional[str] = None,
497
+ file_name: Optional[str] = None,
498
+ inline_keypad: Optional[Dict[str, Any]] = None,
499
+ chat_keypad: Optional[Dict[str, Any]] = None,
500
+ reply_to_message_id: Optional[str] = None,
501
+ disable_notification: bool = False,
502
+ chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "New"
503
+ ) -> Dict[str, Any]:
504
+ if path:
505
+ file_name = file_name or Path(path).name
506
+ upload_url = self.get_upload_url("Voice")
507
+ file_id = self.upload_media_file(upload_url, file_name, path)
508
+ if not file_id:
509
+ raise ValueError("Either path or file_id must be provided.")
510
+ return self._send_uploaded_file(
511
+ chat_id=chat_id,
512
+ file_id=file_id,
513
+ text=text,
514
+ inline_keypad=inline_keypad,
515
+ chat_keypad=chat_keypad,
516
+ reply_to_message_id=reply_to_message_id,
517
+ disable_notification=disable_notification,
518
+ chat_keypad_type=chat_keypad_type
519
+ )
520
+ def send_image(
521
+ self,
522
+ chat_id: str,
523
+ path: Optional[Union[str, Path]] = None,
524
+ file_id: Optional[str] = None,
525
+ text: Optional[str] = None,
526
+ file_name: Optional[str] = None,
527
+ inline_keypad: Optional[Dict[str, Any]] = None,
528
+ chat_keypad: Optional[Dict[str, Any]] = None,
529
+ reply_to_message_id: Optional[str] = None,
530
+ disable_notification: bool = False,
531
+ chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "New"
532
+ ) -> Dict[str, Any]:
533
+ if path:
534
+ file_name = file_name or Path(path).name
535
+ upload_url = self.get_upload_url("Image")
536
+ file_id = self.upload_media_file(upload_url, file_name, path)
537
+ if not file_id:
538
+ raise ValueError("Either path or file_id must be provided.")
539
+ return self._send_uploaded_file(
540
+ chat_id=chat_id,
541
+ file_id=file_id,
542
+ text=text,
543
+ inline_keypad=inline_keypad,
544
+ chat_keypad=chat_keypad,
545
+ reply_to_message_id=reply_to_message_id,
546
+ disable_notification=disable_notification,
547
+ chat_keypad_type=chat_keypad_type
548
+ )
549
+ def send_gif(
550
+ self,
551
+ chat_id: str,
552
+ path: Optional[Union[str, Path]] = None,
553
+ file_id: Optional[str] = None,
554
+ text: Optional[str] = None,
555
+ file_name: Optional[str] = None,
556
+ inline_keypad: Optional[Dict[str, Any]] = None,
557
+ chat_keypad: Optional[Dict[str, Any]] = None,
558
+ reply_to_message_id: Optional[str] = None,
559
+ disable_notification: bool = False,
560
+ chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "New"
561
+ ) -> Dict[str, Any]:
562
+ if path:
563
+ file_name = file_name or Path(path).name
564
+ upload_url = self.get_upload_url("Gif")
565
+ file_id = self.upload_media_file(upload_url, file_name, path)
566
+ if not file_id:
567
+ raise ValueError("Either path or file_id must be provided.")
568
+ return self._send_uploaded_file(
569
+ chat_id=chat_id,
570
+ file_id=file_id,
571
+ text=text,
572
+ inline_keypad=inline_keypad,
573
+ chat_keypad=chat_keypad,
574
+ reply_to_message_id=reply_to_message_id,
575
+ disable_notification=disable_notification,
576
+ chat_keypad_type=chat_keypad_type
577
+ )
335
578
 
336
579
  def get_updates(
337
580
  self,
@@ -381,7 +624,7 @@ class Robot:
381
624
  inline_keypad: Dict[str, Any]
382
625
  ) -> Dict[str, Any]:
383
626
  """Edit inline keypad of a message."""
384
- return self._post("editInlineKeypad", {
627
+ return self._post("editMessageKeypad", {
385
628
  "chat_id": chat_id,
386
629
  "message_id": message_id,
387
630
  "inline_keypad": inline_keypad
rubka/context.py CHANGED
@@ -184,6 +184,9 @@ class Bot:
184
184
  self.username: str = data.get("username")
185
185
  self.start_message: str = data.get("start_message")
186
186
  self.share_url: str = data.get("share_url")
187
+ from typing import Union
188
+ from pathlib import Path
189
+
187
190
  class Message:
188
191
  def __init__(self, bot, chat_id, message_id, sender_id, text=None, raw_data=None):
189
192
  self.bot = bot
@@ -227,6 +230,117 @@ class Message:
227
230
  "reply_to_message_id": self.message_id,
228
231
  **kwargs
229
232
  })
233
+
234
+
235
+ def reply_document(
236
+ self,
237
+ path: Optional[Union[str, Path]] = None,
238
+ file_id: Optional[str] = None,
239
+ text: Optional[str] = None,
240
+ chat_keypad: Optional[Dict[str, Any]] = None,
241
+ inline_keypad: Optional[Dict[str, Any]] = None,
242
+ chat_keypad_type: Optional[str] = "None",
243
+ disable_notification: bool = False
244
+ ):
245
+ return self.bot.send_document(
246
+ chat_id=self.chat_id,
247
+ path=path,
248
+ file_id=file_id,
249
+ text=text,
250
+ chat_keypad=chat_keypad,
251
+ inline_keypad=inline_keypad,
252
+ chat_keypad_type=chat_keypad_type,
253
+ disable_notification=disable_notification,
254
+ reply_to_message_id=self.message_id
255
+ )
256
+
257
+ def reply_image(
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
+ return self.bot.send_image(
268
+ chat_id=self.chat_id,
269
+ path=path,
270
+ file_id=file_id,
271
+ text=text,
272
+ chat_keypad=chat_keypad,
273
+ inline_keypad=inline_keypad,
274
+ chat_keypad_type=chat_keypad_type,
275
+ disable_notification=disable_notification,
276
+ reply_to_message_id=self.message_id
277
+ )
278
+
279
+ def reply_music(
280
+ self,
281
+ path: Optional[Union[str, Path]] = None,
282
+ file_id: Optional[str] = None,
283
+ text: Optional[str] = None,
284
+ chat_keypad: Optional[Dict[str, Any]] = None,
285
+ inline_keypad: Optional[Dict[str, Any]] = None,
286
+ chat_keypad_type: Optional[str] = "None",
287
+ disable_notification: bool = False
288
+ ):
289
+ return self.bot.send_music(
290
+ chat_id=self.chat_id,
291
+ path=path,
292
+ file_id=file_id,
293
+ text=text,
294
+ chat_keypad=chat_keypad,
295
+ inline_keypad=inline_keypad,
296
+ chat_keypad_type=chat_keypad_type,
297
+ disable_notification=disable_notification,
298
+ reply_to_message_id=self.message_id
299
+ )
300
+
301
+ def reply_voice(
302
+ self,
303
+ path: Optional[Union[str, Path]] = None,
304
+ file_id: Optional[str] = None,
305
+ text: Optional[str] = None,
306
+ chat_keypad: Optional[Dict[str, Any]] = None,
307
+ inline_keypad: Optional[Dict[str, Any]] = None,
308
+ chat_keypad_type: Optional[str] = "None",
309
+ disable_notification: bool = False
310
+ ):
311
+ return self.bot.send_voice(
312
+ chat_id=self.chat_id,
313
+ path=path,
314
+ file_id=file_id,
315
+ text=text,
316
+ chat_keypad=chat_keypad,
317
+ inline_keypad=inline_keypad,
318
+ chat_keypad_type=chat_keypad_type,
319
+ disable_notification=disable_notification,
320
+ reply_to_message_id=self.message_id
321
+ )
322
+
323
+ def reply_gif(
324
+ self,
325
+ path: Optional[Union[str, Path]] = None,
326
+ file_id: Optional[str] = None,
327
+ text: Optional[str] = None,
328
+ chat_keypad: Optional[Dict[str, Any]] = None,
329
+ inline_keypad: Optional[Dict[str, Any]] = None,
330
+ chat_keypad_type: Optional[str] = "None",
331
+ disable_notification: bool = False
332
+ ):
333
+ return self.bot.send_gif(
334
+ chat_id=self.chat_id,
335
+ path=path,
336
+ file_id=file_id,
337
+ text=text,
338
+ chat_keypad=chat_keypad,
339
+ inline_keypad=inline_keypad,
340
+ chat_keypad_type=chat_keypad_type,
341
+ disable_notification=disable_notification,
342
+ reply_to_message_id=self.message_id
343
+ )
230
344
 
231
345
  def reply_location(self, latitude: str, longitude: str, **kwargs) -> Dict[str, Any]:
232
346
  return self.bot.send_location(