nonebot-plugin-shiro-web-console 0.1.7__tar.gz → 0.1.8__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.
Potentially problematic release.
This version of nonebot-plugin-shiro-web-console might be problematic. Click here for more details.
- {nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/PKG-INFO +1 -1
- {nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/nonebot_plugin_shiro_web_console/__init__.py +178 -65
- {nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/nonebot_plugin_shiro_web_console/static/index.html +3 -1
- {nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/pyproject.toml +1 -1
- {nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/.gitignore +0 -0
- {nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/LICENSE +0 -0
- {nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/README.md +0 -0
- {nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/nonebot_plugin_shiro_web_console/config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nonebot-plugin-shiro-web-console
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: 一个用于 NoneBot2 的网页控制台插件,支持通过浏览器查看日志和发送消息
|
|
5
5
|
Project-URL: Homepage, https://github.com/luojisama/nonebot-plugin-shiro-web-console
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/luojisama/nonebot-plugin-shiro-web-console/issues
|
|
@@ -16,11 +16,11 @@ from collections import deque
|
|
|
16
16
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request, Response, Depends, HTTPException
|
|
17
17
|
from fastapi.staticfiles import StaticFiles
|
|
18
18
|
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
|
|
19
|
-
from nonebot import get_app, get_bot, get_bots, get_driver, logger, on_message, on_command, require
|
|
19
|
+
from nonebot import get_app, get_bot, get_bots, get_driver, logger, on_message, on_command, require, on_bot_connect
|
|
20
20
|
import nonebot_plugin_localstore
|
|
21
21
|
from .config import Config, config
|
|
22
22
|
from nonebot.permission import SUPERUSER
|
|
23
|
-
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, PrivateMessageEvent, MessageSegment
|
|
23
|
+
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, PrivateMessageEvent, MessageSegment, Message
|
|
24
24
|
from nonebot.plugin import PluginMetadata
|
|
25
25
|
|
|
26
26
|
START_TIME = time.time()
|
|
@@ -35,7 +35,7 @@ __plugin_meta__ = PluginMetadata(
|
|
|
35
35
|
supported_adapters={"~onebot.v11"},
|
|
36
36
|
extra={
|
|
37
37
|
"author": "luojisama",
|
|
38
|
-
"version": "0.1.
|
|
38
|
+
"version": "0.1.8",
|
|
39
39
|
"pypi_test": "nonebot-plugin-shiro-web-console",
|
|
40
40
|
},
|
|
41
41
|
)
|
|
@@ -316,35 +316,34 @@ async def handle_login_cmd(bot: Bot, event: MessageEvent):
|
|
|
316
316
|
first_url = f"http://{public_ips[0]}:{port}/web_console" if public_ips else f"http://127.0.0.1:{port}/web_console"
|
|
317
317
|
await login_cmd.finish(f"私聊发送失败,请确保您已添加机器人为好友。\n(当前环境访问地址提示:{first_url})")
|
|
318
318
|
|
|
319
|
-
#
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
@msg_matcher.handle()
|
|
323
|
-
async def handle_all_messages(bot: Bot, event: MessageEvent):
|
|
324
|
-
chat_id = get_chat_id(event)
|
|
325
|
-
|
|
326
|
-
# 尝试通过 get_msg 获取更详细的消息内容(尤其是 NapCat 等框架提供的 URL)
|
|
327
|
-
sender_name = event.sender.nickname or str(event.user_id)
|
|
328
|
-
try:
|
|
329
|
-
msg_details = await bot.get_msg(message_id=event.message_id)
|
|
330
|
-
message = msg_details["message"]
|
|
331
|
-
# 如果 get_msg 返回了 sender 信息,则优先使用
|
|
332
|
-
if "sender" in msg_details:
|
|
333
|
-
sender_name = msg_details["sender"].get("nickname") or msg_details["sender"].get("card") or sender_name
|
|
334
|
-
except Exception as e:
|
|
335
|
-
logger.warning(f"获取消息详情失败: {e},将使用事件自带消息内容")
|
|
336
|
-
message = event.get_message()
|
|
337
|
-
|
|
338
|
-
# 消息内容解析
|
|
319
|
+
# 辅助函数:解析消息段
|
|
320
|
+
def parse_message_elements(message_segments) -> List[dict]:
|
|
339
321
|
elements = []
|
|
340
|
-
# 消息唯一 ID:使用适配器提供的原始 ID,以便前端去重
|
|
341
|
-
msg_id = str(event.message_id)
|
|
342
322
|
|
|
343
|
-
#
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
323
|
+
# 鲁棒性处理:如果是字符串,尝试转为 Message 对象
|
|
324
|
+
if isinstance(message_segments, str):
|
|
325
|
+
try:
|
|
326
|
+
# Message(str) 会自动解析 CQ 码(如果适配器支持)或作为纯文本
|
|
327
|
+
message_segments = Message(message_segments)
|
|
328
|
+
except Exception:
|
|
329
|
+
# 降级处理
|
|
330
|
+
return [{"type": "text", "data": {"text": message_segments}}]
|
|
331
|
+
|
|
332
|
+
# 如果是 Message 对象,转为 list
|
|
333
|
+
if hasattr(message_segments, "__iter__") and not isinstance(message_segments, (list, tuple)):
|
|
334
|
+
# Message 对象迭代出来是 MessageSegment
|
|
335
|
+
segments = list(message_segments)
|
|
336
|
+
else:
|
|
337
|
+
segments = message_segments
|
|
338
|
+
|
|
339
|
+
for seg in segments:
|
|
340
|
+
# 兼容 dict 和 MessageSegment
|
|
341
|
+
if isinstance(seg, dict):
|
|
342
|
+
seg_type = seg.get("type")
|
|
343
|
+
seg_data = seg.get("data", {})
|
|
344
|
+
else:
|
|
345
|
+
seg_type = seg.type
|
|
346
|
+
seg_data = seg.data
|
|
348
347
|
|
|
349
348
|
if seg_type == "text":
|
|
350
349
|
elements.append({"type": "text", "data": seg_data.get("text", "")})
|
|
@@ -354,32 +353,126 @@ async def handle_all_messages(bot: Bot, event: MessageEvent):
|
|
|
354
353
|
# 优先从 get_msg 的数据中获取 url,NapCat 在 Linux 下可能返回 path 或 file 字段
|
|
355
354
|
raw_url = seg_data.get("url") or seg_data.get("file") or seg_data.get("path") or ""
|
|
356
355
|
|
|
357
|
-
#
|
|
358
|
-
if raw_url
|
|
359
|
-
# 这种情况下可能是 base64 或者本地路径,或者是 CQ 码中的 file 字段
|
|
360
|
-
# 我们先尝试直接传给前端,由前端处理或后续通过代理获取
|
|
361
|
-
pass
|
|
362
|
-
|
|
363
|
-
# 为了确保显示,所有图片都尝试通过代理中转,除非是 base64
|
|
356
|
+
# 代理链接不带 token,由前端动态注入或 check_auth 处理
|
|
357
|
+
final_url = f"/web_console/proxy/image?url={quote(raw_url)}" if raw_url else ""
|
|
364
358
|
if raw_url.startswith("data:image"):
|
|
365
359
|
final_url = raw_url
|
|
366
|
-
|
|
367
|
-
# 代理链接不带 token,由前端动态注入或 check_auth 处理
|
|
368
|
-
final_url = f"/web_console/proxy/image?url={quote(raw_url)}" if raw_url else ""
|
|
369
|
-
|
|
360
|
+
|
|
370
361
|
elements.append({"type": "image", "data": final_url, "raw": raw_url})
|
|
371
362
|
elif seg_type == "face":
|
|
372
363
|
face_id = seg_data.get("id")
|
|
373
364
|
face_url = f"https://s.p.qq.com/pub/get_face?img_type=3&face_id={face_id}"
|
|
374
365
|
elements.append({"type": "face", "data": face_url, "id": face_id})
|
|
375
366
|
elif seg_type == "mface":
|
|
376
|
-
# 商城表情(Stickers)
|
|
377
367
|
url = seg_data.get("url")
|
|
378
368
|
elements.append({"type": "image", "data": url})
|
|
379
369
|
elif seg_type == "at":
|
|
380
370
|
elements.append({"type": "at", "data": seg_data.get("qq")})
|
|
381
371
|
elif seg_type == "reply":
|
|
382
372
|
elements.append({"type": "reply", "data": seg_data.get("id")})
|
|
373
|
+
|
|
374
|
+
return elements
|
|
375
|
+
|
|
376
|
+
# Hook: 监听 Bot API 调用,捕获发送的消息
|
|
377
|
+
async def on_api_called(bot: Bot, exception: Optional[Exception], api: str, data: Dict[str, Any], result: Any):
|
|
378
|
+
if exception:
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
if api in ["send_group_msg", "send_private_msg", "send_msg"]:
|
|
382
|
+
try:
|
|
383
|
+
# Parse data
|
|
384
|
+
message = data.get("message")
|
|
385
|
+
if isinstance(message, str):
|
|
386
|
+
msg_obj = Message(message)
|
|
387
|
+
elif isinstance(message, list):
|
|
388
|
+
# 假设是 list of dicts
|
|
389
|
+
msg_obj = message
|
|
390
|
+
else:
|
|
391
|
+
msg_obj = message
|
|
392
|
+
|
|
393
|
+
elements = parse_message_elements(msg_obj)
|
|
394
|
+
|
|
395
|
+
# Determine chat_id
|
|
396
|
+
chat_id = ""
|
|
397
|
+
if api == "send_group_msg":
|
|
398
|
+
chat_id = f"group_{data.get('group_id')}"
|
|
399
|
+
elif api == "send_private_msg":
|
|
400
|
+
chat_id = f"private_{data.get('user_id')}"
|
|
401
|
+
elif api == "send_msg":
|
|
402
|
+
if data.get("message_type") == "group":
|
|
403
|
+
chat_id = f"group_{data.get('group_id')}"
|
|
404
|
+
else:
|
|
405
|
+
chat_id = f"private_{data.get('user_id')}"
|
|
406
|
+
|
|
407
|
+
if not chat_id:
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
# Construct msg_data
|
|
411
|
+
msg_id = 0
|
|
412
|
+
if isinstance(result, dict):
|
|
413
|
+
msg_id = result.get("message_id", 0)
|
|
414
|
+
elif isinstance(result, int):
|
|
415
|
+
msg_id = result
|
|
416
|
+
|
|
417
|
+
# 获取 content 字符串表示
|
|
418
|
+
content_str = str(message) if not isinstance(message, list) else "[Message]"
|
|
419
|
+
|
|
420
|
+
msg_data = {
|
|
421
|
+
"id": msg_id,
|
|
422
|
+
"chat_id": chat_id,
|
|
423
|
+
"time": int(time.time()),
|
|
424
|
+
"type": "group" if "group" in chat_id else "private",
|
|
425
|
+
"sender_id": bot.self_id,
|
|
426
|
+
"sender_name": "我",
|
|
427
|
+
"sender_avatar": f"https://q1.qlogo.cn/g?b=qq&nk={bot.self_id}&s=640",
|
|
428
|
+
"elements": elements,
|
|
429
|
+
"content": content_str,
|
|
430
|
+
"self_id": bot.self_id,
|
|
431
|
+
"is_self": True
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# Add to cache and broadcast
|
|
435
|
+
if chat_id not in message_cache:
|
|
436
|
+
message_cache[chat_id] = []
|
|
437
|
+
|
|
438
|
+
message_cache[chat_id].append(msg_data)
|
|
439
|
+
if len(message_cache[chat_id]) > CACHE_SIZE:
|
|
440
|
+
message_cache[chat_id].pop(0)
|
|
441
|
+
|
|
442
|
+
await broadcast_message({
|
|
443
|
+
"type": "new_message",
|
|
444
|
+
"chat_id": chat_id,
|
|
445
|
+
"data": msg_data
|
|
446
|
+
})
|
|
447
|
+
except Exception as e:
|
|
448
|
+
logger.error(f"处理 Bot 发送消息 Hook 失败: {e}")
|
|
449
|
+
|
|
450
|
+
@on_bot_connect
|
|
451
|
+
async def _(bot: Bot):
|
|
452
|
+
if hasattr(bot, "on_called_api"):
|
|
453
|
+
bot.on_called_api(on_api_called)
|
|
454
|
+
|
|
455
|
+
# 监听所有消息
|
|
456
|
+
msg_matcher = on_message(priority=1, block=False)
|
|
457
|
+
|
|
458
|
+
@msg_matcher.handle()
|
|
459
|
+
async def handle_all_messages(bot: Bot, event: MessageEvent):
|
|
460
|
+
chat_id = get_chat_id(event)
|
|
461
|
+
|
|
462
|
+
# 尝试通过 get_msg 获取更详细的消息内容(尤其是 NapCat 等框架提供的 URL)
|
|
463
|
+
sender_name = event.sender.nickname or str(event.user_id)
|
|
464
|
+
try:
|
|
465
|
+
msg_details = await bot.get_msg(message_id=event.message_id)
|
|
466
|
+
message = msg_details["message"]
|
|
467
|
+
# 如果 get_msg 返回了 sender 信息,则优先使用
|
|
468
|
+
if "sender" in msg_details:
|
|
469
|
+
sender_name = msg_details["sender"].get("nickname") or msg_details["sender"].get("card") or sender_name
|
|
470
|
+
except Exception as e:
|
|
471
|
+
logger.warning(f"获取消息详情失败: {e},将使用事件自带消息内容")
|
|
472
|
+
message = event.get_message()
|
|
473
|
+
|
|
474
|
+
# 使用辅助函数解析消息内容
|
|
475
|
+
elements = parse_message_elements(message)
|
|
383
476
|
|
|
384
477
|
msg_data = {
|
|
385
478
|
"id": event.message_id,
|
|
@@ -835,6 +928,49 @@ if app:
|
|
|
835
928
|
|
|
836
929
|
@app.get("/web_console/api/history/{chat_id}", dependencies=[Depends(check_auth)])
|
|
837
930
|
async def get_history(chat_id: str):
|
|
931
|
+
# 优先返回缓存
|
|
932
|
+
if chat_id in message_cache and len(message_cache[chat_id]) > 0:
|
|
933
|
+
return message_cache[chat_id]
|
|
934
|
+
|
|
935
|
+
# 尝试从 Bot 获取历史消息 (OneBot v11 get_group_msg_history)
|
|
936
|
+
try:
|
|
937
|
+
from nonebot import get_bots
|
|
938
|
+
bots = get_bots()
|
|
939
|
+
if bots:
|
|
940
|
+
bot = list(bots.values())[0]
|
|
941
|
+
if chat_id.startswith("group_"):
|
|
942
|
+
group_id = int(chat_id.replace("group_", ""))
|
|
943
|
+
# 尝试调用 NapCat/Go-CQHTTP 的 get_group_msg_history
|
|
944
|
+
res = await bot.call_api("get_group_msg_history", group_id=group_id)
|
|
945
|
+
messages = res.get("messages", [])
|
|
946
|
+
|
|
947
|
+
parsed_msgs = []
|
|
948
|
+
for raw in messages:
|
|
949
|
+
# raw: {message_id, time, sender: {...}, message: [...], raw_message: ...}
|
|
950
|
+
sender = raw.get("sender", {})
|
|
951
|
+
sender_id = sender.get("user_id") or 0
|
|
952
|
+
is_self = str(sender_id) == str(bot.self_id)
|
|
953
|
+
|
|
954
|
+
parsed_msgs.append({
|
|
955
|
+
"id": raw.get("message_id"),
|
|
956
|
+
"chat_id": chat_id,
|
|
957
|
+
"time": raw.get("time"),
|
|
958
|
+
"type": "group",
|
|
959
|
+
"sender_id": sender_id,
|
|
960
|
+
"sender_name": sender.get("nickname") or sender.get("card") or str(sender_id),
|
|
961
|
+
"sender_avatar": f"https://q1.qlogo.cn/g?b=qq&nk={sender_id}&s=640",
|
|
962
|
+
"elements": parse_message_elements(raw.get("message", [])),
|
|
963
|
+
"content": raw.get("raw_message", ""),
|
|
964
|
+
"self_id": bot.self_id,
|
|
965
|
+
"is_self": is_self
|
|
966
|
+
})
|
|
967
|
+
|
|
968
|
+
if parsed_msgs:
|
|
969
|
+
message_cache[chat_id] = parsed_msgs[-CACHE_SIZE:]
|
|
970
|
+
return message_cache[chat_id]
|
|
971
|
+
except Exception as e:
|
|
972
|
+
logger.warning(f"获取历史消息失败: {e}")
|
|
973
|
+
|
|
838
974
|
return message_cache.get(chat_id, [])
|
|
839
975
|
|
|
840
976
|
@app.get("/web_console/proxy/image", dependencies=[Depends(check_auth)])
|
|
@@ -903,29 +1039,6 @@ if app:
|
|
|
903
1039
|
user_id = int(chat_id.replace("private_", ""))
|
|
904
1040
|
await bot.send_private_msg(user_id=user_id, message=content)
|
|
905
1041
|
|
|
906
|
-
# 发送成功后手动添加一条自己的消息到缓存并推送
|
|
907
|
-
my_msg = {
|
|
908
|
-
"id": 0,
|
|
909
|
-
"time": int(time.time()),
|
|
910
|
-
"type": "group" if chat_id.startswith("group_") else "private",
|
|
911
|
-
"sender_id": bot.self_id,
|
|
912
|
-
"sender_name": "我",
|
|
913
|
-
"sender_avatar": f"https://q1.qlogo.cn/g?b=qq&nk={bot.self_id}&s=640",
|
|
914
|
-
"elements": [{"type": "text", "data": content}],
|
|
915
|
-
"content": content,
|
|
916
|
-
"is_self": True
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
if chat_id not in message_cache:
|
|
920
|
-
message_cache[chat_id] = []
|
|
921
|
-
message_cache[chat_id].append(my_msg)
|
|
922
|
-
|
|
923
|
-
await broadcast_message({
|
|
924
|
-
"type": "new_message",
|
|
925
|
-
"chat_id": chat_id,
|
|
926
|
-
"data": my_msg
|
|
927
|
-
})
|
|
928
|
-
|
|
929
1042
|
return {"status": "ok"}
|
|
930
1043
|
except Exception as e:
|
|
931
1044
|
return {"error": str(e)}
|
|
@@ -1344,7 +1344,9 @@
|
|
|
1344
1344
|
// 初始化 WebSocket
|
|
1345
1345
|
function initWS() {
|
|
1346
1346
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1347
|
-
|
|
1347
|
+
// 添加 token 参数防止连接被服务端拒绝 (code 1008)
|
|
1348
|
+
const wsUrl = `${protocol}//${window.location.host}/web_console/ws?token=${authToken}`;
|
|
1349
|
+
ws = new WebSocket(wsUrl);
|
|
1348
1350
|
|
|
1349
1351
|
ws.onopen = () => {
|
|
1350
1352
|
statusEl.textContent = '● 已连接';
|
{nonebot_plugin_shiro_web_console-0.1.7 → nonebot_plugin_shiro_web_console-0.1.8}/.gitignore
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|