satori-python-core 0.15.2__tar.gz → 0.16.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.
@@ -15,7 +15,7 @@ dependencies = [
15
15
  description = "Satori Protocol SDK for python, specify common part"
16
16
  license = {text = "MIT"}
17
17
  readme = "README.md"
18
- requires-python = ">=3.8"
18
+ requires-python = ">=3.9"
19
19
  classifiers = [
20
20
  "Typing :: Typed",
21
21
  "Development Status :: 4 - Beta",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python-core
3
- Version: 0.15.2
3
+ Version: 0.16.0
4
4
  Summary: Satori Protocol SDK for python, specify common 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>
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Operating System :: OS Independent
17
17
  Project-URL: Homepage, https://github.com/RF-Tar-Railt/satori-python
18
18
  Project-URL: Repository, https://github.com/RF-Tar-Railt/satori-python
19
- Requires-Python: >=3.8
19
+ Requires-Python: >=3.9
20
20
  Requires-Dist: loguru>=0.7.2
21
21
  Requires-Dist: yarl>=1.9.4
22
22
  Requires-Dist: typing-extensions>=4.7.0
@@ -74,9 +74,9 @@ pip install satori-python-server
74
74
  客户端:
75
75
 
76
76
  ```python
77
- from satori import EventType, WebsocketsInfo
77
+ from satori import EventType
78
78
  from satori.event import MessageEvent
79
- from satori.client import Account, App
79
+ from satori.client import Account, App, WebsocketsInfo
80
80
 
81
81
  app = App(WebsocketsInfo(port=5140))
82
82
 
@@ -50,9 +50,9 @@ pip install satori-python-server
50
50
  客户端:
51
51
 
52
52
  ```python
53
- from satori import EventType, WebsocketsInfo
53
+ from satori import EventType
54
54
  from satori.event import MessageEvent
55
- from satori.client import Account, App
55
+ from satori.client import Account, App, WebsocketsInfo
56
56
 
57
57
  app = App(WebsocketsInfo(port=5140))
58
58
 
@@ -11,7 +11,7 @@ dependencies = [
11
11
  ]
12
12
  description = "Satori Protocol SDK for python, specify common part"
13
13
  readme = "README.md"
14
- requires-python = ">=3.8"
14
+ requires-python = ">=3.9"
15
15
  classifiers = [
16
16
  "Typing :: Typed",
17
17
  "Development Status :: 4 - Beta",
@@ -23,7 +23,7 @@ classifiers = [
23
23
  "Programming Language :: Python :: 3.12",
24
24
  "Operating System :: OS Independent",
25
25
  ]
26
- version = "0.15.2"
26
+ version = "0.16.0"
27
27
 
28
28
  [project.license]
29
29
  text = "MIT"
@@ -1,5 +1,3 @@
1
- from .config import WebhookInfo as WebhookInfo
2
- from .config import WebsocketsInfo as WebsocketsInfo
3
1
  from .const import Api as Api
4
2
  from .const import EventType as EventType
5
3
  from .element import At as At
@@ -43,4 +41,4 @@ from .model import Role as Role
43
41
  from .model import Upload as Upload
44
42
  from .model import User as User
45
43
 
46
- __version__ = "0.15.2"
44
+ __version__ = "0.16.0"
@@ -15,6 +15,7 @@ from .parser import parse
15
15
  @dataclass
16
16
  class ModelBase:
17
17
  __converter__: ClassVar[dict[str, Callable[[Any], Any]]] = {}
18
+ _raw_data: dict[str, Any] = field(init=False, default_factory=dict, repr=False, compare=False, hash=False)
18
19
 
19
20
  @classmethod
20
21
  def parse(cls, raw: dict):
@@ -26,7 +27,9 @@ class ModelBase:
26
27
  data[fd.name] = cls.__converter__[fd.name](raw[fd.name])
27
28
  else:
28
29
  data[fd.name] = raw[fd.name]
29
- return cls(**data) # type: ignore
30
+ obj = cls(**data) # type: ignore
31
+ obj._raw_data = raw
32
+ return obj
30
33
 
31
34
  def dump(self) -> dict:
32
35
  raise NotImplementedError
@@ -129,71 +132,57 @@ class Role(ModelBase):
129
132
 
130
133
  class LoginStatus(IntEnum):
131
134
  OFFLINE = 0
135
+ """离线"""
132
136
  ONLINE = 1
137
+ """在线"""
133
138
  CONNECT = 2
139
+ """正在连接"""
134
140
  DISCONNECT = 3
141
+ """正在断开连接"""
135
142
  RECONNECT = 4
143
+ """正在重新连接"""
136
144
 
137
145
 
138
146
  @dataclass
139
147
  class Login(ModelBase):
148
+ sn: str
140
149
  status: LoginStatus
141
- user: Optional[User] = None
142
- self_id: Optional[str] = None
150
+ adapter: str
143
151
  platform: Optional[str] = None
152
+ user: Optional[User] = None
144
153
  features: list[str] = field(default_factory=list)
145
- proxy_urls: list[str] = field(default_factory=list)
146
154
 
147
155
  __converter__ = {"user": User.parse, "status": LoginStatus}
148
156
 
149
157
  def dump(self):
150
158
  res: dict[str, Any] = {
159
+ "sn": self.sn,
151
160
  "status": self.status.value,
152
- "features": self.features,
153
- "proxy_urls": self.proxy_urls,
161
+ "adapter": self.adapter,
154
162
  }
155
- if self.user:
156
- res["user"] = self.user.dump()
157
- if self.self_id:
158
- res["self_id"] = self.self_id
159
163
  if self.platform:
160
164
  res["platform"] = self.platform
165
+ if self.user:
166
+ res["user"] = self.user.dump()
167
+ if self.features:
168
+ res["features"] = self.features
161
169
  return res
162
170
 
163
- @property
164
- def id(self) -> Optional[str]:
165
- return self.self_id or (self.user.id if self.user else None)
166
-
167
-
168
- @dataclass
169
- class LoginPreview(ModelBase):
170
- user: User
171
- platform: str
172
- status: Optional[LoginStatus] = None
173
- features: list[str] = field(default_factory=list)
174
- proxy_urls: list[str] = field(default_factory=list)
175
-
176
- __converter__ = {"user": User.parse, "status": LoginStatus}
177
-
178
- def dump(self):
179
- res: dict[str, Any] = {
180
- "user": self.user.dump(),
181
- "platform": self.platform,
182
- "features": self.features,
183
- "proxy_urls": self.proxy_urls,
184
- }
185
- if self.status:
186
- res["status"] = self.status.value
187
- return res
171
+ @classmethod
172
+ def parse(cls, raw: dict):
173
+ if "self_id" in raw and "user" not in raw:
174
+ raw["user"] = {"id": raw["self_id"]}
175
+ if "adapter" not in raw:
176
+ raw["adapter"] = "satori"
177
+ return super().parse(raw)
188
178
 
189
179
  @property
190
180
  def id(self) -> str:
181
+ if not self.user:
182
+ raise ValueError(f"Login {self.sn} has not complete yet")
191
183
  return self.user.id
192
184
 
193
185
 
194
- LoginType = Union[Login, LoginPreview]
195
-
196
-
197
186
  @dataclass
198
187
  class ArgvInteraction(ModelBase):
199
188
  name: str
@@ -214,21 +203,70 @@ class ButtonInteraction(ModelBase):
214
203
 
215
204
  class Opcode(IntEnum):
216
205
  EVENT = 0
206
+ """事件 (接收)"""
217
207
  PING = 1
208
+ """心跳 (发送)"""
218
209
  PONG = 2
210
+ """心跳回复 (接收)"""
219
211
  IDENTIFY = 3
212
+ """鉴权 (发送)"""
220
213
  READY = 4
214
+ """鉴权成功 (接收)"""
215
+ META = 5
216
+ """元信息更新 (接收)"""
221
217
 
222
218
 
223
219
  @dataclass
224
220
  class Identify(ModelBase):
225
221
  token: Optional[str] = None
226
- sequence: Optional[int] = None
222
+ sn: Optional[int] = None
223
+
224
+ @classmethod
225
+ def parse(cls, raw: dict):
226
+ if "sequence" in raw and "sn" not in raw:
227
+ raw["sn"] = raw["sequence"]
228
+ return super().parse(raw)
229
+
230
+ @property
231
+ def sequence(self) -> Optional[int]:
232
+ return self.sn
233
+
234
+ def dump(self):
235
+ return asdict(self)
227
236
 
228
237
 
229
238
  @dataclass
230
239
  class Ready(ModelBase):
231
240
  logins: list[Login]
241
+ proxy_urls: list[str] = field(default_factory=list)
242
+
243
+ __converter__ = {"logins": lambda raw: [Login.parse(login) for login in raw]}
244
+
245
+ def dump(self):
246
+ return asdict(self)
247
+
248
+
249
+ @dataclass
250
+ class MetaPayload(ModelBase):
251
+ """Meta 信令"""
252
+
253
+ proxy_urls: list[str]
254
+
255
+ def dump(self):
256
+ return asdict(self)
257
+
258
+
259
+ @dataclass
260
+ class Meta(ModelBase):
261
+ """Meta 数据"""
262
+
263
+ logins: list[Login]
264
+ proxy_urls: list[str] = field(default_factory=list)
265
+
266
+ __converter__ = {"logins": lambda raw: [Login.parse(login) for login in raw]}
267
+
268
+ def dump(self):
269
+ return asdict(self)
232
270
 
233
271
 
234
272
  @dataclass
@@ -334,16 +372,13 @@ class MessageReceipt(ModelBase):
334
372
 
335
373
  @dataclass
336
374
  class Event(ModelBase):
337
- id: int
338
375
  type: str
339
376
  timestamp: datetime
340
- platform: Optional[str] = None
341
- self_id: Optional[str] = None
377
+ login: Login
342
378
  argv: Optional[ArgvInteraction] = None
343
379
  button: Optional[ButtonInteraction] = None
344
380
  channel: Optional[Channel] = None
345
381
  guild: Optional[Guild] = None
346
- login: Optional[LoginType] = None
347
382
  member: Optional[Member] = None
348
383
  message: Optional[MessageObject] = None
349
384
  operator: Optional[User] = None
@@ -353,19 +388,15 @@ class Event(ModelBase):
353
388
  _type: Optional[str] = None
354
389
  _data: Optional[dict] = None
355
390
 
391
+ sn: int = 0
392
+
356
393
  __converter__ = {
357
394
  "timestamp": lambda ts: datetime.fromtimestamp(int(ts) / 1000),
358
395
  "argv": ArgvInteraction.parse,
359
396
  "button": ButtonInteraction.parse,
360
397
  "channel": Channel.parse,
361
398
  "guild": Guild.parse,
362
- "login": lambda raw: (
363
- LoginPreview.parse(
364
- raw if raw["user"] else {**raw, "user": {"id": raw["self_id"]}} if "self_id" in raw else raw
365
- )
366
- if "user" in raw
367
- else Login.parse(raw)
368
- ),
399
+ "login": Login.parse,
369
400
  "member": Member.parse,
370
401
  "message": MessageObject.parse,
371
402
  "operator": User.parse,
@@ -373,33 +404,33 @@ class Event(ModelBase):
373
404
  "user": User.parse,
374
405
  }
375
406
 
376
- @property
377
- def platform_(self):
378
- if self.platform:
379
- return self.platform
380
- if self.login and self.login.platform:
381
- return self.login.platform
382
- raise ValueError("platform not found")
407
+ @classmethod
408
+ def parse(cls, raw: dict):
409
+ if "id" in raw and "sn" not in raw:
410
+ raw["sn"] = raw["id"]
411
+ if "platform" in raw and "self_id" in raw and "login" not in raw:
412
+ raw["login"] = {
413
+ "sn": raw["self_id"],
414
+ "platform": raw["platform"],
415
+ "user": {"id": raw["self_id"]},
416
+ "status": LoginStatus.ONLINE,
417
+ }
418
+ return super().parse(raw)
383
419
 
384
420
  @property
385
- def self_id_(self):
386
- if self.self_id:
387
- return self.self_id
388
- if self.login and self.login.id:
389
- return self.login.id
390
- raise ValueError("self_id not found")
421
+ def platform(self):
422
+ return self.login.platform
391
423
 
392
- def __post_init__(self):
393
- _ = self.platform_
394
- _ = self.self_id_
424
+ @property
425
+ def self_id(self):
426
+ return self.login.id
395
427
 
396
428
  def dump(self):
397
429
  res = {
398
- "id": self.id,
430
+ "sn": self.sn,
399
431
  "type": self.type,
400
- "platform": self.platform_,
401
- "self_id": self.self_id_,
402
432
  "timestamp": int(self.timestamp.timestamp() * 1000),
433
+ "login": self.login.dump(),
403
434
  }
404
435
  if self.argv:
405
436
  res["argv"] = self.argv.dump()
@@ -1,67 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Optional
3
-
4
- from yarl import URL
5
-
6
-
7
- class Config:
8
- @property
9
- def identity(self) -> str:
10
- raise NotImplementedError
11
-
12
- @property
13
- def token(self) -> Optional[str]:
14
- raise NotImplementedError
15
-
16
- @property
17
- def api_base(self) -> URL:
18
- raise NotImplementedError
19
-
20
-
21
- @dataclass
22
- class WebsocketsInfo(Config):
23
- host: str = "localhost"
24
- port: int = 5140
25
- path: str = ""
26
- token: Optional[str] = None
27
-
28
- def __post_init__(self):
29
- if self.path and not self.path.startswith("/"):
30
- self.path = f"/{self.path}"
31
-
32
- @property
33
- def identity(self):
34
- return f"{self.host}:{self.port}"
35
-
36
- @property
37
- def api_base(self):
38
- return URL(f"http://{self.host}:{self.port}{self.path}") / "v1"
39
-
40
- @property
41
- def ws_base(self):
42
- return URL(f"ws://{self.host}:{self.port}{self.path}") / "v1"
43
-
44
-
45
- @dataclass
46
- class WebhookInfo(Config):
47
- host: str = "127.0.0.1"
48
- port: int = 8080
49
- path: str = "v1/events"
50
- token: Optional[str] = None
51
- server_host: str = "localhost"
52
- server_port: int = 5140
53
- server_path: str = ""
54
-
55
- def __post_init__(self):
56
- if self.path and not self.path.startswith("/"):
57
- self.path = f"/{self.path}"
58
- if self.server_path and not self.server_path.startswith("/"):
59
- self.server_path = f"/{self.server_path}"
60
-
61
- @property
62
- def identity(self):
63
- return f"{self.host}:{self.port}{self.path}"
64
-
65
- @property
66
- def api_base(self):
67
- return URL(f"http://{self.server_host}:{self.server_port}{self.server_path}") / "v1"