🐛 fix(*): 修复代码审查中发现的问题
- **Bug 修复** - `message.py`: 批量发送时使用 `(index, msg)` 元组替代 `messages.index(msg)`,避免重复 dict 查找错误 - `message.py`: 多张图片逐张发送,不再静默丢弃后续图片 - `plugin.py`: API Key 日志只打印"已配置/自动生成",不再泄露密钥 - **潜在问题修复** - `message.py`: lambda 闭包添加默认参数绑定,防止循环变量捕获问题 - `upload.py`: 文件超限后消费剩余 multipart 数据,避免 reader 状态异常 - `config.py`: PORT 环境变量非法值容错,默认回退 8081 - `plugin.py`: cleanup task 保存引用,on_close 时正确取消,避免热重载泄漏 - **代码风格** - `message.py`: 无插值 f-string 改为普通字符串 - `upload.py`: read_chunk 硬编码提取为 CHUNK_SIZE 常量
This commit is contained in:
@@ -14,7 +14,10 @@ WEBHOOK_API_KEY: str = os.environ.get("WEBHOOK_API_KEY", "") or uuid.uuid4().hex
|
|||||||
|
|
||||||
# ── 网络 ─────────────────────────────────────────────────────
|
# ── 网络 ─────────────────────────────────────────────────────
|
||||||
HOST: str = os.environ.get("WEBHOOK_HOST", "0.0.0.0")
|
HOST: str = os.environ.get("WEBHOOK_HOST", "0.0.0.0")
|
||||||
PORT: int = int(os.environ.get("WEBHOOK_PORT", "8081"))
|
try:
|
||||||
|
PORT: int = int(os.environ.get("WEBHOOK_PORT", "8081"))
|
||||||
|
except ValueError:
|
||||||
|
PORT = 8081
|
||||||
|
|
||||||
# ── 上传 ─────────────────────────────────────────────────────
|
# ── 上传 ─────────────────────────────────────────────────────
|
||||||
UPLOAD_DIR: Path = Path(os.environ.get("UPLOAD_DIR", str(Path(__file__).parent / "uploads")))
|
UPLOAD_DIR: Path = Path(os.environ.get("UPLOAD_DIR", str(Path(__file__).parent / "uploads")))
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ def _validate_message(msg: dict) -> str | None:
|
|||||||
if msg_type == "text" and not msg.get("msg"):
|
if msg_type == "text" and not msg.get("msg"):
|
||||||
return "missing required field: msg"
|
return "missing required field: msg"
|
||||||
if msg_type in {"image", "file", "video"} and not msg.get("url"):
|
if msg_type in {"image", "file", "video"} and not msg.get("url"):
|
||||||
return f"missing required field: url"
|
return "missing required field: url"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -50,28 +50,195 @@ async def _call_with_retry(coro_factory, logger, rid: str) -> None:
|
|||||||
raise Exception(f"qq api failed after {QQ_API_MAX_RETRIES} retries: {last_exc}")
|
raise Exception(f"qq api failed after {QQ_API_MAX_RETRIES} retries: {last_exc}")
|
||||||
|
|
||||||
|
|
||||||
async def webhook_handler(request: web.Request) -> web.Response:
|
async def _send_single(api, msg_type: str, url: str | None, msg_text: str,
|
||||||
"""处理消息发送请求,支持单条和批量发送。
|
group_id: str | None, user_id: str | None,
|
||||||
|
logger, rid: str) -> None:
|
||||||
|
"""发送单条消息(非组合模式)。"""
|
||||||
|
filename = url.split("/")[-1] if url else None
|
||||||
|
|
||||||
单条格式:
|
if group_id:
|
||||||
{
|
match msg_type:
|
||||||
"group_id": "123",
|
case "text":
|
||||||
"type": "text",
|
await _call_with_retry(
|
||||||
"msg": "hello"
|
lambda t=msg_text: api.qq.send_group_text(group_id=group_id, text=t),
|
||||||
}
|
logger, rid
|
||||||
|
)
|
||||||
|
case "image":
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=url: api.qq.send_group_image(group_id=group_id, image=u),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
case "file":
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=url, n=filename: api.qq.send_group_file(group_id=group_id, file=u, name=n),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
case "video":
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=url: api.qq.send_group_video(group_id=group_id, video=u),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
match msg_type:
|
||||||
|
case "text":
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda t=msg_text: api.qq.send_private_text(user_id=user_id, text=t),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
case "image":
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=url: api.qq.send_private_image(user_id=user_id, image=u),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
case "file":
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=url, n=filename: api.qq.send_private_file(user_id=user_id, file=u, name=n),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
case "video":
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=url: api.qq.send_private_video(user_id=user_id, video=u),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
|
||||||
批量格式(使用 post_group_msg 组合消息段):
|
|
||||||
{
|
|
||||||
"group_id": "123",
|
|
||||||
"messages": [
|
|
||||||
{"type": "text", "msg": "hello"},
|
|
||||||
{"type": "image", "url": "photo.jpg"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
批量发送时,text/image/video 会在一条消息里组合发送(框架自动拆分冲突段),
|
async def _send_batch(api, messages: list[dict], group_id: str | None, user_id: str | None,
|
||||||
file 单独发送(因为 file 是独占段)。
|
logger, rid: str) -> list[dict]:
|
||||||
|
"""批量发送消息。
|
||||||
|
|
||||||
|
- text/image/video 组合为一条消息(post_group_msg / post_private_msg)
|
||||||
|
- 多张图片拆成多条消息发送
|
||||||
|
- file 单独发送(独占段)
|
||||||
"""
|
"""
|
||||||
|
results: list[dict] = []
|
||||||
|
|
||||||
|
# 分组:每条消息记录原始 index
|
||||||
|
combined_items: list[tuple[int, dict]] = [] # (index, msg) — text/image/video
|
||||||
|
file_items: list[tuple[int, dict]] = [] # (index, msg) — file
|
||||||
|
|
||||||
|
for i, msg in enumerate(messages):
|
||||||
|
if msg.get("type") == "file":
|
||||||
|
file_items.append((i, msg))
|
||||||
|
else:
|
||||||
|
combined_items.append((i, msg))
|
||||||
|
|
||||||
|
# 处理组合消息:按图片拆组,每组最多一张图片
|
||||||
|
if combined_items:
|
||||||
|
# 收集所有 text、image、video
|
||||||
|
text_parts: list[str] = []
|
||||||
|
image_items: list[tuple[int, dict]] = []
|
||||||
|
video_url: str | None = None
|
||||||
|
video_index: int | None = None
|
||||||
|
combined_indices: list[int] = []
|
||||||
|
|
||||||
|
for idx, msg in combined_items:
|
||||||
|
msg_type = msg.get("type", "text")
|
||||||
|
if msg_type == "text":
|
||||||
|
text_parts.append(msg.get("msg", ""))
|
||||||
|
combined_indices.append(idx)
|
||||||
|
elif msg_type == "image":
|
||||||
|
image_items.append((idx, msg))
|
||||||
|
elif msg_type == "video":
|
||||||
|
video_url = _resolve_url(msg.get("url", ""))
|
||||||
|
video_index = idx
|
||||||
|
|
||||||
|
text = "\n".join(text_parts) if text_parts else None
|
||||||
|
|
||||||
|
# 如果有视频,视频单独发(独占段会吞掉其他内容)
|
||||||
|
if video_url:
|
||||||
|
try:
|
||||||
|
if group_id:
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=video_url: api.qq.post_group_msg(
|
||||||
|
group_id=group_id, video=u
|
||||||
|
),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=video_url: api.qq.post_private_msg(
|
||||||
|
user_id=user_id, video=u
|
||||||
|
),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
results.append({"index": video_index, "success": True})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"[{rid}] Video message failed: {exc}")
|
||||||
|
results.append({"index": video_index, "success": False, "error": str(exc)})
|
||||||
|
|
||||||
|
# 文本 + 第一张图片组合
|
||||||
|
if text or image_items:
|
||||||
|
first_image_url = _resolve_url(image_items[0][1].get("url", "")) if image_items else None
|
||||||
|
text_image_indices = combined_indices + ([image_items[0][0]] if image_items else [])
|
||||||
|
|
||||||
|
try:
|
||||||
|
if group_id:
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda t=text, img=first_image_url: api.qq.post_group_msg(
|
||||||
|
group_id=group_id, text=t, image=img
|
||||||
|
),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda t=text, img=first_image_url: api.qq.post_private_msg(
|
||||||
|
user_id=user_id, text=t, image=img
|
||||||
|
),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
for idx in text_image_indices:
|
||||||
|
results.append({"index": idx, "success": True})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"[{rid}] Text+image message failed: {exc}")
|
||||||
|
for idx in text_image_indices:
|
||||||
|
results.append({"index": idx, "success": False, "error": str(exc)})
|
||||||
|
|
||||||
|
# 剩余图片逐张发送
|
||||||
|
for img_idx, img_msg in image_items[1:]:
|
||||||
|
img_url = _resolve_url(img_msg.get("url", ""))
|
||||||
|
try:
|
||||||
|
if group_id:
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=img_url: api.qq.send_group_image(group_id=group_id, image=u),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=img_url: api.qq.send_private_image(user_id=user_id, image=u),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
results.append({"index": img_idx, "success": True})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"[{rid}] Image message failed: {exc}")
|
||||||
|
results.append({"index": img_idx, "success": False, "error": str(exc)})
|
||||||
|
|
||||||
|
# 文件单独发送
|
||||||
|
for idx, msg in file_items:
|
||||||
|
url = _resolve_url(msg.get("url", ""))
|
||||||
|
filename = url.split("/")[-1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if group_id:
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=url, n=filename: api.qq.send_group_file(group_id=group_id, file=u, name=n),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await _call_with_retry(
|
||||||
|
lambda u=url, n=filename: api.qq.send_private_file(user_id=user_id, file=u, name=n),
|
||||||
|
logger, rid
|
||||||
|
)
|
||||||
|
results.append({"index": idx, "success": True})
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"[{rid}] File message failed: {exc}")
|
||||||
|
results.append({"index": idx, "success": False, "error": str(exc)})
|
||||||
|
|
||||||
|
results.sort(key=lambda x: x["index"])
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
async def webhook_handler(request: web.Request) -> web.Response:
|
||||||
|
"""处理消息发送请求,支持单条和批量发送。"""
|
||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -108,98 +275,13 @@ async def webhook_handler(request: web.Request) -> web.Response:
|
|||||||
if err:
|
if err:
|
||||||
return error(f"messages[{i}]: {err}")
|
return error(f"messages[{i}]: {err}")
|
||||||
|
|
||||||
# 分组:text/image/video 可以组合,file 必须单独发
|
try:
|
||||||
combined_msgs: list[dict] = [] # text/image/video 组合
|
results = await _send_batch(api, messages, group_id, user_id, logger, rid)
|
||||||
file_msgs: list[dict] = [] # file 单独发
|
except Exception as exc:
|
||||||
|
logger.error(f"[{rid}] Batch send failed: {exc}")
|
||||||
|
return error(f"batch send failed: {exc}", code=502, status=502)
|
||||||
|
|
||||||
for msg in messages:
|
|
||||||
if msg.get("type") == "file":
|
|
||||||
file_msgs.append(msg)
|
|
||||||
else:
|
|
||||||
combined_msgs.append(msg)
|
|
||||||
|
|
||||||
results: list[dict] = []
|
|
||||||
|
|
||||||
# 发送组合消息(text + image + video)
|
|
||||||
if combined_msgs:
|
|
||||||
text_parts: list[str] = []
|
|
||||||
image_urls: list[str] = []
|
|
||||||
video_url: str | None = None
|
|
||||||
|
|
||||||
for msg in combined_msgs:
|
|
||||||
msg_type = msg.get("type", "text")
|
|
||||||
if msg_type == "text":
|
|
||||||
text_parts.append(msg.get("msg", ""))
|
|
||||||
elif msg_type == "image":
|
|
||||||
image_urls.append(_resolve_url(msg.get("url", "")))
|
|
||||||
elif msg_type == "video":
|
|
||||||
video_url = _resolve_url(msg.get("url", ""))
|
|
||||||
|
|
||||||
text = "\n".join(text_parts) if text_parts else None
|
|
||||||
image = image_urls[0] if image_urls else None # post_group_msg 只支持单张图片
|
|
||||||
|
|
||||||
try:
|
|
||||||
if group_id:
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.post_group_msg(
|
|
||||||
group_id=group_id,
|
|
||||||
text=text,
|
|
||||||
image=image,
|
|
||||||
video=video_url,
|
|
||||||
),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.post_private_msg(
|
|
||||||
user_id=user_id,
|
|
||||||
text=text,
|
|
||||||
image=image,
|
|
||||||
video=video_url,
|
|
||||||
),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
|
|
||||||
# 所有组合消息标记为成功
|
|
||||||
for i, msg in enumerate(messages):
|
|
||||||
if msg.get("type") != "file":
|
|
||||||
results.append({"index": i, "success": True})
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(f"[{rid}] Combined message failed: {exc}")
|
|
||||||
for i, msg in enumerate(messages):
|
|
||||||
if msg.get("type") != "file":
|
|
||||||
results.append({"index": i, "success": False, "error": str(exc)})
|
|
||||||
|
|
||||||
# 发送文件消息(每个文件单独一条)
|
|
||||||
for msg in file_msgs:
|
|
||||||
idx = messages.index(msg)
|
|
||||||
url = _resolve_url(msg.get("url", ""))
|
|
||||||
filename = url.split("/")[-1]
|
|
||||||
|
|
||||||
try:
|
|
||||||
if group_id:
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda u=url, n=filename: api.qq.send_group_file(
|
|
||||||
group_id=group_id, file=u, name=n
|
|
||||||
),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda u=url, n=filename: api.qq.send_private_file(
|
|
||||||
user_id=user_id, file=u, name=n
|
|
||||||
),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
results.append({"index": idx, "success": True})
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(f"[{rid}] File message failed: {exc}")
|
|
||||||
results.append({"index": idx, "success": False, "error": str(exc)})
|
|
||||||
|
|
||||||
# 按 index 排序结果
|
|
||||||
results.sort(key=lambda x: x["index"])
|
|
||||||
success_count = sum(1 for r in results if r["success"])
|
success_count = sum(1 for r in results if r["success"])
|
||||||
|
|
||||||
return ok(data={
|
return ok(data={
|
||||||
"total": len(results),
|
"total": len(results),
|
||||||
"success": success_count,
|
"success": success_count,
|
||||||
@@ -208,7 +290,7 @@ async def webhook_handler(request: web.Request) -> web.Response:
|
|||||||
})
|
})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# 单条模式(兼容旧格式)
|
# 单条模式
|
||||||
err = _validate_message(data)
|
err = _validate_message(data)
|
||||||
if err:
|
if err:
|
||||||
return error(err)
|
return error(err)
|
||||||
@@ -216,54 +298,9 @@ async def webhook_handler(request: web.Request) -> web.Response:
|
|||||||
msg_type = data.get("type", "text")
|
msg_type = data.get("type", "text")
|
||||||
url = _resolve_url(data.get("url", "")) if msg_type != "text" else None
|
url = _resolve_url(data.get("url", "")) if msg_type != "text" else None
|
||||||
msg_text = data.get("msg", "")
|
msg_text = data.get("msg", "")
|
||||||
filename = url.split("/")[-1] if url else None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if group_id:
|
await _send_single(api, msg_type, url, msg_text, group_id, user_id, logger, rid)
|
||||||
match msg_type:
|
|
||||||
case "text":
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.send_group_text(group_id=group_id, text=msg_text),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
case "image":
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.send_group_image(group_id=group_id, image=url),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
case "file":
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.send_group_file(group_id=group_id, file=url, name=filename),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
case "video":
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.send_group_video(group_id=group_id, video=url),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
match msg_type:
|
|
||||||
case "text":
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.send_private_text(user_id=user_id, text=msg_text),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
case "image":
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.send_private_image(user_id=user_id, image=url),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
case "file":
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.send_private_file(user_id=user_id, file=url, name=filename),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
case "video":
|
|
||||||
await _call_with_retry(
|
|
||||||
lambda: api.qq.send_private_video(user_id=user_id, video=url),
|
|
||||||
logger, rid
|
|
||||||
)
|
|
||||||
|
|
||||||
return ok()
|
return ok()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"[{rid}] QQ API error: {exc}")
|
logger.error(f"[{rid}] QQ API error: {exc}")
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ logger = logging.getLogger("webhook-plugin.upload")
|
|||||||
|
|
||||||
# 文件最大保留秒数(默认 24 小时)
|
# 文件最大保留秒数(默认 24 小时)
|
||||||
FILE_TTL_SECONDS: int = 24 * 60 * 60
|
FILE_TTL_SECONDS: int = 24 * 60 * 60
|
||||||
|
# 读取块大小
|
||||||
|
CHUNK_SIZE: int = 65536
|
||||||
|
|
||||||
|
|
||||||
def _check_extension(filename: str) -> bool:
|
def _check_extension(filename: str) -> bool:
|
||||||
@@ -58,11 +60,14 @@ async def upload_handler(request: web.Request) -> web.Response:
|
|||||||
chunks: list[bytes] = []
|
chunks: list[bytes] = []
|
||||||
total_size = 0
|
total_size = 0
|
||||||
while True:
|
while True:
|
||||||
chunk = await part.read_chunk(65536)
|
chunk = await part.read_chunk(CHUNK_SIZE)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
total_size += len(chunk)
|
total_size += len(chunk)
|
||||||
if total_size > MAX_UPLOAD_SIZE:
|
if total_size > MAX_UPLOAD_SIZE:
|
||||||
|
# 消费剩余数据,避免 multipart reader 状态异常
|
||||||
|
while await part.read_chunk(CHUNK_SIZE):
|
||||||
|
pass
|
||||||
return error(
|
return error(
|
||||||
f"file too large, max {MAX_UPLOAD_SIZE // (1024*1024)} MB",
|
f"file too large, max {MAX_UPLOAD_SIZE // (1024*1024)} MB",
|
||||||
code=413,
|
code=413,
|
||||||
|
|||||||
13
plugin.py
13
plugin.py
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from ncatbot.plugin import NcatBotPlugin
|
from ncatbot.plugin import NcatBotPlugin
|
||||||
@@ -24,14 +25,22 @@ class WebHookPlugin(NcatBotPlugin):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._webhook_runner: web.AppRunner | None = None
|
self._webhook_runner: web.AppRunner | None = None
|
||||||
|
self._cleanup_task: asyncio.Task | None = None
|
||||||
|
|
||||||
async def on_load(self):
|
async def on_load(self):
|
||||||
self.logger.info("Webhook 插件已加载")
|
self.logger.info("Webhook 插件已加载")
|
||||||
self.logger.info("WEBHOOK_API_KEY: %s", WEBHOOK_API_KEY)
|
self.logger.info("WEBHOOK_API_KEY: %s", "已配置" if os.environ.get("WEBHOOK_API_KEY") else "自动生成")
|
||||||
asyncio.create_task(self._start_webhook())
|
asyncio.create_task(self._start_webhook())
|
||||||
asyncio.create_task(self._cleanup_loop())
|
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
||||||
|
|
||||||
async def on_close(self):
|
async def on_close(self):
|
||||||
|
if self._cleanup_task is not None:
|
||||||
|
self._cleanup_task.cancel()
|
||||||
|
try:
|
||||||
|
await self._cleanup_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
self._cleanup_task = None
|
||||||
await self._stop_webhook()
|
await self._stop_webhook()
|
||||||
self.logger.info("Webhook 插件已卸载")
|
self.logger.info("Webhook 插件已卸载")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user