feat: 群分析本地消息库 / 处理中占位图 / Web 日志窗口重构
- 群分析: 新增 history_store 只读本地消息源(读 learning_chat 落库,含 uninfo 昵称补齐/去重/截断判定),适配器优先读本地库、失败回退 OneBot 分页;分页 锚点字段回退链修复 NapCat 传 message_seq 翻页断裂;新增「本地记录」开关 - core/message_utils: 新增 common_proc_reply 占位图通用回复(引用消息 + 处理中 动图,支持后台任务显式指定 target),群分析/战况/倒放改用 - web_hub + web: 日志页改固定窗口滚动 + 翻页锚定 + 自动换行,SSE 日志轮转发 reset 帧,入口 HTML no-cache,行数统计增量缓存,CPU 改非阻塞采样,登录信息缓存 - 插件内 CLAUDE.md / DESIGN.md 不入库(.gitignore),galgame_card 两份文档取消 跟踪(文件保留在磁盘) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+83
-12
@@ -1,17 +1,26 @@
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
|
||||
from nonebot import get_bot
|
||||
from nonebot import require
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent, MessageSegment
|
||||
from nonebot.log import logger
|
||||
from nonebot_plugin_alconna import UniMessage
|
||||
from nonebot_plugin_alconna.uniseg import Receipt, Target
|
||||
|
||||
require("nonebot_plugin_htmlrender")
|
||||
from nonebot_plugin_htmlrender import md_to_pic
|
||||
|
||||
# 「处理中」占位动图(任务驱动通用回复用)
|
||||
THINKING_GIF = Path(__file__).parents[1] / "resource" / "imgs" / "thinking.gif"
|
||||
|
||||
|
||||
async def send_markdown(
|
||||
bot: Bot,
|
||||
event: MessageEvent,
|
||||
markdown: str,
|
||||
fallback_text: str | None = None,
|
||||
bot: Bot,
|
||||
event: MessageEvent,
|
||||
markdown: str,
|
||||
fallback_text: str | None = None,
|
||||
) -> None:
|
||||
"""发送 Markdown
|
||||
|
||||
@@ -62,9 +71,9 @@ def get_reply_message(event: MessageEvent) -> Message:
|
||||
|
||||
|
||||
def build_forward_nodes(
|
||||
self_id: int,
|
||||
entries: list[tuple[str, str]],
|
||||
sender_name: str | None = None,
|
||||
self_id: int,
|
||||
entries: list[tuple[str, str]],
|
||||
sender_name: str | None = None,
|
||||
) -> list[dict]:
|
||||
"""构造合并转发节点列表
|
||||
|
||||
@@ -86,11 +95,11 @@ def build_forward_nodes(
|
||||
|
||||
|
||||
async def send_forward_msg(
|
||||
bot: Bot,
|
||||
event: MessageEvent,
|
||||
entries: list[tuple[str, str]],
|
||||
fallback: str,
|
||||
sender_name: str | None = None,
|
||||
bot: Bot,
|
||||
event: MessageEvent,
|
||||
entries: list[tuple[str, str]],
|
||||
fallback: str,
|
||||
sender_name: str | None = None,
|
||||
):
|
||||
"""群聊发送合并转发消息,私聊或发送失败时回退纯文本
|
||||
|
||||
@@ -108,3 +117,65 @@ async def send_forward_msg(
|
||||
except Exception as e:
|
||||
logger.warning(f"合并转发发送失败,回退文本: {e}")
|
||||
await bot.send(event, fallback)
|
||||
|
||||
|
||||
async def common_proc_reply(
|
||||
message_id: str | None = None,
|
||||
*,
|
||||
target: Event | Target | None = None,
|
||||
image: str | Path = THINKING_GIF,
|
||||
text: str | None = None,
|
||||
) -> Receipt | None:
|
||||
"""任务驱动通用回复:引用指定消息并回一张「处理中」占位图(UniMessage 版)
|
||||
|
||||
引用哪条消息 / 发到哪:
|
||||
- 事件处理中(有上下文):不传参数即引用当前触发消息,发回当前会话;
|
||||
- 传 message_id 则引用指定消息(仍需事件上下文或用 target 指定会话);
|
||||
- 后台任务(无事件上下文):必须显式传 target(Event 或 alconna Target),
|
||||
如 `Target("872490448")`(群)/`Target("2931589710", private=True)`(私聊);
|
||||
此时不传 message_id 即为不带引用的普通发送。
|
||||
|
||||
Args:
|
||||
message_id: 要引用的消息 id,默认当前事件消息
|
||||
target: 发送目标(Event/Target);仅后台任务需要
|
||||
image: 占位图路径(或 http(s) 链接),默认 hexi/resource/imgs/thinking.gif
|
||||
text: 附加文本,为空只发图
|
||||
|
||||
Returns:
|
||||
alconna Receipt: 可 `.recall()` 撤回占位图、`.msg_ids` 取消息 id
|
||||
(是否支持编辑见 `.editable`);发送失败返回 None
|
||||
"""
|
||||
|
||||
def build() -> UniMessage:
|
||||
# 每次重建:alconna 的 reply_to 会把引用段插入消息自身,降级重发需干净副本
|
||||
# 本地文件转 base64 内联发送:后端(NapCat)读的是它自己 CWD 下的路径,
|
||||
# file:/// 形式不可靠;base64:// 由 bot 直接携带字节,OneBot 实现通用支持
|
||||
if isinstance(image, str) and image.startswith(("http://", "https://")):
|
||||
img = UniMessage.image(url=image)
|
||||
else:
|
||||
img = UniMessage.image(
|
||||
raw=Path(image).read_bytes(),
|
||||
mimetype=mimetypes.guess_type(str(image))[0],
|
||||
)
|
||||
return UniMessage.text(f"{text} ") + img if text else img
|
||||
|
||||
if message_id:
|
||||
reply_to: str | bool = message_id
|
||||
elif target is None:
|
||||
reply_to = True # 从当前事件上下文取被引用消息
|
||||
else:
|
||||
reply_to = False # 后台任务且未指定消息 id → 普通发送
|
||||
|
||||
try:
|
||||
return await build().send(target=target, reply_to=reply_to)
|
||||
except Exception as e:
|
||||
if not reply_to:
|
||||
logger.error(f"占位图发送失败: {e}")
|
||||
return None
|
||||
logger.warning(f"占位回复引用发送失败,降级为不带引用: {e}")
|
||||
|
||||
try:
|
||||
return await build().send(target=target)
|
||||
except Exception as e:
|
||||
logger.error(f"占位图发送失败: {e}")
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user