satori-python 0.15.2__tar.gz → 0.16.1__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-0.15.2 → satori_python-0.16.1}/PKG-INFO +3 -3
- {satori_python-0.15.2 → satori_python-0.16.1}/README.md +2 -2
- {satori_python-0.15.2 → satori_python-0.16.1}/pyproject.toml +1 -1
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/__init__.py +1 -3
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/client/__init__.py +17 -18
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/client/account.py +20 -17
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/client/account.pyi +33 -11
- satori_python-0.16.1/src/satori/client/network/base.py +35 -0
- satori_python-0.16.1/src/satori/client/network/webhook.py +111 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/client/network/websocket.py +44 -35
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/client/protocol.py +41 -13
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/model.py +100 -69
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/server/__init__.py +148 -75
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/server/adapter.py +8 -6
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/server/model.py +14 -5
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/server/route.py +2 -2
- satori_python-0.15.2/src/satori/client/network/base.py +0 -56
- satori_python-0.15.2/src/satori/client/network/webhook.py +0 -131
- {satori_python-0.15.2 → satori_python-0.16.1}/LICENSE +0 -0
- {satori_python-0.15.2/src/satori → satori_python-0.16.1/src/satori/client}/config.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/client/network/__init__.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/client/network/util.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/const.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/element.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/event.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/exception.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/parser.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/server/conection.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/server/formdata.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/server/utils.py +0 -0
- {satori_python-0.15.2 → satori_python-0.16.1}/src/satori/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: satori-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.1
|
|
4
4
|
Summary: Satori Protocol SDK for python
|
|
5
5
|
Home-page: https://github.com/RF-Tar-Railt/satori-python
|
|
6
6
|
Author-Email: RF-Tar-Railt <rf_tar_railt@qq.com>
|
|
@@ -80,9 +80,9 @@ pip install satori-python-server
|
|
|
80
80
|
客户端:
|
|
81
81
|
|
|
82
82
|
```python
|
|
83
|
-
from satori import EventType
|
|
83
|
+
from satori import EventType
|
|
84
84
|
from satori.event import MessageEvent
|
|
85
|
-
from satori.client import Account, App
|
|
85
|
+
from satori.client import Account, App, WebsocketsInfo
|
|
86
86
|
|
|
87
87
|
app = App(WebsocketsInfo(port=5140))
|
|
88
88
|
|
|
@@ -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
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from .config import WebhookInfo as WebhookInfo
|
|
2
|
-
from .config import WebsocketsInfo as WebsocketsInfo
|
|
3
1
|
from .const import Api as Api
|
|
4
2
|
from .const import EventType as EventType
|
|
5
3
|
from .element import At as At
|
|
@@ -43,4 +41,4 @@ from .model import Role as Role
|
|
|
43
41
|
from .model import Upload as Upload
|
|
44
42
|
from .model import User as User
|
|
45
43
|
|
|
46
|
-
__version__ = "0.
|
|
44
|
+
__version__ = "0.16.1"
|
|
@@ -14,12 +14,14 @@ from launart import Launart, Service, any_completed
|
|
|
14
14
|
from loguru import logger
|
|
15
15
|
|
|
16
16
|
from satori import event as events
|
|
17
|
-
from satori.config import Config, WebhookInfo, WebsocketsInfo
|
|
18
17
|
from satori.const import EventType
|
|
19
18
|
from satori.model import Event, LoginStatus
|
|
20
19
|
|
|
21
20
|
from .account import Account as Account
|
|
22
21
|
from .account import ApiInfo as ApiInfo
|
|
22
|
+
from .config import Config
|
|
23
|
+
from .config import WebhookInfo as WebhookInfo
|
|
24
|
+
from .config import WebsocketsInfo as WebsocketsInfo
|
|
23
25
|
from .network.base import BaseNetwork as BaseNetwork
|
|
24
26
|
from .network.webhook import WebhookNetwork
|
|
25
27
|
from .network.websocket import WsNetwork
|
|
@@ -187,16 +189,15 @@ class App(Service):
|
|
|
187
189
|
async def post(self, event: Event, conn: BaseNetwork):
|
|
188
190
|
if not self.event_callbacks:
|
|
189
191
|
return
|
|
190
|
-
|
|
191
|
-
if
|
|
192
|
+
login_sn = f"{event.login.sn}@{id(conn)}"
|
|
193
|
+
if login_sn not in self.accounts:
|
|
192
194
|
if event.type == EventType.LOGIN_ADDED:
|
|
193
195
|
if TYPE_CHECKING:
|
|
194
196
|
assert isinstance(event, events.LoginEvent)
|
|
195
197
|
account = Account(
|
|
196
|
-
event.platform_,
|
|
197
|
-
event.self_id_,
|
|
198
198
|
event.login,
|
|
199
199
|
conn.config,
|
|
200
|
+
conn.proxy_urls,
|
|
200
201
|
self.default_api_cls,
|
|
201
202
|
)
|
|
202
203
|
logger.info(f"account added: {account}")
|
|
@@ -205,26 +206,23 @@ class App(Service):
|
|
|
205
206
|
if event.login.status == LoginStatus.ONLINE
|
|
206
207
|
else account.connected.clear()
|
|
207
208
|
)
|
|
208
|
-
self.accounts[
|
|
209
|
-
conn.accounts[
|
|
210
|
-
await self.account_update(account,
|
|
211
|
-
await self.account_update(account, LoginStatus.ONLINE)
|
|
209
|
+
self.accounts[login_sn] = account
|
|
210
|
+
conn.accounts[login_sn] = account
|
|
211
|
+
await self.account_update(account, event.login.status)
|
|
212
212
|
elif event.type == EventType.LOGIN_UPDATED:
|
|
213
213
|
if TYPE_CHECKING:
|
|
214
214
|
assert isinstance(event, events.LoginEvent)
|
|
215
215
|
if event.login.status == LoginStatus.ONLINE:
|
|
216
216
|
account = Account(
|
|
217
|
-
event.platform_,
|
|
218
|
-
event.self_id_,
|
|
219
217
|
event.login,
|
|
220
218
|
conn.config,
|
|
219
|
+
conn.proxy_urls,
|
|
221
220
|
self.default_api_cls,
|
|
222
221
|
)
|
|
223
222
|
logger.info(f"account added: {account}")
|
|
224
223
|
account.connected.set()
|
|
225
|
-
self.accounts[
|
|
226
|
-
conn.accounts[
|
|
227
|
-
await self.account_update(account, LoginStatus.CONNECT)
|
|
224
|
+
self.accounts[login_sn] = account
|
|
225
|
+
conn.accounts[login_sn] = account
|
|
228
226
|
await self.account_update(account, LoginStatus.ONLINE)
|
|
229
227
|
else:
|
|
230
228
|
logger.warning(f"Received event for unknown account: {event}")
|
|
@@ -233,7 +231,7 @@ class App(Service):
|
|
|
233
231
|
logger.warning(f"Received event for unknown account: {event}")
|
|
234
232
|
return
|
|
235
233
|
else:
|
|
236
|
-
account = self.accounts[
|
|
234
|
+
account = self.accounts[login_sn]
|
|
237
235
|
if event.type == EventType.LOGIN_UPDATED:
|
|
238
236
|
if TYPE_CHECKING:
|
|
239
237
|
assert isinstance(event, events.LoginEvent)
|
|
@@ -243,6 +241,7 @@ class App(Service):
|
|
|
243
241
|
if event.login.status in (LoginStatus.ONLINE, LoginStatus.CONNECT)
|
|
244
242
|
else account.connected.clear()
|
|
245
243
|
)
|
|
244
|
+
await self.account_update(account, event.login.status)
|
|
246
245
|
|
|
247
246
|
await asyncio.gather(*(callback(account, event) for callback in self.event_callbacks))
|
|
248
247
|
|
|
@@ -251,9 +250,9 @@ class App(Service):
|
|
|
251
250
|
assert isinstance(event, events.LoginEvent)
|
|
252
251
|
logger.info(f"account removed: {account}")
|
|
253
252
|
account.connected.clear()
|
|
254
|
-
await self.account_update(account, LoginStatus.
|
|
255
|
-
del self.accounts[
|
|
256
|
-
del conn.accounts[
|
|
253
|
+
await self.account_update(account, LoginStatus.OFFLINE)
|
|
254
|
+
del self.accounts[login_sn]
|
|
255
|
+
del conn.accounts[login_sn]
|
|
257
256
|
|
|
258
257
|
async def launch(self, manager: Launart):
|
|
259
258
|
for conn in self.connections:
|
|
@@ -6,7 +6,7 @@ from typing import Generic, TypeVar
|
|
|
6
6
|
|
|
7
7
|
from yarl import URL
|
|
8
8
|
|
|
9
|
-
from satori.model import
|
|
9
|
+
from satori.model import Login
|
|
10
10
|
|
|
11
11
|
from .protocol import ApiProtocol
|
|
12
12
|
|
|
@@ -33,44 +33,47 @@ class ApiInfo:
|
|
|
33
33
|
class Account(Generic[TP]):
|
|
34
34
|
def __init__(
|
|
35
35
|
self,
|
|
36
|
-
|
|
37
|
-
self_id: str,
|
|
38
|
-
self_info: LoginType,
|
|
36
|
+
login: Login,
|
|
39
37
|
config: ApiInfo,
|
|
38
|
+
proxy_urls: list[str],
|
|
40
39
|
protocol_cls: type[TP] = ApiProtocol,
|
|
41
40
|
):
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.self_info =
|
|
41
|
+
self.sn = login.sn
|
|
42
|
+
self.adapter = login.adapter
|
|
43
|
+
self.self_info = login
|
|
45
44
|
self.config = config
|
|
45
|
+
self.proxy_urls = proxy_urls
|
|
46
46
|
self.protocol = protocol_cls(self) # type: ignore
|
|
47
47
|
self.connected = asyncio.Event()
|
|
48
48
|
|
|
49
|
+
@property
|
|
50
|
+
def platform(self):
|
|
51
|
+
return self.self_info.platform or "satori"
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def self_id(self):
|
|
55
|
+
return self.self_info.id
|
|
56
|
+
|
|
49
57
|
def custom(
|
|
50
58
|
self, config: ApiInfo | None = None, protocol_cls: type[TP1] = ApiProtocol, **kwargs
|
|
51
59
|
) -> "Account[TP1]":
|
|
52
60
|
return Account(
|
|
53
|
-
self.platform,
|
|
54
|
-
self.self_id,
|
|
55
61
|
self.self_info,
|
|
56
62
|
config or (ApiInfo(**kwargs) if kwargs else self.config),
|
|
63
|
+
self.proxy_urls,
|
|
57
64
|
protocol_cls, # type: ignore
|
|
58
65
|
)
|
|
59
66
|
|
|
60
|
-
@property
|
|
61
|
-
def identity(self):
|
|
62
|
-
return f"{self.platform}/{self.self_id}"
|
|
63
|
-
|
|
64
67
|
def ensure_url(self, url: str) -> URL:
|
|
65
68
|
"""确定链接形式。
|
|
66
69
|
|
|
67
70
|
若链接符合以下条件之一,则返回链接的代理形式 ({host}/{path}/{version}/proxy/{url}):
|
|
68
|
-
- 链接以 "
|
|
69
|
-
- 链接开头出现在
|
|
71
|
+
- 链接以 "internal:" 开头
|
|
72
|
+
- 链接开头出现在 proxy_urls 中的某一项
|
|
70
73
|
"""
|
|
71
|
-
if url.startswith("
|
|
74
|
+
if url.startswith("internal:"):
|
|
72
75
|
return self.config.api_base / "proxy" / url.lstrip("/")
|
|
73
|
-
for proxy_url in self.
|
|
76
|
+
for proxy_url in self.proxy_urls:
|
|
74
77
|
if url.startswith(proxy_url):
|
|
75
78
|
return self.config.api_base / "proxy" / url.lstrip("/")
|
|
76
79
|
return URL(url)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from collections.abc import Iterable
|
|
3
3
|
from typing import Any, Generic, Protocol, TypeVar, overload
|
|
4
|
+
from typing_extensions import deprecated
|
|
4
5
|
|
|
5
6
|
from yarl import URL
|
|
6
7
|
|
|
@@ -10,10 +11,11 @@ from satori.model import (
|
|
|
10
11
|
Direction,
|
|
11
12
|
Event,
|
|
12
13
|
Guild,
|
|
13
|
-
|
|
14
|
+
Login,
|
|
14
15
|
Member,
|
|
15
16
|
MessageObject,
|
|
16
17
|
MessageReceipt,
|
|
18
|
+
Meta,
|
|
17
19
|
Order,
|
|
18
20
|
PageDequeResult,
|
|
19
21
|
PageResult,
|
|
@@ -39,23 +41,25 @@ class ApiInfo(Api):
|
|
|
39
41
|
): ...
|
|
40
42
|
|
|
41
43
|
class Account(Generic[TP]):
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
self_info:
|
|
44
|
+
sn: str
|
|
45
|
+
adapter: str
|
|
46
|
+
self_info: Login
|
|
47
|
+
proxy_urls: list[str]
|
|
45
48
|
config: Api
|
|
46
49
|
protocol: TP
|
|
47
50
|
connected: asyncio.Event
|
|
48
51
|
|
|
49
52
|
def __init__(
|
|
50
53
|
self,
|
|
51
|
-
|
|
52
|
-
self_id: str,
|
|
53
|
-
self_info: LoginType,
|
|
54
|
+
login: Login,
|
|
54
55
|
config: Api,
|
|
56
|
+
proxy_urls: list[str],
|
|
55
57
|
protocol_cls: type[TP] = ApiProtocol,
|
|
56
58
|
): ...
|
|
57
59
|
@property
|
|
58
|
-
def
|
|
60
|
+
def platform(self) -> str: ...
|
|
61
|
+
@property
|
|
62
|
+
def self_id(self) -> str: ...
|
|
59
63
|
@overload
|
|
60
64
|
def custom(self, config: Api, protocol_cls: type[TP1] = ApiProtocol) -> Account[TP1]: ...
|
|
61
65
|
@overload
|
|
@@ -496,7 +500,7 @@ class Account(Generic[TP]):
|
|
|
496
500
|
PageResult[User]: `User` 的分页列表
|
|
497
501
|
"""
|
|
498
502
|
|
|
499
|
-
async def login_get(self) ->
|
|
503
|
+
async def login_get(self) -> Login:
|
|
500
504
|
"""获取当前登录信息。返回一个 `Login` 对象。
|
|
501
505
|
|
|
502
506
|
Returns:
|
|
@@ -535,21 +539,36 @@ class Account(Generic[TP]):
|
|
|
535
539
|
None: 该方法无返回值
|
|
536
540
|
"""
|
|
537
541
|
|
|
538
|
-
async def internal(self, action: str, **kwargs) -> Any:
|
|
542
|
+
async def internal(self, action: str, method: str = "POST", **kwargs) -> Any:
|
|
539
543
|
"""内部接口调用。
|
|
540
544
|
|
|
541
545
|
Args:
|
|
542
546
|
action (str): 内部接口名称
|
|
547
|
+
method (str, optional): 请求方法,默认为 POST
|
|
543
548
|
**kwargs: 参数
|
|
544
549
|
"""
|
|
545
550
|
|
|
546
|
-
async def
|
|
551
|
+
async def meta_get(self) -> Meta:
|
|
552
|
+
"""获取元信息。返回一个 `Meta` 对象。
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
Meta: `Meta` 对象
|
|
556
|
+
"""
|
|
557
|
+
|
|
558
|
+
@deprecated("Use `meta_get` instead")
|
|
559
|
+
async def admin_login_list(self) -> list[Login]:
|
|
547
560
|
"""获取登录信息列表。返回一个 `Login` 对象构成的数组。
|
|
548
561
|
|
|
549
562
|
Returns:
|
|
550
563
|
list[Login]: `Login` 对象构成的数组
|
|
551
564
|
"""
|
|
552
565
|
|
|
566
|
+
async def webhook_create(self, url: str, token: str | None = None):
|
|
567
|
+
"""创建 Webhook。"""
|
|
568
|
+
|
|
569
|
+
async def webhook_delete(self, url: str):
|
|
570
|
+
"""删除 Webhook。"""
|
|
571
|
+
|
|
553
572
|
@overload
|
|
554
573
|
async def upload_create(self, *uploads: Upload) -> list[str]: ...
|
|
555
574
|
@overload
|
|
@@ -564,3 +583,6 @@ class Account(Generic[TP]):
|
|
|
564
583
|
|
|
565
584
|
async def download(self, url: str):
|
|
566
585
|
"""访问内部链接。"""
|
|
586
|
+
|
|
587
|
+
async def request_internal(self, url: str, method: str = "GET", **kwargs) -> dict:
|
|
588
|
+
"""访问内部链接。"""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import TYPE_CHECKING, Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
from launart import Service
|
|
7
|
+
|
|
8
|
+
from ..config import Config as Config
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .. import App
|
|
12
|
+
|
|
13
|
+
TConfig = TypeVar("TConfig", bound=Config)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseNetwork(Generic[TConfig], Service):
|
|
17
|
+
close_signal: asyncio.Event
|
|
18
|
+
sequence: int
|
|
19
|
+
|
|
20
|
+
def __init__(self, app: App, config: TConfig):
|
|
21
|
+
super().__init__()
|
|
22
|
+
self.app = app
|
|
23
|
+
self.config = config
|
|
24
|
+
self.accounts = {}
|
|
25
|
+
self.close_signal = asyncio.Event()
|
|
26
|
+
self.sequence = -1
|
|
27
|
+
self.proxy_urls = []
|
|
28
|
+
|
|
29
|
+
async def wait_for_available(self): ...
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def alive(self) -> bool: ...
|
|
33
|
+
|
|
34
|
+
async def connection_closed(self):
|
|
35
|
+
self.close_signal.set()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from aiohttp import web
|
|
6
|
+
from launart.manager import Launart
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
from satori.model import Event, LoginStatus, MetaPayload, Opcode
|
|
10
|
+
|
|
11
|
+
from ..account import Account
|
|
12
|
+
from ..config import WebhookInfo as WebhookInfo
|
|
13
|
+
from .base import BaseNetwork
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WebhookNetwork(BaseNetwork[WebhookInfo]):
|
|
17
|
+
required: set[str] = set()
|
|
18
|
+
stages: set[str] = {"preparing", "blocking", "cleanup"}
|
|
19
|
+
wsgi: web.Application | None = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def id(self):
|
|
23
|
+
return f"satori/network/webhook/{self.config.identity}#{id(self)}"
|
|
24
|
+
|
|
25
|
+
async def handle_request(self, req: web.Request):
|
|
26
|
+
header = req.headers
|
|
27
|
+
auth = header["Authorization"]
|
|
28
|
+
if not auth.startswith("Bearer"):
|
|
29
|
+
return web.Response(status=401)
|
|
30
|
+
token = auth.split(" ", 1)[1]
|
|
31
|
+
if self.config.token and self.config.token != token:
|
|
32
|
+
return web.Response(status=401)
|
|
33
|
+
op_code = int(header.get("Satori-OpCode", "0"))
|
|
34
|
+
body = await req.json()
|
|
35
|
+
if op_code == Opcode.META:
|
|
36
|
+
payload = MetaPayload.parse(body)
|
|
37
|
+
self.proxy_urls = payload.proxy_urls
|
|
38
|
+
return web.Response()
|
|
39
|
+
if op_code != Opcode.EVENT:
|
|
40
|
+
return web.Response(status=202)
|
|
41
|
+
# if "X-Platform" in header and "X-Self-ID" in header:
|
|
42
|
+
# platform = header["X-Platform"]
|
|
43
|
+
# self_id = header["X-Self-ID"]
|
|
44
|
+
# elif "Satori-Platform" in header and "Satori-User-ID" in header:
|
|
45
|
+
# platform = header["Satori-Platform"]
|
|
46
|
+
# self_id = header["Satori-User-ID"]
|
|
47
|
+
# else:
|
|
48
|
+
# return web.Response(status=400)
|
|
49
|
+
try:
|
|
50
|
+
event = Event.parse(body)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
if (
|
|
53
|
+
"self_id" in body
|
|
54
|
+
or ("login" in body and "self_id" in body["login"])
|
|
55
|
+
or ("login" in body and "user" in body["login"] and "self_id" in body["login"]["user"])
|
|
56
|
+
):
|
|
57
|
+
logger.warning(f"Failed to parse event: {body}\nCaused by {e!r}")
|
|
58
|
+
else:
|
|
59
|
+
logger.trace(f"Failed to parse event: {body}\nCaused by {e!r}")
|
|
60
|
+
return web.Response(status=500, reason=f"Failed to parse event caused by {e!r}")
|
|
61
|
+
else:
|
|
62
|
+
logger.trace(f"Received event: {event}")
|
|
63
|
+
self.sequence = event.sn
|
|
64
|
+
login_sn = f"{event.login.sn}@{id(self)}"
|
|
65
|
+
if login_sn in self.app.accounts:
|
|
66
|
+
account = self.app.accounts[login_sn]
|
|
67
|
+
account.connected.set()
|
|
68
|
+
account.config = self.config
|
|
69
|
+
else:
|
|
70
|
+
account = Account(event.login, self.config, self.proxy_urls)
|
|
71
|
+
logger.info(f"account registered: {account}")
|
|
72
|
+
account.connected.set()
|
|
73
|
+
self.app.accounts[login_sn] = account
|
|
74
|
+
self.accounts[login_sn] = account
|
|
75
|
+
await self.app.account_update(account, LoginStatus.ONLINE)
|
|
76
|
+
asyncio.create_task(self.app.post(event, self))
|
|
77
|
+
return web.Response()
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def alive(self):
|
|
81
|
+
return self.wsgi is not None
|
|
82
|
+
|
|
83
|
+
async def wait_for_available(self):
|
|
84
|
+
await self.status.wait_for_available()
|
|
85
|
+
|
|
86
|
+
async def launch(self, manager: Launart):
|
|
87
|
+
async with self.stage("preparing"):
|
|
88
|
+
logger.info(f"starting server on {self.config.identity}")
|
|
89
|
+
self.wsgi = web.Application(logger=logger) # type: ignore
|
|
90
|
+
self.wsgi.router.freeze = lambda: None # monkey patch
|
|
91
|
+
self.wsgi.router.add_post(self.config.path, self.handle_request)
|
|
92
|
+
runner = web.AppRunner(self.wsgi)
|
|
93
|
+
await runner.setup()
|
|
94
|
+
site = web.TCPSite(runner, self.config.host, self.config.port)
|
|
95
|
+
|
|
96
|
+
async with self.stage("blocking"):
|
|
97
|
+
await site.start()
|
|
98
|
+
await manager.status.wait_for_sigexit()
|
|
99
|
+
logger.info(f"{self.id} Webhook server exiting...")
|
|
100
|
+
self.close_signal.set()
|
|
101
|
+
for v in list(self.app.accounts.values()):
|
|
102
|
+
if (identity := f"{v.sn}@{id(self)}") in self.accounts:
|
|
103
|
+
v.connected.clear()
|
|
104
|
+
await self.app.account_update(v, LoginStatus.OFFLINE)
|
|
105
|
+
del self.app.accounts[identity]
|
|
106
|
+
del self.accounts[identity]
|
|
107
|
+
|
|
108
|
+
async with self.stage("cleanup"):
|
|
109
|
+
await site.stop()
|
|
110
|
+
await self.wsgi.shutdown()
|
|
111
|
+
await self.wsgi.cleanup()
|
|
@@ -10,10 +10,10 @@ from launart.manager import Launart
|
|
|
10
10
|
from launart.utilles import any_completed
|
|
11
11
|
from loguru import logger
|
|
12
12
|
|
|
13
|
-
from satori.
|
|
14
|
-
from satori.model import Login, LoginPreview, LoginStatus, Opcode
|
|
13
|
+
from satori.model import Event, Identify, LoginStatus, Opcode, Ready
|
|
15
14
|
|
|
16
15
|
from ..account import Account
|
|
16
|
+
from ..config import WebsocketsInfo as WebsocketsInfo
|
|
17
17
|
from .base import BaseNetwork
|
|
18
18
|
|
|
19
19
|
|
|
@@ -27,6 +27,26 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
27
27
|
|
|
28
28
|
connection: aiohttp.ClientWebSocketResponse | None = None
|
|
29
29
|
|
|
30
|
+
def post_event(self, body: dict):
|
|
31
|
+
async def event_parse_task(raw: dict):
|
|
32
|
+
try:
|
|
33
|
+
event = Event.parse(raw)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
if (
|
|
36
|
+
"self_id" in raw
|
|
37
|
+
or ("login" in raw and "self_id" in raw["login"])
|
|
38
|
+
or ("login" in raw and "user" in raw["login"] and "self_id" in raw["login"]["user"])
|
|
39
|
+
):
|
|
40
|
+
logger.warning(f"Failed to parse event: {raw}\nCaused by {e!r}")
|
|
41
|
+
else:
|
|
42
|
+
logger.trace(f"Failed to parse event: {raw}\nCaused by {e!r}")
|
|
43
|
+
else:
|
|
44
|
+
logger.trace(f"Received event: {event}")
|
|
45
|
+
self.sequence = event.sn
|
|
46
|
+
await self.app.post(event, self)
|
|
47
|
+
|
|
48
|
+
return asyncio.create_task(event_parse_task(body))
|
|
49
|
+
|
|
30
50
|
async def message_receive(self):
|
|
31
51
|
if self.connection is None:
|
|
32
52
|
raise RuntimeError("connection is not established")
|
|
@@ -40,7 +60,7 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
40
60
|
logger.trace(f"Received payload: {data}")
|
|
41
61
|
if data["op"] == Opcode.EVENT:
|
|
42
62
|
self.post_event(data["body"])
|
|
43
|
-
elif data["op"] >
|
|
63
|
+
elif data["op"] > 5:
|
|
44
64
|
logger.warning(f"Received unknown event: {data}")
|
|
45
65
|
continue
|
|
46
66
|
else:
|
|
@@ -63,18 +83,13 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
63
83
|
"""鉴权连接"""
|
|
64
84
|
if not self.connection:
|
|
65
85
|
raise RuntimeError("connection is not established")
|
|
66
|
-
payload =
|
|
67
|
-
"op": Opcode.IDENTIFY,
|
|
68
|
-
"body": {
|
|
69
|
-
"token": self.config.token,
|
|
70
|
-
},
|
|
71
|
-
}
|
|
86
|
+
payload = Identify(self.config.token)
|
|
72
87
|
if self.sequence > -1:
|
|
73
|
-
payload
|
|
88
|
+
payload.sn = self.sequence
|
|
74
89
|
try:
|
|
75
|
-
await self.send(payload)
|
|
90
|
+
await self.send({"op": Opcode.IDENTIFY.value, "body": payload.dump()})
|
|
76
91
|
except Exception as e:
|
|
77
|
-
logger.error(f"Error while sending IDENTIFY event: {e}")
|
|
92
|
+
logger.error(f"Error while sending IDENTIFY event: {e!r}")
|
|
78
93
|
return False
|
|
79
94
|
|
|
80
95
|
resp = await self.connection.receive()
|
|
@@ -85,32 +100,24 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
85
100
|
if data["op"] != Opcode.READY:
|
|
86
101
|
logger.error(f"Received unexpected payload: {data}")
|
|
87
102
|
return False
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
account = self.app.accounts[identity]
|
|
97
|
-
self.accounts[identity] = account
|
|
98
|
-
if not obj.status or obj.status == LoginStatus.ONLINE:
|
|
103
|
+
ready = Ready.parse(data["body"])
|
|
104
|
+
self.proxy_urls = ready.proxy_urls
|
|
105
|
+
for login in ready.logins:
|
|
106
|
+
login_sn = f"{login.sn}@{id(self)}"
|
|
107
|
+
if login_sn in self.app.accounts:
|
|
108
|
+
account = self.app.accounts[login_sn]
|
|
109
|
+
self.accounts[login_sn] = account
|
|
110
|
+
if login.status == LoginStatus.ONLINE:
|
|
99
111
|
account.connected.set()
|
|
100
112
|
else:
|
|
101
113
|
account.connected.clear()
|
|
102
114
|
account.config = self.config
|
|
103
115
|
else:
|
|
104
|
-
account = Account(
|
|
116
|
+
account = Account(login, self.config, ready.proxy_urls, self.app.default_api_cls)
|
|
105
117
|
logger.info(f"account registered: {account}")
|
|
106
|
-
(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else account.connected.clear()
|
|
110
|
-
)
|
|
111
|
-
self.app.accounts[identity] = account
|
|
112
|
-
self.accounts[identity] = account
|
|
113
|
-
await self.app.account_update(account, LoginStatus.CONNECT)
|
|
118
|
+
(account.connected.set() if login.status == LoginStatus.ONLINE else account.connected.clear())
|
|
119
|
+
self.app.accounts[login_sn] = account
|
|
120
|
+
self.accounts[login_sn] = account
|
|
114
121
|
await self.app.account_update(account, LoginStatus.ONLINE)
|
|
115
122
|
if not self.accounts:
|
|
116
123
|
logger.warning(f"No account available for {self.config}")
|
|
@@ -151,9 +158,11 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
151
158
|
self.close_signal.set()
|
|
152
159
|
self.connection = None
|
|
153
160
|
for v in list(self.app.accounts.values()):
|
|
154
|
-
if v.
|
|
161
|
+
if (identity := f"{v.sn}@{id(self)}") in self.accounts:
|
|
155
162
|
v.connected.clear()
|
|
156
|
-
|
|
163
|
+
await self.app.account_update(v, LoginStatus.OFFLINE)
|
|
164
|
+
del self.app.accounts[identity]
|
|
165
|
+
del self.accounts[identity]
|
|
157
166
|
return
|
|
158
167
|
if close_task in done:
|
|
159
168
|
receiver_task.cancel()
|
|
@@ -162,7 +171,7 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
162
171
|
logger.debug(f"Unregistering satori account {k}...")
|
|
163
172
|
account = self.app.accounts[k]
|
|
164
173
|
account.connected.clear()
|
|
165
|
-
await self.app.account_update(account, LoginStatus.
|
|
174
|
+
await self.app.account_update(account, LoginStatus.RECONNECT)
|
|
166
175
|
self.accounts.clear()
|
|
167
176
|
await asyncio.sleep(5)
|
|
168
177
|
logger.info(f"{self} Reconnecting...")
|