satori-python-server 0.15.1__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.
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/.mina/server.toml +2 -2
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/PKG-INFO +5 -5
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/README.md +2 -2
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/pyproject.toml +3 -3
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/src/satori/server/__init__.py +145 -72
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/src/satori/server/adapter.py +8 -6
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/src/satori/server/model.py +14 -5
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/src/satori/server/route.py +2 -2
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/LICENSE +0 -0
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/src/satori/server/conection.py +0 -0
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/src/satori/server/formdata.py +0 -0
- {satori_python_server-0.15.1 → satori_python_server-0.16.0}/src/satori/server/utils.py +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
includes = ["src/satori/server"]
|
|
2
|
-
raw-dependencies = ["satori-python-core >= 0.
|
|
2
|
+
raw-dependencies = ["satori-python-core >= 0.16.0"]
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "satori-python-server"
|
|
@@ -18,7 +18,7 @@ dependencies = [
|
|
|
18
18
|
description = "Satori Protocol SDK for python, specify server part"
|
|
19
19
|
license = {text = "MIT"}
|
|
20
20
|
readme = "README.md"
|
|
21
|
-
requires-python = ">=3.
|
|
21
|
+
requires-python = ">=3.9"
|
|
22
22
|
classifiers = [
|
|
23
23
|
"Typing :: Typed",
|
|
24
24
|
"Development Status :: 4 - Beta",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: satori-python-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
4
4
|
Summary: Satori Protocol SDK for python, specify server 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,14 +16,14 @@ 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.
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
20
|
Requires-Dist: aiohttp>=3.9.3
|
|
21
21
|
Requires-Dist: launart>=0.8.2
|
|
22
22
|
Requires-Dist: graia-amnesia>=0.9.0
|
|
23
23
|
Requires-Dist: starlette[python-multipart]>=0.37.2
|
|
24
24
|
Requires-Dist: uvicorn[standard]>=0.28.0
|
|
25
25
|
Requires-Dist: python-multipart>=0.0.9
|
|
26
|
-
Requires-Dist: satori-python-core>=0.
|
|
26
|
+
Requires-Dist: satori-python-core>=0.16.0
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
# satori-python
|
|
@@ -78,9 +78,9 @@ pip install satori-python-server
|
|
|
78
78
|
客户端:
|
|
79
79
|
|
|
80
80
|
```python
|
|
81
|
-
from satori import EventType
|
|
81
|
+
from satori import EventType
|
|
82
82
|
from satori.event import MessageEvent
|
|
83
|
-
from satori.client import Account, App
|
|
83
|
+
from satori.client import Account, App, WebsocketsInfo
|
|
84
84
|
|
|
85
85
|
app = App(WebsocketsInfo(port=5140))
|
|
86
86
|
|
|
@@ -50,9 +50,9 @@ pip install satori-python-server
|
|
|
50
50
|
客户端:
|
|
51
51
|
|
|
52
52
|
```python
|
|
53
|
-
from satori import EventType
|
|
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,11 +11,11 @@ dependencies = [
|
|
|
11
11
|
"starlette[python-multipart]>=0.37.2",
|
|
12
12
|
"uvicorn[standard]>=0.28.0",
|
|
13
13
|
"python-multipart>=0.0.9",
|
|
14
|
-
"satori-python-core >= 0.
|
|
14
|
+
"satori-python-core >= 0.16.0",
|
|
15
15
|
]
|
|
16
16
|
description = "Satori Protocol SDK for python, specify server part"
|
|
17
17
|
readme = "README.md"
|
|
18
|
-
requires-python = ">=3.
|
|
18
|
+
requires-python = ">=3.9"
|
|
19
19
|
classifiers = [
|
|
20
20
|
"Typing :: Typed",
|
|
21
21
|
"Development Status :: 4 - Beta",
|
|
@@ -27,7 +27,7 @@ classifiers = [
|
|
|
27
27
|
"Programming Language :: Python :: 3.12",
|
|
28
28
|
"Operating System :: OS Independent",
|
|
29
29
|
]
|
|
30
|
-
version = "0.
|
|
30
|
+
version = "0.16.0"
|
|
31
31
|
|
|
32
32
|
[project.license]
|
|
33
33
|
text = "MIT"
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import functools
|
|
5
5
|
import mimetypes
|
|
6
|
+
import re
|
|
6
7
|
import secrets
|
|
7
8
|
import signal
|
|
8
9
|
import threading
|
|
@@ -13,7 +14,7 @@ from itertools import chain
|
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from tempfile import TemporaryDirectory
|
|
15
16
|
from traceback import print_exc
|
|
16
|
-
from typing import Any
|
|
17
|
+
from typing import Any
|
|
17
18
|
|
|
18
19
|
import aiohttp
|
|
19
20
|
from creart import it
|
|
@@ -23,36 +24,44 @@ from loguru import logger
|
|
|
23
24
|
from starlette.applications import Starlette
|
|
24
25
|
from starlette.datastructures import FormData as FormData
|
|
25
26
|
from starlette.requests import Request as StarletteRequest
|
|
26
|
-
from starlette.responses import
|
|
27
|
+
from starlette.responses import (
|
|
28
|
+
FileResponse,
|
|
29
|
+
HTMLResponse,
|
|
30
|
+
JSONResponse,
|
|
31
|
+
PlainTextResponse,
|
|
32
|
+
Response,
|
|
33
|
+
StreamingResponse,
|
|
34
|
+
)
|
|
27
35
|
from starlette.routing import Route, WebSocketRoute
|
|
28
36
|
from starlette.staticfiles import StaticFiles
|
|
29
37
|
from starlette.websockets import WebSocket, WebSocketDisconnect
|
|
30
38
|
from yarl import URL
|
|
31
39
|
|
|
32
|
-
from satori.config import WebhookInfo
|
|
33
40
|
from satori.const import Api
|
|
34
|
-
from satori.model import Event, ModelBase, Opcode
|
|
41
|
+
from satori.model import Event, Meta, ModelBase, Opcode
|
|
35
42
|
|
|
43
|
+
from .. import EventType
|
|
36
44
|
from .adapter import Adapter as Adapter
|
|
37
45
|
from .conection import WebsocketConnection
|
|
38
46
|
from .formdata import parse_content_disposition as parse_content_disposition
|
|
39
47
|
from .model import Provider as Provider
|
|
40
48
|
from .model import Request as Request
|
|
41
49
|
from .model import Router as Router
|
|
50
|
+
from .model import WebhookEndpoint as WebhookEndpoint
|
|
42
51
|
from .route import RouteCall as RouteCall
|
|
43
52
|
from .route import RouterMixin as RouterMixin
|
|
44
53
|
from .utils import Deque
|
|
45
54
|
|
|
46
55
|
|
|
47
56
|
async def _request_handler(
|
|
48
|
-
|
|
57
|
+
action: str, request: StarletteRequest, func: RouteCall, platform: str, self_id: str
|
|
49
58
|
):
|
|
50
|
-
if
|
|
59
|
+
if action == Api.UPLOAD_CREATE.value:
|
|
51
60
|
async with request.form() as form:
|
|
52
61
|
res = await func(
|
|
53
62
|
Request(
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
request,
|
|
64
|
+
action,
|
|
56
65
|
form,
|
|
57
66
|
platform=platform,
|
|
58
67
|
self_id=self_id,
|
|
@@ -62,8 +71,8 @@ async def _request_handler(
|
|
|
62
71
|
try:
|
|
63
72
|
res = await func(
|
|
64
73
|
Request(
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
request,
|
|
75
|
+
action,
|
|
67
76
|
await request.json(),
|
|
68
77
|
platform=platform,
|
|
69
78
|
self_id=self_id,
|
|
@@ -79,6 +88,9 @@ async def _request_handler(
|
|
|
79
88
|
return res if isinstance(res, Response) else JSONResponse(content=res)
|
|
80
89
|
|
|
81
90
|
|
|
91
|
+
INTERNAL_URL_PAT = re.compile("internal:(?P<platform>[^/]+)/(?P<self_id>[^/]+)/(?P<path>.+)")
|
|
92
|
+
|
|
93
|
+
|
|
82
94
|
class Server(Service, RouterMixin):
|
|
83
95
|
id = "satori-python.server"
|
|
84
96
|
required: set[str] = {"asgi.service/uvicorn"}
|
|
@@ -98,7 +110,7 @@ class Server(Service, RouterMixin):
|
|
|
98
110
|
path: str = "",
|
|
99
111
|
version: str = "v1",
|
|
100
112
|
token: str | None = None,
|
|
101
|
-
webhooks: list[
|
|
113
|
+
webhooks: list[WebhookEndpoint] | None = None,
|
|
102
114
|
stream_threshold: int = 16 * 1024 * 1024,
|
|
103
115
|
stream_chunk_size: int = 64 * 1024,
|
|
104
116
|
):
|
|
@@ -141,7 +153,7 @@ class Server(Service, RouterMixin):
|
|
|
141
153
|
self.resources[route_path] = file
|
|
142
154
|
|
|
143
155
|
async def event_callback(self, event: Event):
|
|
144
|
-
event.
|
|
156
|
+
event.sn = self._sequence
|
|
145
157
|
self._event_cache.append(event)
|
|
146
158
|
self._sequence += 1
|
|
147
159
|
for connection in self.connections:
|
|
@@ -155,16 +167,13 @@ class Server(Service, RouterMixin):
|
|
|
155
167
|
for hook in self.webhooks:
|
|
156
168
|
try:
|
|
157
169
|
async with self.session.post(
|
|
158
|
-
URL(
|
|
170
|
+
URL(hook.url),
|
|
159
171
|
headers={
|
|
160
172
|
"Content-Type": "application/json",
|
|
161
173
|
"Authorization": f"Bearer {hook.token or ''}",
|
|
162
|
-
"
|
|
163
|
-
"Satori-Platform": event.platform_,
|
|
164
|
-
"X-Self-ID": event.self_id_,
|
|
165
|
-
"Satori-Login-ID": event.self_id_,
|
|
174
|
+
"Satori-OpCode": str(Opcode.EVENT.value),
|
|
166
175
|
},
|
|
167
|
-
json=
|
|
176
|
+
json=event.dump(),
|
|
168
177
|
) as resp:
|
|
169
178
|
resp.raise_for_status()
|
|
170
179
|
except Exception as e:
|
|
@@ -180,17 +189,18 @@ class Server(Service, RouterMixin):
|
|
|
180
189
|
body = identity["body"]
|
|
181
190
|
token = identity["body"].get("token")
|
|
182
191
|
logins = []
|
|
192
|
+
proxy_urls = []
|
|
183
193
|
if token != self.token:
|
|
184
194
|
return await ws.close(code=3000, reason="Unauthorized")
|
|
185
195
|
for provider in self.providers:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
_login.proxy_urls.extend(provider.proxy_urls())
|
|
189
|
-
logins.extend(_logins)
|
|
196
|
+
logins.extend(await provider.get_logins())
|
|
197
|
+
proxy_urls.extend(provider.proxy_urls())
|
|
190
198
|
sequence = body.get("sequence")
|
|
191
199
|
if sequence is None:
|
|
192
200
|
sequence = -1
|
|
193
|
-
await connection.send(
|
|
201
|
+
await connection.send(
|
|
202
|
+
{"op": Opcode.READY, "body": {"logins": [lo.dump() for lo in logins], "proxy_urls": proxy_urls}}
|
|
203
|
+
)
|
|
194
204
|
self.connections.append(connection)
|
|
195
205
|
logger.debug(f"New connection: {id(connection)}")
|
|
196
206
|
heartbeat_task = asyncio.create_task(connection.heartbeat())
|
|
@@ -198,6 +208,12 @@ class Server(Service, RouterMixin):
|
|
|
198
208
|
try:
|
|
199
209
|
if sequence > -1:
|
|
200
210
|
for event in self._event_cache.after(sequence):
|
|
211
|
+
if event.type in (
|
|
212
|
+
EventType.LOGIN_ADDED,
|
|
213
|
+
EventType.LOGIN_REMOVED,
|
|
214
|
+
EventType.LOGIN_UPDATED,
|
|
215
|
+
):
|
|
216
|
+
continue
|
|
201
217
|
await connection.send({"op": Opcode.EVENT, "body": event.dump()})
|
|
202
218
|
await asyncio.sleep(0.1)
|
|
203
219
|
await any_completed(heartbeat_task, close_task)
|
|
@@ -208,19 +224,10 @@ class Server(Service, RouterMixin):
|
|
|
208
224
|
close_task.cancel()
|
|
209
225
|
self.connections.remove(connection)
|
|
210
226
|
|
|
211
|
-
async def admin_login_list_handler(self, request: StarletteRequest):
|
|
212
|
-
logins = []
|
|
213
|
-
for provider in self.providers:
|
|
214
|
-
_logins = await provider.get_logins()
|
|
215
|
-
for _login in _logins:
|
|
216
|
-
_login.proxy_urls.extend(provider.proxy_urls())
|
|
217
|
-
logins.extend(_logins)
|
|
218
|
-
return JSONResponse(content=[lo.dump() for lo in logins])
|
|
219
|
-
|
|
220
227
|
async def http_server_handler(self, request: StarletteRequest):
|
|
221
228
|
if not self._adapters and not self.routes:
|
|
222
229
|
return Response(status_code=404, content=request.path_params["method"])
|
|
223
|
-
|
|
230
|
+
action = request.path_params["action"]
|
|
224
231
|
if "X-Platform" not in request.headers and "Satori-Platform" not in request.headers:
|
|
225
232
|
return Response(status_code=401, content="Missing header X-Platform or Satori-Platform")
|
|
226
233
|
platform: str = request.headers.get("X-Platform") or request.headers.get("Satori-Platform") # type: ignore
|
|
@@ -229,35 +236,36 @@ class Server(Service, RouterMixin):
|
|
|
229
236
|
self_id: str = request.headers.get("X-Self-ID") or request.headers.get("Satori-Login-ID") # type: ignore
|
|
230
237
|
|
|
231
238
|
for _router in self._adapters:
|
|
232
|
-
if
|
|
239
|
+
if action not in _router.routes:
|
|
233
240
|
continue
|
|
234
241
|
if not _router.ensure(platform, self_id):
|
|
235
242
|
continue
|
|
236
|
-
return await _request_handler(
|
|
237
|
-
if
|
|
238
|
-
return await _request_handler(
|
|
243
|
+
return await _request_handler(action, request, _router.routes[action], platform, self_id)
|
|
244
|
+
if action in self.routes:
|
|
245
|
+
return await _request_handler(action, request, self.routes[action], platform, self_id)
|
|
239
246
|
for _router in self.routers:
|
|
240
|
-
if
|
|
247
|
+
if action not in _router.routes:
|
|
241
248
|
continue
|
|
242
|
-
return await _request_handler(
|
|
243
|
-
return Response(status_code=404, content=
|
|
249
|
+
return await _request_handler(action, request, _router.routes[action], platform, self_id)
|
|
250
|
+
return Response(status_code=404, content=action)
|
|
244
251
|
|
|
245
252
|
async def proxy_url_handler(self, request: StarletteRequest):
|
|
246
|
-
url = request.path_params["
|
|
253
|
+
url = request.path_params["internal_url"]
|
|
247
254
|
try:
|
|
248
|
-
|
|
249
|
-
if isinstance(content, Path):
|
|
250
|
-
return FileResponse(path=content)
|
|
255
|
+
resp = await self.fetch_proxy(url, request)
|
|
251
256
|
# if content size > stream_limit, use streaming response
|
|
252
|
-
if
|
|
257
|
+
if (
|
|
258
|
+
isinstance(resp, (PlainTextResponse, HTMLResponse, JSONResponse))
|
|
259
|
+
or resp.__class__ is Response
|
|
260
|
+
) and len(resp.body) > self.stream_threshold:
|
|
253
261
|
|
|
254
262
|
async def iter_content(body: bytes):
|
|
255
263
|
for i in range(0, len(body), self.stream_chunk_size):
|
|
256
264
|
yield body[i : i + self.stream_chunk_size]
|
|
257
265
|
|
|
258
|
-
return StreamingResponse(content=iter_content(
|
|
259
|
-
return
|
|
260
|
-
except FileNotFoundError as e404:
|
|
266
|
+
return StreamingResponse(content=iter_content(resp.body))
|
|
267
|
+
return resp
|
|
268
|
+
except (FileNotFoundError, NotImplementedError, AssertionError) as e404:
|
|
261
269
|
return Response(status_code=404, content=str(e404))
|
|
262
270
|
except ValueError as e403:
|
|
263
271
|
return Response(status_code=403, content=str(e403))
|
|
@@ -267,28 +275,33 @@ class Server(Service, RouterMixin):
|
|
|
267
275
|
logger.error(repr(e))
|
|
268
276
|
return Response(status_code=500, content=repr(e))
|
|
269
277
|
|
|
270
|
-
async def
|
|
278
|
+
async def fetch_proxy(self, url: str, request: StarletteRequest | None = None):
|
|
271
279
|
url = url.replace(":/", "://", 1).replace(":///", "://", 1)
|
|
272
|
-
|
|
273
|
-
if
|
|
274
|
-
if
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
280
|
+
url = urllib.parse.unquote(url)
|
|
281
|
+
if url.startswith("internal:"):
|
|
282
|
+
if mat := INTERNAL_URL_PAT.match(url):
|
|
283
|
+
platform = mat["platform"]
|
|
284
|
+
self_id = mat["self_id"]
|
|
285
|
+
path = mat["path"]
|
|
286
|
+
if path.startswith("_tmp"):
|
|
287
|
+
file = Path(self._tempdir.name) / path[5:]
|
|
278
288
|
if file.exists():
|
|
279
|
-
return file
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
289
|
+
return FileResponse(file)
|
|
290
|
+
raise FileNotFoundError(f"{path[5:]} not found")
|
|
291
|
+
assert request is not None
|
|
292
|
+
for provider in self.providers:
|
|
293
|
+
if provider.ensure(platform, self_id):
|
|
294
|
+
return await provider.handle_internal(
|
|
295
|
+
Request(request, "internal", {}, platform=platform, self_id=self_id), path
|
|
296
|
+
)
|
|
297
|
+
raise NotImplementedError(f"Login with {platform}:{self_id} not found")
|
|
298
|
+
raise TypeError(f"Invalid internal url: {url}")
|
|
287
299
|
|
|
300
|
+
for provider in self.providers:
|
|
288
301
|
for proxy_url_pf in provider.proxy_urls():
|
|
289
302
|
if not url.startswith(proxy_url_pf):
|
|
290
303
|
continue
|
|
291
|
-
resp = await provider.
|
|
304
|
+
resp = await provider.handle_proxied(proxy_url_pf, url)
|
|
292
305
|
if resp is None:
|
|
293
306
|
continue
|
|
294
307
|
return resp
|
|
@@ -317,18 +330,54 @@ class Server(Service, RouterMixin):
|
|
|
317
330
|
with file.resolve().open("wb+") as f:
|
|
318
331
|
f.write(await data.read())
|
|
319
332
|
|
|
320
|
-
res[disp["name"]] = f"
|
|
333
|
+
res[disp["name"]] = f"internal:{request.platform}/{request.self_id}/_tmp/{filename}"
|
|
321
334
|
|
|
322
335
|
loop = asyncio.get_running_loop()
|
|
323
336
|
loop.call_later(600, file.unlink, True)
|
|
324
337
|
return res
|
|
325
338
|
|
|
339
|
+
async def meta_get_handler(self, request: StarletteRequest):
|
|
340
|
+
logins = []
|
|
341
|
+
proxy_urls = []
|
|
342
|
+
for provider in self.providers:
|
|
343
|
+
logins.extend(await provider.get_logins())
|
|
344
|
+
proxy_urls.extend(provider.proxy_urls())
|
|
345
|
+
return JSONResponse(content=Meta(logins, proxy_urls).dump())
|
|
346
|
+
|
|
347
|
+
async def webhook_create_handler(self, request: StarletteRequest):
|
|
348
|
+
body = await request.json()
|
|
349
|
+
url = body["url"]
|
|
350
|
+
token = body.get("token")
|
|
351
|
+
self.webhooks.append(WebhookEndpoint(url, token))
|
|
352
|
+
proxy_urls = []
|
|
353
|
+
for provider in self.providers:
|
|
354
|
+
proxy_urls.extend(provider.proxy_urls())
|
|
355
|
+
async with self.session.post(
|
|
356
|
+
URL(url),
|
|
357
|
+
headers={
|
|
358
|
+
"Content-Type": "application/json",
|
|
359
|
+
"Authorization": f"Bearer {token or ''}",
|
|
360
|
+
"Satori-OpCode": str(Opcode.META.value),
|
|
361
|
+
},
|
|
362
|
+
json={"proxy_urls": proxy_urls},
|
|
363
|
+
) as resp:
|
|
364
|
+
resp.raise_for_status()
|
|
365
|
+
return Response()
|
|
366
|
+
|
|
367
|
+
async def webhook_delete_handler(self, request: StarletteRequest):
|
|
368
|
+
body = await request.json()
|
|
369
|
+
url = body["url"]
|
|
370
|
+
for endpoint in self.webhooks:
|
|
371
|
+
if endpoint.url == url:
|
|
372
|
+
self.webhooks.remove(endpoint)
|
|
373
|
+
return Response()
|
|
374
|
+
|
|
326
375
|
async def launch(self, manager: Launart):
|
|
327
376
|
self.session = aiohttp.ClientSession()
|
|
328
377
|
for _adapter in self._adapters:
|
|
329
378
|
manager.add_component(_adapter)
|
|
330
379
|
|
|
331
|
-
if Api.UPLOAD_CREATE.value not in self.routes
|
|
380
|
+
if Api.UPLOAD_CREATE.value not in self.routes:
|
|
332
381
|
self.routes[Api.UPLOAD_CREATE.value] = self._default_upload_create_handler
|
|
333
382
|
|
|
334
383
|
async with self.stage("preparing"):
|
|
@@ -338,19 +387,29 @@ class Server(Service, RouterMixin):
|
|
|
338
387
|
*chain.from_iterable(ada.get_routes() for ada in self._adapters),
|
|
339
388
|
WebSocketRoute(f"{self.path}/{self.version}/events", self.websocket_server_handler),
|
|
340
389
|
Route(
|
|
341
|
-
f"{self.path}/{self.version}/
|
|
342
|
-
self.
|
|
390
|
+
f"{self.path}/{self.version}/meta",
|
|
391
|
+
self.meta_get_handler,
|
|
392
|
+
methods=["POST"],
|
|
393
|
+
),
|
|
394
|
+
Route(
|
|
395
|
+
f"{self.path}/{self.version}/meta/webhook.create",
|
|
396
|
+
self.webhook_create_handler,
|
|
343
397
|
methods=["POST"],
|
|
344
398
|
),
|
|
345
399
|
Route(
|
|
346
|
-
f"{self.path}/{self.version}/
|
|
400
|
+
f"{self.path}/{self.version}/meta/webhook.delete",
|
|
401
|
+
self.webhook_delete_handler,
|
|
402
|
+
methods=["POST"],
|
|
403
|
+
),
|
|
404
|
+
Route(
|
|
405
|
+
f"{self.path}/{self.version}/proxy/{{internal_url:path}}",
|
|
347
406
|
self.proxy_url_handler,
|
|
348
|
-
methods=["GET"],
|
|
407
|
+
methods=["GET", "POST", "PUT", "DELETE"],
|
|
349
408
|
),
|
|
350
409
|
Route(
|
|
351
|
-
f"{self.path}/{self.version}/{{
|
|
410
|
+
f"{self.path}/{self.version}/{{action:path}}",
|
|
352
411
|
self.http_server_handler,
|
|
353
|
-
methods=["POST"],
|
|
412
|
+
methods=["GET", "POST", "PUT", "DELETE"],
|
|
354
413
|
),
|
|
355
414
|
]
|
|
356
415
|
)
|
|
@@ -363,6 +422,20 @@ class Server(Service, RouterMixin):
|
|
|
363
422
|
await self.event_callback(event)
|
|
364
423
|
|
|
365
424
|
async with self.stage("blocking"):
|
|
425
|
+
proxy_urls = []
|
|
426
|
+
for provider in self.providers:
|
|
427
|
+
proxy_urls.extend(provider.proxy_urls())
|
|
428
|
+
for hook in self.webhooks:
|
|
429
|
+
async with self.session.post(
|
|
430
|
+
URL(hook.url),
|
|
431
|
+
headers={
|
|
432
|
+
"Content-Type": "application/json",
|
|
433
|
+
"Authorization": f"Bearer {hook.token or ''}",
|
|
434
|
+
"Satori-OpCode": str(Opcode.META.value),
|
|
435
|
+
},
|
|
436
|
+
json={"proxy_urls": proxy_urls},
|
|
437
|
+
) as resp:
|
|
438
|
+
resp.raise_for_status()
|
|
366
439
|
await any_completed(
|
|
367
440
|
manager.status.wait_for_sigexit(),
|
|
368
441
|
*(event_task(provider) for provider in self.providers),
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
2
|
from collections.abc import AsyncIterator
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
4
|
|
|
5
5
|
from launart import Service
|
|
6
|
+
from starlette.responses import Response
|
|
6
7
|
from starlette.routing import BaseRoute
|
|
7
8
|
|
|
8
|
-
from ..model import Event,
|
|
9
|
+
from ..model import Event, Login
|
|
10
|
+
from .model import Request
|
|
9
11
|
from .route import RouterMixin
|
|
10
12
|
from .utils import ctx
|
|
11
13
|
|
|
@@ -30,14 +32,14 @@ class Adapter(Service, RouterMixin):
|
|
|
30
32
|
return []
|
|
31
33
|
|
|
32
34
|
@abstractmethod
|
|
33
|
-
async def
|
|
35
|
+
async def handle_internal(self, request: Request, path: str) -> Response: ...
|
|
34
36
|
|
|
35
|
-
async def
|
|
37
|
+
async def handle_proxied(self, prefix: str, url: str) -> Optional[Response]:
|
|
36
38
|
async with self.server.session.get(url, ssl=ctx) as resp:
|
|
37
|
-
return await resp.read()
|
|
39
|
+
return Response(await resp.read())
|
|
38
40
|
|
|
39
41
|
@abstractmethod
|
|
40
|
-
async def get_logins(self) -> list[
|
|
42
|
+
async def get_logins(self) -> list[Login]: ...
|
|
41
43
|
|
|
42
44
|
def __init__(self):
|
|
43
45
|
super().__init__()
|
|
@@ -2,8 +2,11 @@ from collections.abc import AsyncIterator
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Generic, Optional, Protocol, TypeVar, Union, runtime_checkable
|
|
4
4
|
|
|
5
|
+
from starlette.requests import Request as StarletteRequest
|
|
6
|
+
from starlette.responses import Response
|
|
7
|
+
|
|
5
8
|
from satori.const import Api
|
|
6
|
-
from satori.model import Event, Login
|
|
9
|
+
from satori.model import Event, Login
|
|
7
10
|
|
|
8
11
|
if TYPE_CHECKING:
|
|
9
12
|
from .route import RouteCall
|
|
@@ -15,7 +18,7 @@ TP = TypeVar("TP")
|
|
|
15
18
|
|
|
16
19
|
@dataclass
|
|
17
20
|
class Request(Generic[TP]):
|
|
18
|
-
|
|
21
|
+
origin: StarletteRequest
|
|
19
22
|
action: str
|
|
20
23
|
params: TP
|
|
21
24
|
platform: str
|
|
@@ -26,18 +29,24 @@ class Request(Generic[TP]):
|
|
|
26
29
|
class Provider(Protocol):
|
|
27
30
|
def publisher(self) -> AsyncIterator[Event]: ...
|
|
28
31
|
|
|
29
|
-
async def get_logins(self) ->
|
|
32
|
+
async def get_logins(self) -> list[Login]: ...
|
|
30
33
|
|
|
31
34
|
@staticmethod
|
|
32
35
|
def proxy_urls() -> list[str]: ...
|
|
33
36
|
|
|
34
37
|
def ensure(self, platform: str, self_id: str) -> bool: ...
|
|
35
38
|
|
|
36
|
-
async def
|
|
39
|
+
async def handle_internal(self, request: Request, path: str) -> Response: ...
|
|
37
40
|
|
|
38
|
-
async def
|
|
41
|
+
async def handle_proxied(self, prefix: str, url: str) -> Optional[Response]: ...
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
@runtime_checkable
|
|
42
45
|
class Router(Protocol):
|
|
43
46
|
routes: dict[str, "RouteCall[Any, Any]"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class WebhookEndpoint:
|
|
51
|
+
url: str
|
|
52
|
+
token: Optional[str] = None
|
|
@@ -8,7 +8,7 @@ from satori.model import (
|
|
|
8
8
|
Channel,
|
|
9
9
|
Direction,
|
|
10
10
|
Guild,
|
|
11
|
-
|
|
11
|
+
Login,
|
|
12
12
|
Member,
|
|
13
13
|
MessageObject,
|
|
14
14
|
ModelBase,
|
|
@@ -241,7 +241,7 @@ class ReactionListParam(TypedDict):
|
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
REACTION_LIST: TypeAlias = RouteCall[ReactionListParam, Union[PageResult[User], dict[str, Any]]]
|
|
244
|
-
LOGIN_GET: TypeAlias = RouteCall[Any, Union[
|
|
244
|
+
LOGIN_GET: TypeAlias = RouteCall[Any, Union[Login, dict[str, Any]]]
|
|
245
245
|
|
|
246
246
|
|
|
247
247
|
class UserGetParam(TypedDict):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|