Rubka 6.4.8__py3-none-any.whl → 6.6.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 +21 -4
- rubka/asynco.py +70 -207
- rubka/filters.py +305 -0
- {rubka-6.4.8.dist-info → rubka-6.6.2.dist-info}/METADATA +1 -1
- {rubka-6.4.8.dist-info → rubka-6.6.2.dist-info}/RECORD +7 -6
- {rubka-6.4.8.dist-info → rubka-6.6.2.dist-info}/WHEEL +0 -0
- {rubka-6.4.8.dist-info → rubka-6.6.2.dist-info}/top_level.txt +0 -0
rubka/api.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import List, Optional, Dict, Any, Literal
|
|
|
3
3
|
from .exceptions import APIRequestError
|
|
4
4
|
from .adaptorrubka import Client as Client_get
|
|
5
5
|
from .logger import logger
|
|
6
|
+
from . import filters
|
|
6
7
|
from typing import Callable
|
|
7
8
|
from .context import Message,InlineMessage
|
|
8
9
|
from typing import Optional, Union, Literal, Dict, Any
|
|
@@ -489,14 +490,28 @@ class Robot:
|
|
|
489
490
|
|
|
490
491
|
return decorator
|
|
491
492
|
|
|
492
|
-
def on_message(
|
|
493
|
+
def on_message(
|
|
494
|
+
self,
|
|
495
|
+
filters: Optional[Callable[[Message], bool]] = None,
|
|
496
|
+
commands: Optional[List[str]] = None
|
|
497
|
+
):
|
|
493
498
|
def decorator(func: Callable[[Any, Message], None]):
|
|
499
|
+
def wrapper(bot, message: Message):
|
|
500
|
+
if filters and not filters(message):
|
|
501
|
+
return
|
|
502
|
+
if commands:
|
|
503
|
+
if not getattr(message, "is_command", False):
|
|
504
|
+
return
|
|
505
|
+
cmd = message.text.split()[0].lstrip("/") if message.text else ""
|
|
506
|
+
if cmd not in commands:
|
|
507
|
+
return
|
|
508
|
+
return func(bot, message)
|
|
494
509
|
self._message_handlers.append({
|
|
495
|
-
"func":
|
|
510
|
+
"func": wrapper,
|
|
496
511
|
"filters": filters,
|
|
497
512
|
"commands": commands
|
|
498
513
|
})
|
|
499
|
-
return
|
|
514
|
+
return wrapper
|
|
500
515
|
return decorator
|
|
501
516
|
def on_message_file(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
502
517
|
def decorator(func: Callable[[Any, Message], None]):
|
|
@@ -1654,9 +1669,11 @@ class Robot:
|
|
|
1654
1669
|
self,
|
|
1655
1670
|
chat_id: str,
|
|
1656
1671
|
message_id: str,
|
|
1657
|
-
inline_keypad: Dict[str, Any]
|
|
1672
|
+
inline_keypad: Dict[str, Any],
|
|
1673
|
+
text : str = None
|
|
1658
1674
|
) -> Dict[str, Any]:
|
|
1659
1675
|
"""Edit inline keypad of a message."""
|
|
1676
|
+
if text is not None:self._post("editMessageText", {"chat_id": chat_id,"message_id": message_id,"text": text})
|
|
1660
1677
|
return self._post("editMessageKeypad", {
|
|
1661
1678
|
"chat_id": chat_id,
|
|
1662
1679
|
"message_id": message_id,
|
rubka/asynco.py
CHANGED
|
@@ -1,30 +1,19 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import aiohttp
|
|
3
|
-
import aiofiles
|
|
1
|
+
import asyncio,aiohttp,aiofiles,time,datetime,json,tempfile,os,sys,subprocess,mimetypes
|
|
4
2
|
from typing import List, Optional, Dict, Any, Literal, Callable, Union
|
|
5
3
|
from .exceptions import APIRequestError
|
|
6
4
|
from .adaptorrubka import Client as Client_get
|
|
7
5
|
from .logger import logger
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
except (ImportError, ModuleNotFoundError):
|
|
11
|
-
from context import Message, InlineMessage
|
|
6
|
+
from . import filters
|
|
7
|
+
try:from .context import Message, InlineMessage
|
|
8
|
+
except (ImportError, ModuleNotFoundError):from context import Message, InlineMessage
|
|
12
9
|
class FeatureNotAvailableError(Exception):
|
|
13
10
|
pass
|
|
14
11
|
|
|
15
12
|
from tqdm.asyncio import tqdm
|
|
16
13
|
from urllib.parse import urlparse, parse_qs
|
|
17
14
|
class InvalidTokenError(Exception):pass
|
|
18
|
-
import mimetypes
|
|
19
15
|
from pathlib import Path
|
|
20
|
-
import time
|
|
21
|
-
import datetime,json
|
|
22
|
-
import tempfile
|
|
23
16
|
from tqdm import tqdm
|
|
24
|
-
import os
|
|
25
|
-
import sys
|
|
26
|
-
import subprocess
|
|
27
|
-
|
|
28
17
|
API_URL = "https://botapi.rubika.ir/v3"
|
|
29
18
|
|
|
30
19
|
def install_package(package_name: str) -> bool:
|
|
@@ -34,31 +23,7 @@ def install_package(package_name: str) -> bool:
|
|
|
34
23
|
return True
|
|
35
24
|
except Exception:
|
|
36
25
|
return False
|
|
37
|
-
|
|
38
|
-
def __init__(self, resp: dict, chat_id: str, file_id: str,
|
|
39
|
-
type_file: str, reply_to_message_id: str,
|
|
40
|
-
disable_notification: bool, text: str = None,
|
|
41
|
-
chat_keypad=None, inline_keypad=None, chat_keypad_type="None"):
|
|
42
|
-
|
|
43
|
-
self.status = resp.get("status")
|
|
44
|
-
self.status_det = resp.get("status_det")
|
|
45
|
-
self.file_id = file_id
|
|
46
|
-
self.message_id = resp["data"].get("message_id")
|
|
47
|
-
self.send_to_chat_id = chat_id
|
|
48
|
-
self.reply_to_message_id = reply_to_message_id
|
|
49
|
-
self.disable_notification = disable_notification
|
|
50
|
-
self.type_file = type_file
|
|
51
|
-
self.text = text
|
|
52
|
-
self.chat_keypad = chat_keypad
|
|
53
|
-
self.inline_keypad = inline_keypad
|
|
54
|
-
self.chat_keypad_type = chat_keypad_type
|
|
55
|
-
self.raw_response = resp
|
|
56
|
-
|
|
57
|
-
def to_dict(self):
|
|
58
|
-
return self.__dict__
|
|
59
|
-
|
|
60
|
-
def to_json(self):
|
|
61
|
-
return json.dumps(self.__dict__, ensure_ascii=False, indent=4)
|
|
26
|
+
|
|
62
27
|
def get_importlib_metadata():
|
|
63
28
|
"""Dynamically imports and returns metadata functions from importlib."""
|
|
64
29
|
try:
|
|
@@ -123,12 +88,40 @@ def show_last_six_words(text: str) -> str:
|
|
|
123
88
|
"""Returns the last 6 characters of a stripped string."""
|
|
124
89
|
text = text.strip()
|
|
125
90
|
return text[-6:]
|
|
126
|
-
|
|
91
|
+
class AttrDict(dict):
|
|
92
|
+
def __getattr__(self, item):
|
|
93
|
+
value = self.get(item)
|
|
94
|
+
if isinstance(value, dict):
|
|
95
|
+
return AttrDict(value)
|
|
96
|
+
return value
|
|
127
97
|
|
|
128
98
|
class Robot:
|
|
129
99
|
"""
|
|
130
|
-
Main
|
|
131
|
-
|
|
100
|
+
Main asynchronous class to interact with the Rubika Bot API.
|
|
101
|
+
|
|
102
|
+
This class handles sending and receiving messages, inline queries, callbacks,
|
|
103
|
+
and manages sessions and API interactions. It is initialized with a bot token
|
|
104
|
+
and provides multiple optional parameters for configuration.
|
|
105
|
+
|
|
106
|
+
Attributes:
|
|
107
|
+
token (str): Bot token used for authentication with Rubika Bot API.
|
|
108
|
+
session_name (str | None): Optional session name for storing session data.
|
|
109
|
+
auth (str | None): Optional authentication string for advanced features.
|
|
110
|
+
Key (str | None): Optional key for additional authorization if required.
|
|
111
|
+
platform (str): Platform type, default is 'web'.
|
|
112
|
+
web_hook (str | None): Optional webhook URL for receiving updates.
|
|
113
|
+
timeout (int): Timeout for API requests in seconds (default 10).
|
|
114
|
+
show_progress (bool): Whether to show progress for long operations (default False).
|
|
115
|
+
Example:
|
|
116
|
+
```python
|
|
117
|
+
import asyncio
|
|
118
|
+
from rubka.asynco import Robot,filters,Message
|
|
119
|
+
bot = Robot(token="YOUR_BOT_TOKEN")
|
|
120
|
+
@bot.on_message(filters.is_command.start)
|
|
121
|
+
async def start_command(bot: Robot, message: Message):
|
|
122
|
+
await message.reply("Hello!")
|
|
123
|
+
asyncio.run(bot.run())
|
|
124
|
+
|
|
132
125
|
"""
|
|
133
126
|
|
|
134
127
|
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):
|
|
@@ -172,7 +165,6 @@ class Robot:
|
|
|
172
165
|
print(data)
|
|
173
166
|
json_url = data.get('url', self.web_hook)
|
|
174
167
|
print(self.web_hook)
|
|
175
|
-
|
|
176
168
|
for endpoint_type in [
|
|
177
169
|
"ReceiveUpdate",
|
|
178
170
|
"ReceiveInlineMessage",
|
|
@@ -186,8 +178,6 @@ class Robot:
|
|
|
186
178
|
except Exception as e:
|
|
187
179
|
logger.error(f"Failed to set webhook from {self.web_hook}: {e}")
|
|
188
180
|
self.web_hook = None
|
|
189
|
-
|
|
190
|
-
|
|
191
181
|
async def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
192
182
|
url = f"{API_URL}/{self.token}/{method}"
|
|
193
183
|
session = await self._get_session()
|
|
@@ -200,11 +190,9 @@ class Robot:
|
|
|
200
190
|
text_resp = await response.text()
|
|
201
191
|
logger.error(f"Invalid JSON response from {method}: {text_resp}")
|
|
202
192
|
raise APIRequestError(f"Invalid JSON response: {text_resp}")
|
|
203
|
-
|
|
204
193
|
if method != "getUpdates":
|
|
205
194
|
logger.debug(f"API Response from {method}: {json_resp}")
|
|
206
|
-
|
|
207
|
-
return json_resp
|
|
195
|
+
return AttrDict(json_resp)
|
|
208
196
|
except aiohttp.ClientError as e:
|
|
209
197
|
logger.error(f"API request failed: {e}")
|
|
210
198
|
raise APIRequestError(f"API request failed: {e}") from e
|
|
@@ -274,8 +262,6 @@ class Robot:
|
|
|
274
262
|
return
|
|
275
263
|
if not allow_locations and (message.location or message.live_location):
|
|
276
264
|
return
|
|
277
|
-
|
|
278
|
-
|
|
279
265
|
if message.text:
|
|
280
266
|
text = message.text if case_sensitive else message.text.lower()
|
|
281
267
|
if min_text_length and len(message.text) < min_text_length:
|
|
@@ -288,8 +274,6 @@ class Robot:
|
|
|
288
274
|
return
|
|
289
275
|
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
290
276
|
return
|
|
291
|
-
|
|
292
|
-
|
|
293
277
|
if commands:
|
|
294
278
|
if not message.text:
|
|
295
279
|
return
|
|
@@ -298,13 +282,9 @@ class Robot:
|
|
|
298
282
|
if cmd not in commands:
|
|
299
283
|
return
|
|
300
284
|
message.args = parts[1:]
|
|
301
|
-
|
|
302
|
-
|
|
303
285
|
if filters and not filters(message):
|
|
304
286
|
return
|
|
305
|
-
|
|
306
287
|
return await func(bot, message)
|
|
307
|
-
|
|
308
288
|
self._message_handlers.append({
|
|
309
289
|
"func": wrapper,
|
|
310
290
|
"filters": filters,
|
|
@@ -345,30 +325,20 @@ class Robot:
|
|
|
345
325
|
|
|
346
326
|
if not message.is_channel:
|
|
347
327
|
return
|
|
348
|
-
|
|
349
|
-
|
|
350
328
|
if chat_id:
|
|
351
329
|
if isinstance(chat_id, str) and message.chat_id != chat_id:
|
|
352
330
|
return
|
|
353
331
|
if isinstance(chat_id, list) and message.chat_id not in chat_id:
|
|
354
332
|
return
|
|
355
|
-
|
|
356
|
-
|
|
357
333
|
if sender_id:
|
|
358
334
|
if isinstance(sender_id, str) and message.sender_id != sender_id:
|
|
359
335
|
return
|
|
360
336
|
if isinstance(sender_id, list) and message.sender_id not in sender_id:
|
|
361
337
|
return
|
|
362
|
-
|
|
363
|
-
|
|
364
338
|
if sender_type and message.sender_type != sender_type:
|
|
365
339
|
return
|
|
366
|
-
|
|
367
|
-
|
|
368
340
|
if not allow_forwarded and message.forwarded_from:
|
|
369
341
|
return
|
|
370
|
-
|
|
371
|
-
|
|
372
342
|
if not allow_files and message.file:
|
|
373
343
|
return
|
|
374
344
|
if not allow_stickers and message.sticker:
|
|
@@ -379,8 +349,6 @@ class Robot:
|
|
|
379
349
|
return
|
|
380
350
|
if not allow_locations and (message.location or message.live_location):
|
|
381
351
|
return
|
|
382
|
-
|
|
383
|
-
|
|
384
352
|
if message.text:
|
|
385
353
|
text = message.text if case_sensitive else message.text.lower()
|
|
386
354
|
if min_text_length and len(message.text) < min_text_length:
|
|
@@ -393,8 +361,6 @@ class Robot:
|
|
|
393
361
|
return
|
|
394
362
|
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
395
363
|
return
|
|
396
|
-
|
|
397
|
-
|
|
398
364
|
if commands:
|
|
399
365
|
if not message.text:
|
|
400
366
|
return
|
|
@@ -402,14 +368,10 @@ class Robot:
|
|
|
402
368
|
cmd = parts[0].lstrip("/")
|
|
403
369
|
if cmd not in commands:
|
|
404
370
|
return
|
|
405
|
-
message.args = parts[1:]
|
|
406
|
-
|
|
407
|
-
|
|
371
|
+
message.args = parts[1:]
|
|
408
372
|
if filters and not filters(message):
|
|
409
373
|
return
|
|
410
|
-
|
|
411
374
|
return await func(bot, message)
|
|
412
|
-
|
|
413
375
|
self._message_handlers.append({
|
|
414
376
|
"func": wrapper,
|
|
415
377
|
"filters": filters,
|
|
@@ -447,33 +409,22 @@ class Robot:
|
|
|
447
409
|
|
|
448
410
|
def decorator(func: Callable[[Any, Message], None]):
|
|
449
411
|
async def wrapper(bot, message: Message):
|
|
450
|
-
|
|
451
412
|
if not message.is_group:
|
|
452
413
|
return
|
|
453
|
-
|
|
454
|
-
|
|
455
414
|
if chat_id:
|
|
456
415
|
if isinstance(chat_id, str) and message.chat_id != chat_id:
|
|
457
416
|
return
|
|
458
417
|
if isinstance(chat_id, list) and message.chat_id not in chat_id:
|
|
459
418
|
return
|
|
460
|
-
|
|
461
|
-
|
|
462
419
|
if sender_id:
|
|
463
420
|
if isinstance(sender_id, str) and message.sender_id != sender_id:
|
|
464
421
|
return
|
|
465
422
|
if isinstance(sender_id, list) and message.sender_id not in sender_id:
|
|
466
423
|
return
|
|
467
|
-
|
|
468
|
-
|
|
469
424
|
if sender_type and message.sender_type != sender_type:
|
|
470
425
|
return
|
|
471
|
-
|
|
472
|
-
|
|
473
426
|
if not allow_forwarded and message.forwarded_from:
|
|
474
427
|
return
|
|
475
|
-
|
|
476
|
-
|
|
477
428
|
if not allow_files and message.file:
|
|
478
429
|
return
|
|
479
430
|
if not allow_stickers and message.sticker:
|
|
@@ -484,8 +435,6 @@ class Robot:
|
|
|
484
435
|
return
|
|
485
436
|
if not allow_locations and (message.location or message.live_location):
|
|
486
437
|
return
|
|
487
|
-
|
|
488
|
-
|
|
489
438
|
if message.text:
|
|
490
439
|
text = message.text if case_sensitive else message.text.lower()
|
|
491
440
|
if min_text_length and len(message.text) < min_text_length:
|
|
@@ -498,8 +447,6 @@ class Robot:
|
|
|
498
447
|
return
|
|
499
448
|
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
500
449
|
return
|
|
501
|
-
|
|
502
|
-
|
|
503
450
|
if commands:
|
|
504
451
|
if not message.text:
|
|
505
452
|
return
|
|
@@ -508,13 +455,9 @@ class Robot:
|
|
|
508
455
|
if cmd not in commands:
|
|
509
456
|
return
|
|
510
457
|
message.args = parts[1:]
|
|
511
|
-
|
|
512
|
-
|
|
513
458
|
if filters and not filters(message):
|
|
514
459
|
return
|
|
515
|
-
|
|
516
460
|
return await func(bot, message)
|
|
517
|
-
|
|
518
461
|
self._message_handlers.append({
|
|
519
462
|
"func": wrapper,
|
|
520
463
|
"filters": filters,
|
|
@@ -527,15 +470,27 @@ class Robot:
|
|
|
527
470
|
return wrapper
|
|
528
471
|
return decorator
|
|
529
472
|
|
|
530
|
-
def on_message(
|
|
473
|
+
def on_message(
|
|
474
|
+
self,
|
|
475
|
+
filters: Optional[Callable[[Message], bool]] = None,
|
|
476
|
+
commands: Optional[List[str]] = None
|
|
477
|
+
):
|
|
531
478
|
def decorator(func: Callable[[Any, Message], None]):
|
|
479
|
+
async def wrapper(bot, message: Message):
|
|
480
|
+
if filters and not filters(message):return
|
|
481
|
+
if commands:
|
|
482
|
+
if not message.is_command:return
|
|
483
|
+
cmd = message.text.split()[0].lstrip("/")
|
|
484
|
+
if cmd not in commands:return
|
|
485
|
+
return await func(bot, message)
|
|
532
486
|
self._message_handlers.append({
|
|
533
|
-
"func":
|
|
487
|
+
"func": wrapper,
|
|
534
488
|
"filters": filters,
|
|
535
489
|
"commands": commands
|
|
536
490
|
})
|
|
537
|
-
return
|
|
491
|
+
return wrapper
|
|
538
492
|
return decorator
|
|
493
|
+
|
|
539
494
|
|
|
540
495
|
def on_message_file(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
541
496
|
def decorator(func: Callable[[Any, Message], None]):
|
|
@@ -725,13 +680,12 @@ class Robot:
|
|
|
725
680
|
|
|
726
681
|
async def _handle_inline_query(self, inline_message: InlineMessage):
|
|
727
682
|
aux_button_id = inline_message.aux_data.button_id if inline_message.aux_data else None
|
|
728
|
-
|
|
729
683
|
for handler in self._inline_query_handlers:
|
|
730
684
|
if handler["button_id"] is None or handler["button_id"] == aux_button_id:
|
|
731
685
|
try:
|
|
732
686
|
await handler["func"](self, inline_message)
|
|
733
687
|
except Exception as e:
|
|
734
|
-
|
|
688
|
+
raise Exception(f"Error in inline query handler: {e}")
|
|
735
689
|
|
|
736
690
|
def on_inline_query(self, button_id: Optional[str] = None):
|
|
737
691
|
def decorator(func: Callable[[Any, InlineMessage], None]):
|
|
@@ -744,33 +698,19 @@ class Robot:
|
|
|
744
698
|
def on_inline_query_prefix(self, prefix: str, button_id: Optional[str] = None):
|
|
745
699
|
if not prefix.startswith('/'):
|
|
746
700
|
prefix = '/' + prefix
|
|
747
|
-
|
|
748
701
|
def decorator(func: Callable[[Any, InlineMessage], None]):
|
|
749
|
-
|
|
750
702
|
async def handler_wrapper(bot_instance, inline_message: InlineMessage):
|
|
751
|
-
|
|
752
703
|
if not inline_message.raw_data or 'text' not in inline_message.raw_data:
|
|
753
704
|
return
|
|
754
|
-
|
|
755
705
|
query_text = inline_message.raw_data['text']
|
|
756
|
-
|
|
757
|
-
|
|
758
706
|
if query_text.startswith(prefix):
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
707
|
try:
|
|
763
708
|
await func(bot_instance, inline_message)
|
|
764
709
|
except Exception as e:
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
710
|
+
raise Exception(f"Error in inline query prefix handler '{prefix}': {e}")
|
|
768
711
|
self._inline_query_handlers.append({
|
|
769
712
|
"func": handler_wrapper,
|
|
770
|
-
"button_id": button_id
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
713
|
+
"button_id": button_id
|
|
774
714
|
})
|
|
775
715
|
return func
|
|
776
716
|
return decorator
|
|
@@ -778,40 +718,29 @@ class Robot:
|
|
|
778
718
|
if update.get("type") == "ReceiveQuery":
|
|
779
719
|
msg = update.get("inline_message", {})
|
|
780
720
|
context = InlineMessage(bot=self, raw_data=msg)
|
|
781
|
-
|
|
782
|
-
|
|
783
721
|
if hasattr(self, "_callback_handlers"):
|
|
784
722
|
for handler in self._callback_handlers:
|
|
785
723
|
if not handler["button_id"] or getattr(context.aux_data, "button_id", None) == handler["button_id"]:
|
|
786
724
|
asyncio.create_task(handler["func"](self, context))
|
|
787
|
-
|
|
788
|
-
|
|
789
725
|
asyncio.create_task(self._handle_inline_query(context))
|
|
790
726
|
return
|
|
791
727
|
|
|
792
728
|
if update.get("type") == "NewMessage":
|
|
793
729
|
msg = update.get("new_message", {})
|
|
794
730
|
try:
|
|
795
|
-
if msg.get("time") and (time.time() - float(msg["time"])) > 20:
|
|
796
|
-
|
|
797
|
-
except (ValueError, TypeError):
|
|
798
|
-
return
|
|
799
|
-
|
|
731
|
+
if msg.get("time") and (time.time() - float(msg["time"])) > 20:return
|
|
732
|
+
except (ValueError, TypeError):return
|
|
800
733
|
context = Message(bot=self,
|
|
801
734
|
chat_id=update.get("chat_id"),
|
|
802
735
|
message_id=msg.get("message_id"),
|
|
803
736
|
sender_id=msg.get("sender_id"),
|
|
804
737
|
text=msg.get("text"),
|
|
805
738
|
raw_data=msg)
|
|
806
|
-
|
|
807
|
-
|
|
808
739
|
if context.aux_data and self._callback_handlers:
|
|
809
740
|
for handler in self._callback_handlers:
|
|
810
741
|
if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
|
|
811
742
|
asyncio.create_task(handler["func"](self, context))
|
|
812
743
|
return
|
|
813
|
-
|
|
814
|
-
|
|
815
744
|
if self._message_handlers:
|
|
816
745
|
for handler_info in self._message_handlers:
|
|
817
746
|
|
|
@@ -823,18 +752,12 @@ class Robot:
|
|
|
823
752
|
if cmd not in handler_info["commands"]:
|
|
824
753
|
continue
|
|
825
754
|
context.args = parts[1:]
|
|
826
|
-
|
|
827
|
-
|
|
828
755
|
if handler_info["filters"]:
|
|
829
756
|
if not handler_info["filters"](context):
|
|
830
757
|
continue
|
|
831
|
-
|
|
832
|
-
|
|
833
758
|
if not handler_info["commands"] and not handler_info["filters"]:
|
|
834
759
|
asyncio.create_task(handler_info["func"](self, context))
|
|
835
760
|
continue
|
|
836
|
-
|
|
837
|
-
|
|
838
761
|
if handler_info["commands"] or handler_info["filters"]:
|
|
839
762
|
asyncio.create_task(handler_info["func"](self, context))#jaq
|
|
840
763
|
continue
|
|
@@ -851,25 +774,18 @@ class Robot:
|
|
|
851
774
|
if offset_id: params['offset_id'] = offset_id
|
|
852
775
|
if limit: params['limit'] = limit
|
|
853
776
|
async with session.get(self.web_hook, params=params) as response:
|
|
854
|
-
response.raise_for_status()
|
|
855
|
-
|
|
777
|
+
response.raise_for_status()
|
|
856
778
|
return await response.json()
|
|
857
779
|
|
|
858
780
|
def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
|
|
859
781
|
now = time.time()
|
|
860
782
|
expired = [mid for mid, ts in self._processed_message_ids.items() if now - ts > max_age_sec]
|
|
861
|
-
for mid in expired:
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if message_id in self._processed_message_ids:
|
|
865
|
-
return True
|
|
866
|
-
|
|
783
|
+
for mid in expired:del self._processed_message_ids[mid]
|
|
784
|
+
if message_id in self._processed_message_ids:return True
|
|
867
785
|
self._processed_message_ids[message_id] = now
|
|
868
786
|
return False
|
|
869
787
|
|
|
870
788
|
|
|
871
|
-
|
|
872
|
-
|
|
873
789
|
async def run(
|
|
874
790
|
self,
|
|
875
791
|
debug: bool = False,
|
|
@@ -1436,8 +1352,7 @@ class Robot:
|
|
|
1436
1352
|
|
|
1437
1353
|
|
|
1438
1354
|
_log("Auto-restart requested. You can call run(...) again as needed.", "warning")
|
|
1439
|
-
|
|
1440
|
-
|
|
1355
|
+
|
|
1441
1356
|
async def send_message(
|
|
1442
1357
|
self,
|
|
1443
1358
|
chat_id: str,
|
|
@@ -1447,27 +1362,21 @@ class Robot:
|
|
|
1447
1362
|
disable_notification: bool = False,
|
|
1448
1363
|
reply_to_message_id: Optional[str] = None,
|
|
1449
1364
|
chat_keypad_type: Optional[Literal["New", "Removed"]] = None
|
|
1450
|
-
) -> Dict[str, Any]:
|
|
1365
|
+
) -> Dict[str, Any]:
|
|
1451
1366
|
payload = {
|
|
1452
1367
|
"chat_id": chat_id,
|
|
1453
1368
|
"text": text,
|
|
1454
1369
|
"disable_notification": disable_notification,
|
|
1455
1370
|
}
|
|
1456
|
-
|
|
1457
1371
|
if chat_keypad:
|
|
1458
1372
|
payload["chat_keypad"] = chat_keypad
|
|
1459
1373
|
payload["chat_keypad_type"] = chat_keypad_type or "New"
|
|
1460
|
-
|
|
1461
1374
|
if inline_keypad:
|
|
1462
1375
|
payload["inline_keypad"] = inline_keypad
|
|
1463
|
-
|
|
1464
1376
|
if reply_to_message_id:
|
|
1465
1377
|
payload["reply_to_message_id"] = reply_to_message_id
|
|
1466
|
-
|
|
1467
1378
|
return await self._post("sendMessage", payload)
|
|
1468
1379
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
1380
|
async def get_url_file(self,file_id):
|
|
1472
1381
|
data = await self._post("getFile", {'file_id': file_id})
|
|
1473
1382
|
return data.get("data").get("download_url")
|
|
@@ -1480,14 +1389,11 @@ class Robot:
|
|
|
1480
1389
|
|
|
1481
1390
|
async def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
|
|
1482
1391
|
client = self._get_client()
|
|
1483
|
-
|
|
1484
1392
|
if chat_id:
|
|
1485
1393
|
chat_info_data = await self.get_chat(chat_id)
|
|
1486
1394
|
chat_info = chat_info_data.get('data', {}).get('chat', {})
|
|
1487
1395
|
username = chat_info.get('username')
|
|
1488
1396
|
user_id = chat_info.get('user_id')
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
1397
|
if username:
|
|
1492
1398
|
result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
|
|
1493
1399
|
members = result.get('in_chat_members', [])
|
|
@@ -1506,7 +1412,6 @@ class Robot:
|
|
|
1506
1412
|
id="None"):
|
|
1507
1413
|
from .button import InlineBuilder
|
|
1508
1414
|
builder = InlineBuilder()
|
|
1509
|
-
|
|
1510
1415
|
if isinstance(username, (list, tuple)) and isinstance(title_button, (list, tuple)):
|
|
1511
1416
|
for t, u in zip(title_button, username):
|
|
1512
1417
|
builder = builder.row(
|
|
@@ -1576,55 +1481,41 @@ class Robot:
|
|
|
1576
1481
|
url=url
|
|
1577
1482
|
)
|
|
1578
1483
|
)
|
|
1579
|
-
|
|
1580
1484
|
return await self.send_message(
|
|
1581
1485
|
chat_id=chat_id,
|
|
1582
1486
|
text=text,
|
|
1583
1487
|
inline_keypad=builder.build(),
|
|
1584
1488
|
reply_to_message_id=reply_to_message_id
|
|
1585
1489
|
)
|
|
1586
|
-
|
|
1587
1490
|
def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
|
|
1588
|
-
|
|
1589
1491
|
client = self._get_client()
|
|
1590
1492
|
return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
|
|
1591
|
-
|
|
1592
1493
|
async def send_poll(self, chat_id: str, question: str, options: List[str]) -> Dict[str, Any]:
|
|
1593
1494
|
return await self._post("sendPoll", {"chat_id": chat_id, "question": question, "options": options})
|
|
1594
|
-
|
|
1595
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]:
|
|
1596
1496
|
payload = {"chat_id": chat_id, "latitude": latitude, "longitude": longitude, "disable_notification": disable_notification}
|
|
1597
1497
|
if inline_keypad: payload["inline_keypad"] = inline_keypad
|
|
1598
1498
|
if reply_to_message_id: payload["reply_to_message_id"] = reply_to_message_id
|
|
1599
1499
|
if chat_keypad_type: payload["chat_keypad_type"] = chat_keypad_type
|
|
1600
1500
|
return await self._post("sendLocation", {k: v for k, v in payload.items() if v is not None})
|
|
1601
|
-
|
|
1602
1501
|
async def send_contact(self, chat_id: str, first_name: str, last_name: str, phone_number: str) -> Dict[str, Any]:
|
|
1603
1502
|
return await self._post("sendContact", {"chat_id": chat_id, "first_name": first_name, "last_name": last_name, "phone_number": phone_number})
|
|
1604
|
-
|
|
1605
1503
|
async def get_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
1606
1504
|
return await self._post("getChat", {"chat_id": chat_id})
|
|
1607
|
-
|
|
1608
1505
|
async def upload_media_file(self, upload_url: str, name: str, path: Union[str, Path]) -> str:
|
|
1609
1506
|
is_temp_file = False
|
|
1610
1507
|
session = await self._get_session()
|
|
1611
|
-
|
|
1612
1508
|
if isinstance(path, str) and path.startswith("http"):
|
|
1613
1509
|
async with session.get(path) as response:
|
|
1614
1510
|
if response.status != 200:
|
|
1615
1511
|
raise Exception(f"Failed to download file from URL ({response.status})")
|
|
1616
|
-
|
|
1617
1512
|
content = await response.read()
|
|
1618
|
-
|
|
1619
1513
|
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
|
1620
1514
|
temp_file.write(content)
|
|
1621
1515
|
path = temp_file.name
|
|
1622
1516
|
is_temp_file = True
|
|
1623
|
-
|
|
1624
1517
|
file_size = os.path.getsize(path)
|
|
1625
|
-
|
|
1626
1518
|
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)
|
|
1627
|
-
|
|
1628
1519
|
async def file_progress_generator(file_path, chunk_size=8192):
|
|
1629
1520
|
async with aiofiles.open(file_path, 'rb') as f:
|
|
1630
1521
|
while True:
|
|
@@ -1633,7 +1524,6 @@ class Robot:
|
|
|
1633
1524
|
break
|
|
1634
1525
|
progress_bar.update(len(chunk))
|
|
1635
1526
|
yield chunk
|
|
1636
|
-
|
|
1637
1527
|
data = aiohttp.FormData()
|
|
1638
1528
|
data.add_field('file', file_progress_generator(path), filename=name, content_type='application/octet-stream')
|
|
1639
1529
|
try:
|
|
@@ -1648,12 +1538,9 @@ class Robot:
|
|
|
1648
1538
|
return json_data.get('data', {}).get('file_id')
|
|
1649
1539
|
except :
|
|
1650
1540
|
raise FeatureNotAvailableError(f"files is not currently supported by the server.")
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
1541
|
def get_extension(content_type: str) -> str:
|
|
1654
1542
|
ext = mimetypes.guess_extension(content_type)
|
|
1655
1543
|
return ext if ext else ''
|
|
1656
|
-
|
|
1657
1544
|
async def download(self, file_id: str, save_as: str = None, chunk_size: int = 1024 * 512,timeout_sec: int = 60, verbose: bool = False):
|
|
1658
1545
|
"""
|
|
1659
1546
|
Download a file from server using its file_id with chunked transfer,
|
|
@@ -1672,16 +1559,11 @@ class Robot:
|
|
|
1672
1559
|
Returns:
|
|
1673
1560
|
bool: True if success, raises exceptions otherwise.
|
|
1674
1561
|
"""
|
|
1675
|
-
|
|
1676
1562
|
try:
|
|
1677
1563
|
url = await self.get_url_file(file_id)
|
|
1678
|
-
if not url:
|
|
1679
|
-
|
|
1680
|
-
except Exception as e:
|
|
1681
|
-
raise ValueError(f"Failed to get download URL: {e}")
|
|
1682
|
-
|
|
1564
|
+
if not url:raise ValueError("Download URL not found in response.")
|
|
1565
|
+
except Exception as e:raise ValueError(f"Failed to get download URL: {e}")
|
|
1683
1566
|
timeout = aiohttp.ClientTimeout(total=timeout_sec)
|
|
1684
|
-
|
|
1685
1567
|
try:
|
|
1686
1568
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
1687
1569
|
async with session.get(url) as resp:
|
|
@@ -1693,21 +1575,17 @@ class Robot:
|
|
|
1693
1575
|
message="Failed to download file.",
|
|
1694
1576
|
headers=resp.headers
|
|
1695
1577
|
)
|
|
1696
|
-
|
|
1697
1578
|
if not save_as:
|
|
1698
1579
|
content_disp = resp.headers.get("Content-Disposition", "")
|
|
1699
1580
|
import re
|
|
1700
1581
|
match = re.search(r'filename="?([^\";]+)"?', content_disp)
|
|
1701
|
-
if match:
|
|
1702
|
-
save_as = match.group(1)
|
|
1582
|
+
if match:save_as = match.group(1)
|
|
1703
1583
|
else:
|
|
1704
1584
|
content_type = resp.headers.get("Content-Type", "").split(";")[0]
|
|
1705
1585
|
extension = mimetypes.guess_extension(content_type) or ".bin"
|
|
1706
1586
|
save_as = f"{file_id}{extension}"
|
|
1707
|
-
|
|
1708
1587
|
total_size = int(resp.headers.get("Content-Length", 0))
|
|
1709
1588
|
progress = tqdm(total=total_size, unit="B", unit_scale=True, disable=not verbose)
|
|
1710
|
-
|
|
1711
1589
|
async with aiofiles.open(save_as, "wb") as f:
|
|
1712
1590
|
async for chunk in resp.content.iter_chunked(chunk_size):
|
|
1713
1591
|
await f.write(chunk)
|
|
@@ -1770,7 +1648,7 @@ class Robot:
|
|
|
1770
1648
|
"inline_keypad":inline_keypad,
|
|
1771
1649
|
"chat_keypad_type":chat_keypad_type
|
|
1772
1650
|
}
|
|
1773
|
-
return result
|
|
1651
|
+
return AttrDict(result)
|
|
1774
1652
|
|
|
1775
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):
|
|
1776
1654
|
if path:
|
|
@@ -1789,43 +1667,31 @@ class Robot:
|
|
|
1789
1667
|
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)
|
|
1790
1668
|
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] = "music", inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
1791
1669
|
return await self._send_file_generic("File", chat_id, path, file_id, text, f"{file_name}.ogg", inline_keypad, chat_keypad, reply_to_message_id, disable_notification, chat_keypad_type)
|
|
1792
|
-
|
|
1793
1670
|
async def send_video(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
1794
1671
|
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)
|
|
1795
|
-
|
|
1796
1672
|
async def send_voice(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
1797
1673
|
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)
|
|
1798
|
-
|
|
1799
1674
|
async def send_image(self, chat_id: str, path: Optional[Union[str, Path]] = None, file_id: Optional[str] = None, text: Optional[str] = None, file_name: Optional[str] = None, inline_keypad: Optional[Dict[str, Any]] = None, chat_keypad: Optional[Dict[str, Any]] = None, reply_to_message_id: Optional[str] = None, disable_notification: bool = False, chat_keypad_type: Optional[Literal["New", "Removed", "None"]] = "None") -> Dict[str, Any]:
|
|
1800
1675
|
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)
|
|
1801
|
-
|
|
1802
1676
|
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"]] = "None") -> Dict[str, Any]:
|
|
1803
1677
|
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)
|
|
1804
|
-
|
|
1805
1678
|
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> Dict[str, Any]:
|
|
1806
1679
|
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})
|
|
1807
|
-
|
|
1808
1680
|
async def edit_message_text(self, chat_id: str, message_id: str, text: str) -> Dict[str, Any]:
|
|
1809
1681
|
return await self._post("editMessageText", {"chat_id": chat_id, "message_id": message_id, "text": text})
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
return await self._post("editMessageKeypad", {"chat_id": chat_id,
|
|
1813
|
-
|
|
1682
|
+
async def edit_inline_keypad(self,chat_id: str,message_id: str,inline_keypad: Dict[str, Any],text: str = None) -> Dict[str, Any]:
|
|
1683
|
+
if text is not None:await self._post("editMessageText", {"chat_id": chat_id,"message_id": message_id,"text": text})
|
|
1684
|
+
return await self._post("editMessageKeypad", {"chat_id": chat_id,"message_id": message_id,"inline_keypad": inline_keypad})
|
|
1814
1685
|
async def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
|
|
1815
1686
|
return await self._post("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
|
|
1816
|
-
|
|
1817
1687
|
async def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
1818
1688
|
return await self._post("setCommands", {"bot_commands": bot_commands})
|
|
1819
|
-
|
|
1820
1689
|
async def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
|
|
1821
1690
|
return await self._post("updateBotEndpoints", {"url": url, "type": type})
|
|
1822
|
-
|
|
1823
1691
|
async def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
|
|
1824
1692
|
return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "Removed"})
|
|
1825
|
-
|
|
1826
1693
|
async def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
|
|
1827
1694
|
return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "New", "chat_keypad": chat_keypad})
|
|
1828
|
-
|
|
1829
1695
|
async def get_name(self, chat_id: str) -> str:
|
|
1830
1696
|
try:
|
|
1831
1697
|
chat = await self.get_chat(chat_id)
|
|
@@ -1841,9 +1707,6 @@ class Robot:
|
|
|
1841
1707
|
return title if title else "null"
|
|
1842
1708
|
else:return "null"
|
|
1843
1709
|
except Exception:return "null"
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
1710
|
async def get_username(self, chat_id: str) -> str:
|
|
1848
1711
|
chat_info = await self.get_chat(chat_id)
|
|
1849
1712
|
return chat_info.get("data", {}).get("chat", {}).get("username", "None")
|
rubka/filters.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
import re
|
|
3
|
+
class TextFilter:
|
|
4
|
+
def __call__(self, keyword=None):
|
|
5
|
+
if keyword is None:
|
|
6
|
+
return Filter(lambda m: getattr(m, "is_text", False))
|
|
7
|
+
else:
|
|
8
|
+
return Filter(lambda m: getattr(m, "is_text", False) and keyword in getattr(m, "text", ""))
|
|
9
|
+
class Filter:
|
|
10
|
+
def __init__(self, func: Callable):
|
|
11
|
+
self.func = func
|
|
12
|
+
|
|
13
|
+
def __call__(self, message):
|
|
14
|
+
return self.func(message)
|
|
15
|
+
|
|
16
|
+
def __and__(self, other):
|
|
17
|
+
return Filter(lambda m: self(m) and other(m))
|
|
18
|
+
|
|
19
|
+
def __or__(self, other):
|
|
20
|
+
return Filter(lambda m: self(m) or other(m))
|
|
21
|
+
|
|
22
|
+
def __invert__(self):
|
|
23
|
+
return Filter(lambda m: not self(m))
|
|
24
|
+
|
|
25
|
+
def __xor__(self, other):
|
|
26
|
+
return Filter(lambda m: self(m) != other(m))
|
|
27
|
+
|
|
28
|
+
def __eq__(self, other):
|
|
29
|
+
return Filter(lambda m: self(m) == other)
|
|
30
|
+
|
|
31
|
+
def __ne__(self, other):
|
|
32
|
+
return Filter(lambda m: self(m) != other)
|
|
33
|
+
|
|
34
|
+
def __lt__(self, other):
|
|
35
|
+
return Filter(lambda m: self(m) < other)
|
|
36
|
+
|
|
37
|
+
def __le__(self, other):
|
|
38
|
+
return Filter(lambda m: self(m) <= other)
|
|
39
|
+
|
|
40
|
+
def __gt__(self, other):
|
|
41
|
+
return Filter(lambda m: self(m) > other)
|
|
42
|
+
|
|
43
|
+
def __ge__(self, other):
|
|
44
|
+
return Filter(lambda m: self(m) >= other)
|
|
45
|
+
|
|
46
|
+
def __add__(self, other):
|
|
47
|
+
return Filter(lambda m: self(m) + (other(m) if callable(other) else other))
|
|
48
|
+
|
|
49
|
+
def __sub__(self, other):
|
|
50
|
+
return Filter(lambda m: self(m) - (other(m) if callable(other) else other))
|
|
51
|
+
|
|
52
|
+
def __mul__(self, other):
|
|
53
|
+
return Filter(lambda m: self(m) * (other(m) if callable(other) else other))
|
|
54
|
+
|
|
55
|
+
def __truediv__(self, other):
|
|
56
|
+
return Filter(lambda m: self(m) / (other(m) if callable(other) else other))
|
|
57
|
+
class IsCommand(Filter):
|
|
58
|
+
def __init__(self, commands=None):
|
|
59
|
+
if commands is None:
|
|
60
|
+
func = lambda m: getattr(m, "is_command", False)
|
|
61
|
+
else:
|
|
62
|
+
if isinstance(commands, str):
|
|
63
|
+
commands = [commands]
|
|
64
|
+
func = lambda m: getattr(m, "is_command", False) and getattr(m, "text", "").lstrip("/").split()[0] in commands
|
|
65
|
+
|
|
66
|
+
super().__init__(func)
|
|
67
|
+
|
|
68
|
+
def __getattr__(self, name: str):
|
|
69
|
+
return IsCommand([name])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class IsText:
|
|
73
|
+
def __call__(self, text=None):
|
|
74
|
+
if text is None:
|
|
75
|
+
func = lambda m: m.is_text is True and m.is_file is False
|
|
76
|
+
else:
|
|
77
|
+
if isinstance(text, str):
|
|
78
|
+
text = [text]
|
|
79
|
+
func = lambda m: m.is_text is True and m.is_file is False and m.text in text
|
|
80
|
+
|
|
81
|
+
return Filter(func)
|
|
82
|
+
#is_text = Filter(lambda m: getattr(m, "is_text", False))
|
|
83
|
+
is_file = Filter(lambda m: getattr(m, "file", None) is not None)
|
|
84
|
+
is_sticker = Filter(lambda m: getattr(m, "sticker", None) is not None)
|
|
85
|
+
is_contact = Filter(lambda m: getattr(m, "contact_message", None) is not None)
|
|
86
|
+
is_poll = Filter(lambda m: getattr(m, "poll", None) is not None)
|
|
87
|
+
is_location = Filter(lambda m: getattr(m, "location", None) is not None)
|
|
88
|
+
is_live_location = Filter(lambda m: getattr(m, "live_location", None) is not None)
|
|
89
|
+
has_any_media = Filter(lambda m: getattr(m, "has_any_media", False))
|
|
90
|
+
has_media = Filter(lambda m: getattr(m, "has_media", False))
|
|
91
|
+
is_text = IsText()
|
|
92
|
+
is_command = IsCommand()
|
|
93
|
+
is_user = Filter(lambda m: getattr(m, "is_user", False))
|
|
94
|
+
is_private = Filter(lambda m: getattr(m, "is_private", False))
|
|
95
|
+
is_group = Filter(lambda m: getattr(m, "is_group", False))
|
|
96
|
+
is_channel = Filter(lambda m: getattr(m, "is_channel", False))
|
|
97
|
+
is_reply = Filter(lambda m: getattr(m, "is_reply", False))
|
|
98
|
+
is_forwarded = Filter(lambda m: getattr(m, "is_forwarded", False))
|
|
99
|
+
is_edited = Filter(lambda m: getattr(m, "is_edited", False))
|
|
100
|
+
def text(keyword: str):
|
|
101
|
+
return Filter(lambda m: getattr(m, "text", "") and keyword in m.text)
|
|
102
|
+
def text_length(min_len: int = 0, max_len: int = None):
|
|
103
|
+
def _filter(m):
|
|
104
|
+
t = getattr(m, "text", "")
|
|
105
|
+
if not t: return False
|
|
106
|
+
if len(t) < min_len: return False
|
|
107
|
+
if max_len is not None and len(t) > max_len: return False
|
|
108
|
+
return True
|
|
109
|
+
return Filter(_filter)
|
|
110
|
+
def text_regex(pattern: str):
|
|
111
|
+
regex = re.compile(pattern)
|
|
112
|
+
return Filter(lambda m: getattr(m, "text", "") and regex.search(m.text))
|
|
113
|
+
def text_startswith(prefix: str):
|
|
114
|
+
return Filter(lambda m: getattr(m, "text", "").startswith(prefix) if getattr(m, "text", None) else False)
|
|
115
|
+
def text_endswith(suffix: str):
|
|
116
|
+
return Filter(lambda m: getattr(m, "text", "").endswith(suffix) if getattr(m, "text", None) else False)
|
|
117
|
+
def text_upper():
|
|
118
|
+
return Filter(lambda m: getattr(m, "text", "").isupper() if getattr(m, "text", None) else False)
|
|
119
|
+
def text_lower():
|
|
120
|
+
return Filter(lambda m: getattr(m, "text", "").islower() if getattr(m, "text", None) else False)
|
|
121
|
+
def text_digit():
|
|
122
|
+
return Filter(lambda m: getattr(m, "text", "").isdigit() if getattr(m, "text", None) else False)
|
|
123
|
+
def text_word_count(min_words: int = 1, max_words: int = None):
|
|
124
|
+
def _filter(m):
|
|
125
|
+
t = getattr(m, "text", "")
|
|
126
|
+
if not t: return False
|
|
127
|
+
wc = len(t.split())
|
|
128
|
+
if wc < min_words: return False
|
|
129
|
+
if max_words is not None and wc > max_words: return False
|
|
130
|
+
return True
|
|
131
|
+
return Filter(_filter)
|
|
132
|
+
def text_contains_any(keywords: list):
|
|
133
|
+
return Filter(lambda m: getattr(m, "text", "") and any(k in m.text for k in keywords))
|
|
134
|
+
def text_equals(value: str):
|
|
135
|
+
return Filter(lambda m: getattr(m, "text", None) == value)
|
|
136
|
+
def text_not_equals(value: str):
|
|
137
|
+
return Filter(lambda m: getattr(m, "text", None) != value)
|
|
138
|
+
def file_size_gt(size: int):
|
|
139
|
+
return Filter(lambda m: m.file and getattr(m.file, "size", 0) > size)
|
|
140
|
+
def file_size_lt(size: int):
|
|
141
|
+
return Filter(lambda m: m.file and getattr(m.file, "size", 0) < size)
|
|
142
|
+
def file_name_contains(substring: str):
|
|
143
|
+
return Filter(lambda m: m.file and substring in getattr(m.file, "file_name", ""))
|
|
144
|
+
def file_extension(ext: str):
|
|
145
|
+
return Filter(lambda m: m.file and getattr(m.file, "file_name", "").endswith(ext))
|
|
146
|
+
def file_id_is(file_id: str):
|
|
147
|
+
return Filter(lambda m: m.file and getattr(m.file, "file_id", None) == file_id)
|
|
148
|
+
def sticker_id_is(sid: str):
|
|
149
|
+
return Filter(lambda m: m.sticker and getattr(m.sticker, "sticker_id", None) == sid)
|
|
150
|
+
def sticker_emoji_is(emoji: str):
|
|
151
|
+
return Filter(lambda m: m.sticker and getattr(m.sticker, "emoji", None) == emoji)
|
|
152
|
+
def poll_question_contains(keyword: str):
|
|
153
|
+
return Filter(lambda m: m.poll and keyword in getattr(m.poll, "question", ""))
|
|
154
|
+
def poll_option_count(min_options: int = 1, max_options: int = None):
|
|
155
|
+
def _filter(m):
|
|
156
|
+
if not getattr(m, "poll", None): return False
|
|
157
|
+
options = getattr(m.poll, "options", [])
|
|
158
|
+
if len(options) < min_options: return False
|
|
159
|
+
if max_options is not None and len(options) > max_options: return False
|
|
160
|
+
return True
|
|
161
|
+
return Filter(_filter)
|
|
162
|
+
def location_within(lat_min, lat_max, long_min, long_max):
|
|
163
|
+
def _filter(m):
|
|
164
|
+
loc = getattr(m, "location", None)
|
|
165
|
+
if not loc: return False
|
|
166
|
+
return lat_min <= getattr(loc, "lat", 0) <= lat_max and long_min <= getattr(loc, "long", 0) <= long_max
|
|
167
|
+
return Filter(_filter)
|
|
168
|
+
def live_location_within(lat_min, lat_max, long_min, long_max):
|
|
169
|
+
def _filter(m):
|
|
170
|
+
loc = getattr(m, "live_location", None)
|
|
171
|
+
if not loc: return False
|
|
172
|
+
return lat_min <= getattr(loc, "lat", 0) <= lat_max and long_min <= getattr(loc, "long", 0) <= long_max
|
|
173
|
+
return Filter(_filter)
|
|
174
|
+
def has_media_types(types: list):
|
|
175
|
+
return Filter(lambda m: any(getattr(m, t, None) for t in types))
|
|
176
|
+
|
|
177
|
+
def message_id_is(mid: str):
|
|
178
|
+
return Filter(lambda m: getattr(m, "message_id", None) == mid)
|
|
179
|
+
|
|
180
|
+
def is_reply_to_user(user_id: str):
|
|
181
|
+
return Filter(lambda m: getattr(m, "reply_to_message_id", None) == user_id)
|
|
182
|
+
|
|
183
|
+
def is_forwarded_from(user_id: str):
|
|
184
|
+
return Filter(lambda m: getattr(m.forwarded_from, "sender_id", None) == user_id if getattr(m, "forwarded_from", None) else False)
|
|
185
|
+
|
|
186
|
+
def edited_text_contains(keyword: str):
|
|
187
|
+
return Filter(lambda m: getattr(m, "edited_text", "") and keyword in m.edited_text)
|
|
188
|
+
|
|
189
|
+
def aux_data_contains(key: str, value):
|
|
190
|
+
return Filter(lambda m: getattr(m.aux_data, key, None) == value if getattr(m, "aux_data", None) else False)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def file_attr(attr_name):
|
|
196
|
+
return Filter(lambda m: m.file and getattr(m.file, attr_name, None))
|
|
197
|
+
|
|
198
|
+
def sticker_attr(attr_name):
|
|
199
|
+
return Filter(lambda m: m.sticker and getattr(m.sticker, attr_name, None))
|
|
200
|
+
|
|
201
|
+
def poll_attr(attr_name):
|
|
202
|
+
return Filter(lambda m: m.poll and getattr(m.poll, attr_name, None))
|
|
203
|
+
|
|
204
|
+
def location_attr(attr_name):
|
|
205
|
+
return Filter(lambda m: m.location and getattr(m.location, attr_name, None))
|
|
206
|
+
|
|
207
|
+
def live_location_attr(attr_name):
|
|
208
|
+
return Filter(lambda m: m.live_location and getattr(m.live_location, attr_name, None))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
file_size = file_attr("size")
|
|
214
|
+
file_name = file_attr("file_name")
|
|
215
|
+
sticker_id = sticker_attr("sticker_id")
|
|
216
|
+
poll_question = poll_attr("question")
|
|
217
|
+
location_lat = location_attr("lat")
|
|
218
|
+
location_long = location_attr("long")
|
|
219
|
+
live_location_lat = live_location_attr("lat")
|
|
220
|
+
live_location_long = live_location_attr("long")
|
|
221
|
+
|
|
222
|
+
_custom_filters = {}
|
|
223
|
+
def chat_title_contains(keyword: str):
|
|
224
|
+
|
|
225
|
+
return Filter(lambda m: getattr(m, "chat", None) and keyword in getattr(m.chat, "title", ""))
|
|
226
|
+
|
|
227
|
+
def chat_title_equals(value: str):
|
|
228
|
+
|
|
229
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "title", "") == value)
|
|
230
|
+
|
|
231
|
+
def chat_id_is(cid: str):
|
|
232
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "id", None) == cid)
|
|
233
|
+
|
|
234
|
+
def chat_member_count(min_count: int = 0, max_count: int = None):
|
|
235
|
+
|
|
236
|
+
def _filter(m):
|
|
237
|
+
c = getattr(m, "chat", None)
|
|
238
|
+
if not c: return False
|
|
239
|
+
count = getattr(c, "member_count", 0)
|
|
240
|
+
if count < min_count: return False
|
|
241
|
+
if max_count is not None and count > max_count: return False
|
|
242
|
+
return True
|
|
243
|
+
return Filter(_filter)
|
|
244
|
+
|
|
245
|
+
def chat_type_is(chat_type: str):
|
|
246
|
+
|
|
247
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "type", None) == chat_type)
|
|
248
|
+
|
|
249
|
+
def chat_username_contains(keyword: str):
|
|
250
|
+
|
|
251
|
+
return Filter(lambda m: getattr(m, "chat", None) and keyword in getattr(m.chat, "username", ""))
|
|
252
|
+
|
|
253
|
+
def chat_username_equals(value: str):
|
|
254
|
+
|
|
255
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "username", "") == value)
|
|
256
|
+
|
|
257
|
+
def chat_has_link():
|
|
258
|
+
|
|
259
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "invite_link", None) is not None)
|
|
260
|
+
|
|
261
|
+
def chat_is_private():
|
|
262
|
+
|
|
263
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "type", None) in ["group", "channel"])
|
|
264
|
+
|
|
265
|
+
def chat_member_count_gt(count: int):
|
|
266
|
+
|
|
267
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "member_count", 0) > count)
|
|
268
|
+
|
|
269
|
+
def chat_member_count_lt(count: int):
|
|
270
|
+
|
|
271
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "member_count", 0) < count)
|
|
272
|
+
|
|
273
|
+
def chat_has_username():
|
|
274
|
+
|
|
275
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "username", None) is not None)
|
|
276
|
+
|
|
277
|
+
def chat_type_in(types: list):
|
|
278
|
+
|
|
279
|
+
return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "type", None) in types)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def chat_title_regex(pattern: str):
|
|
283
|
+
regex = re.compile(pattern)
|
|
284
|
+
return Filter(lambda m: getattr(m, "chat", None) and regex.search(getattr(m.chat, "title", "")))
|
|
285
|
+
|
|
286
|
+
def chat_username_regex(pattern: str):
|
|
287
|
+
regex = re.compile(pattern)
|
|
288
|
+
return Filter(lambda m: getattr(m, "chat", None) and regex.search(getattr(m.chat, "username", "")))
|
|
289
|
+
def custom(name):
|
|
290
|
+
def wrapper(func):
|
|
291
|
+
_custom_filters[name] = Filter(func)
|
|
292
|
+
return _custom_filters[name]
|
|
293
|
+
return wrapper
|
|
294
|
+
|
|
295
|
+
def get_custom(name):
|
|
296
|
+
return _custom_filters.get(name)
|
|
297
|
+
|
|
298
|
+
def and_(*filters):
|
|
299
|
+
return Filter(lambda m: all(f(m) for f in filters))
|
|
300
|
+
|
|
301
|
+
def or_(*filters):
|
|
302
|
+
return Filter(lambda m: any(f(m) for f in filters))
|
|
303
|
+
|
|
304
|
+
def not_(filter_):
|
|
305
|
+
return Filter(lambda m: not filter_(m))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.6.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,11 +1,12 @@
|
|
|
1
1
|
rubka/__init__.py,sha256=TR1DABU5Maz2eO62ZEFiwOqNU0dH6l6HZfqRUxeo4eY,194
|
|
2
|
-
rubka/api.py,sha256=
|
|
3
|
-
rubka/asynco.py,sha256=
|
|
2
|
+
rubka/api.py,sha256=FkvXi6lGyDhy-GStfT9IPtmnpir75ggNsC4bsgy2tKw,68838
|
|
3
|
+
rubka/asynco.py,sha256=O5sr6Ev7LCsC5egjDk5uzJyeZEPqmxOH27lUBgzHM8A,83517
|
|
4
4
|
rubka/button.py,sha256=vU9OvWXCD4MRrTJ8Xmivd4L471-06zrD2qpZBTw5vjY,13305
|
|
5
5
|
rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
|
|
6
6
|
rubka/context.py,sha256=4YZs7DiZD_HWOqY76hwwajG0J-bLy6wjeKtQT3EatZU,19341
|
|
7
7
|
rubka/decorators.py,sha256=hGwUoE4q2ImrunJIGJ_kzGYYxQf1ueE0isadqraKEts,1157
|
|
8
8
|
rubka/exceptions.py,sha256=tujZt1XrhWaw-lmdeVadVceUptpw4XzNgE44sAAY0gs,90
|
|
9
|
+
rubka/filters.py,sha256=DY1bdkpRKIiLtVcy6X3hOnlGPcVOK4HFb3QgmaPx6Oo,12116
|
|
9
10
|
rubka/jobs.py,sha256=GvLMLsVhcSEzRTgkvnPISPEBN71suW2xXI0hUaUZPTo,378
|
|
10
11
|
rubka/keyboards.py,sha256=7nr-dT2bQJVQnQ6RMWPTSjML6EEk6dsBx-4d8pab8xk,488
|
|
11
12
|
rubka/keypad.py,sha256=yGsNt8W5HtUFBzVF1m_p7GySlu1hwIcSvXZ4BTdrlvg,9558
|
|
@@ -34,7 +35,7 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
|
|
|
34
35
|
rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
|
|
35
36
|
rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
|
|
36
37
|
rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
|
|
37
|
-
rubka-6.
|
|
38
|
-
rubka-6.
|
|
39
|
-
rubka-6.
|
|
40
|
-
rubka-6.
|
|
38
|
+
rubka-6.6.2.dist-info/METADATA,sha256=-XVEYYI0-PnUjEzLHgnEifIpRem8h7RqL3V7sA07jhQ,33335
|
|
39
|
+
rubka-6.6.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
rubka-6.6.2.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
|
|
41
|
+
rubka-6.6.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|