Rubka 4.4.20__py3-none-any.whl → 4.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rubka/api.py +116 -52
- rubka/asynco.py +563 -0
- {rubka-4.4.20.dist-info → rubka-4.5.2.dist-info}/METADATA +1 -1
- {rubka-4.4.20.dist-info → rubka-4.5.2.dist-info}/RECORD +6 -5
- {rubka-4.4.20.dist-info → rubka-4.5.2.dist-info}/WHEEL +0 -0
- {rubka-4.4.20.dist-info → rubka-4.5.2.dist-info}/top_level.txt +0 -0
rubka/api.py
CHANGED
|
@@ -7,13 +7,14 @@ from typing import Callable
|
|
|
7
7
|
from .context import Message,InlineMessage
|
|
8
8
|
from typing import Optional, Union, Literal, Dict, Any
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
import requests
|
|
11
10
|
import time
|
|
12
11
|
import datetime
|
|
12
|
+
import tempfile
|
|
13
|
+
from tqdm import tqdm
|
|
14
|
+
import os
|
|
13
15
|
API_URL = "https://botapi.rubika.ir/v3"
|
|
14
16
|
import sys
|
|
15
17
|
import subprocess
|
|
16
|
-
import requests
|
|
17
18
|
def install_package(package_name):
|
|
18
19
|
try:
|
|
19
20
|
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
@@ -78,7 +79,7 @@ class Robot:
|
|
|
78
79
|
Initialized with bot token.
|
|
79
80
|
"""
|
|
80
81
|
|
|
81
|
-
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):
|
|
82
|
+
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):
|
|
82
83
|
"""
|
|
83
84
|
راهاندازی اولیه ربات روبیکا و پیکربندی پارامترهای پایه.
|
|
84
85
|
|
|
@@ -103,6 +104,7 @@ class Robot:
|
|
|
103
104
|
self._inline_query_handlers: List[dict] = []
|
|
104
105
|
self.timeout = timeout
|
|
105
106
|
self.auth = auth
|
|
107
|
+
self.show_progress = show_progress
|
|
106
108
|
self.session_name = session_name
|
|
107
109
|
self.Key = Key
|
|
108
110
|
self.platform = platform
|
|
@@ -113,6 +115,7 @@ class Robot:
|
|
|
113
115
|
self._callback_handler = None
|
|
114
116
|
self._message_handler = None
|
|
115
117
|
self._inline_query_handler = None
|
|
118
|
+
self._message_handlers: List[dict] = []
|
|
116
119
|
self._callback_handlers = None
|
|
117
120
|
self._callback_handlers = [] # ✅ این خط مهمه
|
|
118
121
|
if web_hook:
|
|
@@ -159,11 +162,11 @@ class Robot:
|
|
|
159
162
|
return self._post("getMe", {})
|
|
160
163
|
def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
161
164
|
def decorator(func: Callable[[Any, Message], None]):
|
|
162
|
-
self.
|
|
165
|
+
self._message_handlers.append({
|
|
163
166
|
"func": func,
|
|
164
167
|
"filters": filters,
|
|
165
168
|
"commands": commands
|
|
166
|
-
}
|
|
169
|
+
})
|
|
167
170
|
return func
|
|
168
171
|
return decorator
|
|
169
172
|
|
|
@@ -198,64 +201,58 @@ class Robot:
|
|
|
198
201
|
|
|
199
202
|
|
|
200
203
|
def _process_update(self, update: dict):
|
|
201
|
-
import threading
|
|
202
|
-
|
|
203
|
-
# هندل پیام inline (معمولاً type = ReceiveQuery)
|
|
204
|
+
import threading
|
|
205
|
+
# هندل پیام inline (بدون تغییر)
|
|
204
206
|
if update.get("type") == "ReceiveQuery":
|
|
205
207
|
msg = update.get("inline_message", {})
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
threading.Thread(target=self._handle_inline_query, args=(context,), daemon=True).start()
|
|
210
|
-
return
|
|
208
|
+
context = InlineMessage(bot=self, raw_data=msg)
|
|
209
|
+
threading.Thread(target=self._handle_inline_query, args=(context,), daemon=True).start()
|
|
210
|
+
return
|
|
211
211
|
|
|
212
212
|
# هندل پیام جدید متنی
|
|
213
213
|
if update.get("type") == "NewMessage":
|
|
214
214
|
msg = update.get("new_message", {})
|
|
215
|
-
chat_id = update.get("chat_id")
|
|
216
|
-
message_id = msg.get("message_id")
|
|
217
|
-
sender_id = msg.get("sender_id")
|
|
218
|
-
text = msg.get("text")
|
|
219
|
-
|
|
220
|
-
# فیلتر زمان پیام (مثلاً پیامهای خیلی قدیمی)
|
|
221
215
|
try:
|
|
222
216
|
if msg.get("time") and (time.time() - float(msg["time"])) > 20:
|
|
223
217
|
return
|
|
224
218
|
except Exception:
|
|
225
219
|
return
|
|
226
220
|
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
context = Message(bot=self,
|
|
222
|
+
chat_id=update.get("chat_id"),
|
|
223
|
+
message_id=msg.get("message_id"),
|
|
224
|
+
sender_id=msg.get("sender_id"),
|
|
225
|
+
text=msg.get("text"),
|
|
226
|
+
raw_data=msg)
|
|
229
227
|
|
|
230
|
-
# هندل callback ها (دکمهها)
|
|
231
|
-
if context.aux_data and
|
|
228
|
+
# هندل callback ها (دکمهها) - بدون تغییر
|
|
229
|
+
if context.aux_data and self._callback_handlers:
|
|
232
230
|
for handler in self._callback_handlers:
|
|
233
231
|
if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
|
|
234
232
|
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
235
|
-
return # فقط یک callback اجرا شود
|
|
236
|
-
|
|
237
|
-
# هندل پیامهای متنی معمولی
|
|
238
|
-
if hasattr(self, "_message_handler") and self._message_handler:
|
|
239
|
-
handler = self._message_handler
|
|
240
|
-
|
|
241
|
-
if handler["commands"]:
|
|
242
|
-
if not context.text or not context.text.startswith("/"):
|
|
243
|
-
return
|
|
244
|
-
parts = context.text.split()
|
|
245
|
-
cmd = parts[0][1:]
|
|
246
|
-
if cmd not in handler["commands"]:
|
|
247
233
|
return
|
|
248
|
-
context.args = parts[1:]
|
|
249
234
|
|
|
250
|
-
|
|
235
|
+
# هندل پیامهای متنی با حلقه روی تمام هندلرها
|
|
236
|
+
if self._message_handlers:
|
|
237
|
+
for handler in self._message_handlers:
|
|
238
|
+
# بررسی شرط دستورات (commands)
|
|
239
|
+
if handler["commands"]:
|
|
240
|
+
if not context.text or not context.text.startswith("/"):
|
|
241
|
+
continue
|
|
242
|
+
parts = context.text.split()
|
|
243
|
+
cmd = parts[0][1:]
|
|
244
|
+
if cmd not in handler["commands"]:
|
|
245
|
+
continue
|
|
246
|
+
context.args = parts[1:]
|
|
247
|
+
|
|
248
|
+
# بررسی شرط فیلترها (filters)
|
|
249
|
+
if handler["filters"] and not handler["filters"](context):
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
# اگر شرایط بالا برقرار بود یا هندلر عمومی بود، اجرا کن و خارج شو
|
|
253
|
+
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
251
254
|
return
|
|
252
255
|
|
|
253
|
-
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
256
|
def get_updates(
|
|
260
257
|
self,
|
|
261
258
|
offset_id: Optional[str] = None,
|
|
@@ -506,9 +503,10 @@ class Robot:
|
|
|
506
503
|
def get_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
507
504
|
"""Get chat info."""
|
|
508
505
|
return self._post("getChat", {"chat_id": chat_id})
|
|
506
|
+
|
|
509
507
|
def upload_media_file(self, upload_url: str, name: str, path: Union[str, Path]) -> str:
|
|
510
|
-
# بررسی اینکه ورودی URL است یا مسیر محلی
|
|
511
508
|
is_temp_file = False
|
|
509
|
+
|
|
512
510
|
if isinstance(path, str) and path.startswith("http"):
|
|
513
511
|
response = requests.get(path)
|
|
514
512
|
if response.status_code != 200:
|
|
@@ -519,22 +517,59 @@ class Robot:
|
|
|
519
517
|
path = temp_file.name
|
|
520
518
|
is_temp_file = True
|
|
521
519
|
|
|
522
|
-
|
|
520
|
+
file_size = os.path.getsize(path)
|
|
521
|
+
|
|
522
|
+
with open(path, 'rb') as f:
|
|
523
|
+
progress_bar = None
|
|
524
|
+
|
|
525
|
+
if self.show_progress:
|
|
526
|
+
progress_bar = tqdm(
|
|
527
|
+
total=file_size,
|
|
528
|
+
unit='B',
|
|
529
|
+
unit_scale=True,
|
|
530
|
+
unit_divisor=1024,
|
|
531
|
+
desc=f'Uploading : {name}',
|
|
532
|
+
bar_format='{l_bar}{bar:100}{r_bar}',
|
|
533
|
+
colour='cyan'
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
class FileWithProgress:
|
|
537
|
+
def __init__(self, file, progress):
|
|
538
|
+
self.file = file
|
|
539
|
+
self.progress = progress
|
|
540
|
+
|
|
541
|
+
def read(self, size=-1):
|
|
542
|
+
data = self.file.read(size)
|
|
543
|
+
if self.progress:
|
|
544
|
+
self.progress.update(len(data))
|
|
545
|
+
return data
|
|
546
|
+
|
|
547
|
+
def __getattr__(self, attr):
|
|
548
|
+
return getattr(self.file, attr)
|
|
549
|
+
|
|
550
|
+
file_with_progress = FileWithProgress(f, progress_bar)
|
|
551
|
+
|
|
523
552
|
files = {
|
|
524
|
-
'file': (name,
|
|
553
|
+
'file': (name, file_with_progress, 'application/octet-stream')
|
|
525
554
|
}
|
|
555
|
+
|
|
526
556
|
response = requests.post(upload_url, files=files)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
557
|
+
|
|
558
|
+
if progress_bar:
|
|
559
|
+
progress_bar.close()
|
|
530
560
|
|
|
531
561
|
if is_temp_file:
|
|
532
|
-
os.
|
|
562
|
+
os.remove(path)
|
|
533
563
|
|
|
564
|
+
if response.status_code != 200:
|
|
565
|
+
raise Exception(f"Upload failed ({response.status_code}): {response.text}")
|
|
566
|
+
|
|
567
|
+
data = response.json()
|
|
534
568
|
return data.get('data', {}).get('file_id')
|
|
535
569
|
|
|
536
|
-
|
|
537
|
-
|
|
570
|
+
|
|
571
|
+
def get_upload_url(self, media_type: Literal['File', 'Image', 'Voice', 'Music', 'Gif','Video']) -> str:
|
|
572
|
+
allowed = ['File', 'Image', 'Voice', 'Music', 'Gif','Video']
|
|
538
573
|
if media_type not in allowed:
|
|
539
574
|
raise ValueError(f"Invalid media type. Must be one of {allowed}")
|
|
540
575
|
result = self._post("requestSendFile", {"type": media_type})
|
|
@@ -624,6 +659,35 @@ class Robot:
|
|
|
624
659
|
disable_notification=disable_notification,
|
|
625
660
|
chat_keypad_type=chat_keypad_type
|
|
626
661
|
)
|
|
662
|
+
def send_video(
|
|
663
|
+
self,
|
|
664
|
+
chat_id: str,
|
|
665
|
+
path: Optional[Union[str, Path]] = None,
|
|
666
|
+
file_id: Optional[str] = None,
|
|
667
|
+
text: Optional[str] = None,
|
|
668
|
+
file_name: Optional[str] = None,
|
|
669
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
670
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
671
|
+
reply_to_message_id: Optional[str] = None,
|
|
672
|
+
disable_notification: bool = False,
|
|
673
|
+
chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "New"
|
|
674
|
+
) -> Dict[str, Any]:
|
|
675
|
+
if path:
|
|
676
|
+
file_name = file_name or Path(path).name
|
|
677
|
+
upload_url = self.get_upload_url("Video")
|
|
678
|
+
file_id = self.upload_media_file(upload_url, file_name, path)
|
|
679
|
+
if not file_id:
|
|
680
|
+
raise ValueError("Either path or file_id must be provided.")
|
|
681
|
+
return self._send_uploaded_file(
|
|
682
|
+
chat_id=chat_id,
|
|
683
|
+
file_id=file_id,
|
|
684
|
+
text=text,
|
|
685
|
+
inline_keypad=inline_keypad,
|
|
686
|
+
chat_keypad=chat_keypad,
|
|
687
|
+
reply_to_message_id=reply_to_message_id,
|
|
688
|
+
disable_notification=disable_notification,
|
|
689
|
+
chat_keypad_type=chat_keypad_type
|
|
690
|
+
)
|
|
627
691
|
def send_voice(
|
|
628
692
|
self,
|
|
629
693
|
chat_id: str,
|
rubka/asynco.py
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import aiohttp
|
|
3
|
+
import aiofiles
|
|
4
|
+
from typing import List, Optional, Dict, Any, Literal, Callable, Union
|
|
5
|
+
from .exceptions import APIRequestError
|
|
6
|
+
from .adaptorrubka import Client as Client_get
|
|
7
|
+
from .logger import logger
|
|
8
|
+
try:
|
|
9
|
+
from .context import Message, InlineMessage
|
|
10
|
+
except (ImportError, ModuleNotFoundError):
|
|
11
|
+
# اگر به صورت مستقیم اجرا شود، از این حالت استفاده میکند
|
|
12
|
+
from context import Message, InlineMessage
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import time
|
|
15
|
+
import datetime
|
|
16
|
+
import tempfile
|
|
17
|
+
from tqdm import tqdm
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import subprocess
|
|
21
|
+
|
|
22
|
+
API_URL = "https://botapi.rubika.ir/v3"
|
|
23
|
+
|
|
24
|
+
def install_package(package_name: str) -> bool:
|
|
25
|
+
"""Installs a package using pip."""
|
|
26
|
+
try:
|
|
27
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
28
|
+
return True
|
|
29
|
+
except Exception:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
def get_importlib_metadata():
|
|
33
|
+
"""Dynamically imports and returns metadata functions from importlib."""
|
|
34
|
+
try:
|
|
35
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
36
|
+
return version, PackageNotFoundError
|
|
37
|
+
except ImportError:
|
|
38
|
+
if install_package("importlib-metadata"):
|
|
39
|
+
try:
|
|
40
|
+
from importlib_metadata import version, PackageNotFoundError
|
|
41
|
+
return version, PackageNotFoundError
|
|
42
|
+
except ImportError:
|
|
43
|
+
return None, None
|
|
44
|
+
return None, None
|
|
45
|
+
|
|
46
|
+
version, PackageNotFoundError = get_importlib_metadata()
|
|
47
|
+
|
|
48
|
+
def get_installed_version(package_name: str) -> Optional[str]:
|
|
49
|
+
"""Gets the installed version of a package."""
|
|
50
|
+
if version is None:
|
|
51
|
+
return "unknown"
|
|
52
|
+
try:
|
|
53
|
+
return version(package_name)
|
|
54
|
+
except PackageNotFoundError:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
async def get_latest_version(package_name: str) -> Optional[str]:
|
|
58
|
+
"""Fetches the latest version of a package from PyPI asynchronously."""
|
|
59
|
+
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
60
|
+
try:
|
|
61
|
+
async with aiohttp.ClientSession() as session:
|
|
62
|
+
async with session.get(url, timeout=5) as resp:
|
|
63
|
+
resp.raise_for_status()
|
|
64
|
+
data = await resp.json()
|
|
65
|
+
return data.get("info", {}).get("version")
|
|
66
|
+
except Exception:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
async def check_rubka_version():
|
|
70
|
+
"""Checks for outdated 'rubka' package and warns the user."""
|
|
71
|
+
package_name = "rubka"
|
|
72
|
+
installed_version = get_installed_version(package_name)
|
|
73
|
+
if installed_version is None:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
latest_version = await get_latest_version(package_name)
|
|
77
|
+
if latest_version is None:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
if installed_version != latest_version:
|
|
81
|
+
print(f"\n\nWARNING: Your installed version of '{package_name}' is OUTDATED and may cause errors or security risks!")
|
|
82
|
+
print(f"Installed version : {installed_version}")
|
|
83
|
+
print(f"Latest available version : {latest_version}")
|
|
84
|
+
print(f"Please update IMMEDIATELY by running:")
|
|
85
|
+
print(f"\npip install {package_name}=={latest_version}\n")
|
|
86
|
+
print("Not updating may lead to malfunctions or incompatibility.")
|
|
87
|
+
print("To see new methods : @rubka_library\n\n")
|
|
88
|
+
|
|
89
|
+
# To run the check at startup in an async context
|
|
90
|
+
# asyncio.run(check_rubka_version())
|
|
91
|
+
|
|
92
|
+
def show_last_six_words(text: str) -> str:
|
|
93
|
+
"""Returns the last 6 characters of a stripped string."""
|
|
94
|
+
text = text.strip()
|
|
95
|
+
return text[-6:]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Robot:
|
|
99
|
+
"""
|
|
100
|
+
Main async class to interact with Rubika Bot API.
|
|
101
|
+
Initialized with a bot token.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
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):
|
|
105
|
+
self.token = token
|
|
106
|
+
self._inline_query_handlers: List[dict] = []
|
|
107
|
+
self.timeout = timeout
|
|
108
|
+
self.auth = auth
|
|
109
|
+
self.show_progress = show_progress
|
|
110
|
+
self.session_name = session_name
|
|
111
|
+
self.Key = Key
|
|
112
|
+
self.platform = platform
|
|
113
|
+
self.web_hook = web_hook
|
|
114
|
+
self._offset_id: Optional[str] = None
|
|
115
|
+
self._aiohttp_session: aiohttp.ClientSession = None
|
|
116
|
+
self.sessions: Dict[str, Dict[str, Any]] = {}
|
|
117
|
+
self._callback_handler = None
|
|
118
|
+
self._message_handler = None
|
|
119
|
+
self._inline_query_handler = None
|
|
120
|
+
self._callback_handlers: List[dict] = []
|
|
121
|
+
self._processed_message_ids: Dict[str, float] = {}
|
|
122
|
+
self._message_handlers: List[dict] = []
|
|
123
|
+
|
|
124
|
+
logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
|
|
125
|
+
|
|
126
|
+
async def _get_session(self) -> aiohttp.ClientSession:
|
|
127
|
+
"""Lazily creates and returns the aiohttp session."""
|
|
128
|
+
if self._aiohttp_session is None or self._aiohttp_session.closed:
|
|
129
|
+
self._aiohttp_session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.timeout))
|
|
130
|
+
return self._aiohttp_session
|
|
131
|
+
|
|
132
|
+
async def _initialize_webhook(self):
|
|
133
|
+
"""Initializes and sets the webhook endpoint if provided."""
|
|
134
|
+
if not self.web_hook:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
session = await self._get_session()
|
|
138
|
+
try:
|
|
139
|
+
async with session.get(self.web_hook, timeout=self.timeout) as response:
|
|
140
|
+
response.raise_for_status()
|
|
141
|
+
data = await response.json()
|
|
142
|
+
print(data)
|
|
143
|
+
json_url = data.get('url', self.web_hook)
|
|
144
|
+
print(self.web_hook)
|
|
145
|
+
|
|
146
|
+
for endpoint_type in [
|
|
147
|
+
"ReceiveUpdate",
|
|
148
|
+
"ReceiveInlineMessage",
|
|
149
|
+
"ReceiveQuery",
|
|
150
|
+
"GetSelectionItem",
|
|
151
|
+
"SearchSelectionItems"
|
|
152
|
+
]:
|
|
153
|
+
result = await self.update_bot_endpoint(self.web_hook, endpoint_type)
|
|
154
|
+
print(result)
|
|
155
|
+
self.web_hook = json_url
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.error(f"Failed to set webhook from {self.web_hook}: {e}")
|
|
158
|
+
self.web_hook = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
162
|
+
url = f"{API_URL}/{self.token}/{method}"
|
|
163
|
+
session = await self._get_session()
|
|
164
|
+
try:
|
|
165
|
+
async with session.post(url, json=data) as response:
|
|
166
|
+
response.raise_for_status()
|
|
167
|
+
try:
|
|
168
|
+
json_resp = await response.json()
|
|
169
|
+
except aiohttp.ContentTypeError:
|
|
170
|
+
text_resp = await response.text()
|
|
171
|
+
logger.error(f"Invalid JSON response from {method}: {text_resp}")
|
|
172
|
+
raise APIRequestError(f"Invalid JSON response: {text_resp}")
|
|
173
|
+
|
|
174
|
+
if method != "getUpdates":
|
|
175
|
+
logger.debug(f"API Response from {method}: {json_resp}")
|
|
176
|
+
|
|
177
|
+
return json_resp
|
|
178
|
+
except aiohttp.ClientError as e:
|
|
179
|
+
logger.error(f"API request failed: {e}")
|
|
180
|
+
raise APIRequestError(f"API request failed: {e}") from e
|
|
181
|
+
|
|
182
|
+
async def get_me(self) -> Dict[str, Any]:
|
|
183
|
+
"""Get info about the bot itself."""
|
|
184
|
+
return await self._post("getMe", {})
|
|
185
|
+
|
|
186
|
+
def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
187
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
188
|
+
self._message_handlers.append({
|
|
189
|
+
"func": func,
|
|
190
|
+
"filters": filters,
|
|
191
|
+
"commands": commands
|
|
192
|
+
})
|
|
193
|
+
return func
|
|
194
|
+
return decorator
|
|
195
|
+
|
|
196
|
+
def on_callback(self, button_id: Optional[str] = None):
|
|
197
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
198
|
+
if not hasattr(self, "_callback_handlers"):
|
|
199
|
+
self._callback_handlers = []
|
|
200
|
+
self._callback_handlers.append({
|
|
201
|
+
"func": func,
|
|
202
|
+
"button_id": button_id
|
|
203
|
+
})
|
|
204
|
+
return func
|
|
205
|
+
return decorator
|
|
206
|
+
|
|
207
|
+
async def _handle_inline_query(self, inline_message: InlineMessage):
|
|
208
|
+
aux_button_id = inline_message.aux_data.button_id if inline_message.aux_data else None
|
|
209
|
+
|
|
210
|
+
for handler in self._inline_query_handlers:
|
|
211
|
+
if handler["button_id"] is None or handler["button_id"] == aux_button_id:
|
|
212
|
+
try:
|
|
213
|
+
await handler["func"](self, inline_message)
|
|
214
|
+
except Exception as e:
|
|
215
|
+
print(f"Error in inline query handler: {e}")
|
|
216
|
+
|
|
217
|
+
def on_inline_query(self, button_id: Optional[str] = None):
|
|
218
|
+
def decorator(func: Callable[[Any, InlineMessage], None]):
|
|
219
|
+
self._inline_query_handlers.append({
|
|
220
|
+
"func": func,
|
|
221
|
+
"button_id": button_id
|
|
222
|
+
})
|
|
223
|
+
return func
|
|
224
|
+
return decorator
|
|
225
|
+
|
|
226
|
+
async def _process_update(self, update: dict):
|
|
227
|
+
if update.get("type") == "ReceiveQuery":
|
|
228
|
+
msg = update.get("inline_message", {})
|
|
229
|
+
context = InlineMessage(bot=self, raw_data=msg)
|
|
230
|
+
asyncio.create_task(self._handle_inline_query(context))
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
if update.get("type") == "NewMessage":
|
|
234
|
+
msg = update.get("new_message", {})
|
|
235
|
+
try:
|
|
236
|
+
if msg.get("time") and (time.time() - float(msg["time"])) > 20:
|
|
237
|
+
return
|
|
238
|
+
except (ValueError, TypeError):
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
context = Message(bot=self,
|
|
242
|
+
chat_id=update.get("chat_id"),
|
|
243
|
+
message_id=msg.get("message_id"),
|
|
244
|
+
sender_id=msg.get("sender_id"),
|
|
245
|
+
text=msg.get("text"),
|
|
246
|
+
raw_data=msg)
|
|
247
|
+
|
|
248
|
+
# پردازش دکمههای شیشهای (بدون تغییر)
|
|
249
|
+
if context.aux_data and self._callback_handlers:
|
|
250
|
+
for handler in self._callback_handlers:
|
|
251
|
+
if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
|
|
252
|
+
asyncio.create_task(handler["func"](self, context))
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
# پردازش پیامهای متنی با حلقه روی تمام هندلرها
|
|
256
|
+
if self._message_handlers:
|
|
257
|
+
for handler_info in self._message_handlers:
|
|
258
|
+
# بررسی شرط دستورات (commands)
|
|
259
|
+
if handler_info["commands"]:
|
|
260
|
+
if not context.text or not context.text.startswith("/"):
|
|
261
|
+
continue # اگر پیام کامند نبود، این هندلر را رد کن
|
|
262
|
+
parts = context.text.split()
|
|
263
|
+
cmd = parts[0][1:]
|
|
264
|
+
if cmd not in handler_info["commands"]:
|
|
265
|
+
continue # اگر کامند مطابقت نداشت، این هندلر را رد کن
|
|
266
|
+
context.args = parts[1:]
|
|
267
|
+
|
|
268
|
+
# بررسی شرط فیلترها (filters)
|
|
269
|
+
if handler_info["filters"]:
|
|
270
|
+
if not handler_info["filters"](context):
|
|
271
|
+
continue # اگر فیلتر برقرار نبود، این هندلر را رد کن
|
|
272
|
+
|
|
273
|
+
# اگر هندلری برای همه پیامها باشد (بدون کامند و فیلتر)
|
|
274
|
+
if not handler_info["commands"] and not handler_info["filters"]:
|
|
275
|
+
asyncio.create_task(handler_info["func"](self, context))
|
|
276
|
+
return # بعد از یافتن هندلر مناسب، از حلقه خارج شو
|
|
277
|
+
|
|
278
|
+
# اگر شرایط کامند یا فیلتر برقرار بود
|
|
279
|
+
if handler_info["commands"] or handler_info["filters"]:
|
|
280
|
+
asyncio.create_task(handler_info["func"](self, context))
|
|
281
|
+
return # بعد از یافتن هندلر مناسب، از حلقه خارج شو
|
|
282
|
+
|
|
283
|
+
async def get_updates(self, offset_id: Optional[str] = None, limit: Optional[int] = None) -> Dict[str, Any]:
|
|
284
|
+
data = {}
|
|
285
|
+
if offset_id: data["offset_id"] = offset_id
|
|
286
|
+
if limit: data["limit"] = limit
|
|
287
|
+
return await self._post("getUpdates", data)
|
|
288
|
+
|
|
289
|
+
async def update_webhook(self, offset_id: Optional[str] = None, limit: Optional[int] = None) -> List[Dict[str, Any]]:
|
|
290
|
+
session = await self._get_session()
|
|
291
|
+
params = {}
|
|
292
|
+
if offset_id: params['offset_id'] = offset_id
|
|
293
|
+
if limit: params['limit'] = limit
|
|
294
|
+
async with session.get(self.web_hook, params=params) as response:
|
|
295
|
+
response.raise_for_status()
|
|
296
|
+
# وبهوک باید لیستی از رویدادها را برگرداند
|
|
297
|
+
return await response.json()
|
|
298
|
+
|
|
299
|
+
def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
|
|
300
|
+
now = time.time()
|
|
301
|
+
expired = [mid for mid, ts in self._processed_message_ids.items() if now - ts > max_age_sec]
|
|
302
|
+
for mid in expired:
|
|
303
|
+
del self._processed_message_ids[mid]
|
|
304
|
+
|
|
305
|
+
if message_id in self._processed_message_ids:
|
|
306
|
+
return True
|
|
307
|
+
|
|
308
|
+
self._processed_message_ids[message_id] = now
|
|
309
|
+
return False
|
|
310
|
+
|
|
311
|
+
async def run(self):
|
|
312
|
+
"""
|
|
313
|
+
Starts the bot.
|
|
314
|
+
This method is now corrected to handle webhook updates similarly to the original synchronous code.
|
|
315
|
+
"""
|
|
316
|
+
await check_rubka_version()
|
|
317
|
+
await self._initialize_webhook()
|
|
318
|
+
print("Bot started running...")
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
while True:
|
|
322
|
+
try:
|
|
323
|
+
if self.web_hook:
|
|
324
|
+
# ----- منطق وبهوک (اصلاح شده) -----
|
|
325
|
+
# آپدیتها مستقیما از وبهوک گرفته و پردازش میشوند
|
|
326
|
+
webhook_data = await self.update_webhook()
|
|
327
|
+
if isinstance(webhook_data, list):
|
|
328
|
+
for item in webhook_data:
|
|
329
|
+
data = item.get("data", {})
|
|
330
|
+
|
|
331
|
+
received_at_str = item.get("received_at")
|
|
332
|
+
if received_at_str:
|
|
333
|
+
try:
|
|
334
|
+
received_at_ts = datetime.datetime.strptime(received_at_str, "%Y-%m-%d %H:%M:%S").timestamp()
|
|
335
|
+
if time.time() - received_at_ts > 20:
|
|
336
|
+
continue
|
|
337
|
+
except (ValueError, TypeError):
|
|
338
|
+
pass # رد شدن در صورت فرمت اشتباه زمان
|
|
339
|
+
|
|
340
|
+
update = None
|
|
341
|
+
if "update" in data:
|
|
342
|
+
update = data["update"]
|
|
343
|
+
elif "inline_message" in data:
|
|
344
|
+
update = {"type": "ReceiveQuery", "inline_message": data["inline_message"]}
|
|
345
|
+
else:
|
|
346
|
+
continue
|
|
347
|
+
|
|
348
|
+
message_id = None
|
|
349
|
+
if update.get("type") == "NewMessage":
|
|
350
|
+
message_id = update.get("new_message", {}).get("message_id")
|
|
351
|
+
elif update.get("type") == "ReceiveQuery":
|
|
352
|
+
message_id = update.get("inline_message", {}).get("message_id")
|
|
353
|
+
elif "message_id" in update:
|
|
354
|
+
message_id = update.get("message_id")
|
|
355
|
+
|
|
356
|
+
if message_id and not self._is_duplicate(str(message_id)):
|
|
357
|
+
await self._process_update(update)
|
|
358
|
+
|
|
359
|
+
else:
|
|
360
|
+
# ----- منطق Polling (بدون تغییر) -----
|
|
361
|
+
get_updates_response = await self.get_updates(offset_id=self._offset_id, limit=100)
|
|
362
|
+
if get_updates_response and get_updates_response.get("data"):
|
|
363
|
+
updates = get_updates_response["data"].get("updates", [])
|
|
364
|
+
self._offset_id = get_updates_response["data"].get("next_offset_id", self._offset_id)
|
|
365
|
+
|
|
366
|
+
for update in updates:
|
|
367
|
+
message_id = None
|
|
368
|
+
if update.get("type") == "NewMessage":
|
|
369
|
+
message_id = update.get("new_message", {}).get("message_id")
|
|
370
|
+
elif update.get("type") == "ReceiveQuery":
|
|
371
|
+
message_id = update.get("inline_message", {}).get("message_id")
|
|
372
|
+
elif "message_id" in update:
|
|
373
|
+
message_id = update.get("message_id")
|
|
374
|
+
|
|
375
|
+
if message_id and not self._is_duplicate(str(message_id)):
|
|
376
|
+
await self._process_update(update)
|
|
377
|
+
|
|
378
|
+
await asyncio.sleep(0.1) # وقفه کوتاه برای جلوگیری از مصرف CPU
|
|
379
|
+
except Exception as e:
|
|
380
|
+
print(f"❌ Error in run loop: {e}")
|
|
381
|
+
await asyncio.sleep(5) # وقفه طولانیتر در صورت بروز خطا
|
|
382
|
+
finally:
|
|
383
|
+
if self._aiohttp_session:
|
|
384
|
+
await self._aiohttp_session.close()
|
|
385
|
+
print("Bot stopped and session closed.")
|
|
386
|
+
|
|
387
|
+
async def send_message(self, chat_id: str, text: str, 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) -> Dict[str, Any]:
|
|
388
|
+
payload = {"chat_id": chat_id, "text": text, "disable_notification": disable_notification}
|
|
389
|
+
if chat_keypad: payload["chat_keypad"] = chat_keypad
|
|
390
|
+
if inline_keypad: payload["inline_keypad"] = inline_keypad
|
|
391
|
+
if reply_to_message_id: payload["reply_to_message_id"] = reply_to_message_id
|
|
392
|
+
if chat_keypad_type: payload["chat_keypad_type"] = chat_keypad_type
|
|
393
|
+
return await self._post("sendMessage", payload)
|
|
394
|
+
|
|
395
|
+
def _get_client(self) -> Client_get:
|
|
396
|
+
if self.session_name:
|
|
397
|
+
return Client_get(self.session_name, self.auth, self.Key, self.platform)
|
|
398
|
+
else:
|
|
399
|
+
return Client_get(show_last_six_words(self.token), self.auth, self.Key, self.platform)
|
|
400
|
+
|
|
401
|
+
async def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
|
|
402
|
+
client = self._get_client()
|
|
403
|
+
|
|
404
|
+
if chat_id:
|
|
405
|
+
chat_info_data = await self.get_chat(chat_id)
|
|
406
|
+
chat_info = chat_info_data.get('data', {}).get('chat', {})
|
|
407
|
+
username = chat_info.get('username')
|
|
408
|
+
user_id = chat_info.get('user_id')
|
|
409
|
+
|
|
410
|
+
# Since client methods are sync, run them in a thread pool
|
|
411
|
+
if username:
|
|
412
|
+
result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
|
|
413
|
+
members = result.get('in_chat_members', [])
|
|
414
|
+
return any(m.get('username') == username for m in members)
|
|
415
|
+
elif user_id:
|
|
416
|
+
member_guids = await asyncio.to_thread(client.get_all_members, channel_guid, just_get_guids=True)
|
|
417
|
+
return user_id in member_guids
|
|
418
|
+
return False
|
|
419
|
+
|
|
420
|
+
def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
|
|
421
|
+
# This is a sync method that will be called with asyncio.to_thread
|
|
422
|
+
client = self._get_client()
|
|
423
|
+
return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
|
|
424
|
+
|
|
425
|
+
async def send_poll(self, chat_id: str, question: str, options: List[str]) -> Dict[str, Any]:
|
|
426
|
+
return await self._post("sendPoll", {"chat_id": chat_id, "question": question, "options": options})
|
|
427
|
+
|
|
428
|
+
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]:
|
|
429
|
+
payload = {"chat_id": chat_id, "latitude": latitude, "longitude": longitude, "disable_notification": disable_notification}
|
|
430
|
+
if inline_keypad: payload["inline_keypad"] = inline_keypad
|
|
431
|
+
if reply_to_message_id: payload["reply_to_message_id"] = reply_to_message_id
|
|
432
|
+
if chat_keypad_type: payload["chat_keypad_type"] = chat_keypad_type
|
|
433
|
+
return await self._post("sendLocation", {k: v for k, v in payload.items() if v is not None})
|
|
434
|
+
|
|
435
|
+
async def send_contact(self, chat_id: str, first_name: str, last_name: str, phone_number: str) -> Dict[str, Any]:
|
|
436
|
+
return await self._post("sendContact", {"chat_id": chat_id, "first_name": first_name, "last_name": last_name, "phone_number": phone_number})
|
|
437
|
+
|
|
438
|
+
async def get_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
439
|
+
return await self._post("getChat", {"chat_id": chat_id})
|
|
440
|
+
|
|
441
|
+
async def upload_media_file(self, upload_url: str, name: str, path: Union[str, Path]) -> str:
|
|
442
|
+
is_temp_file = False
|
|
443
|
+
session = await self._get_session()
|
|
444
|
+
|
|
445
|
+
if isinstance(path, str) and path.startswith("http"):
|
|
446
|
+
async with session.get(path) as response:
|
|
447
|
+
if response.status != 200:
|
|
448
|
+
raise Exception(f"Failed to download file from URL ({response.status})")
|
|
449
|
+
|
|
450
|
+
content = await response.read()
|
|
451
|
+
|
|
452
|
+
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
|
453
|
+
temp_file.write(content)
|
|
454
|
+
path = temp_file.name
|
|
455
|
+
is_temp_file = True
|
|
456
|
+
|
|
457
|
+
file_size = os.path.getsize(path) # Note: os.path.getsize is sync, but fast enough for most cases. aiofiles can be used for async alternative if needed on huge file lists.
|
|
458
|
+
|
|
459
|
+
progress_bar = tqdm(total=file_size, unit='B', unit_scale=True, unit_divisor=1024, desc=f'Uploading : {name}', bar_format='{l_bar}{bar:100}{r_bar}', colour='cyan', disable=not self.show_progress)
|
|
460
|
+
|
|
461
|
+
async def file_progress_generator(file_path, chunk_size=8192):
|
|
462
|
+
async with aiofiles.open(file_path, 'rb') as f:
|
|
463
|
+
while True:
|
|
464
|
+
chunk = await f.read(chunk_size)
|
|
465
|
+
if not chunk:
|
|
466
|
+
break
|
|
467
|
+
progress_bar.update(len(chunk))
|
|
468
|
+
yield chunk
|
|
469
|
+
|
|
470
|
+
data = aiohttp.FormData()
|
|
471
|
+
data.add_field('file', file_progress_generator(path), filename=name, content_type='application/octet-stream')
|
|
472
|
+
|
|
473
|
+
async with session.post(upload_url, data=data) as response:
|
|
474
|
+
progress_bar.close()
|
|
475
|
+
if response.status != 200:
|
|
476
|
+
raise Exception(f"Upload failed ({response.status}): {await response.text()}")
|
|
477
|
+
|
|
478
|
+
json_data = await response.json()
|
|
479
|
+
if is_temp_file:
|
|
480
|
+
os.remove(path)
|
|
481
|
+
|
|
482
|
+
return json_data.get('data', {}).get('file_id')
|
|
483
|
+
|
|
484
|
+
async def get_upload_url(self, media_type: Literal['File', 'Image', 'Voice', 'Music', 'Gif', 'Video']) -> str:
|
|
485
|
+
allowed = ['File', 'Image', 'Voice', 'Music', 'Gif', 'Video']
|
|
486
|
+
if media_type not in allowed:
|
|
487
|
+
raise ValueError(f"Invalid media type. Must be one of {allowed}")
|
|
488
|
+
result = await self._post("requestSendFile", {"type": media_type})
|
|
489
|
+
return result.get("data", {}).get("upload_url")
|
|
490
|
+
|
|
491
|
+
async def _send_uploaded_file(self, chat_id: str, file_id: str, 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]:
|
|
492
|
+
payload = {"chat_id": chat_id, "file_id": file_id, "text": text, "disable_notification": disable_notification, "chat_keypad_type": chat_keypad_type}
|
|
493
|
+
if chat_keypad: payload["chat_keypad"] = chat_keypad
|
|
494
|
+
if inline_keypad: payload["inline_keypad"] = inline_keypad
|
|
495
|
+
if reply_to_message_id: payload["reply_to_message_id"] = str(reply_to_message_id)
|
|
496
|
+
return await self._post("sendFile", payload)
|
|
497
|
+
|
|
498
|
+
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):
|
|
499
|
+
if path:
|
|
500
|
+
file_name = file_name or Path(path).name
|
|
501
|
+
upload_url = await self.get_upload_url(media_type)
|
|
502
|
+
file_id = await self.upload_media_file(upload_url, file_name, path)
|
|
503
|
+
if not file_id:
|
|
504
|
+
raise ValueError("Either path or file_id must be provided.")
|
|
505
|
+
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)
|
|
506
|
+
|
|
507
|
+
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"]] = "New") -> Dict[str, Any]:
|
|
508
|
+
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)
|
|
509
|
+
|
|
510
|
+
async def send_music(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"]] = "New") -> Dict[str, Any]:
|
|
511
|
+
return await self._send_file_generic("Music", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
512
|
+
|
|
513
|
+
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"]] = "New") -> Dict[str, Any]:
|
|
514
|
+
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)
|
|
515
|
+
|
|
516
|
+
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"]] = "New") -> Dict[str, Any]:
|
|
517
|
+
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)
|
|
518
|
+
|
|
519
|
+
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"]] = "New") -> Dict[str, Any]:
|
|
520
|
+
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)
|
|
521
|
+
|
|
522
|
+
async def send_gif(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"]] = "New") -> Dict[str, Any]:
|
|
523
|
+
return await self._send_file_generic("Gif", chat_id, path, file_id, text, file_name, inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
524
|
+
|
|
525
|
+
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> Dict[str, Any]:
|
|
526
|
+
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})
|
|
527
|
+
|
|
528
|
+
async def edit_message_text(self, chat_id: str, message_id: str, text: str) -> Dict[str, Any]:
|
|
529
|
+
return await self._post("editMessageText", {"chat_id": chat_id, "message_id": message_id, "text": text})
|
|
530
|
+
|
|
531
|
+
async def edit_inline_keypad(self, chat_id: str, message_id: str, inline_keypad: Dict[str, Any]) -> Dict[str, Any]:
|
|
532
|
+
return await self._post("editMessageKeypad", {"chat_id": chat_id, "message_id": message_id, "inline_keypad": inline_keypad})
|
|
533
|
+
|
|
534
|
+
async def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
|
|
535
|
+
return await self._post("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
|
|
536
|
+
|
|
537
|
+
async def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
538
|
+
return await self._post("setCommands", {"bot_commands": bot_commands})
|
|
539
|
+
|
|
540
|
+
async def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
|
|
541
|
+
return await self._post("updateBotEndpoints", {"url": url, "type": type})
|
|
542
|
+
|
|
543
|
+
async def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
|
|
544
|
+
return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "Removed"})
|
|
545
|
+
|
|
546
|
+
async def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
|
|
547
|
+
return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "New", "chat_keypad": chat_keypad})
|
|
548
|
+
|
|
549
|
+
async def get_name(self, chat_id: str) -> str:
|
|
550
|
+
try:
|
|
551
|
+
chat = await self.get_chat(chat_id)
|
|
552
|
+
chat_info = chat.get("data", {}).get("chat", {})
|
|
553
|
+
first_name = chat_info.get("first_name", "")
|
|
554
|
+
last_name = chat_info.get("last_name", "")
|
|
555
|
+
|
|
556
|
+
full_name = f"{first_name} {last_name}".strip()
|
|
557
|
+
return full_name if full_name else "Unknown"
|
|
558
|
+
except Exception:
|
|
559
|
+
return "Unknown"
|
|
560
|
+
|
|
561
|
+
async def get_username(self, chat_id: str) -> str:
|
|
562
|
+
chat_info = await self.get_chat(chat_id)
|
|
563
|
+
return chat_info.get("data", {}).get("chat", {}).get("username", "None")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.2
|
|
4
4
|
Summary: A Python library for interacting with Rubika Bot API.
|
|
5
5
|
Home-page: https://github.com/Mahdy-Ahmadi/Rubka
|
|
6
6
|
Download-URL: https://github.com/Mahdy-Ahmadi/rubka/blob/main/project_library.zip
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
rubka/__init__.py,sha256=TR1DABU5Maz2eO62ZEFiwOqNU0dH6l6HZfqRUxeo4eY,194
|
|
2
|
-
rubka/api.py,sha256=
|
|
2
|
+
rubka/api.py,sha256=gzaH2nfZjpxlMwzNsgA4FFBR38bAfQK2-2oTPs555Xw,35160
|
|
3
|
+
rubka/asynco.py,sha256=m3WztgmkzK6Jvrko8HxqLlIigWTbtJkdbDdQq3NtbMo,31994
|
|
3
4
|
rubka/button.py,sha256=4fMSZR7vUADxSmw1R3_pZ4dw5uMLZX5sOkwPPyNTBDE,8437
|
|
4
5
|
rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
|
|
5
6
|
rubka/context.py,sha256=j1scXTy_wBY52MmMixfZyIQnB0sdYjRwx17-8ZZmyB4,17017
|
|
@@ -32,7 +33,7 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
|
|
|
32
33
|
rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
|
|
33
34
|
rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
|
|
34
35
|
rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
|
|
35
|
-
rubka-4.
|
|
36
|
-
rubka-4.
|
|
37
|
-
rubka-4.
|
|
38
|
-
rubka-4.
|
|
36
|
+
rubka-4.5.2.dist-info/METADATA,sha256=xgwDJfrcrmaI2Hr9YEugIt8_3OzJiAXBHx2sldoW7ZU,33216
|
|
37
|
+
rubka-4.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
rubka-4.5.2.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
|
|
39
|
+
rubka-4.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|