Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## v3.18.1 JM 本子文件改到转发外发送

本版本修复禁漫自动提取的加密 PDF 在合并转发节点里下载失败的问题:转发退回「本子信息 / 解密密码」两个节点,PDF 改为转发之后单独发一条文件消息。

- 加密 PDF 不再放进合并转发:转发节点里的文件在 QQ 客户端下载会失败,改为紧随转发之后单独发送(群聊走群文件上传、私聊走私聊文件),独立文件消息可以正常下载;群聊下同一个 PDF 仍会出现在群文件列表里。`build_forward_nodes()` 因此不再接收 PDF 路径,没有 PDF 时依然是「本子信息 / 状态」两个节点。
- 转发与文件分离后的投递语义:转发被协议端**明确拒绝**时退化为「信息 + 密码两条普通消息」,文件照旧单独发送;转发遇到「投递结果未确认」(`delivery_uncertain`)或「文件传输错误」(`file_transfer_error`)时不重发转发与信息/密码(转发可能已经送达),但文件此前从未发出、不算重复投递,仍会继续单独发送,否则整本下载白跑。文件自身投递结果未确认时直接上抛不重发,普通失败时补一条「PDF 上传失败」提示(这条提示写入历史,转发投递未确认时提示里也不会声称信息与密码已经送达)。
- 发送状态与历史摘要同步:状态前缀改为「已发送合并转发 / 合并转发被拒,已改为普通消息发送信息与密码 / 合并转发投递结果未确认」+「PDF 文件已单独发送」或「PDF 文件发送失败」;写历史时摘要只说「PDF: 加密文件随后单独发送(…,密码见转发节点)」——这条摘要随转发落地、此时文件尚未上传,不能提前声称已发送,文件真正发出后由 `send_group_file` 自己补一条 `[文件] …` 历史记录。密码仍然只出现在转发节点里、不进历史。

---

## v3.18.0 JM 漫画自动提取

本版本新增禁漫自动提取管线:消息里出现 `JM` 车号或禁漫链接时,自动获取本子详情、把全部章节合成为一个带密码的 PDF,以「本子信息 / 解密密码 / PDF 文件」三节点合并转发发送(文件只放在转发里,群聊下同时进入群文件列表),并新增与 `arxiv_paper` 同构的 `jm_book` 工具供 AI 按需取详情或附件。功能默认关闭,密码随机生成且不写进历史。
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
- **可选音乐工具集**:配置独立部署的 [lxmusic2api](https://github.com/69gg/lxmusic2api) 后,主 AI 可搜索歌曲/歌单、浏览热搜与排行榜、读取歌词/封面/评论、跨平台匹配并交付普通音频附件;完整 Track 通过任务内短引用流转,只暴露面向用户的高层 `music.*` 能力,不暴露下载作业生命周期接口。详见 [使用指南](docs/usage.md#音乐-music) 与 [配置说明](docs/configuration.md#4201-lxmusic2api-音乐服务)。
- **arXiv 论文提取、搜索与分析**:自动检测消息中的 arXiv 链接/标识并发送论文信息与 PDF;`arxiv_paper` 也可只返回 PDF 附件 UID,供 `file_analysis_agent` 做文本提取或指定页视觉分析;`arxiv_search` 负责论文检索。
- **GitHub 仓库卡片**:自动检测 GitHub 仓库链接或 `owner/repo` 仓库 ID,获取 public 仓库信息并发送简洁图片卡片,展示头像、简介、stars、forks、issues、contributors 等概览;API 与头像下载统一遵循 `[github].use_proxy`,头像以内联资源进入离线渲染器。
- **禁漫(JM)本子提取**:自动检测 `JM`+车号或禁漫链接,下载全部章节并合成为一个 AES-256 加密 PDF,以「本子信息 / 解密密码 / PDF 文件」三节点合并转发发送(文件只在转发里,群聊下同时进入群文件列表),密码每次随机生成且不写入历史;`jm_book` 工具也可只返回本子信息或未加密 PDF 附件 UID,供 `file_analysis_agent` 解析。默认关闭,`[jm].auto_extract_enabled` 打开。详见 [配置说明](docs/configuration.md#4203-jm-禁漫自动提取)。
- **禁漫(JM)本子提取**:自动检测 `JM`+车号或禁漫链接,下载全部章节并合成为一个 AES-256 加密 PDF,以「本子信息 / 解密密码」两节点合并转发发送,加密 PDF 不进转发、随后单独作为一条文件消息发送(转发里的文件在 QQ 客户端下载会失败,独立文件消息才下得动,群聊下同时进入群文件列表),密码每次随机生成且不写入历史;`jm_book` 工具也可只返回本子信息或未加密 PDF 附件 UID,供 `file_analysis_agent` 解析。默认关闭,`[jm].auto_extract_enabled` 打开。详见 [配置说明](docs/configuration.md#4203-jm-禁漫自动提取)。
- **自动处理管线**:Bilibili、arXiv、GitHub、JM 等自动提取统一运行在 `skills/pipelines` 中,斜杠命令优先级更高;命令输入/输出会写入历史,非命令消息会并行检测和处理命中管线,结果通过统一发送层写入历史并登记附件 UID 后再进入 AI 回复。远程大附件超过 `[attachments].remote_download_max_size_mb` 时只登记 URL 引用,避免无界下载和缓存膨胀。
- **同 sender 短时消息合并**:默认开启。连续发的多条消息会合并到同一轮 AI 调用,AI 一次看到全部意图自行识别"独立请求/修正/打断";告别"画猫→改成狗"的重复触发与回复打架。主提示词按 batcher 的"当前输入批次"语义适配,关闭该功能可能导致连续补充/修正消息与提示词不匹配,需要单独适配。可选投机预发送让用户停顿时 LLM 提前开跑、新消息可在未发出回复前取消,进一步压低响应延迟。详见 [docs/message-batching.md](docs/message-batching.md)。
- **思维链支持**:支持开启思维链,提升复杂逻辑推理能力。
Expand Down
4 changes: 2 additions & 2 deletions apps/undefined-chat/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-chat/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "undefined-chat",
"private": true,
"version": "3.18.0",
"version": "3.18.1",
"type": "module",
"scripts": {
"tauri": "tauri",
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "undefined_chat"
version = "3.18.0"
version = "3.18.1"
description = "Undefined native chat client"
authors = ["Undefined contributors"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Undefined Chat",
"version": "3.18.0",
"version": "3.18.1",
"identifier": "com.undefined.chat",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
4 changes: 2 additions & 2 deletions apps/undefined-console/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-console/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "undefined-console",
"private": true,
"version": "3.18.0",
"version": "3.18.1",
"type": "module",
"scripts": {
"tauri": "tauri",
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "undefined_console"
version = "3.18.0"
version = "3.18.1"
description = "Undefined cross-platform management console"
authors = ["Undefined contributors"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Undefined Console",
"version": "3.18.0",
"version": "3.18.1",
"identifier": "com.undefined.console",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
10 changes: 5 additions & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1139,15 +1139,15 @@ api_key = "replace-with-your-key"
- 裸数字不触发,避免群号、时间戳等误报;QQ 分享卡片(`json` 消息段)里的链接同样会解析。

自动提取行为:
- 命中后发送一次外层合并转发,固定三个节点:**本子信息**(车号、标题、作者、章节数、标签、观看/点赞、页数、大小、简介预览,末尾是可直接复制的 `JM<车号>`)、**PDF 解密密码**(每次随机生成 8 位,字母表去掉 `0/O/1/l/I` 等形近字符)、**PDF 文件**(本地合成后随转发上传,群聊下会成为真正的群文件)。文件只走转发,不额外发独立文件消息。
- 命中后发送一次外层合并转发,固定两个节点:**本子信息**(车号、标题、作者、章节数、标签、观看/点赞、页数、大小、简介预览,末尾是可直接复制的 `JM<车号>`)、**PDF 解密密码**(每次随机生成 8 位,字母表去掉 `0/O/1/l/I` 等形近字符)。加密 PDF **不进转发**,而是紧随转发之后单独作为一条文件消息发送。
- 多章节本子会把全部章节按顺序下载后合成为一个 PDF(`max_chapters` 可限制章数),PDF 用 AES-256 加密,打开时需要密码。
- 每页按 `pdf_dpi` 统一换算物理尺寸(`页宽 = 像素宽 ÷ DPI × 72`),下载与合成都不缩放像素:PDF 里的页就是站点源图的原始分辨率,画质上限由站点自己提供的图决定。
- 每页只做一次有损编码:取服务端原始字节(无损落盘)→ 解扰 → JPEG(`image_quality`,4:4:4)→ 直接作为 PDF 的图像数据,中间不再多一代重编码。需要更高画质时可把 `image_quality` 提到 `98`/`100`(约 `+30%`/`+50%` 体积),源图本身的分辨率无法通过配置提升。
- 单次合并转发里的 PDF 在内存中合成后写盘,所以 `max_file_size` 只是**近似**内存上限:先按各章节原始图片累计预判(超限即停止下载),组装时再按**编码后字节**累计复核,超过上限就提前中止、不发文件;PDF 是重编码 JPEG(4:4:4 / q95 可能比源 WebP 大),峰值可以略高于该值。
- 单次合并转发的 PDF 在内存中合成后写盘,所以 `max_file_size` 只是**近似**内存上限:先按各章节原始图片累计预判(超限即停止下载),组装时再按**编码后字节**累计复核,超过上限就提前中止、不发文件;PDF 是重编码 JPEG(4:4:4 / q95 可能比源 WebP 大),峰值可以略高于该值。
- 密码只出现在转发节点里;写入历史的摘要只说明「密码见转发节点」,因此后续 AI 轮次能看到「发过什么」,但不会把密码复述出来。
- 转发节点里的 PDF 随转发一起上传:群聊下 NapCat 会把它作为群文件上传(`busid=102`),所以同一个 PDF 也会出现在群文件列表里,节点里的文件可以直接下载(已用 14MB 的 PDF 实测)。文件只发一次,不额外发独立文件消息。
- 转发本身被拒时退化为两条普通消息(信息、密码)+ 一条独立文件消息(此时文件没有别的入口,必须发出来);密码消息写进历史时只记「PDF 解密密码已单独发送」,不重复写摘要,上传彻底失败时再补一条不写历史的提示。
- 「投递结果未确认」(`delivery_uncertain`)与「文件传输错误」(`file_transfer_error`)**不降级重发**,直接上抛给调用方:这两种情况下转发可能已经送达,换个 action 再发会造成真实重复投递(判据与 B 站发送器一致)。
- PDF 不放进转发:转发节点里的文件在 QQ 客户端下载会失败(2026-10-01 实测),所以加密 PDF 在转发之后单独作为一条文件消息发送,独立文件消息可以正常下载;群聊下同一个 PDF 因此也会出现在群文件列表里。
- 转发本身被明确拒绝时退化为两条普通消息(信息、密码),文件照旧单独发送;密码消息写进历史时只记「PDF 解密密码已单独发送」,不重复写摘要。独立文件也上传失败时再补一条会写入历史的「PDF 上传失败」提示(转发投递结果未确认时,提示不声称信息与密码已经送达)。历史摘要只说文件「随后单独发送」,上传成功由 `send_group_file` 自己补一条 `[文件] …` 记录,因此历史能区分文件到底发出去没有。
- 「投递结果未确认」(`delivery_uncertain`)与「文件传输错误」(`file_transfer_error`)**不降级重发**转发与信息/密码:这两种情况下转发可能已经送达,换个 action 再发会造成真实重复投递(判据与 B 站发送器一致)。文件是另一条此前从未发出的消息,这种情况下仍会继续单独发送;文件自身投递结果未确认时直接上抛,不重发。
- 降级路径会把密码作为普通消息发出(转发发不出去时这是唯一能拿到密码的方式),因此这条消息的正文也会进入本地 INFO 日志的「内容摘要」,转发节点路径则不会。密码始终不进历史摘要与转发卡片之外的用户可见正文。
- 下载量超过 `max_file_size` 或没有下到任何页面时,只发送信息与状态说明两个节点(此时信息里不展示 PDF 大小,因为还没有 PDF),不发密码与文件。
- 车号不存在、接口不可用时发送一行 `JM 提取失败:<原因>`,日志记录异常类型与堆栈;车号与链接混排时按原文顺序取第一个,分享卡片结构异常只跳过该段。
Expand Down
8 changes: 4 additions & 4 deletions docs/pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ Bilibili 图文管线命中 `bilibili.com/opus/<id>`、`t.bilibili.com/<id>`、`
JM 管线命中 `JM` 前缀加 5–8 位车号(`JM1114751`、`jm 1114751`、`jm:1114751`)或主机名含 `18comic` / `jmcomic` 的链接(`/album/<id>`、`/photo/<id>`、`?id=<id>`)后,发送一次外层合并转发,节点顺序固定:

1. `本子信息`:车号与标题、作者、章节数、标签、观看/点赞、页数、PDF 大小、简介预览,末行是可直接复制的 `JM<车号>`(不带站点链接);
2. `解密密码`:每次随机生成的 8 位 PDF 打开密码;
3. `PDF`:本地合成好的加密 PDF(群聊下会作为真正的群文件上传)。
2. `解密密码`:每次随机生成的 8 位 PDF 打开密码。

PDF 随转发一起上传,节点里的文件可以直接下载(已用 14MB 的 PDF 实测);群聊下 NapCat 会把它作为群文件上传(`isGroupFile`、`busid=102`,元素里带 `fileId` / `fileMd5` / `fileSha1`),所以同一个 PDF 也会出现在群文件列表里。文件只发一次,不额外发独立文件消息;只有转发本身发送失败时才退化为「信息 + 密码 + 独立文件消息」。
加密 PDF **不进转发**,而是紧随转发之后单独发一条文件消息:转发节点里的文件在 QQ 客户端下载会失败(2026-10-01 实测,同一份文件独立发送可正常下载),独立文件消息才是可下载的入口,群聊下同一个 PDF 也会出现在群文件列表里。

多章节本子按章节顺序全部下载(`[jm].max_chapters` 可限制),再合成为一个 AES-256 加密 PDF。裸数字不触发,避免群号、时间戳等误报;`xxjm1234567` 这类前缀也不触发。

失败语义:

- 转发被拒时退化为两条普通消息(信息、密码)+ 独立文件消息(这种兜底情况下文件没有别的入口)。密码始终不会写进历史摘要。
- 转发被明确拒绝时退化为两条普通消息(信息、密码),文件照旧单独发送;密码始终不会写进历史摘要。
- 投递结果未确认(`delivery_uncertain` / `file_transfer_error`)时不重发转发与信息/密码(可能已经送达),但文件此前从未发出,仍会继续单独发送,否则整本下载白跑;文件自身投递结果未确认时直接上抛,不重发。
- 下载量或 PDF 体积超过 `[jm].max_file_size`(原始图片累计预判 + 组装时按编码后字节复核)、以及没有下到任何页面时,只发信息与状态两个节点,不发密码与文件。

PDF 合成没有走 `jmcpy.imaging.write_pdf`,而是自己用 PyMuPDF 逐页写入:jmcpy 的下载 API 一章只能产出一个 PDF,而这里要把多个章节合进同一个文件;同时 PATH 输出会先把解扰后的图重新编码一次、合成时再编码第二次,这里改为 `decode=False` 取服务端原始字节(无损落盘)、自己解扰、每页只编码一次 JPEG(`[jm].image_quality`,色度 4:4:4)后直接作为 PDF 图像数据,并按 `[jm].pdf_dpi` 统一页尺寸、做 AES-256 加密。页像素始终不做缩放,画质上限由站点源图自身分辨率决定。(jmcpy ≤0.1.1 的 `write_pdf` 另有「追加页退回 72 DPI、同文档页尺寸不一致」的问题,已在 0.1.2 修复;页内色度采样从 0.1.3 起默认也是 4:4:4。本管线依赖 `jmcpy>=0.1.3`。)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "Undefined-bot"
version = "3.18.0"
version = "3.18.1"
description = "QQ bot platform with cognitive memory architecture and multi-agent Skills, via OneBot V11."
readme = "README.md"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion src/Undefined/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from .skills.registry import BaseRegistry as BaseRegistry
from .skills.tools import ToolRegistry as ToolRegistry

__version__: str = "3.18.0"
__version__: str = "3.18.1"

# symbol -> (module_path, attribute_name);首次访问时才 importlib 加载
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
Expand Down
Loading
Loading