Rubka 6.6.2__py3-none-any.whl → 7.1.17__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/__init__.py +70 -0
- rubka/api.py +6 -17
- rubka/asynco.py +1058 -255
- rubka/button.py +1 -1
- rubka/context.py +560 -25
- rubka/exceptions.py +35 -1
- rubka/filters.py +16 -0
- rubka/helpers.py +1461 -0
- rubka/metadata.py +117 -0
- rubka/rubino.py +227 -96
- rubka/tv.py +145 -0
- rubka/update.py +560 -25
- {rubka-6.6.2.dist-info → rubka-7.1.17.dist-info}/METADATA +29 -11
- {rubka-6.6.2.dist-info → rubka-7.1.17.dist-info}/RECORD +17 -13
- rubka-7.1.17.dist-info/entry_points.txt +2 -0
- {rubka-6.6.2.dist-info → rubka-7.1.17.dist-info}/WHEEL +0 -0
- {rubka-6.6.2.dist-info → rubka-7.1.17.dist-info}/top_level.txt +0 -0
rubka/asynco.py
CHANGED
|
@@ -1,31 +1,155 @@
|
|
|
1
|
-
import asyncio,aiohttp,aiofiles,time,datetime,json,tempfile,os,sys,subprocess,mimetypes
|
|
2
|
-
from typing import List, Optional, Dict, Any, Literal, Callable, Union
|
|
3
|
-
from
|
|
1
|
+
import asyncio,aiohttp,aiofiles,time,datetime,json,tempfile,os,sys,subprocess,mimetypes,time, hashlib,sqlite3,re
|
|
2
|
+
from typing import List, Optional, Dict, Any, Literal, Callable, Union,Set
|
|
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
|
|
7
|
+
from .metadata import Track_parsed as GlyphWeaver
|
|
8
|
+
from .rubino import Bot as Rubino
|
|
6
9
|
from . import filters
|
|
7
10
|
try:from .context import Message, InlineMessage
|
|
8
11
|
except (ImportError, ModuleNotFoundError):from context import Message, InlineMessage
|
|
12
|
+
try:from .button import ChatKeypadBuilder, InlineBuilder
|
|
13
|
+
except (ImportError, ModuleNotFoundError):from button import ChatKeypadBuilder, InlineBuilder
|
|
9
14
|
class FeatureNotAvailableError(Exception):
|
|
10
15
|
pass
|
|
11
16
|
|
|
12
17
|
from tqdm.asyncio import tqdm
|
|
13
18
|
from urllib.parse import urlparse, parse_qs
|
|
14
|
-
|
|
19
|
+
|
|
15
20
|
from pathlib import Path
|
|
16
21
|
from tqdm import tqdm
|
|
17
22
|
API_URL = "https://botapi.rubika.ir/v3"
|
|
18
23
|
|
|
19
24
|
def install_package(package_name: str) -> bool:
|
|
20
|
-
"""Installs a package using pip."""
|
|
21
25
|
try:
|
|
22
26
|
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
23
27
|
return True
|
|
24
28
|
except Exception:
|
|
25
29
|
return False
|
|
26
30
|
|
|
31
|
+
def metadata_CallBack(text: str, mode: str) -> Dict[str, Any]:
|
|
32
|
+
meta_data_parts = []
|
|
33
|
+
if mode == "Markdown":
|
|
34
|
+
for match in re.finditer(r"\*\*(.*?)\*\*", text):
|
|
35
|
+
meta_data_parts.append({
|
|
36
|
+
"type": "Bold",
|
|
37
|
+
"from_index": match.start(1),
|
|
38
|
+
"length": len(match.group(1))
|
|
39
|
+
})
|
|
40
|
+
for match in re.finditer(r"(?<!\*)\*(?!\*)(.*?)\*(?!\*)", text):
|
|
41
|
+
meta_data_parts.append({
|
|
42
|
+
"type": "Italic",
|
|
43
|
+
"from_index": match.start(1),
|
|
44
|
+
"length": len(match.group(1))
|
|
45
|
+
})
|
|
46
|
+
for match in re.finditer(r"~~(.*?)~~", text):
|
|
47
|
+
meta_data_parts.append({
|
|
48
|
+
"type": "Strike",
|
|
49
|
+
"from_index": match.start(1),
|
|
50
|
+
"length": len(match.group(1))
|
|
51
|
+
})
|
|
52
|
+
for match in re.finditer(r"__(.*?)__", text):
|
|
53
|
+
meta_data_parts.append({
|
|
54
|
+
"type": "Underline",
|
|
55
|
+
"from_index": match.start(1),
|
|
56
|
+
"length": len(match.group(1))
|
|
57
|
+
})
|
|
58
|
+
for match in re.finditer(r"^> (.*)$", text, re.MULTILINE):
|
|
59
|
+
meta_data_parts.append({
|
|
60
|
+
"type": "Quote",
|
|
61
|
+
"from_index": match.start(1),
|
|
62
|
+
"length": len(match.group(1))
|
|
63
|
+
})
|
|
64
|
+
for match in re.finditer(r"\|\|(.*?)\|\|", text):
|
|
65
|
+
meta_data_parts.append({
|
|
66
|
+
"type": "Spoiler",
|
|
67
|
+
"from_index": match.start(1),
|
|
68
|
+
"length": len(match.group(1))
|
|
69
|
+
})
|
|
70
|
+
for match in re.finditer(r"`(.*?)`", text):
|
|
71
|
+
meta_data_parts.append({
|
|
72
|
+
"type": "Mono",
|
|
73
|
+
"from_index": match.start(1),
|
|
74
|
+
"length": len(match.group(1))
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
for match in re.finditer(r"```(.*?)```", text, re.DOTALL):
|
|
78
|
+
meta_data_parts.append({
|
|
79
|
+
"type": "Pre",
|
|
80
|
+
"from_index": match.start(1),
|
|
81
|
+
"length": len(match.group(1))
|
|
82
|
+
})
|
|
83
|
+
for match in re.finditer(r"\[(.*?)\]\((.*?)\)", text):
|
|
84
|
+
meta_data_parts.append({
|
|
85
|
+
"type": "Link",
|
|
86
|
+
"from_index": match.start(1),
|
|
87
|
+
"length": len(match.group(1)),
|
|
88
|
+
"link_url": match.group(2)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
elif mode == "HTML":
|
|
92
|
+
for match in re.finditer(r"<b>(.*?)</b>", text):
|
|
93
|
+
meta_data_parts.append({
|
|
94
|
+
"type": "Bold",
|
|
95
|
+
"from_index": match.start(1),
|
|
96
|
+
"length": len(match.group(1))
|
|
97
|
+
})
|
|
98
|
+
for match in re.finditer(r"<i>(.*?)</i>", text):
|
|
99
|
+
meta_data_parts.append({
|
|
100
|
+
"type": "Italic",
|
|
101
|
+
"from_index": match.start(1),
|
|
102
|
+
"length": len(match.group(1))
|
|
103
|
+
})
|
|
104
|
+
for match in re.finditer(r"<u>(.*?)</u>", text):
|
|
105
|
+
meta_data_parts.append({
|
|
106
|
+
"type": "Underline",
|
|
107
|
+
"from_index": match.start(1),
|
|
108
|
+
"length": len(match.group(1))
|
|
109
|
+
})
|
|
110
|
+
for match in re.finditer(r"<s>(.*?)</s>|<strike>(.*?)</strike>", text):
|
|
111
|
+
inner = match.group(1) or match.group(2)
|
|
112
|
+
meta_data_parts.append({
|
|
113
|
+
"type": "Strike",
|
|
114
|
+
"from_index": match.start(),
|
|
115
|
+
"length": len(inner)
|
|
116
|
+
})
|
|
117
|
+
for match in re.finditer(r"<blockquote>(.*?)</blockquote>", text, re.DOTALL):
|
|
118
|
+
meta_data_parts.append({
|
|
119
|
+
"type": "Quote",
|
|
120
|
+
"from_index": match.start(1),
|
|
121
|
+
"length": len(match.group(1))
|
|
122
|
+
})
|
|
123
|
+
for match in re.finditer(r'<span class="spoiler">(.*?)</span>', text):
|
|
124
|
+
meta_data_parts.append({
|
|
125
|
+
"type": "Spoiler",
|
|
126
|
+
"from_index": match.start(1),
|
|
127
|
+
"length": len(match.group(1))
|
|
128
|
+
})
|
|
129
|
+
for match in re.finditer(r"<code>(.*?)</code>", text):
|
|
130
|
+
meta_data_parts.append({
|
|
131
|
+
"type": "Pre",
|
|
132
|
+
"from_index": match.start(1),
|
|
133
|
+
"length": len(match.group(1))
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
for match in re.finditer(r"<pre>(.*?)</pre>", text, re.DOTALL):
|
|
137
|
+
meta_data_parts.append({
|
|
138
|
+
"type": "Pre",
|
|
139
|
+
"from_index": match.start(1),
|
|
140
|
+
"length": len(match.group(1))
|
|
141
|
+
})
|
|
142
|
+
for match in re.finditer(r'<a href="(.*?)">(.*?)</a>', text):
|
|
143
|
+
meta_data_parts.append({
|
|
144
|
+
"type": "Link",
|
|
145
|
+
"from_index": match.start(2),
|
|
146
|
+
"length": len(match.group(2)),
|
|
147
|
+
"link_url": match.group(1)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
return {"meta_data_parts": meta_data_parts} if meta_data_parts else {}
|
|
151
|
+
|
|
27
152
|
def get_importlib_metadata():
|
|
28
|
-
"""Dynamically imports and returns metadata functions from importlib."""
|
|
29
153
|
try:
|
|
30
154
|
from importlib.metadata import version, PackageNotFoundError
|
|
31
155
|
return version, PackageNotFoundError
|
|
@@ -41,16 +165,17 @@ def get_importlib_metadata():
|
|
|
41
165
|
version, PackageNotFoundError = get_importlib_metadata()
|
|
42
166
|
|
|
43
167
|
def get_installed_version(package_name: str) -> Optional[str]:
|
|
44
|
-
"""Gets the installed version of a package."""
|
|
45
168
|
if version is None:
|
|
46
169
|
return "unknown"
|
|
47
170
|
try:
|
|
48
171
|
return version(package_name)
|
|
49
172
|
except PackageNotFoundError:
|
|
50
173
|
return None
|
|
51
|
-
|
|
174
|
+
BASE_URLS = {
|
|
175
|
+
"botapi": "https://botapi.rubika.ir/v3",
|
|
176
|
+
"messenger": "https://messengerg2b1.iranlms.ir/v3"
|
|
177
|
+
}
|
|
52
178
|
async def get_latest_version(package_name: str) -> Optional[str]:
|
|
53
|
-
"""Fetches the latest version of a package from PyPI asynchronously."""
|
|
54
179
|
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
55
180
|
try:
|
|
56
181
|
async with aiohttp.ClientSession() as session:
|
|
@@ -62,7 +187,6 @@ async def get_latest_version(package_name: str) -> Optional[str]:
|
|
|
62
187
|
return None
|
|
63
188
|
|
|
64
189
|
async def check_rubka_version():
|
|
65
|
-
"""Checks for outdated 'rubka' package and warns the user."""
|
|
66
190
|
package_name = "rubka"
|
|
67
191
|
installed_version = get_installed_version(package_name)
|
|
68
192
|
if installed_version is None:
|
|
@@ -73,19 +197,20 @@ async def check_rubka_version():
|
|
|
73
197
|
return
|
|
74
198
|
|
|
75
199
|
if installed_version != latest_version:
|
|
76
|
-
print(f"
|
|
77
|
-
print(
|
|
78
|
-
print(f"
|
|
79
|
-
print(f"
|
|
200
|
+
print(f"CRITICAL WARNING: Your installed version of '{package_name}' is outdated.")
|
|
201
|
+
print("This poses a serious risk to stability, security, and compatibility with current features.")
|
|
202
|
+
print(f"- Installed version : {installed_version}")
|
|
203
|
+
print(f"- Latest version : {latest_version}")
|
|
204
|
+
print("\nImmediate action is required.")
|
|
205
|
+
print(f"Run the following command to update safely:")
|
|
80
206
|
print(f"\npip install {package_name}=={latest_version}\n")
|
|
81
|
-
print("
|
|
82
|
-
print("
|
|
83
|
-
|
|
207
|
+
print("Delaying this update may result in unexpected crashes, data loss, or broken functionality.")
|
|
208
|
+
print("Stay up-to-date to ensure full support and access to the latest improvements.")
|
|
209
|
+
print("For new methods and updates, visit: @rubka_library\n")
|
|
84
210
|
|
|
85
211
|
|
|
86
212
|
|
|
87
213
|
def show_last_six_words(text: str) -> str:
|
|
88
|
-
"""Returns the last 6 characters of a stripped string."""
|
|
89
214
|
text = text.strip()
|
|
90
215
|
return text[-6:]
|
|
91
216
|
class AttrDict(dict):
|
|
@@ -97,60 +222,100 @@ class AttrDict(dict):
|
|
|
97
222
|
|
|
98
223
|
class Robot:
|
|
99
224
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
225
|
+
Main asynchronous class to interact with the Rubika Bot API.
|
|
226
|
+
|
|
227
|
+
This class handles sending and receiving messages, inline queries, callbacks,
|
|
228
|
+
and manages sessions and API interactions. It is initialized with a bot token
|
|
229
|
+
and provides multiple optional parameters for configuration.
|
|
230
|
+
|
|
231
|
+
Attributes:
|
|
232
|
+
token (str): Bot token used for authentication with Rubika Bot API.
|
|
233
|
+
session_name (str | None): Optional session name for storing session data.
|
|
234
|
+
auth (str | None): Optional authentication string for advanced features related to account key.
|
|
235
|
+
Key (str | None): Optional account key for additional authorization if required.
|
|
236
|
+
platform (str): Platform type, default is 'web'.
|
|
237
|
+
web_hook (str | None): Optional webhook URL for receiving updates.
|
|
238
|
+
timeout (int): Timeout for API requests in seconds (default 10).
|
|
239
|
+
show_progress (bool): Whether to show progress for long operations (default False).
|
|
240
|
+
raise_errors (bool): Whether to raise exceptions on API errors (default True).
|
|
241
|
+
proxy (str | None): Optional proxy URL to route requests through.
|
|
242
|
+
retries (int): Number of times to retry a failed API request (default 2).
|
|
243
|
+
retry_delay (float): Delay between retries in seconds (default 0.5).
|
|
244
|
+
user_agent (str | None): Custom User-Agent header for requests.
|
|
245
|
+
safeSendMode (bool): If True, messages are sent safely. If reply fails using message_id, sends without message_id (default False).
|
|
246
|
+
max_cache_size (int): Maximum number of processed messages stored to prevent duplicates (default 1000).
|
|
247
|
+
max_msg_age (int): Maximum age of messages in seconds to consider for processing (default 20).
|
|
248
|
+
|
|
249
|
+
Example:
|
|
250
|
+
```python
|
|
251
|
+
import asyncio
|
|
252
|
+
from rubka.asynco import Robot, filters, Message
|
|
253
|
+
|
|
254
|
+
bot = Robot(token="YOUR_BOT_TOKEN", safeSendMode=False, max_cache_size=1000)
|
|
255
|
+
|
|
256
|
+
@bot.on_message(filters.is_command.start)
|
|
257
|
+
async def start_command(bot: Robot, message: Message):
|
|
258
|
+
await message.reply("Hello!")
|
|
259
|
+
|
|
260
|
+
asyncio.run(bot.run())
|
|
261
|
+
```
|
|
262
|
+
Notes:
|
|
263
|
+
|
|
264
|
+
token is mandatory, all other parameters are optional.
|
|
265
|
+
|
|
266
|
+
safeSendMode ensures reliable message sending even if replying by message_id fails.
|
|
267
|
+
|
|
268
|
+
max_cache_size and max_msg_age help manage duplicate message processing efficiently.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
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,chunk_size : int = 64 * 1024,parse_mode: Optional[Literal["HTML", "Markdown"]] = "Markdown",api_endpoint: Optional[Literal["botapi", "messenger"]] = "botapi"):
|
|
128
273
|
self.token = token
|
|
129
274
|
self._inline_query_handlers: List[dict] = []
|
|
130
275
|
self.timeout = timeout
|
|
131
276
|
self.auth = auth
|
|
277
|
+
self.chunk_size = chunk_size
|
|
278
|
+
self.safeSendMode = safeSendMode
|
|
279
|
+
self.user_agent = user_agent
|
|
280
|
+
self.proxy = proxy
|
|
281
|
+
self.max_msg_age = max_msg_age
|
|
282
|
+
self.retries = retries
|
|
283
|
+
|
|
284
|
+
self.retry_delay = retry_delay
|
|
285
|
+
self.raise_errors = raise_errors
|
|
132
286
|
self.show_progress = show_progress
|
|
133
287
|
self.session_name = session_name
|
|
134
288
|
self.Key = Key
|
|
135
289
|
self.platform = platform
|
|
136
290
|
self.web_hook = web_hook
|
|
291
|
+
self.parse_mode = parse_mode
|
|
137
292
|
self._offset_id: Optional[str] = None
|
|
138
293
|
self._aiohttp_session: aiohttp.ClientSession = None
|
|
139
294
|
self.sessions: Dict[str, Dict[str, Any]] = {}
|
|
140
295
|
self._callback_handler = None
|
|
141
|
-
self.
|
|
142
|
-
self.
|
|
143
|
-
|
|
296
|
+
self._processed_message_ids = OrderedDict()
|
|
297
|
+
self._max_cache_size = max_cache_size
|
|
144
298
|
self._callback_handlers: List[dict] = []
|
|
145
|
-
self.
|
|
299
|
+
self._edited_message_handlers = []
|
|
300
|
+
self._message_saver_enabled = False
|
|
301
|
+
self._max_messages = None
|
|
302
|
+
self._db_path = os.path.join(os.getcwd(), "RubkaSaveMessage.db")
|
|
303
|
+
self._ensure_db()
|
|
146
304
|
self._message_handlers: List[dict] = []
|
|
305
|
+
if api_endpoint not in BASE_URLS:raise ValueError(f"api_endpoint must be one of {list(BASE_URLS.keys())}")
|
|
306
|
+
self.api_endpoint = api_endpoint
|
|
147
307
|
|
|
148
308
|
logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
|
|
149
309
|
async def _get_session(self) -> aiohttp.ClientSession:
|
|
150
|
-
"""Lazily creates and returns the aiohttp session."""
|
|
151
310
|
if self._aiohttp_session is None or self._aiohttp_session.closed:
|
|
152
|
-
|
|
311
|
+
connector = aiohttp.TCPConnector(limit=100, ssl=False)
|
|
312
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
|
313
|
+
self._aiohttp_session = aiohttp.ClientSession(connector=connector, timeout=timeout)
|
|
153
314
|
return self._aiohttp_session
|
|
315
|
+
async def close(self):
|
|
316
|
+
if self._aiohttp_session and not self._aiohttp_session.closed:
|
|
317
|
+
await self._aiohttp_session.close()
|
|
318
|
+
logger.debug("aiohttp session closed successfully.")
|
|
154
319
|
|
|
155
320
|
async def _initialize_webhook(self):
|
|
156
321
|
"""Initializes and sets the webhook endpoint if provided."""
|
|
@@ -162,9 +327,8 @@ class Robot:
|
|
|
162
327
|
async with session.get(self.web_hook, timeout=self.timeout) as response:
|
|
163
328
|
response.raise_for_status()
|
|
164
329
|
data = await response.json()
|
|
165
|
-
print(
|
|
330
|
+
if data:print(f"[INFO] Retrieving WebHook URL information...")
|
|
166
331
|
json_url = data.get('url', self.web_hook)
|
|
167
|
-
print(self.web_hook)
|
|
168
332
|
for endpoint_type in [
|
|
169
333
|
"ReceiveUpdate",
|
|
170
334
|
"ReceiveInlineMessage",
|
|
@@ -173,40 +337,209 @@ class Robot:
|
|
|
173
337
|
"SearchSelectionItems"
|
|
174
338
|
]:
|
|
175
339
|
result = await self.update_bot_endpoint(self.web_hook, endpoint_type)
|
|
176
|
-
print(result)
|
|
340
|
+
if result['status'] =="OK":print(f"✔ Set endpoint type to '{endpoint_type}' — Operation succeeded with status: {result['status']}")
|
|
341
|
+
else:print(f"[ERROR] Failed to set endpoint type '{endpoint_type}': Status code {result['status']}")
|
|
177
342
|
self.web_hook = json_url
|
|
178
343
|
except Exception as e:
|
|
179
344
|
logger.error(f"Failed to set webhook from {self.web_hook}: {e}")
|
|
180
345
|
self.web_hook = None
|
|
181
346
|
async def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
182
|
-
|
|
347
|
+
base_url = BASE_URLS[self.api_endpoint]
|
|
348
|
+
url = f"{base_url}/{self.token}/{method}"
|
|
349
|
+
print(url)
|
|
183
350
|
session = await self._get_session()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
351
|
+
for attempt in range(1, self.retries + 1):
|
|
352
|
+
try:
|
|
353
|
+
headers = {}
|
|
354
|
+
if self.user_agent:headers["User-Agent"] = self.user_agent
|
|
355
|
+
async with session.post(url, json=data, proxy=self.proxy,headers=headers) as response:
|
|
356
|
+
if response.status in (429, 500, 502, 503, 504):
|
|
357
|
+
logger.warning(f"[{method}] Got status {response.status}, retry {attempt}/{self.retries}...")
|
|
358
|
+
if attempt < self.retries:
|
|
359
|
+
await asyncio.sleep(self.retry_delay)
|
|
360
|
+
continue
|
|
361
|
+
response.raise_for_status()
|
|
362
|
+
|
|
363
|
+
response.raise_for_status()
|
|
364
|
+
try:
|
|
365
|
+
json_resp = await response.json(content_type=None)
|
|
366
|
+
except Exception:
|
|
367
|
+
text_resp = await response.text()
|
|
368
|
+
logger.error(f"[{method}] Invalid JSON response: {text_resp}")
|
|
369
|
+
raise APIRequestError(f"Invalid JSON response: {text_resp}")
|
|
370
|
+
|
|
371
|
+
status = json_resp.get("status")
|
|
372
|
+
if status in {"INVALID_ACCESS", "INVALID_INPUT", "TOO_REQUESTS"}:
|
|
373
|
+
if self.raise_errors:
|
|
374
|
+
raise_for_status(json_resp)
|
|
375
|
+
return AttrDict(json_resp)
|
|
376
|
+
return AttrDict({**json_resp, **data,"message_id":json_resp.get("data").get("message_id")})
|
|
377
|
+
|
|
378
|
+
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
|
379
|
+
logger.warning(f"[{method}] Attempt {attempt}/{self.retries} failed: {e}")
|
|
380
|
+
if attempt < self.retries:
|
|
381
|
+
await asyncio.sleep(self.retry_delay)
|
|
382
|
+
continue
|
|
383
|
+
logger.error(f"[{method}] API request failed after {self.retries} retries: {e}")
|
|
384
|
+
raise APIRequestError(f"API request failed: {e}") from e
|
|
385
|
+
def _make_dup_key(self, message_id: str, update_type: str, msg_data: dict) -> str:
|
|
386
|
+
raw = f"{message_id}:{update_type}:{msg_data.get('text','')}:{msg_data.get('author_guid','')}"
|
|
387
|
+
return hashlib.sha1(raw.encode()).hexdigest()
|
|
200
388
|
async def get_me(self) -> Dict[str, Any]:
|
|
201
|
-
"""Get info about the bot itself."""
|
|
202
389
|
return await self._post("getMe", {})
|
|
203
390
|
async def geteToken(self):
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
391
|
+
try:
|
|
392
|
+
if (await self.get_me())['status'] != "OK":
|
|
393
|
+
raise InvalidTokenError("The provided bot token is invalid or expired.")
|
|
394
|
+
except Exception as e:
|
|
395
|
+
print(e)
|
|
207
396
|
from typing import Callable, Any, Optional, List
|
|
208
397
|
|
|
209
398
|
|
|
399
|
+
#save message database __________________________
|
|
400
|
+
|
|
401
|
+
def _ensure_db(self):
|
|
402
|
+
conn = sqlite3.connect(self._db_path)
|
|
403
|
+
cur = conn.cursor()
|
|
404
|
+
cur.execute("""
|
|
405
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
406
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
407
|
+
chat_id TEXT NOT NULL,
|
|
408
|
+
message_id TEXT NOT NULL,
|
|
409
|
+
sender_id TEXT,
|
|
410
|
+
text TEXT,
|
|
411
|
+
raw_data TEXT,
|
|
412
|
+
time TEXT,
|
|
413
|
+
saved_at INTEGER
|
|
414
|
+
);
|
|
415
|
+
""")
|
|
416
|
+
cur.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_message ON messages(chat_id, message_id);")
|
|
417
|
+
conn.commit()
|
|
418
|
+
conn.close()
|
|
419
|
+
|
|
420
|
+
def _insert_message(self, record: dict):
|
|
421
|
+
conn = sqlite3.connect(self._db_path)
|
|
422
|
+
cur = conn.cursor()
|
|
423
|
+
cur.execute("""
|
|
424
|
+
INSERT OR IGNORE INTO messages
|
|
425
|
+
(chat_id, message_id, sender_id, text, raw_data, time, saved_at)
|
|
426
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
427
|
+
""", (
|
|
428
|
+
record.get("chat_id"),
|
|
429
|
+
record.get("message_id"),
|
|
430
|
+
record.get("sender_id"),
|
|
431
|
+
record.get("text"),
|
|
432
|
+
json.dumps(record.get("raw_data") or {}, ensure_ascii=False),
|
|
433
|
+
record.get("time"),
|
|
434
|
+
int(time.time())
|
|
435
|
+
))
|
|
436
|
+
conn.commit()
|
|
437
|
+
if getattr(self, "_max_messages", None) is not None:
|
|
438
|
+
cur.execute("SELECT COUNT(*) FROM messages")
|
|
439
|
+
total = cur.fetchone()[0]
|
|
440
|
+
if total > self._max_messages:
|
|
441
|
+
remove_count = total - self._max_messages
|
|
442
|
+
cur.execute(
|
|
443
|
+
"DELETE FROM messages WHERE id IN (SELECT id FROM messages ORDER BY saved_at ASC LIMIT ?)",
|
|
444
|
+
(remove_count,)
|
|
445
|
+
)
|
|
446
|
+
conn.commit()
|
|
447
|
+
|
|
448
|
+
conn.close()
|
|
449
|
+
|
|
450
|
+
def _fetch_message(self, chat_id: str, message_id: str):
|
|
451
|
+
conn = sqlite3.connect(self._db_path)
|
|
452
|
+
cur = conn.cursor()
|
|
453
|
+
cur.execute(
|
|
454
|
+
"SELECT chat_id, message_id, sender_id, text, raw_data, time, saved_at FROM messages WHERE chat_id=? AND message_id=?",
|
|
455
|
+
(chat_id, message_id)
|
|
456
|
+
)
|
|
457
|
+
row = cur.fetchone()
|
|
458
|
+
conn.close()
|
|
459
|
+
if not row:
|
|
460
|
+
return None
|
|
461
|
+
chat_id, message_id, sender_id, text, raw_data_json, time_val, saved_at = row
|
|
462
|
+
try:
|
|
463
|
+
raw = json.loads(raw_data_json)
|
|
464
|
+
except:
|
|
465
|
+
raw = {}
|
|
466
|
+
return {
|
|
467
|
+
"chat_id": chat_id,
|
|
468
|
+
"message_id": message_id,
|
|
469
|
+
"sender_id": sender_id,
|
|
470
|
+
"text": text,
|
|
471
|
+
"raw_data": raw,
|
|
472
|
+
"time": time_val,
|
|
473
|
+
"saved_at": saved_at
|
|
474
|
+
}
|
|
475
|
+
async def save_message(self, message: Message):
|
|
476
|
+
try:
|
|
477
|
+
record = {
|
|
478
|
+
"chat_id": getattr(message, "chat_id", None),
|
|
479
|
+
"message_id": getattr(message, "message_id", None),
|
|
480
|
+
"sender_id": getattr(message, "author_guid", None),
|
|
481
|
+
"text": getattr(message, "text", None),
|
|
482
|
+
"raw_data": getattr(message, "raw_data", {}),
|
|
483
|
+
"time": getattr(message, "time", None),
|
|
484
|
+
}
|
|
485
|
+
await asyncio.to_thread(self._insert_message, record)
|
|
486
|
+
except Exception as e:
|
|
487
|
+
print(f"[DB] Error saving message: {e}")
|
|
488
|
+
|
|
489
|
+
async def get_message(self, chat_id: str, message_id: str):
|
|
490
|
+
return await asyncio.to_thread(self._fetch_message, chat_id, message_id)
|
|
491
|
+
|
|
492
|
+
def start_save_message(self, max_messages: int = 1000):
|
|
493
|
+
if self._message_saver_enabled:
|
|
494
|
+
return
|
|
495
|
+
self._message_saver_enabled = True
|
|
496
|
+
self._max_messages = max_messages
|
|
497
|
+
decorators = [
|
|
498
|
+
"on_message", "on_edited_message", "on_message_file", "on_message_forwarded",
|
|
499
|
+
"on_message_reply", "on_message_text", "on_update", "on_callback",
|
|
500
|
+
"on_callback_query", "callback_query_handler", "callback_query",
|
|
501
|
+
"on_inline_query", "on_inline_query_prefix", "on_message_private", "on_message_group"
|
|
502
|
+
]
|
|
503
|
+
|
|
504
|
+
for decorator_name in decorators:
|
|
505
|
+
if hasattr(self, decorator_name):
|
|
506
|
+
original_decorator = getattr(self, decorator_name)
|
|
507
|
+
|
|
508
|
+
def make_wrapper(orig_decorator):
|
|
509
|
+
def wrapper(*args, **kwargs):
|
|
510
|
+
decorator = orig_decorator(*args, **kwargs)
|
|
511
|
+
def inner_wrapper(func):
|
|
512
|
+
async def inner(bot, message, *a, **kw):
|
|
513
|
+
try:
|
|
514
|
+
await bot.save_message(message)
|
|
515
|
+
if getattr(self, "_max_messages", None) is not None:
|
|
516
|
+
await asyncio.to_thread(self._prune_old_messages)
|
|
517
|
+
except Exception as e:
|
|
518
|
+
print(f"[DB] Save error: {e}")
|
|
519
|
+
return await func(bot, message, *a, **kw)
|
|
520
|
+
return decorator(inner)
|
|
521
|
+
return inner_wrapper
|
|
522
|
+
return wrapper
|
|
523
|
+
|
|
524
|
+
setattr(self, decorator_name, make_wrapper(original_decorator))
|
|
525
|
+
def _prune_old_messages(self):
|
|
526
|
+
if not hasattr(self, "_max_messages") or self._max_messages is None:
|
|
527
|
+
return
|
|
528
|
+
conn = sqlite3.connect(self._db_path)
|
|
529
|
+
cur = conn.cursor()
|
|
530
|
+
cur.execute("SELECT COUNT(*) FROM messages")
|
|
531
|
+
total = cur.fetchone()[0]
|
|
532
|
+
if total > self._max_messages:
|
|
533
|
+
remove_count = total - self._max_messages
|
|
534
|
+
cur.execute(
|
|
535
|
+
"DELETE FROM messages WHERE id IN (SELECT id FROM messages ORDER BY saved_at ASC LIMIT ?)",
|
|
536
|
+
(remove_count,)
|
|
537
|
+
)
|
|
538
|
+
conn.commit()
|
|
539
|
+
conn.close()
|
|
540
|
+
|
|
541
|
+
#save message database __________________________ end
|
|
542
|
+
|
|
210
543
|
#decorator#
|
|
211
544
|
|
|
212
545
|
def on_message_private(
|
|
@@ -222,7 +555,7 @@ class Robot:
|
|
|
222
555
|
allow_polls: bool = True,
|
|
223
556
|
allow_contacts: bool = True,
|
|
224
557
|
allow_locations: bool = True,
|
|
225
|
-
min_text_length: Optional[int] = None,
|
|
558
|
+
min_text_length: Optional[int] = None,
|
|
226
559
|
max_text_length: Optional[int] = None,
|
|
227
560
|
contains: Optional[str] = None,
|
|
228
561
|
startswith: Optional[str] = None,
|
|
@@ -409,6 +742,7 @@ class Robot:
|
|
|
409
742
|
|
|
410
743
|
def decorator(func: Callable[[Any, Message], None]):
|
|
411
744
|
async def wrapper(bot, message: Message):
|
|
745
|
+
|
|
412
746
|
if not message.is_group:
|
|
413
747
|
return
|
|
414
748
|
if chat_id:
|
|
@@ -469,27 +803,66 @@ class Robot:
|
|
|
469
803
|
})
|
|
470
804
|
return wrapper
|
|
471
805
|
return decorator
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
806
|
+
def remove_handler(self, func: Callable):
|
|
807
|
+
"""
|
|
808
|
+
Remove a message handler by its original function reference.
|
|
809
|
+
"""
|
|
810
|
+
self._message_handlers = [
|
|
811
|
+
h for h in self._message_handlers if h["func"].__wrapped__ != func
|
|
812
|
+
]
|
|
813
|
+
def on_edited_message(
|
|
814
|
+
self,
|
|
815
|
+
filters: Optional[Callable[[Message], bool]] = None,
|
|
476
816
|
commands: Optional[List[str]] = None
|
|
477
|
-
|
|
817
|
+
):
|
|
478
818
|
def decorator(func: Callable[[Any, Message], None]):
|
|
479
819
|
async def wrapper(bot, message: Message):
|
|
480
|
-
if filters and not filters(message):
|
|
820
|
+
if filters and not filters(message):
|
|
821
|
+
return
|
|
481
822
|
if commands:
|
|
482
|
-
if not message.is_command:
|
|
823
|
+
if not message.is_command:
|
|
824
|
+
return
|
|
483
825
|
cmd = message.text.split()[0].lstrip("/")
|
|
484
|
-
if cmd not in commands:
|
|
826
|
+
if cmd not in commands:
|
|
827
|
+
return
|
|
485
828
|
return await func(bot, message)
|
|
486
|
-
|
|
829
|
+
|
|
830
|
+
self._edited_message_handlers.append({
|
|
487
831
|
"func": wrapper,
|
|
488
832
|
"filters": filters,
|
|
489
833
|
"commands": commands
|
|
490
834
|
})
|
|
491
835
|
return wrapper
|
|
492
836
|
return decorator
|
|
837
|
+
def on_message(
|
|
838
|
+
self,
|
|
839
|
+
filters: Optional[Callable[[Message], bool]] = None,
|
|
840
|
+
commands: Optional[List[str]] = None):
|
|
841
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
842
|
+
async def wrapper(bot, message: Message):
|
|
843
|
+
if filters and not filters(message):
|
|
844
|
+
return
|
|
845
|
+
if commands:
|
|
846
|
+
if not message.is_command:
|
|
847
|
+
return
|
|
848
|
+
cmd = message.text.split()[0].lstrip("/")
|
|
849
|
+
if cmd not in commands:
|
|
850
|
+
return
|
|
851
|
+
|
|
852
|
+
return await func(bot, message)
|
|
853
|
+
self._message_handlers.append({
|
|
854
|
+
"func": wrapper,
|
|
855
|
+
"filters": filters,
|
|
856
|
+
"commands": commands
|
|
857
|
+
})
|
|
858
|
+
self._edited_message_handlers.append({
|
|
859
|
+
"func": wrapper,
|
|
860
|
+
"filters": filters,
|
|
861
|
+
"commands": commands
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
return wrapper
|
|
865
|
+
return decorator
|
|
493
866
|
|
|
494
867
|
|
|
495
868
|
def on_message_file(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
@@ -759,9 +1132,36 @@ class Robot:
|
|
|
759
1132
|
asyncio.create_task(handler_info["func"](self, context))
|
|
760
1133
|
continue
|
|
761
1134
|
if handler_info["commands"] or handler_info["filters"]:
|
|
762
|
-
asyncio.create_task(handler_info["func"](self, context))#
|
|
1135
|
+
asyncio.create_task(handler_info["func"](self, context))#kir baba kir
|
|
763
1136
|
continue
|
|
1137
|
+
elif update.get("type") == "UpdatedMessage":
|
|
1138
|
+
msg = update.get("updated_message", {})
|
|
1139
|
+
if not msg:
|
|
1140
|
+
return
|
|
764
1141
|
|
|
1142
|
+
context = Message(
|
|
1143
|
+
bot=self,
|
|
1144
|
+
chat_id=update.get("chat_id"),
|
|
1145
|
+
message_id=msg.get("message_id"),
|
|
1146
|
+
text=msg.get("text"),
|
|
1147
|
+
sender_id=msg.get("sender_id"),
|
|
1148
|
+
raw_data=msg
|
|
1149
|
+
)
|
|
1150
|
+
if self._edited_message_handlers:
|
|
1151
|
+
for handler_info in self._edited_message_handlers:
|
|
1152
|
+
if handler_info["commands"]:
|
|
1153
|
+
if not context.text or not context.text.startswith("/"):
|
|
1154
|
+
continue
|
|
1155
|
+
parts = context.text.split()
|
|
1156
|
+
cmd = parts[0][1:]
|
|
1157
|
+
if cmd not in handler_info["commands"]:
|
|
1158
|
+
continue
|
|
1159
|
+
context.args = parts[1:]
|
|
1160
|
+
if handler_info["filters"]:
|
|
1161
|
+
if not handler_info["filters"](context):
|
|
1162
|
+
continue
|
|
1163
|
+
asyncio.create_task(handler_info["func"](self, context))
|
|
1164
|
+
|
|
765
1165
|
async def get_updates(self, offset_id: Optional[str] = None, limit: Optional[int] = None) -> Dict[str, Any]:
|
|
766
1166
|
data = {}
|
|
767
1167
|
if offset_id: data["offset_id"] = offset_id
|
|
@@ -777,63 +1177,66 @@ class Robot:
|
|
|
777
1177
|
response.raise_for_status()
|
|
778
1178
|
return await response.json()
|
|
779
1179
|
|
|
780
|
-
def _is_duplicate(self,
|
|
1180
|
+
def _is_duplicate(self, key: str, max_age_sec: int = 300) -> bool:
|
|
781
1181
|
now = time.time()
|
|
782
1182
|
expired = [mid for mid, ts in self._processed_message_ids.items() if now - ts > max_age_sec]
|
|
783
|
-
for mid in expired:
|
|
784
|
-
|
|
785
|
-
self._processed_message_ids
|
|
1183
|
+
for mid in expired:
|
|
1184
|
+
del self._processed_message_ids[mid]
|
|
1185
|
+
if key in self._processed_message_ids:
|
|
1186
|
+
return True
|
|
1187
|
+
self._processed_message_ids[key] = now
|
|
1188
|
+
if len(self._processed_message_ids) > self._max_cache_size:
|
|
1189
|
+
self._processed_message_ids.popitem(last=False)
|
|
786
1190
|
return False
|
|
787
|
-
|
|
788
1191
|
|
|
789
|
-
async def
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1192
|
+
async def run_progelry(
|
|
1193
|
+
self,
|
|
1194
|
+
debug: bool = False,
|
|
1195
|
+
sleep_time: float = 0.1,
|
|
1196
|
+
webhook_timeout: int = 20,
|
|
1197
|
+
update_limit: int = 100,
|
|
1198
|
+
retry_delay: float = 5.0,
|
|
1199
|
+
stop_on_error: bool = False,
|
|
1200
|
+
max_errors: int = 0,
|
|
1201
|
+
auto_restart: bool = False,
|
|
1202
|
+
max_runtime: Optional[float] = None,
|
|
1203
|
+
loop_forever: bool = True,
|
|
1204
|
+
allowed_update_types: Optional[List[str]] = None,
|
|
1205
|
+
ignore_duplicate_messages: bool = True,
|
|
1206
|
+
skip_inline_queries: bool = False,
|
|
1207
|
+
skip_channel_posts: bool = False,
|
|
1208
|
+
skip_service_messages: bool = False,
|
|
1209
|
+
skip_edited_messages: bool = False,
|
|
1210
|
+
skip_bot_messages: bool = False,
|
|
1211
|
+
log_file: Optional[str] = None,
|
|
1212
|
+
log_level: str = "info",
|
|
1213
|
+
print_exceptions: bool = True,
|
|
1214
|
+
error_handler: Optional[Callable[[Exception], Any]] = None,
|
|
1215
|
+
shutdown_hook: Optional[Callable[[], Any]] = None,
|
|
1216
|
+
save_unprocessed_updates: bool = False,
|
|
1217
|
+
log_to_console: bool = True,
|
|
1218
|
+
rate_limit: Optional[float] = None,
|
|
1219
|
+
max_message_size: Optional[int] = None,
|
|
1220
|
+
ignore_users: Optional[Set[str]] = None,
|
|
1221
|
+
ignore_groups: Optional[Set[str]] = None,
|
|
1222
|
+
require_auth_token: bool = False,
|
|
1223
|
+
only_private_chats: bool = False,
|
|
1224
|
+
only_groups: bool = False,
|
|
1225
|
+
require_admin_rights: bool = False,
|
|
1226
|
+
custom_update_fetcher: Optional[Callable[[], Any]] = None,
|
|
1227
|
+
custom_update_processor: Optional[Callable[[Any], Any]] = None,
|
|
1228
|
+
process_in_background: bool = False,
|
|
1229
|
+
max_queue_size: int = 1000,
|
|
1230
|
+
thread_workers: int = 3,
|
|
1231
|
+
message_filter: Optional[Callable[[Any], bool]] = None,
|
|
1232
|
+
pause_on_idle: bool = False,
|
|
1233
|
+
max_concurrent_tasks: Optional[int] = None,
|
|
1234
|
+
metrics_enabled: bool = False,
|
|
1235
|
+
metrics_handler: Optional[Callable[[dict], Any]] = None,
|
|
1236
|
+
notify_on_error: bool = False,
|
|
1237
|
+
notification_handler: Optional[Callable[[str], Any]] = None,
|
|
1238
|
+
watchdog_timeout: Optional[float] = None,
|
|
1239
|
+
):
|
|
837
1240
|
"""
|
|
838
1241
|
Starts the bot's main execution loop with extensive configuration options.
|
|
839
1242
|
|
|
@@ -976,12 +1379,16 @@ class Robot:
|
|
|
976
1379
|
im = update.get("inline_message", {})
|
|
977
1380
|
sender = im.get("author_object_guid") or im.get("author_guid")
|
|
978
1381
|
chat = im.get("object_guid") or im.get("chat_id")
|
|
1382
|
+
elif t == "UpdatedMessage":
|
|
1383
|
+
im = update.get("updated_message", {})
|
|
1384
|
+
sender = im.get("author_object_guid") or im.get("author_guid")
|
|
1385
|
+
chat = im.get("object_guid") or im.get("chat_id")
|
|
979
1386
|
else:
|
|
980
1387
|
sender = update.get("author_guid") or update.get("from_id")
|
|
981
1388
|
chat = update.get("object_guid") or update.get("chat_id")
|
|
982
1389
|
return str(sender) if sender is not None else None, str(chat) if chat is not None else None
|
|
983
1390
|
|
|
984
|
-
def _is_group_chat(chat_guid: str
|
|
1391
|
+
def _is_group_chat(chat_guid: Optional[str]) -> Optional[bool]:
|
|
985
1392
|
|
|
986
1393
|
if chat_guid is None:
|
|
987
1394
|
return None
|
|
@@ -1049,8 +1456,6 @@ class Robot:
|
|
|
1049
1456
|
return False
|
|
1050
1457
|
if skip_service_messages and t == "ServiceMessage":
|
|
1051
1458
|
return False
|
|
1052
|
-
if skip_edited_messages and t == "EditMessage":
|
|
1053
|
-
return False
|
|
1054
1459
|
if skip_channel_posts and t == "ChannelPost":
|
|
1055
1460
|
return False
|
|
1056
1461
|
|
|
@@ -1080,6 +1485,8 @@ class Robot:
|
|
|
1080
1485
|
content = (update.get("new_message") or {}).get("text")
|
|
1081
1486
|
elif t == "ReceiveQuery":
|
|
1082
1487
|
content = (update.get("inline_message") or {}).get("text")
|
|
1488
|
+
elif t == "UpdatedMessage":
|
|
1489
|
+
content = (update.get("updated_message") or {}).get("text")
|
|
1083
1490
|
elif "text" in update:
|
|
1084
1491
|
content = update.get("text")
|
|
1085
1492
|
if content and isinstance(content, str) and len(content) > max_message_size:
|
|
@@ -1142,7 +1549,7 @@ class Robot:
|
|
|
1142
1549
|
await check_rubka_version()
|
|
1143
1550
|
await self._initialize_webhook()
|
|
1144
1551
|
await self.geteToken()
|
|
1145
|
-
_log("Bot
|
|
1552
|
+
_log("Bot is up and running...", "info")
|
|
1146
1553
|
|
|
1147
1554
|
try:
|
|
1148
1555
|
while True:
|
|
@@ -1196,6 +1603,8 @@ class Robot:
|
|
|
1196
1603
|
message_id = update.get("new_message", {}).get("message_id")
|
|
1197
1604
|
elif update.get("type") == "ReceiveQuery":
|
|
1198
1605
|
message_id = update.get("inline_message", {}).get("message_id")
|
|
1606
|
+
elif update.get("type") == "UpdatedMessage":
|
|
1607
|
+
message_id = update.get("updated_message", {}).get("message_id")
|
|
1199
1608
|
elif "message_id" in update:
|
|
1200
1609
|
message_id = update.get("message_id")
|
|
1201
1610
|
|
|
@@ -1216,26 +1625,41 @@ class Robot:
|
|
|
1216
1625
|
for update in updates:
|
|
1217
1626
|
message_id = None
|
|
1218
1627
|
if update.get("type") == "NewMessage":
|
|
1219
|
-
|
|
1628
|
+
msg_data = update.get("new_message", {})
|
|
1629
|
+
message_id = msg_data.get("message_id")
|
|
1630
|
+
text_content = msg_data.get("text", "")
|
|
1631
|
+
msg_time = int(msg_data.get("time", 0))
|
|
1220
1632
|
elif update.get("type") == "ReceiveQuery":
|
|
1221
|
-
|
|
1633
|
+
msg_data = update.get("inline_message", {})
|
|
1634
|
+
message_id = msg_data.get("message_id")
|
|
1635
|
+
text_content = msg_data.get("text", "")
|
|
1636
|
+
msg_time = int(msg_data.get("time", 0))
|
|
1637
|
+
elif update.get("type") == "UpdatedMessage":
|
|
1638
|
+
msg_data = update.get("updated_message", {})
|
|
1639
|
+
message_id = msg_data.get("message_id")
|
|
1640
|
+
text_content = msg_data.get("text", "")
|
|
1641
|
+
msg_time = int(msg_data.get("time", 0))
|
|
1222
1642
|
elif "message_id" in update:
|
|
1223
1643
|
message_id = update.get("message_id")
|
|
1224
|
-
|
|
1644
|
+
else:
|
|
1645
|
+
msg_time = time.time()
|
|
1646
|
+
msg_data = update.get("updated_message", {})
|
|
1647
|
+
message_id = msg_data.get("message_id")
|
|
1648
|
+
text_content = msg_data.get("text", "")
|
|
1649
|
+
now = int(time.time())
|
|
1650
|
+
|
|
1651
|
+
if msg_time and (now - msg_time > self.max_msg_age):
|
|
1652
|
+
continue
|
|
1225
1653
|
dup_ok = True
|
|
1226
|
-
if ignore_duplicate_messages:
|
|
1227
|
-
|
|
1654
|
+
if ignore_duplicate_messages and message_id:
|
|
1655
|
+
dup_key = self._make_dup_key(message_id, update.get("type", ""), msg_data)
|
|
1656
|
+
dup_ok = not self._is_duplicate(dup_key)
|
|
1228
1657
|
if message_id and dup_ok:
|
|
1229
1658
|
received_updates.append(update)
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
1659
|
if not received_updates:
|
|
1233
|
-
if pause_on_idle and sleep_time == 0:
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
await asyncio.sleep(sleep_time)
|
|
1237
|
-
if not loop_forever and max_runtime is None:
|
|
1238
|
-
break
|
|
1660
|
+
if pause_on_idle and sleep_time == 0:await asyncio.sleep(0.005)
|
|
1661
|
+
else:await asyncio.sleep(sleep_time)
|
|
1662
|
+
if not loop_forever and max_runtime is None:break
|
|
1239
1663
|
continue
|
|
1240
1664
|
|
|
1241
1665
|
|
|
@@ -1352,22 +1776,87 @@ class Robot:
|
|
|
1352
1776
|
|
|
1353
1777
|
|
|
1354
1778
|
_log("Auto-restart requested. You can call run(...) again as needed.", "warning")
|
|
1355
|
-
|
|
1779
|
+
def run(self, sleep_time: float = 0.1, *args, **kwargs):
|
|
1780
|
+
print("Connecting to the server...")
|
|
1781
|
+
try:
|
|
1782
|
+
loop = asyncio.get_running_loop()
|
|
1783
|
+
return loop.create_task(self.run_progelry(sleep_time=sleep_time, *args, **kwargs))
|
|
1784
|
+
except RuntimeError:return asyncio.run(self.run_progelry(sleep_time=sleep_time, *args, **kwargs))
|
|
1785
|
+
async def _delete_after_task(self, chat_id: str, message_id: str, delay: int):
|
|
1786
|
+
try:
|
|
1787
|
+
await asyncio.sleep(delay)
|
|
1788
|
+
await self.delete_message(chat_id=chat_id, message_id=message_id)
|
|
1789
|
+
except Exception:
|
|
1790
|
+
return False
|
|
1791
|
+
async def _edit_after_task(self, chat_id: str, message_id: str, text:str, delay: int):
|
|
1792
|
+
try:
|
|
1793
|
+
await asyncio.sleep(delay)
|
|
1794
|
+
await self.edit_message_text(chat_id=chat_id, message_id=message_id,text=text)
|
|
1795
|
+
except Exception:
|
|
1796
|
+
return False
|
|
1797
|
+
|
|
1798
|
+
async def delete_after(self, chat_id: str, message_id: str, delay: int = 30) -> asyncio.Task:
|
|
1799
|
+
async def _task():
|
|
1800
|
+
await asyncio.sleep(delay)
|
|
1801
|
+
try:
|
|
1802
|
+
await self.delete_message(chat_id, message_id)
|
|
1803
|
+
except Exception:
|
|
1804
|
+
pass
|
|
1805
|
+
|
|
1806
|
+
try:
|
|
1807
|
+
loop = asyncio.get_running_loop()
|
|
1808
|
+
except RuntimeError:
|
|
1809
|
+
loop = asyncio.new_event_loop()
|
|
1810
|
+
asyncio.set_event_loop(loop)
|
|
1811
|
+
|
|
1812
|
+
task = loop.create_task(_task())
|
|
1813
|
+
return task
|
|
1814
|
+
|
|
1815
|
+
async def edit_after(self, chat_id: str, message_id: str, text : str, delay: int = 30) -> asyncio.Task:
|
|
1816
|
+
async def _task():
|
|
1817
|
+
await asyncio.sleep(delay)
|
|
1818
|
+
try:
|
|
1819
|
+
await self.edit_message_text(chat_id, message_id,text)
|
|
1820
|
+
except Exception:
|
|
1821
|
+
pass
|
|
1822
|
+
|
|
1823
|
+
try:
|
|
1824
|
+
loop = asyncio.get_running_loop()
|
|
1825
|
+
except RuntimeError:
|
|
1826
|
+
loop = asyncio.new_event_loop()
|
|
1827
|
+
asyncio.set_event_loop(loop)
|
|
1828
|
+
|
|
1829
|
+
task = loop.create_task(_task())
|
|
1830
|
+
return task
|
|
1831
|
+
def _parse_text_metadata(self, text: str, parse_mode: str):
|
|
1832
|
+
formatter = GlyphWeaver()
|
|
1833
|
+
parsed = formatter.parse(text, parse_mode)
|
|
1834
|
+
return parsed.get("text"), parsed.get("metadata")
|
|
1835
|
+
|
|
1356
1836
|
async def send_message(
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1837
|
+
self,
|
|
1838
|
+
chat_id: str,
|
|
1839
|
+
text: str,
|
|
1840
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
1841
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
1842
|
+
disable_notification: bool = False,
|
|
1843
|
+
reply_to_message_id: Optional[str] = None,
|
|
1844
|
+
chat_keypad_type: Optional[Literal["New", "Remove"]] = None,
|
|
1845
|
+
delete_after: Optional[int] = None,
|
|
1846
|
+
parse_mode: Optional[Literal["HTML", "Markdown"]] = None
|
|
1365
1847
|
) -> Dict[str, Any]:
|
|
1848
|
+
|
|
1366
1849
|
payload = {
|
|
1367
1850
|
"chat_id": chat_id,
|
|
1368
1851
|
"text": text,
|
|
1369
1852
|
"disable_notification": disable_notification,
|
|
1370
1853
|
}
|
|
1854
|
+
parse_mode_to_use = parse_mode or self.parse_mode
|
|
1855
|
+
if text:
|
|
1856
|
+
text, metadata = self._parse_text_metadata(text, parse_mode_to_use)
|
|
1857
|
+
payload["text"] = text
|
|
1858
|
+
if metadata:
|
|
1859
|
+
payload["metadata"] = metadata
|
|
1371
1860
|
if chat_keypad:
|
|
1372
1861
|
payload["chat_keypad"] = chat_keypad
|
|
1373
1862
|
payload["chat_keypad_type"] = chat_keypad_type or "New"
|
|
@@ -1375,7 +1864,56 @@ class Robot:
|
|
|
1375
1864
|
payload["inline_keypad"] = inline_keypad
|
|
1376
1865
|
if reply_to_message_id:
|
|
1377
1866
|
payload["reply_to_message_id"] = reply_to_message_id
|
|
1378
|
-
|
|
1867
|
+
try:
|
|
1868
|
+
state = await self._post("sendMessage", payload)
|
|
1869
|
+
except Exception:
|
|
1870
|
+
if self.safeSendMode and reply_to_message_id:
|
|
1871
|
+
payload.pop("reply_to_message_id", None)
|
|
1872
|
+
state = await self._post("sendMessage", payload)
|
|
1873
|
+
else:
|
|
1874
|
+
raise
|
|
1875
|
+
if delete_after:
|
|
1876
|
+
await self.delete_after(chat_id, state.message_id, delete_after)
|
|
1877
|
+
return state
|
|
1878
|
+
|
|
1879
|
+
|
|
1880
|
+
async def send_sticker(
|
|
1881
|
+
self,
|
|
1882
|
+
chat_id: str,
|
|
1883
|
+
sticker_id: str,
|
|
1884
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
1885
|
+
disable_notification: bool = False,
|
|
1886
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
1887
|
+
reply_to_message_id: Optional[str] = None,
|
|
1888
|
+
chat_keypad_type: Optional[Literal['New', 'Remove']] = None,
|
|
1889
|
+
) -> str:
|
|
1890
|
+
"""
|
|
1891
|
+
Send a sticker to a chat.
|
|
1892
|
+
|
|
1893
|
+
Args:
|
|
1894
|
+
token: Bot token.
|
|
1895
|
+
chat_id: Target chat ID.
|
|
1896
|
+
sticker_id: ID of the sticker to send.
|
|
1897
|
+
chat_keypad: Optional chat keypad data.
|
|
1898
|
+
disable_notification: If True, disables notification.
|
|
1899
|
+
inline_keypad: Optional inline keyboard data.
|
|
1900
|
+
reply_to_message_id: Optional message ID to reply to.
|
|
1901
|
+
chat_keypad_type: Type of chat keypad change ('New' or 'Remove').
|
|
1902
|
+
|
|
1903
|
+
Returns:
|
|
1904
|
+
API response as a string.
|
|
1905
|
+
"""
|
|
1906
|
+
data = {
|
|
1907
|
+
'chat_id': chat_id,
|
|
1908
|
+
'sticker_id': sticker_id,
|
|
1909
|
+
'chat_keypad': chat_keypad,
|
|
1910
|
+
'disable_notification': disable_notification,
|
|
1911
|
+
'inline_keypad': inline_keypad,
|
|
1912
|
+
'reply_to_message_id': reply_to_message_id,
|
|
1913
|
+
'chat_keypad_type': chat_keypad_type,
|
|
1914
|
+
}
|
|
1915
|
+
return await self._post("sendSticker", data)
|
|
1916
|
+
|
|
1379
1917
|
|
|
1380
1918
|
async def get_url_file(self,file_id):
|
|
1381
1919
|
data = await self._post("getFile", {'file_id': file_id})
|
|
@@ -1386,22 +1924,6 @@ class Robot:
|
|
|
1386
1924
|
return Client_get(self.session_name, self.auth, self.Key, self.platform)
|
|
1387
1925
|
else:
|
|
1388
1926
|
return Client_get(show_last_six_words(self.token), self.auth, self.Key, self.platform)
|
|
1389
|
-
|
|
1390
|
-
async def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
|
|
1391
|
-
client = self._get_client()
|
|
1392
|
-
if chat_id:
|
|
1393
|
-
chat_info_data = await self.get_chat(chat_id)
|
|
1394
|
-
chat_info = chat_info_data.get('data', {}).get('chat', {})
|
|
1395
|
-
username = chat_info.get('username')
|
|
1396
|
-
user_id = chat_info.get('user_id')
|
|
1397
|
-
if username:
|
|
1398
|
-
result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
|
|
1399
|
-
members = result.get('in_chat_members', [])
|
|
1400
|
-
return any(m.get('username') == username for m in members)
|
|
1401
|
-
elif user_id:
|
|
1402
|
-
member_guids = await asyncio.to_thread(client.get_all_members, channel_guid, just_get_guids=True)
|
|
1403
|
-
return user_id in member_guids
|
|
1404
|
-
return False
|
|
1405
1927
|
async def send_button_join(
|
|
1406
1928
|
self,
|
|
1407
1929
|
chat_id,
|
|
@@ -1487,57 +2009,55 @@ class Robot:
|
|
|
1487
2009
|
inline_keypad=builder.build(),
|
|
1488
2010
|
reply_to_message_id=reply_to_message_id
|
|
1489
2011
|
)
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
return
|
|
1493
|
-
async def
|
|
1494
|
-
return await self._post("sendPoll", {"chat_id": chat_id, "question": question, "options": options})
|
|
1495
|
-
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]:
|
|
2012
|
+
|
|
2013
|
+
async def close_poll(self, chat_id: str, message_id: str) -> Dict[str, Any]:
|
|
2014
|
+
return await self._post("closePoll", {"chat_id": chat_id, "message_id": message_id})
|
|
2015
|
+
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", "Remove"]] = None) -> Dict[str, Any]:
|
|
1496
2016
|
payload = {"chat_id": chat_id, "latitude": latitude, "longitude": longitude, "disable_notification": disable_notification}
|
|
1497
2017
|
if inline_keypad: payload["inline_keypad"] = inline_keypad
|
|
1498
2018
|
if reply_to_message_id: payload["reply_to_message_id"] = reply_to_message_id
|
|
1499
2019
|
if chat_keypad_type: payload["chat_keypad_type"] = chat_keypad_type
|
|
1500
2020
|
return await self._post("sendLocation", {k: v for k, v in payload.items() if v is not None})
|
|
1501
|
-
async def send_contact(self, chat_id: str, first_name: str, last_name: str, phone_number: str) -> Dict[str, Any]:
|
|
1502
|
-
return await self._post("sendContact", {"chat_id": chat_id, "first_name": first_name, "last_name": last_name, "phone_number": phone_number})
|
|
1503
|
-
async def get_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
1504
|
-
return await self._post("getChat", {"chat_id": chat_id})
|
|
1505
2021
|
async def upload_media_file(self, upload_url: str, name: str, path: Union[str, Path]) -> str:
|
|
1506
|
-
is_temp_file = False
|
|
1507
2022
|
session = await self._get_session()
|
|
2023
|
+
is_temp_file = False
|
|
1508
2024
|
if isinstance(path, str) and path.startswith("http"):
|
|
1509
2025
|
async with session.get(path) as response:
|
|
1510
2026
|
if response.status != 200:
|
|
1511
2027
|
raise Exception(f"Failed to download file from URL ({response.status})")
|
|
1512
2028
|
content = await response.read()
|
|
1513
|
-
with tempfile.NamedTemporaryFile(delete=False) as
|
|
1514
|
-
|
|
1515
|
-
path =
|
|
2029
|
+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
|
2030
|
+
tmp.write(content)
|
|
2031
|
+
path = tmp.name
|
|
1516
2032
|
is_temp_file = True
|
|
2033
|
+
|
|
1517
2034
|
file_size = os.path.getsize(path)
|
|
1518
|
-
|
|
1519
|
-
|
|
2035
|
+
chunk_size = self.chunk_size
|
|
2036
|
+
|
|
2037
|
+
progress_bar = tqdm(total=file_size, unit='B', unit_scale=True, unit_divisor=1024,
|
|
2038
|
+
desc=f'Uploading: {name}', colour='cyan', disable=not getattr(self, 'show_progress', True))
|
|
2039
|
+
|
|
2040
|
+
async def file_generator(file_path):
|
|
1520
2041
|
async with aiofiles.open(file_path, 'rb') as f:
|
|
1521
|
-
while
|
|
1522
|
-
chunk = await f.read(chunk_size)
|
|
1523
|
-
if not chunk:
|
|
1524
|
-
break
|
|
2042
|
+
while chunk := await f.read(chunk_size):
|
|
1525
2043
|
progress_bar.update(len(chunk))
|
|
1526
2044
|
yield chunk
|
|
1527
|
-
|
|
1528
|
-
|
|
2045
|
+
|
|
2046
|
+
form = aiohttp.FormData()
|
|
2047
|
+
form.add_field('file', file_generator(path), filename=name, content_type='application/octet-stream')
|
|
2048
|
+
|
|
1529
2049
|
try:
|
|
1530
|
-
async with session.post(upload_url, data=
|
|
2050
|
+
async with session.post(upload_url, data=form, timeout=aiohttp.ClientTimeout(total=None)) as response:
|
|
1531
2051
|
progress_bar.close()
|
|
1532
2052
|
if response.status != 200:
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
2053
|
+
text = await response.text()
|
|
2054
|
+
raise Exception(f"Upload failed ({response.status}): {text}")
|
|
2055
|
+
return (await response.json()).get('data', {}).get('file_id')
|
|
2056
|
+
except Exception as e:
|
|
2057
|
+
raise FeatureNotAvailableError(f"File upload not supported: {e}")
|
|
2058
|
+
finally:
|
|
2059
|
+
if is_temp_file:
|
|
2060
|
+
os.remove(path)
|
|
1541
2061
|
def get_extension(content_type: str) -> str:
|
|
1542
2062
|
ext = mimetypes.guess_extension(content_type)
|
|
1543
2063
|
return ext if ext else ''
|
|
@@ -1617,19 +2137,22 @@ class Robot:
|
|
|
1617
2137
|
raise asyncio.TimeoutError("The download operation timed out.")
|
|
1618
2138
|
except Exception as e:
|
|
1619
2139
|
raise Exception(f"An error occurred while downloading the file: {e}")
|
|
1620
|
-
|
|
1621
2140
|
async def get_upload_url(self, media_type: Literal['File', 'Image', 'voice', 'Music', 'Gif', 'Video']) -> str:
|
|
1622
2141
|
allowed = ['File', 'Image', 'voice', 'Music', 'Gif', 'Video']
|
|
1623
2142
|
if media_type not in allowed:
|
|
1624
2143
|
raise ValueError(f"Invalid media type. Must be one of {allowed}")
|
|
1625
2144
|
result = await self._post("requestSendFile", {"type": media_type})
|
|
1626
2145
|
return result.get("data", {}).get("upload_url")
|
|
1627
|
-
|
|
1628
|
-
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]:
|
|
2146
|
+
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", "Remove", "None"]] = "None",parse_mode: Optional[Literal["HTML", "Markdown"]] = None) -> Dict[str, Any]:
|
|
1629
2147
|
payload = {"chat_id": chat_id, "file_id": file_id, "text": text, "disable_notification": disable_notification, "chat_keypad_type": chat_keypad_type}
|
|
1630
2148
|
if chat_keypad: payload["chat_keypad"] = chat_keypad
|
|
1631
2149
|
if inline_keypad: payload["inline_keypad"] = inline_keypad
|
|
1632
2150
|
if reply_to_message_id: payload["reply_to_message_id"] = str(reply_to_message_id)
|
|
2151
|
+
parse_mode_to_use = parse_mode or self.parse_mode
|
|
2152
|
+
if text:
|
|
2153
|
+
text, metadata = self._parse_text_metadata(text, parse_mode_to_use)
|
|
2154
|
+
payload["text"] = text
|
|
2155
|
+
if metadata:payload["metadata"] = metadata
|
|
1633
2156
|
payload["time"] = "10"
|
|
1634
2157
|
resp = await self._post("sendFile", payload)
|
|
1635
2158
|
message_id_put = resp["data"]["message_id"]
|
|
@@ -1649,49 +2172,149 @@ class Robot:
|
|
|
1649
2172
|
"chat_keypad_type":chat_keypad_type
|
|
1650
2173
|
}
|
|
1651
2174
|
return AttrDict(result)
|
|
1652
|
-
|
|
1653
|
-
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):
|
|
2175
|
+
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,parse_mode: Optional[Literal["HTML", "Markdown"]] = None):
|
|
1654
2176
|
if path:
|
|
1655
2177
|
file_name = file_name or Path(path).name
|
|
1656
2178
|
upload_url = await self.get_upload_url(media_type)
|
|
1657
2179
|
file_id = await self.upload_media_file(upload_url, file_name, path)
|
|
1658
2180
|
if not file_id:
|
|
1659
2181
|
raise ValueError("Either path or file_id must be provided.")
|
|
1660
|
-
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)
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
2182
|
+
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,parse_mode=parse_mode)
|
|
2183
|
+
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", "Remove", "None"]] = "None",parse_mode: Optional[Literal["HTML", "Markdown"]] = None) -> Dict[str, Any]:
|
|
2184
|
+
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,parse_mode=parse_mode)
|
|
2185
|
+
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", "Remove", "None"]] = "None",parse_mode: Optional[Literal["HTML", "Markdown"]] = None) -> Dict[str, Any]:
|
|
2186
|
+
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,parse_mode=parse_mode)
|
|
2187
|
+
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", "Remove", "None"]] = "None",parse_mode: Optional[Literal["HTML", "Markdown"]] = None) -> Dict[str, Any]:
|
|
2188
|
+
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,parse_mode=parse_mode)
|
|
2189
|
+
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", "Remove", "None"]] = "None",parse_mode: Optional[Literal["HTML", "Markdown"]] = None) -> Dict[str, Any]:
|
|
2190
|
+
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,parse_mode=parse_mode)
|
|
2191
|
+
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", "Remove", "None"]] = "None",parse_mode: Optional[Literal["HTML", "Markdown"]] = None) -> Dict[str, Any]:
|
|
2192
|
+
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,parse_mode=parse_mode)
|
|
2193
|
+
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", "Remove", "None"]] = "None",parse_mode: Optional[Literal["HTML", "Markdown"]] = None) -> Dict[str, Any]:
|
|
2194
|
+
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,parse_mode=parse_mode)
|
|
2195
|
+
async def send_music(
|
|
2196
|
+
self,
|
|
2197
|
+
chat_id: str,
|
|
2198
|
+
path: Optional[Union[str, Path]] = None,
|
|
2199
|
+
file_id: Optional[str] = None,
|
|
2200
|
+
text: Optional[str] = None,
|
|
2201
|
+
file_name: Optional[str] = None,
|
|
2202
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
2203
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
2204
|
+
reply_to_message_id: Optional[str] = None,
|
|
2205
|
+
disable_notification: bool = False,
|
|
2206
|
+
chat_keypad_type: Optional[Literal["New", "Remove", "None"]] = "None",
|
|
2207
|
+
parse_mode: Optional[Literal["HTML", "Markdown"]] = None
|
|
2208
|
+
) -> Dict[str, Any]:
|
|
2209
|
+
valid_extensions = {"ogg", "oga", "opus", "flac"}
|
|
2210
|
+
extension = "flac"
|
|
2211
|
+
if path:
|
|
2212
|
+
path_str = str(path)
|
|
2213
|
+
if path_str.startswith("http://") or path_str.startswith("https://"):
|
|
2214
|
+
parsed = urlparse(path_str)
|
|
2215
|
+
base_name = os.path.basename(parsed.path)
|
|
2216
|
+
else:
|
|
2217
|
+
base_name = os.path.basename(path_str)
|
|
2218
|
+
name, ext = os.path.splitext(base_name)
|
|
2219
|
+
|
|
2220
|
+
if file_name is None or not file_name.strip():
|
|
2221
|
+
file_name = name or "music"
|
|
2222
|
+
ext = ext.lower().replace(".", "")
|
|
2223
|
+
if ext in valid_extensions:
|
|
2224
|
+
extension = ext
|
|
2225
|
+
else:
|
|
2226
|
+
if file_name is None:
|
|
2227
|
+
file_name = "music"
|
|
2228
|
+
return await self._send_file_generic(
|
|
2229
|
+
"File",
|
|
2230
|
+
chat_id,
|
|
2231
|
+
path,
|
|
2232
|
+
file_id,
|
|
2233
|
+
text,
|
|
2234
|
+
f"{file_name}.{extension}",
|
|
2235
|
+
inline_keypad,
|
|
2236
|
+
chat_keypad,
|
|
2237
|
+
reply_to_message_id,
|
|
2238
|
+
disable_notification,
|
|
2239
|
+
chat_keypad_type,
|
|
2240
|
+
parse_mode=parse_mode
|
|
2241
|
+
)
|
|
2242
|
+
async def send_gif(
|
|
2243
|
+
self,
|
|
2244
|
+
chat_id: str,
|
|
2245
|
+
path: Optional[Union[str, Path]] = None,
|
|
2246
|
+
file_id: Optional[str] = None,
|
|
2247
|
+
text: Optional[str] = None,
|
|
2248
|
+
file_name: Optional[str] = None,
|
|
2249
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
2250
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
2251
|
+
reply_to_message_id: Optional[str] = None,
|
|
2252
|
+
disable_notification: bool = False,
|
|
2253
|
+
chat_keypad_type: Optional[Literal["New", "Remove", "None"]] = "None",
|
|
2254
|
+
parse_mode: Optional[Literal["HTML", "Markdown"]] = None
|
|
2255
|
+
) -> Dict[str, Any]:
|
|
2256
|
+
valid_extensions = {"gif"}
|
|
2257
|
+
extension = "gif"
|
|
2258
|
+
if path:
|
|
2259
|
+
path_str = str(path)
|
|
2260
|
+
if path_str.startswith("http://") or path_str.startswith("https://"):
|
|
2261
|
+
parsed = urlparse(path_str)
|
|
2262
|
+
base_name = os.path.basename(parsed.path)
|
|
2263
|
+
else:
|
|
2264
|
+
base_name = os.path.basename(path_str)
|
|
2265
|
+
name, ext = os.path.splitext(base_name)
|
|
2266
|
+
|
|
2267
|
+
if file_name is None or not file_name.strip():
|
|
2268
|
+
file_name = name or "gif"
|
|
2269
|
+
ext = ext.lower().replace(".", "")
|
|
2270
|
+
if ext in valid_extensions:
|
|
2271
|
+
extension = ext
|
|
2272
|
+
else:
|
|
2273
|
+
if file_name is None:
|
|
2274
|
+
file_name = "gif"
|
|
2275
|
+
return await self._send_file_generic(
|
|
2276
|
+
"File",
|
|
2277
|
+
chat_id,
|
|
2278
|
+
path,
|
|
2279
|
+
file_id,
|
|
2280
|
+
text,
|
|
2281
|
+
f"{file_name}.{extension}",
|
|
2282
|
+
inline_keypad,
|
|
2283
|
+
chat_keypad,
|
|
2284
|
+
reply_to_message_id,
|
|
2285
|
+
disable_notification,
|
|
2286
|
+
chat_keypad_type,
|
|
2287
|
+
parse_mode=parse_mode
|
|
2288
|
+
)
|
|
2289
|
+
|
|
2290
|
+
async def get_avatar_me(self, save_as: str = None) -> str:
|
|
2291
|
+
session = None
|
|
2292
|
+
try:
|
|
2293
|
+
me_info = await self.get_me()
|
|
2294
|
+
avatar = me_info.get('data', {}).get('bot', {}).get('avatar', {})
|
|
2295
|
+
file_id = avatar.get('file_id')
|
|
2296
|
+
if not file_id:
|
|
2297
|
+
return "null"
|
|
2298
|
+
|
|
2299
|
+
file_info = await self.get_url_file(file_id)
|
|
2300
|
+
url = file_info.get("download_url") if isinstance(file_info, dict) else file_info
|
|
2301
|
+
|
|
2302
|
+
if save_as:
|
|
2303
|
+
session = aiohttp.ClientSession()
|
|
2304
|
+
async with session.get(url) as resp:
|
|
2305
|
+
if resp.status == 200:
|
|
2306
|
+
content = await resp.read()
|
|
2307
|
+
with open(save_as, "wb") as f:
|
|
2308
|
+
f.write(content)
|
|
2309
|
+
|
|
2310
|
+
return url
|
|
2311
|
+
except Exception as e:
|
|
2312
|
+
print(f"[get_avatar_me] Error: {e}")
|
|
2313
|
+
return "null"
|
|
2314
|
+
finally:
|
|
2315
|
+
if session and not session.closed:
|
|
2316
|
+
await session.close()
|
|
2317
|
+
|
|
1695
2318
|
async def get_name(self, chat_id: str) -> str:
|
|
1696
2319
|
try:
|
|
1697
2320
|
chat = await self.get_chat(chat_id)
|
|
@@ -1709,4 +2332,184 @@ class Robot:
|
|
|
1709
2332
|
except Exception:return "null"
|
|
1710
2333
|
async def get_username(self, chat_id: str) -> str:
|
|
1711
2334
|
chat_info = await self.get_chat(chat_id)
|
|
1712
|
-
return chat_info.get("data", {}).get("chat", {}).get("username", "None")
|
|
2335
|
+
return chat_info.get("data", {}).get("chat", {}).get("username", "None")
|
|
2336
|
+
async def send_bulk_message(
|
|
2337
|
+
self,
|
|
2338
|
+
chat_ids: List[str],
|
|
2339
|
+
text: str,
|
|
2340
|
+
concurrency: int = 5,
|
|
2341
|
+
delay_between: float = 0.0,
|
|
2342
|
+
log_errors: bool = True,
|
|
2343
|
+
**kwargs
|
|
2344
|
+
) -> Dict[str, Optional[Dict]]:
|
|
2345
|
+
if not chat_ids:return {}
|
|
2346
|
+
semaphore = asyncio.Semaphore(concurrency)
|
|
2347
|
+
results: Dict[str, Optional[Dict]] = {}
|
|
2348
|
+
async def _send(chat_id: str):
|
|
2349
|
+
async with semaphore:
|
|
2350
|
+
try:
|
|
2351
|
+
res = await self.send_message(chat_id, text, **kwargs)
|
|
2352
|
+
results[chat_id] = res
|
|
2353
|
+
except Exception as e:
|
|
2354
|
+
results[chat_id] = None
|
|
2355
|
+
if log_errors:print(f"[send_bulk_message] Error {chat_id} : {e}")
|
|
2356
|
+
if delay_between > 0:await asyncio.sleep(delay_between)
|
|
2357
|
+
await asyncio.gather(*[_send(cid) for cid in chat_ids])
|
|
2358
|
+
return results
|
|
2359
|
+
async def delete_bulk_message(self, chat_id: str, message_ids: list[str]):
|
|
2360
|
+
tasks = [self.delete_message(chat_id, mid) for mid in message_ids]
|
|
2361
|
+
return await asyncio.gather(*tasks, return_exceptions=True)
|
|
2362
|
+
async def edit_bulk_message(self, chat_id: str, messages: dict[str, str]):
|
|
2363
|
+
tasks = [self.edit_message_text(chat_id, mid, new_text) for mid, new_text in messages.items()]
|
|
2364
|
+
return await asyncio.gather(*tasks, return_exceptions=True)
|
|
2365
|
+
async def send_scheduled_message(self, chat_id: str, text: str, delay: int, **kwargs):
|
|
2366
|
+
await asyncio.sleep(delay)
|
|
2367
|
+
return await self.send_message(chat_id, text, **kwargs)
|
|
2368
|
+
async def disable_inline_keyboard(
|
|
2369
|
+
self,
|
|
2370
|
+
chat_id: str,
|
|
2371
|
+
message_id: str,
|
|
2372
|
+
text: Optional[str] = "~",
|
|
2373
|
+
delay: float = 5.0,
|
|
2374
|
+
) -> Dict[str, any]:
|
|
2375
|
+
if text is not None:await self.edit_inline_keypad(chat_id, message_id, inline_keypad={}, text=text)
|
|
2376
|
+
if delay > 0:
|
|
2377
|
+
await asyncio.sleep(delay)
|
|
2378
|
+
response = await self.edit_inline_keypad(chat_id, message_id, inline_keypad={})
|
|
2379
|
+
return response
|
|
2380
|
+
else:return await self.edit_inline_keypad(chat_id, message_id, inline_keypad={})
|
|
2381
|
+
async def get_chat_admins(self, chat_id: str) -> Dict[str, Any]:
|
|
2382
|
+
return await self._post("getChatAdmins", {"chat_id": chat_id})
|
|
2383
|
+
async def get_chat_members(self, chat_id: str, start_id: str = "") -> Dict[str, Any]:
|
|
2384
|
+
return await self._post("getChatMembers", {"chat_id": chat_id, "start_id": start_id})
|
|
2385
|
+
async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
|
|
2386
|
+
return await self._post("getChatInfo", {"chat_id": chat_id})
|
|
2387
|
+
async def set_chat_title(self, chat_id: str, title: str) -> Dict[str, Any]:
|
|
2388
|
+
return await self._post("editChatTitle", {"chat_id": chat_id, "title": title})
|
|
2389
|
+
async def set_chat_description(self, chat_id: str, description: str) -> Dict[str, Any]:
|
|
2390
|
+
return await self._post("editChatDescription", {"chat_id": chat_id, "description": description})
|
|
2391
|
+
async def set_chat_photo(self, chat_id: str, file_id: str) -> Dict[str, Any]:
|
|
2392
|
+
return await self._post("editChatPhoto", {"chat_id": chat_id, "file_id": file_id})
|
|
2393
|
+
async def remove_chat_photo(self, chat_id: str) -> Dict[str, Any]:
|
|
2394
|
+
return await self._post("editChatPhoto", {"chat_id": chat_id, "file_id": "Remove"})
|
|
2395
|
+
async def add_member_chat(self, chat_id: str, user_ids: list[str]) -> Dict[str, Any]:
|
|
2396
|
+
return await self._post("addChatMembers", {"chat_id": chat_id, "member_ids": user_ids})
|
|
2397
|
+
async def ban_member_chat(self, chat_id: str, user_id: str) -> Dict[str, Any]:
|
|
2398
|
+
return await self._post("banChatMember", {"chat_id": chat_id, "member_id": user_id})
|
|
2399
|
+
async def unban_chat_member(self, chat_id: str, user_id: str) -> Dict[str, Any]:
|
|
2400
|
+
return await self._post("unbanChatMember", {"chat_id": chat_id, "member_id": user_id})
|
|
2401
|
+
async def restrict_chat_member(self, chat_id: str, user_id: str, until: int = 0) -> Dict[str, Any]:
|
|
2402
|
+
return await self._post("restrictChatMember", {"chat_id": chat_id, "member_id": user_id, "until_date": until})
|
|
2403
|
+
async def get_chat_member(self, chat_id: str, user_id: str):
|
|
2404
|
+
return await self._post("getChatMember", {"chat_id": chat_id, "user_id": user_id})
|
|
2405
|
+
async def get_admin_chat(self, chat_id: str):
|
|
2406
|
+
return await self._post("getChatAdministrators", {"chat_id": chat_id})
|
|
2407
|
+
async def get_chat_member_count(self, chat_id: str):
|
|
2408
|
+
return await self._post("getChatMemberCount", {"chat_id": chat_id})
|
|
2409
|
+
async def ban_chat_member(self, chat_id: str, user_id: str):
|
|
2410
|
+
return await self._post("banChatMember", {"chat_id": chat_id, "user_id": user_id})
|
|
2411
|
+
async def promote_chat_member(self, chat_id: str, user_id: str, rights: dict) -> Dict[str, Any]:
|
|
2412
|
+
return await self._post("promoteChatMember", {"chat_id": chat_id, "member_id": user_id, "rights": rights})
|
|
2413
|
+
async def demote_chat_member(self, chat_id: str, user_id: str) -> Dict[str, Any]:
|
|
2414
|
+
return await self._post("promoteChatMember", {"chat_id": chat_id, "member_id": user_id, "rights": {}})
|
|
2415
|
+
async def pin_chat_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
|
|
2416
|
+
return await self._post("pinChatMessage", {"chat_id": chat_id, "message_id": message_id})
|
|
2417
|
+
async def unpin_chat_message(self, chat_id: str, message_id: str = "") -> Dict[str, Any]:
|
|
2418
|
+
return await self._post("unpinChatMessage", {"chat_id": chat_id, "message_id": message_id})
|
|
2419
|
+
async def export_chat_invite_link(self, chat_id: str) -> Dict[str, Any]:
|
|
2420
|
+
return await self._post("exportChatInviteLink", {"chat_id": chat_id})
|
|
2421
|
+
async def revoke_chat_invite_link(self, chat_id: str, link: str) -> Dict[str, Any]:
|
|
2422
|
+
return await self._post("revokeChatInviteLink", {"chat_id": chat_id, "invite_link": link})
|
|
2423
|
+
async def create_group(self, title: str, user_ids: list[str]) -> Dict[str, Any]:
|
|
2424
|
+
return await self._post("createGroup", {"title": title, "user_ids": user_ids})
|
|
2425
|
+
async def create_channel(self, title: str, description: str = "") -> Dict[str, Any]:
|
|
2426
|
+
return await self._post("createChannel", {"title": title, "description": description})
|
|
2427
|
+
async def leave_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
2428
|
+
return await self._post("leaveChat", {"chat_id": chat_id})
|
|
2429
|
+
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> Dict[str, Any]:
|
|
2430
|
+
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})
|
|
2431
|
+
async def edit_message_text(self, chat_id: str, message_id: str, text: str, parse_mode: Optional[Literal["HTML", "Markdown"]] = None) -> Dict[str, Any]:
|
|
2432
|
+
payload = {
|
|
2433
|
+
"chat_id": chat_id,
|
|
2434
|
+
"message_id": message_id,
|
|
2435
|
+
"text": text,
|
|
2436
|
+
}
|
|
2437
|
+
parse_mode_to_use = parse_mode or self.parse_mode
|
|
2438
|
+
if text:
|
|
2439
|
+
text, metadata = self._parse_text_metadata(text, parse_mode_to_use)
|
|
2440
|
+
payload["text"] = text
|
|
2441
|
+
if metadata:
|
|
2442
|
+
payload["metadata"] = metadata
|
|
2443
|
+
return await self._post("editMessageText", payload)
|
|
2444
|
+
async def edit_inline_keypad(self,chat_id: str,message_id: str,inline_keypad: Dict[str, Any],text: str = None) -> Dict[str, Any]:
|
|
2445
|
+
if text is not None:await self._post("editMessageText", {"chat_id": chat_id,"message_id": message_id,"text": text})
|
|
2446
|
+
return await self._post("editMessageKeypad", {"chat_id": chat_id,"message_id": message_id,"inline_keypad": inline_keypad})
|
|
2447
|
+
async def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
|
|
2448
|
+
return await self._post("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
|
|
2449
|
+
async def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
2450
|
+
return await self._post("setCommands", {"bot_commands": bot_commands})
|
|
2451
|
+
async def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
|
|
2452
|
+
return await self._post("updateBotEndpoints", {"url": url, "type": type})
|
|
2453
|
+
async def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
|
|
2454
|
+
return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "Remove"})
|
|
2455
|
+
async def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
|
|
2456
|
+
return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "New", "chat_keypad": chat_keypad})
|
|
2457
|
+
async def send_contact(self, chat_id: str, first_name: str, last_name: str, phone_number: str,inline_keypad: Optional[Dict[str, Any]] = None,chat_keypad: Optional[Dict[str, Any]] = None,chat_keypad_type: Optional[Literal["New", "Remove", "None"]] = None,) -> Dict[str, Any]:
|
|
2458
|
+
return await self._post("sendContact", {"chat_id": chat_id, "first_name": first_name, "last_name": last_name, "phone_number": phone_number,"inline_keypad": inline_keypad,"chat_keypad": chat_keypad,"chat_keypad_type": chat_keypad_type})
|
|
2459
|
+
async def get_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
2460
|
+
return await self._post("getChat", {"chat_id": chat_id})
|
|
2461
|
+
|
|
2462
|
+
def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
|
|
2463
|
+
client = self._get_client()
|
|
2464
|
+
return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
|
|
2465
|
+
async def send_poll(
|
|
2466
|
+
self,
|
|
2467
|
+
chat_id: str,
|
|
2468
|
+
question: str,
|
|
2469
|
+
options: List[str],
|
|
2470
|
+
type: Literal["Regular", "Quiz"] = "Regular",
|
|
2471
|
+
allows_multiple_answers: bool = False,
|
|
2472
|
+
is_anonymous: bool = True,
|
|
2473
|
+
correct_option_index: Optional[int] = None,
|
|
2474
|
+
hint: Optional[str] = None,
|
|
2475
|
+
reply_to_message_id: Optional[str] = None,
|
|
2476
|
+
disable_notification: bool = False,
|
|
2477
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
2478
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
2479
|
+
chat_keypad_type: Optional[Literal["New", "Remove", "None"]] = None,
|
|
2480
|
+
) -> AttrDict:
|
|
2481
|
+
|
|
2482
|
+
payload = {
|
|
2483
|
+
"chat_id": chat_id,
|
|
2484
|
+
"question": question,
|
|
2485
|
+
"options": options,
|
|
2486
|
+
"type": type,
|
|
2487
|
+
"allows_multiple_answers": allows_multiple_answers,
|
|
2488
|
+
"is_anonymous": is_anonymous,
|
|
2489
|
+
"correct_option_index": correct_option_index,
|
|
2490
|
+
"explanation": hint,
|
|
2491
|
+
"reply_to_message_id": reply_to_message_id,
|
|
2492
|
+
"disable_notification": disable_notification,
|
|
2493
|
+
"inline_keypad": inline_keypad,
|
|
2494
|
+
"chat_keypad": chat_keypad,
|
|
2495
|
+
"chat_keypad_type": chat_keypad_type,
|
|
2496
|
+
}
|
|
2497
|
+
payload = {k: v for k, v in payload.items() if v is not None or (k in ["is_anonymous", "disable_notification"] and v is False)}
|
|
2498
|
+
return await self._post("sendPoll", payload)
|
|
2499
|
+
|
|
2500
|
+
async def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
|
|
2501
|
+
client = self._get_client()
|
|
2502
|
+
if chat_id:
|
|
2503
|
+
chat_info_data = await self.get_chat(chat_id)
|
|
2504
|
+
chat_info = chat_info_data.get('data', {}).get('chat', {})
|
|
2505
|
+
username = chat_info.get('username')
|
|
2506
|
+
first_name = chat_info.get("first_name", "")
|
|
2507
|
+
if username:
|
|
2508
|
+
result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
|
|
2509
|
+
members = result.get('in_chat_members', [])
|
|
2510
|
+
return any(m.get('username') == username for m in members)
|
|
2511
|
+
elif first_name:
|
|
2512
|
+
result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=first_name)
|
|
2513
|
+
members = result.get('in_chat_members', [])
|
|
2514
|
+
return any(m.get('first_name') == first_name for m in members)
|
|
2515
|
+
return False
|