satori-python 0.16.2__tar.gz → 0.16.3__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.16.2 → satori_python-0.16.3}/PKG-INFO +1 -1
- {satori_python-0.16.2 → satori_python-0.16.3}/pyproject.toml +1 -1
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/__init__.py +1 -1
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/__init__.py +52 -37
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/account.py +1 -2
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/account.pyi +14 -15
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/network/base.py +2 -2
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/network/webhook.py +31 -14
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/network/websocket.py +11 -4
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/protocol.py +72 -46
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/event.py +2 -2
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/model.py +39 -11
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/server/adapter.py +7 -3
- {satori_python-0.16.2 → satori_python-0.16.3}/LICENSE +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/README.md +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/config.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/network/__init__.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/client/network/util.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/const.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/element.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/exception.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/parser.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/server/__init__.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/server/conection.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/server/formdata.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/server/model.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/server/route.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/server/utils.py +0 -0
- {satori_python-0.16.2 → satori_python-0.16.3}/src/satori/utils.py +0 -0
|
@@ -189,32 +189,37 @@ class App(Service):
|
|
|
189
189
|
async def post(self, event: Event, conn: BaseNetwork):
|
|
190
190
|
if not self.event_callbacks:
|
|
191
191
|
return
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
192
|
+
if event.type == EventType.LOGIN_ADDED:
|
|
193
|
+
if TYPE_CHECKING:
|
|
194
|
+
assert isinstance(event, events.LoginEvent)
|
|
195
|
+
login = event.login
|
|
196
|
+
if not login.user:
|
|
197
|
+
logger.warning(f"Received login-added event without user info: {login}")
|
|
198
|
+
return
|
|
199
|
+
login_sn = f"{login.user.id}@{id(conn)}"
|
|
200
|
+
account = Account(
|
|
201
|
+
login,
|
|
202
|
+
conn.config,
|
|
203
|
+
conn.proxy_urls,
|
|
204
|
+
self.default_api_cls,
|
|
205
|
+
)
|
|
206
|
+
logger.info(f"account added: {account}")
|
|
207
|
+
(account.connected.set() if login.status == LoginStatus.ONLINE else account.connected.clear())
|
|
208
|
+
self.accounts[login_sn] = account
|
|
209
|
+
conn.accounts[login_sn] = account
|
|
210
|
+
await self.account_update(account, login.status)
|
|
211
|
+
elif event.type == EventType.LOGIN_UPDATED:
|
|
212
|
+
if TYPE_CHECKING:
|
|
213
|
+
assert isinstance(event, events.LoginEvent)
|
|
214
|
+
login = event.login
|
|
215
|
+
if not login.user:
|
|
216
|
+
logger.warning(f"Received login-updated event without user info: {login}")
|
|
217
|
+
return
|
|
218
|
+
login_sn = f"{login.user.id}@{id(conn)}"
|
|
219
|
+
if login_sn not in self.accounts:
|
|
220
|
+
if login.status == LoginStatus.ONLINE:
|
|
216
221
|
account = Account(
|
|
217
|
-
|
|
222
|
+
login,
|
|
218
223
|
conn.config,
|
|
219
224
|
conn.proxy_urls,
|
|
220
225
|
self.default_api_cls,
|
|
@@ -228,27 +233,37 @@ class App(Service):
|
|
|
228
233
|
logger.warning(f"Received event for unknown account: {event}")
|
|
229
234
|
return
|
|
230
235
|
else:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
else:
|
|
234
|
-
account = self.accounts[login_sn]
|
|
235
|
-
account.self_info = event.login
|
|
236
|
-
if event.type == EventType.LOGIN_UPDATED:
|
|
237
|
-
if TYPE_CHECKING:
|
|
238
|
-
assert isinstance(event, events.LoginEvent)
|
|
236
|
+
account = self.accounts[login_sn]
|
|
237
|
+
account.self_info = login
|
|
239
238
|
logger.info(f"account updated: {account}")
|
|
240
239
|
(
|
|
241
240
|
account.connected.set()
|
|
242
|
-
if
|
|
241
|
+
if login.status in (LoginStatus.ONLINE, LoginStatus.CONNECT)
|
|
243
242
|
else account.connected.clear()
|
|
244
243
|
)
|
|
245
|
-
await self.account_update(account,
|
|
244
|
+
await self.account_update(account, login.status)
|
|
245
|
+
elif event.type == EventType.LOGIN_REMOVED:
|
|
246
|
+
if TYPE_CHECKING:
|
|
247
|
+
assert isinstance(event, events.LoginEvent)
|
|
248
|
+
login = event.login
|
|
249
|
+
if not login.user:
|
|
250
|
+
logger.warning(f"Received login-removed event without user info: {login}")
|
|
251
|
+
return
|
|
252
|
+
login_sn = f"{login.user.id}@{id(conn)}"
|
|
253
|
+
if login_sn not in self.accounts:
|
|
254
|
+
logger.warning(f"Received event for unknown account: {event}")
|
|
255
|
+
return
|
|
256
|
+
account = self.accounts[login_sn]
|
|
257
|
+
else:
|
|
258
|
+
login_sn = f"{event.login.user.id}@{id(conn)}"
|
|
259
|
+
if login_sn not in self.accounts:
|
|
260
|
+
logger.warning(f"Received event for unknown account: {event}")
|
|
261
|
+
return
|
|
262
|
+
account = self.accounts[login_sn]
|
|
246
263
|
|
|
247
264
|
await asyncio.gather(*(callback(account, event) for callback in self.event_callbacks))
|
|
248
265
|
|
|
249
266
|
if event.type == EventType.LOGIN_REMOVED:
|
|
250
|
-
if TYPE_CHECKING:
|
|
251
|
-
assert isinstance(event, events.LoginEvent)
|
|
252
267
|
logger.info(f"account removed: {account}")
|
|
253
268
|
account.connected.clear()
|
|
254
269
|
await self.account_update(account, LoginStatus.OFFLINE)
|
|
@@ -38,7 +38,6 @@ class Account(Generic[TP]):
|
|
|
38
38
|
proxy_urls: list[str],
|
|
39
39
|
protocol_cls: type[TP] = ApiProtocol,
|
|
40
40
|
):
|
|
41
|
-
self.sn = login.sn
|
|
42
41
|
self.adapter = login.adapter
|
|
43
42
|
self.self_info = login
|
|
44
43
|
self.config = config
|
|
@@ -52,7 +51,7 @@ class Account(Generic[TP]):
|
|
|
52
51
|
|
|
53
52
|
@property
|
|
54
53
|
def self_id(self):
|
|
55
|
-
return self.self_info.id
|
|
54
|
+
return self.self_info.user.id
|
|
56
55
|
|
|
57
56
|
def custom(
|
|
58
57
|
self, config: ApiInfo | None = None, protocol_cls: type[TP1] = ApiProtocol, **kwargs
|
|
@@ -11,6 +11,7 @@ from satori.model import (
|
|
|
11
11
|
Direction,
|
|
12
12
|
Event,
|
|
13
13
|
Guild,
|
|
14
|
+
IterablePageResult,
|
|
14
15
|
Login,
|
|
15
16
|
Member,
|
|
16
17
|
MessageObject,
|
|
@@ -18,7 +19,6 @@ from satori.model import (
|
|
|
18
19
|
Meta,
|
|
19
20
|
Order,
|
|
20
21
|
PageDequeResult,
|
|
21
|
-
PageResult,
|
|
22
22
|
Role,
|
|
23
23
|
Upload,
|
|
24
24
|
User,
|
|
@@ -41,7 +41,6 @@ class ApiInfo(Api):
|
|
|
41
41
|
): ...
|
|
42
42
|
|
|
43
43
|
class Account(Generic[TP]):
|
|
44
|
-
sn: str
|
|
45
44
|
adapter: str
|
|
46
45
|
self_info: Login
|
|
47
46
|
proxy_urls: list[str]
|
|
@@ -211,7 +210,7 @@ class Account(Generic[TP]):
|
|
|
211
210
|
Channel: `Channel` 对象
|
|
212
211
|
"""
|
|
213
212
|
|
|
214
|
-
|
|
213
|
+
def channel_list(self, guild_id: str, next_token: str | None = None) -> IterablePageResult[Channel]:
|
|
215
214
|
"""获取群组中的全部频道。返回一个 Channel 的分页列表。
|
|
216
215
|
|
|
217
216
|
Args:
|
|
@@ -219,7 +218,7 @@ class Account(Generic[TP]):
|
|
|
219
218
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
220
219
|
|
|
221
220
|
Returns:
|
|
222
|
-
|
|
221
|
+
IterablePageResult[Channel]: `Channel` 的分页列表
|
|
223
222
|
"""
|
|
224
223
|
|
|
225
224
|
async def channel_create(self, guild_id: str, data: Channel) -> Channel:
|
|
@@ -291,14 +290,14 @@ class Account(Generic[TP]):
|
|
|
291
290
|
Guild: `Guild` 对象
|
|
292
291
|
"""
|
|
293
292
|
|
|
294
|
-
|
|
293
|
+
def guild_list(self, next_token: str | None = None) -> IterablePageResult[Guild]:
|
|
295
294
|
"""获取当前用户加入的全部群组。返回一个 Guild 的分页列表。
|
|
296
295
|
|
|
297
296
|
Args:
|
|
298
297
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
299
298
|
|
|
300
299
|
Returns:
|
|
301
|
-
|
|
300
|
+
IterablePageResult[Guild]: `Guild` 的分页列表
|
|
302
301
|
"""
|
|
303
302
|
|
|
304
303
|
async def guild_approve(self, request_id: str, approve: bool, comment: str) -> None:
|
|
@@ -313,7 +312,7 @@ class Account(Generic[TP]):
|
|
|
313
312
|
None: 该方法无返回值
|
|
314
313
|
"""
|
|
315
314
|
|
|
316
|
-
|
|
315
|
+
def guild_member_list(self, guild_id: str, next_token: str | None = None) -> IterablePageResult[Member]:
|
|
317
316
|
"""获取群组成员列表。返回一个 Member 的分页列表。
|
|
318
317
|
|
|
319
318
|
Args:
|
|
@@ -321,7 +320,7 @@ class Account(Generic[TP]):
|
|
|
321
320
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
322
321
|
|
|
323
322
|
Returns:
|
|
324
|
-
|
|
323
|
+
IterablePageResult[Member]: `Member` 的分页列表
|
|
325
324
|
"""
|
|
326
325
|
|
|
327
326
|
async def guild_member_get(self, guild_id: str, user_id: str) -> Member:
|
|
@@ -397,7 +396,7 @@ class Account(Generic[TP]):
|
|
|
397
396
|
None: 该方法无返回值
|
|
398
397
|
"""
|
|
399
398
|
|
|
400
|
-
|
|
399
|
+
def guild_role_list(self, guild_id: str, next_token: str | None = None) -> IterablePageResult[Role]:
|
|
401
400
|
"""获取群组角色列表。返回一个 Role 的分页列表。
|
|
402
401
|
|
|
403
402
|
Args:
|
|
@@ -405,7 +404,7 @@ class Account(Generic[TP]):
|
|
|
405
404
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
406
405
|
|
|
407
406
|
Returns:
|
|
408
|
-
|
|
407
|
+
IterablePageResult[Role]: `Role` 的分页列表
|
|
409
408
|
"""
|
|
410
409
|
|
|
411
410
|
async def guild_role_create(self, guild_id: str, role: Role) -> Role:
|
|
@@ -485,9 +484,9 @@ class Account(Generic[TP]):
|
|
|
485
484
|
None: 该方法无返回值
|
|
486
485
|
"""
|
|
487
486
|
|
|
488
|
-
|
|
487
|
+
def reaction_list(
|
|
489
488
|
self, channel_id: str, message_id: str, emoji: str, next_token: str | None = None
|
|
490
|
-
) ->
|
|
489
|
+
) -> IterablePageResult[User]:
|
|
491
490
|
"""获取添加特定消息的特定表态的用户列表。返回一个 User 的分页列表。
|
|
492
491
|
|
|
493
492
|
Args:
|
|
@@ -497,7 +496,7 @@ class Account(Generic[TP]):
|
|
|
497
496
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
498
497
|
|
|
499
498
|
Returns:
|
|
500
|
-
|
|
499
|
+
IterablePageResult[User]: `User` 的分页列表
|
|
501
500
|
"""
|
|
502
501
|
|
|
503
502
|
async def login_get(self) -> Login:
|
|
@@ -517,14 +516,14 @@ class Account(Generic[TP]):
|
|
|
517
516
|
User: `User` 对象
|
|
518
517
|
"""
|
|
519
518
|
|
|
520
|
-
|
|
519
|
+
def friend_list(self, next_token: str | None = None) -> IterablePageResult[User]:
|
|
521
520
|
"""获取好友列表。返回一个 User 的分页列表。
|
|
522
521
|
|
|
523
522
|
Args:
|
|
524
523
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
525
524
|
|
|
526
525
|
Returns:
|
|
527
|
-
|
|
526
|
+
IterablePageResult[User]: `User` 的分页列表
|
|
528
527
|
"""
|
|
529
528
|
|
|
530
529
|
async def friend_approve(self, request_id: str, approve: bool, comment: str) -> None:
|
|
@@ -8,7 +8,7 @@ from launart import Service
|
|
|
8
8
|
from ..config import Config as Config
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
|
-
from .. import App
|
|
11
|
+
from .. import Account, App
|
|
12
12
|
|
|
13
13
|
TConfig = TypeVar("TConfig", bound=Config)
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ class BaseNetwork(Generic[TConfig], Service):
|
|
|
21
21
|
super().__init__()
|
|
22
22
|
self.app = app
|
|
23
23
|
self.config = config
|
|
24
|
-
self.accounts = {}
|
|
24
|
+
self.accounts: dict[str, Account] = {}
|
|
25
25
|
self.close_signal = asyncio.Event()
|
|
26
26
|
self.sequence = -1
|
|
27
27
|
self.proxy_urls = []
|
|
@@ -3,14 +3,16 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
|
|
5
5
|
from aiohttp import web
|
|
6
|
+
from graia.amnesia.builtins.aiohttp import AiohttpClientService
|
|
6
7
|
from launart.manager import Launart
|
|
7
8
|
from loguru import logger
|
|
8
9
|
|
|
9
|
-
from satori.model import Event, LoginStatus, MetaPayload, Opcode
|
|
10
|
+
from satori.model import Event, LoginStatus, Meta, MetaPayload, Opcode
|
|
10
11
|
|
|
11
12
|
from ..account import Account
|
|
12
13
|
from ..config import WebhookInfo as WebhookInfo
|
|
13
14
|
from .base import BaseNetwork
|
|
15
|
+
from .util import validate_response
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class WebhookNetwork(BaseNetwork[WebhookInfo]):
|
|
@@ -35,6 +37,8 @@ class WebhookNetwork(BaseNetwork[WebhookInfo]):
|
|
|
35
37
|
if op_code == Opcode.META:
|
|
36
38
|
payload = MetaPayload.parse(body)
|
|
37
39
|
self.proxy_urls = payload.proxy_urls
|
|
40
|
+
for account in self.accounts.values():
|
|
41
|
+
account.proxy_urls = payload.proxy_urls
|
|
38
42
|
return web.Response()
|
|
39
43
|
if op_code != Opcode.EVENT:
|
|
40
44
|
return web.Response(status=202)
|
|
@@ -61,18 +65,6 @@ class WebhookNetwork(BaseNetwork[WebhookInfo]):
|
|
|
61
65
|
else:
|
|
62
66
|
logger.trace(f"Received event: {event}")
|
|
63
67
|
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
68
|
asyncio.create_task(self.app.post(event, self))
|
|
77
69
|
return web.Response()
|
|
78
70
|
|
|
@@ -94,12 +86,37 @@ class WebhookNetwork(BaseNetwork[WebhookInfo]):
|
|
|
94
86
|
site = web.TCPSite(runner, self.config.host, self.config.port)
|
|
95
87
|
|
|
96
88
|
async with self.stage("blocking"):
|
|
89
|
+
endpoint = self.config.api_base / "meta"
|
|
90
|
+
headers = {
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
}
|
|
93
|
+
aio = Launart.current().get_component(AiohttpClientService)
|
|
94
|
+
|
|
95
|
+
async with aio.session.request(
|
|
96
|
+
"POST",
|
|
97
|
+
endpoint,
|
|
98
|
+
json={},
|
|
99
|
+
headers=headers,
|
|
100
|
+
) as resp:
|
|
101
|
+
data = await validate_response(resp)
|
|
102
|
+
meta = Meta.parse(data)
|
|
103
|
+
self.proxy_urls = meta.proxy_urls
|
|
104
|
+
for login in meta.logins:
|
|
105
|
+
if not login.user:
|
|
106
|
+
continue
|
|
107
|
+
login_sn = f"{login.user.id}@{id(self)}"
|
|
108
|
+
account = Account(login, self.config, meta.proxy_urls, self.app.default_api_cls)
|
|
109
|
+
logger.info(f"account registered: {account}")
|
|
110
|
+
(account.connected.set() if login.status == LoginStatus.ONLINE else account.connected.clear())
|
|
111
|
+
self.app.accounts[login_sn] = account
|
|
112
|
+
self.accounts[login_sn] = account
|
|
113
|
+
await self.app.account_update(account, LoginStatus.ONLINE)
|
|
97
114
|
await site.start()
|
|
98
115
|
await manager.status.wait_for_sigexit()
|
|
99
116
|
logger.info(f"{self.id} Webhook server exiting...")
|
|
100
117
|
self.close_signal.set()
|
|
101
118
|
for v in list(self.app.accounts.values()):
|
|
102
|
-
if (identity := f"{v.
|
|
119
|
+
if (identity := f"{v.self_id}@{id(self)}") in self.accounts:
|
|
103
120
|
v.connected.clear()
|
|
104
121
|
await self.app.account_update(v, LoginStatus.OFFLINE)
|
|
105
122
|
del self.app.accounts[identity]
|
|
@@ -10,7 +10,7 @@ from launart.manager import Launart
|
|
|
10
10
|
from launart.utilles import any_completed
|
|
11
11
|
from loguru import logger
|
|
12
12
|
|
|
13
|
-
from satori.model import Event, Identify, LoginStatus, Opcode, Ready
|
|
13
|
+
from satori.model import Event, Identify, LoginStatus, MetaPayload, Opcode, Ready
|
|
14
14
|
|
|
15
15
|
from ..account import Account
|
|
16
16
|
from ..config import WebsocketsInfo as WebsocketsInfo
|
|
@@ -60,6 +60,11 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
60
60
|
logger.trace(f"Received payload: {data}")
|
|
61
61
|
if data["op"] == Opcode.EVENT:
|
|
62
62
|
self.post_event(data["body"])
|
|
63
|
+
elif data["op"] == Opcode.META:
|
|
64
|
+
payload = MetaPayload.parse(data["body"])
|
|
65
|
+
self.proxy_urls = payload.proxy_urls
|
|
66
|
+
for account in self.accounts.values():
|
|
67
|
+
account.proxy_urls = payload.proxy_urls.copy()
|
|
63
68
|
elif data["op"] > 5:
|
|
64
69
|
logger.warning(f"Received unknown event: {data}")
|
|
65
70
|
continue
|
|
@@ -103,7 +108,9 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
103
108
|
ready = Ready.parse(data["body"])
|
|
104
109
|
self.proxy_urls = ready.proxy_urls
|
|
105
110
|
for login in ready.logins:
|
|
106
|
-
|
|
111
|
+
if not login.user:
|
|
112
|
+
continue
|
|
113
|
+
login_sn = f"{login.user.id}@{id(self)}"
|
|
107
114
|
if login_sn in self.app.accounts:
|
|
108
115
|
account = self.app.accounts[login_sn]
|
|
109
116
|
self.accounts[login_sn] = account
|
|
@@ -121,7 +128,7 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
121
128
|
await self.app.account_update(account, LoginStatus.ONLINE)
|
|
122
129
|
if not self.accounts:
|
|
123
130
|
logger.warning(f"No account available for {self.config}")
|
|
124
|
-
return False
|
|
131
|
+
# return False
|
|
125
132
|
return True
|
|
126
133
|
|
|
127
134
|
async def _heartbeat(self):
|
|
@@ -158,7 +165,7 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
|
|
|
158
165
|
self.close_signal.set()
|
|
159
166
|
self.connection = None
|
|
160
167
|
for v in list(self.app.accounts.values()):
|
|
161
|
-
if (identity := f"{v.
|
|
168
|
+
if (identity := f"{v.self_id}@{id(self)}") in self.accounts:
|
|
162
169
|
v.connected.clear()
|
|
163
170
|
await self.app.account_update(v, LoginStatus.OFFLINE)
|
|
164
171
|
del self.app.accounts[identity]
|
|
@@ -15,7 +15,9 @@ from satori.model import (
|
|
|
15
15
|
Direction,
|
|
16
16
|
Event,
|
|
17
17
|
Guild,
|
|
18
|
+
IterablePageResult,
|
|
18
19
|
Login,
|
|
20
|
+
LoginPartial,
|
|
19
21
|
Member,
|
|
20
22
|
MessageObject,
|
|
21
23
|
MessageReceipt,
|
|
@@ -278,7 +280,7 @@ class ApiProtocol:
|
|
|
278
280
|
)
|
|
279
281
|
return Channel.parse(res)
|
|
280
282
|
|
|
281
|
-
|
|
283
|
+
def channel_list(self, guild_id: str, next_token: str | None = None) -> IterablePageResult[Channel]:
|
|
282
284
|
"""获取群组中的全部频道。返回一个 Channel 的分页列表。
|
|
283
285
|
|
|
284
286
|
Args:
|
|
@@ -286,13 +288,17 @@ class ApiProtocol:
|
|
|
286
288
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
287
289
|
|
|
288
290
|
Returns:
|
|
289
|
-
|
|
291
|
+
IterablePageResult[Channel]: `Channel` 的分页列表
|
|
290
292
|
"""
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
|
|
294
|
+
async def _(token: str | None):
|
|
295
|
+
res = await self.call_api(
|
|
296
|
+
Api.CHANNEL_LIST,
|
|
297
|
+
{"guild_id": guild_id, "next": token},
|
|
298
|
+
)
|
|
299
|
+
return PageResult.parse(res, Channel.parse)
|
|
300
|
+
|
|
301
|
+
return IterablePageResult(_, next_token)
|
|
296
302
|
|
|
297
303
|
async def channel_create(self, guild_id: str, data: Channel) -> Channel:
|
|
298
304
|
"""创建群组频道。返回一个 Channel 对象。
|
|
@@ -393,20 +399,24 @@ class ApiProtocol:
|
|
|
393
399
|
)
|
|
394
400
|
return Guild.parse(res)
|
|
395
401
|
|
|
396
|
-
|
|
402
|
+
def guild_list(self, next_token: str | None = None) -> IterablePageResult[Guild]:
|
|
397
403
|
"""获取当前用户加入的全部群组。返回一个 Guild 的分页列表。
|
|
398
404
|
|
|
399
405
|
Args:
|
|
400
406
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
401
407
|
|
|
402
408
|
Returns:
|
|
403
|
-
|
|
409
|
+
IterablePageResult[Guild]: `Guild` 的分页列表
|
|
404
410
|
"""
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
411
|
+
|
|
412
|
+
async def _(token: str | None):
|
|
413
|
+
res = await self.call_api(
|
|
414
|
+
Api.GUILD_LIST,
|
|
415
|
+
{"next": token},
|
|
416
|
+
)
|
|
417
|
+
return PageResult.parse(res, Guild.parse)
|
|
418
|
+
|
|
419
|
+
return IterablePageResult(_, next_token)
|
|
410
420
|
|
|
411
421
|
async def guild_approve(self, request_id: str, approve: bool, comment: str) -> None:
|
|
412
422
|
"""处理来自群组的邀请。
|
|
@@ -424,7 +434,7 @@ class ApiProtocol:
|
|
|
424
434
|
{"message_id": request_id, "approve": approve, "comment": comment},
|
|
425
435
|
)
|
|
426
436
|
|
|
427
|
-
|
|
437
|
+
def guild_member_list(self, guild_id: str, next_token: str | None = None) -> IterablePageResult[Member]:
|
|
428
438
|
"""获取群组成员列表。返回一个 Member 的分页列表。
|
|
429
439
|
|
|
430
440
|
Args:
|
|
@@ -432,13 +442,17 @@ class ApiProtocol:
|
|
|
432
442
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
433
443
|
|
|
434
444
|
Returns:
|
|
435
|
-
|
|
445
|
+
IterablePageResult[Member]: `Member` 的分页列表
|
|
436
446
|
"""
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
447
|
+
|
|
448
|
+
async def _(token: str | None):
|
|
449
|
+
res = await self.call_api(
|
|
450
|
+
Api.GUILD_MEMBER_LIST,
|
|
451
|
+
{"guild_id": guild_id, "next": token},
|
|
452
|
+
)
|
|
453
|
+
return PageResult.parse(res, Member.parse)
|
|
454
|
+
|
|
455
|
+
return IterablePageResult(_, next_token)
|
|
442
456
|
|
|
443
457
|
async def guild_member_get(self, guild_id: str, user_id: str) -> Member:
|
|
444
458
|
"""获取群成员信息。返回一个 `Member` 对象。
|
|
@@ -538,7 +552,7 @@ class ApiProtocol:
|
|
|
538
552
|
{"guild_id": guild_id, "user_id": user_id, "role_id": role_id},
|
|
539
553
|
)
|
|
540
554
|
|
|
541
|
-
|
|
555
|
+
def guild_role_list(self, guild_id: str, next_token: str | None = None) -> IterablePageResult[Role]:
|
|
542
556
|
"""获取群组角色列表。返回一个 Role 的分页列表。
|
|
543
557
|
|
|
544
558
|
Args:
|
|
@@ -546,13 +560,17 @@ class ApiProtocol:
|
|
|
546
560
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
547
561
|
|
|
548
562
|
Returns:
|
|
549
|
-
|
|
563
|
+
IterablePageResult[Role]: `Role` 的分页列表
|
|
550
564
|
"""
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
565
|
+
|
|
566
|
+
async def _(token: str | None):
|
|
567
|
+
res = await self.call_api(
|
|
568
|
+
Api.GUILD_ROLE_LIST,
|
|
569
|
+
{"guild_id": guild_id, "next": token},
|
|
570
|
+
)
|
|
571
|
+
return PageResult.parse(res, Role.parse)
|
|
572
|
+
|
|
573
|
+
return IterablePageResult(_, next_token)
|
|
556
574
|
|
|
557
575
|
async def guild_role_create(self, guild_id: str, role: Role) -> Role:
|
|
558
576
|
"""创建群组角色。返回一个 Role 对象。
|
|
@@ -662,9 +680,9 @@ class ApiProtocol:
|
|
|
662
680
|
data,
|
|
663
681
|
)
|
|
664
682
|
|
|
665
|
-
|
|
683
|
+
def reaction_list(
|
|
666
684
|
self, channel_id: str, message_id: str, emoji: str, next_token: str | None = None
|
|
667
|
-
) ->
|
|
685
|
+
) -> IterablePageResult[User]:
|
|
668
686
|
"""获取添加特定消息的特定表态的用户列表。返回一个 User 的分页列表。
|
|
669
687
|
|
|
670
688
|
Args:
|
|
@@ -674,18 +692,22 @@ class ApiProtocol:
|
|
|
674
692
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
675
693
|
|
|
676
694
|
Returns:
|
|
677
|
-
|
|
695
|
+
IterablePageResult[User]: `User` 的分页列表
|
|
678
696
|
"""
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
697
|
+
|
|
698
|
+
async def _(token: str | None):
|
|
699
|
+
res = await self.call_api(
|
|
700
|
+
Api.REACTION_LIST,
|
|
701
|
+
{
|
|
702
|
+
"channel_id": channel_id,
|
|
703
|
+
"message_id": message_id,
|
|
704
|
+
"emoji": emoji,
|
|
705
|
+
"next": token,
|
|
706
|
+
},
|
|
707
|
+
)
|
|
708
|
+
return PageResult.parse(res, User.parse)
|
|
709
|
+
|
|
710
|
+
return IterablePageResult(_, next_token)
|
|
689
711
|
|
|
690
712
|
async def login_get(self) -> Login:
|
|
691
713
|
"""获取当前登录信息。返回一个 `Login` 对象。
|
|
@@ -708,17 +730,21 @@ class ApiProtocol:
|
|
|
708
730
|
res = await self.call_api(Api.USER_GET, {"user_id": user_id})
|
|
709
731
|
return User.parse(res)
|
|
710
732
|
|
|
711
|
-
|
|
733
|
+
def friend_list(self, next_token: str | None = None) -> IterablePageResult[User]:
|
|
712
734
|
"""获取好友列表。返回一个 User 的分页列表。
|
|
713
735
|
|
|
714
736
|
Args:
|
|
715
737
|
next_token (str | None, optional): 分页令牌,默认为空
|
|
716
738
|
|
|
717
739
|
Returns:
|
|
718
|
-
|
|
740
|
+
IterablePageResult[User]: `User` 的分页列表
|
|
719
741
|
"""
|
|
720
|
-
|
|
721
|
-
|
|
742
|
+
|
|
743
|
+
async def _(token: str | None):
|
|
744
|
+
res = await self.call_api(Api.FRIEND_LIST, {"next": token})
|
|
745
|
+
return PageResult.parse(res, User.parse)
|
|
746
|
+
|
|
747
|
+
return IterablePageResult(_, next_token)
|
|
722
748
|
|
|
723
749
|
async def friend_approve(self, request_id: str, approve: bool, comment: str) -> None:
|
|
724
750
|
"""处理好友申请。
|
|
@@ -756,7 +782,7 @@ class ApiProtocol:
|
|
|
756
782
|
return Meta.parse(res)
|
|
757
783
|
|
|
758
784
|
@deprecated("Use `meta_get` instead")
|
|
759
|
-
async def admin_login_list(self) -> list[
|
|
785
|
+
async def admin_login_list(self) -> list[LoginPartial]:
|
|
760
786
|
"""获取登录信息列表。返回一个 `Login` 对象构成的数组。
|
|
761
787
|
|
|
762
788
|
Returns:
|
|
@@ -4,7 +4,7 @@ from satori.model import (
|
|
|
4
4
|
Channel,
|
|
5
5
|
Event,
|
|
6
6
|
Guild,
|
|
7
|
-
|
|
7
|
+
LoginPartial,
|
|
8
8
|
Member,
|
|
9
9
|
MessageObject,
|
|
10
10
|
Role,
|
|
@@ -37,7 +37,7 @@ class GuildRoleEvent(GuildEvent):
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class LoginEvent(Event):
|
|
40
|
-
login:
|
|
40
|
+
login: LoginPartial
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class ReactionEvent(Event):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import mimetypes
|
|
2
|
+
from collections.abc import AsyncIterable, Awaitable
|
|
2
3
|
from dataclasses import asdict, dataclass, field, fields
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from enum import IntEnum
|
|
@@ -145,11 +146,11 @@ class LoginStatus(IntEnum):
|
|
|
145
146
|
|
|
146
147
|
@dataclass
|
|
147
148
|
class Login(ModelBase):
|
|
148
|
-
sn:
|
|
149
|
+
sn: int
|
|
149
150
|
status: LoginStatus
|
|
150
151
|
adapter: str
|
|
151
|
-
platform:
|
|
152
|
-
user:
|
|
152
|
+
platform: str
|
|
153
|
+
user: User
|
|
153
154
|
features: list[str] = field(default_factory=list)
|
|
154
155
|
|
|
155
156
|
__converter__ = {"user": User.parse, "status": LoginStatus}
|
|
@@ -173,7 +174,7 @@ class Login(ModelBase):
|
|
|
173
174
|
if "self_id" in raw and "user" not in raw:
|
|
174
175
|
raw["user"] = {"id": raw["self_id"]}
|
|
175
176
|
if "sn" not in raw:
|
|
176
|
-
raw["sn"] =
|
|
177
|
+
raw["sn"] = 0
|
|
177
178
|
if "adapter" not in raw:
|
|
178
179
|
raw["adapter"] = "satori"
|
|
179
180
|
if "status" not in raw:
|
|
@@ -182,11 +183,15 @@ class Login(ModelBase):
|
|
|
182
183
|
|
|
183
184
|
@property
|
|
184
185
|
def id(self) -> str:
|
|
185
|
-
if not self.user:
|
|
186
|
-
raise ValueError(f"Login {self.sn} has not complete yet")
|
|
187
186
|
return self.user.id
|
|
188
187
|
|
|
189
188
|
|
|
189
|
+
@dataclass
|
|
190
|
+
class LoginPartial(Login):
|
|
191
|
+
platform: Optional[str] = None
|
|
192
|
+
user: Optional[User] = None
|
|
193
|
+
|
|
194
|
+
|
|
190
195
|
@dataclass
|
|
191
196
|
class ArgvInteraction(ModelBase):
|
|
192
197
|
name: str
|
|
@@ -241,10 +246,10 @@ class Identify(ModelBase):
|
|
|
241
246
|
|
|
242
247
|
@dataclass
|
|
243
248
|
class Ready(ModelBase):
|
|
244
|
-
logins: list[
|
|
249
|
+
logins: list[LoginPartial]
|
|
245
250
|
proxy_urls: list[str] = field(default_factory=list)
|
|
246
251
|
|
|
247
|
-
__converter__ = {"logins": lambda raw: [
|
|
252
|
+
__converter__ = {"logins": lambda raw: [LoginPartial.parse(login) for login in raw]}
|
|
248
253
|
|
|
249
254
|
def dump(self):
|
|
250
255
|
return asdict(self)
|
|
@@ -264,10 +269,10 @@ class MetaPayload(ModelBase):
|
|
|
264
269
|
class Meta(ModelBase):
|
|
265
270
|
"""Meta 数据"""
|
|
266
271
|
|
|
267
|
-
logins: list[
|
|
272
|
+
logins: list[LoginPartial]
|
|
268
273
|
proxy_urls: list[str] = field(default_factory=list)
|
|
269
274
|
|
|
270
|
-
__converter__ = {"logins": lambda raw: [
|
|
275
|
+
__converter__ = {"logins": lambda raw: [LoginPartial.parse(login) for login in raw]}
|
|
271
276
|
|
|
272
277
|
def dump(self):
|
|
273
278
|
return asdict(self)
|
|
@@ -414,7 +419,7 @@ class Event(ModelBase):
|
|
|
414
419
|
raw["sn"] = raw["id"]
|
|
415
420
|
if "platform" in raw and "self_id" in raw and "login" not in raw:
|
|
416
421
|
raw["login"] = {
|
|
417
|
-
"sn":
|
|
422
|
+
"sn": 0,
|
|
418
423
|
"platform": raw["platform"],
|
|
419
424
|
"user": {"id": raw["self_id"]},
|
|
420
425
|
"status": LoginStatus.ONLINE,
|
|
@@ -501,6 +506,29 @@ class PageDequeResult(PageResult[T]):
|
|
|
501
506
|
return res
|
|
502
507
|
|
|
503
508
|
|
|
509
|
+
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
|
+
):
|
|
513
|
+
self.func = func
|
|
514
|
+
self.next_page = initial_page
|
|
515
|
+
|
|
516
|
+
def __await__(self):
|
|
517
|
+
return self.func(self.next_page).__await__()
|
|
518
|
+
|
|
519
|
+
def __aiter__(self):
|
|
520
|
+
async def _gen():
|
|
521
|
+
while True:
|
|
522
|
+
result = await self.func(self.next_page)
|
|
523
|
+
for item in result.data:
|
|
524
|
+
yield item
|
|
525
|
+
self.next_page = result.next
|
|
526
|
+
if not self.next_page:
|
|
527
|
+
break
|
|
528
|
+
|
|
529
|
+
return _gen()
|
|
530
|
+
|
|
531
|
+
|
|
504
532
|
Direction: TypeAlias = Literal["before", "after", "around"]
|
|
505
533
|
Order: TypeAlias = Literal["asc", "desc"]
|
|
506
534
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
2
|
from collections.abc import AsyncIterator
|
|
3
|
-
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
4
4
|
|
|
5
5
|
from launart import Service
|
|
6
6
|
from starlette.responses import Response
|
|
7
7
|
from starlette.routing import BaseRoute
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from satori.model import Event, Login, LoginPartial
|
|
10
|
+
|
|
10
11
|
from .model import Request
|
|
11
12
|
from .route import RouterMixin
|
|
12
13
|
from .utils import ctx
|
|
@@ -15,6 +16,9 @@ if TYPE_CHECKING:
|
|
|
15
16
|
from . import Server
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
LoginType = Union[Login, LoginPartial]
|
|
20
|
+
|
|
21
|
+
|
|
18
22
|
class Adapter(Service, RouterMixin):
|
|
19
23
|
server: "Server"
|
|
20
24
|
|
|
@@ -39,7 +43,7 @@ class Adapter(Service, RouterMixin):
|
|
|
39
43
|
return Response(await resp.read())
|
|
40
44
|
|
|
41
45
|
@abstractmethod
|
|
42
|
-
async def get_logins(self) -> list[
|
|
46
|
+
async def get_logins(self) -> list[LoginType]: ...
|
|
43
47
|
|
|
44
48
|
def __init__(self):
|
|
45
49
|
super().__init__()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|