Rubka 6.6.12__py3-none-any.whl → 6.7.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/asynco.py CHANGED
@@ -1,6 +1,7 @@
1
- import asyncio,aiohttp,aiofiles,time,datetime,json,tempfile,os,sys,subprocess,mimetypes
1
+ import asyncio,aiohttp,aiofiles,time,datetime,json,tempfile,os,sys,subprocess,mimetypes,time, hashlib
2
2
  from typing import List, Optional, Dict, Any, Literal, Callable, Union,Set
3
- from .exceptions import APIRequestError
3
+ from collections import OrderedDict
4
+ from .exceptions import APIRequestError,raise_for_status,InvalidAccessError,InvalidInputError,TooRequestError,InvalidTokenError
4
5
  from .adaptorrubka import Client as Client_get
5
6
  from .logger import logger
6
7
  from .rubino import Bot as Rubino
@@ -12,7 +13,7 @@ class FeatureNotAvailableError(Exception):
12
13
 
13
14
  from tqdm.asyncio import tqdm
14
15
  from urllib.parse import urlparse, parse_qs
15
- class InvalidTokenError(Exception):pass
16
+
16
17
  from pathlib import Path
17
18
  from tqdm import tqdm
18
19
  API_URL = "https://botapi.rubika.ir/v3"
@@ -98,38 +99,64 @@ class AttrDict(dict):
98
99
 
99
100
  class Robot:
100
101
  """
101
- Main asynchronous class to interact with the Rubika Bot API.
102
-
103
- This class handles sending and receiving messages, inline queries, callbacks,
104
- and manages sessions and API interactions. It is initialized with a bot token
105
- and provides multiple optional parameters for configuration.
106
-
107
- Attributes:
108
- token (str): Bot token used for authentication with Rubika Bot API.
109
- session_name (str | None): Optional session name for storing session data.
110
- auth (str | None): Optional authentication string for advanced features.
111
- Key (str | None): Optional key for additional authorization if required.
112
- platform (str): Platform type, default is 'web'.
113
- web_hook (str | None): Optional webhook URL for receiving updates.
114
- timeout (int): Timeout for API requests in seconds (default 10).
115
- show_progress (bool): Whether to show progress for long operations (default False).
116
- Example:
117
- ```python
118
- import asyncio
119
- from rubka.asynco import Robot,filters,Message
120
- bot = Robot(token="YOUR_BOT_TOKEN")
121
- @bot.on_message(filters.is_command.start)
122
- async def start_command(bot: Robot, message: Message):
123
- await message.reply("Hello!")
124
- asyncio.run(bot.run())
125
-
126
- """
127
-
128
- 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):
102
+ Main asynchronous class to interact with the Rubika Bot API.
103
+
104
+ This class handles sending and receiving messages, inline queries, callbacks,
105
+ and manages sessions and API interactions. It is initialized with a bot token
106
+ and provides multiple optional parameters for configuration.
107
+
108
+ Attributes:
109
+ token (str): Bot token used for authentication with Rubika Bot API.
110
+ session_name (str | None): Optional session name for storing session data.
111
+ auth (str | None): Optional authentication string for advanced features related to account key.
112
+ Key (str | None): Optional account key for additional authorization if required.
113
+ platform (str): Platform type, default is 'web'.
114
+ web_hook (str | None): Optional webhook URL for receiving updates.
115
+ timeout (int): Timeout for API requests in seconds (default 10).
116
+ show_progress (bool): Whether to show progress for long operations (default False).
117
+ raise_errors (bool): Whether to raise exceptions on API errors (default True).
118
+ proxy (str | None): Optional proxy URL to route requests through.
119
+ retries (int): Number of times to retry a failed API request (default 2).
120
+ retry_delay (float): Delay between retries in seconds (default 0.5).
121
+ user_agent (str | None): Custom User-Agent header for requests.
122
+ safeSendMode (bool): If True, messages are sent safely. If reply fails using message_id, sends without message_id (default False).
123
+ max_cache_size (int): Maximum number of processed messages stored to prevent duplicates (default 1000).
124
+ max_msg_age (int): Maximum age of messages in seconds to consider for processing (default 20).
125
+
126
+ Example:
127
+ ```python
128
+ import asyncio
129
+ from rubka.asynco import Robot, filters, Message
130
+
131
+ bot = Robot(token="YOUR_BOT_TOKEN", safeSendMode=False, max_cache_size=1000)
132
+
133
+ @bot.on_message(filters.is_command.start)
134
+ async def start_command(bot: Robot, message: Message):
135
+ await message.reply("Hello!")
136
+
137
+ asyncio.run(bot.run())
138
+ ```
139
+ Notes:
140
+
141
+ token is mandatory, all other parameters are optional.
142
+
143
+ safeSendMode ensures reliable message sending even if replying by message_id fails.
144
+
145
+ max_cache_size and max_msg_age help manage duplicate message processing efficiently.
146
+ """
147
+
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):
129
149
  self.token = token
130
150
  self._inline_query_handlers: List[dict] = []
131
151
  self.timeout = timeout
132
152
  self.auth = auth
153
+ self.safeSendMode = safeSendMode
154
+ self.user_agent = user_agent
155
+ self.proxy = proxy
156
+ self.max_msg_age = max_msg_age
157
+ self.retries = retries
158
+ self.retry_delay = retry_delay
159
+ self.raise_errors = raise_errors
133
160
  self.show_progress = show_progress
134
161
  self.session_name = session_name
135
162
  self.Key = Key
@@ -139,20 +166,25 @@ class Robot:
139
166
  self._aiohttp_session: aiohttp.ClientSession = None
140
167
  self.sessions: Dict[str, Dict[str, Any]] = {}
141
168
  self._callback_handler = None
142
- self._message_handler = None
143
- self._inline_query_handler = None
169
+ self._processed_message_ids = OrderedDict()
170
+ self._max_cache_size = max_cache_size
171
+
172
+ self._connector = aiohttp.TCPConnector(limit=100, ssl=False)
144
173
 
145
174
  self._callback_handlers: List[dict] = []
146
175
  self._edited_message_handlers = []
147
- self._processed_message_ids: Dict[str, float] = {}
148
176
  self._message_handlers: List[dict] = []
149
177
 
150
178
  logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
151
179
  async def _get_session(self) -> aiohttp.ClientSession:
152
- """Lazily creates and returns the aiohttp session."""
153
180
  if self._aiohttp_session is None or self._aiohttp_session.closed:
154
- self._aiohttp_session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.timeout))
181
+ connector = aiohttp.TCPConnector(limit=100, ssl=False) # limit اتصالات همزمان
182
+ timeout = aiohttp.ClientTimeout(total=self.timeout)
183
+ self._aiohttp_session = aiohttp.ClientSession(connector=connector, timeout=timeout)
155
184
  return self._aiohttp_session
185
+ async def close(self):
186
+ if self._session and not self._session.closed:
187
+ await self._session.close()
156
188
 
157
189
  async def _initialize_webhook(self):
158
190
  """Initializes and sets the webhook endpoint if provided."""
@@ -183,22 +215,43 @@ class Robot:
183
215
  async def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
184
216
  url = f"{API_URL}/{self.token}/{method}"
185
217
  session = await self._get_session()
186
- try:
187
- async with session.post(url, json=data) as response:
188
- response.raise_for_status()
189
- try:
190
- json_resp = await response.json()
191
- except aiohttp.ContentTypeError:
192
- text_resp = await response.text()
193
- logger.error(f"Invalid JSON response from {method}: {text_resp}")
194
- raise APIRequestError(f"Invalid JSON response: {text_resp}")
195
- if method != "getUpdates":
196
- logger.debug(f"API Response from {method}: {json_resp}")
197
- return AttrDict(json_resp)
198
- except aiohttp.ClientError as e:
199
- logger.error(f"API request failed: {e}")
200
- raise APIRequestError(f"API request failed: {e}") from e
218
+ for attempt in range(1, self.retries + 1):
219
+ try:
220
+ headers = {}
221
+ if self.user_agent:headers["User-Agent"] = self.user_agent
222
+ async with session.post(url, json=data, proxy=self.proxy,headers=headers) as response:
223
+ if response.status in (429, 500, 502, 503, 504):
224
+ logger.warning(f"[{method}] Got status {response.status}, retry {attempt}/{self.retries}...")
225
+ if attempt < self.retries:
226
+ await asyncio.sleep(self.retry_delay)
227
+ continue
228
+ response.raise_for_status()
201
229
 
230
+ response.raise_for_status()
231
+ try:
232
+ json_resp = await response.json(content_type=None)
233
+ except Exception:
234
+ text_resp = await response.text()
235
+ logger.error(f"[{method}] Invalid JSON response: {text_resp}")
236
+ raise APIRequestError(f"Invalid JSON response: {text_resp}")
237
+
238
+ status = json_resp.get("status")
239
+ if status in {"INVALID_ACCESS", "INVALID_INPUT", "TOO_REQUESTS"}:
240
+ if self.raise_errors:
241
+ raise_for_status(json_resp)
242
+ return AttrDict(json_resp)
243
+ return AttrDict({**json_resp, **data,"message_id":json_resp.get("data").get("message_id")})
244
+
245
+ except (aiohttp.ClientError, asyncio.TimeoutError) as e:
246
+ logger.warning(f"[{method}] Attempt {attempt}/{self.retries} failed: {e}")
247
+ if attempt < self.retries:
248
+ await asyncio.sleep(self.retry_delay)
249
+ continue
250
+ logger.error(f"[{method}] API request failed after {self.retries} retries: {e}")
251
+ raise APIRequestError(f"API request failed: {e}") from e
252
+ def _make_dup_key(self, message_id: str, update_type: str, msg_data: dict) -> str:
253
+ raw = f"{message_id}:{update_type}:{msg_data.get('text','')}:{msg_data.get('author_guid','')}"
254
+ return hashlib.sha1(raw.encode()).hexdigest()
202
255
  async def get_me(self) -> Dict[str, Any]:
203
256
  """Get info about the bot itself."""
204
257
  return await self._post("getMe", {})
@@ -504,25 +557,34 @@ class Robot:
504
557
  return wrapper
505
558
  return decorator
506
559
  def on_message(
507
- self,
508
- filters: Optional[Callable[[Message], bool]] = None,
509
- commands: Optional[List[str]] = None
510
- ):
511
- def decorator(func: Callable[[Any, Message], None]):
512
- async def wrapper(bot, message: Message):
513
- if filters and not filters(message):return
514
- if commands:
515
- if not message.is_command:return
516
- cmd = message.text.split()[0].lstrip("/")
517
- if cmd not in commands:return
518
- return await func(bot, message)
519
- self._message_handlers.append({
520
- "func": wrapper,
521
- "filters": filters,
522
- "commands": commands
523
- })
524
- return wrapper
525
- return decorator
560
+ self,
561
+ filters: Optional[Callable[[Message], bool]] = None,
562
+ commands: Optional[List[str]] = None):
563
+ def decorator(func: Callable[[Any, Message], None]):
564
+ async def wrapper(bot, message: Message):
565
+ if filters and not filters(message):
566
+ return
567
+ if commands:
568
+ if not message.is_command:
569
+ return
570
+ cmd = message.text.split()[0].lstrip("/")
571
+ if cmd not in commands:
572
+ return
573
+
574
+ return await func(bot, message)
575
+ self._message_handlers.append({
576
+ "func": wrapper,
577
+ "filters": filters,
578
+ "commands": commands
579
+ })
580
+ self._edited_message_handlers.append({
581
+ "func": wrapper,
582
+ "filters": filters,
583
+ "commands": commands
584
+ })
585
+
586
+ return wrapper
587
+ return decorator
526
588
 
527
589
 
528
590
  def on_message_file(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
@@ -837,14 +899,17 @@ class Robot:
837
899
  response.raise_for_status()
838
900
  return await response.json()
839
901
 
840
- def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
902
+ def _is_duplicate(self, key: str, max_age_sec: int = 300) -> bool:
841
903
  now = time.time()
842
904
  expired = [mid for mid, ts in self._processed_message_ids.items() if now - ts > max_age_sec]
843
- for mid in expired:del self._processed_message_ids[mid]
844
- if message_id in self._processed_message_ids:return True
845
- self._processed_message_ids[message_id] = now
905
+ for mid in expired:
906
+ del self._processed_message_ids[mid]
907
+ if key in self._processed_message_ids:
908
+ return True
909
+ self._processed_message_ids[key] = now
910
+ if len(self._processed_message_ids) > self._max_cache_size:
911
+ self._processed_message_ids.popitem(last=False)
846
912
  return False
847
-
848
913
 
849
914
  async def run(
850
915
  self,
@@ -893,7 +958,7 @@ class Robot:
893
958
  notify_on_error: bool = False,
894
959
  notification_handler: Optional[Callable[[str], Any]] = None,
895
960
  watchdog_timeout: Optional[float] = None,
896
- ):
961
+ ):
897
962
  """
898
963
  Starts the bot's main execution loop with extensive configuration options.
899
964
 
@@ -1045,7 +1110,7 @@ class Robot:
1045
1110
  chat = update.get("object_guid") or update.get("chat_id")
1046
1111
  return str(sender) if sender is not None else None, str(chat) if chat is not None else None
1047
1112
 
1048
- def _is_group_chat(chat_guid: str | None) -> bool | None:
1113
+ def _is_group_chat(chat_guid: Optional[str]) -> Optional[bool]:
1049
1114
 
1050
1115
  if chat_guid is None:
1051
1116
  return None
@@ -1282,24 +1347,36 @@ class Robot:
1282
1347
  for update in updates:
1283
1348
  message_id = None
1284
1349
  if update.get("type") == "NewMessage":
1285
- message_id = update.get("new_message", {}).get("message_id")
1350
+ msg_data = update.get("new_message", {})
1351
+ message_id = msg_data.get("message_id")
1352
+ text_content = msg_data.get("text", "")
1353
+ msg_time = int(msg_data.get("time", 0))
1286
1354
  elif update.get("type") == "ReceiveQuery":
1287
- message_id = update.get("inline_message", {}).get("message_id")
1355
+ msg_data = update.get("inline_message", {})
1356
+ message_id = msg_data.get("message_id")
1357
+ text_content = msg_data.get("text", "")
1358
+ msg_time = int(msg_data.get("time", 0))
1288
1359
  elif update.get("type") == "UpdatedMessage":
1289
- message_id = update.get("updated_message", {}).get("message_id")
1360
+ msg_data = update.get("updated_message", {})
1361
+ message_id = msg_data.get("message_id")
1362
+ text_content = msg_data.get("text", "")
1363
+ msg_time = int(msg_data.get("time", 0))
1290
1364
  elif "message_id" in update:
1291
1365
  message_id = update.get("message_id")
1292
-
1366
+ now = int(time.time())
1367
+ if msg_time and (now - msg_time > self.max_msg_age):
1368
+ continue
1293
1369
  dup_ok = True
1294
- if ignore_duplicate_messages:
1295
- dup_ok = (not self._is_duplicate(str(message_id))) if message_id else True
1370
+ if ignore_duplicate_messages and message_id:
1371
+ dup_key = self._make_dup_key(message_id, update.get("type", ""), msg_data)
1372
+ dup_ok = not self._is_duplicate(dup_key)
1296
1373
  if message_id and dup_ok:
1297
1374
  received_updates.append(update)
1298
1375
 
1299
1376
 
1300
1377
  if not received_updates:
1301
1378
  if pause_on_idle and sleep_time == 0:
1302
- await asyncio.sleep(0.05)
1379
+ await asyncio.sleep(0.005)
1303
1380
  else:
1304
1381
  await asyncio.sleep(sleep_time)
1305
1382
  if not loop_forever and max_runtime is None:
@@ -1430,7 +1507,7 @@ class Robot:
1430
1507
  disable_notification: bool = False,
1431
1508
  reply_to_message_id: Optional[str] = None,
1432
1509
  chat_keypad_type: Optional[Literal["New", "Removed"]] = None
1433
- ) -> Dict[str, Any]:
1510
+ ) -> Dict[str, Any]:
1434
1511
  payload = {
1435
1512
  "chat_id": chat_id,
1436
1513
  "text": text,
@@ -1443,9 +1520,15 @@ class Robot:
1443
1520
  payload["inline_keypad"] = inline_keypad
1444
1521
  if reply_to_message_id:
1445
1522
  payload["reply_to_message_id"] = reply_to_message_id
1446
- return await self._post("sendMessage", payload)
1523
+ if self.safeSendMode and reply_to_message_id:
1524
+ try:
1525
+ return await self._post("sendMessage", payload)
1526
+ except Exception:
1527
+ payload.pop("reply_to_message_id", None)
1528
+ return await self._post("sendMessage", payload)
1529
+ else:
1530
+ return await self._post("sendMessage", payload)
1447
1531
 
1448
- from typing import Optional, Dict, Any, Literal
1449
1532
 
1450
1533
  async def send_sticker(
1451
1534
  self,
@@ -1494,22 +1577,6 @@ class Robot:
1494
1577
  return Client_get(self.session_name, self.auth, self.Key, self.platform)
1495
1578
  else:
1496
1579
  return Client_get(show_last_six_words(self.token), self.auth, self.Key, self.platform)
1497
-
1498
- async def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
1499
- client = self._get_client()
1500
- if chat_id:
1501
- chat_info_data = await self.get_chat(chat_id)
1502
- chat_info = chat_info_data.get('data', {}).get('chat', {})
1503
- username = chat_info.get('username')
1504
- user_id = chat_info.get('user_id')
1505
- if username:
1506
- result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
1507
- members = result.get('in_chat_members', [])
1508
- return any(m.get('username') == username for m in members)
1509
- elif user_id:
1510
- member_guids = await asyncio.to_thread(client.get_all_members, channel_guid, just_get_guids=True)
1511
- return user_id in member_guids
1512
- return False
1513
1580
  async def send_button_join(
1514
1581
  self,
1515
1582
  chat_id,
@@ -1595,21 +1662,15 @@ class Robot:
1595
1662
  inline_keypad=builder.build(),
1596
1663
  reply_to_message_id=reply_to_message_id
1597
1664
  )
1598
- def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
1599
- client = self._get_client()
1600
- return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
1601
- async def send_poll(self, chat_id: str, question: str, options: List[str]) -> Dict[str, Any]:
1602
- return await self._post("sendPoll", {"chat_id": chat_id, "question": question, "options": options})
1665
+
1666
+ async def close_poll(self, chat_id: str, message_id: str) -> Dict[str, Any]:
1667
+ return await self._post("closePoll", {"chat_id": chat_id, "message_id": message_id})
1603
1668
  async def send_location(self, chat_id: str, latitude: str, longitude: str, disable_notification: bool = False, inline_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, chat_keypad_type: Optional[Literal["New", "Removed"]] = None) -> Dict[str, Any]:
1604
1669
  payload = {"chat_id": chat_id, "latitude": latitude, "longitude": longitude, "disable_notification": disable_notification}
1605
1670
  if inline_keypad: payload["inline_keypad"] = inline_keypad
1606
1671
  if reply_to_message_id: payload["reply_to_message_id"] = reply_to_message_id
1607
1672
  if chat_keypad_type: payload["chat_keypad_type"] = chat_keypad_type
1608
1673
  return await self._post("sendLocation", {k: v for k, v in payload.items() if v is not None})
1609
- async def send_contact(self, chat_id: str, first_name: str, last_name: str, phone_number: str) -> Dict[str, Any]:
1610
- return await self._post("sendContact", {"chat_id": chat_id, "first_name": first_name, "last_name": last_name, "phone_number": phone_number})
1611
- async def get_chat(self, chat_id: str) -> Dict[str, Any]:
1612
- return await self._post("getChat", {"chat_id": chat_id})
1613
1674
  async def upload_media_file(self, upload_url: str, name: str, path: Union[str, Path]) -> str:
1614
1675
  is_temp_file = False
1615
1676
  session = await self._get_session()
@@ -1725,14 +1786,12 @@ class Robot:
1725
1786
  raise asyncio.TimeoutError("The download operation timed out.")
1726
1787
  except Exception as e:
1727
1788
  raise Exception(f"An error occurred while downloading the file: {e}")
1728
-
1729
1789
  async def get_upload_url(self, media_type: Literal['File', 'Image', 'voice', 'Music', 'Gif', 'Video']) -> str:
1730
1790
  allowed = ['File', 'Image', 'voice', 'Music', 'Gif', 'Video']
1731
1791
  if media_type not in allowed:
1732
1792
  raise ValueError(f"Invalid media type. Must be one of {allowed}")
1733
1793
  result = await self._post("requestSendFile", {"type": media_type})
1734
1794
  return result.get("data", {}).get("upload_url")
1735
-
1736
1795
  async def _send_uploaded_file(self, chat_id: str, file_id: str,type_file : str = "file",text: Optional[str] = None, chat_keypad: Optional[Dict[str, Any]] = None, inline_keypad: Optional[Dict[str, Any]] = None, disable_notification: bool = False, reply_to_message_id: Optional[str] = None, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
1737
1796
  payload = {"chat_id": chat_id, "file_id": file_id, "text": text, "disable_notification": disable_notification, "chat_keypad_type": chat_keypad_type}
1738
1797
  if chat_keypad: payload["chat_keypad"] = chat_keypad
@@ -1757,7 +1816,6 @@ class Robot:
1757
1816
  "chat_keypad_type":chat_keypad_type
1758
1817
  }
1759
1818
  return AttrDict(result)
1760
-
1761
1819
  async def _send_file_generic(self, media_type, chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type):
1762
1820
  if path:
1763
1821
  file_name = file_name or Path(path).name
@@ -1766,14 +1824,12 @@ class Robot:
1766
1824
  if not file_id:
1767
1825
  raise ValueError("Either path or file_id must be provided.")
1768
1826
  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,type_file=media_type)
1769
-
1770
1827
  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]:
1771
1828
  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)
1772
1829
  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]:
1773
1830
  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)
1774
1831
  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]:
1775
1832
  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)
1776
-
1777
1833
  async def send_music(
1778
1834
  self,
1779
1835
  chat_id: str,
@@ -1819,12 +1875,6 @@ class Robot:
1819
1875
  disable_notification,
1820
1876
  chat_keypad_type
1821
1877
  )
1822
- 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]:
1823
- 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)
1824
- 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]:
1825
- 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)
1826
- 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]:
1827
- 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)
1828
1878
  async def send_gif(
1829
1879
  self,
1830
1880
  chat_id: str,
@@ -1870,23 +1920,33 @@ class Robot:
1870
1920
  disable_notification,
1871
1921
  chat_keypad_type
1872
1922
  )
1873
- async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> Dict[str, Any]:
1874
- return await self._post("forwardMessage", {"from_chat_id": from_chat_id, "message_id": message_id, "to_chat_id": to_chat_id, "disable_notification": disable_notification})
1875
- async def edit_message_text(self, chat_id: str, message_id: str, text: str) -> Dict[str, Any]:
1876
- return await self._post("editMessageText", {"chat_id": chat_id, "message_id": message_id, "text": text})
1877
- async def edit_inline_keypad(self,chat_id: str,message_id: str,inline_keypad: Dict[str, Any],text: str = None) -> Dict[str, Any]:
1878
- if text is not None:await self._post("editMessageText", {"chat_id": chat_id,"message_id": message_id,"text": text})
1879
- return await self._post("editMessageKeypad", {"chat_id": chat_id,"message_id": message_id,"inline_keypad": inline_keypad})
1880
- async def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
1881
- return await self._post("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
1882
- async def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
1883
- return await self._post("setCommands", {"bot_commands": bot_commands})
1884
- async def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
1885
- return await self._post("updateBotEndpoints", {"url": url, "type": type})
1886
- async def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
1887
- return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "Removed"})
1888
- async def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
1889
- return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "New", "chat_keypad": chat_keypad})
1923
+ async def get_avatar_me(
1924
+ self,
1925
+ save_as: str = None) -> str:
1926
+ """
1927
+ Get the bot's profile photo URL and optionally save it locally.
1928
+ Args:
1929
+ save_as (str, optional): File name to save the avatar locally.
1930
+ Returns:
1931
+ str: Download URL of the bot's avatar, or "null" if not available.
1932
+ """
1933
+ try:
1934
+ me_info = await self.get_me()
1935
+ avatar = me_info.get('data', {}).get('bot', {}).get('avatar', {})
1936
+ file_id = avatar.get('file_id')
1937
+ if not file_id:
1938
+ return None
1939
+ url = await self.get_url_file(file_id)
1940
+ if save_as:
1941
+ async with aiohttp.ClientSession() as session:
1942
+ async with session.get(url) as resp:
1943
+ if resp.status == 200:
1944
+ content = await resp.read()
1945
+ with open(save_as, "wb") as f:
1946
+ f.write(content)
1947
+ return url
1948
+ except Exception:
1949
+ return None
1890
1950
  async def get_name(self, chat_id: str) -> str:
1891
1951
  try:
1892
1952
  chat = await self.get_chat(chat_id)
@@ -1904,4 +1964,99 @@ class Robot:
1904
1964
  except Exception:return "null"
1905
1965
  async def get_username(self, chat_id: str) -> str:
1906
1966
  chat_info = await self.get_chat(chat_id)
1907
- return chat_info.get("data", {}).get("chat", {}).get("username", "None")
1967
+ return chat_info.get("data", {}).get("chat", {}).get("username", "None")
1968
+ async def get_chat_admins(self, chat_id: str) -> Dict[str, Any]:
1969
+ return await self._post("getChatAdmins", {"chat_id": chat_id})
1970
+ async def get_chat_members(self, chat_id: str, start_id: str = "") -> Dict[str, Any]:
1971
+ return await self._post("getChatMembers", {"chat_id": chat_id, "start_id": start_id})
1972
+ async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
1973
+ return await self._post("getChatInfo", {"chat_id": chat_id})
1974
+ async def set_chat_title(self, chat_id: str, title: str) -> Dict[str, Any]:
1975
+ return await self._post("editChatTitle", {"chat_id": chat_id, "title": title})
1976
+ async def set_chat_description(self, chat_id: str, description: str) -> Dict[str, Any]:
1977
+ return await self._post("editChatDescription", {"chat_id": chat_id, "description": description})
1978
+ async def set_chat_photo(self, chat_id: str, file_id: str) -> Dict[str, Any]:
1979
+ return await self._post("editChatPhoto", {"chat_id": chat_id, "file_id": file_id})
1980
+ async def remove_chat_photo(self, chat_id: str) -> Dict[str, Any]:
1981
+ return await self._post("editChatPhoto", {"chat_id": chat_id, "file_id": "Removed"})
1982
+ async def add_member_chat(self, chat_id: str, user_ids: list[str]) -> Dict[str, Any]:
1983
+ return await self._post("addChatMembers", {"chat_id": chat_id, "member_ids": user_ids})
1984
+ async def ban_member_chat(self, chat_id: str, user_id: str) -> Dict[str, Any]:
1985
+ return await self._post("banChatMember", {"chat_id": chat_id, "member_id": user_id})
1986
+ async def unban_chat_member(self, chat_id: str, user_id: str) -> Dict[str, Any]:
1987
+ return await self._post("unbanChatMember", {"chat_id": chat_id, "member_id": user_id})
1988
+ async def restrict_chat_member(self, chat_id: str, user_id: str, until: int = 0) -> Dict[str, Any]:
1989
+ return await self._post("restrictChatMember", {"chat_id": chat_id, "member_id": user_id, "until_date": until})
1990
+ async def get_chat_member(self, chat_id: str, user_id: str):
1991
+ return await self._post("getChatMember", {"chat_id": chat_id, "user_id": user_id})
1992
+ async def get_admin_chat(self, chat_id: str):
1993
+ return await self._post("getChatAdministrators", {"chat_id": chat_id})
1994
+ async def get_chat_member_count(self, chat_id: str):
1995
+ return await self._post("getChatMemberCount", {"chat_id": chat_id})
1996
+ async def ban_chat_member(self, chat_id: str, user_id: str):
1997
+ return await self._post("banChatMember", {"chat_id": chat_id, "user_id": user_id})
1998
+ async def promote_chat_member(self, chat_id: str, user_id: str, rights: dict) -> Dict[str, Any]:
1999
+ return await self._post("promoteChatMember", {"chat_id": chat_id, "member_id": user_id, "rights": rights})
2000
+ async def demote_chat_member(self, chat_id: str, user_id: str) -> Dict[str, Any]:
2001
+ return await self._post("promoteChatMember", {"chat_id": chat_id, "member_id": user_id, "rights": {}})
2002
+ async def pin_chat_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
2003
+ return await self._post("pinChatMessage", {"chat_id": chat_id, "message_id": message_id})
2004
+ async def unpin_chat_message(self, chat_id: str, message_id: str = "") -> Dict[str, Any]:
2005
+ return await self._post("unpinChatMessage", {"chat_id": chat_id, "message_id": message_id})
2006
+ async def export_chat_invite_link(self, chat_id: str) -> Dict[str, Any]:
2007
+ return await self._post("exportChatInviteLink", {"chat_id": chat_id})
2008
+ async def revoke_chat_invite_link(self, chat_id: str, link: str) -> Dict[str, Any]:
2009
+ return await self._post("revokeChatInviteLink", {"chat_id": chat_id, "invite_link": link})
2010
+ async def create_group(self, title: str, user_ids: list[str]) -> Dict[str, Any]:
2011
+ return await self._post("createGroup", {"title": title, "user_ids": user_ids})
2012
+ async def create_channel(self, title: str, description: str = "") -> Dict[str, Any]:
2013
+ return await self._post("createChannel", {"title": title, "description": description})
2014
+ async def leave_chat(self, chat_id: str) -> Dict[str, Any]:
2015
+ return await self._post("leaveChat", {"chat_id": chat_id})
2016
+ async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> Dict[str, Any]:
2017
+ return await self._post("forwardMessage", {"from_chat_id": from_chat_id, "message_id": message_id, "to_chat_id": to_chat_id, "disable_notification": disable_notification})
2018
+ async def edit_message_text(self, chat_id: str, message_id: str, text: str) -> Dict[str, Any]:
2019
+ return await self._post("editMessageText", {"chat_id": chat_id, "message_id": message_id, "text": text})
2020
+ async def edit_inline_keypad(self,chat_id: str,message_id: str,inline_keypad: Dict[str, Any],text: str = None) -> Dict[str, Any]:
2021
+ if text is not None:await self._post("editMessageText", {"chat_id": chat_id,"message_id": message_id,"text": text})
2022
+ return await self._post("editMessageKeypad", {"chat_id": chat_id,"message_id": message_id,"inline_keypad": inline_keypad})
2023
+ async def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
2024
+ return await self._post("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
2025
+ async def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
2026
+ return await self._post("setCommands", {"bot_commands": bot_commands})
2027
+ async def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
2028
+ return await self._post("updateBotEndpoints", {"url": url, "type": type})
2029
+ async def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
2030
+ return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "Removed"})
2031
+ async def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
2032
+ return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "New", "chat_keypad": chat_keypad})
2033
+ async def send_contact(self, chat_id: str, first_name: str, last_name: str, phone_number: str) -> Dict[str, Any]:
2034
+ return await self._post("sendContact", {"chat_id": chat_id, "first_name": first_name, "last_name": last_name, "phone_number": phone_number})
2035
+ async def get_chat(self, chat_id: str) -> Dict[str, Any]:
2036
+ return await self._post("getChat", {"chat_id": chat_id})
2037
+ 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]:
2038
+ 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)
2039
+ 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]:
2040
+ 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)
2041
+ 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]:
2042
+ 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)
2043
+ def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
2044
+ client = self._get_client()
2045
+ return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
2046
+ async def send_poll(self, chat_id: str, question: str, options: List[str]) -> Dict[str, Any]:
2047
+ return await self._post("sendPoll", {"chat_id": chat_id, "question": question, "options": options})
2048
+ async def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
2049
+ client = self._get_client()
2050
+ if chat_id:
2051
+ chat_info_data = await self.get_chat(chat_id)
2052
+ chat_info = chat_info_data.get('data', {}).get('chat', {})
2053
+ username = chat_info.get('username')
2054
+ user_id = chat_info.get('user_id')
2055
+ if username:
2056
+ result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
2057
+ members = result.get('in_chat_members', [])
2058
+ return any(m.get('username') == username for m in members)
2059
+ elif user_id:
2060
+ member_guids = await asyncio.to_thread(client.get_all_members, channel_guid, just_get_guids=True)
2061
+ return user_id in member_guids
2062
+ return False
rubka/context.py CHANGED
@@ -508,20 +508,14 @@ class InlineMessage:
508
508
  self.file = File(self.raw_data["file"]) if "file" in self.raw_data else None
509
509
  self.sticker = Sticker(self.raw_data["sticker"]) if "sticker" in self.raw_data else None
510
510
  self.contact_message = ContactMessage(self.raw_data["contact_message"]) if "contact_message" in self.raw_data else None
511
- self.poll = Poll(self.raw_data["poll"]) if "poll" in self.raw_data else None
512
- self.location = Location(self.raw_data["location"]) if "location" in self.raw_data else None
513
- self.live_location = LiveLocation(self.raw_data["live_location"]) if "live_location" in self.raw_data else None
514
511
  self.aux_data = AuxData(self.raw_data["aux_data"]) if "aux_data" in self.raw_data else None
515
512
  self.is_reply = self.reply_to_message_id is not None
516
- self.has_media = any([self.file, self.sticker, self.poll, self.location, self.live_location])
513
+ self.has_media = any([self.file, self.sticker])
517
514
  self.is_forwarded = self.forwarded_from is not None
518
515
  self.is_text = bool(self.text and not self.has_media)
519
516
  self.is_media = self.has_media
520
- self.is_poll = self.poll is not None
521
- self.is_location = self.location is not None
522
- self.is_live_location = self.live_location is not None
523
517
  self.is_contact = self.contact_message is not None
524
- self.has_any_media = any([self.file, self.sticker, self.poll, self.location, self.live_location])
518
+ self.has_any_media = any([self.file, self.sticker,])
525
519
  self.edited_text = self.raw_data.get("edited_text") if self.is_edited else None
526
520
  if self.file and self.file.file_name:
527
521
  name = self.file.file_name.lower()
@@ -535,10 +529,15 @@ class InlineMessage:
535
529
  self.is_font = name.endswith((".ttf", ".otf", ".woff", ".woff2"))
536
530
 
537
531
 
532
+ @property
533
+ def session(self):
534
+ if self.chat_id not in self.bot.sessions:
535
+ self.bot.sessions[self.chat_id] = {}
536
+ return self.bot.sessions[self.chat_id]
538
537
  def reply(self, text: str, **kwargs):
539
538
  return self.bot.send_message(
540
- chat_id=self.chat_id,
541
- text=text,
539
+ self.chat_id,
540
+ text,
542
541
  reply_to_message_id=self.message_id,
543
542
  **kwargs
544
543
  )
@@ -550,14 +549,212 @@ class InlineMessage:
550
549
  **kwargs
551
550
  )
552
551
 
553
- def edit(self, new_text: str):
552
+ def reply_poll(self, question: str, options: List[str], **kwargs) -> Dict[str, Any]:
553
+ return self.bot._post("sendPoll", {
554
+ "chat_id": self.chat_id,
555
+ "question": question,
556
+ "options": options,
557
+ "reply_to_message_id": self.message_id,
558
+ **kwargs
559
+ })
560
+
561
+
562
+ def reply_document(
563
+ self,
564
+ path: Optional[Union[str, Path]] = None,
565
+ file_id: Optional[str] = None,
566
+ text: Optional[str] = None,
567
+ chat_keypad: Optional[Dict[str, Any]] = None,
568
+ inline_keypad: Optional[Dict[str, Any]] = None,
569
+ chat_keypad_type: Optional[str] = "None",
570
+ disable_notification: bool = False
571
+ ):
572
+ if chat_keypad and chat_keypad_type == "none":chat_keypad_type == "New"
573
+ return self.bot.send_document(
574
+ chat_id=self.chat_id,
575
+ path=path,
576
+ file_id=file_id,
577
+ text=text,
578
+ chat_keypad=chat_keypad,
579
+ inline_keypad=inline_keypad,
580
+ chat_keypad_type=chat_keypad_type,
581
+ disable_notification=disable_notification,
582
+ reply_to_message_id=self.message_id
583
+ )
584
+ def reply_file(
585
+ self,
586
+ path: Optional[Union[str, Path]] = None,
587
+ file_id: Optional[str] = None,
588
+ text: Optional[str] = None,
589
+ chat_keypad: Optional[Dict[str, Any]] = None,
590
+ inline_keypad: Optional[Dict[str, Any]] = None,
591
+ chat_keypad_type: Optional[str] = "None",
592
+ disable_notification: bool = False
593
+ ):
594
+ if chat_keypad and chat_keypad_type == "none":
595
+ chat_keypad_type == "New"
596
+
597
+ return self.bot.send_document(
598
+ chat_id=self.chat_id,
599
+ path=path,
600
+ file_id=file_id,
601
+ text=text,
602
+ chat_keypad=chat_keypad,
603
+ inline_keypad=inline_keypad,
604
+ chat_keypad_type=chat_keypad_type,
605
+ disable_notification=disable_notification,
606
+ reply_to_message_id=self.message_id
607
+ )
608
+
609
+ def reply_image(
610
+ self,
611
+ path: Optional[Union[str, Path]] = None,
612
+ file_id: Optional[str] = None,
613
+ text: Optional[str] = None,
614
+ chat_keypad: Optional[Dict[str, Any]] = None,
615
+ inline_keypad: Optional[Dict[str, Any]] = None,
616
+ chat_keypad_type: Optional[str] = "None",
617
+ disable_notification: bool = False
618
+ ):
619
+ if chat_keypad and chat_keypad_type == "none":
620
+ chat_keypad_type == "New"
621
+ return self.bot.send_image(
622
+ chat_id=self.chat_id,
623
+ path=path,
624
+ file_id=file_id,
625
+ text=text,
626
+ chat_keypad=chat_keypad,
627
+ inline_keypad=inline_keypad,
628
+ chat_keypad_type=chat_keypad_type,
629
+ disable_notification=disable_notification,
630
+ reply_to_message_id=self.message_id
631
+ )
632
+
633
+ def reply_music(
634
+ self,
635
+ path: Optional[Union[str, Path]] = None,
636
+ file_id: Optional[str] = None,
637
+ text: Optional[str] = None,
638
+ chat_keypad: Optional[Dict[str, Any]] = None,
639
+ inline_keypad: Optional[Dict[str, Any]] = None,
640
+ chat_keypad_type: Optional[str] = "None",
641
+ disable_notification: bool = False
642
+ ):
643
+ if chat_keypad and chat_keypad_type == "none":
644
+ chat_keypad_type == "New"
645
+ return self.bot.send_music(
646
+ chat_id=self.chat_id,
647
+ path=path,
648
+ file_id=file_id,
649
+ text=text,
650
+ chat_keypad=chat_keypad,
651
+ inline_keypad=inline_keypad,
652
+ chat_keypad_type=chat_keypad_type,
653
+ disable_notification=disable_notification,
654
+ reply_to_message_id=self.message_id
655
+ )
656
+
657
+ def reply_voice(
658
+ self,
659
+ path: Optional[Union[str, Path]] = None,
660
+ file_id: Optional[str] = None,
661
+ text: Optional[str] = None,
662
+ chat_keypad: Optional[Dict[str, Any]] = None,
663
+ inline_keypad: Optional[Dict[str, Any]] = None,
664
+ chat_keypad_type: Optional[str] = "None",
665
+ disable_notification: bool = False
666
+ ):
667
+ if chat_keypad and chat_keypad_type == "none":
668
+ chat_keypad_type == "New"
669
+ return self.bot.send_voice(
670
+ chat_id=self.chat_id,
671
+ path=path,
672
+ file_id=file_id,
673
+ text=text,
674
+ chat_keypad=chat_keypad,
675
+ inline_keypad=inline_keypad,
676
+ chat_keypad_type=chat_keypad_type,
677
+ disable_notification=disable_notification,
678
+ reply_to_message_id=self.message_id
679
+ )
680
+
681
+ def reply_gif(
682
+ self,
683
+ path: Optional[Union[str, Path]] = None,
684
+ file_id: Optional[str] = None,
685
+ text: Optional[str] = None,
686
+ chat_keypad: Optional[Dict[str, Any]] = None,
687
+ inline_keypad: Optional[Dict[str, Any]] = None,
688
+ chat_keypad_type: Optional[str] = "None",
689
+ disable_notification: bool = False
690
+ ):
691
+ if chat_keypad and chat_keypad_type == "none":chat_keypad_type == "New"
692
+ return self.bot.send_gif(
693
+ chat_id=self.chat_id,
694
+ path=path,
695
+ file_id=file_id,
696
+ text=text,
697
+ chat_keypad=chat_keypad,
698
+ inline_keypad=inline_keypad,
699
+ chat_keypad_type=chat_keypad_type,
700
+ disable_notification=disable_notification,
701
+ reply_to_message_id=self.message_id
702
+ )
703
+
704
+ def reply_location(self, latitude: str, longitude: str, **kwargs) -> Dict[str, Any]:
705
+ return self.bot.send_location(
706
+ chat_id=self.chat_id,
707
+ latitude=latitude,
708
+ longitude=longitude,
709
+ reply_to_message_id=self.message_id,
710
+ **kwargs
711
+ )
712
+
713
+ def reply_contact(self, first_name: str, last_name: str, phone_number: str, **kwargs) -> Dict[str, Any]:
714
+ return self.bot.send_contact(
715
+ chat_id=self.chat_id,
716
+ first_name=first_name,
717
+ last_name=last_name,
718
+ phone_number=phone_number,
719
+ reply_to_message_id=self.message_id,
720
+ **kwargs
721
+ )
722
+
723
+ def reply_keypad(self, text: str, keypad: Dict[str, Any], **kwargs) -> Dict[str, Any]:
724
+ return self.bot.send_message(
725
+ chat_id=self.chat_id,
726
+ text=text,
727
+ chat_keypad_type="New",
728
+ chat_keypad=keypad,
729
+ reply_to_message_id=self.message_id,
730
+ **kwargs
731
+ )
732
+
733
+ def reply_inline(self, text: str, inline_keypad: Dict[str, Any], **kwargs) -> Dict[str, Any]:
734
+ return self.bot.send_message(
735
+ chat_id=self.chat_id,
736
+ text=text,
737
+ inline_keypad=inline_keypad,
738
+ reply_to_message_id=self.message_id,
739
+ **kwargs
740
+ )
741
+
742
+ def reply_sticker(self, sticker_id: str, **kwargs) -> Dict[str, Any]:
743
+ return self.bot._post("sendSticker", {
744
+ "chat_id": self.chat_id,
745
+ "sticker_id": sticker_id,
746
+ "reply_to_message_id": self.message_id,
747
+ **kwargs
748
+ })
749
+
750
+ def edit(self, new_text: str) -> Dict[str, Any]:
554
751
  return self.bot.edit_message_text(
555
752
  chat_id=self.chat_id,
556
753
  message_id=self.message_id,
557
754
  text=new_text
558
755
  )
559
756
 
560
- def delete(self):
757
+ def delete(self) -> Dict[str, Any]:
561
758
  return self.bot.delete_message(
562
759
  chat_id=self.chat_id,
563
760
  message_id=self.message_id
rubka/exceptions.py CHANGED
@@ -1,3 +1,37 @@
1
1
  class APIRequestError(Exception):
2
- """Raised when an API request fails."""
2
+ """Base class for all API request errors."""
3
+ def __init__(self, status: str, dev_message: str = None):
4
+ self.status = status
5
+ self.dev_message = dev_message
6
+ msg = f"{self.__class__.__name__}: {status}"
7
+ if dev_message:
8
+ msg += f" | Message : {dev_message}"
9
+ super().__init__(msg)
10
+
11
+
12
+ class InvalidAccessError(APIRequestError):
13
+ """Raised when access is invalid."""
14
+
15
+ class InvalidTokenError(Exception):
3
16
  pass
17
+
18
+ class InvalidInputError(APIRequestError):
19
+ """Raised when input is invalid."""
20
+
21
+
22
+ class TooRequestError(APIRequestError):
23
+ """Raised when too many requests are made."""
24
+
25
+
26
+ def raise_for_status(response: dict):
27
+ status = response.get("status")
28
+ dev_message = response.get("dev_message")
29
+
30
+ if status == "INVALID_ACCESS":
31
+ raise InvalidAccessError(status, dev_message)
32
+ elif status == "INVALID_INPUT":
33
+ raise InvalidInputError(status, dev_message)
34
+ elif status == "TOO_REQUEST":
35
+ raise TooRequestError(status, dev_message)
36
+ elif status != "OK":
37
+ raise APIRequestError(status, dev_message)
rubka/update.py CHANGED
@@ -508,20 +508,14 @@ class InlineMessage:
508
508
  self.file = File(self.raw_data["file"]) if "file" in self.raw_data else None
509
509
  self.sticker = Sticker(self.raw_data["sticker"]) if "sticker" in self.raw_data else None
510
510
  self.contact_message = ContactMessage(self.raw_data["contact_message"]) if "contact_message" in self.raw_data else None
511
- self.poll = Poll(self.raw_data["poll"]) if "poll" in self.raw_data else None
512
- self.location = Location(self.raw_data["location"]) if "location" in self.raw_data else None
513
- self.live_location = LiveLocation(self.raw_data["live_location"]) if "live_location" in self.raw_data else None
514
511
  self.aux_data = AuxData(self.raw_data["aux_data"]) if "aux_data" in self.raw_data else None
515
512
  self.is_reply = self.reply_to_message_id is not None
516
- self.has_media = any([self.file, self.sticker, self.poll, self.location, self.live_location])
513
+ self.has_media = any([self.file, self.sticker])
517
514
  self.is_forwarded = self.forwarded_from is not None
518
515
  self.is_text = bool(self.text and not self.has_media)
519
516
  self.is_media = self.has_media
520
- self.is_poll = self.poll is not None
521
- self.is_location = self.location is not None
522
- self.is_live_location = self.live_location is not None
523
517
  self.is_contact = self.contact_message is not None
524
- self.has_any_media = any([self.file, self.sticker, self.poll, self.location, self.live_location])
518
+ self.has_any_media = any([self.file, self.sticker,])
525
519
  self.edited_text = self.raw_data.get("edited_text") if self.is_edited else None
526
520
  if self.file and self.file.file_name:
527
521
  name = self.file.file_name.lower()
@@ -535,10 +529,15 @@ class InlineMessage:
535
529
  self.is_font = name.endswith((".ttf", ".otf", ".woff", ".woff2"))
536
530
 
537
531
 
532
+ @property
533
+ def session(self):
534
+ if self.chat_id not in self.bot.sessions:
535
+ self.bot.sessions[self.chat_id] = {}
536
+ return self.bot.sessions[self.chat_id]
538
537
  def reply(self, text: str, **kwargs):
539
538
  return self.bot.send_message(
540
- chat_id=self.chat_id,
541
- text=text,
539
+ self.chat_id,
540
+ text,
542
541
  reply_to_message_id=self.message_id,
543
542
  **kwargs
544
543
  )
@@ -550,14 +549,212 @@ class InlineMessage:
550
549
  **kwargs
551
550
  )
552
551
 
553
- def edit(self, new_text: str):
552
+ def reply_poll(self, question: str, options: List[str], **kwargs) -> Dict[str, Any]:
553
+ return self.bot._post("sendPoll", {
554
+ "chat_id": self.chat_id,
555
+ "question": question,
556
+ "options": options,
557
+ "reply_to_message_id": self.message_id,
558
+ **kwargs
559
+ })
560
+
561
+
562
+ def reply_document(
563
+ self,
564
+ path: Optional[Union[str, Path]] = None,
565
+ file_id: Optional[str] = None,
566
+ text: Optional[str] = None,
567
+ chat_keypad: Optional[Dict[str, Any]] = None,
568
+ inline_keypad: Optional[Dict[str, Any]] = None,
569
+ chat_keypad_type: Optional[str] = "None",
570
+ disable_notification: bool = False
571
+ ):
572
+ if chat_keypad and chat_keypad_type == "none":chat_keypad_type == "New"
573
+ return self.bot.send_document(
574
+ chat_id=self.chat_id,
575
+ path=path,
576
+ file_id=file_id,
577
+ text=text,
578
+ chat_keypad=chat_keypad,
579
+ inline_keypad=inline_keypad,
580
+ chat_keypad_type=chat_keypad_type,
581
+ disable_notification=disable_notification,
582
+ reply_to_message_id=self.message_id
583
+ )
584
+ def reply_file(
585
+ self,
586
+ path: Optional[Union[str, Path]] = None,
587
+ file_id: Optional[str] = None,
588
+ text: Optional[str] = None,
589
+ chat_keypad: Optional[Dict[str, Any]] = None,
590
+ inline_keypad: Optional[Dict[str, Any]] = None,
591
+ chat_keypad_type: Optional[str] = "None",
592
+ disable_notification: bool = False
593
+ ):
594
+ if chat_keypad and chat_keypad_type == "none":
595
+ chat_keypad_type == "New"
596
+
597
+ return self.bot.send_document(
598
+ chat_id=self.chat_id,
599
+ path=path,
600
+ file_id=file_id,
601
+ text=text,
602
+ chat_keypad=chat_keypad,
603
+ inline_keypad=inline_keypad,
604
+ chat_keypad_type=chat_keypad_type,
605
+ disable_notification=disable_notification,
606
+ reply_to_message_id=self.message_id
607
+ )
608
+
609
+ def reply_image(
610
+ self,
611
+ path: Optional[Union[str, Path]] = None,
612
+ file_id: Optional[str] = None,
613
+ text: Optional[str] = None,
614
+ chat_keypad: Optional[Dict[str, Any]] = None,
615
+ inline_keypad: Optional[Dict[str, Any]] = None,
616
+ chat_keypad_type: Optional[str] = "None",
617
+ disable_notification: bool = False
618
+ ):
619
+ if chat_keypad and chat_keypad_type == "none":
620
+ chat_keypad_type == "New"
621
+ return self.bot.send_image(
622
+ chat_id=self.chat_id,
623
+ path=path,
624
+ file_id=file_id,
625
+ text=text,
626
+ chat_keypad=chat_keypad,
627
+ inline_keypad=inline_keypad,
628
+ chat_keypad_type=chat_keypad_type,
629
+ disable_notification=disable_notification,
630
+ reply_to_message_id=self.message_id
631
+ )
632
+
633
+ def reply_music(
634
+ self,
635
+ path: Optional[Union[str, Path]] = None,
636
+ file_id: Optional[str] = None,
637
+ text: Optional[str] = None,
638
+ chat_keypad: Optional[Dict[str, Any]] = None,
639
+ inline_keypad: Optional[Dict[str, Any]] = None,
640
+ chat_keypad_type: Optional[str] = "None",
641
+ disable_notification: bool = False
642
+ ):
643
+ if chat_keypad and chat_keypad_type == "none":
644
+ chat_keypad_type == "New"
645
+ return self.bot.send_music(
646
+ chat_id=self.chat_id,
647
+ path=path,
648
+ file_id=file_id,
649
+ text=text,
650
+ chat_keypad=chat_keypad,
651
+ inline_keypad=inline_keypad,
652
+ chat_keypad_type=chat_keypad_type,
653
+ disable_notification=disable_notification,
654
+ reply_to_message_id=self.message_id
655
+ )
656
+
657
+ def reply_voice(
658
+ self,
659
+ path: Optional[Union[str, Path]] = None,
660
+ file_id: Optional[str] = None,
661
+ text: Optional[str] = None,
662
+ chat_keypad: Optional[Dict[str, Any]] = None,
663
+ inline_keypad: Optional[Dict[str, Any]] = None,
664
+ chat_keypad_type: Optional[str] = "None",
665
+ disable_notification: bool = False
666
+ ):
667
+ if chat_keypad and chat_keypad_type == "none":
668
+ chat_keypad_type == "New"
669
+ return self.bot.send_voice(
670
+ chat_id=self.chat_id,
671
+ path=path,
672
+ file_id=file_id,
673
+ text=text,
674
+ chat_keypad=chat_keypad,
675
+ inline_keypad=inline_keypad,
676
+ chat_keypad_type=chat_keypad_type,
677
+ disable_notification=disable_notification,
678
+ reply_to_message_id=self.message_id
679
+ )
680
+
681
+ def reply_gif(
682
+ self,
683
+ path: Optional[Union[str, Path]] = None,
684
+ file_id: Optional[str] = None,
685
+ text: Optional[str] = None,
686
+ chat_keypad: Optional[Dict[str, Any]] = None,
687
+ inline_keypad: Optional[Dict[str, Any]] = None,
688
+ chat_keypad_type: Optional[str] = "None",
689
+ disable_notification: bool = False
690
+ ):
691
+ if chat_keypad and chat_keypad_type == "none":chat_keypad_type == "New"
692
+ return self.bot.send_gif(
693
+ chat_id=self.chat_id,
694
+ path=path,
695
+ file_id=file_id,
696
+ text=text,
697
+ chat_keypad=chat_keypad,
698
+ inline_keypad=inline_keypad,
699
+ chat_keypad_type=chat_keypad_type,
700
+ disable_notification=disable_notification,
701
+ reply_to_message_id=self.message_id
702
+ )
703
+
704
+ def reply_location(self, latitude: str, longitude: str, **kwargs) -> Dict[str, Any]:
705
+ return self.bot.send_location(
706
+ chat_id=self.chat_id,
707
+ latitude=latitude,
708
+ longitude=longitude,
709
+ reply_to_message_id=self.message_id,
710
+ **kwargs
711
+ )
712
+
713
+ def reply_contact(self, first_name: str, last_name: str, phone_number: str, **kwargs) -> Dict[str, Any]:
714
+ return self.bot.send_contact(
715
+ chat_id=self.chat_id,
716
+ first_name=first_name,
717
+ last_name=last_name,
718
+ phone_number=phone_number,
719
+ reply_to_message_id=self.message_id,
720
+ **kwargs
721
+ )
722
+
723
+ def reply_keypad(self, text: str, keypad: Dict[str, Any], **kwargs) -> Dict[str, Any]:
724
+ return self.bot.send_message(
725
+ chat_id=self.chat_id,
726
+ text=text,
727
+ chat_keypad_type="New",
728
+ chat_keypad=keypad,
729
+ reply_to_message_id=self.message_id,
730
+ **kwargs
731
+ )
732
+
733
+ def reply_inline(self, text: str, inline_keypad: Dict[str, Any], **kwargs) -> Dict[str, Any]:
734
+ return self.bot.send_message(
735
+ chat_id=self.chat_id,
736
+ text=text,
737
+ inline_keypad=inline_keypad,
738
+ reply_to_message_id=self.message_id,
739
+ **kwargs
740
+ )
741
+
742
+ def reply_sticker(self, sticker_id: str, **kwargs) -> Dict[str, Any]:
743
+ return self.bot._post("sendSticker", {
744
+ "chat_id": self.chat_id,
745
+ "sticker_id": sticker_id,
746
+ "reply_to_message_id": self.message_id,
747
+ **kwargs
748
+ })
749
+
750
+ def edit(self, new_text: str) -> Dict[str, Any]:
554
751
  return self.bot.edit_message_text(
555
752
  chat_id=self.chat_id,
556
753
  message_id=self.message_id,
557
754
  text=new_text
558
755
  )
559
756
 
560
- def delete(self):
757
+ def delete(self) -> Dict[str, Any]:
561
758
  return self.bot.delete_message(
562
759
  chat_id=self.chat_id,
563
760
  message_id=self.message_id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 6.6.12
3
+ Version: 6.7.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
@@ -1,11 +1,11 @@
1
1
  rubka/__init__.py,sha256=P6IBiORfp-GqKHe5LZ-5lldWyG7tnrUYUcAQDUgwXmY,1973
2
2
  rubka/api.py,sha256=1S8JIcYN_Zxb7JT73eQl9me-qDDDTDzcSk6_qawAfkA,68861
3
- rubka/asynco.py,sha256=7aRjRfI9ORxtQcjmEs7_ETwptN4ONGaEVz-7bmKgbqI,90042
3
+ rubka/asynco.py,sha256=QRieOUl4w81zP-a2FtbLmI9DuExy9d6m1jDR4RUhuo4,100228
4
4
  rubka/button.py,sha256=vU9OvWXCD4MRrTJ8Xmivd4L471-06zrD2qpZBTw5vjY,13305
5
5
  rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
6
- rubka/context.py,sha256=OcYY0G0IFQa29zpw6YRm873RwJOf568qxJplm1UdeAM,23936
6
+ rubka/context.py,sha256=kYLsGq4cQhmMX16WrzH_kxYzGGyvulZyxazoN_5tul4,30768
7
7
  rubka/decorators.py,sha256=hGwUoE4q2ImrunJIGJ_kzGYYxQf1ueE0isadqraKEts,1157
8
- rubka/exceptions.py,sha256=tujZt1XrhWaw-lmdeVadVceUptpw4XzNgE44sAAY0gs,90
8
+ rubka/exceptions.py,sha256=DDOGIHEMoliHNW5E7C_s38WZgqqMBv9812fcJGvj7TY,1173
9
9
  rubka/filters.py,sha256=DY1bdkpRKIiLtVcy6X3hOnlGPcVOK4HFb3QgmaPx6Oo,12116
10
10
  rubka/helpers.py,sha256=QvK5lg4QDmycImxJia4m8HDpfacYzbKKZiOk536mafc,65161
11
11
  rubka/jobs.py,sha256=GvLMLsVhcSEzRTgkvnPISPEBN71suW2xXI0hUaUZPTo,378
@@ -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=OcYY0G0IFQa29zpw6YRm873RwJOf568qxJplm1UdeAM,23936
17
+ rubka/update.py,sha256=kYLsGq4cQhmMX16WrzH_kxYzGGyvulZyxazoN_5tul4,30768
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.6.12.dist-info/METADATA,sha256=XXvtNm6Q_G1rE8UMYIgGOrhY166X4rnp85URJ-r6lLc,34270
41
- rubka-6.6.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
- rubka-6.6.12.dist-info/entry_points.txt,sha256=4aESuUmuUOALMUy7Kucv_Gb5YlqhsJmTmdXLlZU9sJ0,46
43
- rubka-6.6.12.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
44
- rubka-6.6.12.dist-info/RECORD,,
40
+ rubka-6.7.1.dist-info/METADATA,sha256=EWh4ebJjsnf3NgzY03qEjhZbuQhQ54GfljbLr7WNTnM,34269
41
+ rubka-6.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
42
+ rubka-6.7.1.dist-info/entry_points.txt,sha256=4aESuUmuUOALMUy7Kucv_Gb5YlqhsJmTmdXLlZU9sJ0,46
43
+ rubka-6.7.1.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
44
+ rubka-6.7.1.dist-info/RECORD,,
File without changes