Rubka 6.7.11__py3-none-any.whl → 7.1.1__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.

Potentially problematic release.


This version of Rubka might be problematic. Click here for more details.

rubka/api.py CHANGED
@@ -1016,7 +1016,8 @@ class Robot:
1016
1016
  inline_keypad: Optional[Dict[str, Any]] = None,
1017
1017
  disable_notification: bool = False,
1018
1018
  reply_to_message_id: Optional[str] = None,
1019
- chat_keypad_type: Optional[Literal["New", "Removed"]] = None
1019
+ chat_keypad_type: Optional[Literal["New", "Removed"]] = None,
1020
+ delete_after = None
1020
1021
  ) -> Dict[str, Any]:
1021
1022
  """
1022
1023
  Send a text message to a chat.
rubka/asynco.py CHANGED
@@ -1,4 +1,4 @@
1
- import asyncio,aiohttp,aiofiles,time,datetime,json,tempfile,os,sys,subprocess,mimetypes,time, hashlib
1
+ import asyncio,aiohttp,aiofiles,time,datetime,json,tempfile,os,sys,subprocess,mimetypes,time, hashlib,sqlite3
2
2
  from typing import List, Optional, Dict, Any, Literal, Callable, Union,Set
3
3
  from collections import OrderedDict
4
4
  from .exceptions import APIRequestError,raise_for_status,InvalidAccessError,InvalidInputError,TooRequestError,InvalidTokenError
@@ -19,7 +19,6 @@ from tqdm import tqdm
19
19
  API_URL = "https://botapi.rubika.ir/v3"
20
20
 
21
21
  def install_package(package_name: str) -> bool:
22
- """Installs a package using pip."""
23
22
  try:
24
23
  subprocess.check_call([sys.executable, "-m", "pip", "install", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
25
24
  return True
@@ -27,7 +26,6 @@ def install_package(package_name: str) -> bool:
27
26
  return False
28
27
 
29
28
  def get_importlib_metadata():
30
- """Dynamically imports and returns metadata functions from importlib."""
31
29
  try:
32
30
  from importlib.metadata import version, PackageNotFoundError
33
31
  return version, PackageNotFoundError
@@ -43,7 +41,6 @@ def get_importlib_metadata():
43
41
  version, PackageNotFoundError = get_importlib_metadata()
44
42
 
45
43
  def get_installed_version(package_name: str) -> Optional[str]:
46
- """Gets the installed version of a package."""
47
44
  if version is None:
48
45
  return "unknown"
49
46
  try:
@@ -52,7 +49,6 @@ def get_installed_version(package_name: str) -> Optional[str]:
52
49
  return None
53
50
 
54
51
  async def get_latest_version(package_name: str) -> Optional[str]:
55
- """Fetches the latest version of a package from PyPI asynchronously."""
56
52
  url = f"https://pypi.org/pypi/{package_name}/json"
57
53
  try:
58
54
  async with aiohttp.ClientSession() as session:
@@ -64,7 +60,6 @@ async def get_latest_version(package_name: str) -> Optional[str]:
64
60
  return None
65
61
 
66
62
  async def check_rubka_version():
67
- """Checks for outdated 'rubka' package and warns the user."""
68
63
  package_name = "rubka"
69
64
  installed_version = get_installed_version(package_name)
70
65
  if installed_version is None:
@@ -87,7 +82,6 @@ async def check_rubka_version():
87
82
 
88
83
 
89
84
  def show_last_six_words(text: str) -> str:
90
- """Returns the last 6 characters of a stripped string."""
91
85
  text = text.strip()
92
86
  return text[-6:]
93
87
  class AttrDict(dict):
@@ -145,7 +139,7 @@ safeSendMode ensures reliable message sending even if replying by message_id fai
145
139
  max_cache_size and max_msg_age help manage duplicate message processing efficiently.
146
140
  """
147
141
 
148
- def __init__(self, token: str, session_name: str = None, auth: str = None, Key: str = None, platform: str = "web", web_hook: str = None, timeout: int = 10, show_progress: bool = False, raise_errors: bool = True,proxy: str = None,retries: int = 2,retry_delay: float = 0.5,user_agent: str = None,safeSendMode = False,max_cache_size: int = 1000,max_msg_age : int = 60):
142
+ def __init__(self, token: str, session_name: str = None, auth: str = None, Key: str = None, platform: str = "web", web_hook: str = None, timeout: int = 10, show_progress: bool = False, raise_errors: bool = True,proxy: str = None,retries: int = 2,retry_delay: float = 0.5,user_agent: str = None,safeSendMode = False,max_cache_size: int = 2000,max_msg_age : int = 60):
149
143
  self.token = token
150
144
  self._inline_query_handlers: List[dict] = []
151
145
  self.timeout = timeout
@@ -170,18 +164,23 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
170
164
  self._max_cache_size = max_cache_size
171
165
  self._callback_handlers: List[dict] = []
172
166
  self._edited_message_handlers = []
167
+ self._message_saver_enabled = False
168
+ self._max_messages = None
169
+ self._db_path = os.path.join(os.getcwd(), "RubkaSaveMessage.db")
170
+ self._ensure_db()
173
171
  self._message_handlers: List[dict] = []
174
172
 
175
173
  logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
176
174
  async def _get_session(self) -> aiohttp.ClientSession:
177
175
  if self._aiohttp_session is None or self._aiohttp_session.closed:
178
- connector = aiohttp.TCPConnector(limit=100, ssl=False) # limit اتصالات همزمان
176
+ connector = aiohttp.TCPConnector(limit=100, ssl=False)
179
177
  timeout = aiohttp.ClientTimeout(total=self.timeout)
180
178
  self._aiohttp_session = aiohttp.ClientSession(connector=connector, timeout=timeout)
181
179
  return self._aiohttp_session
182
180
  async def close(self):
183
- if self._session and not self._session.closed:
184
- await self._session.close()
181
+ if self._aiohttp_session and not self._aiohttp_session.closed:
182
+ await self._aiohttp_session.close()
183
+ logger.debug("aiohttp session closed successfully.")
185
184
 
186
185
  async def _initialize_webhook(self):
187
186
  """Initializes and sets the webhook endpoint if provided."""
@@ -250,15 +249,157 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
250
249
  raw = f"{message_id}:{update_type}:{msg_data.get('text','')}:{msg_data.get('author_guid','')}"
251
250
  return hashlib.sha1(raw.encode()).hexdigest()
252
251
  async def get_me(self) -> Dict[str, Any]:
253
- """Get info about the bot itself."""
254
252
  return await self._post("getMe", {})
255
253
  async def geteToken(self):
256
- """Check if the bot token is valid."""
257
254
  if (await self.get_me())['status'] != "OK":
258
255
  raise InvalidTokenError("The provided bot token is invalid or expired.")
259
256
  from typing import Callable, Any, Optional, List
260
257
 
261
258
 
259
+ #save message database __________________________
260
+
261
+ def _ensure_db(self):
262
+ conn = sqlite3.connect(self._db_path)
263
+ cur = conn.cursor()
264
+ cur.execute("""
265
+ CREATE TABLE IF NOT EXISTS messages (
266
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
267
+ chat_id TEXT NOT NULL,
268
+ message_id TEXT NOT NULL,
269
+ sender_id TEXT,
270
+ text TEXT,
271
+ raw_data TEXT,
272
+ time TEXT,
273
+ saved_at INTEGER
274
+ );
275
+ """)
276
+ cur.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_message ON messages(chat_id, message_id);")
277
+ conn.commit()
278
+ conn.close()
279
+
280
+ def _insert_message(self, record: dict):
281
+ conn = sqlite3.connect(self._db_path)
282
+ cur = conn.cursor()
283
+ cur.execute("""
284
+ INSERT OR IGNORE INTO messages
285
+ (chat_id, message_id, sender_id, text, raw_data, time, saved_at)
286
+ VALUES (?, ?, ?, ?, ?, ?, ?)
287
+ """, (
288
+ record.get("chat_id"),
289
+ record.get("message_id"),
290
+ record.get("sender_id"),
291
+ record.get("text"),
292
+ json.dumps(record.get("raw_data") or {}, ensure_ascii=False),
293
+ record.get("time"),
294
+ int(time.time())
295
+ ))
296
+ conn.commit()
297
+ if getattr(self, "_max_messages", None) is not None:
298
+ cur.execute("SELECT COUNT(*) FROM messages")
299
+ total = cur.fetchone()[0]
300
+ if total > self._max_messages:
301
+ remove_count = total - self._max_messages
302
+ cur.execute(
303
+ "DELETE FROM messages WHERE id IN (SELECT id FROM messages ORDER BY saved_at ASC LIMIT ?)",
304
+ (remove_count,)
305
+ )
306
+ conn.commit()
307
+
308
+ conn.close()
309
+
310
+ def _fetch_message(self, chat_id: str, message_id: str):
311
+ conn = sqlite3.connect(self._db_path)
312
+ cur = conn.cursor()
313
+ cur.execute(
314
+ "SELECT chat_id, message_id, sender_id, text, raw_data, time, saved_at FROM messages WHERE chat_id=? AND message_id=?",
315
+ (chat_id, message_id)
316
+ )
317
+ row = cur.fetchone()
318
+ conn.close()
319
+ if not row:
320
+ return None
321
+ chat_id, message_id, sender_id, text, raw_data_json, time_val, saved_at = row
322
+ try:
323
+ raw = json.loads(raw_data_json)
324
+ except:
325
+ raw = {}
326
+ return {
327
+ "chat_id": chat_id,
328
+ "message_id": message_id,
329
+ "sender_id": sender_id,
330
+ "text": text,
331
+ "raw_data": raw,
332
+ "time": time_val,
333
+ "saved_at": saved_at
334
+ }
335
+ async def save_message(self, message: Message):
336
+ try:
337
+ record = {
338
+ "chat_id": getattr(message, "chat_id", None),
339
+ "message_id": getattr(message, "message_id", None),
340
+ "sender_id": getattr(message, "author_guid", None),
341
+ "text": getattr(message, "text", None),
342
+ "raw_data": getattr(message, "raw_data", {}),
343
+ "time": getattr(message, "time", None),
344
+ }
345
+ await asyncio.to_thread(self._insert_message, record)
346
+ except Exception as e:
347
+ print(f"[DB] Error saving message: {e}")
348
+
349
+ async def get_message(self, chat_id: str, message_id: str):
350
+ return await asyncio.to_thread(self._fetch_message, chat_id, message_id)
351
+
352
+ def start_save_message(self, max_messages: int = 1000):
353
+ if self._message_saver_enabled:
354
+ return
355
+ self._message_saver_enabled = True
356
+ self._max_messages = max_messages
357
+ decorators = [
358
+ "on_message", "on_edited_message", "on_message_file", "on_message_forwarded",
359
+ "on_message_reply", "on_message_text", "on_update", "on_callback",
360
+ "on_callback_query", "callback_query_handler", "callback_query",
361
+ "on_inline_query", "on_inline_query_prefix", "on_message_private", "on_message_group"
362
+ ]
363
+
364
+ for decorator_name in decorators:
365
+ if hasattr(self, decorator_name):
366
+ original_decorator = getattr(self, decorator_name)
367
+
368
+ def make_wrapper(orig_decorator):
369
+ def wrapper(*args, **kwargs):
370
+ decorator = orig_decorator(*args, **kwargs)
371
+ def inner_wrapper(func):
372
+ async def inner(bot, message, *a, **kw):
373
+ try:
374
+ await bot.save_message(message)
375
+ if getattr(self, "_max_messages", None) is not None:
376
+ await asyncio.to_thread(self._prune_old_messages)
377
+ except Exception as e:
378
+ print(f"[DB] Save error: {e}")
379
+ return await func(bot, message, *a, **kw)
380
+ return decorator(inner)
381
+ return inner_wrapper
382
+ return wrapper
383
+
384
+ setattr(self, decorator_name, make_wrapper(original_decorator))
385
+ def _prune_old_messages(self):
386
+ if not hasattr(self, "_max_messages") or self._max_messages is None:
387
+ return
388
+ conn = sqlite3.connect(self._db_path)
389
+ cur = conn.cursor()
390
+ cur.execute("SELECT COUNT(*) FROM messages")
391
+ total = cur.fetchone()[0]
392
+ if total > self._max_messages:
393
+ remove_count = total - self._max_messages
394
+ cur.execute(
395
+ "DELETE FROM messages WHERE id IN (SELECT id FROM messages ORDER BY saved_at ASC LIMIT ?)",
396
+ (remove_count,)
397
+ )
398
+ conn.commit()
399
+ conn.close()
400
+
401
+ #save message database __________________________ end
402
+
262
403
  #decorator#
263
404
 
264
405
  def on_message_private(
@@ -851,7 +992,7 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
851
992
  asyncio.create_task(handler_info["func"](self, context))
852
993
  continue
853
994
  if handler_info["commands"] or handler_info["filters"]:
854
- asyncio.create_task(handler_info["func"](self, context))#jaq
995
+ asyncio.create_task(handler_info["func"](self, context))#kir baba kir
855
996
  continue
856
997
  elif update.get("type") == "UpdatedMessage":
857
998
  msg = update.get("updated_message", {})
@@ -911,7 +1052,7 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
911
1052
  async def run(
912
1053
  self,
913
1054
  debug: bool = False,
914
- sleep_time: float = 0.2,
1055
+ sleep_time: float = 0.1,
915
1056
  webhook_timeout: int = 20,
916
1057
  update_limit: int = 100,
917
1058
  retry_delay: float = 5.0,
@@ -1360,7 +1501,13 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
1360
1501
  msg_time = int(msg_data.get("time", 0))
1361
1502
  elif "message_id" in update:
1362
1503
  message_id = update.get("message_id")
1504
+ else:
1505
+ msg_time = time.time()
1506
+ msg_data = update.get("updated_message", {})
1507
+ message_id = msg_data.get("message_id")
1508
+ text_content = msg_data.get("text", "")
1363
1509
  now = int(time.time())
1510
+
1364
1511
  if msg_time and (now - msg_time > self.max_msg_age):
1365
1512
  continue
1366
1513
  dup_ok = True
@@ -1369,15 +1516,10 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
1369
1516
  dup_ok = not self._is_duplicate(dup_key)
1370
1517
  if message_id and dup_ok:
1371
1518
  received_updates.append(update)
1372
-
1373
-
1374
1519
  if not received_updates:
1375
- if pause_on_idle and sleep_time == 0:
1376
- await asyncio.sleep(0.005)
1377
- else:
1378
- await asyncio.sleep(sleep_time)
1379
- if not loop_forever and max_runtime is None:
1380
- break
1520
+ if pause_on_idle and sleep_time == 0:await asyncio.sleep(0.005)
1521
+ else:await asyncio.sleep(sleep_time)
1522
+ if not loop_forever and max_runtime is None:break
1381
1523
  continue
1382
1524
 
1383
1525
 
@@ -1494,7 +1636,53 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
1494
1636
 
1495
1637
 
1496
1638
  _log("Auto-restart requested. You can call run(...) again as needed.", "warning")
1497
-
1639
+ async def _delete_after_task(self, chat_id: str, message_id: str, delay: int):
1640
+ try:
1641
+ await asyncio.sleep(delay)
1642
+ await self.delete_message(chat_id=chat_id, message_id=message_id)
1643
+ except Exception:
1644
+ return False
1645
+ async def _edit_after_task(self, chat_id: str, message_id: str, text:str, delay: int):
1646
+ try:
1647
+ await asyncio.sleep(delay)
1648
+ await self.edit_message_text(chat_id=chat_id, message_id=message_id,text=text)
1649
+ except Exception:
1650
+ return False
1651
+
1652
+ async def delete_after(self, chat_id: str, message_id: str, delay: int = 30) -> asyncio.Task:
1653
+ async def _task():
1654
+ await asyncio.sleep(delay)
1655
+ try:
1656
+ await self.delete_message(chat_id, message_id)
1657
+ except Exception:
1658
+ pass
1659
+
1660
+ try:
1661
+ loop = asyncio.get_running_loop()
1662
+ except RuntimeError:
1663
+ loop = asyncio.new_event_loop()
1664
+ asyncio.set_event_loop(loop)
1665
+
1666
+ task = loop.create_task(_task())
1667
+ return task
1668
+
1669
+ async def edit_after(self, chat_id: str, message_id: str, text : str, delay: int = 30) -> asyncio.Task:
1670
+ async def _task():
1671
+ await asyncio.sleep(delay)
1672
+ try:
1673
+ await self.edit_message_text(chat_id, message_id,text)
1674
+ except Exception:
1675
+ pass
1676
+
1677
+ try:
1678
+ loop = asyncio.get_running_loop()
1679
+ except RuntimeError:
1680
+ loop = asyncio.new_event_loop()
1681
+ asyncio.set_event_loop(loop)
1682
+
1683
+ task = loop.create_task(_task())
1684
+ return task
1685
+
1498
1686
  async def send_message(
1499
1687
  self,
1500
1688
  chat_id: str,
@@ -1503,7 +1691,8 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
1503
1691
  inline_keypad: Optional[Dict[str, Any]] = None,
1504
1692
  disable_notification: bool = False,
1505
1693
  reply_to_message_id: Optional[str] = None,
1506
- chat_keypad_type: Optional[Literal["New", "Removed"]] = None
1694
+ chat_keypad_type: Optional[Literal["New", "Removed"]] = None,
1695
+ delete_after : int = None
1507
1696
  ) -> Dict[str, Any]:
1508
1697
  payload = {
1509
1698
  "chat_id": chat_id,
@@ -1519,12 +1708,16 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
1519
1708
  payload["reply_to_message_id"] = reply_to_message_id
1520
1709
  if self.safeSendMode and reply_to_message_id:
1521
1710
  try:
1522
- return await self._post("sendMessage", payload)
1711
+ state = await self._post("sendMessage", payload)
1523
1712
  except Exception:
1524
1713
  payload.pop("reply_to_message_id", None)
1525
- return await self._post("sendMessage", payload)
1714
+ state = await self._post("sendMessage", payload)
1526
1715
  else:
1527
- return await self._post("sendMessage", payload)
1716
+ state = await self._post("sendMessage", payload)
1717
+ if delete_after:
1718
+ await self.delete_after(chat_id, state.message_id, delete_after)
1719
+ return state
1720
+ return state
1528
1721
 
1529
1722
 
1530
1723
  async def send_sticker(
@@ -1917,33 +2110,35 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
1917
2110
  disable_notification,
1918
2111
  chat_keypad_type
1919
2112
  )
1920
- async def get_avatar_me(
1921
- self,
1922
- save_as: str = None) -> str:
1923
- """
1924
- Get the bot's profile photo URL and optionally save it locally.
1925
- Args:
1926
- save_as (str, optional): File name to save the avatar locally.
1927
- Returns:
1928
- str: Download URL of the bot's avatar, or "null" if not available.
1929
- """
2113
+
2114
+ async def get_avatar_me(self, save_as: str = None) -> str:
2115
+ session = None
1930
2116
  try:
1931
2117
  me_info = await self.get_me()
1932
2118
  avatar = me_info.get('data', {}).get('bot', {}).get('avatar', {})
1933
2119
  file_id = avatar.get('file_id')
1934
2120
  if not file_id:
1935
- return None
1936
- url = await self.get_url_file(file_id)
2121
+ return "null"
2122
+
2123
+ file_info = await self.get_url_file(file_id)
2124
+ url = file_info.get("download_url") if isinstance(file_info, dict) else file_info
2125
+
1937
2126
  if save_as:
1938
- async with aiohttp.ClientSession() as session:
1939
- async with session.get(url) as resp:
1940
- if resp.status == 200:
1941
- content = await resp.read()
1942
- with open(save_as, "wb") as f:
1943
- f.write(content)
2127
+ session = aiohttp.ClientSession()
2128
+ async with session.get(url) as resp:
2129
+ if resp.status == 200:
2130
+ content = await resp.read()
2131
+ with open(save_as, "wb") as f:
2132
+ f.write(content)
2133
+
1944
2134
  return url
1945
- except Exception:
1946
- return None
2135
+ except Exception as e:
2136
+ print(f"[get_avatar_me] Error: {e}")
2137
+ return "null"
2138
+ finally:
2139
+ if session and not session.closed:
2140
+ await session.close()
2141
+
1947
2142
  async def get_name(self, chat_id: str) -> str:
1948
2143
  try:
1949
2144
  chat = await self.get_chat(chat_id)
rubka/context.py CHANGED
@@ -1,5 +1,8 @@
1
1
  from typing import Any, Dict, List,Optional
2
-
2
+ from typing import Union
3
+ from pathlib import Path
4
+ import re,inspect
5
+ import asyncio
3
6
  class File:
4
7
  def __init__(self, data: dict):
5
8
  self.file_id: str = data.get("file_id")
@@ -184,9 +187,25 @@ class Bot:
184
187
  self.username: str = data.get("username")
185
188
  self.start_message: str = data.get("start_message")
186
189
  self.share_url: str = data.get("share_url")
187
- from typing import Union
188
- from pathlib import Path
189
- import re
190
+
191
+ class hybrid_property:
192
+ def __init__(self, func):
193
+ self.func = func
194
+
195
+ def __get__(self, instance, owner):
196
+ if instance is None:
197
+ return self
198
+ coro = self.func(instance)
199
+ try:
200
+ loop = asyncio.get_running_loop()
201
+ return coro
202
+ except RuntimeError:
203
+ try:
204
+ loop = asyncio.get_event_loop()
205
+ except RuntimeError:
206
+ loop = asyncio.new_event_loop()
207
+ asyncio.set_event_loop(loop)
208
+ return loop.run_until_complete(coro)
190
209
  class Message:
191
210
  def __init__(self, bot, chat_id, message_id, sender_id, text=None, raw_data=None):
192
211
  self.bot = bot
@@ -240,19 +259,74 @@ class Message:
240
259
  self.is_archive = name.endswith((".zip", ".rar", ".7z", ".tar", ".gz"))
241
260
  self.is_executable = name.endswith((".exe", ".msi", ".bat", ".sh"))
242
261
  self.is_font = name.endswith((".ttf", ".otf", ".woff", ".woff2"))
243
- self.info = {attr: value for attr, value in vars(self).items()}
244
262
  @property
245
263
  def session(self):
246
264
  if self.chat_id not in self.bot.sessions:
247
265
  self.bot.sessions[self.chat_id] = {}
248
266
  return self.bot.sessions[self.chat_id]
249
- def reply(self, text: str, **kwargs):
250
- return self.bot.send_message(
251
- self.chat_id,
252
- text,
253
- reply_to_message_id=self.message_id,
254
- **kwargs
255
- )
267
+
268
+ def reply(self, text: str, delete_after: int = None, **kwargs):
269
+ async def _reply_async():
270
+ send_func = self.bot.send_message
271
+ if inspect.iscoroutinefunction(send_func):
272
+ msg = await send_func(
273
+ self.chat_id,
274
+ text,
275
+ reply_to_message_id=self.message_id,
276
+ delete_after=delete_after,
277
+ **kwargs
278
+ )
279
+ else:
280
+ msg = send_func(
281
+ self.chat_id,
282
+ text,
283
+ reply_to_message_id=self.message_id,
284
+ delete_after=delete_after,
285
+ **kwargs
286
+ )
287
+ class Pick:
288
+ def __init__(self, bot, chat_id, message_id):
289
+ self.bot = bot
290
+ self.chat_id = chat_id
291
+ self.message_id = message_id
292
+
293
+ def edit(self, new_text):
294
+ async def _edit():
295
+ func = self.bot.edit_message_text
296
+ if inspect.iscoroutinefunction(func):
297
+ await func(self.chat_id, self.message_id, new_text)
298
+ else:
299
+ func(self.chat_id, self.message_id, new_text)
300
+
301
+ try:
302
+ loop = asyncio.get_running_loop()
303
+ if loop.is_running():
304
+ return asyncio.create_task(_edit())
305
+ except RuntimeError:
306
+ return asyncio.run(_edit())
307
+
308
+ def delete(self):
309
+ async def _delete():
310
+ func = self.bot.delete_message
311
+ if inspect.iscoroutinefunction(func):
312
+ await func(self.chat_id, self.message_id)
313
+ else:
314
+ func(self.chat_id, self.message_id)
315
+ try:
316
+ loop = asyncio.get_running_loop()
317
+ if loop.is_running():
318
+ return asyncio.create_task(_delete())
319
+ except RuntimeError:
320
+ return asyncio.run(_delete())
321
+ chat_id = msg.get("chat_id") if isinstance(msg, dict) else getattr(msg, "chat_id", self.chat_id)
322
+ message_id = msg.get("message_id") if isinstance(msg, dict) else getattr(msg, "message_id", self.message_id)
323
+ return Pick(self.bot, chat_id, message_id)
324
+ try:
325
+ loop = asyncio.get_running_loop()
326
+ if loop.is_running():
327
+ return asyncio.create_task(_reply_async())
328
+ except RuntimeError:
329
+ return asyncio.run(_reply_async())
256
330
  def answer(self, text: str, **kwargs):
257
331
  return self.bot.send_message(
258
332
  self.chat_id,
@@ -260,6 +334,7 @@ class Message:
260
334
  reply_to_message_id=self.message_id,
261
335
  **kwargs
262
336
  )
337
+
263
338
 
264
339
  def reply_poll(self, question: str, options: List[str], **kwargs) -> Dict[str, Any]:
265
340
  return self.bot._post("sendPoll", {
@@ -471,6 +546,14 @@ class Message:
471
546
  chat_id=self.chat_id,
472
547
  message_id=self.message_id
473
548
  )
549
+ @hybrid_property
550
+ async def author_name(self):return await self.bot.get_name(self.chat_id)
551
+ @hybrid_property
552
+ async def name(self):return await self.bot.get_name(self.chat_id)
553
+ @hybrid_property
554
+ async def username(self):return await self.bot.get_username(self.chat_id)
555
+ @hybrid_property
556
+ async def author_info(self):return await self.bot.get_chat(self.chat_id)
474
557
  class AuxData:
475
558
  def __init__(self, data: dict):
476
559
  self.start_id = data.get("start_id")
rubka/update.py CHANGED
@@ -1,5 +1,8 @@
1
1
  from typing import Any, Dict, List,Optional
2
-
2
+ from typing import Union
3
+ from pathlib import Path
4
+ import re,inspect
5
+ import asyncio
3
6
  class File:
4
7
  def __init__(self, data: dict):
5
8
  self.file_id: str = data.get("file_id")
@@ -184,9 +187,25 @@ class Bot:
184
187
  self.username: str = data.get("username")
185
188
  self.start_message: str = data.get("start_message")
186
189
  self.share_url: str = data.get("share_url")
187
- from typing import Union
188
- from pathlib import Path
189
- import re
190
+
191
+ class hybrid_property:
192
+ def __init__(self, func):
193
+ self.func = func
194
+
195
+ def __get__(self, instance, owner):
196
+ if instance is None:
197
+ return self
198
+ coro = self.func(instance)
199
+ try:
200
+ loop = asyncio.get_running_loop()
201
+ return coro
202
+ except RuntimeError:
203
+ try:
204
+ loop = asyncio.get_event_loop()
205
+ except RuntimeError:
206
+ loop = asyncio.new_event_loop()
207
+ asyncio.set_event_loop(loop)
208
+ return loop.run_until_complete(coro)
190
209
  class Message:
191
210
  def __init__(self, bot, chat_id, message_id, sender_id, text=None, raw_data=None):
192
211
  self.bot = bot
@@ -240,19 +259,74 @@ class Message:
240
259
  self.is_archive = name.endswith((".zip", ".rar", ".7z", ".tar", ".gz"))
241
260
  self.is_executable = name.endswith((".exe", ".msi", ".bat", ".sh"))
242
261
  self.is_font = name.endswith((".ttf", ".otf", ".woff", ".woff2"))
243
- self.info = {attr: value for attr, value in vars(self).items()}
244
262
  @property
245
263
  def session(self):
246
264
  if self.chat_id not in self.bot.sessions:
247
265
  self.bot.sessions[self.chat_id] = {}
248
266
  return self.bot.sessions[self.chat_id]
249
- def reply(self, text: str, **kwargs):
250
- return self.bot.send_message(
251
- self.chat_id,
252
- text,
253
- reply_to_message_id=self.message_id,
254
- **kwargs
255
- )
267
+
268
+ def reply(self, text: str, delete_after: int = None, **kwargs):
269
+ async def _reply_async():
270
+ send_func = self.bot.send_message
271
+ if inspect.iscoroutinefunction(send_func):
272
+ msg = await send_func(
273
+ self.chat_id,
274
+ text,
275
+ reply_to_message_id=self.message_id,
276
+ delete_after=delete_after,
277
+ **kwargs
278
+ )
279
+ else:
280
+ msg = send_func(
281
+ self.chat_id,
282
+ text,
283
+ reply_to_message_id=self.message_id,
284
+ delete_after=delete_after,
285
+ **kwargs
286
+ )
287
+ class Pick:
288
+ def __init__(self, bot, chat_id, message_id):
289
+ self.bot = bot
290
+ self.chat_id = chat_id
291
+ self.message_id = message_id
292
+
293
+ def edit(self, new_text):
294
+ async def _edit():
295
+ func = self.bot.edit_message_text
296
+ if inspect.iscoroutinefunction(func):
297
+ await func(self.chat_id, self.message_id, new_text)
298
+ else:
299
+ func(self.chat_id, self.message_id, new_text)
300
+
301
+ try:
302
+ loop = asyncio.get_running_loop()
303
+ if loop.is_running():
304
+ return asyncio.create_task(_edit())
305
+ except RuntimeError:
306
+ return asyncio.run(_edit())
307
+
308
+ def delete(self):
309
+ async def _delete():
310
+ func = self.bot.delete_message
311
+ if inspect.iscoroutinefunction(func):
312
+ await func(self.chat_id, self.message_id)
313
+ else:
314
+ func(self.chat_id, self.message_id)
315
+ try:
316
+ loop = asyncio.get_running_loop()
317
+ if loop.is_running():
318
+ return asyncio.create_task(_delete())
319
+ except RuntimeError:
320
+ return asyncio.run(_delete())
321
+ chat_id = msg.get("chat_id") if isinstance(msg, dict) else getattr(msg, "chat_id", self.chat_id)
322
+ message_id = msg.get("message_id") if isinstance(msg, dict) else getattr(msg, "message_id", self.message_id)
323
+ return Pick(self.bot, chat_id, message_id)
324
+ try:
325
+ loop = asyncio.get_running_loop()
326
+ if loop.is_running():
327
+ return asyncio.create_task(_reply_async())
328
+ except RuntimeError:
329
+ return asyncio.run(_reply_async())
256
330
  def answer(self, text: str, **kwargs):
257
331
  return self.bot.send_message(
258
332
  self.chat_id,
@@ -260,6 +334,7 @@ class Message:
260
334
  reply_to_message_id=self.message_id,
261
335
  **kwargs
262
336
  )
337
+
263
338
 
264
339
  def reply_poll(self, question: str, options: List[str], **kwargs) -> Dict[str, Any]:
265
340
  return self.bot._post("sendPoll", {
@@ -471,6 +546,14 @@ class Message:
471
546
  chat_id=self.chat_id,
472
547
  message_id=self.message_id
473
548
  )
549
+ @hybrid_property
550
+ async def author_name(self):return await self.bot.get_name(self.chat_id)
551
+ @hybrid_property
552
+ async def name(self):return await self.bot.get_name(self.chat_id)
553
+ @hybrid_property
554
+ async def username(self):return await self.bot.get_username(self.chat_id)
555
+ @hybrid_property
556
+ async def author_info(self):return await self.bot.get_chat(self.chat_id)
474
557
  class AuxData:
475
558
  def __init__(self, data: dict):
476
559
  self.start_id = data.get("start_id")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 6.7.11
3
+ Version: 7.1.1
4
4
  Summary: rubika 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/archive/refs/tags/v6.6.4.zip
@@ -31,6 +31,7 @@ Requires-Dist: Pillow
31
31
  Requires-Dist: websocket-client
32
32
  Requires-Dist: pycryptodome
33
33
  Requires-Dist: aiohttp
34
+ Requires-Dist: httpx
34
35
  Requires-Dist: tqdm
35
36
  Requires-Dist: mutagen
36
37
  Requires-Dist: filetype
@@ -1,9 +1,9 @@
1
1
  rubka/__init__.py,sha256=P6IBiORfp-GqKHe5LZ-5lldWyG7tnrUYUcAQDUgwXmY,1973
2
- rubka/api.py,sha256=1S8JIcYN_Zxb7JT73eQl9me-qDDDTDzcSk6_qawAfkA,68861
3
- rubka/asynco.py,sha256=lmORNDwpDkz1gfgT_3J9PiHo2WTJoOkU8GiQSeTFSKM,100154
2
+ rubka/api.py,sha256=fo4X9MH_HqQmLkRNhjJcJJZOHp5lkFZqitCXs2qL_kc,68891
3
+ rubka/asynco.py,sha256=CD_4UBhRSE5RxgVSJKb7_nBcRE7YowKeEHeYeyfYmY4,107945
4
4
  rubka/button.py,sha256=vU9OvWXCD4MRrTJ8Xmivd4L471-06zrD2qpZBTw5vjY,13305
5
5
  rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
6
- rubka/context.py,sha256=kYLsGq4cQhmMX16WrzH_kxYzGGyvulZyxazoN_5tul4,30768
6
+ rubka/context.py,sha256=-oC9h7U_H3CrtqUCDCnFXAvC7zdSmwxlc0CNwL1XLxM,34314
7
7
  rubka/decorators.py,sha256=hGwUoE4q2ImrunJIGJ_kzGYYxQf1ueE0isadqraKEts,1157
8
8
  rubka/exceptions.py,sha256=DDOGIHEMoliHNW5E7C_s38WZgqqMBv9812fcJGvj7TY,1173
9
9
  rubka/filters.py,sha256=DY1bdkpRKIiLtVcy6X3hOnlGPcVOK4HFb3QgmaPx6Oo,12116
@@ -14,7 +14,7 @@ rubka/keypad.py,sha256=yGsNt8W5HtUFBzVF1m_p7GySlu1hwIcSvXZ4BTdrlvg,9558
14
14
  rubka/logger.py,sha256=J2I6NiK1z32lrAzC4H1Et6WPMBXxXGCVUsW4jgcAofs,289
15
15
  rubka/rubino.py,sha256=GuXnEUTTSZUw68FMTQBYsB2zG_3rzdDa-krsrl4ib8w,58755
16
16
  rubka/tv.py,sha256=o7CW-6EKLhmM--rzcHcKPcXfzOixcL_B4j037R3mHts,5399
17
- rubka/update.py,sha256=kYLsGq4cQhmMX16WrzH_kxYzGGyvulZyxazoN_5tul4,30768
17
+ rubka/update.py,sha256=-oC9h7U_H3CrtqUCDCnFXAvC7zdSmwxlc0CNwL1XLxM,34314
18
18
  rubka/utils.py,sha256=XUQUZxQt9J2f0X5hmAH_MH1kibTAfdT1T4AaBkBhBBs,148
19
19
  rubka/adaptorrubka/__init__.py,sha256=6o2tCXnVeES7nx-LjnzsuMqjKcWIm9qwKficLE54s-U,83
20
20
  rubka/adaptorrubka/enums.py,sha256=cyiakExmZi-QQpYuf_A93HQvfZVmyG_0uVuvTTNT5To,1053
@@ -37,8 +37,8 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
37
37
  rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
38
38
  rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
39
39
  rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
40
- rubka-6.7.11.dist-info/METADATA,sha256=DHDKmq65MyUA59K4Rg-JaPVFO59c71Rt87Wkqba-g0E,34270
41
- rubka-6.7.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
- rubka-6.7.11.dist-info/entry_points.txt,sha256=4aESuUmuUOALMUy7Kucv_Gb5YlqhsJmTmdXLlZU9sJ0,46
43
- rubka-6.7.11.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
44
- rubka-6.7.11.dist-info/RECORD,,
40
+ rubka-7.1.1.dist-info/METADATA,sha256=mAyvw8N3LjT2Hz7bPllCqAwrs_nvhzvKNtaFKXHjKX4,34291
41
+ rubka-7.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
+ rubka-7.1.1.dist-info/entry_points.txt,sha256=4aESuUmuUOALMUy7Kucv_Gb5YlqhsJmTmdXLlZU9sJ0,46
43
+ rubka-7.1.1.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
44
+ rubka-7.1.1.dist-info/RECORD,,
File without changes