satori-python-client 0.11.4__tar.gz → 0.13.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.
@@ -10,6 +10,7 @@ authors = [
10
10
  dependencies = [
11
11
  "aiohttp",
12
12
  "launart",
13
+ "graia-amnesia",
13
14
  ]
14
15
  description = "Satori Protocol SDK for python, specify client part"
15
16
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python-client
3
- Version: 0.11.4
3
+ Version: 0.13.0
4
4
  Summary: Satori Protocol SDK for python, specify client part
5
5
  Home-page: https://github.com/RF-Tar-Railt/satori-python
6
6
  Author-Email: RF-Tar-Railt <rf_tar_railt@qq.com>
@@ -19,6 +19,7 @@ Project-URL: Repository, https://github.com/RF-Tar-Railt/satori-python
19
19
  Requires-Python: >=3.8
20
20
  Requires-Dist: aiohttp>=3.9.3
21
21
  Requires-Dist: launart>=0.8.2
22
+ Requires-Dist: graia-amnesia>=0.9.0
22
23
  Requires-Dist: satori-python-core>=0.11.4
23
24
  Description-Content-Type: text/markdown
24
25
 
@@ -42,6 +43,10 @@ Description-Content-Type: text/markdown
42
43
  - [Chronocat](https://chronocat.vercel.app)
43
44
  - Koishi (搭配 `@koishijs/plugin-server`)
44
45
 
46
+ ### 使用该 SDK 的框架
47
+
48
+ - [`Entari`](https://github.com/ArcletProject/Entari)
49
+
45
50
  ## 安装
46
51
 
47
52
  安装完整体:
@@ -69,14 +74,15 @@ pip install satori-python-server
69
74
  客户端:
70
75
 
71
76
  ```python
72
- from satori import Event, WebsocketsInfo
77
+ from satori import EventType, WebsocketsInfo
78
+ from satori.event import MessageEvent
73
79
  from satori.client import Account, App
74
80
 
75
81
  app = App(WebsocketsInfo(port=5140))
76
82
 
77
- @app.register
78
- async def on_message(account: Account, event: Event):
79
- if event.user and event.user.id == "xxxxxxxxxxx":
83
+ @app.register_on(EventType.MESSAGE_CREATED)
84
+ async def on_message(account: Account, event: MessageEvent):
85
+ if event.user.id == "xxxxxxxxxxx":
80
86
  await account.send(event, "Hello, World!")
81
87
 
82
88
  app.run()
@@ -18,6 +18,10 @@
18
18
  - [Chronocat](https://chronocat.vercel.app)
19
19
  - Koishi (搭配 `@koishijs/plugin-server`)
20
20
 
21
+ ### 使用该 SDK 的框架
22
+
23
+ - [`Entari`](https://github.com/ArcletProject/Entari)
24
+
21
25
  ## 安装
22
26
 
23
27
  安装完整体:
@@ -45,14 +49,15 @@ pip install satori-python-server
45
49
  客户端:
46
50
 
47
51
  ```python
48
- from satori import Event, WebsocketsInfo
52
+ from satori import EventType, WebsocketsInfo
53
+ from satori.event import MessageEvent
49
54
  from satori.client import Account, App
50
55
 
51
56
  app = App(WebsocketsInfo(port=5140))
52
57
 
53
- @app.register
54
- async def on_message(account: Account, event: Event):
55
- if event.user and event.user.id == "xxxxxxxxxxx":
58
+ @app.register_on(EventType.MESSAGE_CREATED)
59
+ async def on_message(account: Account, event: MessageEvent):
60
+ if event.user.id == "xxxxxxxxxxx":
56
61
  await account.send(event, "Hello, World!")
57
62
 
58
63
  app.run()
@@ -7,6 +7,7 @@ authors = [
7
7
  dependencies = [
8
8
  "aiohttp>=3.9.3",
9
9
  "launart>=0.8.2",
10
+ "graia-amnesia>=0.9.0",
10
11
  "satori-python-core >= 0.11.4",
11
12
  ]
12
13
  description = "Satori Protocol SDK for python, specify client part"
@@ -23,7 +24,7 @@ classifiers = [
23
24
  "Programming Language :: Python :: 3.12",
24
25
  "Operating System :: OS Independent",
25
26
  ]
26
- version = "0.11.4"
27
+ version = "0.13.0"
27
28
 
28
29
  [project.license]
29
30
  text = "MIT"
@@ -41,9 +42,9 @@ build-backend = "mina.backend"
41
42
  [tool.pdm.dev-dependencies]
42
43
  dev = [
43
44
  "isort>=5.13.2",
44
- "black>=24.2.0",
45
- "ruff>=0.3.2",
46
- "pre-commit>=3.6.2",
45
+ "black>=24.4.0",
46
+ "ruff>=0.4.1",
47
+ "pre-commit>=3.7.0",
47
48
  "fix-future-annotations>=0.5.0",
48
49
  "mina-build<0.6,>=0.5.1",
49
50
  "pdm-mina>=0.3.2",
@@ -5,13 +5,14 @@ import functools
5
5
  import signal
6
6
  import threading
7
7
  from functools import wraps
8
- from typing import Any, Awaitable, Callable, Iterable, Literal, TypeVar, overload
8
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Iterable, Literal, TypeVar, overload
9
9
 
10
10
  from creart import it
11
+ from graia.amnesia.builtins.aiohttp import AiohttpClientService
11
12
  from launart import Launart, Service, any_completed
12
13
  from loguru import logger
13
14
 
14
- from satori import event
15
+ from satori import event as events
15
16
  from satori.config import Config, WebhookInfo, WebsocketsInfo
16
17
  from satori.const import EventType
17
18
  from satori.model import Event, LoginStatus
@@ -33,7 +34,7 @@ MAPPING: dict[type[Config], type[BaseNetwork]] = {
33
34
 
34
35
  class App(Service):
35
36
  id = "satori-python.client"
36
- required: set[str] = set()
37
+ required: set[str] = {"http.client/aiohttp"}
37
38
  stages: set[str] = {"preparing", "blocking", "cleanup"}
38
39
 
39
40
  accounts: dict[str, Account]
@@ -69,8 +70,8 @@ class App(Service):
69
70
 
70
71
  @overload
71
72
  def register_on(self, event_type: Literal[EventType.FRIEND_REQUEST]) -> Callable[
72
- [Callable[[Account, event.UserEvent], Awaitable[Any]]],
73
- Callable[[Account, event.UserEvent], Awaitable[Any]],
73
+ [Callable[[Account, events.UserEvent], Awaitable[Any]]],
74
+ Callable[[Account, events.UserEvent], Awaitable[Any]],
74
75
  ]: ...
75
76
 
76
77
  @overload
@@ -80,8 +81,8 @@ class App(Service):
80
81
  EventType.GUILD_ADDED, EventType.GUILD_REMOVED, EventType.GUILD_REQUEST, EventType.GUILD_UPDATED
81
82
  ],
82
83
  ) -> Callable[
83
- [Callable[[Account, event.GuildEvent], Awaitable[Any]]],
84
- Callable[[Account, event.GuildEvent], Awaitable[Any]],
84
+ [Callable[[Account, events.GuildEvent], Awaitable[Any]]],
85
+ Callable[[Account, events.GuildEvent], Awaitable[Any]],
85
86
  ]: ...
86
87
 
87
88
  @overload
@@ -94,8 +95,8 @@ class App(Service):
94
95
  EventType.GUILD_MEMBER_REQUEST,
95
96
  ],
96
97
  ) -> Callable[
97
- [Callable[[Account, event.GuildMemberEvent], Awaitable[Any]]],
98
- Callable[[Account, event.GuildMemberEvent], Awaitable[Any]],
98
+ [Callable[[Account, events.GuildMemberEvent], Awaitable[Any]]],
99
+ Callable[[Account, events.GuildMemberEvent], Awaitable[Any]],
99
100
  ]: ...
100
101
 
101
102
  @overload
@@ -105,16 +106,16 @@ class App(Service):
105
106
  EventType.GUILD_ROLE_CREATED, EventType.GUILD_ROLE_DELETED, EventType.GUILD_ROLE_UPDATED
106
107
  ],
107
108
  ) -> Callable[
108
- [Callable[[Account, event.GuildRoleEvent], Awaitable[Any]]],
109
- Callable[[Account, event.GuildRoleEvent], Awaitable[Any]],
109
+ [Callable[[Account, events.GuildRoleEvent], Awaitable[Any]]],
110
+ Callable[[Account, events.GuildRoleEvent], Awaitable[Any]],
110
111
  ]: ...
111
112
 
112
113
  @overload
113
114
  def register_on(
114
115
  self, event_type: Literal[EventType.LOGIN_ADDED, EventType.LOGIN_REMOVED, EventType.LOGIN_UPDATED]
115
116
  ) -> Callable[
116
- [Callable[[Account, event.LoginEvent], Awaitable[Any]]],
117
- Callable[[Account, event.LoginEvent], Awaitable[Any]],
117
+ [Callable[[Account, events.LoginEvent], Awaitable[Any]]],
118
+ Callable[[Account, events.LoginEvent], Awaitable[Any]],
118
119
  ]: ...
119
120
 
120
121
  @overload
@@ -122,34 +123,34 @@ class App(Service):
122
123
  self,
123
124
  event_type: Literal[EventType.MESSAGE_CREATED, EventType.MESSAGE_DELETED, EventType.MESSAGE_UPDATED],
124
125
  ) -> Callable[
125
- [Callable[[Account, event.MessageEvent], Awaitable[Any]]],
126
- Callable[[Account, event.MessageEvent], Awaitable[Any]],
126
+ [Callable[[Account, events.MessageEvent], Awaitable[Any]]],
127
+ Callable[[Account, events.MessageEvent], Awaitable[Any]],
127
128
  ]: ...
128
129
 
129
130
  @overload
130
131
  def register_on(
131
132
  self, event_type: Literal[EventType.REACTION_ADDED, EventType.REACTION_REMOVED]
132
133
  ) -> Callable[
133
- [Callable[[Account, event.ReactionEvent], Awaitable[Any]]],
134
- Callable[[Account, event.ReactionEvent], Awaitable[Any]],
134
+ [Callable[[Account, events.ReactionEvent], Awaitable[Any]]],
135
+ Callable[[Account, events.ReactionEvent], Awaitable[Any]],
135
136
  ]: ...
136
137
 
137
138
  @overload
138
139
  def register_on(self, event_type: Literal[EventType.INTERACTION_BUTTON]) -> Callable[
139
- [Callable[[Account, event.ButtonInteractionEvent], Awaitable[Any]]],
140
- Callable[[Account, event.ButtonInteractionEvent], Awaitable[Any]],
140
+ [Callable[[Account, events.ButtonInteractionEvent], Awaitable[Any]]],
141
+ Callable[[Account, events.ButtonInteractionEvent], Awaitable[Any]],
141
142
  ]: ...
142
143
 
143
144
  @overload
144
145
  def register_on(self, event_type: Literal[EventType.INTERACTION_COMMAND]) -> Callable[
145
- [Callable[[Account, event.ArgvInteractionEvent | event.MessageEvent], Awaitable[Any]]],
146
- Callable[[Account, event.ArgvInteractionEvent | event.MessageEvent], Awaitable[Any]],
146
+ [Callable[[Account, events.ArgvInteractionEvent | events.MessageEvent], Awaitable[Any]]],
147
+ Callable[[Account, events.ArgvInteractionEvent | events.MessageEvent], Awaitable[Any]],
147
148
  ]: ...
148
149
 
149
150
  @overload
150
151
  def register_on(self, event_type: Literal[EventType.INTERNAL]) -> Callable[
151
- [Callable[[Account, event.InternalEvent], Awaitable[Any]]],
152
- Callable[[Account, event.InternalEvent], Awaitable[Any]],
152
+ [Callable[[Account, events.InternalEvent], Awaitable[Any]]],
153
+ Callable[[Account, events.InternalEvent], Awaitable[Any]],
153
154
  ]: ...
154
155
 
155
156
  @overload
@@ -180,16 +181,56 @@ class App(Service):
180
181
  if self.lifecycle_callbacks:
181
182
  await asyncio.gather(*(callback(account, state) for callback in self.lifecycle_callbacks))
182
183
 
183
- async def post(self, event: Event):
184
+ async def post(self, event: Event, conn: BaseNetwork):
184
185
  if not self.event_callbacks:
185
186
  return
186
187
  identity = f"{event.platform}/{event.self_id}"
187
188
  if identity not in self.accounts:
188
- logger.warning(f"Received event for unknown account: {event}")
189
- return
190
- account = self.accounts[identity]
189
+ if event.type == EventType.LOGIN_ADDED:
190
+ if TYPE_CHECKING:
191
+ assert isinstance(event, events.LoginEvent)
192
+ account = Account(
193
+ event.platform,
194
+ event.self_id,
195
+ event.login,
196
+ conn.config,
197
+ )
198
+ logger.info(f"account added: {account}")
199
+ (
200
+ account.connected.set()
201
+ if event.login.status == LoginStatus.ONLINE
202
+ else account.connected.clear()
203
+ )
204
+ self.accounts[identity] = account
205
+ conn.accounts[identity] = account
206
+ await self.account_update(account, LoginStatus.ONLINE)
207
+ await self.account_update(account, LoginStatus.CONNECT)
208
+ else:
209
+ logger.warning(f"Received event for unknown account: {event}")
210
+ return
211
+ else:
212
+ account = self.accounts[identity]
213
+ if event.type == EventType.LOGIN_UPDATED:
214
+ if TYPE_CHECKING:
215
+ assert isinstance(event, events.LoginEvent)
216
+ logger.info(f"account updated: {account}")
217
+ (
218
+ account.connected.set()
219
+ if event.login.status in (LoginStatus.ONLINE, LoginStatus.CONNECT)
220
+ else account.connected.clear()
221
+ )
222
+
191
223
  await asyncio.gather(*(callback(account, event) for callback in self.event_callbacks))
192
224
 
225
+ if event.type == EventType.LOGIN_REMOVED:
226
+ if TYPE_CHECKING:
227
+ assert isinstance(event, events.LoginEvent)
228
+ logger.info(f"account removed: {account}")
229
+ account.connected.clear()
230
+ await self.account_update(account, LoginStatus.DISCONNECT)
231
+ del self.accounts[identity]
232
+ del conn.accounts[identity]
233
+
193
234
  async def launch(self, manager: Launart):
194
235
  for conn in self.connections:
195
236
  manager.add_component(conn)
@@ -217,6 +258,7 @@ class App(Service):
217
258
  ):
218
259
  if manager is None:
219
260
  manager = it(Launart)
261
+ manager.add_component(AiohttpClientService())
220
262
  manager.add_component(self)
221
263
  manager.launch_blocking(loop=loop, stop_signal=stop_signal)
222
264
 
@@ -227,6 +269,7 @@ class App(Service):
227
269
  ):
228
270
  if manager is None:
229
271
  manager = it(Launart)
272
+ manager.add_component(AiohttpClientService())
230
273
  manager.add_component(self)
231
274
  handled_signals: dict[signal.Signals, Any] = {}
232
275
  launch_task = asyncio.create_task(manager.launch(), name="amnesia-launch")
@@ -6,9 +6,11 @@ from typing import TypeVar
6
6
 
7
7
  from yarl import URL
8
8
 
9
- from .session import Session
9
+ from satori.model import Login
10
10
 
11
- TS = TypeVar("TS", bound="Session")
11
+ from .protocol import ApiProtocol
12
+
13
+ TP = TypeVar("TP", bound="ApiProtocol")
12
14
 
13
15
 
14
16
  @dataclass
@@ -28,15 +30,29 @@ class ApiInfo:
28
30
 
29
31
 
30
32
  class Account:
31
- def __init__(self, platform: str, self_id: str, config: ApiInfo, session_cls: type[Session] = Session):
33
+ def __init__(
34
+ self,
35
+ platform: str,
36
+ self_id: str,
37
+ self_info: Login,
38
+ config: ApiInfo,
39
+ protocol_cls: type[ApiProtocol] = ApiProtocol,
40
+ ):
32
41
  self.platform = platform
33
42
  self.self_id = self_id
43
+ self.self_info = self_info
34
44
  self.config = config
35
- self.session = session_cls(self) # type: ignore
45
+ self.protocol = protocol_cls(self) # type: ignore
36
46
  self.connected = asyncio.Event()
37
47
 
38
- def custom(self, config: ApiInfo | None = None, session_cls: type[TS] = Session, **kwargs) -> TS:
39
- return Account(self.platform, self.self_id, config or ApiInfo(**kwargs), session_cls).session # type: ignore
48
+ def custom(self, config: ApiInfo | None = None, protocol_cls: type[TP] = ApiProtocol, **kwargs) -> TP:
49
+ return Account(
50
+ self.platform,
51
+ self.self_id,
52
+ self.self_info,
53
+ config or ApiInfo(**kwargs),
54
+ protocol_cls, # type: ignore
55
+ ).protocol
40
56
 
41
57
  @property
42
58
  def identity(self):
@@ -46,4 +62,4 @@ class Account:
46
62
  return f"<Account {self.self_id} ({self.platform})>"
47
63
 
48
64
  def __getattr__(self, item):
49
- return getattr(self.session, item)
65
+ return getattr(self.protocol, item)