Rubka 6.4.6__tar.gz → 6.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {rubka-6.4.6 → rubka-6.5.2}/PKG-INFO +1 -1
  2. {rubka-6.4.6 → rubka-6.5.2}/Rubka.egg-info/PKG-INFO +1 -1
  3. {rubka-6.4.6 → rubka-6.5.2}/Rubka.egg-info/SOURCES.txt +1 -0
  4. {rubka-6.4.6 → rubka-6.5.2}/rubka/api.py +180 -3
  5. {rubka-6.4.6 → rubka-6.5.2}/rubka/asynco.py +169 -22
  6. {rubka-6.4.6 → rubka-6.5.2}/rubka/context.py +16 -2
  7. rubka-6.5.2/rubka/filters.py +317 -0
  8. {rubka-6.4.6 → rubka-6.5.2}/rubka/update.py +16 -2
  9. {rubka-6.4.6 → rubka-6.5.2}/setup.py +1 -1
  10. {rubka-6.4.6 → rubka-6.5.2}/README.md +0 -0
  11. {rubka-6.4.6 → rubka-6.5.2}/Rubka.egg-info/dependency_links.txt +0 -0
  12. {rubka-6.4.6 → rubka-6.5.2}/Rubka.egg-info/requires.txt +0 -0
  13. {rubka-6.4.6 → rubka-6.5.2}/Rubka.egg-info/top_level.txt +0 -0
  14. {rubka-6.4.6 → rubka-6.5.2}/rubka/__init__.py +0 -0
  15. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/__init__.py +0 -0
  16. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/client/__init__.py +0 -0
  17. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/client/client.py +0 -0
  18. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/crypto/__init__.py +0 -0
  19. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/crypto/crypto.py +0 -0
  20. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/enums.py +0 -0
  21. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/exceptions.py +0 -0
  22. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/methods/__init__.py +0 -0
  23. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/methods/methods.py +0 -0
  24. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/network/__init__.py +0 -0
  25. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/network/helper.py +0 -0
  26. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/network/network.py +0 -0
  27. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/network/socket.py +0 -0
  28. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/sessions/__init__.py +0 -0
  29. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/sessions/sessions.py +0 -0
  30. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/types/__init__.py +0 -0
  31. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/types/socket/__init__.py +0 -0
  32. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/types/socket/message.py +0 -0
  33. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/utils/__init__.py +0 -0
  34. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/utils/configs.py +0 -0
  35. {rubka-6.4.6 → rubka-6.5.2}/rubka/adaptorrubka/utils/utils.py +0 -0
  36. {rubka-6.4.6 → rubka-6.5.2}/rubka/button.py +0 -0
  37. {rubka-6.4.6 → rubka-6.5.2}/rubka/config.py +0 -0
  38. {rubka-6.4.6 → rubka-6.5.2}/rubka/decorators.py +0 -0
  39. {rubka-6.4.6 → rubka-6.5.2}/rubka/exceptions.py +0 -0
  40. {rubka-6.4.6 → rubka-6.5.2}/rubka/jobs.py +0 -0
  41. {rubka-6.4.6 → rubka-6.5.2}/rubka/keyboards.py +0 -0
  42. {rubka-6.4.6 → rubka-6.5.2}/rubka/keypad.py +0 -0
  43. {rubka-6.4.6 → rubka-6.5.2}/rubka/logger.py +0 -0
  44. {rubka-6.4.6 → rubka-6.5.2}/rubka/rubino.py +0 -0
  45. {rubka-6.4.6 → rubka-6.5.2}/rubka/utils.py +0 -0
  46. {rubka-6.4.6 → rubka-6.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 6.4.6
3
+ Version: 6.5.2
4
4
  Summary: A Python library for interacting with Rubika Bot API.
5
5
  Home-page: https://github.com/Mahdy-Ahmadi/Rubka
6
6
  Download-URL: https://github.com/Mahdy-Ahmadi/rubka/blob/main/project_library.zip
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 6.4.6
3
+ Version: 6.5.2
4
4
  Summary: A Python library for interacting with Rubika Bot API.
5
5
  Home-page: https://github.com/Mahdy-Ahmadi/Rubka
6
6
  Download-URL: https://github.com/Mahdy-Ahmadi/rubka/blob/main/project_library.zip
@@ -13,6 +13,7 @@ rubka/config.py
13
13
  rubka/context.py
14
14
  rubka/decorators.py
15
15
  rubka/exceptions.py
16
+ rubka/filters.py
16
17
  rubka/jobs.py
17
18
  rubka/keyboards.py
18
19
  rubka/keypad.py
@@ -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,15 +490,191 @@ class Robot:
489
490
 
490
491
  return decorator
491
492
 
492
- def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
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": func,
510
+ "func": wrapper,
496
511
  "filters": filters,
497
512
  "commands": commands
498
513
  })
499
- return func
514
+ return wrapper
515
+ return decorator
516
+ def on_message_file(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
517
+ def decorator(func: Callable[[Any, Message], None]):
518
+ def wrapper(bot, message: Message):
519
+ if not message.file:
520
+ return
521
+ if filters and not filters(message):
522
+ return
523
+ return func(bot, message)
524
+
525
+ self._message_handlers.append({
526
+ "func": wrapper,
527
+ "filters": filters,
528
+ "file_only": True,
529
+ "commands":commands
530
+ })
531
+ return wrapper
532
+ return decorator
533
+
534
+ def on_message_forwarded(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
535
+ def decorator(func: Callable[[Any, Message], None]):
536
+ def wrapper(bot, message: Message):
537
+ if not message.is_forwarded:
538
+ return
539
+ if filters and not filters(message):
540
+ return
541
+ return func(bot, message)
542
+
543
+ self._message_handlers.append({
544
+ "func": wrapper,
545
+ "filters": filters,
546
+ "forwarded_only": True,
547
+ "commands":commands
548
+ })
549
+ return wrapper
550
+ return decorator
551
+
552
+ def on_message_reply(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
553
+ def decorator(func: Callable[[Any, Message], None]):
554
+ def wrapper(bot, message: Message):
555
+ if not message.is_reply:
556
+ return
557
+ if filters and not filters(message):
558
+ return
559
+ return func(bot, message)
560
+
561
+ self._message_handlers.append({
562
+ "func": wrapper,
563
+ "filters": filters,
564
+ "reply_only": True,
565
+ "commands":commands
566
+ })
567
+ return wrapper
568
+ return decorator
569
+
570
+ def on_message_text(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
571
+ def decorator(func: Callable[[Any, Message], None]):
572
+ def wrapper(bot, message: Message):
573
+ if not message.text:
574
+ return
575
+ if filters and not filters(message):
576
+ return
577
+ return func(bot, message)
578
+
579
+ self._message_handlers.append({
580
+ "func": wrapper,
581
+ "filters": filters,
582
+ "text_only": True,
583
+ "commands":commands
584
+ })
585
+ return wrapper
586
+ return decorator
587
+
588
+ def on_message_media(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
589
+ def decorator(func: Callable[[Any, Message], None]):
590
+ def wrapper(bot, message: Message):
591
+ if not message.is_media:
592
+ return
593
+ if filters and not filters(message):
594
+ return
595
+ return func(bot, message)
596
+
597
+ self._message_handlers.append({
598
+ "func": wrapper,
599
+ "filters": filters,
600
+ "media_only": True,
601
+ "commands":commands
602
+ })
603
+ return wrapper
604
+ return decorator
605
+
606
+ def on_message_sticker(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
607
+ def decorator(func: Callable[[Any, Message], None]):
608
+ def wrapper(bot, message: Message):
609
+ if not message.sticker:
610
+ return
611
+ if filters and not filters(message):
612
+ return
613
+ return func(bot, message)
614
+
615
+ self._message_handlers.append({
616
+ "func": wrapper,
617
+ "filters": filters,
618
+ "sticker_only": True,
619
+ "commands":commands
620
+ })
621
+ return wrapper
622
+ return decorator
623
+
624
+ def on_message_contact(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
625
+ def decorator(func: Callable[[Any, Message], None]):
626
+ def wrapper(bot, message: Message):
627
+ if not message.is_contact:
628
+ return
629
+ if filters and not filters(message):
630
+ return
631
+ return func(bot, message)
632
+
633
+ self._message_handlers.append({
634
+ "func": wrapper,
635
+ "filters": filters,
636
+ "contact_only": True,
637
+ "commands":commands
638
+ })
639
+ return wrapper
500
640
  return decorator
641
+
642
+ def on_message_location(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
643
+ def decorator(func: Callable[[Any, Message], None]):
644
+ def wrapper(bot, message: Message):
645
+ if not message.is_location:
646
+ return
647
+ if filters and not filters(message):
648
+ return
649
+ return func(bot, message)
650
+
651
+ self._message_handlers.append({
652
+ "func": wrapper,
653
+ "filters": filters,
654
+ "location_only": True,
655
+ "commands":commands
656
+ })
657
+ return wrapper
658
+ return decorator
659
+
660
+ def on_message_poll(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
661
+ def decorator(func: Callable[[Any, Message], None]):
662
+ def wrapper(bot, message: Message):
663
+ if not message.is_poll:
664
+ return
665
+ if filters and not filters(message):
666
+ return
667
+ return func(bot, message)
668
+
669
+ self._message_handlers.append({
670
+ "func": wrapper,
671
+ "filters": filters,
672
+ "poll_only": True,
673
+ "commands":commands
674
+ })
675
+ return wrapper
676
+ return decorator
677
+
501
678
  def message_handler(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
502
679
  def decorator(func: Callable[[Any, Message], None]):
503
680
  self._message_handlers.append({
@@ -5,6 +5,7 @@ from typing import List, Optional, Dict, Any, Literal, Callable, Union
5
5
  from .exceptions import APIRequestError
6
6
  from .adaptorrubka import Client as Client_get
7
7
  from .logger import logger
8
+ from . import filters
8
9
  try:
9
10
  from .context import Message, InlineMessage
10
11
  except (ImportError, ModuleNotFoundError):
@@ -218,6 +219,9 @@ class Robot:
218
219
  raise InvalidTokenError("The provided bot token is invalid or expired.")
219
220
  from typing import Callable, Any, Optional, List
220
221
 
222
+
223
+ #decorator#
224
+
221
225
  def on_message_private(
222
226
  self,
223
227
  chat_id: Optional[Union[str, List[str]]] = None,
@@ -247,30 +251,20 @@ class Robot:
247
251
 
248
252
  if not message.is_private:
249
253
  return
250
-
251
-
252
254
  if chat_id:
253
255
  if isinstance(chat_id, str) and message.chat_id != chat_id:
254
256
  return
255
257
  if isinstance(chat_id, list) and message.chat_id not in chat_id:
256
258
  return
257
-
258
-
259
259
  if sender_id:
260
260
  if isinstance(sender_id, str) and message.sender_id != sender_id:
261
261
  return
262
262
  if isinstance(sender_id, list) and message.sender_id not in sender_id:
263
263
  return
264
-
265
-
266
264
  if sender_type and message.sender_type != sender_type:
267
265
  return
268
-
269
-
270
266
  if not allow_forwarded and message.forwarded_from:
271
267
  return
272
-
273
-
274
268
  if not allow_files and message.file:
275
269
  return
276
270
  if not allow_stickers and message.sticker:
@@ -534,14 +528,162 @@ class Robot:
534
528
  return wrapper
535
529
  return decorator
536
530
 
537
- def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
531
+ def on_message(
532
+ self,
533
+ filters: Optional[Callable[[Message], bool]] = None,
534
+ commands: Optional[List[str]] = None
535
+ ):
538
536
  def decorator(func: Callable[[Any, Message], None]):
537
+ async def wrapper(bot, message: Message):
538
+ if filters and not filters(message):return
539
+ if commands:
540
+ if not message.is_command:return
541
+ cmd = message.text.split()[0].lstrip("/")
542
+ if cmd not in commands:return
543
+ return await func(bot, message)
539
544
  self._message_handlers.append({
540
- "func": func,
545
+ "func": wrapper,
541
546
  "filters": filters,
542
547
  "commands": commands
543
548
  })
544
- return func
549
+ return wrapper
550
+ return decorator
551
+
552
+
553
+ def on_message_file(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
554
+ def decorator(func: Callable[[Any, Message], None]):
555
+ async def wrapper(bot, message: Message):
556
+ if not message.file:return
557
+ if filters and not filters(message):return
558
+ return await func(bot, message)
559
+
560
+ self._message_handlers.append({
561
+ "func": wrapper,
562
+ "filters": filters,
563
+ "file_only": True,
564
+ "commands": commands
565
+ })
566
+ return wrapper
567
+ return decorator
568
+ def on_message_forwarded(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
569
+ def decorator(func: Callable[[Any, Message], None]):
570
+ async def wrapper(bot, message: Message):
571
+ if not message.is_forwarded:return
572
+ if filters and not filters(message):return
573
+ return await func(bot, message)
574
+
575
+ self._message_handlers.append({
576
+ "func": wrapper,
577
+ "filters": filters,
578
+ "forwarded_only": True,
579
+ "commands": commands
580
+ })
581
+ return wrapper
582
+ return decorator
583
+ def on_message_reply(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
584
+ def decorator(func: Callable[[Any, Message], None]):
585
+ async def wrapper(bot, message: Message):
586
+ if not message.is_reply:return
587
+ if filters and not filters(message):return
588
+ return await func(bot, message)
589
+
590
+ self._message_handlers.append({
591
+ "func": wrapper,
592
+ "filters": filters,
593
+ "reply_only": True,
594
+ "commands": commands
595
+ })
596
+ return wrapper
597
+ return decorator
598
+ def on_message_text(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
599
+ def decorator(func: Callable[[Any, Message], None]):
600
+ async def wrapper(bot, message: Message):
601
+ if not message.text:return
602
+ if filters and not filters(message):return
603
+ return await func(bot, message)
604
+
605
+ self._message_handlers.append({
606
+ "func": wrapper,
607
+ "filters": filters,
608
+ "text_only": True,
609
+ "commands": commands
610
+ })
611
+ return wrapper
612
+ return decorator
613
+ def on_message_media(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
614
+ def decorator(func: Callable[[Any, Message], None]):
615
+ async def wrapper(bot, message: Message):
616
+ if not message.is_media:return
617
+ if filters and not filters(message):return
618
+ return await func(bot, message)
619
+
620
+ self._message_handlers.append({
621
+ "func": wrapper,
622
+ "filters": filters,
623
+ "media_only": True,
624
+ "commands": commands
625
+ })
626
+ return wrapper
627
+ return decorator
628
+ def on_message_sticker(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
629
+ def decorator(func: Callable[[Any, Message], None]):
630
+ async def wrapper(bot, message: Message):
631
+ if not message.sticker:return
632
+ if filters and not filters(message):return
633
+ return await func(bot, message)
634
+
635
+ self._message_handlers.append({
636
+ "func": wrapper,
637
+ "filters": filters,
638
+ "sticker_only": True,
639
+ "commands": commands
640
+ })
641
+ return wrapper
642
+ return decorator
643
+ def on_message_contact(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
644
+ def decorator(func: Callable[[Any, Message], None]):
645
+ async def wrapper(bot, message: Message):
646
+ if not message.is_contact:return
647
+ if filters and not filters(message):return
648
+ return await func(bot, message)
649
+
650
+ self._message_handlers.append({
651
+ "func": wrapper,
652
+ "filters": filters,
653
+ "contact_only": True,
654
+ "commands": commands
655
+ })
656
+ return wrapper
657
+ return decorator
658
+ def on_message_location(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
659
+ def decorator(func: Callable[[Any, Message], None]):
660
+ async def wrapper(bot, message: Message):
661
+ if not message.is_location:return
662
+ if filters and not filters(message):return
663
+ return await func(bot, message)
664
+
665
+ self._message_handlers.append({
666
+ "func": wrapper,
667
+ "filters": filters,
668
+ "location_only": True,
669
+ "commands": commands
670
+ })
671
+ return wrapper
672
+ return decorator
673
+ def on_message_poll(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
674
+ def decorator(func: Callable[[Any, Message], None]):
675
+ async def wrapper(bot, message: Message):
676
+ if not message.is_poll:return
677
+ if filters and not filters(message):return
678
+ return await func(bot, message)
679
+
680
+ self._message_handlers.append({
681
+ "func": wrapper,
682
+ "filters": filters,
683
+ "poll_only": True,
684
+ "commands": commands
685
+ })
686
+ return wrapper
545
687
  return decorator
546
688
  def on_update(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
547
689
  def decorator(func: Callable[[Any, Message], None]):
@@ -1516,7 +1658,6 @@ class Robot:
1516
1658
  json_data = await response.json()
1517
1659
  if is_temp_file:
1518
1660
  os.remove(path)
1519
- print(json_data)
1520
1661
  return json_data.get('data', {}).get('file_id')
1521
1662
  except :
1522
1663
  raise FeatureNotAvailableError(f"files is not currently supported by the server.")
@@ -1642,7 +1783,7 @@ class Robot:
1642
1783
  "inline_keypad":inline_keypad,
1643
1784
  "chat_keypad_type":chat_keypad_type
1644
1785
  }
1645
- return json.dumps(result, ensure_ascii=False, indent=4)
1786
+ return result
1646
1787
 
1647
1788
  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):
1648
1789
  if path:
@@ -1702,13 +1843,19 @@ class Robot:
1702
1843
  try:
1703
1844
  chat = await self.get_chat(chat_id)
1704
1845
  chat_info = chat.get("data", {}).get("chat", {})
1705
- first_name = chat_info.get("first_name", "")
1706
- last_name = chat_info.get("last_name", "")
1707
-
1708
- full_name = f"{first_name} {last_name}".strip()
1709
- return full_name if full_name else "Unknown"
1710
- except Exception:
1711
- return "Unknown"
1846
+ chat_type = chat_info.get("chat_type", "").lower()
1847
+ if chat_type == "user":
1848
+ first_name = chat_info.get("first_name", "")
1849
+ last_name = chat_info.get("last_name", "")
1850
+ full_name = f"{first_name} {last_name}".strip()
1851
+ return full_name if full_name else "null"
1852
+ elif chat_type in ["group", "channel"]:
1853
+ title = chat_info.get("title", "")
1854
+ return title if title else "null"
1855
+ else:return "null"
1856
+ except Exception:return "null"
1857
+
1858
+
1712
1859
 
1713
1860
  async def get_username(self, chat_id: str) -> str:
1714
1861
  chat_info = await self.get_chat(chat_id)
@@ -190,8 +190,10 @@ from pathlib import Path
190
190
  class Message:
191
191
  def __init__(self, bot, chat_id, message_id, sender_id, text=None, raw_data=None):
192
192
  self.bot = bot
193
- self.chat_id = chat_id
194
193
  self.raw_data = raw_data or {}
194
+ self.chat_id = chat_id
195
+ self.object_guid = chat_id
196
+ self.author_guid = self.raw_data.get("sender_id", sender_id)
195
197
  self.message_id: str = self.raw_data.get("message_id", message_id)
196
198
  self.text: str = self.raw_data.get("text", text)
197
199
  self.sender_id: str = self.raw_data.get("sender_id", sender_id)
@@ -199,6 +201,7 @@ class Message:
199
201
  self.is_edited: bool = self.raw_data.get("is_edited", False)
200
202
  self.sender_type: str = self.raw_data.get("sender_type")
201
203
  self.args = []
204
+ self.is_command = bool(self.text and self.text.startswith("/"))
202
205
  self.is_user = self.chat_id.startswith("b")
203
206
  self.is_private = self.chat_id.startswith("b")
204
207
  self.is_group = self.chat_id.startswith("g")
@@ -212,7 +215,18 @@ class Message:
212
215
  self.location = Location(self.raw_data["location"]) if "location" in self.raw_data else None
213
216
  self.live_location = LiveLocation(self.raw_data["live_location"]) if "live_location" in self.raw_data else None
214
217
  self.aux_data = AuxData(self.raw_data["aux_data"]) if "aux_data" in self.raw_data else None
215
-
218
+ self.is_reply = self.reply_to_message_id is not None
219
+ self.has_media = any([self.file, self.sticker, self.poll, self.location, self.live_location])
220
+ self.is_forwarded = self.forwarded_from is not None
221
+ self.is_text = bool(self.text and not self.has_media)
222
+ self.is_media = self.has_media
223
+ self.is_poll = self.poll is not None
224
+ self.is_location = self.location is not None
225
+ self.is_live_location = self.live_location is not None
226
+ self.is_contact = self.contact_message is not None
227
+ self.has_any_media = any([self.file, self.sticker, self.poll, self.location, self.live_location])
228
+ self.edited_text = self.raw_data.get("edited_text") if self.is_edited else None
229
+ self.info = {attr: value for attr, value in vars(self).items()}
216
230
  @property
217
231
  def session(self):
218
232
  if self.chat_id not in self.bot.sessions:
@@ -0,0 +1,317 @@
1
+ from typing import Callable
2
+ import re
3
+
4
+ class Filter:
5
+ def __init__(self, func: Callable):
6
+ self.func = func
7
+
8
+ def __call__(self, message):
9
+ return self.func(message)
10
+
11
+ def __and__(self, other):
12
+ return Filter(lambda m: self(m) and other(m))
13
+
14
+ def __or__(self, other):
15
+ return Filter(lambda m: self(m) or other(m))
16
+
17
+ def __invert__(self):
18
+ return Filter(lambda m: not self(m))
19
+
20
+ def __xor__(self, other):
21
+ return Filter(lambda m: self(m) != other(m))
22
+
23
+ def __eq__(self, other):
24
+ return Filter(lambda m: self(m) == other)
25
+
26
+ def __ne__(self, other):
27
+ return Filter(lambda m: self(m) != other)
28
+
29
+ def __lt__(self, other):
30
+ return Filter(lambda m: self(m) < other)
31
+
32
+ def __le__(self, other):
33
+ return Filter(lambda m: self(m) <= other)
34
+
35
+ def __gt__(self, other):
36
+ return Filter(lambda m: self(m) > other)
37
+
38
+ def __ge__(self, other):
39
+ return Filter(lambda m: self(m) >= other)
40
+
41
+ def __add__(self, other):
42
+ return Filter(lambda m: self(m) + (other(m) if callable(other) else other))
43
+
44
+ def __sub__(self, other):
45
+ return Filter(lambda m: self(m) - (other(m) if callable(other) else other))
46
+
47
+ def __mul__(self, other):
48
+ return Filter(lambda m: self(m) * (other(m) if callable(other) else other))
49
+
50
+ def __truediv__(self, other):
51
+ return Filter(lambda m: self(m) / (other(m) if callable(other) else other))
52
+
53
+ is_text = Filter(lambda m: getattr(m, "is_text", False))
54
+ is_file = Filter(lambda m: getattr(m, "file", None) is not None)
55
+ is_sticker = Filter(lambda m: getattr(m, "sticker", None) is not None)
56
+ is_contact = Filter(lambda m: getattr(m, "contact_message", None) is not None)
57
+ is_poll = Filter(lambda m: getattr(m, "poll", None) is not None)
58
+ is_location = Filter(lambda m: getattr(m, "location", None) is not None)
59
+ is_live_location = Filter(lambda m: getattr(m, "live_location", None) is not None)
60
+ has_any_media = Filter(lambda m: getattr(m, "has_any_media", False))
61
+ has_media = Filter(lambda m: getattr(m, "has_media", False))
62
+ is_command = Filter(lambda m: getattr(m, "is_command", False))
63
+ is_user = Filter(lambda m: getattr(m, "is_user", False))
64
+ is_private = Filter(lambda m: getattr(m, "is_private", False))
65
+ is_group = Filter(lambda m: getattr(m, "is_group", False))
66
+ is_channel = Filter(lambda m: getattr(m, "is_channel", False))
67
+ is_reply = Filter(lambda m: getattr(m, "is_reply", False))
68
+ is_forwarded = Filter(lambda m: getattr(m, "is_forwarded", False))
69
+ is_edited = Filter(lambda m: getattr(m, "is_edited", False))
70
+
71
+
72
+
73
+
74
+ def text(keyword: str):
75
+ return Filter(lambda m: getattr(m, "text", "") and keyword in m.text)
76
+
77
+ def text_length(min_len: int = 0, max_len: int = None):
78
+ def _filter(m):
79
+ t = getattr(m, "text", "")
80
+ if not t: return False
81
+ if len(t) < min_len: return False
82
+ if max_len is not None and len(t) > max_len: return False
83
+ return True
84
+ return Filter(_filter)
85
+
86
+ def text_regex(pattern: str):
87
+ regex = re.compile(pattern)
88
+ return Filter(lambda m: getattr(m, "text", "") and regex.search(m.text))
89
+
90
+ def text_startswith(prefix: str):
91
+ return Filter(lambda m: getattr(m, "text", "").startswith(prefix) if getattr(m, "text", None) else False)
92
+
93
+ def text_endswith(suffix: str):
94
+ return Filter(lambda m: getattr(m, "text", "").endswith(suffix) if getattr(m, "text", None) else False)
95
+
96
+ def text_upper():
97
+ return Filter(lambda m: getattr(m, "text", "").isupper() if getattr(m, "text", None) else False)
98
+
99
+ def text_lower():
100
+ return Filter(lambda m: getattr(m, "text", "").islower() if getattr(m, "text", None) else False)
101
+
102
+ def text_digit():
103
+ return Filter(lambda m: getattr(m, "text", "").isdigit() if getattr(m, "text", None) else False)
104
+
105
+ def text_word_count(min_words: int = 1, max_words: int = None):
106
+ def _filter(m):
107
+ t = getattr(m, "text", "")
108
+ if not t: return False
109
+ wc = len(t.split())
110
+ if wc < min_words: return False
111
+ if max_words is not None and wc > max_words: return False
112
+ return True
113
+ return Filter(_filter)
114
+
115
+ def text_contains_any(keywords: list):
116
+ return Filter(lambda m: getattr(m, "text", "") and any(k in m.text for k in keywords))
117
+
118
+ def text_equals(value: str):
119
+ return Filter(lambda m: getattr(m, "text", None) == value)
120
+
121
+ def text_not_equals(value: str):
122
+ return Filter(lambda m: getattr(m, "text", None) != value)
123
+
124
+
125
+
126
+
127
+ def file_size_gt(size: int):
128
+ return Filter(lambda m: m.file and getattr(m.file, "size", 0) > size)
129
+
130
+ def file_size_lt(size: int):
131
+ return Filter(lambda m: m.file and getattr(m.file, "size", 0) < size)
132
+
133
+ def file_name_contains(substring: str):
134
+ return Filter(lambda m: m.file and substring in getattr(m.file, "file_name", ""))
135
+
136
+ def file_extension(ext: str):
137
+ return Filter(lambda m: m.file and getattr(m.file, "file_name", "").endswith(ext))
138
+
139
+ def file_id_is(file_id: str):
140
+ return Filter(lambda m: m.file and getattr(m.file, "file_id", None) == file_id)
141
+
142
+
143
+
144
+
145
+ def sticker_id_is(sid: str):
146
+ return Filter(lambda m: m.sticker and getattr(m.sticker, "sticker_id", None) == sid)
147
+
148
+ def sticker_emoji_is(emoji: str):
149
+ return Filter(lambda m: m.sticker and getattr(m.sticker, "emoji", None) == emoji)
150
+
151
+
152
+
153
+
154
+ def poll_question_contains(keyword: str):
155
+ return Filter(lambda m: m.poll and keyword in getattr(m.poll, "question", ""))
156
+
157
+ def poll_option_count(min_options: int = 1, max_options: int = None):
158
+ def _filter(m):
159
+ if not getattr(m, "poll", None): return False
160
+ options = getattr(m.poll, "options", [])
161
+ if len(options) < min_options: return False
162
+ if max_options is not None and len(options) > max_options: return False
163
+ return True
164
+ return Filter(_filter)
165
+
166
+
167
+
168
+
169
+ def location_within(lat_min, lat_max, long_min, long_max):
170
+ def _filter(m):
171
+ loc = getattr(m, "location", None)
172
+ if not loc: return False
173
+ return lat_min <= getattr(loc, "lat", 0) <= lat_max and long_min <= getattr(loc, "long", 0) <= long_max
174
+ return Filter(_filter)
175
+
176
+ def live_location_within(lat_min, lat_max, long_min, long_max):
177
+ def _filter(m):
178
+ loc = getattr(m, "live_location", None)
179
+ if not loc: return False
180
+ return lat_min <= getattr(loc, "lat", 0) <= lat_max and long_min <= getattr(loc, "long", 0) <= long_max
181
+ return Filter(_filter)
182
+
183
+
184
+
185
+
186
+ def has_media_types(types: list):
187
+ return Filter(lambda m: any(getattr(m, t, None) for t in types))
188
+
189
+ def message_id_is(mid: str):
190
+ return Filter(lambda m: getattr(m, "message_id", None) == mid)
191
+
192
+ def is_reply_to_user(user_id: str):
193
+ return Filter(lambda m: getattr(m, "reply_to_message_id", None) == user_id)
194
+
195
+ def is_forwarded_from(user_id: str):
196
+ return Filter(lambda m: getattr(m.forwarded_from, "sender_id", None) == user_id if getattr(m, "forwarded_from", None) else False)
197
+
198
+ def edited_text_contains(keyword: str):
199
+ return Filter(lambda m: getattr(m, "edited_text", "") and keyword in m.edited_text)
200
+
201
+ def aux_data_contains(key: str, value):
202
+ return Filter(lambda m: getattr(m.aux_data, key, None) == value if getattr(m, "aux_data", None) else False)
203
+
204
+
205
+
206
+
207
+ def file_attr(attr_name):
208
+ return Filter(lambda m: m.file and getattr(m.file, attr_name, None))
209
+
210
+ def sticker_attr(attr_name):
211
+ return Filter(lambda m: m.sticker and getattr(m.sticker, attr_name, None))
212
+
213
+ def poll_attr(attr_name):
214
+ return Filter(lambda m: m.poll and getattr(m.poll, attr_name, None))
215
+
216
+ def location_attr(attr_name):
217
+ return Filter(lambda m: m.location and getattr(m.location, attr_name, None))
218
+
219
+ def live_location_attr(attr_name):
220
+ return Filter(lambda m: m.live_location and getattr(m.live_location, attr_name, None))
221
+
222
+
223
+
224
+
225
+ file_size = file_attr("size")
226
+ file_name = file_attr("file_name")
227
+ sticker_id = sticker_attr("sticker_id")
228
+ poll_question = poll_attr("question")
229
+ location_lat = location_attr("lat")
230
+ location_long = location_attr("long")
231
+ live_location_lat = live_location_attr("lat")
232
+ live_location_long = live_location_attr("long")
233
+
234
+ _custom_filters = {}
235
+ def chat_title_contains(keyword: str):
236
+
237
+ return Filter(lambda m: getattr(m, "chat", None) and keyword in getattr(m.chat, "title", ""))
238
+
239
+ def chat_title_equals(value: str):
240
+
241
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "title", "") == value)
242
+
243
+ def chat_id_is(cid: str):
244
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "id", None) == cid)
245
+
246
+ def chat_member_count(min_count: int = 0, max_count: int = None):
247
+
248
+ def _filter(m):
249
+ c = getattr(m, "chat", None)
250
+ if not c: return False
251
+ count = getattr(c, "member_count", 0)
252
+ if count < min_count: return False
253
+ if max_count is not None and count > max_count: return False
254
+ return True
255
+ return Filter(_filter)
256
+
257
+ def chat_type_is(chat_type: str):
258
+
259
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "type", None) == chat_type)
260
+
261
+ def chat_username_contains(keyword: str):
262
+
263
+ return Filter(lambda m: getattr(m, "chat", None) and keyword in getattr(m.chat, "username", ""))
264
+
265
+ def chat_username_equals(value: str):
266
+
267
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "username", "") == value)
268
+
269
+ def chat_has_link():
270
+
271
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "invite_link", None) is not None)
272
+
273
+ def chat_is_private():
274
+
275
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "type", None) in ["group", "channel"])
276
+
277
+ def chat_member_count_gt(count: int):
278
+
279
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "member_count", 0) > count)
280
+
281
+ def chat_member_count_lt(count: int):
282
+
283
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "member_count", 0) < count)
284
+
285
+ def chat_has_username():
286
+
287
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "username", None) is not None)
288
+
289
+ def chat_type_in(types: list):
290
+
291
+ return Filter(lambda m: getattr(m, "chat", None) and getattr(m.chat, "type", None) in types)
292
+
293
+
294
+ def chat_title_regex(pattern: str):
295
+ regex = re.compile(pattern)
296
+ return Filter(lambda m: getattr(m, "chat", None) and regex.search(getattr(m.chat, "title", "")))
297
+
298
+ def chat_username_regex(pattern: str):
299
+ regex = re.compile(pattern)
300
+ return Filter(lambda m: getattr(m, "chat", None) and regex.search(getattr(m.chat, "username", "")))
301
+ def custom(name):
302
+ def wrapper(func):
303
+ _custom_filters[name] = Filter(func)
304
+ return _custom_filters[name]
305
+ return wrapper
306
+
307
+ def get_custom(name):
308
+ return _custom_filters.get(name)
309
+
310
+ def and_(*filters):
311
+ return Filter(lambda m: all(f(m) for f in filters))
312
+
313
+ def or_(*filters):
314
+ return Filter(lambda m: any(f(m) for f in filters))
315
+
316
+ def not_(filter_):
317
+ return Filter(lambda m: not filter_(m))
@@ -190,8 +190,10 @@ from pathlib import Path
190
190
  class Message:
191
191
  def __init__(self, bot, chat_id, message_id, sender_id, text=None, raw_data=None):
192
192
  self.bot = bot
193
- self.chat_id = chat_id
194
193
  self.raw_data = raw_data or {}
194
+ self.chat_id = chat_id
195
+ self.object_guid = chat_id
196
+ self.author_guid = self.raw_data.get("sender_id", sender_id)
195
197
  self.message_id: str = self.raw_data.get("message_id", message_id)
196
198
  self.text: str = self.raw_data.get("text", text)
197
199
  self.sender_id: str = self.raw_data.get("sender_id", sender_id)
@@ -199,6 +201,7 @@ class Message:
199
201
  self.is_edited: bool = self.raw_data.get("is_edited", False)
200
202
  self.sender_type: str = self.raw_data.get("sender_type")
201
203
  self.args = []
204
+ self.is_command = bool(self.text and self.text.startswith("/"))
202
205
  self.is_user = self.chat_id.startswith("b")
203
206
  self.is_private = self.chat_id.startswith("b")
204
207
  self.is_group = self.chat_id.startswith("g")
@@ -212,7 +215,18 @@ class Message:
212
215
  self.location = Location(self.raw_data["location"]) if "location" in self.raw_data else None
213
216
  self.live_location = LiveLocation(self.raw_data["live_location"]) if "live_location" in self.raw_data else None
214
217
  self.aux_data = AuxData(self.raw_data["aux_data"]) if "aux_data" in self.raw_data else None
215
-
218
+ self.is_reply = self.reply_to_message_id is not None
219
+ self.has_media = any([self.file, self.sticker, self.poll, self.location, self.live_location])
220
+ self.is_forwarded = self.forwarded_from is not None
221
+ self.is_text = bool(self.text and not self.has_media)
222
+ self.is_media = self.has_media
223
+ self.is_poll = self.poll is not None
224
+ self.is_location = self.location is not None
225
+ self.is_live_location = self.live_location is not None
226
+ self.is_contact = self.contact_message is not None
227
+ self.has_any_media = any([self.file, self.sticker, self.poll, self.location, self.live_location])
228
+ self.edited_text = self.raw_data.get("edited_text") if self.is_edited else None
229
+ self.info = {attr: value for attr, value in vars(self).items()}
216
230
  @property
217
231
  def session(self):
218
232
  if self.chat_id not in self.bot.sessions:
@@ -8,7 +8,7 @@ except FileNotFoundError:
8
8
 
9
9
  setup(
10
10
  name='Rubka',
11
- version='6.4.6',
11
+ version='6.5.2',
12
12
  description='A Python library for interacting with Rubika Bot API.',
13
13
  long_description=long_description,
14
14
  long_description_content_type='text/markdown',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes