satori-python-core 0.11.2__tar.gz → 0.11.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python-core
3
- Version: 0.11.2
3
+ Version: 0.11.4
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>
@@ -23,7 +23,7 @@ classifiers = [
23
23
  "Programming Language :: Python :: 3.12",
24
24
  "Operating System :: OS Independent",
25
25
  ]
26
- version = "0.11.2"
26
+ version = "0.11.4"
27
27
 
28
28
  [project.license]
29
29
  text = "MIT"
@@ -39,4 +39,4 @@ from .model import MessageObject as MessageObject
39
39
  from .model import Role as Role
40
40
  from .model import User as User
41
41
 
42
- __version__ = "0.11.2"
42
+ __version__ = "0.11.4"
@@ -2,7 +2,7 @@ from base64 import b64encode
2
2
  from dataclasses import InitVar, dataclass, field, fields
3
3
  from io import BytesIO
4
4
  from pathlib import Path
5
- from typing import Any, Dict, List, Optional, TypeVar, Union, get_args
5
+ from typing import Any, ClassVar, Dict, List, Optional, Tuple, TypeVar, Union, get_args
6
6
  from typing_extensions import override
7
7
 
8
8
  from .parser import Element as RawElement
@@ -11,15 +11,23 @@ from .parser import escape, param_case
11
11
  TE = TypeVar("TE", bound="Element")
12
12
 
13
13
 
14
- @dataclass
14
+ @dataclass(repr=False)
15
15
  class Element:
16
- _attrs: Dict[str, Any] = field(init=False, default_factory=dict, repr=False)
16
+ _attrs: Dict[str, Any] = field(init=False, default_factory=dict)
17
17
  _children: List["Element"] = field(init=False, default_factory=list)
18
18
 
19
+ __names__: ClassVar[Tuple[str, ...]]
20
+
19
21
  @property
20
22
  def tag(self) -> str:
21
23
  return self.__class__.__name__.lower()
22
24
 
25
+ @classmethod
26
+ def unpack(cls, attrs: Dict[str, Any]):
27
+ obj = cls(**{k: v for k, v in attrs.items() if k in cls.__names__})
28
+ obj._attrs.update({k: v for k, v in attrs.items() if k not in cls.__names__})
29
+ return obj
30
+
23
31
  def __post_init__(self):
24
32
  for f in fields(self):
25
33
  if f.name in ("_attrs", "_children"):
@@ -62,6 +70,13 @@ class Element:
62
70
  def __str__(self) -> str:
63
71
  return self.dumps()
64
72
 
73
+ def __repr__(self) -> str:
74
+ args = {**self._attrs}
75
+ elem = f"{self.__class__.__name__}(" + ", ".join(f"{k}={v!r}" for k, v in args.items())
76
+ if self._children:
77
+ elem += ", { " + ", ".join(repr(i) for i in self._children) + " }"
78
+ return elem + ")"
79
+
65
80
  def __call__(self, *content: Union[str, "Element"]):
66
81
  self._children.extend(Text(i) if isinstance(i, str) else i for i in content)
67
82
  self.__post_call__()
@@ -69,8 +84,11 @@ class Element:
69
84
 
70
85
  def __post_call__(self): ...
71
86
 
87
+ def __getitem__(self, key: str) -> Any:
88
+ return self._attrs[key]
72
89
 
73
- @dataclass
90
+
91
+ @dataclass(repr=False)
74
92
  class Text(Element):
75
93
  """一段纯文本。"""
76
94
 
@@ -80,8 +98,10 @@ class Text(Element):
80
98
  def dumps(self, strip: bool = False) -> str:
81
99
  return self.text if strip else escape(self.text)
82
100
 
101
+ __names__ = ("text",)
83
102
 
84
- @dataclass
103
+
104
+ @dataclass(repr=False)
85
105
  class At(Element):
86
106
  """<at> 元素用于提及某个或某些用户。"""
87
107
 
@@ -101,8 +121,10 @@ class At(Element):
101
121
  def all(here: bool = False) -> "At":
102
122
  return At(type="here" if here else "all")
103
123
 
124
+ __names__ = ("id", "name", "role", "type")
104
125
 
105
- @dataclass
126
+
127
+ @dataclass(repr=False)
106
128
  class Sharp(Element):
107
129
  """<sharp> 元素用于提及某个频道。"""
108
130
 
@@ -114,7 +136,9 @@ class Sharp(Element):
114
136
  class Link(Element):
115
137
  """<a> 元素用于显示一个链接。"""
116
138
 
117
- url: str
139
+ href: str
140
+
141
+ __names__ = ("href",)
118
142
 
119
143
  def __post_call__(self):
120
144
  if not self._children:
@@ -128,18 +152,21 @@ class Link(Element):
128
152
  def tag(self) -> str:
129
153
  return "a"
130
154
 
131
- @override
132
- def attributes(self) -> str:
133
- return f' href="{escape(self.url)}"'
155
+ @property
156
+ def url(self) -> str:
157
+ return self.href
134
158
 
135
159
 
136
- @dataclass
160
+ @dataclass(repr=False)
137
161
  class Resource(Element):
138
162
  src: str
163
+ title: Optional[str] = None
139
164
  extra: InitVar[Optional[Dict[str, Any]]] = None
140
165
  cache: Optional[bool] = None
141
166
  timeout: Optional[str] = None
142
167
 
168
+ __names__ = ("src", "title")
169
+
143
170
  @classmethod
144
171
  def of(
145
172
  cls,
@@ -147,9 +174,12 @@ class Resource(Element):
147
174
  path: Optional[Union[str, Path]] = None,
148
175
  raw: Optional[Union[bytes, BytesIO]] = None,
149
176
  mime: Optional[str] = None,
177
+ name: Optional[str] = None,
178
+ poster: Optional[str] = None,
150
179
  extra: Optional[Dict[str, Any]] = None,
151
180
  cache: Optional[bool] = None,
152
181
  timeout: Optional[str] = None,
182
+ **kwargs,
153
183
  ):
154
184
  data: Dict[str, Any] = {"extra": extra}
155
185
  if url is not None:
@@ -161,6 +191,10 @@ class Resource(Element):
161
191
  data = {"src": f"data:{mime};base64,{b64encode(bd).decode()}"}
162
192
  else:
163
193
  raise ValueError(f"{cls} need at least one of url, path and raw")
194
+ if name is not None:
195
+ data["title"] = name
196
+ if poster is not None and cls in (Video, Audio, File):
197
+ data["poster"] = poster
164
198
  if cache is not None:
165
199
  data["cache"] = cache
166
200
  if timeout is not None:
@@ -173,44 +207,58 @@ class Resource(Element):
173
207
  self._attrs.update(extra)
174
208
 
175
209
 
176
- @dataclass
210
+ @dataclass(repr=False)
177
211
  class Image(Resource):
178
212
  """<img> 元素用于表示图片。"""
179
213
 
180
214
  width: Optional[int] = None
181
215
  height: Optional[int] = None
182
216
 
217
+ __names__ = ("src", "title", "width", "height")
218
+
183
219
  @property
184
220
  @override
185
221
  def tag(self) -> str:
186
222
  return "img"
187
223
 
188
224
 
189
- @dataclass
225
+ @dataclass(repr=False)
190
226
  class Audio(Resource):
191
227
  """<audio> 元素用于表示语音。"""
192
228
 
193
- pass
229
+ duration: Optional[int] = None
230
+ poster: Optional[str] = None
194
231
 
232
+ __names__ = ("src", "title", "duration", "poster")
195
233
 
196
- @dataclass
234
+
235
+ @dataclass(repr=False)
197
236
  class Video(Resource):
198
237
  """<video> 元素用于表示视频。"""
199
238
 
200
- pass
239
+ width: Optional[int] = None
240
+ height: Optional[int] = None
241
+ duration: Optional[int] = None
242
+ poster: Optional[str] = None
201
243
 
244
+ __names__ = ("src", "title", "width", "height", "duration", "poster")
202
245
 
203
- @dataclass
246
+
247
+ @dataclass(repr=False)
204
248
  class File(Resource):
205
249
  """<file> 元素用于表示文件。"""
206
250
 
207
- pass
251
+ poster: Optional[str] = None
252
+
253
+ __names__ = ("src", "title", "poster")
208
254
 
209
255
 
210
- @dataclass(init=False)
256
+ @dataclass(init=False, repr=False)
211
257
  class Style(Element):
212
258
  """样式元素的基类。"""
213
259
 
260
+ __names__ = ()
261
+
214
262
  def __init__(self, *text: Union[str, Text, "Style"]):
215
263
  super().__init__()
216
264
  self.__call__(*text)
@@ -311,13 +359,15 @@ class Paragraph(Style):
311
359
  return "p"
312
360
 
313
361
 
314
- @dataclass(init=False)
362
+ @dataclass(init=False, repr=False)
315
363
  class Message(Element):
316
364
  """<message> 元素的基本用法是表示一条消息。
317
365
 
318
366
  子元素对应于消息的内容。如果其没有子元素,则消息不会被发送。
319
367
  """
320
368
 
369
+ __names__ = ("id", "forward")
370
+
321
371
  id: Optional[str]
322
372
  forward: Optional[bool]
323
373
 
@@ -342,16 +392,18 @@ class Quote(Message):
342
392
  pass
343
393
 
344
394
 
345
- @dataclass
395
+ @dataclass(repr=False)
346
396
  class Author(Element):
347
397
  """<author> 元素用于表示消息的作者。它的子元素会被渲染为作者的名字。"""
348
398
 
349
399
  id: str
350
- nickname: Optional[str] = None
400
+ name: Optional[str] = None
351
401
  avatar: Optional[str] = None
352
402
 
403
+ __names__ = ("id", "name", "avatar")
353
404
 
354
- @dataclass
405
+
406
+ @dataclass(repr=False)
355
407
  class Button(Element):
356
408
  """<button> 元素用于表示一个按钮。它的子元素会被渲染为按钮的文本。"""
357
409
 
@@ -361,6 +413,8 @@ class Button(Element):
361
413
  text: Optional[str] = None
362
414
  theme: Optional[str] = None
363
415
 
416
+ __names__ = ("type", "id", "href", "text", "theme")
417
+
364
418
  @classmethod
365
419
  def action(cls, button_id: str, theme: Optional[str] = None):
366
420
  return Button("action", id=button_id, theme=theme)
@@ -394,12 +448,14 @@ class Button(Element):
394
448
  raise ValueError("Button can only have one Text child")
395
449
 
396
450
 
397
- @dataclass(init=False)
451
+ @dataclass(init=False, repr=False)
398
452
  class Custom(Element):
399
453
  """自定义元素用于构造标准元素以外的元素"""
400
454
 
401
455
  type: str
402
456
 
457
+ __names__ = ()
458
+
403
459
  def __init__(
404
460
  self,
405
461
  type: str,
@@ -423,12 +479,14 @@ class Custom(Element):
423
479
  return self.type
424
480
 
425
481
 
426
- @dataclass
482
+ @dataclass(repr=False)
427
483
  class Raw(Element):
428
484
  """Raw 元素表示原始文本"""
429
485
 
430
486
  content: str
431
487
 
488
+ __names__ = ()
489
+
432
490
  @override
433
491
  def dumps(self, strip: bool = False):
434
492
  return self.content if strip else escape(self.content)
@@ -479,25 +537,25 @@ def transform(elements: List[RawElement]) -> List[Element]:
479
537
  tag = elem.tag()
480
538
  if tag in ELEMENT_TYPE_MAP:
481
539
  seg_cls = ELEMENT_TYPE_MAP[tag]
482
- msg.append(seg_cls(**elem.attrs))
540
+ msg.append(seg_cls.unpack(elem.attrs))
483
541
  elif tag in ("a", "link"):
484
- link = Link(elem.attrs["href"])
542
+ link = Link.unpack(elem.attrs)
485
543
  if elem.children:
486
544
  link(*transform(elem.children))
487
545
  msg.append(link)
488
546
  elif tag == "button":
489
- button = Button(**elem.attrs)
547
+ button = Button.unpack(elem.attrs)
490
548
  if elem.children:
491
549
  button(*transform(elem.children))
492
550
  elif tag in STYLE_TYPE_MAP:
493
551
  seg_cls = STYLE_TYPE_MAP[tag]
494
- msg.append(seg_cls()(*transform(elem.children)))
552
+ msg.append(seg_cls.unpack(elem.attrs)(*transform(elem.children)))
495
553
  elif tag in ("br", "newline"):
496
554
  msg.append(Br())
497
555
  elif tag == "message":
498
- msg.append(Message(**elem.attrs)(*transform(elem.children)))
556
+ msg.append(Message.unpack(elem.attrs)(*transform(elem.children)))
499
557
  elif tag == "quote":
500
- msg.append(Quote(**elem.attrs)(*transform(elem.children)))
558
+ msg.append(Quote.unpack(elem.attrs)(*transform(elem.children)))
501
559
  else:
502
560
  msg.append(Custom(elem.type, elem.attrs)(*transform(elem.children)))
503
561
  return msg