satori-python 0.18.0__tar.gz → 1.3.0__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 (30) hide show
  1. {satori_python-0.18.0 → satori_python-1.3.0}/PKG-INFO +8 -8
  2. {satori_python-0.18.0 → satori_python-1.3.0}/README.md +7 -7
  3. {satori_python-0.18.0 → satori_python-1.3.0}/pyproject.toml +1 -1
  4. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/__init__.py +4 -1
  5. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/__init__.py +9 -0
  6. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/account.py +2 -1
  7. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/account.pyi +30 -16
  8. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/config.py +7 -3
  9. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/protocol.py +42 -28
  10. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/const.py +5 -1
  11. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/element.py +18 -3
  12. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/event.py +6 -0
  13. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/model.py +55 -9
  14. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/server/__init__.py +4 -0
  15. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/server/route.py +9 -7
  16. {satori_python-0.18.0 → satori_python-1.3.0}/LICENSE +0 -0
  17. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/_vendor/fleep.py +0 -0
  18. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/network/__init__.py +0 -0
  19. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/network/base.py +0 -0
  20. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/network/util.py +0 -0
  21. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/network/webhook.py +0 -0
  22. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/client/network/websocket.py +0 -0
  23. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/exception.py +0 -0
  24. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/parser.py +0 -0
  25. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/server/adapter.py +0 -0
  26. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/server/connection.py +0 -0
  27. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/server/formdata.py +0 -0
  28. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/server/model.py +0 -0
  29. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/server/utils.py +0 -0
  30. {satori_python-0.18.0 → satori_python-1.3.0}/src/satori/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python
3
- Version: 0.18.0
3
+ Version: 1.3.0
4
4
  Summary: Satori Protocol SDK for python
5
5
  Home-page: https://github.com/RF-Tar-Railt/satori-python
6
6
  Author-Email: RF-Tar-Railt <rf_tar_railt@qq.com>
@@ -81,13 +81,13 @@ pip install satori-python-server
81
81
 
82
82
  ### 官方适配器
83
83
 
84
- | 适配器 | 安装 | 路径 |
85
- |------------|----------------------------------------------|--------------------------------------------------------------------|
86
- | Satori | `pip install satori-python-adapter-satori` | satori.adapters.satori |
87
- | OneBot V11 | `pip install satori-python-adapter-onebot11` | satori.adapters.onebot11.forward, satori.adapters.onebot11.reverse |
88
- | Console | `pip install satori-python-adapter-console` | satori.adapters.console |
89
- | Milky | `pip install satori-python-adapter-milky` | satori.adapters.milky.main, satori.adapters.milky.webhook |
90
- | QQ | `pip install satori-python-adapter-qq` | satori.adapters.milky.main, satori.adapters.milky.websocket |
84
+ | 适配器 | 安装 | 路径 |
85
+ |------------|----------------------------------------------|--------------------------------------------------------------------------------------|
86
+ | Satori | `pip install satori-python-adapter-satori` | satori.adapters.satori |
87
+ | OneBot V11 | `pip install satori-python-adapter-onebot11` | satori.adapters.onebot11.forward, satori.adapters.onebot11.reverse |
88
+ | Console | `pip install satori-python-adapter-console` | satori.adapters.console |
89
+ | Milky | `pip install satori-python-adapter-milky` | satori.adapters.milky.main, satori.adapters.milky.webhook, satori.adapters.milky.sse |
90
+ | QQ | `pip install satori-python-adapter-qq` | satori.adapters.qq.main, satori.adapters.qq.websocket |
91
91
 
92
92
  ### 社区适配器
93
93
 
@@ -48,13 +48,13 @@ pip install satori-python-server
48
48
 
49
49
  ### 官方适配器
50
50
 
51
- | 适配器 | 安装 | 路径 |
52
- |------------|----------------------------------------------|--------------------------------------------------------------------|
53
- | Satori | `pip install satori-python-adapter-satori` | satori.adapters.satori |
54
- | OneBot V11 | `pip install satori-python-adapter-onebot11` | satori.adapters.onebot11.forward, satori.adapters.onebot11.reverse |
55
- | Console | `pip install satori-python-adapter-console` | satori.adapters.console |
56
- | Milky | `pip install satori-python-adapter-milky` | satori.adapters.milky.main, satori.adapters.milky.webhook |
57
- | QQ | `pip install satori-python-adapter-qq` | satori.adapters.milky.main, satori.adapters.milky.websocket |
51
+ | 适配器 | 安装 | 路径 |
52
+ |------------|----------------------------------------------|--------------------------------------------------------------------------------------|
53
+ | Satori | `pip install satori-python-adapter-satori` | satori.adapters.satori |
54
+ | OneBot V11 | `pip install satori-python-adapter-onebot11` | satori.adapters.onebot11.forward, satori.adapters.onebot11.reverse |
55
+ | Console | `pip install satori-python-adapter-console` | satori.adapters.console |
56
+ | Milky | `pip install satori-python-adapter-milky` | satori.adapters.milky.main, satori.adapters.milky.webhook, satori.adapters.milky.sse |
57
+ | QQ | `pip install satori-python-adapter-qq` | satori.adapters.qq.main, satori.adapters.qq.websocket |
58
58
 
59
59
  ### 社区适配器
60
60
 
@@ -30,7 +30,7 @@ classifiers = [
30
30
  "Programming Language :: Python :: 3.12",
31
31
  "Operating System :: OS Independent",
32
32
  ]
33
- version = "0.18.0"
33
+ version = "1.3.0"
34
34
 
35
35
  [project.license]
36
36
  text = "MIT"
@@ -8,6 +8,7 @@ from .element import Button as Button
8
8
  from .element import Code as Code
9
9
  from .element import E as E
10
10
  from .element import Element as Element
11
+ from .element import Emoji as Emoji
11
12
  from .element import File as File
12
13
  from .element import Image as Image
13
14
  from .element import Italic as Italic
@@ -30,7 +31,9 @@ from .model import ArgvInteraction as ArgvInteraction
30
31
  from .model import ButtonInteraction as ButtonInteraction
31
32
  from .model import Channel as Channel
32
33
  from .model import ChannelType as ChannelType
34
+ from .model import EmojiObject as EmojiObject
33
35
  from .model import Event as Event
36
+ from .model import Friend as Friend
34
37
  from .model import Guild as Guild
35
38
  from .model import Login as Login
36
39
  from .model import LoginStatus as LoginStatus
@@ -42,7 +45,7 @@ from .model import Role as Role
42
45
  from .model import Upload as Upload
43
46
  from .model import User as User
44
47
 
45
- __version__ = "0.18.0"
48
+ __version__ = "1.3.0"
46
49
 
47
50
 
48
51
  MessageReceipt = MessageObject
@@ -152,6 +152,15 @@ class App(Service):
152
152
  Callable[[Account, events.GuildRoleEvent], Awaitable[Any]],
153
153
  ]: ...
154
154
 
155
+ @overload
156
+ def register_on(
157
+ self,
158
+ event_type: Literal[EventType.GUILD_EMOJI_ADDED, EventType.GUILD_EMOJI_REMOVED, EventType.GUILD_EMOJI_UPDATED],
159
+ ) -> Callable[
160
+ [Callable[[Account, events.GuildEmojiEvent], Awaitable[Any]]],
161
+ Callable[[Account, events.GuildEmojiEvent], Awaitable[Any]],
162
+ ]: ...
163
+
155
164
  @overload
156
165
  def register_on(
157
166
  self, event_type: Literal[EventType.LOGIN_ADDED, EventType.LOGIN_REMOVED, EventType.LOGIN_UPDATED]
@@ -20,13 +20,14 @@ class ApiInfo:
20
20
  port: int = 5140
21
21
  path: str = ""
22
22
  token: str | None = None
23
+ secure: bool = False
23
24
  timeout: float | None = None
24
25
  api_base: URL = field(init=False)
25
26
 
26
27
  def __post_init__(self):
27
28
  if self.path and not self.path.startswith("/"):
28
29
  self.path = f"/{self.path}"
29
- self.api_base = URL(f"http://{self.host}:{self.port}{self.path}") / "v1"
30
+ self.api_base = URL(f"http{'s' if self.secure else ''}://{self.host}:{self.port}{self.path}") / "v1"
30
31
 
31
32
 
32
33
  class Account(Generic[TP]):
@@ -10,6 +10,7 @@ from satori.model import (
10
10
  Channel,
11
11
  Direction,
12
12
  Event,
13
+ Friend,
13
14
  Guild,
14
15
  IterablePageResult,
15
16
  Login,
@@ -40,6 +41,7 @@ class ApiInfo(Api):
40
41
  port: int = 5140,
41
42
  path: str = "",
42
43
  token: str | None = None,
44
+ secure: bool = False,
43
45
  timeout: float | None = None,
44
46
  ):
45
47
  self.api_base: URL = ...
@@ -450,19 +452,21 @@ class Account(Generic[TP]):
450
452
  None: 该方法无返回值
451
453
  """
452
454
 
453
- async def reaction_create(self, channel_id: str, message_id: str, emoji: str) -> None:
455
+ async def reaction_create(self, channel_id: str, message_id: str, emoji_id: str) -> None:
454
456
  """向特定消息添加表态。
455
457
 
456
458
  Args:
457
459
  channel_id (str): 频道 ID
458
460
  message_id (str): 消息 ID
459
- emoji (str): 表态名称
461
+ emoji_id (str): 表情 ID
460
462
 
461
463
  Returns:
462
464
  None: 该方法无返回值
463
465
  """
464
466
 
465
- async def reaction_delete(self, channel_id: str, message_id: str, emoji: str, user_id: str | None = None) -> None:
467
+ async def reaction_delete(
468
+ self, channel_id: str, message_id: str, emoji_id: str, user_id: str | None = None
469
+ ) -> None:
466
470
  """从特定消息删除某个用户添加的特定表态。
467
471
 
468
472
  如果没有传入用户 ID 则表示删除自己的表态。
@@ -470,14 +474,14 @@ class Account(Generic[TP]):
470
474
  Args:
471
475
  channel_id (str): 频道 ID
472
476
  message_id (str): 消息 ID
473
- emoji (str): 表态名称
477
+ emoji_id (str): 表情 ID
474
478
  user_id (str | None, optional): 用户 ID,默认为 None
475
479
 
476
480
  Returns:
477
481
  None: 该方法无返回值
478
482
  """
479
483
 
480
- async def reaction_clear(self, channel_id: str, message_id: str, emoji: str | None = None) -> None:
484
+ async def reaction_clear(self, channel_id: str, message_id: str, emoji_id: str | None = None) -> None:
481
485
  """从特定消息清除某个特定表态。
482
486
 
483
487
  如果没有传入表态名称则表示清除所有表态。
@@ -485,21 +489,21 @@ class Account(Generic[TP]):
485
489
  Args:
486
490
  channel_id (str): 频道 ID
487
491
  message_id (str): 消息 ID
488
- emoji (str | None, optional): 表态名称,默认为 None
492
+ emoji_id (str | None, optional): 表情 ID,默认为 None
489
493
 
490
494
  Returns:
491
495
  None: 该方法无返回值
492
496
  """
493
497
 
494
498
  def reaction_list(
495
- self, channel_id: str, message_id: str, emoji: str, next_token: str | None = None
499
+ self, channel_id: str, message_id: str, emoji_id: str, next_token: str | None = None
496
500
  ) -> IterablePageResult[User]:
497
501
  """获取添加特定消息的特定表态的用户列表。返回一个 User 的分页列表。
498
502
 
499
503
  Args:
500
504
  channel_id (str): 频道 ID
501
505
  message_id (str): 消息 ID
502
- emoji (str): 表态名称
506
+ emoji_id (str): 表情 ID
503
507
  next_token (str | None, optional): 分页令牌,默认为空
504
508
 
505
509
  Returns:
@@ -513,24 +517,24 @@ class Account(Generic[TP]):
513
517
  Login: `Login` 对象
514
518
  """
515
519
 
516
- async def user_get(self, user_id: str) -> User:
517
- """获取用户信息。返回一个 `User` 对象。
520
+ def friend_list(self, next_token: str | None = None) -> IterablePageResult[Friend]:
521
+ """获取好友列表。返回一个 Friend 的分页列表。
518
522
 
519
523
  Args:
520
- user_id (str): 用户 ID
524
+ next_token (str | None, optional): 分页令牌,默认为空
521
525
 
522
526
  Returns:
523
- User: `User` 对象
527
+ IterablePageResult[Friend]: `Friend` 的分页列表
524
528
  """
525
529
 
526
- def friend_list(self, next_token: str | None = None) -> IterablePageResult[User]:
527
- """获取好友列表。返回一个 User 的分页列表。
530
+ async def friend_delete(self, user_id: str) -> None:
531
+ """删除好友。
528
532
 
529
533
  Args:
530
- next_token (str | None, optional): 分页令牌,默认为空
534
+ user_id (str): 用户 ID
531
535
 
532
536
  Returns:
533
- IterablePageResult[User]: `User` 的分页列表
537
+ None: 该方法无返回值
534
538
  """
535
539
 
536
540
  async def friend_approve(self, request_id: str, approve: bool, comment: str) -> None:
@@ -545,6 +549,16 @@ class Account(Generic[TP]):
545
549
  None: 该方法无返回值
546
550
  """
547
551
 
552
+ async def user_get(self, user_id: str) -> User:
553
+ """获取用户信息。返回一个 `User` 对象。
554
+
555
+ Args:
556
+ user_id (str): 用户 ID
557
+
558
+ Returns:
559
+ User: `User` 对象
560
+ """
561
+
548
562
  async def internal(self, action: str, method: str = "POST", **kwargs) -> Any:
549
563
  """内部接口调用。
550
564
 
@@ -23,6 +23,7 @@ class WebsocketsInfo(Config):
23
23
  port: int = 5140
24
24
  path: str = ""
25
25
  token: str | None = None
26
+ secure: bool = False
26
27
  timeout: float | None = None
27
28
  identity: str = None # type: ignore
28
29
  api_base: URL = None # type: ignore
@@ -31,11 +32,11 @@ class WebsocketsInfo(Config):
31
32
  if self.path and not self.path.startswith("/"):
32
33
  self.path = f"/{self.path}"
33
34
  self.identity = f"{self.host}:{self.port}"
34
- self.api_base = URL(f"http://{self.host}:{self.port}{self.path}") / "v1"
35
+ self.api_base = URL(f"http{'s' if self.secure else ''}://{self.host}:{self.port}{self.path}") / "v1"
35
36
 
36
37
  @property
37
38
  def ws_base(self):
38
- return URL(f"ws://{self.host}:{self.port}{self.path}") / "v1"
39
+ return URL(f"ws{'s' if self.secure else ''}://{self.host}:{self.port}{self.path}") / "v1"
39
40
 
40
41
 
41
42
  @dataclass
@@ -44,6 +45,7 @@ class WebhookInfo(Config):
44
45
  port: int = 8080
45
46
  path: str = "v1/events"
46
47
  token: str | None = None
48
+ secure: bool = False
47
49
  server_host: str = "localhost"
48
50
  server_port: int = 5140
49
51
  server_path: str = ""
@@ -57,4 +59,6 @@ class WebhookInfo(Config):
57
59
  if self.server_path and not self.server_path.startswith("/"):
58
60
  self.server_path = f"/{self.server_path}"
59
61
  self.identity = f"{self.host}:{self.port}{self.path}"
60
- self.api_base = URL(f"http://{self.server_host}:{self.server_port}{self.server_path}") / "v1"
62
+ self.api_base = (
63
+ URL(f"http{'s' if self.secure else ''}://{self.server_host}:{self.server_port}{self.server_path}") / "v1"
64
+ )
@@ -29,6 +29,7 @@ from satori.model import (
29
29
  User,
30
30
  )
31
31
 
32
+ from .. import Friend
32
33
  from .network.util import validate_response
33
34
 
34
35
  if TYPE_CHECKING:
@@ -632,23 +633,25 @@ class ApiProtocol:
632
633
  {"guild_id": guild_id, "role_id": role_id},
633
634
  )
634
635
 
635
- async def reaction_create(self, channel_id: str, message_id: str, emoji: str) -> None:
636
+ async def reaction_create(self, channel_id: str, message_id: str, emoji_id: str) -> None:
636
637
  """向特定消息添加表态。
637
638
 
638
639
  Args:
639
640
  channel_id (str): 频道 ID
640
641
  message_id (str): 消息 ID
641
- emoji (str): 表态名称
642
+ emoji_id (str): 表情 ID
642
643
 
643
644
  Returns:
644
645
  None: 该方法无返回值
645
646
  """
646
647
  await self.call_api(
647
648
  Api.REACTION_CREATE,
648
- {"channel_id": channel_id, "message_id": message_id, "emoji": emoji},
649
+ {"channel_id": channel_id, "message_id": message_id, "emoji_id": emoji_id},
649
650
  )
650
651
 
651
- async def reaction_delete(self, channel_id: str, message_id: str, emoji: str, user_id: str | None = None) -> None:
652
+ async def reaction_delete(
653
+ self, channel_id: str, message_id: str, emoji_id: str, user_id: str | None = None
654
+ ) -> None:
652
655
  """从特定消息删除某个用户添加的特定表态。
653
656
 
654
657
  如果没有传入用户 ID 则表示删除自己的表态。
@@ -656,13 +659,13 @@ class ApiProtocol:
656
659
  Args:
657
660
  channel_id (str): 频道 ID
658
661
  message_id (str): 消息 ID
659
- emoji (str): 表态名称
662
+ emoji_id (str): 表情 ID
660
663
  user_id (str | None, optional): 用户 ID,默认为 None
661
664
 
662
665
  Returns:
663
666
  None: 该方法无返回值
664
667
  """
665
- data = {"channel_id": channel_id, "message_id": message_id, "emoji": emoji}
668
+ data = {"channel_id": channel_id, "message_id": message_id, "emoji_id": emoji_id}
666
669
  if user_id is not None:
667
670
  data["user_id"] = user_id
668
671
  await self.call_api(
@@ -670,7 +673,7 @@ class ApiProtocol:
670
673
  data,
671
674
  )
672
675
 
673
- async def reaction_clear(self, channel_id: str, message_id: str, emoji: str | None = None) -> None:
676
+ async def reaction_clear(self, channel_id: str, message_id: str, emoji_id: str | None = None) -> None:
674
677
  """从特定消息清除某个特定表态。
675
678
 
676
679
  如果没有传入表态名称则表示清除所有表态。
@@ -678,28 +681,28 @@ class ApiProtocol:
678
681
  Args:
679
682
  channel_id (str): 频道 ID
680
683
  message_id (str): 消息 ID
681
- emoji (str | None, optional): 表态名称,默认为 None
684
+ emoji_id (str | None, optional): 表情 ID,默认为 None
682
685
 
683
686
  Returns:
684
687
  None: 该方法无返回值
685
688
  """
686
689
  data = {"channel_id": channel_id, "message_id": message_id}
687
- if emoji is not None:
688
- data["emoji"] = emoji
690
+ if emoji_id is not None:
691
+ data["emoji_id"] = emoji_id
689
692
  await self.call_api(
690
693
  Api.REACTION_CLEAR,
691
694
  data,
692
695
  )
693
696
 
694
697
  def reaction_list(
695
- self, channel_id: str, message_id: str, emoji: str, next_token: str | None = None
698
+ self, channel_id: str, message_id: str, emoji_id: str, next_token: str | None = None
696
699
  ) -> IterablePageResult[User]:
697
700
  """获取添加特定消息的特定表态的用户列表。返回一个 User 的分页列表。
698
701
 
699
702
  Args:
700
703
  channel_id (str): 频道 ID
701
704
  message_id (str): 消息 ID
702
- emoji (str): 表态名称
705
+ emoji_id (str): 表情 ID
703
706
  next_token (str | None, optional): 分页令牌,默认为空
704
707
 
705
708
  Returns:
@@ -712,7 +715,7 @@ class ApiProtocol:
712
715
  {
713
716
  "channel_id": channel_id,
714
717
  "message_id": message_id,
715
- "emoji": emoji,
718
+ "emoji_id": emoji_id,
716
719
  "next": token,
717
720
  },
718
721
  )
@@ -729,34 +732,33 @@ class ApiProtocol:
729
732
  res = await self.call_api(Api.LOGIN_GET, {})
730
733
  return Login.parse(res)
731
734
 
732
- async def user_get(self, user_id: str) -> User:
733
- """获取用户信息。返回一个 `User` 对象。
734
-
735
- Args:
736
- user_id (str): 用户 ID
737
-
738
- Returns:
739
- User: `User` 对象
740
- """
741
- res = await self.call_api(Api.USER_GET, {"user_id": user_id})
742
- return User.parse(res)
743
-
744
- def friend_list(self, next_token: str | None = None) -> IterablePageResult[User]:
735
+ def friend_list(self, next_token: str | None = None) -> IterablePageResult[Friend]:
745
736
  """获取好友列表。返回一个 User 的分页列表。
746
737
 
747
738
  Args:
748
739
  next_token (str | None, optional): 分页令牌,默认为空
749
740
 
750
741
  Returns:
751
- IterablePageResult[User]: `User` 的分页列表
742
+ IterablePageResult[Friend]: `Friend` 的分页列表
752
743
  """
753
744
 
754
745
  async def _(token: str | None):
755
746
  res = await self.call_api(Api.FRIEND_LIST, {"next": token})
756
- return PageResult.parse(res, User.parse)
747
+ return PageResult.parse(res, Friend.parse)
757
748
 
758
749
  return IterablePageResult(_, next_token)
759
750
 
751
+ async def friend_delete(self, user_id: str) -> None:
752
+ """删除好友。
753
+
754
+ Args:
755
+ user_id (str): 用户 ID
756
+
757
+ Returns:
758
+ None: 该方法无返回值
759
+ """
760
+ await self.call_api(Api.FRIEND_DELETE, {"user_id": user_id})
761
+
760
762
  async def friend_approve(self, request_id: str, approve: bool, comment: str) -> None:
761
763
  """处理好友申请。
762
764
 
@@ -773,6 +775,18 @@ class ApiProtocol:
773
775
  {"message_id": request_id, "approve": approve, "comment": comment},
774
776
  )
775
777
 
778
+ async def user_get(self, user_id: str) -> User:
779
+ """获取用户信息。返回一个 `User` 对象。
780
+
781
+ Args:
782
+ user_id (str): 用户 ID
783
+
784
+ Returns:
785
+ User: `User` 对象
786
+ """
787
+ res = await self.call_api(Api.USER_GET, {"user_id": user_id})
788
+ return User.parse(res)
789
+
776
790
  async def internal(self, action: str, method: str = "POST", **kwargs) -> Any:
777
791
  """内部接口调用。
778
792
 
@@ -40,10 +40,11 @@ class Api(str, Enum):
40
40
 
41
41
  LOGIN_GET = "login.get"
42
42
 
43
- USER_GET = "user.get"
44
43
  FRIEND_LIST = "friend.list"
44
+ FRIEND_DELETE = "friend.delete"
45
45
  FRIEND_APPROVE = "friend.approve"
46
46
 
47
+ USER_GET = "user.get"
47
48
  UPLOAD_CREATE = "upload.create"
48
49
 
49
50
 
@@ -65,6 +66,9 @@ class EventType(str, Enum):
65
66
  GUILD_ROLE_CREATED = "guild-role-created"
66
67
  GUILD_ROLE_DELETED = "guild-role-deleted"
67
68
  GUILD_ROLE_UPDATED = "guild-role-updated"
69
+ GUILD_EMOJI_ADDED = "guild-emoji-added"
70
+ GUILD_EMOJI_REMOVED = "guild-emoji-removed"
71
+ GUILD_EMOJI_UPDATED = "guild-emoji-updated"
68
72
  LOGIN_ADDED = "login-added"
69
73
  LOGIN_REMOVED = "login-removed"
70
74
  LOGIN_UPDATED = "login-updated"
@@ -5,7 +5,7 @@ from io import BytesIO
5
5
  from pathlib import Path
6
6
  from types import UnionType
7
7
  from typing import Any, ClassVar, Final, TypeVar, Union, final, get_args, get_origin, overload
8
- from typing_extensions import override
8
+ from typing_extensions import Self, override
9
9
 
10
10
  from ._vendor.fleep import get
11
11
  from .parser import Element as RawElement
@@ -157,6 +157,19 @@ class At(Element):
157
157
  return At(type="here" if here else "all")
158
158
 
159
159
 
160
+ @dataclass(repr=False)
161
+ class Emoji(Element):
162
+ """<emoji> 元素用于表示一个表情。"""
163
+
164
+ id: str
165
+ name: str | None = None
166
+
167
+ def to_model(self):
168
+ from .model import EmojiObject
169
+
170
+ return EmojiObject(self.id, self.name)
171
+
172
+
160
173
  @dataclass(repr=False)
161
174
  class Sharp(Element):
162
175
  """<sharp> 元素用于提及某个频道。"""
@@ -200,7 +213,7 @@ class Resource(Element):
200
213
 
201
214
  @classmethod
202
215
  def of(
203
- cls,
216
+ cls: type[Self],
204
217
  url: str | None = None,
205
218
  path: str | Path | None = None,
206
219
  raw: bytes | BytesIO | None = None,
@@ -212,7 +225,7 @@ class Resource(Element):
212
225
  cache: bool | None = None,
213
226
  timeout: int | None = None,
214
227
  **kwargs,
215
- ):
228
+ ) -> Self:
216
229
  data: dict[str, Any] = {"extra": extra, **kwargs}
217
230
  if url is not None:
218
231
  data |= {"src": url}
@@ -537,6 +550,7 @@ def register_element(cls: type[TE], tag: str | None = None) -> type[TE]:
537
550
  ELEMENT_TYPE_MAP = {
538
551
  "text": Text,
539
552
  "at": At,
553
+ "emoji": Emoji,
540
554
  "sharp": Sharp,
541
555
  "img": Image,
542
556
  "image": Image,
@@ -637,6 +651,7 @@ class _E:
637
651
  self.at = At
638
652
  self.at_role = At.role_
639
653
  self.at_all = At.all
654
+ self.emoji = Emoji
640
655
  self.sharp = Sharp
641
656
  self.link = Link
642
657
  self.image = Image.of
@@ -2,6 +2,7 @@ from satori.model import (
2
2
  ArgvInteraction,
3
3
  ButtonInteraction,
4
4
  Channel,
5
+ EmojiObject,
5
6
  Event,
6
7
  Guild,
7
8
  LoginPartial,
@@ -40,6 +41,10 @@ class GuildRoleEvent(GuildEvent):
40
41
  role: Role
41
42
 
42
43
 
44
+ class GuildEmojiEvent(GuildEvent):
45
+ emoji: EmojiObject
46
+
47
+
43
48
  class LoginEvent(Event):
44
49
  login: LoginPartial
45
50
 
@@ -48,6 +53,7 @@ class ReactionEvent(Event):
48
53
  channel: Channel
49
54
  user: User
50
55
  message: MessageObject
56
+ emoji: EmojiObject
51
57
 
52
58
 
53
59
  class ButtonInteractionEvent(Event):
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
  from typing import IO, Any, ClassVar, Generic, Literal, TypeAlias, TypeVar
9
9
  from typing_extensions import Self
10
10
 
11
- from .element import Element, transform
11
+ from .element import Element, Emoji, transform
12
12
  from .parser import Element as RawElement
13
13
  from .parser import parse
14
14
 
@@ -98,13 +98,15 @@ class User(ModelBase):
98
98
 
99
99
 
100
100
  @dataclass
101
- class Member(ModelBase):
101
+ class Friend(ModelBase):
102
102
  user: User | None = None
103
103
  nick: str | None = None
104
- avatar: str | None = None
105
- joined_at: datetime | None = None
106
104
 
107
- __converter__ = {"user": User.parse, "joined_at": lambda ts: datetime.fromtimestamp(int(ts) / 1000)}
105
+ @property
106
+ def remark(self) -> str | None:
107
+ return self.nick
108
+
109
+ __converter__ = {"user": User.parse}
108
110
 
109
111
  def dump(self):
110
112
  res = {}
@@ -112,10 +114,6 @@ class Member(ModelBase):
112
114
  res["user"] = self.user.dump()
113
115
  if self.nick:
114
116
  res["nick"] = self.nick
115
- if self.avatar:
116
- res["avatar"] = self.avatar
117
- if self.joined_at:
118
- res["joined_at"] = int(self.joined_at.timestamp() * 1000)
119
117
  return res
120
118
 
121
119
 
@@ -131,6 +129,35 @@ class Role(ModelBase):
131
129
  return res
132
130
 
133
131
 
132
+ @dataclass
133
+ class Member(ModelBase):
134
+ user: User | None = None
135
+ nick: str | None = None
136
+ avatar: str | None = None
137
+ joined_at: datetime | None = None
138
+ roles: list[Role] = field(default_factory=list)
139
+
140
+ __converter__ = {
141
+ "user": User.parse,
142
+ "joined_at": lambda ts: datetime.fromtimestamp(int(ts) / 1000),
143
+ "roles": lambda raw: [Role.parse(role) for role in raw],
144
+ } # noqa: E501
145
+
146
+ def dump(self):
147
+ res = {}
148
+ if self.user:
149
+ res["user"] = self.user.dump()
150
+ if self.nick:
151
+ res["nick"] = self.nick
152
+ if self.avatar:
153
+ res["avatar"] = self.avatar
154
+ if self.joined_at:
155
+ res["joined_at"] = int(self.joined_at.timestamp() * 1000)
156
+ if self.roles:
157
+ res["roles"] = [role.dump() for role in self.roles]
158
+ return res
159
+
160
+
134
161
  class LoginStatus(IntEnum):
135
162
  OFFLINE = 0
136
163
  """离线"""
@@ -282,6 +309,21 @@ class Meta(ModelBase):
282
309
  return {"logins": [login.dump() for login in self.logins], "proxy_urls": self.proxy_urls}
283
310
 
284
311
 
312
+ @dataclass
313
+ class EmojiObject(ModelBase):
314
+ id: str
315
+ name: str | None = None
316
+
317
+ def dump(self):
318
+ res = {"id": self.id}
319
+ if self.name:
320
+ res["name"] = self.name
321
+ return res
322
+
323
+ def to_element(self) -> Emoji:
324
+ return Emoji(self.id, self.name)
325
+
326
+
285
327
  @dataclass
286
328
  class MessageObject(ModelBase):
287
329
  id: str
@@ -373,6 +415,7 @@ class Event(ModelBase):
373
415
  role: Role | None = None
374
416
  user: User | None = None
375
417
  referrer: dict | None = None
418
+ emoji: EmojiObject | None = None
376
419
 
377
420
  _type: str | None = None
378
421
  _data: dict | None = None
@@ -391,6 +434,7 @@ class Event(ModelBase):
391
434
  "operator": User.parse,
392
435
  "role": Role.parse,
393
436
  "user": User.parse,
437
+ "emoji": EmojiObject.parse,
394
438
  }
395
439
 
396
440
  @classmethod
@@ -447,6 +491,8 @@ class Event(ModelBase):
447
491
  res["user"] = self.user.dump()
448
492
  if self.referrer:
449
493
  res["referrer"] = self.referrer
494
+ if self.emoji:
495
+ res["emoji"] = self.emoji.dump()
450
496
  if self._type:
451
497
  res["_type"] = self._type
452
498
  if self._data:
@@ -97,6 +97,10 @@ async def _request_handler(action: str, request: StarletteRequest, func: RouteCa
97
97
  self_id=self_id,
98
98
  )
99
99
  )
100
+ except asyncio.CancelledError:
101
+ return Response(status_code=503, content="Request cancelled")
102
+ except asyncio.TimeoutError:
103
+ return Response(status_code=504, content="Request timeout")
100
104
  except ActionFailed as ae:
101
105
  logger.warning(ae)
102
106
  return Response(status_code=ae.CODE, content=str(ae))
@@ -7,6 +7,7 @@ from starlette.datastructures import FormData
7
7
  from satori.model import (
8
8
  Channel,
9
9
  Direction,
10
+ Friend,
10
11
  Guild,
11
12
  Login,
12
13
  Member,
@@ -207,7 +208,7 @@ GUILD_ROLE_DELETE: TypeAlias = RouteCall[GuildRoleDeleteParam, None]
207
208
  class ReactionCreateParam(TypedDict):
208
209
  channel_id: str
209
210
  message_id: str
210
- emoji: str
211
+ emoji_id: str
211
212
 
212
213
 
213
214
  REACTION_CREATE: TypeAlias = RouteCall[ReactionCreateParam, None]
@@ -216,7 +217,7 @@ REACTION_CREATE: TypeAlias = RouteCall[ReactionCreateParam, None]
216
217
  class ReactionDeleteParam(TypedDict):
217
218
  channel_id: str
218
219
  message_id: str
219
- emoji: str
220
+ emoji_id: str
220
221
  user_id: NotRequired[str]
221
222
 
222
223
 
@@ -226,7 +227,7 @@ REACTION_DELETE: TypeAlias = RouteCall[ReactionDeleteParam, None]
226
227
  class ReactionClearParam(TypedDict):
227
228
  channel_id: str
228
229
  message_id: str
229
- emoji: NotRequired[str]
230
+ emoji_id: NotRequired[str]
230
231
 
231
232
 
232
233
  REACTION_CLEAR: TypeAlias = RouteCall[ReactionClearParam, None]
@@ -235,7 +236,7 @@ REACTION_CLEAR: TypeAlias = RouteCall[ReactionClearParam, None]
235
236
  class ReactionListParam(TypedDict):
236
237
  channel_id: str
237
238
  message_id: str
238
- emoji: str
239
+ emoji_id: str
239
240
  next: NotRequired[str]
240
241
 
241
242
 
@@ -243,18 +244,19 @@ REACTION_LIST: TypeAlias = RouteCall[ReactionListParam, PageResult[User] | dict[
243
244
  LOGIN_GET: TypeAlias = RouteCall[Any, Login | dict[str, Any]]
244
245
 
245
246
 
246
- class UserGetParam(TypedDict):
247
+ class UserOpParam(TypedDict):
247
248
  user_id: str
248
249
 
249
250
 
250
- USER_GET: TypeAlias = RouteCall[UserGetParam, User | dict[str, Any]]
251
+ USER_GET: TypeAlias = RouteCall[UserOpParam, User | dict[str, Any]]
252
+ FRIEND_DELETE: TypeAlias = RouteCall[UserOpParam, None]
251
253
 
252
254
 
253
255
  class FriendListParam(TypedDict):
254
256
  next: NotRequired[str]
255
257
 
256
258
 
257
- FRIEND_LIST: TypeAlias = RouteCall[FriendListParam, PageResult[User] | dict[str, Any]]
259
+ FRIEND_LIST: TypeAlias = RouteCall[FriendListParam, PageResult[Friend] | dict[str, Any]]
258
260
 
259
261
 
260
262
  class ApproveParam(TypedDict):
File without changes