Rubka 6.5.2__py3-none-any.whl → 6.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Rubka might be problematic. Click here for more details.
- rubka/api.py +3 -1
- rubka/asynco.py +102 -251
- rubka/filters.py +34 -46
- rubka/rubino.py +59 -8
- {rubka-6.5.2.dist-info → rubka-6.6.4.dist-info}/METADATA +24 -10
- {rubka-6.5.2.dist-info → rubka-6.6.4.dist-info}/RECORD +9 -8
- rubka-6.6.4.dist-info/entry_points.txt +2 -0
- {rubka-6.5.2.dist-info → rubka-6.6.4.dist-info}/WHEEL +0 -0
- {rubka-6.5.2.dist-info → rubka-6.6.4.dist-info}/top_level.txt +0 -0
rubka/api.py
CHANGED
|
@@ -1669,9 +1669,11 @@ class Robot:
|
|
|
1669
1669
|
self,
|
|
1670
1670
|
chat_id: str,
|
|
1671
1671
|
message_id: str,
|
|
1672
|
-
inline_keypad: Dict[str, Any]
|
|
1672
|
+
inline_keypad: Dict[str, Any],
|
|
1673
|
+
text : str = None
|
|
1673
1674
|
) -> Dict[str, Any]:
|
|
1674
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})
|
|
1675
1677
|
return self._post("editMessageKeypad", {
|
|
1676
1678
|
"chat_id": chat_id,
|
|
1677
1679
|
"message_id": message_id,
|
rubka/asynco.py
CHANGED
|
@@ -1,31 +1,20 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import
|
|
3
|
-
import aiofiles
|
|
4
|
-
from typing import List, Optional, Dict, Any, Literal, Callable, Union
|
|
1
|
+
import asyncio,aiohttp,aiofiles,time,datetime,json,tempfile,os,sys,subprocess,mimetypes
|
|
2
|
+
from typing import List, Optional, Dict, Any, Literal, Callable, Union,Set
|
|
5
3
|
from .exceptions import APIRequestError
|
|
6
4
|
from .adaptorrubka import Client as Client_get
|
|
7
5
|
from .logger import logger
|
|
6
|
+
from .rubino import Bot as Rubino
|
|
8
7
|
from . import filters
|
|
9
|
-
try:
|
|
10
|
-
|
|
11
|
-
except (ImportError, ModuleNotFoundError):
|
|
12
|
-
from context import Message, InlineMessage
|
|
8
|
+
try:from .context import Message, InlineMessage
|
|
9
|
+
except (ImportError, ModuleNotFoundError):from context import Message, InlineMessage
|
|
13
10
|
class FeatureNotAvailableError(Exception):
|
|
14
11
|
pass
|
|
15
12
|
|
|
16
13
|
from tqdm.asyncio import tqdm
|
|
17
14
|
from urllib.parse import urlparse, parse_qs
|
|
18
15
|
class InvalidTokenError(Exception):pass
|
|
19
|
-
import mimetypes
|
|
20
16
|
from pathlib import Path
|
|
21
|
-
import time
|
|
22
|
-
import datetime,json
|
|
23
|
-
import tempfile
|
|
24
17
|
from tqdm import tqdm
|
|
25
|
-
import os
|
|
26
|
-
import sys
|
|
27
|
-
import subprocess
|
|
28
|
-
|
|
29
18
|
API_URL = "https://botapi.rubika.ir/v3"
|
|
30
19
|
|
|
31
20
|
def install_package(package_name: str) -> bool:
|
|
@@ -35,31 +24,7 @@ def install_package(package_name: str) -> bool:
|
|
|
35
24
|
return True
|
|
36
25
|
except Exception:
|
|
37
26
|
return False
|
|
38
|
-
|
|
39
|
-
def __init__(self, resp: dict, chat_id: str, file_id: str,
|
|
40
|
-
type_file: str, reply_to_message_id: str,
|
|
41
|
-
disable_notification: bool, text: str = None,
|
|
42
|
-
chat_keypad=None, inline_keypad=None, chat_keypad_type="None"):
|
|
43
|
-
|
|
44
|
-
self.status = resp.get("status")
|
|
45
|
-
self.status_det = resp.get("status_det")
|
|
46
|
-
self.file_id = file_id
|
|
47
|
-
self.message_id = resp["data"].get("message_id")
|
|
48
|
-
self.send_to_chat_id = chat_id
|
|
49
|
-
self.reply_to_message_id = reply_to_message_id
|
|
50
|
-
self.disable_notification = disable_notification
|
|
51
|
-
self.type_file = type_file
|
|
52
|
-
self.text = text
|
|
53
|
-
self.chat_keypad = chat_keypad
|
|
54
|
-
self.inline_keypad = inline_keypad
|
|
55
|
-
self.chat_keypad_type = chat_keypad_type
|
|
56
|
-
self.raw_response = resp
|
|
57
|
-
|
|
58
|
-
def to_dict(self):
|
|
59
|
-
return self.__dict__
|
|
60
|
-
|
|
61
|
-
def to_json(self):
|
|
62
|
-
return json.dumps(self.__dict__, ensure_ascii=False, indent=4)
|
|
27
|
+
|
|
63
28
|
def get_importlib_metadata():
|
|
64
29
|
"""Dynamically imports and returns metadata functions from importlib."""
|
|
65
30
|
try:
|
|
@@ -124,12 +89,40 @@ def show_last_six_words(text: str) -> str:
|
|
|
124
89
|
"""Returns the last 6 characters of a stripped string."""
|
|
125
90
|
text = text.strip()
|
|
126
91
|
return text[-6:]
|
|
127
|
-
|
|
92
|
+
class AttrDict(dict):
|
|
93
|
+
def __getattr__(self, item):
|
|
94
|
+
value = self.get(item)
|
|
95
|
+
if isinstance(value, dict):
|
|
96
|
+
return AttrDict(value)
|
|
97
|
+
return value
|
|
128
98
|
|
|
129
99
|
class Robot:
|
|
130
100
|
"""
|
|
131
|
-
Main
|
|
132
|
-
|
|
101
|
+
Main asynchronous class to interact with the Rubika Bot API.
|
|
102
|
+
|
|
103
|
+
This class handles sending and receiving messages, inline queries, callbacks,
|
|
104
|
+
and manages sessions and API interactions. It is initialized with a bot token
|
|
105
|
+
and provides multiple optional parameters for configuration.
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
token (str): Bot token used for authentication with Rubika Bot API.
|
|
109
|
+
session_name (str | None): Optional session name for storing session data.
|
|
110
|
+
auth (str | None): Optional authentication string for advanced features.
|
|
111
|
+
Key (str | None): Optional key for additional authorization if required.
|
|
112
|
+
platform (str): Platform type, default is 'web'.
|
|
113
|
+
web_hook (str | None): Optional webhook URL for receiving updates.
|
|
114
|
+
timeout (int): Timeout for API requests in seconds (default 10).
|
|
115
|
+
show_progress (bool): Whether to show progress for long operations (default False).
|
|
116
|
+
Example:
|
|
117
|
+
```python
|
|
118
|
+
import asyncio
|
|
119
|
+
from rubka.asynco import Robot,filters,Message
|
|
120
|
+
bot = Robot(token="YOUR_BOT_TOKEN")
|
|
121
|
+
@bot.on_message(filters.is_command.start)
|
|
122
|
+
async def start_command(bot: Robot, message: Message):
|
|
123
|
+
await message.reply("Hello!")
|
|
124
|
+
asyncio.run(bot.run())
|
|
125
|
+
|
|
133
126
|
"""
|
|
134
127
|
|
|
135
128
|
def __init__(self, token: str, session_name: str = None, auth: str = None, Key: str = None, platform: str = "web", web_hook: str = None, timeout: int = 10, show_progress: bool = False):
|
|
@@ -173,7 +166,6 @@ class Robot:
|
|
|
173
166
|
print(data)
|
|
174
167
|
json_url = data.get('url', self.web_hook)
|
|
175
168
|
print(self.web_hook)
|
|
176
|
-
|
|
177
169
|
for endpoint_type in [
|
|
178
170
|
"ReceiveUpdate",
|
|
179
171
|
"ReceiveInlineMessage",
|
|
@@ -187,8 +179,6 @@ class Robot:
|
|
|
187
179
|
except Exception as e:
|
|
188
180
|
logger.error(f"Failed to set webhook from {self.web_hook}: {e}")
|
|
189
181
|
self.web_hook = None
|
|
190
|
-
|
|
191
|
-
|
|
192
182
|
async def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
193
183
|
url = f"{API_URL}/{self.token}/{method}"
|
|
194
184
|
session = await self._get_session()
|
|
@@ -201,11 +191,9 @@ class Robot:
|
|
|
201
191
|
text_resp = await response.text()
|
|
202
192
|
logger.error(f"Invalid JSON response from {method}: {text_resp}")
|
|
203
193
|
raise APIRequestError(f"Invalid JSON response: {text_resp}")
|
|
204
|
-
|
|
205
194
|
if method != "getUpdates":
|
|
206
195
|
logger.debug(f"API Response from {method}: {json_resp}")
|
|
207
|
-
|
|
208
|
-
return json_resp
|
|
196
|
+
return AttrDict(json_resp)
|
|
209
197
|
except aiohttp.ClientError as e:
|
|
210
198
|
logger.error(f"API request failed: {e}")
|
|
211
199
|
raise APIRequestError(f"API request failed: {e}") from e
|
|
@@ -275,8 +263,6 @@ class Robot:
|
|
|
275
263
|
return
|
|
276
264
|
if not allow_locations and (message.location or message.live_location):
|
|
277
265
|
return
|
|
278
|
-
|
|
279
|
-
|
|
280
266
|
if message.text:
|
|
281
267
|
text = message.text if case_sensitive else message.text.lower()
|
|
282
268
|
if min_text_length and len(message.text) < min_text_length:
|
|
@@ -289,8 +275,6 @@ class Robot:
|
|
|
289
275
|
return
|
|
290
276
|
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
291
277
|
return
|
|
292
|
-
|
|
293
|
-
|
|
294
278
|
if commands:
|
|
295
279
|
if not message.text:
|
|
296
280
|
return
|
|
@@ -299,13 +283,9 @@ class Robot:
|
|
|
299
283
|
if cmd not in commands:
|
|
300
284
|
return
|
|
301
285
|
message.args = parts[1:]
|
|
302
|
-
|
|
303
|
-
|
|
304
286
|
if filters and not filters(message):
|
|
305
287
|
return
|
|
306
|
-
|
|
307
288
|
return await func(bot, message)
|
|
308
|
-
|
|
309
289
|
self._message_handlers.append({
|
|
310
290
|
"func": wrapper,
|
|
311
291
|
"filters": filters,
|
|
@@ -346,30 +326,20 @@ class Robot:
|
|
|
346
326
|
|
|
347
327
|
if not message.is_channel:
|
|
348
328
|
return
|
|
349
|
-
|
|
350
|
-
|
|
351
329
|
if chat_id:
|
|
352
330
|
if isinstance(chat_id, str) and message.chat_id != chat_id:
|
|
353
331
|
return
|
|
354
332
|
if isinstance(chat_id, list) and message.chat_id not in chat_id:
|
|
355
333
|
return
|
|
356
|
-
|
|
357
|
-
|
|
358
334
|
if sender_id:
|
|
359
335
|
if isinstance(sender_id, str) and message.sender_id != sender_id:
|
|
360
336
|
return
|
|
361
337
|
if isinstance(sender_id, list) and message.sender_id not in sender_id:
|
|
362
338
|
return
|
|
363
|
-
|
|
364
|
-
|
|
365
339
|
if sender_type and message.sender_type != sender_type:
|
|
366
340
|
return
|
|
367
|
-
|
|
368
|
-
|
|
369
341
|
if not allow_forwarded and message.forwarded_from:
|
|
370
342
|
return
|
|
371
|
-
|
|
372
|
-
|
|
373
343
|
if not allow_files and message.file:
|
|
374
344
|
return
|
|
375
345
|
if not allow_stickers and message.sticker:
|
|
@@ -380,8 +350,6 @@ class Robot:
|
|
|
380
350
|
return
|
|
381
351
|
if not allow_locations and (message.location or message.live_location):
|
|
382
352
|
return
|
|
383
|
-
|
|
384
|
-
|
|
385
353
|
if message.text:
|
|
386
354
|
text = message.text if case_sensitive else message.text.lower()
|
|
387
355
|
if min_text_length and len(message.text) < min_text_length:
|
|
@@ -394,8 +362,6 @@ class Robot:
|
|
|
394
362
|
return
|
|
395
363
|
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
396
364
|
return
|
|
397
|
-
|
|
398
|
-
|
|
399
365
|
if commands:
|
|
400
366
|
if not message.text:
|
|
401
367
|
return
|
|
@@ -403,14 +369,10 @@ class Robot:
|
|
|
403
369
|
cmd = parts[0].lstrip("/")
|
|
404
370
|
if cmd not in commands:
|
|
405
371
|
return
|
|
406
|
-
message.args = parts[1:]
|
|
407
|
-
|
|
408
|
-
|
|
372
|
+
message.args = parts[1:]
|
|
409
373
|
if filters and not filters(message):
|
|
410
374
|
return
|
|
411
|
-
|
|
412
375
|
return await func(bot, message)
|
|
413
|
-
|
|
414
376
|
self._message_handlers.append({
|
|
415
377
|
"func": wrapper,
|
|
416
378
|
"filters": filters,
|
|
@@ -448,33 +410,22 @@ class Robot:
|
|
|
448
410
|
|
|
449
411
|
def decorator(func: Callable[[Any, Message], None]):
|
|
450
412
|
async def wrapper(bot, message: Message):
|
|
451
|
-
|
|
452
413
|
if not message.is_group:
|
|
453
414
|
return
|
|
454
|
-
|
|
455
|
-
|
|
456
415
|
if chat_id:
|
|
457
416
|
if isinstance(chat_id, str) and message.chat_id != chat_id:
|
|
458
417
|
return
|
|
459
418
|
if isinstance(chat_id, list) and message.chat_id not in chat_id:
|
|
460
419
|
return
|
|
461
|
-
|
|
462
|
-
|
|
463
420
|
if sender_id:
|
|
464
421
|
if isinstance(sender_id, str) and message.sender_id != sender_id:
|
|
465
422
|
return
|
|
466
423
|
if isinstance(sender_id, list) and message.sender_id not in sender_id:
|
|
467
424
|
return
|
|
468
|
-
|
|
469
|
-
|
|
470
425
|
if sender_type and message.sender_type != sender_type:
|
|
471
426
|
return
|
|
472
|
-
|
|
473
|
-
|
|
474
427
|
if not allow_forwarded and message.forwarded_from:
|
|
475
428
|
return
|
|
476
|
-
|
|
477
|
-
|
|
478
429
|
if not allow_files and message.file:
|
|
479
430
|
return
|
|
480
431
|
if not allow_stickers and message.sticker:
|
|
@@ -485,8 +436,6 @@ class Robot:
|
|
|
485
436
|
return
|
|
486
437
|
if not allow_locations and (message.location or message.live_location):
|
|
487
438
|
return
|
|
488
|
-
|
|
489
|
-
|
|
490
439
|
if message.text:
|
|
491
440
|
text = message.text if case_sensitive else message.text.lower()
|
|
492
441
|
if min_text_length and len(message.text) < min_text_length:
|
|
@@ -499,8 +448,6 @@ class Robot:
|
|
|
499
448
|
return
|
|
500
449
|
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
501
450
|
return
|
|
502
|
-
|
|
503
|
-
|
|
504
451
|
if commands:
|
|
505
452
|
if not message.text:
|
|
506
453
|
return
|
|
@@ -509,13 +456,9 @@ class Robot:
|
|
|
509
456
|
if cmd not in commands:
|
|
510
457
|
return
|
|
511
458
|
message.args = parts[1:]
|
|
512
|
-
|
|
513
|
-
|
|
514
459
|
if filters and not filters(message):
|
|
515
460
|
return
|
|
516
|
-
|
|
517
461
|
return await func(bot, message)
|
|
518
|
-
|
|
519
462
|
self._message_handlers.append({
|
|
520
463
|
"func": wrapper,
|
|
521
464
|
"filters": filters,
|
|
@@ -738,13 +681,12 @@ class Robot:
|
|
|
738
681
|
|
|
739
682
|
async def _handle_inline_query(self, inline_message: InlineMessage):
|
|
740
683
|
aux_button_id = inline_message.aux_data.button_id if inline_message.aux_data else None
|
|
741
|
-
|
|
742
684
|
for handler in self._inline_query_handlers:
|
|
743
685
|
if handler["button_id"] is None or handler["button_id"] == aux_button_id:
|
|
744
686
|
try:
|
|
745
687
|
await handler["func"](self, inline_message)
|
|
746
688
|
except Exception as e:
|
|
747
|
-
|
|
689
|
+
raise Exception(f"Error in inline query handler: {e}")
|
|
748
690
|
|
|
749
691
|
def on_inline_query(self, button_id: Optional[str] = None):
|
|
750
692
|
def decorator(func: Callable[[Any, InlineMessage], None]):
|
|
@@ -757,33 +699,19 @@ class Robot:
|
|
|
757
699
|
def on_inline_query_prefix(self, prefix: str, button_id: Optional[str] = None):
|
|
758
700
|
if not prefix.startswith('/'):
|
|
759
701
|
prefix = '/' + prefix
|
|
760
|
-
|
|
761
702
|
def decorator(func: Callable[[Any, InlineMessage], None]):
|
|
762
|
-
|
|
763
703
|
async def handler_wrapper(bot_instance, inline_message: InlineMessage):
|
|
764
|
-
|
|
765
704
|
if not inline_message.raw_data or 'text' not in inline_message.raw_data:
|
|
766
705
|
return
|
|
767
|
-
|
|
768
706
|
query_text = inline_message.raw_data['text']
|
|
769
|
-
|
|
770
|
-
|
|
771
707
|
if query_text.startswith(prefix):
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
708
|
try:
|
|
776
709
|
await func(bot_instance, inline_message)
|
|
777
710
|
except Exception as e:
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
711
|
+
raise Exception(f"Error in inline query prefix handler '{prefix}': {e}")
|
|
781
712
|
self._inline_query_handlers.append({
|
|
782
713
|
"func": handler_wrapper,
|
|
783
|
-
"button_id": button_id
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
714
|
+
"button_id": button_id
|
|
787
715
|
})
|
|
788
716
|
return func
|
|
789
717
|
return decorator
|
|
@@ -791,40 +719,29 @@ class Robot:
|
|
|
791
719
|
if update.get("type") == "ReceiveQuery":
|
|
792
720
|
msg = update.get("inline_message", {})
|
|
793
721
|
context = InlineMessage(bot=self, raw_data=msg)
|
|
794
|
-
|
|
795
|
-
|
|
796
722
|
if hasattr(self, "_callback_handlers"):
|
|
797
723
|
for handler in self._callback_handlers:
|
|
798
724
|
if not handler["button_id"] or getattr(context.aux_data, "button_id", None) == handler["button_id"]:
|
|
799
725
|
asyncio.create_task(handler["func"](self, context))
|
|
800
|
-
|
|
801
|
-
|
|
802
726
|
asyncio.create_task(self._handle_inline_query(context))
|
|
803
727
|
return
|
|
804
728
|
|
|
805
729
|
if update.get("type") == "NewMessage":
|
|
806
730
|
msg = update.get("new_message", {})
|
|
807
731
|
try:
|
|
808
|
-
if msg.get("time") and (time.time() - float(msg["time"])) > 20:
|
|
809
|
-
|
|
810
|
-
except (ValueError, TypeError):
|
|
811
|
-
return
|
|
812
|
-
|
|
732
|
+
if msg.get("time") and (time.time() - float(msg["time"])) > 20:return
|
|
733
|
+
except (ValueError, TypeError):return
|
|
813
734
|
context = Message(bot=self,
|
|
814
735
|
chat_id=update.get("chat_id"),
|
|
815
736
|
message_id=msg.get("message_id"),
|
|
816
737
|
sender_id=msg.get("sender_id"),
|
|
817
738
|
text=msg.get("text"),
|
|
818
739
|
raw_data=msg)
|
|
819
|
-
|
|
820
|
-
|
|
821
740
|
if context.aux_data and self._callback_handlers:
|
|
822
741
|
for handler in self._callback_handlers:
|
|
823
742
|
if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
|
|
824
743
|
asyncio.create_task(handler["func"](self, context))
|
|
825
744
|
return
|
|
826
|
-
|
|
827
|
-
|
|
828
745
|
if self._message_handlers:
|
|
829
746
|
for handler_info in self._message_handlers:
|
|
830
747
|
|
|
@@ -836,18 +753,12 @@ class Robot:
|
|
|
836
753
|
if cmd not in handler_info["commands"]:
|
|
837
754
|
continue
|
|
838
755
|
context.args = parts[1:]
|
|
839
|
-
|
|
840
|
-
|
|
841
756
|
if handler_info["filters"]:
|
|
842
757
|
if not handler_info["filters"](context):
|
|
843
758
|
continue
|
|
844
|
-
|
|
845
|
-
|
|
846
759
|
if not handler_info["commands"] and not handler_info["filters"]:
|
|
847
760
|
asyncio.create_task(handler_info["func"](self, context))
|
|
848
761
|
continue
|
|
849
|
-
|
|
850
|
-
|
|
851
762
|
if handler_info["commands"] or handler_info["filters"]:
|
|
852
763
|
asyncio.create_task(handler_info["func"](self, context))#jaq
|
|
853
764
|
continue
|
|
@@ -864,72 +775,65 @@ class Robot:
|
|
|
864
775
|
if offset_id: params['offset_id'] = offset_id
|
|
865
776
|
if limit: params['limit'] = limit
|
|
866
777
|
async with session.get(self.web_hook, params=params) as response:
|
|
867
|
-
response.raise_for_status()
|
|
868
|
-
|
|
778
|
+
response.raise_for_status()
|
|
869
779
|
return await response.json()
|
|
870
780
|
|
|
871
781
|
def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
|
|
872
782
|
now = time.time()
|
|
873
783
|
expired = [mid for mid, ts in self._processed_message_ids.items() if now - ts > max_age_sec]
|
|
874
|
-
for mid in expired:
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
if message_id in self._processed_message_ids:
|
|
878
|
-
return True
|
|
879
|
-
|
|
784
|
+
for mid in expired:del self._processed_message_ids[mid]
|
|
785
|
+
if message_id in self._processed_message_ids:return True
|
|
880
786
|
self._processed_message_ids[message_id] = now
|
|
881
787
|
return False
|
|
882
788
|
|
|
883
789
|
|
|
884
|
-
|
|
885
|
-
|
|
886
790
|
async def run(
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
791
|
+
self,
|
|
792
|
+
debug: bool = False,
|
|
793
|
+
sleep_time: float = 0.2,
|
|
794
|
+
webhook_timeout: int = 20,
|
|
795
|
+
update_limit: int = 100,
|
|
796
|
+
retry_delay: float = 5.0,
|
|
797
|
+
stop_on_error: bool = False,
|
|
798
|
+
max_errors: int = 0,
|
|
799
|
+
auto_restart: bool = False,
|
|
800
|
+
max_runtime: Optional[float] = None,
|
|
801
|
+
loop_forever: bool = True,
|
|
802
|
+
allowed_update_types: Optional[List[str]] = None,
|
|
803
|
+
ignore_duplicate_messages: bool = True,
|
|
804
|
+
skip_inline_queries: bool = False,
|
|
805
|
+
skip_channel_posts: bool = False,
|
|
806
|
+
skip_service_messages: bool = False,
|
|
807
|
+
skip_edited_messages: bool = False,
|
|
808
|
+
skip_bot_messages: bool = False,
|
|
809
|
+
log_file: Optional[str] = None,
|
|
810
|
+
log_level: str = "info",
|
|
811
|
+
print_exceptions: bool = True,
|
|
812
|
+
error_handler: Optional[Callable[[Exception], Any]] = None,
|
|
813
|
+
shutdown_hook: Optional[Callable[[], Any]] = None,
|
|
814
|
+
save_unprocessed_updates: bool = False,
|
|
815
|
+
log_to_console: bool = True,
|
|
816
|
+
rate_limit: Optional[float] = None,
|
|
817
|
+
max_message_size: Optional[int] = None,
|
|
818
|
+
ignore_users: Optional[Set[str]] = None,
|
|
819
|
+
ignore_groups: Optional[Set[str]] = None,
|
|
820
|
+
require_auth_token: bool = False,
|
|
821
|
+
only_private_chats: bool = False,
|
|
822
|
+
only_groups: bool = False,
|
|
823
|
+
require_admin_rights: bool = False,
|
|
824
|
+
custom_update_fetcher: Optional[Callable[[], Any]] = None,
|
|
825
|
+
custom_update_processor: Optional[Callable[[Any], Any]] = None,
|
|
826
|
+
process_in_background: bool = False,
|
|
827
|
+
max_queue_size: int = 1000,
|
|
828
|
+
thread_workers: int = 3,
|
|
829
|
+
message_filter: Optional[Callable[[Any], bool]] = None,
|
|
830
|
+
pause_on_idle: bool = False,
|
|
831
|
+
max_concurrent_tasks: Optional[int] = None,
|
|
832
|
+
metrics_enabled: bool = False,
|
|
833
|
+
metrics_handler: Optional[Callable[[dict], Any]] = None,
|
|
834
|
+
notify_on_error: bool = False,
|
|
835
|
+
notification_handler: Optional[Callable[[str], Any]] = None,
|
|
836
|
+
watchdog_timeout: Optional[float] = None,
|
|
933
837
|
):
|
|
934
838
|
"""
|
|
935
839
|
Starts the bot's main execution loop with extensive configuration options.
|
|
@@ -1449,8 +1353,7 @@ class Robot:
|
|
|
1449
1353
|
|
|
1450
1354
|
|
|
1451
1355
|
_log("Auto-restart requested. You can call run(...) again as needed.", "warning")
|
|
1452
|
-
|
|
1453
|
-
|
|
1356
|
+
|
|
1454
1357
|
async def send_message(
|
|
1455
1358
|
self,
|
|
1456
1359
|
chat_id: str,
|
|
@@ -1460,27 +1363,21 @@ class Robot:
|
|
|
1460
1363
|
disable_notification: bool = False,
|
|
1461
1364
|
reply_to_message_id: Optional[str] = None,
|
|
1462
1365
|
chat_keypad_type: Optional[Literal["New", "Removed"]] = None
|
|
1463
|
-
) -> Dict[str, Any]:
|
|
1366
|
+
) -> Dict[str, Any]:
|
|
1464
1367
|
payload = {
|
|
1465
1368
|
"chat_id": chat_id,
|
|
1466
1369
|
"text": text,
|
|
1467
1370
|
"disable_notification": disable_notification,
|
|
1468
1371
|
}
|
|
1469
|
-
|
|
1470
1372
|
if chat_keypad:
|
|
1471
1373
|
payload["chat_keypad"] = chat_keypad
|
|
1472
1374
|
payload["chat_keypad_type"] = chat_keypad_type or "New"
|
|
1473
|
-
|
|
1474
1375
|
if inline_keypad:
|
|
1475
1376
|
payload["inline_keypad"] = inline_keypad
|
|
1476
|
-
|
|
1477
1377
|
if reply_to_message_id:
|
|
1478
1378
|
payload["reply_to_message_id"] = reply_to_message_id
|
|
1479
|
-
|
|
1480
1379
|
return await self._post("sendMessage", payload)
|
|
1481
1380
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
1381
|
async def get_url_file(self,file_id):
|
|
1485
1382
|
data = await self._post("getFile", {'file_id': file_id})
|
|
1486
1383
|
return data.get("data").get("download_url")
|
|
@@ -1493,14 +1390,11 @@ class Robot:
|
|
|
1493
1390
|
|
|
1494
1391
|
async def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
|
|
1495
1392
|
client = self._get_client()
|
|
1496
|
-
|
|
1497
1393
|
if chat_id:
|
|
1498
1394
|
chat_info_data = await self.get_chat(chat_id)
|
|
1499
1395
|
chat_info = chat_info_data.get('data', {}).get('chat', {})
|
|
1500
1396
|
username = chat_info.get('username')
|
|
1501
1397
|
user_id = chat_info.get('user_id')
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
1398
|
if username:
|
|
1505
1399
|
result = await asyncio.to_thread(self.get_all_member, channel_guid, search_text=username)
|
|
1506
1400
|
members = result.get('in_chat_members', [])
|
|
@@ -1519,7 +1413,6 @@ class Robot:
|
|
|
1519
1413
|
id="None"):
|
|
1520
1414
|
from .button import InlineBuilder
|
|
1521
1415
|
builder = InlineBuilder()
|
|
1522
|
-
|
|
1523
1416
|
if isinstance(username, (list, tuple)) and isinstance(title_button, (list, tuple)):
|
|
1524
1417
|
for t, u in zip(title_button, username):
|
|
1525
1418
|
builder = builder.row(
|
|
@@ -1589,55 +1482,41 @@ class Robot:
|
|
|
1589
1482
|
url=url
|
|
1590
1483
|
)
|
|
1591
1484
|
)
|
|
1592
|
-
|
|
1593
1485
|
return await self.send_message(
|
|
1594
1486
|
chat_id=chat_id,
|
|
1595
1487
|
text=text,
|
|
1596
1488
|
inline_keypad=builder.build(),
|
|
1597
1489
|
reply_to_message_id=reply_to_message_id
|
|
1598
1490
|
)
|
|
1599
|
-
|
|
1600
1491
|
def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
|
|
1601
|
-
|
|
1602
1492
|
client = self._get_client()
|
|
1603
1493
|
return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
|
|
1604
|
-
|
|
1605
1494
|
async def send_poll(self, chat_id: str, question: str, options: List[str]) -> Dict[str, Any]:
|
|
1606
1495
|
return await self._post("sendPoll", {"chat_id": chat_id, "question": question, "options": options})
|
|
1607
|
-
|
|
1608
1496
|
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]:
|
|
1609
1497
|
payload = {"chat_id": chat_id, "latitude": latitude, "longitude": longitude, "disable_notification": disable_notification}
|
|
1610
1498
|
if inline_keypad: payload["inline_keypad"] = inline_keypad
|
|
1611
1499
|
if reply_to_message_id: payload["reply_to_message_id"] = reply_to_message_id
|
|
1612
1500
|
if chat_keypad_type: payload["chat_keypad_type"] = chat_keypad_type
|
|
1613
1501
|
return await self._post("sendLocation", {k: v for k, v in payload.items() if v is not None})
|
|
1614
|
-
|
|
1615
1502
|
async def send_contact(self, chat_id: str, first_name: str, last_name: str, phone_number: str) -> Dict[str, Any]:
|
|
1616
1503
|
return await self._post("sendContact", {"chat_id": chat_id, "first_name": first_name, "last_name": last_name, "phone_number": phone_number})
|
|
1617
|
-
|
|
1618
1504
|
async def get_chat(self, chat_id: str) -> Dict[str, Any]:
|
|
1619
1505
|
return await self._post("getChat", {"chat_id": chat_id})
|
|
1620
|
-
|
|
1621
1506
|
async def upload_media_file(self, upload_url: str, name: str, path: Union[str, Path]) -> str:
|
|
1622
1507
|
is_temp_file = False
|
|
1623
1508
|
session = await self._get_session()
|
|
1624
|
-
|
|
1625
1509
|
if isinstance(path, str) and path.startswith("http"):
|
|
1626
1510
|
async with session.get(path) as response:
|
|
1627
1511
|
if response.status != 200:
|
|
1628
1512
|
raise Exception(f"Failed to download file from URL ({response.status})")
|
|
1629
|
-
|
|
1630
1513
|
content = await response.read()
|
|
1631
|
-
|
|
1632
1514
|
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
|
1633
1515
|
temp_file.write(content)
|
|
1634
1516
|
path = temp_file.name
|
|
1635
1517
|
is_temp_file = True
|
|
1636
|
-
|
|
1637
1518
|
file_size = os.path.getsize(path)
|
|
1638
|
-
|
|
1639
1519
|
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)
|
|
1640
|
-
|
|
1641
1520
|
async def file_progress_generator(file_path, chunk_size=8192):
|
|
1642
1521
|
async with aiofiles.open(file_path, 'rb') as f:
|
|
1643
1522
|
while True:
|
|
@@ -1646,7 +1525,6 @@ class Robot:
|
|
|
1646
1525
|
break
|
|
1647
1526
|
progress_bar.update(len(chunk))
|
|
1648
1527
|
yield chunk
|
|
1649
|
-
|
|
1650
1528
|
data = aiohttp.FormData()
|
|
1651
1529
|
data.add_field('file', file_progress_generator(path), filename=name, content_type='application/octet-stream')
|
|
1652
1530
|
try:
|
|
@@ -1661,12 +1539,9 @@ class Robot:
|
|
|
1661
1539
|
return json_data.get('data', {}).get('file_id')
|
|
1662
1540
|
except :
|
|
1663
1541
|
raise FeatureNotAvailableError(f"files is not currently supported by the server.")
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
1542
|
def get_extension(content_type: str) -> str:
|
|
1667
1543
|
ext = mimetypes.guess_extension(content_type)
|
|
1668
1544
|
return ext if ext else ''
|
|
1669
|
-
|
|
1670
1545
|
async def download(self, file_id: str, save_as: str = None, chunk_size: int = 1024 * 512,timeout_sec: int = 60, verbose: bool = False):
|
|
1671
1546
|
"""
|
|
1672
1547
|
Download a file from server using its file_id with chunked transfer,
|
|
@@ -1685,16 +1560,11 @@ class Robot:
|
|
|
1685
1560
|
Returns:
|
|
1686
1561
|
bool: True if success, raises exceptions otherwise.
|
|
1687
1562
|
"""
|
|
1688
|
-
|
|
1689
1563
|
try:
|
|
1690
1564
|
url = await self.get_url_file(file_id)
|
|
1691
|
-
if not url:
|
|
1692
|
-
|
|
1693
|
-
except Exception as e:
|
|
1694
|
-
raise ValueError(f"Failed to get download URL: {e}")
|
|
1695
|
-
|
|
1565
|
+
if not url:raise ValueError("Download URL not found in response.")
|
|
1566
|
+
except Exception as e:raise ValueError(f"Failed to get download URL: {e}")
|
|
1696
1567
|
timeout = aiohttp.ClientTimeout(total=timeout_sec)
|
|
1697
|
-
|
|
1698
1568
|
try:
|
|
1699
1569
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
1700
1570
|
async with session.get(url) as resp:
|
|
@@ -1706,21 +1576,17 @@ class Robot:
|
|
|
1706
1576
|
message="Failed to download file.",
|
|
1707
1577
|
headers=resp.headers
|
|
1708
1578
|
)
|
|
1709
|
-
|
|
1710
1579
|
if not save_as:
|
|
1711
1580
|
content_disp = resp.headers.get("Content-Disposition", "")
|
|
1712
1581
|
import re
|
|
1713
1582
|
match = re.search(r'filename="?([^\";]+)"?', content_disp)
|
|
1714
|
-
if match:
|
|
1715
|
-
save_as = match.group(1)
|
|
1583
|
+
if match:save_as = match.group(1)
|
|
1716
1584
|
else:
|
|
1717
1585
|
content_type = resp.headers.get("Content-Type", "").split(";")[0]
|
|
1718
1586
|
extension = mimetypes.guess_extension(content_type) or ".bin"
|
|
1719
1587
|
save_as = f"{file_id}{extension}"
|
|
1720
|
-
|
|
1721
1588
|
total_size = int(resp.headers.get("Content-Length", 0))
|
|
1722
1589
|
progress = tqdm(total=total_size, unit="B", unit_scale=True, disable=not verbose)
|
|
1723
|
-
|
|
1724
1590
|
async with aiofiles.open(save_as, "wb") as f:
|
|
1725
1591
|
async for chunk in resp.content.iter_chunked(chunk_size):
|
|
1726
1592
|
await f.write(chunk)
|
|
@@ -1783,7 +1649,7 @@ class Robot:
|
|
|
1783
1649
|
"inline_keypad":inline_keypad,
|
|
1784
1650
|
"chat_keypad_type":chat_keypad_type
|
|
1785
1651
|
}
|
|
1786
|
-
return result
|
|
1652
|
+
return AttrDict(result)
|
|
1787
1653
|
|
|
1788
1654
|
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):
|
|
1789
1655
|
if path:
|
|
@@ -1802,43 +1668,31 @@ class Robot:
|
|
|
1802
1668
|
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)
|
|
1803
1669
|
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]:
|
|
1804
1670
|
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)
|
|
1805
|
-
|
|
1806
1671
|
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]:
|
|
1807
1672
|
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)
|
|
1808
|
-
|
|
1809
1673
|
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]:
|
|
1810
1674
|
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)
|
|
1811
|
-
|
|
1812
1675
|
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]:
|
|
1813
1676
|
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)
|
|
1814
|
-
|
|
1815
1677
|
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]:
|
|
1816
1678
|
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)
|
|
1817
|
-
|
|
1818
1679
|
async def forward_message(self, from_chat_id: str, message_id: str, to_chat_id: str, disable_notification: bool = False) -> Dict[str, Any]:
|
|
1819
1680
|
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})
|
|
1820
|
-
|
|
1821
1681
|
async def edit_message_text(self, chat_id: str, message_id: str, text: str) -> Dict[str, Any]:
|
|
1822
1682
|
return await self._post("editMessageText", {"chat_id": chat_id, "message_id": message_id, "text": text})
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
return await self._post("editMessageKeypad", {"chat_id": chat_id,
|
|
1826
|
-
|
|
1683
|
+
async def edit_inline_keypad(self,chat_id: str,message_id: str,inline_keypad: Dict[str, Any],text: str = None) -> Dict[str, Any]:
|
|
1684
|
+
if text is not None:await self._post("editMessageText", {"chat_id": chat_id,"message_id": message_id,"text": text})
|
|
1685
|
+
return await self._post("editMessageKeypad", {"chat_id": chat_id,"message_id": message_id,"inline_keypad": inline_keypad})
|
|
1827
1686
|
async def delete_message(self, chat_id: str, message_id: str) -> Dict[str, Any]:
|
|
1828
1687
|
return await self._post("deleteMessage", {"chat_id": chat_id, "message_id": message_id})
|
|
1829
|
-
|
|
1830
1688
|
async def set_commands(self, bot_commands: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
1831
1689
|
return await self._post("setCommands", {"bot_commands": bot_commands})
|
|
1832
|
-
|
|
1833
1690
|
async def update_bot_endpoint(self, url: str, type: str) -> Dict[str, Any]:
|
|
1834
1691
|
return await self._post("updateBotEndpoints", {"url": url, "type": type})
|
|
1835
|
-
|
|
1836
1692
|
async def remove_keypad(self, chat_id: str) -> Dict[str, Any]:
|
|
1837
1693
|
return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "Removed"})
|
|
1838
|
-
|
|
1839
1694
|
async def edit_chat_keypad(self, chat_id: str, chat_keypad: Dict[str, Any]) -> Dict[str, Any]:
|
|
1840
1695
|
return await self._post("editChatKeypad", {"chat_id": chat_id, "chat_keypad_type": "New", "chat_keypad": chat_keypad})
|
|
1841
|
-
|
|
1842
1696
|
async def get_name(self, chat_id: str) -> str:
|
|
1843
1697
|
try:
|
|
1844
1698
|
chat = await self.get_chat(chat_id)
|
|
@@ -1854,9 +1708,6 @@ class Robot:
|
|
|
1854
1708
|
return title if title else "null"
|
|
1855
1709
|
else:return "null"
|
|
1856
1710
|
except Exception:return "null"
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
1711
|
async def get_username(self, chat_id: str) -> str:
|
|
1861
1712
|
chat_info = await self.get_chat(chat_id)
|
|
1862
1713
|
return chat_info.get("data", {}).get("chat", {}).get("username", "None")
|
rubka/filters.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
import re
|
|
3
|
-
|
|
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", ""))
|
|
4
9
|
class Filter:
|
|
5
10
|
def __init__(self, func: Callable):
|
|
6
11
|
self.func = func
|
|
@@ -49,8 +54,32 @@ class Filter:
|
|
|
49
54
|
|
|
50
55
|
def __truediv__(self, other):
|
|
51
56
|
return Filter(lambda m: self(m) / (other(m) if callable(other) else other))
|
|
52
|
-
|
|
53
|
-
|
|
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))
|
|
54
83
|
is_file = Filter(lambda m: getattr(m, "file", None) is not None)
|
|
55
84
|
is_sticker = Filter(lambda m: getattr(m, "sticker", None) is not None)
|
|
56
85
|
is_contact = Filter(lambda m: getattr(m, "contact_message", None) is not None)
|
|
@@ -59,7 +88,8 @@ is_location = Filter(lambda m: getattr(m, "location", None) is not None)
|
|
|
59
88
|
is_live_location = Filter(lambda m: getattr(m, "live_location", None) is not None)
|
|
60
89
|
has_any_media = Filter(lambda m: getattr(m, "has_any_media", False))
|
|
61
90
|
has_media = Filter(lambda m: getattr(m, "has_media", False))
|
|
62
|
-
|
|
91
|
+
is_text = IsText()
|
|
92
|
+
is_command = IsCommand()
|
|
63
93
|
is_user = Filter(lambda m: getattr(m, "is_user", False))
|
|
64
94
|
is_private = Filter(lambda m: getattr(m, "is_private", False))
|
|
65
95
|
is_group = Filter(lambda m: getattr(m, "is_group", False))
|
|
@@ -67,13 +97,8 @@ is_channel = Filter(lambda m: getattr(m, "is_channel", False))
|
|
|
67
97
|
is_reply = Filter(lambda m: getattr(m, "is_reply", False))
|
|
68
98
|
is_forwarded = Filter(lambda m: getattr(m, "is_forwarded", False))
|
|
69
99
|
is_edited = Filter(lambda m: getattr(m, "is_edited", False))
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
100
|
def text(keyword: str):
|
|
75
101
|
return Filter(lambda m: getattr(m, "text", "") and keyword in m.text)
|
|
76
|
-
|
|
77
102
|
def text_length(min_len: int = 0, max_len: int = None):
|
|
78
103
|
def _filter(m):
|
|
79
104
|
t = getattr(m, "text", "")
|
|
@@ -82,26 +107,19 @@ def text_length(min_len: int = 0, max_len: int = None):
|
|
|
82
107
|
if max_len is not None and len(t) > max_len: return False
|
|
83
108
|
return True
|
|
84
109
|
return Filter(_filter)
|
|
85
|
-
|
|
86
110
|
def text_regex(pattern: str):
|
|
87
111
|
regex = re.compile(pattern)
|
|
88
112
|
return Filter(lambda m: getattr(m, "text", "") and regex.search(m.text))
|
|
89
|
-
|
|
90
113
|
def text_startswith(prefix: str):
|
|
91
114
|
return Filter(lambda m: getattr(m, "text", "").startswith(prefix) if getattr(m, "text", None) else False)
|
|
92
|
-
|
|
93
115
|
def text_endswith(suffix: str):
|
|
94
116
|
return Filter(lambda m: getattr(m, "text", "").endswith(suffix) if getattr(m, "text", None) else False)
|
|
95
|
-
|
|
96
117
|
def text_upper():
|
|
97
118
|
return Filter(lambda m: getattr(m, "text", "").isupper() if getattr(m, "text", None) else False)
|
|
98
|
-
|
|
99
119
|
def text_lower():
|
|
100
120
|
return Filter(lambda m: getattr(m, "text", "").islower() if getattr(m, "text", None) else False)
|
|
101
|
-
|
|
102
121
|
def text_digit():
|
|
103
122
|
return Filter(lambda m: getattr(m, "text", "").isdigit() if getattr(m, "text", None) else False)
|
|
104
|
-
|
|
105
123
|
def text_word_count(min_words: int = 1, max_words: int = None):
|
|
106
124
|
def _filter(m):
|
|
107
125
|
t = getattr(m, "text", "")
|
|
@@ -111,49 +129,28 @@ def text_word_count(min_words: int = 1, max_words: int = None):
|
|
|
111
129
|
if max_words is not None and wc > max_words: return False
|
|
112
130
|
return True
|
|
113
131
|
return Filter(_filter)
|
|
114
|
-
|
|
115
132
|
def text_contains_any(keywords: list):
|
|
116
133
|
return Filter(lambda m: getattr(m, "text", "") and any(k in m.text for k in keywords))
|
|
117
|
-
|
|
118
134
|
def text_equals(value: str):
|
|
119
135
|
return Filter(lambda m: getattr(m, "text", None) == value)
|
|
120
|
-
|
|
121
136
|
def text_not_equals(value: str):
|
|
122
137
|
return Filter(lambda m: getattr(m, "text", None) != value)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
138
|
def file_size_gt(size: int):
|
|
128
139
|
return Filter(lambda m: m.file and getattr(m.file, "size", 0) > size)
|
|
129
|
-
|
|
130
140
|
def file_size_lt(size: int):
|
|
131
141
|
return Filter(lambda m: m.file and getattr(m.file, "size", 0) < size)
|
|
132
|
-
|
|
133
142
|
def file_name_contains(substring: str):
|
|
134
143
|
return Filter(lambda m: m.file and substring in getattr(m.file, "file_name", ""))
|
|
135
|
-
|
|
136
144
|
def file_extension(ext: str):
|
|
137
145
|
return Filter(lambda m: m.file and getattr(m.file, "file_name", "").endswith(ext))
|
|
138
|
-
|
|
139
146
|
def file_id_is(file_id: str):
|
|
140
147
|
return Filter(lambda m: m.file and getattr(m.file, "file_id", None) == file_id)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
148
|
def sticker_id_is(sid: str):
|
|
146
149
|
return Filter(lambda m: m.sticker and getattr(m.sticker, "sticker_id", None) == sid)
|
|
147
|
-
|
|
148
150
|
def sticker_emoji_is(emoji: str):
|
|
149
151
|
return Filter(lambda m: m.sticker and getattr(m.sticker, "emoji", None) == emoji)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
152
|
def poll_question_contains(keyword: str):
|
|
155
153
|
return Filter(lambda m: m.poll and keyword in getattr(m.poll, "question", ""))
|
|
156
|
-
|
|
157
154
|
def poll_option_count(min_options: int = 1, max_options: int = None):
|
|
158
155
|
def _filter(m):
|
|
159
156
|
if not getattr(m, "poll", None): return False
|
|
@@ -162,27 +159,18 @@ def poll_option_count(min_options: int = 1, max_options: int = None):
|
|
|
162
159
|
if max_options is not None and len(options) > max_options: return False
|
|
163
160
|
return True
|
|
164
161
|
return Filter(_filter)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
162
|
def location_within(lat_min, lat_max, long_min, long_max):
|
|
170
163
|
def _filter(m):
|
|
171
164
|
loc = getattr(m, "location", None)
|
|
172
165
|
if not loc: return False
|
|
173
166
|
return lat_min <= getattr(loc, "lat", 0) <= lat_max and long_min <= getattr(loc, "long", 0) <= long_max
|
|
174
167
|
return Filter(_filter)
|
|
175
|
-
|
|
176
168
|
def live_location_within(lat_min, lat_max, long_min, long_max):
|
|
177
169
|
def _filter(m):
|
|
178
170
|
loc = getattr(m, "live_location", None)
|
|
179
171
|
if not loc: return False
|
|
180
172
|
return lat_min <= getattr(loc, "lat", 0) <= lat_max and long_min <= getattr(loc, "long", 0) <= long_max
|
|
181
173
|
return Filter(_filter)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
174
|
def has_media_types(types: list):
|
|
187
175
|
return Filter(lambda m: any(getattr(m, t, None) for t in types))
|
|
188
176
|
|
rubka/rubino.py
CHANGED
|
@@ -446,7 +446,7 @@ class Bot():
|
|
|
446
446
|
"api_version":"0",
|
|
447
447
|
"client":{
|
|
448
448
|
"app_name":"Main",
|
|
449
|
-
"app_version":"2.
|
|
449
|
+
"app_version":"2.4.7",
|
|
450
450
|
"package":"m.rubika.ir",
|
|
451
451
|
"platform":"PWA"
|
|
452
452
|
},
|
|
@@ -926,6 +926,20 @@ class Bot():
|
|
|
926
926
|
"profile_id": profile_id
|
|
927
927
|
},methode="getProfileFollowers")
|
|
928
928
|
|
|
929
|
+
def get_Highlight_StoryIds(self,highlight_id:str,profile_id:str,target_profile_id:str):
|
|
930
|
+
return self._reuests_post(data={
|
|
931
|
+
"highlight_id": highlight_id,
|
|
932
|
+
"profile_id": profile_id,
|
|
933
|
+
"target_profile_id": target_profile_id
|
|
934
|
+
},methode="getHighlightStoryIds")
|
|
935
|
+
def get_Highlight_Stories(self,highlight_id:str,profile_id:str,target_profile_id:str,story_ids:list):
|
|
936
|
+
return self._reuests_post(data={
|
|
937
|
+
"highlight_id": highlight_id,
|
|
938
|
+
"profile_id": profile_id,
|
|
939
|
+
"target_profile_id": target_profile_id,
|
|
940
|
+
"story_ids":story_ids
|
|
941
|
+
},methode="getHighlightStories")
|
|
942
|
+
|
|
929
943
|
def get_NewFollow_Requests(self,sort:str="FromMax",limit:int=50,equal:bool=False,profile_id:str=None):
|
|
930
944
|
return self._reuests_post(data={
|
|
931
945
|
"equal": equal,
|
|
@@ -955,14 +969,37 @@ class Bot():
|
|
|
955
969
|
"track_id": "Related",
|
|
956
970
|
"profile_id": profile_id
|
|
957
971
|
},methode="postBookmarkAction")
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
972
|
+
def get_saved_posts(
|
|
973
|
+
self,
|
|
974
|
+
max_id: str,
|
|
975
|
+
limit: int = 20,
|
|
976
|
+
profile_id: Optional[str] = None,
|
|
977
|
+
sort: str = "FromMax") -> Dict[str, Any]:
|
|
978
|
+
"""
|
|
979
|
+
دریافت پستهای ذخیرهشده (بوکمارکشده)
|
|
980
|
+
|
|
981
|
+
Args:
|
|
982
|
+
max_id (str): شناسه آخرین پست برای صفحهبندی.
|
|
983
|
+
limit (int, optional): تعداد پستها در هر درخواست. پیشفرض 20.
|
|
984
|
+
profile_id (str, optional): شناسه پروفایل (در صورت نیاز).
|
|
985
|
+
sort (str, optional): نوع مرتبسازی. پیشفرض "FromMax".
|
|
986
|
+
|
|
987
|
+
Returns:
|
|
988
|
+
Dict[str, Any]: پاسخ سرور شامل لیست پستهای ذخیرهشده.
|
|
989
|
+
"""
|
|
990
|
+
payload = {
|
|
962
991
|
"limit": limit,
|
|
963
|
-
"
|
|
964
|
-
"
|
|
965
|
-
}
|
|
992
|
+
"max_id": max_id,
|
|
993
|
+
"sort": sort
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if profile_id is not None:
|
|
997
|
+
payload["profile_id"] = profile_id
|
|
998
|
+
|
|
999
|
+
return self._reuests_post(
|
|
1000
|
+
data=payload,
|
|
1001
|
+
methode="getBookmarkedPosts"
|
|
1002
|
+
)
|
|
966
1003
|
|
|
967
1004
|
def search_Page(self,username:str,sort:str="FromMax",limit:int=50,equal:bool=False,profile_id:str=None):
|
|
968
1005
|
return self._reuests_post(data={
|
|
@@ -994,6 +1031,14 @@ class Bot():
|
|
|
994
1031
|
"record_id": post_id,
|
|
995
1032
|
"profile_id": profile_id
|
|
996
1033
|
},methode="setReportRecord")
|
|
1034
|
+
def report_Post(self,post_profile_id,post_id:str,reason:int=2,profile_id:str=None):
|
|
1035
|
+
return self._reuests_post(data={
|
|
1036
|
+
"model": "Post",
|
|
1037
|
+
"reason": reason,
|
|
1038
|
+
"post_profile_id":post_profile_id,
|
|
1039
|
+
"record_id": post_id,
|
|
1040
|
+
"profile_id": profile_id
|
|
1041
|
+
},methode="setReportRecord")
|
|
997
1042
|
|
|
998
1043
|
def delete_Post(self,post_id:str,profile_id:str=None):
|
|
999
1044
|
return self._reuests_post(data={
|
|
@@ -1013,6 +1058,12 @@ class Bot():
|
|
|
1013
1058
|
"profile_status": profile_status,
|
|
1014
1059
|
"profile_id": profile_id
|
|
1015
1060
|
},methode="updateProfile")
|
|
1061
|
+
def get_New_Events(self,limit:int=20,sort:str="FromMax",profile_id:str=None):
|
|
1062
|
+
return self._reuests_post(data={
|
|
1063
|
+
"limit": limit,
|
|
1064
|
+
"profile_id": profile_id,
|
|
1065
|
+
"sort":sort
|
|
1066
|
+
},methode="updateProfile")
|
|
1016
1067
|
|
|
1017
1068
|
def allow_Send_MessagePv(self,is_message_allowed:bool=False,profile_id:str=None):
|
|
1018
1069
|
return self._reuests_post(data={
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 6.
|
|
4
|
-
Summary: A Python library for interacting with Rubika Bot API.
|
|
3
|
+
Version: 6.6.4
|
|
4
|
+
Summary: rubika A Python library for interacting with Rubika Bot API.
|
|
5
5
|
Home-page: https://github.com/Mahdy-Ahmadi/Rubka
|
|
6
|
-
Download-URL: https://github.com/Mahdy-Ahmadi/rubka/
|
|
6
|
+
Download-URL: https://github.com/Mahdy-Ahmadi/rubka/archive/refs/tags/v6.6.4.zip
|
|
7
7
|
Author: Mahdi Ahmadi
|
|
8
8
|
Author-email: mahdiahmadi.1208@gmail.com
|
|
9
9
|
Maintainer: Mahdi Ahmadi
|
|
10
10
|
Maintainer-email: mahdiahmadi.1208@gmail.com
|
|
11
|
+
License: MIT
|
|
12
|
+
Project-URL: Bug Tracker, https://t.me/Bprogrammer
|
|
13
|
+
Project-URL: Documentation, https://github.com/Mahdy-Ahmadi/rubka/blob/main/README.md
|
|
14
|
+
Project-URL: Source Code, https://github.com/Mahdy-Ahmadi/Rubka
|
|
15
|
+
Keywords: rubika bot api library chat messaging rubpy pyrubi rubigram
|
|
16
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
11
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
22
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
23
|
Classifier: Operating System :: OS Independent
|
|
14
24
|
Classifier: Topic :: Communications :: Chat
|
|
15
25
|
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Classifier: Natural Language :: Persian
|
|
16
27
|
Requires-Python: >=3.6
|
|
17
28
|
Description-Content-Type: text/markdown
|
|
18
29
|
Requires-Dist: requests
|
|
@@ -31,8 +42,11 @@ Dynamic: description
|
|
|
31
42
|
Dynamic: description-content-type
|
|
32
43
|
Dynamic: download-url
|
|
33
44
|
Dynamic: home-page
|
|
45
|
+
Dynamic: keywords
|
|
46
|
+
Dynamic: license
|
|
34
47
|
Dynamic: maintainer
|
|
35
48
|
Dynamic: maintainer-email
|
|
49
|
+
Dynamic: project-url
|
|
36
50
|
Dynamic: requires-dist
|
|
37
51
|
Dynamic: requires-python
|
|
38
52
|
Dynamic: summary
|
|
@@ -62,16 +76,16 @@ If `importlib.metadata` is not available, it installs `importlib-metadata` autom
|
|
|
62
76
|
## 🚀 Getting Started
|
|
63
77
|
|
|
64
78
|
```python
|
|
65
|
-
from rubka import Robot
|
|
79
|
+
from rubka.asynco import Robot
|
|
66
80
|
from rubka.context import Message
|
|
67
|
-
|
|
81
|
+
import asyncio
|
|
68
82
|
bot = Robot(token="YOUR_TOKEN_HERE")
|
|
69
83
|
|
|
70
84
|
@bot.on_message(commands=["start"])
|
|
71
|
-
def start(bot: Robot, message: Message):
|
|
72
|
-
message.reply("سلام! خوش آمدید!")
|
|
85
|
+
async def start(bot: Robot, message: Message):
|
|
86
|
+
await message.reply("سلام! خوش آمدید!")
|
|
73
87
|
|
|
74
|
-
bot.run()
|
|
88
|
+
asyncio.run(bot.run())
|
|
75
89
|
```
|
|
76
90
|
|
|
77
91
|
---
|
|
@@ -82,8 +96,8 @@ You can handle incoming text messages using `@bot.on_message()`:
|
|
|
82
96
|
|
|
83
97
|
```python
|
|
84
98
|
@bot.on_message(commands=["hello"])
|
|
85
|
-
def greet(bot: Robot, message: Message):
|
|
86
|
-
|
|
99
|
+
async def greet(bot: Robot, message: Message):
|
|
100
|
+
await message.reply("سلام کاربر عزیز 👋")
|
|
87
101
|
```
|
|
88
102
|
|
|
89
103
|
You can also add filters.
|
|
@@ -1,17 +1,17 @@
|
|
|
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=V0PW63HpBZJ1B39aKRPKU0gHDqriH7u9Z-6LFiFaWQg,83562
|
|
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=
|
|
9
|
+
rubka/filters.py,sha256=DY1bdkpRKIiLtVcy6X3hOnlGPcVOK4HFb3QgmaPx6Oo,12116
|
|
10
10
|
rubka/jobs.py,sha256=GvLMLsVhcSEzRTgkvnPISPEBN71suW2xXI0hUaUZPTo,378
|
|
11
11
|
rubka/keyboards.py,sha256=7nr-dT2bQJVQnQ6RMWPTSjML6EEk6dsBx-4d8pab8xk,488
|
|
12
12
|
rubka/keypad.py,sha256=yGsNt8W5HtUFBzVF1m_p7GySlu1hwIcSvXZ4BTdrlvg,9558
|
|
13
13
|
rubka/logger.py,sha256=J2I6NiK1z32lrAzC4H1Et6WPMBXxXGCVUsW4jgcAofs,289
|
|
14
|
-
rubka/rubino.py,sha256=
|
|
14
|
+
rubka/rubino.py,sha256=GuXnEUTTSZUw68FMTQBYsB2zG_3rzdDa-krsrl4ib8w,58755
|
|
15
15
|
rubka/update.py,sha256=4YZs7DiZD_HWOqY76hwwajG0J-bLy6wjeKtQT3EatZU,19341
|
|
16
16
|
rubka/utils.py,sha256=XUQUZxQt9J2f0X5hmAH_MH1kibTAfdT1T4AaBkBhBBs,148
|
|
17
17
|
rubka/adaptorrubka/__init__.py,sha256=6o2tCXnVeES7nx-LjnzsuMqjKcWIm9qwKficLE54s-U,83
|
|
@@ -35,7 +35,8 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
|
|
|
35
35
|
rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
|
|
36
36
|
rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
|
|
37
37
|
rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
|
|
38
|
-
rubka-6.
|
|
39
|
-
rubka-6.
|
|
40
|
-
rubka-6.
|
|
41
|
-
rubka-6.
|
|
38
|
+
rubka-6.6.4.dist-info/METADATA,sha256=FH4BjESYZfSWjk76Us_1LFKez1zxqv72MRv6-cINDCE,34043
|
|
39
|
+
rubka-6.6.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
rubka-6.6.4.dist-info/entry_points.txt,sha256=4aESuUmuUOALMUy7Kucv_Gb5YlqhsJmTmdXLlZU9sJ0,46
|
|
41
|
+
rubka-6.6.4.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
|
|
42
|
+
rubka-6.6.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|