entari-plugin-server 0.1.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.
- entari_plugin_server-0.1.0/LICENSE +21 -0
- entari_plugin_server-0.1.0/PKG-INFO +29 -0
- entari_plugin_server-0.1.0/README.md +16 -0
- entari_plugin_server-0.1.0/pyproject.toml +35 -0
- entari_plugin_server-0.1.0/src/entari_plugin_server/__init__.py +79 -0
- entari_plugin_server-0.1.0/src/entari_plugin_server/patch.py +83 -0
- entari_plugin_server-0.1.0/tests/__init__.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ARCLET
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: entari-plugin-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Entari plugin for running Satori Server
|
|
5
|
+
Author-Email: RF-Tar-Railt <rf_tar_railt@qq.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: homepage, https://github.com/ArcletProject/entari-plugin-server
|
|
8
|
+
Project-URL: repository, https://github.com/ArcletProject/entari-plugin-server
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Requires-Dist: arclet-entari>=0.12.1
|
|
11
|
+
Requires-Dist: satori-python-server>=0.16.3
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# entari-plugin-server
|
|
15
|
+
Entari plugin for running Satori Server
|
|
16
|
+
|
|
17
|
+
## Example Usage
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
plugins:
|
|
21
|
+
server:
|
|
22
|
+
adapters:
|
|
23
|
+
- $path: package.module:AdapterClass
|
|
24
|
+
# Following are adapter's configuration
|
|
25
|
+
key1: value1
|
|
26
|
+
key2: value2
|
|
27
|
+
host: 127.0.0.1
|
|
28
|
+
port: 5140
|
|
29
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# entari-plugin-server
|
|
2
|
+
Entari plugin for running Satori Server
|
|
3
|
+
|
|
4
|
+
## Example Usage
|
|
5
|
+
|
|
6
|
+
```yaml
|
|
7
|
+
plugins:
|
|
8
|
+
server:
|
|
9
|
+
adapters:
|
|
10
|
+
- $path: package.module:AdapterClass
|
|
11
|
+
# Following are adapter's configuration
|
|
12
|
+
key1: value1
|
|
13
|
+
key2: value2
|
|
14
|
+
host: 127.0.0.1
|
|
15
|
+
port: 5140
|
|
16
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "entari-plugin-server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Entari plugin for running Satori Server"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "RF-Tar-Railt", email = "rf_tar_railt@qq.com" },
|
|
7
|
+
]
|
|
8
|
+
dependencies = [
|
|
9
|
+
"arclet-entari>=0.12.1",
|
|
10
|
+
"satori-python-server>=0.16.3",
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
|
|
15
|
+
[project.license]
|
|
16
|
+
text = "MIT"
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
homepage = "https://github.com/ArcletProject/entari-plugin-server"
|
|
20
|
+
repository = "https://github.com/ArcletProject/entari-plugin-server"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = [
|
|
24
|
+
"pdm-backend",
|
|
25
|
+
]
|
|
26
|
+
build-backend = "pdm.backend"
|
|
27
|
+
|
|
28
|
+
[tool.pdm]
|
|
29
|
+
distribution = true
|
|
30
|
+
|
|
31
|
+
[tool.pdm.dev-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"arclet-entari[yaml]>=0.12.1",
|
|
34
|
+
"nekobox @ git+https://github.com/wyapx/nekobox.git",
|
|
35
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from functools import reduce
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
|
|
5
|
+
from arclet.entari import plugin
|
|
6
|
+
from arclet.entari import logger as log_m
|
|
7
|
+
from arclet.entari.config import BasicConfModel, field
|
|
8
|
+
from graia.amnesia.builtins import asgi
|
|
9
|
+
from satori.server import Adapter, Server
|
|
10
|
+
|
|
11
|
+
from .patch import DirectAdapterServer
|
|
12
|
+
|
|
13
|
+
asgi.LoguruHandler = log_m.LoguruHandler
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Config(BasicConfModel):
|
|
17
|
+
direct_adapter: bool = False
|
|
18
|
+
"""是否使用直连适配器"""
|
|
19
|
+
adapters: list[dict] = field(default_factory=list)
|
|
20
|
+
host: str = "127.0.0.1"
|
|
21
|
+
port: int = 5140
|
|
22
|
+
path: str = "satori"
|
|
23
|
+
version: str = "v1"
|
|
24
|
+
token: str | None = None
|
|
25
|
+
stream_threshold: int = 16 * 1024 * 1024
|
|
26
|
+
stream_chunk_size: int = 64 * 1024
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
conf = plugin.get_config(Config)
|
|
30
|
+
logger = log_m.log.wrapper("[Server]")
|
|
31
|
+
|
|
32
|
+
if conf.direct_adapter:
|
|
33
|
+
server = DirectAdapterServer(conf.host, conf.port, conf.path, conf.version, conf.token, None, conf.stream_threshold, conf.stream_chunk_size)
|
|
34
|
+
else:
|
|
35
|
+
server = Server(conf.host, conf.port, conf.path, conf.version, conf.token, None, conf.stream_threshold, conf.stream_chunk_size)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
pattern = re.compile(r"(?P<module>[\w.]+)\s*(:\s*(?P<attr>[\w.]+)\s*)?((?P<extras>\[.*\])\s*)?$")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _load_adapter(adapter_config: dict):
|
|
42
|
+
if "$path" not in adapter_config:
|
|
43
|
+
logger.warning(f"Adapter config missing `$path`: {adapter_config}")
|
|
44
|
+
return None
|
|
45
|
+
path = adapter_config["$path"]
|
|
46
|
+
if path.startswith("@."):
|
|
47
|
+
path = f"satori.adapters{path[1:]}"
|
|
48
|
+
elif path.startswith("@"):
|
|
49
|
+
path = f"satori.adapters.{path[1:]}"
|
|
50
|
+
match = pattern.match(path)
|
|
51
|
+
if not match:
|
|
52
|
+
logger.warning(f"Invalid adapter path: {path}")
|
|
53
|
+
return None
|
|
54
|
+
try:
|
|
55
|
+
module = import_module(match.group("module"))
|
|
56
|
+
except ImportError:
|
|
57
|
+
logger.warning(f"Could not import module {match.group('module')}")
|
|
58
|
+
return None
|
|
59
|
+
try:
|
|
60
|
+
attrs = filter(None, (match.group("attr") or "Adapter").split("."))
|
|
61
|
+
ext = reduce(getattr, attrs, module)
|
|
62
|
+
except AttributeError:
|
|
63
|
+
logger.warning(f"Could not find adapter in {module.__name__}")
|
|
64
|
+
return None
|
|
65
|
+
if isinstance(ext, type) and issubclass(ext, Adapter):
|
|
66
|
+
return ext(**{k: v for k, v in adapter_config.items() if k != "$path"}) # type: ignore
|
|
67
|
+
elif isinstance(ext, Adapter):
|
|
68
|
+
return ext
|
|
69
|
+
logger.warning(f"Invalid adapter in {module.__name__}")
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
adapters: list[Adapter] = [*filter(None, map(_load_adapter, conf.adapters))]
|
|
74
|
+
|
|
75
|
+
for adapter in adapters:
|
|
76
|
+
logger.debug(f"Applying adapter {adapter}")
|
|
77
|
+
server.apply(adapter)
|
|
78
|
+
|
|
79
|
+
plugin.add_service(server)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
|
|
4
|
+
from arclet.entari import Entari
|
|
5
|
+
from arclet.entari.session import EntariProtocol
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from satori import Api, Event
|
|
8
|
+
from satori.client import Account, ApiInfo
|
|
9
|
+
from satori.exception import ActionFailed
|
|
10
|
+
from satori.server import Server
|
|
11
|
+
from starlette.datastructures import FormData, Headers, UploadFile
|
|
12
|
+
from starlette.requests import Request
|
|
13
|
+
from starlette.responses import JSONResponse
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DirectAdapterProtocol(EntariProtocol):
|
|
17
|
+
server: Server
|
|
18
|
+
|
|
19
|
+
async def call_api(
|
|
20
|
+
self, action: str | Api, params: dict | None = None, multipart: bool = False, method: str = "POST"
|
|
21
|
+
):
|
|
22
|
+
headers = {
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
"Authorization": f"Bearer {self.account.config.token or ''}",
|
|
25
|
+
"X-Platform": self.account.platform,
|
|
26
|
+
"X-Self-ID": self.account.self_id,
|
|
27
|
+
"Satori-Platform": self.account.platform,
|
|
28
|
+
"Satori-User-ID": self.account.self_id,
|
|
29
|
+
}
|
|
30
|
+
_form = None
|
|
31
|
+
if multipart:
|
|
32
|
+
if params is None:
|
|
33
|
+
raise TypeError("multipart requires params")
|
|
34
|
+
forms = []
|
|
35
|
+
headers.pop("Content-Type")
|
|
36
|
+
for k, v in params.items():
|
|
37
|
+
if isinstance(v, dict):
|
|
38
|
+
forms.append(
|
|
39
|
+
(
|
|
40
|
+
k,
|
|
41
|
+
UploadFile(
|
|
42
|
+
BytesIO(v["value"]),
|
|
43
|
+
filename=v.get("filename"),
|
|
44
|
+
headers=Headers({"content-type": v["content_type"]})
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
forms.append((k, v))
|
|
50
|
+
_form = FormData(forms)
|
|
51
|
+
_headers = [(k.lower().encode("latin-1"), v.encode("latin-1")) for k, v in headers.items()]
|
|
52
|
+
req = Request({"type": "http", "method": method, "path_params": {"action": action}, "headers": _headers})
|
|
53
|
+
if _form:
|
|
54
|
+
req._form = _form
|
|
55
|
+
else:
|
|
56
|
+
req._json = params or {}
|
|
57
|
+
resp = await self.server.http_server_handler(req)
|
|
58
|
+
if isinstance(resp, JSONResponse):
|
|
59
|
+
return json.loads(resp.body) # type: ignore
|
|
60
|
+
raise ActionFailed(resp.body.decode()) # type: ignore
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DirectAdapterServer(Server):
|
|
64
|
+
async def event_callback(self, event: Event):
|
|
65
|
+
app = Entari.current()
|
|
66
|
+
proxy_urls = []
|
|
67
|
+
for provider in self.providers:
|
|
68
|
+
proxy_urls.extend(provider.proxy_urls())
|
|
69
|
+
await super().event_callback(event)
|
|
70
|
+
login_sn = f"{event.login.user.id}@{id(self)}"
|
|
71
|
+
if login_sn not in app.accounts:
|
|
72
|
+
acc = Account(
|
|
73
|
+
event.login,
|
|
74
|
+
ApiInfo(),
|
|
75
|
+
proxy_urls,
|
|
76
|
+
DirectAdapterProtocol
|
|
77
|
+
)
|
|
78
|
+
acc.protocol.server = self
|
|
79
|
+
app.accounts[login_sn] = acc
|
|
80
|
+
logger.info(f"account added: {acc}")
|
|
81
|
+
else:
|
|
82
|
+
acc = app.accounts[login_sn]
|
|
83
|
+
await app.handle_event(acc, event)
|
|
File without changes
|