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.
@@ -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