Rubka 6.7.11__tar.gz → 7.1.1__tar.gz
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-6.7.11 → rubka-7.1.1}/PKG-INFO +2 -1
- {rubka-6.7.11 → rubka-7.1.1}/Rubka.egg-info/PKG-INFO +2 -1
- {rubka-6.7.11 → rubka-7.1.1}/Rubka.egg-info/requires.txt +1 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/api.py +2 -1
- {rubka-6.7.11 → rubka-7.1.1}/rubka/asynco.py +243 -48
- {rubka-6.7.11 → rubka-7.1.1}/rubka/context.py +95 -12
- {rubka-6.7.11 → rubka-7.1.1}/rubka/update.py +95 -12
- {rubka-6.7.11 → rubka-7.1.1}/setup.py +2 -1
- {rubka-6.7.11 → rubka-7.1.1}/README.md +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/Rubka.egg-info/SOURCES.txt +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/Rubka.egg-info/dependency_links.txt +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/Rubka.egg-info/entry_points.txt +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/Rubka.egg-info/not-zip-safe +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/Rubka.egg-info/top_level.txt +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/client/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/client/client.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/crypto/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/crypto/crypto.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/enums.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/exceptions.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/methods/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/methods/methods.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/network/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/network/helper.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/network/network.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/network/socket.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/sessions/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/sessions/sessions.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/types/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/types/socket/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/types/socket/message.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/utils/__init__.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/utils/configs.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/adaptorrubka/utils/utils.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/button.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/config.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/decorators.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/exceptions.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/filters.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/helpers.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/jobs.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/keyboards.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/keypad.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/logger.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/rubino.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/tv.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/rubka/utils.py +0 -0
- {rubka-6.7.11 → rubka-7.1.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version:
|
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version:
|
|
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
|
|
@@ -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.
|
|
@@ -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 =
|
|
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)
|
|
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.
|
|
184
|
-
await self.
|
|
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))#
|
|
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.
|
|
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
|
-
|
|
1377
|
-
|
|
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
|
-
|
|
1711
|
+
state = await self._post("sendMessage", payload)
|
|
1523
1712
|
except Exception:
|
|
1524
1713
|
payload.pop("reply_to_message_id", None)
|
|
1525
|
-
|
|
1714
|
+
state = await self._post("sendMessage", payload)
|
|
1526
1715
|
else:
|
|
1527
|
-
|
|
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
|
-
|
|
1921
|
-
self,
|
|
1922
|
-
|
|
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
|
|
1936
|
-
|
|
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
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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,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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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")
|
|
@@ -13,7 +13,7 @@ with open("README.md", "r", encoding="utf-8") as f:
|
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name='Rubka',
|
|
16
|
-
version='
|
|
16
|
+
version='7.1.1',
|
|
17
17
|
description='rubika A Python library for interacting with Rubika Bot API.',
|
|
18
18
|
long_description=long_description,
|
|
19
19
|
long_description_content_type='text/markdown',
|
|
@@ -45,6 +45,7 @@ setup(
|
|
|
45
45
|
"websocket-client",
|
|
46
46
|
'pycryptodome',
|
|
47
47
|
'aiohttp',
|
|
48
|
+
'httpx',
|
|
48
49
|
'tqdm',
|
|
49
50
|
'mutagen',
|
|
50
51
|
'filetype',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|