satori-python-core 0.16.7__tar.gz → 0.17.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.
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/.mina/core.toml +2 -1
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/PKG-INFO +2 -2
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/pyproject.toml +10 -9
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/src/satori/__init__.py +2 -1
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/src/satori/element.py +152 -120
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/src/satori/model.py +78 -74
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/src/satori/parser.py +21 -28
- satori_python_core-0.17.0/src/satori/utils.py +33 -0
- satori_python_core-0.16.7/src/satori/utils.py +0 -17
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/LICENSE +0 -0
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/README.md +0 -0
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/src/satori/const.py +0 -0
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/src/satori/event.py +0 -0
- {satori_python_core-0.16.7 → satori_python_core-0.17.0}/src/satori/exception.py +0 -0
|
@@ -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.
|
|
18
|
+
requires-python = ">=3.10,<4.0"
|
|
19
19
|
classifiers = [
|
|
20
20
|
"Typing :: Typed",
|
|
21
21
|
"Development Status :: 4 - Beta",
|
|
@@ -31,3 +31,4 @@ classifiers = [
|
|
|
31
31
|
[project.urls]
|
|
32
32
|
homepage = "https://github.com/RF-Tar-Railt/satori-python"
|
|
33
33
|
repository = "https://github.com/RF-Tar-Railt/satori-python"
|
|
34
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: satori-python-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.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:
|
|
19
|
+
Requires-Python: <4.0,>=3.10
|
|
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
|
|
@@ -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.
|
|
14
|
+
requires-python = ">=3.10,<4.0"
|
|
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.
|
|
26
|
+
version = "0.17.0"
|
|
27
27
|
|
|
28
28
|
[project.license]
|
|
29
29
|
text = "MIT"
|
|
@@ -39,7 +39,7 @@ requires = [
|
|
|
39
39
|
]
|
|
40
40
|
build-backend = "mina.backend"
|
|
41
41
|
|
|
42
|
-
[
|
|
42
|
+
[dependency-groups]
|
|
43
43
|
dev = [
|
|
44
44
|
"isort>=5.13.2",
|
|
45
45
|
"black>=24.4.0",
|
|
@@ -49,6 +49,7 @@ dev = [
|
|
|
49
49
|
"mina-build<0.6,>=0.5.1",
|
|
50
50
|
"pdm-mina>=0.3.2",
|
|
51
51
|
"nonechat<0.7.0,>=0.6.0",
|
|
52
|
+
"uvicorn[standard]>=0.35.0",
|
|
52
53
|
]
|
|
53
54
|
|
|
54
55
|
[tool.pdm.build]
|
|
@@ -74,21 +75,21 @@ source = "file"
|
|
|
74
75
|
path = "src/satori/__init__.py"
|
|
75
76
|
|
|
76
77
|
[tool.black]
|
|
77
|
-
line-length =
|
|
78
|
+
line-length = 120
|
|
78
79
|
include = "\\.pyi?$"
|
|
79
80
|
extend-exclude = ""
|
|
80
81
|
|
|
81
82
|
[tool.isort]
|
|
82
83
|
profile = "black"
|
|
83
|
-
line_length =
|
|
84
|
+
line_length = 120
|
|
84
85
|
skip_gitignore = true
|
|
85
86
|
extra_standard_library = [
|
|
86
87
|
"typing_extensions",
|
|
87
88
|
]
|
|
88
89
|
|
|
89
90
|
[tool.ruff]
|
|
90
|
-
line-length =
|
|
91
|
-
target-version = "
|
|
91
|
+
line-length = 120
|
|
92
|
+
target-version = "py310"
|
|
92
93
|
exclude = [
|
|
93
94
|
"exam.py",
|
|
94
95
|
]
|
|
@@ -108,12 +109,12 @@ ignore = [
|
|
|
108
109
|
"F403",
|
|
109
110
|
"F405",
|
|
110
111
|
"C901",
|
|
111
|
-
"
|
|
112
|
+
"UP038",
|
|
112
113
|
]
|
|
113
114
|
|
|
114
115
|
[tool.pyright]
|
|
115
116
|
pythonPlatform = "All"
|
|
116
|
-
pythonVersion = "3.
|
|
117
|
+
pythonVersion = "3.10"
|
|
117
118
|
typeCheckingMode = "basic"
|
|
118
119
|
reportShadowedImports = false
|
|
119
120
|
disableBytesTypePromotions = true
|
|
@@ -23,6 +23,7 @@ from .element import Superscript as Superscript
|
|
|
23
23
|
from .element import Text as Text
|
|
24
24
|
from .element import Underline as Underline
|
|
25
25
|
from .element import Video as Video
|
|
26
|
+
from .element import register_element as register_element
|
|
26
27
|
from .element import select as select
|
|
27
28
|
from .element import transform as transform
|
|
28
29
|
from .model import ArgvInteraction as ArgvInteraction
|
|
@@ -41,4 +42,4 @@ from .model import Role as Role
|
|
|
41
42
|
from .model import Upload as Upload
|
|
42
43
|
from .model import User as User
|
|
43
44
|
|
|
44
|
-
__version__ = "0.
|
|
45
|
+
__version__ = "0.17.0"
|
|
@@ -1,23 +1,57 @@
|
|
|
1
1
|
from base64 import b64encode
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Callable, Sequence
|
|
3
|
+
from dataclasses import InitVar, dataclass, field
|
|
3
4
|
from io import BytesIO
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from
|
|
6
|
+
from types import UnionType
|
|
7
|
+
from typing import Any, ClassVar, Final, TypeVar, Union, final, get_args, get_origin, overload
|
|
6
8
|
from typing_extensions import override
|
|
7
9
|
|
|
8
10
|
from .parser import Element as RawElement
|
|
9
|
-
from .parser import escape, param_case
|
|
11
|
+
from .parser import escape, param_case, parse
|
|
10
12
|
from .parser import select as select_raw
|
|
13
|
+
from .utils import decode
|
|
11
14
|
|
|
12
15
|
TE = TypeVar("TE", bound="Element")
|
|
13
16
|
|
|
14
17
|
|
|
18
|
+
def conv_bool(v: str) -> bool:
|
|
19
|
+
if v.lower() not in ("true", "false"):
|
|
20
|
+
raise ValueError(v)
|
|
21
|
+
return v.lower() == "true"
|
|
22
|
+
|
|
23
|
+
|
|
15
24
|
@dataclass(repr=False)
|
|
16
25
|
class Element:
|
|
17
26
|
_attrs: dict[str, Any] = field(init=False, default_factory=dict)
|
|
18
27
|
_children: list["Element"] = field(init=False, default_factory=list)
|
|
19
28
|
|
|
20
29
|
__names__: ClassVar[tuple[str, ...]]
|
|
30
|
+
__convert_fields__: ClassVar[dict[str, Callable[[str], Any]]]
|
|
31
|
+
|
|
32
|
+
def __init_subclass__(cls, **kwargs):
|
|
33
|
+
cls.__convert_fields__ = {}
|
|
34
|
+
annotations = cls.__annotations__
|
|
35
|
+
for name, typ in annotations.items():
|
|
36
|
+
if name.startswith("_"):
|
|
37
|
+
continue
|
|
38
|
+
# _type = get_args(typ)[0] if hasattr(typ, "__origin__") else typ
|
|
39
|
+
orig = get_origin(typ)
|
|
40
|
+
if orig in (Union, UnionType):
|
|
41
|
+
args = get_args(typ)
|
|
42
|
+
if len(args) == 2 and type(None) in args:
|
|
43
|
+
_type = args[0] if args[1] is type(None) else args[1]
|
|
44
|
+
else:
|
|
45
|
+
_type = args[0]
|
|
46
|
+
else:
|
|
47
|
+
_type = typ
|
|
48
|
+
if _type is not str:
|
|
49
|
+
if _type is bool:
|
|
50
|
+
cls.__convert_fields__[name] = conv_bool
|
|
51
|
+
elif _type in (list, dict):
|
|
52
|
+
cls.__convert_fields__[name] = decode
|
|
53
|
+
else:
|
|
54
|
+
cls.__convert_fields__[name] = _type
|
|
21
55
|
|
|
22
56
|
@property
|
|
23
57
|
def children(self) -> list["Element"]:
|
|
@@ -29,24 +63,21 @@ class Element:
|
|
|
29
63
|
|
|
30
64
|
@classmethod
|
|
31
65
|
def unpack(cls, attrs: dict[str, Any]):
|
|
32
|
-
|
|
33
|
-
|
|
66
|
+
data = {}
|
|
67
|
+
names = getattr(cls, "__names__", None)
|
|
68
|
+
for name in cls.__dataclass_fields__.keys():
|
|
69
|
+
if name not in attrs:
|
|
70
|
+
continue
|
|
71
|
+
if name in cls.__convert_fields__:
|
|
72
|
+
data[name] = cls.__convert_fields__[name](attrs[name])
|
|
73
|
+
else:
|
|
74
|
+
data[name] = attrs[name]
|
|
75
|
+
obj = cls(**{k: v for k, v in data.items() if names is None or k in names}) # type: ignore
|
|
76
|
+
obj._attrs.update(data)
|
|
34
77
|
return obj
|
|
35
78
|
|
|
36
79
|
def __post_init__(self):
|
|
37
|
-
for
|
|
38
|
-
if f.name in ("_attrs", "_children"):
|
|
39
|
-
continue
|
|
40
|
-
_type = get_args(f.type)[0] if hasattr(f.type, "__origin__") else f.type
|
|
41
|
-
if _type is not str and isinstance(attr := getattr(self, f.name), str):
|
|
42
|
-
if _type is bool:
|
|
43
|
-
if attr.lower() not in ("true", "false"):
|
|
44
|
-
raise TypeError(f.name, attr)
|
|
45
|
-
setattr(self, f.name, attr.lower() == "true")
|
|
46
|
-
else:
|
|
47
|
-
setattr(self, f.name, _type(attr)) # type: ignore
|
|
48
|
-
self._attrs[f.name] = getattr(self, f.name)
|
|
49
|
-
self._attrs = {k: v for k, v in self._attrs.items() if v is not None}
|
|
80
|
+
self._attrs = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
|
50
81
|
|
|
51
82
|
def attributes(self) -> str:
|
|
52
83
|
def _attr(key: str, value: Any):
|
|
@@ -82,7 +113,7 @@ class Element:
|
|
|
82
113
|
elem += ", { " + ", ".join(repr(i) for i in self._children) + " }"
|
|
83
114
|
return elem + ")"
|
|
84
115
|
|
|
85
|
-
def __call__(self, *content:
|
|
116
|
+
def __call__(self, *content: "str | Element"):
|
|
86
117
|
self._children.extend(Text(i) if isinstance(i, str) else i for i in content)
|
|
87
118
|
self.__post_call__()
|
|
88
119
|
return self
|
|
@@ -103,22 +134,20 @@ class Text(Element):
|
|
|
103
134
|
def dumps(self, strip: bool = False) -> str:
|
|
104
135
|
return self.text if strip else escape(self.text)
|
|
105
136
|
|
|
106
|
-
__names__ = ("text",)
|
|
107
|
-
|
|
108
137
|
|
|
109
138
|
@dataclass(repr=False)
|
|
110
139
|
class At(Element):
|
|
111
140
|
"""<at> 元素用于提及某个或某些用户。"""
|
|
112
141
|
|
|
113
|
-
id:
|
|
114
|
-
name:
|
|
115
|
-
role:
|
|
116
|
-
type:
|
|
142
|
+
id: str | None = None
|
|
143
|
+
name: str | None = None
|
|
144
|
+
role: str | None = None
|
|
145
|
+
type: str | None = None
|
|
117
146
|
|
|
118
147
|
@staticmethod
|
|
119
148
|
def role_(
|
|
120
149
|
role: str,
|
|
121
|
-
name:
|
|
150
|
+
name: str | None = None,
|
|
122
151
|
) -> "At":
|
|
123
152
|
return At(role=role, name=name)
|
|
124
153
|
|
|
@@ -126,15 +155,13 @@ class At(Element):
|
|
|
126
155
|
def all(here: bool = False) -> "At":
|
|
127
156
|
return At(type="here" if here else "all")
|
|
128
157
|
|
|
129
|
-
__names__ = ("id", "name", "role", "type")
|
|
130
|
-
|
|
131
158
|
|
|
132
159
|
@dataclass(repr=False)
|
|
133
160
|
class Sharp(Element):
|
|
134
161
|
"""<sharp> 元素用于提及某个频道。"""
|
|
135
162
|
|
|
136
163
|
id: str
|
|
137
|
-
name:
|
|
164
|
+
name: str | None = None
|
|
138
165
|
|
|
139
166
|
|
|
140
167
|
@dataclass(repr=False)
|
|
@@ -143,8 +170,6 @@ class Link(Element):
|
|
|
143
170
|
|
|
144
171
|
href: str
|
|
145
172
|
|
|
146
|
-
__names__ = ("href",)
|
|
147
|
-
|
|
148
173
|
def __post_call__(self):
|
|
149
174
|
if not self._children:
|
|
150
175
|
return
|
|
@@ -165,26 +190,26 @@ class Link(Element):
|
|
|
165
190
|
@dataclass(repr=False)
|
|
166
191
|
class Resource(Element):
|
|
167
192
|
src: str
|
|
168
|
-
title:
|
|
169
|
-
extra: InitVar[
|
|
170
|
-
cache:
|
|
171
|
-
timeout:
|
|
193
|
+
title: str | None = None
|
|
194
|
+
extra: InitVar[dict[str, Any] | None] = None
|
|
195
|
+
cache: bool | None = None
|
|
196
|
+
timeout: int | None = None
|
|
172
197
|
|
|
173
198
|
__names__ = ("src", "title")
|
|
174
199
|
|
|
175
200
|
@classmethod
|
|
176
201
|
def of(
|
|
177
202
|
cls,
|
|
178
|
-
url:
|
|
179
|
-
path:
|
|
180
|
-
raw:
|
|
181
|
-
mime:
|
|
182
|
-
name:
|
|
183
|
-
duration:
|
|
184
|
-
poster:
|
|
185
|
-
extra:
|
|
186
|
-
cache:
|
|
187
|
-
timeout:
|
|
203
|
+
url: str | None = None,
|
|
204
|
+
path: str | Path | None = None,
|
|
205
|
+
raw: bytes | BytesIO | None = None,
|
|
206
|
+
mime: str | None = None,
|
|
207
|
+
name: str | None = None,
|
|
208
|
+
duration: float | None = None,
|
|
209
|
+
poster: str | None = None,
|
|
210
|
+
extra: dict[str, Any] | None = None,
|
|
211
|
+
cache: bool | None = None,
|
|
212
|
+
timeout: int | None = None,
|
|
188
213
|
**kwargs,
|
|
189
214
|
):
|
|
190
215
|
data: dict[str, Any] = {"extra": extra or kwargs}
|
|
@@ -209,7 +234,7 @@ class Resource(Element):
|
|
|
209
234
|
data["timeout"] = timeout
|
|
210
235
|
return cls(**data)
|
|
211
236
|
|
|
212
|
-
def __post_init__(self, extra:
|
|
237
|
+
def __post_init__(self, extra: dict[str, Any] | None = None):
|
|
213
238
|
super().__post_init__()
|
|
214
239
|
if extra:
|
|
215
240
|
self._attrs.update(extra)
|
|
@@ -219,8 +244,8 @@ class Resource(Element):
|
|
|
219
244
|
class Image(Resource):
|
|
220
245
|
"""<img> 元素用于表示图片。"""
|
|
221
246
|
|
|
222
|
-
width:
|
|
223
|
-
height:
|
|
247
|
+
width: int | None = None
|
|
248
|
+
height: int | None = None
|
|
224
249
|
|
|
225
250
|
__names__ = ("src", "title", "width", "height")
|
|
226
251
|
|
|
@@ -234,8 +259,8 @@ class Image(Resource):
|
|
|
234
259
|
class Audio(Resource):
|
|
235
260
|
"""<audio> 元素用于表示语音。"""
|
|
236
261
|
|
|
237
|
-
duration:
|
|
238
|
-
poster:
|
|
262
|
+
duration: float | None = None
|
|
263
|
+
poster: str | None = None
|
|
239
264
|
|
|
240
265
|
__names__ = ("src", "title", "duration", "poster")
|
|
241
266
|
|
|
@@ -244,10 +269,10 @@ class Audio(Resource):
|
|
|
244
269
|
class Video(Resource):
|
|
245
270
|
"""<video> 元素用于表示视频。"""
|
|
246
271
|
|
|
247
|
-
width:
|
|
248
|
-
height:
|
|
249
|
-
duration:
|
|
250
|
-
poster:
|
|
272
|
+
width: int | None = None
|
|
273
|
+
height: int | None = None
|
|
274
|
+
duration: float | None = None
|
|
275
|
+
poster: str | None = None
|
|
251
276
|
|
|
252
277
|
__names__ = ("src", "title", "width", "height", "duration", "poster")
|
|
253
278
|
|
|
@@ -256,7 +281,7 @@ class Video(Resource):
|
|
|
256
281
|
class File(Resource):
|
|
257
282
|
"""<file> 元素用于表示文件。"""
|
|
258
283
|
|
|
259
|
-
poster:
|
|
284
|
+
poster: str | None = None
|
|
260
285
|
|
|
261
286
|
__names__ = ("src", "title", "poster")
|
|
262
287
|
|
|
@@ -267,7 +292,7 @@ class Style(Element):
|
|
|
267
292
|
|
|
268
293
|
__names__ = ()
|
|
269
294
|
|
|
270
|
-
def __init__(self, *text:
|
|
295
|
+
def __init__(self, *text: "str | Text | Style"):
|
|
271
296
|
super().__init__()
|
|
272
297
|
self.__call__(*text)
|
|
273
298
|
|
|
@@ -374,16 +399,14 @@ class Message(Element):
|
|
|
374
399
|
子元素对应于消息的内容。如果其没有子元素,则消息不会被发送。
|
|
375
400
|
"""
|
|
376
401
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
id: Optional[str]
|
|
380
|
-
forward: Optional[bool]
|
|
402
|
+
id: str | None
|
|
403
|
+
forward: bool | None
|
|
381
404
|
|
|
382
405
|
def __init__(
|
|
383
406
|
self,
|
|
384
|
-
id:
|
|
385
|
-
forward:
|
|
386
|
-
content:
|
|
407
|
+
id: str | None = None,
|
|
408
|
+
forward: bool | None = None,
|
|
409
|
+
content: list[str | Element] | None = None,
|
|
387
410
|
):
|
|
388
411
|
self.id = id
|
|
389
412
|
self.forward = forward
|
|
@@ -405,10 +428,8 @@ class Author(Element):
|
|
|
405
428
|
"""<author> 元素用于表示消息的作者。它的子元素会被渲染为作者的名字。"""
|
|
406
429
|
|
|
407
430
|
id: str
|
|
408
|
-
name:
|
|
409
|
-
avatar:
|
|
410
|
-
|
|
411
|
-
__names__ = ("id", "name", "avatar")
|
|
431
|
+
name: str | None = None
|
|
432
|
+
avatar: str | None = None
|
|
412
433
|
|
|
413
434
|
|
|
414
435
|
@dataclass(repr=False)
|
|
@@ -416,23 +437,21 @@ class Button(Element):
|
|
|
416
437
|
"""<button> 元素用于表示一个按钮。它的子元素会被渲染为按钮的文本。"""
|
|
417
438
|
|
|
418
439
|
type: str
|
|
419
|
-
id:
|
|
420
|
-
href:
|
|
421
|
-
text:
|
|
422
|
-
theme:
|
|
423
|
-
|
|
424
|
-
__names__ = ("type", "id", "href", "text", "theme")
|
|
440
|
+
id: str | None = None
|
|
441
|
+
href: str | None = None
|
|
442
|
+
text: str | None = None
|
|
443
|
+
theme: str | None = None
|
|
425
444
|
|
|
426
445
|
@classmethod
|
|
427
|
-
def action(cls, button_id: str, theme:
|
|
446
|
+
def action(cls, button_id: str, theme: str | None = None):
|
|
428
447
|
return Button("action", id=button_id, theme=theme)
|
|
429
448
|
|
|
430
449
|
@classmethod
|
|
431
|
-
def link(cls, url: str, theme:
|
|
450
|
+
def link(cls, url: str, theme: str | None = None):
|
|
432
451
|
return Button("link", href=url, theme=theme)
|
|
433
452
|
|
|
434
453
|
@classmethod
|
|
435
|
-
def input(cls, text: str, theme:
|
|
454
|
+
def input(cls, text: str, theme: str | None = None):
|
|
436
455
|
return Button("input", text=text, theme=theme)
|
|
437
456
|
|
|
438
457
|
def attributes(self) -> str:
|
|
@@ -460,18 +479,15 @@ class Button(Element):
|
|
|
460
479
|
class Custom(Element):
|
|
461
480
|
"""自定义元素用于构造标准元素以外的元素"""
|
|
462
481
|
|
|
463
|
-
type: str
|
|
464
|
-
|
|
465
482
|
__names__ = ()
|
|
466
483
|
|
|
467
484
|
def __init__(
|
|
468
485
|
self,
|
|
469
486
|
type: str,
|
|
470
|
-
attrs:
|
|
471
|
-
children:
|
|
487
|
+
attrs: dict[str, Any] | None = None,
|
|
488
|
+
children: Sequence[str | Element] | None = None,
|
|
472
489
|
):
|
|
473
490
|
self.type = type
|
|
474
|
-
super().__init__()
|
|
475
491
|
if not hasattr(self, "_attrs"):
|
|
476
492
|
self._attrs = attrs or {}
|
|
477
493
|
else:
|
|
@@ -500,6 +516,17 @@ class Raw(Element):
|
|
|
500
516
|
return self.content if strip else escape(self.content)
|
|
501
517
|
|
|
502
518
|
|
|
519
|
+
def register_element(cls: type[TE], tag: str | None = None) -> type[TE]:
|
|
520
|
+
"""注册一个自定义元素类,使其可以被 `transform` 函数识别。
|
|
521
|
+
|
|
522
|
+
该类必须继承自 `Element`; 必要时还需要定义 `__names__` 类变量以指定可接受的属性名。
|
|
523
|
+
"""
|
|
524
|
+
if not issubclass(cls, Element):
|
|
525
|
+
raise TypeError("cls must be a subclass of Element")
|
|
526
|
+
ELEMENT_TYPE_MAP[tag or cls.__name__.lower()] = cls
|
|
527
|
+
return cls
|
|
528
|
+
|
|
529
|
+
|
|
503
530
|
ELEMENT_TYPE_MAP = {
|
|
504
531
|
"text": Text,
|
|
505
532
|
"at": At,
|
|
@@ -571,14 +598,14 @@ def transform(elements: list[RawElement]) -> list[Element]:
|
|
|
571
598
|
|
|
572
599
|
|
|
573
600
|
@overload
|
|
574
|
-
def select(elements:
|
|
601
|
+
def select(elements: Element | list[Element], query: type[TE]) -> list[TE]: ...
|
|
575
602
|
|
|
576
603
|
|
|
577
604
|
@overload
|
|
578
|
-
def select(elements:
|
|
605
|
+
def select(elements: Element | list[Element], query: str) -> list[Element]: ...
|
|
579
606
|
|
|
580
607
|
|
|
581
|
-
def select(elements:
|
|
608
|
+
def select(elements: Element | list[Element], query: type[TE] | str):
|
|
582
609
|
if not elements:
|
|
583
610
|
return []
|
|
584
611
|
if isinstance(elements, Element):
|
|
@@ -596,39 +623,44 @@ def select(elements: Union[Element, list[Element]], query: Union[type[TE], str])
|
|
|
596
623
|
return results
|
|
597
624
|
|
|
598
625
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
def
|
|
634
|
-
|
|
626
|
+
@final
|
|
627
|
+
class _E:
|
|
628
|
+
def __init__(self):
|
|
629
|
+
self.text = Text
|
|
630
|
+
self.at = At
|
|
631
|
+
self.at_role = At.role_
|
|
632
|
+
self.at_all = At.all
|
|
633
|
+
self.sharp = Sharp
|
|
634
|
+
self.link = Link
|
|
635
|
+
self.image = Image.of
|
|
636
|
+
self.audio = Audio.of
|
|
637
|
+
self.video = Video.of
|
|
638
|
+
self.file = File.of
|
|
639
|
+
self.resource = Resource
|
|
640
|
+
self.bold = Bold
|
|
641
|
+
self.italic = Italic
|
|
642
|
+
self.underline = Underline
|
|
643
|
+
self.strikethrough = Strikethrough
|
|
644
|
+
self.spoiler = Spoiler
|
|
645
|
+
self.code = Code
|
|
646
|
+
self.sup = Superscript
|
|
647
|
+
self.sub = Subscript
|
|
648
|
+
self.br = Br
|
|
649
|
+
self.paragraph = Paragraph
|
|
650
|
+
self.message = Message
|
|
651
|
+
self.quote = Quote
|
|
652
|
+
self.author = Author
|
|
653
|
+
self.raw = Raw
|
|
654
|
+
self.button = Button
|
|
655
|
+
self.action_button = Button.action
|
|
656
|
+
self.link_button = Button.link
|
|
657
|
+
self.input_button = Button.input
|
|
658
|
+
self.select = select
|
|
659
|
+
|
|
660
|
+
def __call__(self, elem: str, context: dict | None = None) -> Custom:
|
|
661
|
+
"""创建一个自定义元素"""
|
|
662
|
+
e = parse(elem, context)[0]
|
|
663
|
+
return Custom(e.type, e.attrs, transform(e.children))
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
E: Final = _E()
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import mimetypes
|
|
2
|
-
from collections.abc import AsyncIterable, Awaitable
|
|
3
|
-
from dataclasses import
|
|
2
|
+
from collections.abc import AsyncIterable, Awaitable, Callable
|
|
3
|
+
from dataclasses import Field, dataclass, field
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from enum import IntEnum
|
|
6
6
|
from os import PathLike
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import IO, Any,
|
|
9
|
-
from typing_extensions import
|
|
8
|
+
from typing import IO, Any, ClassVar, Generic, Literal, TypeAlias, TypeVar
|
|
9
|
+
from typing_extensions import Self
|
|
10
10
|
|
|
11
11
|
from .element import Element, transform
|
|
12
12
|
from .parser import Element as RawElement
|
|
@@ -19,15 +19,15 @@ class ModelBase:
|
|
|
19
19
|
_raw_data: dict[str, Any] = field(init=False, default_factory=dict, repr=False, compare=False, hash=False)
|
|
20
20
|
|
|
21
21
|
@classmethod
|
|
22
|
-
def parse(cls, raw: dict):
|
|
23
|
-
fs =
|
|
22
|
+
def parse(cls: type[Self], raw: dict) -> Self:
|
|
23
|
+
fs: dict[str, Field] = cls.__dataclass_fields__
|
|
24
24
|
data = {}
|
|
25
|
-
for
|
|
26
|
-
if
|
|
27
|
-
if
|
|
28
|
-
data[
|
|
25
|
+
for name in fs.keys():
|
|
26
|
+
if name in raw:
|
|
27
|
+
if name in cls.__converter__:
|
|
28
|
+
data[name] = cls.__converter__[name](raw[name])
|
|
29
29
|
else:
|
|
30
|
-
data[
|
|
30
|
+
data[name] = raw[name]
|
|
31
31
|
obj = cls(**data) # type: ignore
|
|
32
32
|
obj._raw_data = raw
|
|
33
33
|
return obj
|
|
@@ -47,8 +47,8 @@ class ChannelType(IntEnum):
|
|
|
47
47
|
class Channel(ModelBase):
|
|
48
48
|
id: str
|
|
49
49
|
type: ChannelType = ChannelType.TEXT
|
|
50
|
-
name:
|
|
51
|
-
parent_id:
|
|
50
|
+
name: str | None = None
|
|
51
|
+
parent_id: str | None = None
|
|
52
52
|
|
|
53
53
|
__converter__ = {"type": ChannelType}
|
|
54
54
|
|
|
@@ -64,8 +64,8 @@ class Channel(ModelBase):
|
|
|
64
64
|
@dataclass
|
|
65
65
|
class Guild(ModelBase):
|
|
66
66
|
id: str
|
|
67
|
-
name:
|
|
68
|
-
avatar:
|
|
67
|
+
name: str | None = None
|
|
68
|
+
avatar: str | None = None
|
|
69
69
|
|
|
70
70
|
def dump(self):
|
|
71
71
|
res = {"id": self.id}
|
|
@@ -79,10 +79,10 @@ class Guild(ModelBase):
|
|
|
79
79
|
@dataclass
|
|
80
80
|
class User(ModelBase):
|
|
81
81
|
id: str
|
|
82
|
-
name:
|
|
83
|
-
nick:
|
|
84
|
-
avatar:
|
|
85
|
-
is_bot:
|
|
82
|
+
name: str | None = None
|
|
83
|
+
nick: str | None = None
|
|
84
|
+
avatar: str | None = None
|
|
85
|
+
is_bot: bool | None = None
|
|
86
86
|
|
|
87
87
|
def dump(self):
|
|
88
88
|
res: dict[str, Any] = {"id": self.id}
|
|
@@ -99,10 +99,10 @@ class User(ModelBase):
|
|
|
99
99
|
|
|
100
100
|
@dataclass
|
|
101
101
|
class Member(ModelBase):
|
|
102
|
-
user:
|
|
103
|
-
nick:
|
|
104
|
-
avatar:
|
|
105
|
-
joined_at:
|
|
102
|
+
user: User | None = None
|
|
103
|
+
nick: str | None = None
|
|
104
|
+
avatar: str | None = None
|
|
105
|
+
joined_at: datetime | None = None
|
|
106
106
|
|
|
107
107
|
__converter__ = {"user": User.parse, "joined_at": lambda ts: datetime.fromtimestamp(int(ts) / 1000)}
|
|
108
108
|
|
|
@@ -122,7 +122,7 @@ class Member(ModelBase):
|
|
|
122
122
|
@dataclass
|
|
123
123
|
class Role(ModelBase):
|
|
124
124
|
id: str
|
|
125
|
-
name:
|
|
125
|
+
name: str | None = None
|
|
126
126
|
|
|
127
127
|
def dump(self):
|
|
128
128
|
res = {"id": self.id}
|
|
@@ -188,8 +188,8 @@ class Login(ModelBase):
|
|
|
188
188
|
|
|
189
189
|
@dataclass
|
|
190
190
|
class LoginPartial(Login):
|
|
191
|
-
platform:
|
|
192
|
-
user:
|
|
191
|
+
platform: str | None = None
|
|
192
|
+
user: User | None = None
|
|
193
193
|
|
|
194
194
|
|
|
195
195
|
@dataclass
|
|
@@ -199,7 +199,7 @@ class ArgvInteraction(ModelBase):
|
|
|
199
199
|
options: Any
|
|
200
200
|
|
|
201
201
|
def dump(self):
|
|
202
|
-
return
|
|
202
|
+
return {"name": self.name, "arguments": self.arguments, "options": self.options}
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
@dataclass
|
|
@@ -207,7 +207,7 @@ class ButtonInteraction(ModelBase):
|
|
|
207
207
|
id: str
|
|
208
208
|
|
|
209
209
|
def dump(self):
|
|
210
|
-
return
|
|
210
|
+
return {"id": self.id}
|
|
211
211
|
|
|
212
212
|
|
|
213
213
|
class Opcode(IntEnum):
|
|
@@ -227,8 +227,8 @@ class Opcode(IntEnum):
|
|
|
227
227
|
|
|
228
228
|
@dataclass
|
|
229
229
|
class Identify(ModelBase):
|
|
230
|
-
token:
|
|
231
|
-
sn:
|
|
230
|
+
token: str | None = None
|
|
231
|
+
sn: int | None = None
|
|
232
232
|
|
|
233
233
|
@classmethod
|
|
234
234
|
def parse(cls, raw: dict):
|
|
@@ -237,11 +237,11 @@ class Identify(ModelBase):
|
|
|
237
237
|
return super().parse(raw)
|
|
238
238
|
|
|
239
239
|
@property
|
|
240
|
-
def sequence(self) ->
|
|
240
|
+
def sequence(self) -> int | None:
|
|
241
241
|
return self.sn
|
|
242
242
|
|
|
243
243
|
def dump(self):
|
|
244
|
-
return
|
|
244
|
+
return {k: v for k, v in (("token", self.token), ("sn", self.sn)) if v is not None}
|
|
245
245
|
|
|
246
246
|
|
|
247
247
|
@dataclass
|
|
@@ -252,7 +252,7 @@ class Ready(ModelBase):
|
|
|
252
252
|
__converter__ = {"logins": lambda raw: [LoginPartial.parse(login) for login in raw]}
|
|
253
253
|
|
|
254
254
|
def dump(self):
|
|
255
|
-
return
|
|
255
|
+
return {"logins": [login.dump() for login in self.logins], "proxy_urls": self.proxy_urls}
|
|
256
256
|
|
|
257
257
|
|
|
258
258
|
@dataclass
|
|
@@ -262,7 +262,7 @@ class MetaPayload(ModelBase):
|
|
|
262
262
|
proxy_urls: list[str]
|
|
263
263
|
|
|
264
264
|
def dump(self):
|
|
265
|
-
return
|
|
265
|
+
return {"proxy_urls": self.proxy_urls}
|
|
266
266
|
|
|
267
267
|
|
|
268
268
|
@dataclass
|
|
@@ -275,40 +275,46 @@ class Meta(ModelBase):
|
|
|
275
275
|
__converter__ = {"logins": lambda raw: [LoginPartial.parse(login) for login in raw]}
|
|
276
276
|
|
|
277
277
|
def dump(self):
|
|
278
|
-
return
|
|
278
|
+
return {"logins": [login.dump() for login in self.logins], "proxy_urls": self.proxy_urls}
|
|
279
279
|
|
|
280
280
|
|
|
281
281
|
@dataclass
|
|
282
282
|
class MessageObject(ModelBase):
|
|
283
283
|
id: str
|
|
284
284
|
content: str
|
|
285
|
-
channel:
|
|
286
|
-
guild:
|
|
287
|
-
member:
|
|
288
|
-
user:
|
|
289
|
-
created_at:
|
|
290
|
-
updated_at:
|
|
285
|
+
channel: Channel | None = None
|
|
286
|
+
guild: Guild | None = None
|
|
287
|
+
member: Member | None = None
|
|
288
|
+
user: User | None = None
|
|
289
|
+
created_at: datetime | None = None
|
|
290
|
+
updated_at: datetime | None = None
|
|
291
291
|
|
|
292
292
|
@classmethod
|
|
293
293
|
def from_elements(
|
|
294
294
|
cls,
|
|
295
295
|
id: str,
|
|
296
296
|
content: list[Element],
|
|
297
|
-
channel:
|
|
298
|
-
guild:
|
|
299
|
-
member:
|
|
300
|
-
user:
|
|
301
|
-
created_at:
|
|
302
|
-
updated_at:
|
|
297
|
+
channel: Channel | None = None,
|
|
298
|
+
guild: Guild | None = None,
|
|
299
|
+
member: Member | None = None,
|
|
300
|
+
user: User | None = None,
|
|
301
|
+
created_at: datetime | None = None,
|
|
302
|
+
updated_at: datetime | None = None,
|
|
303
303
|
):
|
|
304
|
-
|
|
304
|
+
obj = cls(id, "".join(str(i) for i in content), channel, guild, member, user, created_at, updated_at)
|
|
305
|
+
obj._parsed_message = content
|
|
306
|
+
return obj
|
|
305
307
|
|
|
306
308
|
@property
|
|
307
309
|
def message(self) -> list[Element]:
|
|
308
|
-
|
|
310
|
+
if hasattr(self, "_parsed_message"):
|
|
311
|
+
return self._parsed_message
|
|
312
|
+
self._parsed_message = transform(parse(self.content))
|
|
313
|
+
return self._parsed_message
|
|
309
314
|
|
|
310
315
|
@message.setter
|
|
311
316
|
def message(self, value: list[Element]):
|
|
317
|
+
self._parsed_message = value
|
|
312
318
|
self.content = "".join(str(i) for i in value)
|
|
313
319
|
|
|
314
320
|
@classmethod
|
|
@@ -347,22 +353,22 @@ class MessageObject(ModelBase):
|
|
|
347
353
|
@dataclass
|
|
348
354
|
class MessageReceipt(ModelBase):
|
|
349
355
|
id: str
|
|
350
|
-
content:
|
|
356
|
+
content: str | None = None
|
|
351
357
|
|
|
352
358
|
@classmethod
|
|
353
359
|
def from_elements(
|
|
354
360
|
cls,
|
|
355
361
|
id: str,
|
|
356
|
-
content:
|
|
362
|
+
content: list[Element] | None = None,
|
|
357
363
|
):
|
|
358
364
|
return cls(id, "".join(str(i) for i in content) if content else None)
|
|
359
365
|
|
|
360
366
|
@property
|
|
361
|
-
def message(self) ->
|
|
367
|
+
def message(self) -> list[Element] | None:
|
|
362
368
|
return transform(parse(self.content)) if self.content else None
|
|
363
369
|
|
|
364
370
|
@message.setter
|
|
365
|
-
def message(self, value:
|
|
371
|
+
def message(self, value: list[Element] | None):
|
|
366
372
|
self.content = "".join(str(i) for i in value) if value else None
|
|
367
373
|
|
|
368
374
|
@classmethod
|
|
@@ -384,18 +390,18 @@ class Event(ModelBase):
|
|
|
384
390
|
type: str
|
|
385
391
|
timestamp: datetime
|
|
386
392
|
login: Login
|
|
387
|
-
argv:
|
|
388
|
-
button:
|
|
389
|
-
channel:
|
|
390
|
-
guild:
|
|
391
|
-
member:
|
|
392
|
-
message:
|
|
393
|
-
operator:
|
|
394
|
-
role:
|
|
395
|
-
user:
|
|
396
|
-
|
|
397
|
-
_type:
|
|
398
|
-
_data:
|
|
393
|
+
argv: ArgvInteraction | None = None
|
|
394
|
+
button: ButtonInteraction | None = None
|
|
395
|
+
channel: Channel | None = None
|
|
396
|
+
guild: Guild | None = None
|
|
397
|
+
member: Member | None = None
|
|
398
|
+
message: MessageObject | None = None
|
|
399
|
+
operator: User | None = None
|
|
400
|
+
role: Role | None = None
|
|
401
|
+
user: User | None = None
|
|
402
|
+
|
|
403
|
+
_type: str | None = None
|
|
404
|
+
_data: dict | None = None
|
|
399
405
|
|
|
400
406
|
sn: int = 0
|
|
401
407
|
|
|
@@ -474,10 +480,10 @@ T = TypeVar("T", bound=ModelBase)
|
|
|
474
480
|
@dataclass
|
|
475
481
|
class PageResult(ModelBase, Generic[T]):
|
|
476
482
|
data: list[T]
|
|
477
|
-
next:
|
|
483
|
+
next: str | None = None
|
|
478
484
|
|
|
479
485
|
@classmethod
|
|
480
|
-
def parse(cls, raw: dict, parser:
|
|
486
|
+
def parse(cls, raw: dict, parser: Callable[[dict], T] | None = None) -> "PageResult[T]":
|
|
481
487
|
data = [(parser or ModelBase.parse)(item) for item in raw["data"]]
|
|
482
488
|
return cls(data, raw.get("next")) # type: ignore
|
|
483
489
|
|
|
@@ -490,10 +496,10 @@ class PageResult(ModelBase, Generic[T]):
|
|
|
490
496
|
|
|
491
497
|
@dataclass
|
|
492
498
|
class PageDequeResult(PageResult[T]):
|
|
493
|
-
prev:
|
|
499
|
+
prev: str | None = None
|
|
494
500
|
|
|
495
501
|
@classmethod
|
|
496
|
-
def parse(cls, raw: dict, parser:
|
|
502
|
+
def parse(cls, raw: dict, parser: Callable[[dict], T] | None = None) -> "PageDequeResult[T]":
|
|
497
503
|
data = [(parser or ModelBase.parse)(item) for item in raw["data"]]
|
|
498
504
|
return cls(data, raw.get("next"), raw.get("prev")) # type: ignore
|
|
499
505
|
|
|
@@ -507,9 +513,7 @@ class PageDequeResult(PageResult[T]):
|
|
|
507
513
|
|
|
508
514
|
|
|
509
515
|
class IterablePageResult(Generic[T], AsyncIterable[T], Awaitable[PageResult[T]]):
|
|
510
|
-
def __init__(
|
|
511
|
-
self, func: Callable[[Optional[str]], Awaitable[PageResult[T]]], initial_page: Optional[str] = None
|
|
512
|
-
):
|
|
516
|
+
def __init__(self, func: Callable[[str | None], Awaitable[PageResult[T]]], initial_page: str | None = None):
|
|
513
517
|
self.func = func
|
|
514
518
|
self.next_page = initial_page
|
|
515
519
|
|
|
@@ -535,9 +539,9 @@ Order: TypeAlias = Literal["asc", "desc"]
|
|
|
535
539
|
|
|
536
540
|
@dataclass
|
|
537
541
|
class Upload:
|
|
538
|
-
file:
|
|
542
|
+
file: bytes | IO[bytes] | PathLike
|
|
539
543
|
mimetype: str = "image/png"
|
|
540
|
-
name:
|
|
544
|
+
name: str | None = None
|
|
541
545
|
|
|
542
546
|
def __post_init__(self):
|
|
543
547
|
if isinstance(self.file, PathLike):
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from collections.abc import Iterable
|
|
2
|
+
from collections.abc import Callable, Iterable
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from enum import IntEnum
|
|
5
|
-
from typing import Any,
|
|
6
|
-
from typing_extensions import TypeAlias
|
|
5
|
+
from typing import Any, Literal, Optional, TypeAlias, TypedDict, TypeVar, Union, cast
|
|
7
6
|
|
|
8
7
|
T = TypeVar("T")
|
|
9
8
|
|
|
@@ -29,18 +28,14 @@ def camel_case(source: str) -> str:
|
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
def param_case(source: str) -> str:
|
|
32
|
-
return re.sub(
|
|
33
|
-
".[A-Z]+", lambda mat: mat[0][0] + "-" + mat[0][1:].lower(), uncapitalize(source).replace("_", "-")
|
|
34
|
-
)
|
|
31
|
+
return re.sub(".[A-Z]+", lambda mat: mat[0][0] + "-" + mat[0][1:].lower(), uncapitalize(source).replace("_", "-"))
|
|
35
32
|
|
|
36
33
|
|
|
37
34
|
def snake_case(source: str) -> str:
|
|
38
|
-
return re.sub(
|
|
39
|
-
".[A-Z]", lambda mat: mat[0][0] + "_" + mat[0][1:].lower(), uncapitalize(source).replace("-", "_")
|
|
40
|
-
)
|
|
35
|
+
return re.sub(".[A-Z]", lambda mat: mat[0][0] + "_" + mat[0][1:].lower(), uncapitalize(source).replace("-", "_"))
|
|
41
36
|
|
|
42
37
|
|
|
43
|
-
def ensure_list(value:
|
|
38
|
+
def ensure_list(value: T | list[T] | None) -> list[T]:
|
|
44
39
|
return value if isinstance(value, list) else [value] if value else []
|
|
45
40
|
|
|
46
41
|
|
|
@@ -73,12 +68,12 @@ class Element:
|
|
|
73
68
|
type: str
|
|
74
69
|
attrs: dict[str, Any]
|
|
75
70
|
children: list["Element"]
|
|
76
|
-
source:
|
|
71
|
+
source: str | None = None
|
|
77
72
|
|
|
78
73
|
def __init__(
|
|
79
74
|
self,
|
|
80
|
-
type:
|
|
81
|
-
attrs:
|
|
75
|
+
type: str | Render[Fragment, Any],
|
|
76
|
+
attrs: dict[str, Any] | None = None,
|
|
82
77
|
*children: Fragment,
|
|
83
78
|
) -> None:
|
|
84
79
|
self.attrs = {}
|
|
@@ -174,7 +169,7 @@ def parse_selector(input: str) -> list[list[Selector]]:
|
|
|
174
169
|
return [_quert(q) for q in input.split(",")]
|
|
175
170
|
|
|
176
171
|
|
|
177
|
-
def select(source:
|
|
172
|
+
def select(source: str | list[Element], query: str | list[list[Selector]]) -> list[Element]:
|
|
178
173
|
if not source or not query:
|
|
179
174
|
return []
|
|
180
175
|
if isinstance(source, str):
|
|
@@ -237,9 +232,7 @@ tag_pat2 = re.compile(
|
|
|
237
232
|
r"(?P<comment><!--[\s\S]*?-->)|(?P<tag><(/?)([^!\s>/]*)([^>]*?)\s*(/?)>)|(?P<curly>\{(?P<derivative>[@:/#][^\s\}]*)?[\s\S]*?\})"
|
|
238
233
|
)
|
|
239
234
|
attr_pat1 = re.compile(r"([^\s=]+)(?:=\"(?P<value1>[^\"]*)\"|='(?P<value2>[^']*)')?", re.S)
|
|
240
|
-
attr_pat2 = re.compile(
|
|
241
|
-
r"([^\s=]+)(?:=\"(?P<value1>[^\"]*)\"|='(?P<value2>[^']*)'|=\{(?P<value3>[^\}]+)\})?", re.S
|
|
242
|
-
)
|
|
235
|
+
attr_pat2 = re.compile(r"([^\s=]+)(?:=\"(?P<value1>[^\"]*)\"|='(?P<value2>[^']*)'|=\{(?P<curly>[^\}]+)\})?", re.S)
|
|
243
236
|
|
|
244
237
|
|
|
245
238
|
class Position(IntEnum):
|
|
@@ -264,7 +257,7 @@ class StackItem(TypedDict):
|
|
|
264
257
|
slot: str
|
|
265
258
|
|
|
266
259
|
|
|
267
|
-
def fold_tokens(tokens: list[
|
|
260
|
+
def fold_tokens(tokens: list[str | Token]) -> list[str | Token]:
|
|
268
261
|
stack: list[StackItem] = [
|
|
269
262
|
{
|
|
270
263
|
"token": Token(
|
|
@@ -279,7 +272,7 @@ def fold_tokens(tokens: list[Union[str, Token]]) -> list[Union[str, Token]]:
|
|
|
279
272
|
}
|
|
280
273
|
]
|
|
281
274
|
|
|
282
|
-
def push_token(*tokens:
|
|
275
|
+
def push_token(*tokens: str | Token):
|
|
283
276
|
token = stack[0]["token"]
|
|
284
277
|
token.children[stack[0]["slot"]].extend(tokens)
|
|
285
278
|
|
|
@@ -302,7 +295,7 @@ def fold_tokens(tokens: list[Union[str, Token]]) -> list[Union[str, Token]]:
|
|
|
302
295
|
return stack[-1]["token"].children["default"]
|
|
303
296
|
|
|
304
297
|
|
|
305
|
-
def parse_tokens(tokens: list[
|
|
298
|
+
def parse_tokens(tokens: list[str | Token], context: dict | None = None) -> list[Element]:
|
|
306
299
|
result: list[Element] = []
|
|
307
300
|
for token in tokens:
|
|
308
301
|
if isinstance(token, str):
|
|
@@ -313,12 +306,12 @@ def parse_tokens(tokens: list[Union[str, Token]], context: Optional[dict] = None
|
|
|
313
306
|
while mat := attr_pat.search(token.extra):
|
|
314
307
|
key = mat.group(1)
|
|
315
308
|
groupdict = mat.groupdict()
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if
|
|
319
|
-
attrs[key] = interpolate(
|
|
320
|
-
elif
|
|
321
|
-
attrs[key] = unescape(
|
|
309
|
+
v2 = groupdict.get("value2", groupdict.get("value1"))
|
|
310
|
+
curly = groupdict.get("curly")
|
|
311
|
+
if curly and context is not None:
|
|
312
|
+
attrs[key] = interpolate(curly, context)
|
|
313
|
+
elif v2 is not None:
|
|
314
|
+
attrs[key] = unescape(v2)
|
|
322
315
|
elif key.startswith("no-"):
|
|
323
316
|
attrs[key[3:]] = False
|
|
324
317
|
else:
|
|
@@ -348,8 +341,8 @@ def parse_tokens(tokens: list[Union[str, Token]], context: Optional[dict] = None
|
|
|
348
341
|
return result
|
|
349
342
|
|
|
350
343
|
|
|
351
|
-
def parse(src: str, context:
|
|
352
|
-
tokens: list[
|
|
344
|
+
def parse(src: str, context: dict | None = None):
|
|
345
|
+
tokens: list[str | Token] = []
|
|
353
346
|
|
|
354
347
|
def push_text(text: str):
|
|
355
348
|
if text:
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_public_ip():
|
|
5
|
+
st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
6
|
+
try:
|
|
7
|
+
st.connect(("10.255.255.255", 1))
|
|
8
|
+
IP = st.getsockname()[0]
|
|
9
|
+
except Exception:
|
|
10
|
+
IP = "localhost"
|
|
11
|
+
finally:
|
|
12
|
+
st.close()
|
|
13
|
+
return IP
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from msgspec.json import Decoder, Encoder # noqa: F401
|
|
18
|
+
|
|
19
|
+
decoder = Decoder()
|
|
20
|
+
encoder = Encoder()
|
|
21
|
+
|
|
22
|
+
decode = decoder.decode
|
|
23
|
+
|
|
24
|
+
def encode(obj):
|
|
25
|
+
return encoder.encode(obj).decode()
|
|
26
|
+
|
|
27
|
+
except ImportError:
|
|
28
|
+
import json
|
|
29
|
+
|
|
30
|
+
def encode(obj):
|
|
31
|
+
return json.dumps(obj, separators=(",", ":"), ensure_ascii=False)
|
|
32
|
+
|
|
33
|
+
decode = json.loads
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import socket
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def get_public_ip():
|
|
5
|
-
st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
6
|
-
try:
|
|
7
|
-
st.connect(("10.255.255.255", 1))
|
|
8
|
-
IP = st.getsockname()[0]
|
|
9
|
-
except Exception:
|
|
10
|
-
IP = "localhost"
|
|
11
|
-
finally:
|
|
12
|
-
st.close()
|
|
13
|
-
return IP
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if __name__ == "__main__":
|
|
17
|
-
print(get_public_ip()) # noqa: T201
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|