Rubka 4.5.11__py3-none-any.whl → 4.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rubka/api.py CHANGED
@@ -331,7 +331,7 @@ class Robot:
331
331
  if message_id is not None:
332
332
  message_id = str(message_id)
333
333
 
334
- if message_id and self._is_duplicate(message_id):
334
+ if message_id and self._is_duplicate(received_at_str):
335
335
  continue
336
336
 
337
337
  self._process_update(update)
rubka/asynco.py CHANGED
@@ -90,8 +90,8 @@ async def check_rubka_version():
90
90
  print("Not updating may lead to malfunctions or incompatibility.")
91
91
  print("To see new methods : @rubka_library\n\n")
92
92
 
93
- # To run the check at startup in an async context
94
- # asyncio.run(check_rubka_version())
93
+
94
+
95
95
 
96
96
  def show_last_six_words(text: str) -> str:
97
97
  """Returns the last 6 characters of a stripped string."""
@@ -226,7 +226,39 @@ class Robot:
226
226
  })
227
227
  return func
228
228
  return decorator
229
+ def on_inline_query_prefix(self, prefix: str, button_id: Optional[str] = None):
230
+ if not prefix.startswith('/'):
231
+ prefix = '/' + prefix
232
+
233
+ def decorator(func: Callable[[Any, InlineMessage], None]):
234
+
235
+ async def handler_wrapper(bot_instance, inline_message: InlineMessage):
236
+
237
+ if not inline_message.raw_data or 'text' not in inline_message.raw_data:
238
+ return
229
239
 
240
+ query_text = inline_message.raw_data['text']
241
+
242
+
243
+ if query_text.startswith(prefix):
244
+
245
+
246
+
247
+ try:
248
+ await func(bot_instance, inline_message)
249
+ except Exception as e:
250
+ print(f"Error in inline query prefix handler '{prefix}': {e}")
251
+
252
+
253
+ self._inline_query_handlers.append({
254
+ "func": handler_wrapper,
255
+ "button_id": button_id
256
+
257
+
258
+
259
+ })
260
+ return func
261
+ return decorator
230
262
  async def _process_update(self, update: dict):
231
263
  if update.get("type") == "ReceiveQuery":
232
264
  msg = update.get("inline_message", {})
@@ -249,40 +281,40 @@ class Robot:
249
281
  text=msg.get("text"),
250
282
  raw_data=msg)
251
283
 
252
- # پردازش دکمه‌های شیشه‌ای (بدون تغییر)
284
+
253
285
  if context.aux_data and self._callback_handlers:
254
286
  for handler in self._callback_handlers:
255
287
  if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
256
288
  asyncio.create_task(handler["func"](self, context))
257
289
  return
258
290
 
259
- # پردازش پیام‌های متنی با حلقه روی تمام هندلرها
291
+
260
292
  if self._message_handlers:
261
293
  for handler_info in self._message_handlers:
262
- # بررسی شرط دستورات (commands)
294
+
263
295
  if handler_info["commands"]:
264
296
  if not context.text or not context.text.startswith("/"):
265
- continue # اگر پیام کامند نبود، این هندلر را رد کن
297
+ continue
266
298
  parts = context.text.split()
267
299
  cmd = parts[0][1:]
268
300
  if cmd not in handler_info["commands"]:
269
- continue # اگر کامند مطابقت نداشت، این هندلر را رد کن
301
+ continue
270
302
  context.args = parts[1:]
271
303
 
272
- # بررسی شرط فیلترها (filters)
304
+
273
305
  if handler_info["filters"]:
274
306
  if not handler_info["filters"](context):
275
- continue # اگر فیلتر برقرار نبود، این هندلر را رد کن
307
+ continue
308
+
276
309
 
277
- # اگر هندلری برای همه پیام‌ها باشد (بدون کامند و فیلتر)
278
310
  if not handler_info["commands"] and not handler_info["filters"]:
279
311
  asyncio.create_task(handler_info["func"](self, context))
280
- return # بعد از یافتن هندلر مناسب، از حلقه خارج شو
312
+ return
313
+
281
314
 
282
- # اگر شرایط کامند یا فیلتر برقرار بود
283
315
  if handler_info["commands"] or handler_info["filters"]:
284
316
  asyncio.create_task(handler_info["func"](self, context))
285
- return # بعد از یافتن هندلر مناسب، از حلقه خارج شو
317
+ return
286
318
 
287
319
  async def get_updates(self, offset_id: Optional[str] = None, limit: Optional[int] = None) -> Dict[str, Any]:
288
320
  data = {}
@@ -297,7 +329,7 @@ class Robot:
297
329
  if limit: params['limit'] = limit
298
330
  async with session.get(self.web_hook, params=params) as response:
299
331
  response.raise_for_status()
300
- # وب‌هوک باید لیستی از رویدادها را برگرداند
332
+
301
333
  return await response.json()
302
334
 
303
335
  def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
@@ -326,8 +358,8 @@ class Robot:
326
358
  while True:
327
359
  try:
328
360
  if self.web_hook:
329
- # ----- منطق وب‌هوک (اصلاح شده) -----
330
- # آپدیت‌ها مستقیما از وب‌هوک گرفته و پردازش می‌شوند
361
+
362
+
331
363
  webhook_data = await self.update_webhook()
332
364
  if isinstance(webhook_data, list):
333
365
  for item in webhook_data:
@@ -340,7 +372,7 @@ class Robot:
340
372
  if time.time() - received_at_ts > 20:
341
373
  continue
342
374
  except (ValueError, TypeError):
343
- pass # رد شدن در صورت فرمت اشتباه زمان
375
+ pass
344
376
 
345
377
  update = None
346
378
  if "update" in data:
@@ -358,11 +390,11 @@ class Robot:
358
390
  elif "message_id" in update:
359
391
  message_id = update.get("message_id")
360
392
 
361
- if message_id and not self._is_duplicate(str(message_id)):
393
+ if message_id and not self._is_duplicate(str(received_at_str)):
362
394
  await self._process_update(update)
363
395
 
364
396
  else:
365
- # ----- منطق Polling (بدون تغییر) -----
397
+
366
398
  get_updates_response = await self.get_updates(offset_id=self._offset_id, limit=100)
367
399
  if get_updates_response and get_updates_response.get("data"):
368
400
  updates = get_updates_response["data"].get("updates", [])
@@ -380,10 +412,10 @@ class Robot:
380
412
  if message_id and not self._is_duplicate(str(message_id)):
381
413
  await self._process_update(update)
382
414
 
383
- await asyncio.sleep(0.1) # وقفه کوتاه برای جلوگیری از مصرف CPU
415
+ await asyncio.sleep(0)
384
416
  except Exception as e:
385
417
  print(f"❌ Error in run loop: {e}")
386
- await asyncio.sleep(5) # وقفه طولانی‌تر در صورت بروز خطا
418
+ await asyncio.sleep(5)
387
419
  finally:
388
420
  if self._aiohttp_session:
389
421
  await self._aiohttp_session.close()
@@ -438,7 +470,7 @@ class Robot:
438
470
  username = chat_info.get('username')
439
471
  user_id = chat_info.get('user_id')
440
472
 
441
- # Since client methods are sync, run them in a thread pool
473
+
442
474
  if username:
443
475
  result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
444
476
  members = result.get('in_chat_members', [])
@@ -449,7 +481,7 @@ class Robot:
449
481
  return False
450
482
 
451
483
  def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
452
- # This is a sync method that will be called with asyncio.to_thread
484
+
453
485
  client = self._get_client()
454
486
  return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
455
487
 
@@ -639,7 +671,7 @@ class Robot:
639
671
  return await self._send_file_generic("Video", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
640
672
 
641
673
  async def send_voice(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
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)
674
+ return await self._send_file_generic("Video", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
643
675
 
644
676
  async def send_image(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
645
677
  return await self._send_file_generic("Image", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
rubka/context.py CHANGED
@@ -14,9 +14,9 @@ class Sticker:
14
14
  self.file = File(data.get("file", {}))
15
15
 
16
16
 
17
- # =========================
18
- # Poll
19
- # =========================
17
+
18
+
19
+
20
20
  class PollStatus:
21
21
  def __init__(self, data: dict):
22
22
  self.state: str = data.get("state")
@@ -33,9 +33,9 @@ class Poll:
33
33
  self.poll_status = PollStatus(data.get("poll_status", {}))
34
34
 
35
35
 
36
- # =========================
37
- # Location & Contact & ForwardedFrom
38
- # =========================
36
+
37
+
38
+
39
39
  class Location:
40
40
  def __init__(self, data: dict):
41
41
  self.latitude: str = data.get("latitude")
@@ -67,18 +67,18 @@ class ForwardedFrom:
67
67
  self.from_sender_id: str = data.get("from_sender_id")
68
68
 
69
69
 
70
- # =========================
71
- # AuxData
72
- # =========================
70
+
71
+
72
+
73
73
  class AuxData:
74
74
  def __init__(self, data: dict):
75
75
  self.start_id: str = data.get("start_id")
76
76
  self.button_id: str = data.get("button_id")
77
77
 
78
78
 
79
- # =========================
80
- # Button Models
81
- # =========================
79
+
80
+
81
+
82
82
  class ButtonTextbox:
83
83
  def __init__(self, data: dict):
84
84
  self.type_line: str = data.get("type_line")
@@ -221,6 +221,13 @@ class Message:
221
221
  reply_to_message_id=self.message_id,
222
222
  **kwargs
223
223
  )
224
+ def answer(self, text: str, **kwargs):
225
+ return self.bot.send_message(
226
+ self.chat_id,
227
+ text,
228
+ reply_to_message_id=self.message_id,
229
+ **kwargs
230
+ )
224
231
 
225
232
  def reply_poll(self, question: str, options: List[str], **kwargs) -> Dict[str, Any]:
226
233
  return self.bot._post("sendPoll", {
@@ -437,12 +444,25 @@ class AuxData:
437
444
  self.start_id = data.get("start_id")
438
445
  self.button_id = data.get("button_id")
439
446
 
440
- # نمونه کلاس InlineMessage با ویژگی aux_data که شامل دکمه است
447
+
441
448
  class InlineMessage:
442
449
  def __init__(self, bot, raw_data: dict):
443
450
  self.bot = bot
444
451
  self.raw_data = raw_data
445
-
452
+ self.chat_id = self.raw_data.get("chat_id")
453
+ self.time: str = self.raw_data.get("time")
454
+ self.is_edited: bool = self.raw_data.get("is_edited", False)
455
+ self.sender_type: str = self.raw_data.get("sender_type")
456
+ self.args = []
457
+ self.reply_to_message_id: Optional[str] = self.raw_data.get("reply_to_message_id")
458
+ self.forwarded_from = ForwardedFrom(self.raw_data["forwarded_from"]) if "forwarded_from" in self.raw_data else None
459
+ self.file = File(self.raw_data["file"]) if "file" in self.raw_data else None
460
+ self.sticker = Sticker(self.raw_data["sticker"]) if "sticker" in self.raw_data else None
461
+ self.contact_message = ContactMessage(self.raw_data["contact_message"]) if "contact_message" in self.raw_data else None
462
+ self.poll = Poll(self.raw_data["poll"]) if "poll" in self.raw_data else None
463
+ self.location = Location(self.raw_data["location"]) if "location" in self.raw_data else None
464
+ self.live_location = LiveLocation(self.raw_data["live_location"]) if "live_location" in self.raw_data else None
465
+ self.aux_data = AuxData(self.raw_data["aux_data"]) if "aux_data" in self.raw_data else None
446
466
  self.chat_id: str = raw_data.get("chat_id")
447
467
  self.message_id: str = raw_data.get("message_id")
448
468
  self.sender_id: str = raw_data.get("sender_id")
@@ -456,6 +476,13 @@ class InlineMessage:
456
476
  reply_to_message_id=self.message_id,
457
477
  **kwargs
458
478
  )
479
+ def answer(self, text: str, **kwargs):
480
+ return self.bot.send_message(
481
+ self.chat_id,
482
+ text,
483
+ reply_to_message_id=self.message_id,
484
+ **kwargs
485
+ )
459
486
 
460
487
  def edit(self, new_text: str):
461
488
  return self.bot.edit_message_text(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 4.5.11
3
+ Version: 4.6.0
4
4
  Summary: A Python library for interacting with Rubika Bot API.
5
5
  Home-page: https://github.com/Mahdy-Ahmadi/Rubka
6
6
  Download-URL: https://github.com/Mahdy-Ahmadi/rubka/blob/main/project_library.zip
@@ -19,6 +19,11 @@ Requires-Dist: requests
19
19
  Requires-Dist: Pillow
20
20
  Requires-Dist: websocket-client
21
21
  Requires-Dist: pycryptodome
22
+ Requires-Dist: aiohttp
23
+ Requires-Dist: tqdm
24
+ Requires-Dist: mutagen
25
+ Requires-Dist: filetype
26
+ Requires-Dist: aiofiles
22
27
  Dynamic: author
23
28
  Dynamic: author-email
24
29
  Dynamic: classifier
@@ -1,9 +1,9 @@
1
1
  rubka/__init__.py,sha256=TR1DABU5Maz2eO62ZEFiwOqNU0dH6l6HZfqRUxeo4eY,194
2
- rubka/api.py,sha256=0gj52uxIGmLCVRUxnvjIHF6MVBiXnVBdVr_SRXp2I4M,39416
3
- rubka/asynco.py,sha256=DDAeES9qDQ9vegHT-7X7RL72G510YObFN7qqJ948NEQ,37647
2
+ rubka/api.py,sha256=-ayu17rFI35hU1O7ePxLQNTK-upj2uJxOflA0JUdWdI,39421
3
+ rubka/asynco.py,sha256=f2OH2JDdB98x7T4c6B1n-dTWpCe8R5KkkHUdwr9IP9g,37433
4
4
  rubka/button.py,sha256=4fMSZR7vUADxSmw1R3_pZ4dw5uMLZX5sOkwPPyNTBDE,8437
5
5
  rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
6
- rubka/context.py,sha256=KXfDqn3vir6oIupF7piKD89Sqj1MjWoSerS4WyyHXBw,18062
6
+ rubka/context.py,sha256=jtdMYf2slFxK2kmkVO6MMSTl-yu5NH52UvzAOyB79hw,19317
7
7
  rubka/decorators.py,sha256=hGwUoE4q2ImrunJIGJ_kzGYYxQf1ueE0isadqraKEts,1157
8
8
  rubka/exceptions.py,sha256=tujZt1XrhWaw-lmdeVadVceUptpw4XzNgE44sAAY0gs,90
9
9
  rubka/jobs.py,sha256=GvLMLsVhcSEzRTgkvnPISPEBN71suW2xXI0hUaUZPTo,378
@@ -33,7 +33,7 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
33
33
  rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
34
34
  rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
35
35
  rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
36
- rubka-4.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,,
36
+ rubka-4.6.0.dist-info/METADATA,sha256=ZSH799BnofV3TFaXKdi_unkoru9Yki1Sbv3L1upObMI,33335
37
+ rubka-4.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ rubka-4.6.0.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
39
+ rubka-4.6.0.dist-info/RECORD,,
File without changes