satori-python-adapter-qq 0.3.0__tar.gz → 0.3.2__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_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/.mina/adapter_qq.toml +2 -2
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/PKG-INFO +2 -2
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/pyproject.toml +13 -15
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/main.py +9 -2
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/message.py +114 -14
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/utils.py +22 -2
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/websocket.py +12 -5
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/LICENSE +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/README.md +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/__init__.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/api.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/audit_store.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/events/__init__.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/events/base.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/events/group.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/events/guild.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/events/interaction.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/events/message.py +0 -0
- {satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/exception.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
includes = ["src/satori/adapters/qq"]
|
|
2
2
|
raw-dependencies = [
|
|
3
|
-
"satori-python<1.4.0,>= 1.3.
|
|
3
|
+
"satori-python<1.4.0,>= 1.3.3",
|
|
4
4
|
"cryptography>=46.0.4",
|
|
5
5
|
]
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "satori-python-adapter-qq"
|
|
9
|
-
version = "0.3.
|
|
9
|
+
version = "0.3.2"
|
|
10
10
|
authors = [
|
|
11
11
|
{name = "RF-Tar-Railt", email = "rf_tar_railt@qq.com"}
|
|
12
12
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: satori-python-adapter-qq
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Satori Protocol SDK for python, adapter for QQ
|
|
5
5
|
Home-page: https://github.com/RF-Tar-Railt/satori-python
|
|
6
6
|
Author-Email: RF-Tar-Railt <rf_tar_railt@qq.com>
|
|
@@ -17,7 +17,7 @@ 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
19
|
Requires-Python: <4.0,>=3.10
|
|
20
|
-
Requires-Dist: satori-python<1.4.0,>=1.3.
|
|
20
|
+
Requires-Dist: satori-python<1.4.0,>=1.3.3
|
|
21
21
|
Requires-Dist: cryptography>=46.0.4
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "satori-python-adapter-qq"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name = "RF-Tar-Railt", email = "rf_tar_railt@qq.com" },
|
|
6
6
|
]
|
|
7
7
|
dependencies = [
|
|
8
|
-
"satori-python<1.4.0,>= 1.3.
|
|
8
|
+
"satori-python<1.4.0,>= 1.3.3",
|
|
9
9
|
"cryptography>=46.0.4",
|
|
10
10
|
]
|
|
11
11
|
description = "Satori Protocol SDK for python, adapter for QQ"
|
|
@@ -39,15 +39,14 @@ build-backend = "mina.backend"
|
|
|
39
39
|
|
|
40
40
|
[dependency-groups]
|
|
41
41
|
dev = [
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"ruff>=0.4.1",
|
|
42
|
+
"black>=26.3.0",
|
|
43
|
+
"ruff>=0.15.1",
|
|
45
44
|
"pre-commit>=3.7.0",
|
|
46
|
-
"fix-future-annotations>=0.5.0",
|
|
47
45
|
"mina-build<0.6,>=0.5.1",
|
|
48
46
|
"pdm-mina>=0.3.2",
|
|
49
47
|
"nonechat<0.7.0,>=0.6.0",
|
|
50
48
|
"uvicorn[standard]>=0.37.0",
|
|
49
|
+
"pydantic>=2.13.1",
|
|
51
50
|
]
|
|
52
51
|
|
|
53
52
|
[tool.pdm.build]
|
|
@@ -61,7 +60,7 @@ excludes = [
|
|
|
61
60
|
|
|
62
61
|
[tool.pdm.scripts.format]
|
|
63
62
|
composite = [
|
|
64
|
-
"
|
|
63
|
+
"ruff check --select I --fix ./src/ ./example/",
|
|
65
64
|
"black ./src/ ./example/",
|
|
66
65
|
"ruff check",
|
|
67
66
|
]
|
|
@@ -75,14 +74,6 @@ line-length = 120
|
|
|
75
74
|
include = "\\.pyi?$"
|
|
76
75
|
extend-exclude = ""
|
|
77
76
|
|
|
78
|
-
[tool.isort]
|
|
79
|
-
profile = "black"
|
|
80
|
-
line_length = 120
|
|
81
|
-
skip_gitignore = true
|
|
82
|
-
extra_standard_library = [
|
|
83
|
-
"typing_extensions",
|
|
84
|
-
]
|
|
85
|
-
|
|
86
77
|
[tool.ruff]
|
|
87
78
|
line-length = 120
|
|
88
79
|
target-version = "py310"
|
|
@@ -92,6 +83,7 @@ exclude = [
|
|
|
92
83
|
"exam2.py",
|
|
93
84
|
"src/satori/_vendor/*",
|
|
94
85
|
]
|
|
86
|
+
respect-gitignore = true
|
|
95
87
|
|
|
96
88
|
[tool.ruff.lint]
|
|
97
89
|
select = [
|
|
@@ -111,6 +103,12 @@ ignore = [
|
|
|
111
103
|
"T201",
|
|
112
104
|
]
|
|
113
105
|
|
|
106
|
+
[tool.ruff.lint.isort]
|
|
107
|
+
extra-standard-library = [
|
|
108
|
+
"typing_extensions",
|
|
109
|
+
]
|
|
110
|
+
force-sort-within-sections = false
|
|
111
|
+
|
|
114
112
|
[tool.pyright]
|
|
115
113
|
pythonPlatform = "All"
|
|
116
114
|
pythonVersion = "3.10"
|
{satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/main.py
RENAMED
|
@@ -318,7 +318,9 @@ class QQBotWebhookAdapter(BaseAdapter):
|
|
|
318
318
|
bot_info = await network.call_api("get", "users/@me")
|
|
319
319
|
user = decode_user(bot_info)
|
|
320
320
|
user.is_bot = True
|
|
321
|
-
login = Login(
|
|
321
|
+
login = Login(
|
|
322
|
+
sn=0, status=LoginStatus.ONLINE, adapter="qqbot", platform="qq", user=user, features=QQ_FEATURES.copy()
|
|
323
|
+
)
|
|
322
324
|
previous = next((lg for lg in self.logins if lg.id == login.id and lg.platform == "qq"), None)
|
|
323
325
|
if previous:
|
|
324
326
|
previous.user = login.user
|
|
@@ -331,7 +333,12 @@ class QQBotWebhookAdapter(BaseAdapter):
|
|
|
331
333
|
event_type = EventType.LOGIN_ADDED
|
|
332
334
|
await self.server.post(Event(event_type, datetime.now(), login))
|
|
333
335
|
guild_login = Login(
|
|
334
|
-
0,
|
|
336
|
+
sn=0,
|
|
337
|
+
status=LoginStatus.ONLINE,
|
|
338
|
+
adapter="qqbot",
|
|
339
|
+
platform="qqguild",
|
|
340
|
+
user=user,
|
|
341
|
+
features=QQ_GUILD_FEATURES.copy(),
|
|
335
342
|
)
|
|
336
343
|
previous = next((lg for lg in self.logins if lg.id == guild_login.id and lg.platform == "qqguild"), None)
|
|
337
344
|
if previous:
|
{satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/message.py
RENAMED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import base64
|
|
4
5
|
import json
|
|
5
6
|
import random
|
|
6
7
|
import re
|
|
7
8
|
from datetime import datetime, timezone
|
|
8
|
-
from
|
|
9
|
+
from hashlib import md5, sha1
|
|
9
10
|
|
|
10
11
|
from loguru import logger
|
|
11
12
|
|
|
12
|
-
from satori.element import
|
|
13
|
+
from satori.element import Custom, E, Element, Raw
|
|
13
14
|
from satori.model import Login, MessageObject
|
|
14
15
|
from satori.parser import Element as RawElement
|
|
15
|
-
from satori.parser import parse
|
|
16
|
+
from satori.parser import parse, select
|
|
16
17
|
|
|
17
18
|
from ...exception import ActionFailed
|
|
18
19
|
from .exception import AuditException
|
|
19
|
-
from .utils import QQBotNetwork
|
|
20
|
+
from .utils import QQBotNetwork, parse_file_uri
|
|
20
21
|
|
|
21
22
|
_BASE64_RE = re.compile(r"^data:([\w/.+-]+);base64,")
|
|
23
|
+
MAX_FILESIZE_ONCE = 10 * 1024 * 1024 # 10MB
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
def escape(s: str) -> str:
|
|
@@ -88,12 +90,12 @@ class QQBotMessageEncoder:
|
|
|
88
90
|
|
|
89
91
|
async def send(self, content: str):
|
|
90
92
|
self._raw_content = content
|
|
91
|
-
msg =
|
|
92
|
-
btns = select(msg,
|
|
93
|
-
btns = [btn for btn in btns if btn.
|
|
93
|
+
msg = parse(content)
|
|
94
|
+
btns = select(msg, "button")
|
|
95
|
+
btns = [btn for btn in btns if btn.tag() != "link" and not btn.attrs.get("id")]
|
|
94
96
|
for btn in btns:
|
|
95
|
-
btn.id = random.randbytes(8).hex()
|
|
96
|
-
await self.render(
|
|
97
|
+
btn.attrs["id"] = random.randbytes(8).hex()
|
|
98
|
+
await self.render(msg)
|
|
97
99
|
await self.flush()
|
|
98
100
|
return self.results
|
|
99
101
|
|
|
@@ -230,7 +232,7 @@ class QQGuildMessageEncoder(QQBotMessageEncoder):
|
|
|
230
232
|
content_type = b64_match.group(1)
|
|
231
233
|
filename = f"file.{content_type.split('/')[-1]}"
|
|
232
234
|
else:
|
|
233
|
-
path =
|
|
235
|
+
path = parse_file_uri(url)
|
|
234
236
|
data = path.read_bytes()
|
|
235
237
|
filename = path.name
|
|
236
238
|
content_type = None
|
|
@@ -301,6 +303,7 @@ class QQGroupMessageEncoder(QQBotMessageEncoder):
|
|
|
301
303
|
"event_id": event_id,
|
|
302
304
|
"msg_id": msg_id,
|
|
303
305
|
"msg_seq": data["msg_seq"],
|
|
306
|
+
"ref_idx": resp["ext_info"].get("ref_idx"),
|
|
304
307
|
}
|
|
305
308
|
self.results.append(MessageObject(resp["id"], self._raw_content, referrer=referrer))
|
|
306
309
|
except Exception as e:
|
|
@@ -315,6 +318,7 @@ class QQGroupMessageEncoder(QQBotMessageEncoder):
|
|
|
315
318
|
|
|
316
319
|
async def send_file(self, type_: str, attrs: dict) -> dict | None:
|
|
317
320
|
url = attrs.get("url") or attrs["src"]
|
|
321
|
+
filename = attrs.get("name") or attrs.get("title")
|
|
318
322
|
is_uri = url.startswith("file://")
|
|
319
323
|
b64_match = _BASE64_RE.match(url)
|
|
320
324
|
file_type = 0
|
|
@@ -331,13 +335,27 @@ class QQGroupMessageEncoder(QQBotMessageEncoder):
|
|
|
331
335
|
"file_type": file_type,
|
|
332
336
|
"srv_send_msg": False,
|
|
333
337
|
}
|
|
338
|
+
file_data = None
|
|
339
|
+
raw_data = None
|
|
334
340
|
if b64_match:
|
|
335
|
-
|
|
341
|
+
file_data = url[len(b64_match.group(0)) :]
|
|
342
|
+
raw_data = base64.b64decode(file_data)
|
|
336
343
|
elif is_uri:
|
|
337
|
-
path =
|
|
338
|
-
|
|
339
|
-
|
|
344
|
+
path = parse_file_uri(url)
|
|
345
|
+
filename = filename or path.name
|
|
346
|
+
raw_data = path.read_bytes()
|
|
347
|
+
file_data = base64.b64encode(raw_data).decode("utf-8")
|
|
348
|
+
if not file_data:
|
|
340
349
|
req["url"] = url
|
|
350
|
+
else:
|
|
351
|
+
req["file_data"] = file_data
|
|
352
|
+
if filename:
|
|
353
|
+
req["file_name"] = filename
|
|
354
|
+
|
|
355
|
+
if raw_data and len(raw_data) > MAX_FILESIZE_ONCE:
|
|
356
|
+
# 走分片上传
|
|
357
|
+
return await self._send_file_chunked(raw_data, file_type, filename)
|
|
358
|
+
|
|
341
359
|
if self.channel_id.startswith("private:") or (self.referrer and self.referrer.get("direct", False)):
|
|
342
360
|
endpoint = f"v2/users/{self.channel_id.split(':',1)[-1]}/files"
|
|
343
361
|
else:
|
|
@@ -349,11 +367,83 @@ class QQGroupMessageEncoder(QQBotMessageEncoder):
|
|
|
349
367
|
logger.error(f"Failed to upload file to {self.channel_id}: {url}\nError: {e}")
|
|
350
368
|
return None
|
|
351
369
|
|
|
370
|
+
async def _send_file_chunked(self, raw: bytes, file_type: int, filename: str | None = None):
|
|
371
|
+
req: dict = {
|
|
372
|
+
"file_type": file_type,
|
|
373
|
+
"file_name": filename or "file",
|
|
374
|
+
"file_size": len(raw),
|
|
375
|
+
"md5": md5(raw).hexdigest(),
|
|
376
|
+
"sha1": sha1(raw).hexdigest(),
|
|
377
|
+
}
|
|
378
|
+
buffer = memoryview(raw)
|
|
379
|
+
first_part = buffer[:MAX_FILESIZE_ONCE]
|
|
380
|
+
req["md5_10m"] = md5(first_part).hexdigest()
|
|
381
|
+
if self.channel_id.startswith("private:") or (self.referrer and self.referrer.get("direct", False)):
|
|
382
|
+
endpoint = f"v2/users/{self.channel_id.split(':', 1)[-1]}/files"
|
|
383
|
+
prepare_endpoint = f"v2/users/{self.channel_id.split(':', 1)[-1]}/upload_prepare"
|
|
384
|
+
finish_endpoint = f"v2/users/{self.channel_id.split(':', 1)[-1]}/upload_part_finish"
|
|
385
|
+
else:
|
|
386
|
+
endpoint = f"v2/groups/{self.channel_id}/files"
|
|
387
|
+
prepare_endpoint = f"v2/groups/{self.channel_id}/upload_prepare"
|
|
388
|
+
finish_endpoint = f"v2/groups/{self.channel_id}/upload_part_finish"
|
|
389
|
+
try:
|
|
390
|
+
prepare_resp = await self.net.call_api("post", prepare_endpoint, req)
|
|
391
|
+
except Exception as e:
|
|
392
|
+
logger.error(f"Failed to prepare file upload to {self.channel_id}: {filename}\nError: {e}")
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
async def _put(url, data):
|
|
396
|
+
async with self.net.session.put(url, data=data) as rp:
|
|
397
|
+
rp.raise_for_status()
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
upload_id = prepare_resp["upload_id"]
|
|
401
|
+
base_block_size = int(prepare_resp["block_size"])
|
|
402
|
+
chunks = bytearray(raw)
|
|
403
|
+
parts = prepare_resp["parts"]
|
|
404
|
+
concurrency = prepare_resp["upload_config"]["concurrency"]
|
|
405
|
+
sent = 0
|
|
406
|
+
while parts:
|
|
407
|
+
batch = parts[:concurrency]
|
|
408
|
+
parts = parts[concurrency:]
|
|
409
|
+
tasks = []
|
|
410
|
+
finish = []
|
|
411
|
+
for part in batch:
|
|
412
|
+
index = part["index"]
|
|
413
|
+
block_size = int(part.get("block_size") or base_block_size)
|
|
414
|
+
chunk = chunks[sent : sent + block_size]
|
|
415
|
+
sent += block_size
|
|
416
|
+
tasks.append(_put(part["presigned_url"], chunk))
|
|
417
|
+
req = {
|
|
418
|
+
"upload_id": upload_id,
|
|
419
|
+
"part_index": index,
|
|
420
|
+
"block_size": block_size,
|
|
421
|
+
"md5": md5(chunk).hexdigest(),
|
|
422
|
+
}
|
|
423
|
+
finish.append(self.net.call_api("post", finish_endpoint, req))
|
|
424
|
+
if concurrency > 1:
|
|
425
|
+
await asyncio.gather(*tasks)
|
|
426
|
+
await asyncio.gather(*finish)
|
|
427
|
+
else:
|
|
428
|
+
await tasks[0]
|
|
429
|
+
await finish[0]
|
|
430
|
+
try:
|
|
431
|
+
resp = await self.net.call_api("post", endpoint, {"upload_id": upload_id})
|
|
432
|
+
return resp
|
|
433
|
+
except Exception as e:
|
|
434
|
+
logger.error(f"Failed to finalize file upload to {self.channel_id}: {filename}\nError: {e}")
|
|
435
|
+
return None
|
|
436
|
+
|
|
352
437
|
async def visit(self, element: RawElement):
|
|
353
438
|
type_, attrs, children = element.type, element.attrs, element.children
|
|
354
439
|
match type_:
|
|
355
440
|
case "text":
|
|
356
441
|
self.content += attrs["text"]
|
|
442
|
+
case "at":
|
|
443
|
+
if attrs.get("id"):
|
|
444
|
+
self.content += f"<qqbot-at-user id=\"{attrs['id']}\" />"
|
|
445
|
+
else:
|
|
446
|
+
await self.render(children)
|
|
357
447
|
case "img" | "image":
|
|
358
448
|
if attrs.get("src") or attrs.get("url"):
|
|
359
449
|
# await self.flush()
|
|
@@ -388,9 +478,13 @@ class QQGroupMessageEncoder(QQBotMessageEncoder):
|
|
|
388
478
|
last = self.last_row()
|
|
389
479
|
last.append(self.decode_button(attrs, "".join(map(str, children))))
|
|
390
480
|
case "markdown":
|
|
481
|
+
if self.content:
|
|
482
|
+
self.content += "\n"
|
|
391
483
|
self.use_markdown = True
|
|
392
484
|
await self.render(children)
|
|
393
485
|
case "qq:markdown":
|
|
486
|
+
if self.content:
|
|
487
|
+
self.content += "\n"
|
|
394
488
|
self.use_markdown = True
|
|
395
489
|
if attrs.get("template_id"):
|
|
396
490
|
self.md_templates = {
|
|
@@ -398,6 +492,12 @@ class QQGroupMessageEncoder(QQBotMessageEncoder):
|
|
|
398
492
|
"params": [{"key": k, "value": v} for k, v in attrs.items() if k != "template_id"],
|
|
399
493
|
}
|
|
400
494
|
await self.render(children)
|
|
495
|
+
case "qq:cmd-enter":
|
|
496
|
+
self.use_markdown = True
|
|
497
|
+
self.content += RawElement("qqbot-cmd-enter", attrs, []).dumps()
|
|
498
|
+
case "qq:cmd-input":
|
|
499
|
+
self.use_markdown = True
|
|
500
|
+
self.content += RawElement("qqbot-cmd-input", attrs, []).dumps()
|
|
401
501
|
case "message":
|
|
402
502
|
await self.flush()
|
|
403
503
|
await self.render(children)
|
{satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/utils.py
RENAMED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
from typing import Literal, Protocol
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
from urllib.request import url2pathname
|
|
5
8
|
|
|
6
|
-
from aiohttp import ClientResponse
|
|
9
|
+
from aiohttp import ClientResponse, ClientSession
|
|
10
|
+
|
|
11
|
+
from satori.model import Channel, ChannelType, Guild, Member, Role, User
|
|
7
12
|
|
|
8
|
-
from ... import Channel, ChannelType, Guild, Member, Role, User
|
|
9
13
|
from .exception import ActionFailed, ApiNotAvailable, AuditException, RateLimitException, UnauthorizedException
|
|
10
14
|
|
|
11
15
|
CallMethod = Literal["get", "post", "fetch", "update", "multipart", "put", "delete", "patch"]
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class QQBotNetwork(Protocol):
|
|
19
|
+
session: ClientSession
|
|
20
|
+
|
|
15
21
|
async def call_api(self, method: CallMethod, action: str, params: dict | None = None) -> dict: ...
|
|
16
22
|
|
|
17
23
|
|
|
@@ -135,3 +141,17 @@ ROLE_MAPPING = {
|
|
|
135
141
|
"2": Role("admin", "管理员"),
|
|
136
142
|
"4": Role("owner", "创建者"),
|
|
137
143
|
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def parse_file_uri(uri):
|
|
147
|
+
"""解析 file URI 为 Path 对象"""
|
|
148
|
+
parsed = urlparse(uri)
|
|
149
|
+
if parsed.scheme != "file":
|
|
150
|
+
raise ValueError(f"不是 file URI: {uri}")
|
|
151
|
+
|
|
152
|
+
path = url2pathname(parsed.path)
|
|
153
|
+
|
|
154
|
+
if parsed.netloc and parsed.netloc != "localhost":
|
|
155
|
+
path = f"//{parsed.netloc}{path}"
|
|
156
|
+
|
|
157
|
+
return Path(path)
|
|
@@ -112,7 +112,7 @@ class Intents:
|
|
|
112
112
|
class QQBotWebsocketAdapter(BaseAdapter):
|
|
113
113
|
|
|
114
114
|
connections: dict[tuple[int, int], aiohttp.ClientWebSocketResponse]
|
|
115
|
-
session: aiohttp.ClientSession
|
|
115
|
+
session: aiohttp.ClientSession
|
|
116
116
|
sequence: int | None
|
|
117
117
|
session_id: str | None
|
|
118
118
|
_access_token: str | None
|
|
@@ -139,7 +139,7 @@ class QQBotWebsocketAdapter(BaseAdapter):
|
|
|
139
139
|
self.intent = intent
|
|
140
140
|
self.api_base = URL(str(api_base if not is_sandbox else sandbox_api_base))
|
|
141
141
|
self.auth_base = URL(str(auth_base))
|
|
142
|
-
self.session = None
|
|
142
|
+
self.session = None # type: ignore
|
|
143
143
|
self.logins: list[Login] = []
|
|
144
144
|
self.bot_id_mapping: dict[str, str] = {} # login.id -> bot app_id
|
|
145
145
|
self.close_signal = asyncio.Event()
|
|
@@ -330,7 +330,9 @@ class QQBotWebsocketAdapter(BaseAdapter):
|
|
|
330
330
|
async def refresh_login(self, profile: dict):
|
|
331
331
|
user = decode_user(profile)
|
|
332
332
|
user.is_bot = True
|
|
333
|
-
login = Login(
|
|
333
|
+
login = Login(
|
|
334
|
+
sn=0, status=LoginStatus.ONLINE, adapter="qqbot", platform="qq", user=user, features=QQ_FEATURES.copy()
|
|
335
|
+
)
|
|
334
336
|
previous = next((lg for lg in self.logins if lg.id == login.id and lg.platform == "qq"), None)
|
|
335
337
|
if previous:
|
|
336
338
|
previous.user = login.user
|
|
@@ -343,7 +345,12 @@ class QQBotWebsocketAdapter(BaseAdapter):
|
|
|
343
345
|
event_type = EventType.LOGIN_ADDED
|
|
344
346
|
await self.server.post(Event(event_type, datetime.now(), login))
|
|
345
347
|
guild_login = Login(
|
|
346
|
-
0,
|
|
348
|
+
sn=0,
|
|
349
|
+
status=LoginStatus.ONLINE,
|
|
350
|
+
adapter="qqbot",
|
|
351
|
+
platform="qqguild",
|
|
352
|
+
user=user,
|
|
353
|
+
features=QQ_GUILD_FEATURES.copy(),
|
|
347
354
|
)
|
|
348
355
|
previous = next((lg for lg in self.logins if lg.id == guild_login.id and lg.platform == "qqguild"), None)
|
|
349
356
|
if previous:
|
|
@@ -498,7 +505,7 @@ class QQBotWebsocketAdapter(BaseAdapter):
|
|
|
498
505
|
async with self.stage("cleanup"):
|
|
499
506
|
if self.session:
|
|
500
507
|
await self.session.close()
|
|
501
|
-
self.session = None
|
|
508
|
+
self.session = None # type: ignore
|
|
502
509
|
for task in tasks:
|
|
503
510
|
task.cancel()
|
|
504
511
|
await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)
|
|
File without changes
|
|
File without changes
|
{satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/__init__.py
RENAMED
|
File without changes
|
{satori_python_adapter_qq-0.3.0 → satori_python_adapter_qq-0.3.2}/src/satori/adapters/qq/api.py
RENAMED
|
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
|