支持群聊/私聊消息归档、检索和 AI 图片理解总结的 NoneBot2 插件。
功能特色:
- 基础功能:群聊/私聊消息存储,私聊使用
group_id=-1存入同一张消息表,并支持/查消息检索。 - 增值服务:撤回、戳一戳、加群、退群/被踢等信息存储。
- 核心技术:AI 带上下文识图理解存储,识别成功后把原图片 CQ 码回写为
[image:{summary:"",tip:""}]。 - 上下文感知:识图时会带上图片前后方的聊天上下文,已识别图片会使用数据库中的总结版本。
- 批量缓存:待识别图片写入
pending_images.json,累计 5 张、等待 30 分钟或收到命令时提交。 - 本地缓存:使用
nonebot-plugin-localstore管理缓存目录,成功或失败后自动清理图片缓存。
在 nonebot2 项目的根目录下打开命令行,输入以下指令:
nb plugin install nonebot-plugin-smart-message-storage在 nonebot2 项目的插件目录下,打开命令行,根据你使用的包管理器输入相应命令。
pdm add nonebot-plugin-smart-message-storagepoetry add nonebot-plugin-smart-message-storage然后打开 nonebot2 项目根目录下的 pyproject.toml 文件,在 [tool.nonebot] 部分追加写入:
plugins = ["nonebot_plugin_smart_message_storage"]如果直接使用本仓库源码,可以将 nonebot_plugin_smart_message_storage 文件夹放入项目插件目录,并在 pyproject.toml 中加载:
plugins = ["nonebot_plugin_smart_message_storage"]在 nonebot2 项目的 .env 或 .env.prod 文件中添加下表中的配置。
| 配置项 | 必填 | 默认值 | 说明 |
|---|---|---|---|
DB_URL |
否 | sqlite:///qq_messages.db |
SQLAlchemy 数据库连接地址。 |
IMAGE_BATCH_SIZE |
否 | 5 |
待识别图片累计达到该数量后自动提交 AI。 |
IMAGE_FLUSH_SECONDS |
否 | 1800 |
待识别图片最久等待时间,单位秒。 |
IMAGE_CONTEXT_BEFORE_CHARS |
否 | 100 |
每张图片前方上下文的目标字数。 |
IMAGE_CONTEXT_AFTER_CHARS |
否 | 100 |
每张图片后方上下文的目标字数。 |
| 配置项 | 必填 | 默认值 | 说明 |
|---|---|---|---|
AI_API_KEY |
否 | (空字符串) | AI 接口密钥。为空时不启用 AI 识图,只保存原始消息内容。 |
AI_BASE_URL |
否 | https://api.exesim.com/v1 |
OpenAI 兼容接口地址。 |
AI_MODEL |
否 | gemini-3.5-flash |
用于识图总结的模型名称。 |
配置示例:
AI_API_KEY="sk-xxxxxxxxxxxxxxxx"
AI_BASE_URL="https://api.exesim.com/v1"
AI_MODEL="gemini-3.5-flash"
IMAGE_BATCH_SIZE=5
IMAGE_FLUSH_SECONDS=1800
IMAGE_CONTEXT_BEFORE_CHARS=100
IMAGE_CONTEXT_AFTER_CHARS=100未配置
AI_API_KEY时,插件启动会输出 info提示跳过识图逻辑;图片消息仍会按原始 CQ 码存入数据库。
| 指令 | 权限 | 说明 |
|---|---|---|
/查消息 <关键词> |
所有用户 | 在当前群聊或当前私聊中搜索消息。 |
/查消息 <群号> <关键词> |
所有用户 | 搜索指定群号中的消息。 |
/识别 |
所有用户 | 回复一条图片消息使用;已有总结则返回数据库中的总结,未识别则立即提交识别后返回。 |
/立即识别 |
所有用户 | 提交当前群聊或当前私聊中的待识别图片。 |
/立即识别 全部 |
超级用户 | 提交所有会话中的待识别图片。 |
- 群聊消息按真实
group_id存储。 - 私聊消息也写入
group_messages,其中group_id=-1,使用user_id区分私聊会话。 - 图片未识别或识别失败时,数据库中保留原始 CQ 码。
- 图片识别成功后,会回写当前消息的
raw_message,将图片段替换为:
[image:{summary:"图片总结",tip:"不确定性提示"}]
- 收到图片消息后,先下载并压缩到本地缓存,再写入
pending_images.json。 - 待识别图片累计达到
IMAGE_BATCH_SIZE,且这些图片都已积攒到IMAGE_CONTEXT_AFTER_CHARS的后文时自动提交。 - 如果达到批量数量时又出现了新图片,新图片会继续等待自己的后文,不会被提前并入上一批。
- 最早一张待识别图片等待超过
IMAGE_FLUSH_SECONDS时自动提交。 - 用户发送
/识别或/立即识别时会立即提交对应待识别图片,不等待后文继续积累。 - 超级用户发送
/立即识别 全部时提交全局待识别图片。
- 每张图片会分别向前、向后读取上下文,默认目标字数分别是
IMAGE_CONTEXT_BEFORE_CHARS=100和IMAGE_CONTEXT_AFTER_CHARS=100。 - 图片后的发言会保留给这张图片作为后文上下文,直到批量提交、超时提交或命令立即提交。
- 从当前图片消息前后持续取整条消息,没有固定条数限制。
- 上下文窗口会按真实聊天顺序合并去重,多张图片上下文重叠时不会重复塞给 AI。
- 单条消息不会截断;如果加入某条消息会让单侧上下文超过 600 字,则不加入该条。
- 如果上下文中已有 AI 图片总结,会使用数据库内的总结版本。
插件使用 nonebot-plugin-localstore 管理本地数据目录:
pending_images.json:待识别图片任务账本。image_cache/:待识别图片缓存目录。
图片识别成功或失败后,任务会从 pending_images.json 中移除,对应缓存图片也会删除。
/识别 效果
(gif动图)
(奶龙恶搞)
(动漫角色)
(截图)
(动漫表情包)
(看不清的情况会写入tip)
(特定含义表情包)
(表情包)
nonebot_plugin_smart_message_storage/
├── __init__.py # 插件入口,声明元数据,初始化数据库,注册启动任务并加载 handlers
├── config.py # 插件配置模型,读取数据库地址、AI 接口、批量识别数量和超时时间
├── constants.py # 定义 localstore 数据目录、图片缓存目录和 pending_images.json 路径
├── db.py # 创建 SQLAlchemy engine/session,并提供 init_db() 初始化表结构
├── models.py # 定义 GroupMessage 数据模型,对应 group_messages 表
├── prompt.py # 构造 AI 识图提示词,包含聊天时间线、图片任务和返回格式要求
├── vision.py # 调用 OpenAI 兼容视觉接口,上传 base64 图片并解析 AI JSON 返回
├── assets/
│ └── simkai.ttf # 跨平台渲染中文搜索结果所使用的内置楷体字体
├── handlers/
│ ├── __init__.py # 汇总导入所有 handler,完成指令和事件监听注册
│ ├── notices.py # 监听戳一戳、加群、退群、撤回等 notice 事件并写入消息表
│ ├── recognize.py # 实现 /立即识别、/立即识别 全部 和回复图片消息使用的 /识别
│ ├── search.py # 实现 /查消息 指令,将搜索结果渲染为图片回复
│ └── store.py # 监听群聊/私聊消息,写入数据库,并为图片消息建立待识别任务
└── services/
├── __init__.py # services 子包标记文件
├── contacts.py # 获取用户展示名,优先群名片,再好友备注/昵称,最后陌生人昵称
├── context.py # 为图片识别选择前文消息,并生成消息快照
├── images.py # 下载 OneBot 图片,兼容 URL 与本地路径,并压缩为 JPEG 缓存
├── image_tasks.py # 将消息中的图片转换为 pending 任务,写入缓存图片和任务账本
├── message_utils.py # 消息类型判断、图片段提取、CQ 图片正则和图片总结段格式化工具
└── pending.py # 管理 pending_images.json,批量识别图片、构造时间线、回写数据库并清理缓存
- Python
3.10+ - NoneBot2 2.3.0+
- OneBot v11 适配器
- 依赖
nonebot-plugin-localstore
本项目遵循仓库中的 LICENSE 文件。
1.1.0:
现在已经全部跑通了。需要做一个小优化:图片发完后,以及等待攒满batchsize张时,这期间群友会发言。每张图后面允许100字内容,逻辑与每张图前面类似(图后发言仍然缓存在那张图上下文)。这样可以更好的表达上下文。逻辑改为满足batchsize张且后面字数达到指定值(前后预期字数都设为配置项,默认前100,后100),提交(后面字数达到指定值期间可能会有新图,此时该新图应该未满足后预期字数,则作为下一批pending任务)。若为/识别或/立即识别。马上提交,也就是不等后面的100字。但是对于比如之前的未识别图可能后面已经积攒了上下文,不要删除。依然构造完整去重上下文。
已完成这次“图后上下文 + batch 等待”的优化。
改动要点:
新增配置项:IMAGE_CONTEXT_BEFORE_CHARS,默认 100
IMAGE_CONTEXT_AFTER_CHARS,默认 100
每张待识别图片现在会取:图前上下文,默认目标 100 字
图后上下文,默认目标 100 字
batch 自动提交逻辑改为:pending 中达到 IMAGE_BATCH_SIZE
且被提交的那些图片都已经积攒到足够图后上下文
新来的图片如果还没攒够后文,会留在下一批
/识别、/立即识别、超时提交仍然立即提交,不等待后文继续积累。
timeline 构造仍然会合并去重所有前后上下文,按真实聊天顺序给 AI。
图片 CQ 码在普通文本上下文里会被剔除,避免未选中的新图以原始 CQ 码污染 prompt。
1.0.0:
加一个命令:/识别。命令只在带有回复消息时有效(checker)此时去读回复的消息(参考:if event.reply:
state["abstract"] = str(event.reply.message)
return)或者查数据库已存消息ID
若其中无图片,回复"回复的消息似乎不包含图片哦",如果有,如果已经提交过识别存入,
先贴表情( try:
await bot.call_api('set_msg_emoji_like', group_id=event.group_id, message_id=event.message_id, emoji_id='320',
set=True)
except Exception as e:
logger.opt(exception=e).warning("贴表情失败"))
返回数据库中那条消息(即识别后结果)
如果没有,
先贴表情(314)自动执行/立即识别,然后返回数据库中那条消息(即识别后结果)
已加 /识别 命令。
实现行为:
只在“回复消息”时触发,非回复不会响应。
优先从 event.reply 读取回复消息;没有的话读 reply 消息段并 bot.get_msg;再兜底用数据库里的 raw CQ 解析。
根据回复消息的 message_id 查数据库。
如果数据库中没有这条消息:返回 没有在数据库中找到回复的消息。
如果没有图片:返回 回复的消息似乎不包含图片哦
如果图片已经识别完成,也就是数据库里已经是 [image:{summary:"",tip:""}]:群聊里给命令消息贴 emoji_id=320
返回数据库中那条消息的 raw_message
如果还有未识别的 CQ 图片:群聊里给命令消息贴 emoji_id=314
立即为回复消息补建 pending 图片任务
执行当前会话范围的识别
返回数据库中更新后的那条消息