最后验证:2026-10-13T13:06:08Z
QQ 归档
  1. QQ 归档 00:项目总览
  2. QQ 归档 01:快速开始
  3. QQ 归档 02:完整使用手册
  4. QQ 归档 03:部署与运维手册
  5. QQ 归档 04:架构设计
  6. QQ 归档 05:数据模型与格式映射
  7. QQ 归档 06:验收测试报告
  8. QQ 归档 07:独立复核报告
  9. QQ 归档 08:项目报告
  10. QQ 归档 09:最终交付报告
  11. QQ 归档 10:同类导出项目调研
  12. QQ 归档 11:qq-chat-exporter 复用性分析
  13. QQ 归档 12:SnowLuma 调研与改进落地
  14. QQ 归档 13:Windows 部署指南
  15. QQ 归档 14:脱敏样例数据集

QQ 聊天记录归档系统 · 完整使用手册

这份文档是给使用者看的:它把这套工具”能做什么、怎么用、遇到问题怎么办”全写在了一起。
面向实施细节的文档另有 ARCHITECTURE.md(架构)、DEPLOYMENT.md(部署运维)、
format-mapping.md(导入格式映射与有损清单)、
INDEPENDENT-VERIFICATION.md(独立复核:哪些验证过、哪些没有)。


目录

  1. 这是什么,能做什么
  2. 它做不到什么(先看这一节,避免误解)
  3. 需要什么前提
  4. 五分钟上手
  5. 完整使用手册
  6. 部署:本机、Linux 服务器、Windows
  7. 命令速查表
  8. 常见问题(FAQ)
  9. 安全与隐私(必读)
  10. 怎么把它交给别人用
  11. 已知限制清单
  12. 数据在哪里、怎么迁移、怎么彻底删掉

1. 这是什么,能做什么

一句话:一个独立于 QQ 客户端的个人聊天记录归档系统——把能拿到的聊天记录和媒体文件
存到自己的硬盘上、做成可以搜索的网页,从此不再依赖 QQ 客户端是否还留着这些记录。

能力 具体是什么
导入已有记录 读 QQ Chat Exporter 的 json / 分块 jsonl / 旧版 txt,以及通用 jsonl 和 HTML 导出页面。格式自动识别,重复导入幂等(不会产生重复消息)
持续采集新消息 通过 OneBot v11 的只读接口(NapCat 等实现)增量拉取,每 60 秒一轮、断电/重启后自动续传、失败重试退避
图片/视频/语音/文件归档 按内容寻址(文件名就是 SHA-256)落盘,自动校验完整性;拿不到的资源明确标记缺失,绝不伪造文件
合并转发展开 把”合并转发”里的子消息递归展开,附件一起归档
中文全文检索 支持中文子串搜索(二字组 + FTS5),命中高亮
网页管理界面 会话列表、时间线、引用预览、媒体预览/播放/下载、归档统计、任务与日志、手动导入/备份
引用关系 显示”这条在回复哪一句”,并展开被引用的内容
采集过滤 按关键词/发送者/类型把你不想看的内容标记出来(默认只标记不丢弃)
导出 按会话导出 json / jsonl / txt
备份与恢复 一致性快照 + SHA-256 清单 + 完整性校验,并有可实际执行的恢复流程(会先把当前库另存)
语音转文字 可选:用本机的 whisper.cpp 把语音转成中文文本(默认关闭)
扫码登录 网页里显示二维码、或终端里直接画二维码;也支持用本机已保存的登录态快捷登录

真实数据量级参考(本机实测):13 个会话、近 300 条消息、28 个媒体文件、53 MB 媒体占用,
单进程内存几十 MB,查询与页面都是毫秒级。


2. 它做不到什么(先看这一节,避免误解)

这一节比上一节更重要。 下面这些不是”还没做”,是任何第三方工具都做不到或本工具刻意不做:

做不到的事 为什么
阻止 QQ 删除消息 本工具的前提认知:它只保证「已经到达本工具的数据永久保留」,不试图阻止 QQ 自己清理
取回手机端尚未同步到服务端的记录 这些记录不存在于任何可访问的位置。需要设备端解密(root/Frida 注入),风险高、极易失效,本工具有意不提供
补齐机器人离线期间的消息 上游 NapCat 明确表态这是设计行为(Issue #1838):离线窗口内的消息不会补推
取回已撤回的消息 只能记录「此处有一条被撤回的消息」;撤回前没采到就永远没有了
读取已解散/已退出的群 OneBot 接口明确不支持
保证腾讯服务端记录的完整性 官方导出工具自己都标注 serverCompletenessProven: false。任何导出接口返回的都只是”服务端愿意给的部分”
账号密码登录 QQ 腾讯早已不支持第三方客户端密码直登(设备锁/验证码/风控);NapCat 本身也只提供扫码与快捷登录
发消息、删消息、管群 采集只用只读接口(代码里硬编码 11 个白名单),发送/删除类接口在本地直接拒绝、连请求都不发。这是刻意的安全边界

因此本工具的正确用法:把采集端长期挂着(systemd 自动重启),把”离线窗口”压到最短;
电脑上能导出多少就完整收下多少。它保护的是”已经到手的东西”,而不是”让 QQ 别删”。


3. 需要什么前提

项 要求 说明
归档服务器 Node.js ≥ 22.5(推荐 24.x) 不需要 npm install,生产依赖为 0 个;解包即可跑
操作系统 Linux(Debian/Ubuntu/Arch 实测)、macOS、Windows 只要 Node 能跑就行,无原生扩展、无编译步骤
磁盘 媒体会占空间(本机 28 个媒体 ≈ 53 MB) 建议单独一块盘;alert.diskLowGb 会在空间不足时告警
采集端(可选) 一个 OneBot 实现,如 NapCat 只有”持续采新消息”才需要;NapCat 需要一台能跑 QQ 客户端的机器(NTR 类实现通常要 Windows/Wine 或容器)
可选组件 ffmpeg(生成缩略图)、whisper-cli + 模型(语音转文字) 没有也能跑:缩略图降级、转写功能显示为不可用

4. 五分钟上手

# 1. 解包(或 git clone)
tar -xzf qq-archive-0.1.0.tar.gz && cd qq-archive-0.1.0

# 2. 配置:只需要复制一份模板
cp config.example.json config.json
chmod 600 config.json            # 里面会写入登录令牌

# 3. 自检:第一次运行会自动生成令牌、建库、跑数据库迁移
node --experimental-strip-types apps/cli/src/main.ts doctor

# 4. (可选)导入一份 QQ 导出文件,格式自动识别
node --experimental-strip-types apps/cli/src/main.ts import ~/Downloads/群聊导出.json

# 5. 起服务并打开网页
node --experimental-strip-types apps/cli/src/main.ts token     # 打印登录令牌
node --experimental-strip-types apps/cli/src/main.ts serve     # 默认 http://127.0.0.1:8787

浏览器打开 http://127.0.0.1:8787,粘贴令牌登录。

嫌命令长:npm link 之后可以直接用 qq-archive serve;
或写别名 alias qq-archive='node /绝对路径/qq-archive-0.1.0/bin/qq-archive.mjs'。

config.json 可以带注释和尾逗号(//、/* */ 都行),手改方便;
程序写回令牌时会保留你写的注释。


5. 完整使用手册

5.1 导入已有的聊天记录

支持以下格式,自动识别,也可以用 --format 强制指定:

格式 说明
qce-json QQ Chat Exporter 的单文件 JSON 导出
qce-jsonl QCE 的分块导出(manifest.json + chunks/*.jsonl)
qce-txt 旧版纯文本([时间] 昵称(QQ号): 内容)
generic-jsonl 通用 JSONL(每行一条消息)
HTML 导出工具生成的网页文件(保守探测,不像聊天页会明确判 unknown 而不是硬猜)
qq-archive import ~/Downloads/群聊导出.json          # 单文件
qq-archive import ~/Downloads/导出目录/              # 目录会递归扫描
qq-archive import big.jsonl --limit 1000            # 只导前 1000 条(调试用)
qq-archive import x.json --no-media                 # 跳过媒体下载(只要文字)
qq-archive import x.json --account u_self           # 指定归属账号

导入行为:

  • 幂等:同一个文件导入两次,第二次 新增 0|重复 N——不会产生重复消息,所以放心重跑。
  • 有进度与错误统计:解析 247|新增 244|重复 3|失败 0,失败逐条列出原因。
  • 不静默丢字段:无法映射到标准字段的原始内容会保留在 raw/extra 里;
    哪些字段做了有损映射,全部列在 format-mapping.md(21 条),不声称无损。
  • 媒体会一起归档(除非 --no-media);拿不到的文件标记为”缺失”并保留失败原因,不会伪造。

5.2 持续采集新消息(接 NapCat)

只有”持续把新消息存下来”才需要这一步。采集端不是本工具的一部分,需要你自己跑一个 OneBot 实现。

Step 1:把 NapCat 跑起来(一条命令)

qq-archive collector setup --write-config

这条命令会自动:检测 podman/docker → (首次)从 NapCat 官方镜像仓库拉取镜像(约 1.5 GB)
→ 按正确端口与挂载起容器(--restart unless-stopped,开机自启)→ 等 OneBot 端口就绪
→ 从 NapCat 的配置里读出 OneBot token 与 WebUI token 并写进 config.json(写前自动备份原文件)。

它做的事和手工跑一遍 podman run 完全一样,只是把那步最容易填错的”两个 token 从哪来”自动化了。
常用选项:--name(容器名,默认 napcat-qq)、--onebot-port(默认 3000)、
--webui-port(默认 6099)、--data-dir(默认 ~/napcat/data)。
qq-archive collector status 可以随时看容器与端口的现状。

为什么是”帮你装”而不是”内置进来”:NapCat 容器里跑的是官方 QQ 客户端本体(Electron)
加虚拟显示器与注入加载器,镜像 1.5 GB。把它打进本项目的发布包意味着再分发闭源的 QQ 客户端
(许可不允许)、再分发 NapCat(其自定义许可禁止发布修改版),并且让归档进程直接持有你的 QQ 登录会话。
所以本项目只在 loopback 上用公开的 OneBot HTTP 接口与它互操作 —— 但安装、配置、对 token 这些活
完全可以自动化,这就是上面那条命令。

等价的手工方式(想自己控制容器时用)
mkdir -p ~/napcat/data/config ~/napcat/data/qq ~/napcat/data/plugins

# 3000 = OneBot HTTP(本工具连这个);6099 = NapCat WebUI(扫码登录用)
# 端口只绑 127.0.0.1:OneBot 端口等于你 QQ 账号的读写权限,别暴露到公网
podman run -d --name napcat-qq --restart unless-stopped \
  -p 127.0.0.1:3000:3000 \
  -p 127.0.0.1:6099:6099 \
  -v ~/napcat/data/config:/app/napcat/config \
  -v ~/napcat/data/qq:/app/.config/QQ \
  -v ~/napcat/data/plugins:/app/napcat/plugins \
  docker.io/mlikiowa/napcat-docker:latest

Step 2:登录 QQ,两种方式任选:

qq-archive login          # 终端里直接画出二维码(也可用网页「QQ 登录」页)

或打开 NapCat 自己的 WebUI http://127.0.0.1:6099 扫码。登录过的账号以后可以快捷登录,不用再扫。

Step 3:打开 OneBot 的 HTTP 服务。配置文件在
~/napcat/data/config/onebot11_<你的QQ号>.json,关键字段:

"network": {
  "httpServers": [{
    "name": "qq-archive",
    "host": "0.0.0.0",
    "port": 3000,
    "enable": true,                 // 必须是 true
    "messagePostFormat": "array",   // 本工具按 array 解析
    "token": "……一串随机字符……"      // 下面 config.json 要填的就是它
  }]
}

改完重启容器:podman restart napcat-qq。

Step 4:把两边配置对上(config.json):

"collector": {
  "kind": "onebot",
  "baseUrl": "http://127.0.0.1:3000",   // 对应 httpServers.port
  "token": "……与上面那个串完全一致……",
  "intervalSec": 60,                     // 每 60 秒一轮
  "initialBackfillDays": 3,              // 首次回填多久的历史
  "syncMedia": true,                     // 是否下载媒体
  "forwardDepth": 3,                     // 合并转发展开层数
  "napcat": {                            // 只有「网页扫码登录」需要这一段
    "webuiUrl": "http://127.0.0.1:6099",
    "token": "……NapCat WebUI 的登录密码……"
  }
}

⚠️ 最常见的坑:两个 token 不是同一个东西
collector.token 来自 onebot11_<QQ号>.json 里的 httpServers[].token(用来读消息);
collector.napcat.token 是 NapCat WebUI 的登录密码(用来扫码登录)。
填错的表现是:采集报”采集端不可用”,或登录页提示令牌错误。

Step 5:验证

qq-archive collect --once     # 立刻跑一轮

正常输出类似:

已连接采集端:昵称 (QQ号)
一轮采集完成:会话 13|拉取 27|新增 0|重复 27|失败 0
媒体:保存 0|缺失 0

重复 27 = 拉回 27 条全都已经在库里了(去重生效),这是正常的,不是错误。

Step 6:让它一直跑

sudo cp deploy/qq-archive.service /etc/systemd/system/
sudo cp deploy/qq-archive-collect.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now qq-archive qq-archive-collect

也可以只用一条命令常驻:qq-archive collect(前台跑,按 Ctrl+C 停)。

5.2.1 采集控制面板(看状态、开始、停止)

「总览」和「归档管理」两页都有采集控制面板:

显示 含义
定时采集:运行中 · 每 60 秒一轮 服务端正在按 collector.intervalSec 定时轮询
定时采集:已停止 定时轮询已停(不影响已归档的数据)
采集源 当前用的是哪个 OneBot 地址(collector.kind=none 时显示”未启用”)
最近一轮 最近一次轮询的时间;旁边出现红色「有失败」表示有会话采集失败

三个按钮:

  • 开始采集:启动定时轮询(已在运行时按钮为禁用,重复点不会叠加循环)。
  • 停止采集:停掉定时轮询。点击后会显示「停止中…」——它会等当前这一轮采集跑完再返回
    (一轮可能要十几秒),这是刻意的:避免把半写入的一轮截断。
  • 立即采集一轮:只跑一轮,不影响定时器的开关状态。

停掉再开,中间的消息会丢吗?不会。 停止只停”定时轮询”:
已归档的数据一条不动;重新开始时按 collect_state 里的游标继续(并往前多看
collector.lookbackSec 做重叠保护),所以停机期间到达、且上游历史里还在的消息会被一并补上。
唯一的上游限制是 NapCat 的历史缓存容量——停机太久、老消息被上游缓存淘汰后,那段就补不回来了。

实测(本机真实归档):停止采集 → 等 70 秒(期间确认没有任何新轮次)→ 重新开始 →
一轮拉到 89 条,其中 5 条是停机期间的新消息,全部入库,另有 1 个新媒体被保存。
另有一条自动化用例专门守这个语义(tests/collect.test.ts 用例 20:停机期间来 3 条,重开后必须补齐且不重复)。

5.3 网页界面每一页能干什么

页面 能做什么
总览 采集控制面板(运行状态 + 开始/停止/立即一轮)、账号/会话/消息/媒体统计、占用空间、最近采集与备份时间、系统健康(数据库、媒体目录、磁盘剩余)、采集源状态
QQ 登录 显示登录二维码、轮询登录状态、列出本机可快捷登录的账号并一键上线
聊天记录 左侧会话列表(可按名称/QQ号筛选、按类型过滤、按最近消息/消息数/名称排序);右侧时间线(发送者、时间、类型徽标、引用预览、媒体缩略图、撤回标记);顶部全文搜索
媒体管理 缩略图网格;按类型/状态/会话/日期筛选;图片预览、视频播放、文件下载;完整性校验;语音转写;显示缺失/损坏的资源及原因
归档管理 采集控制面板(同总览)、手动导入、手动备份;任务列表与实时进度(SSE);备份列表(含校验值与路径)
日志 按级别与 scope 筛选运行日志(含采集失败原因)
导出 选择会话导出 json/jsonl/txt

界面细节:

  • 引用预览:被引用消息还在库里时,直接在气泡里显示引用内容;不在库里则显示”原文不在归档内”。
  • “包含已过滤消息”勾选框:默认隐藏被过滤标记的消息,勾上即可看到(带橙色徽标说明命中原因)。
  • 界面完全离线自包含(无 CDN、无外部字体),因此部署在内网/无外网的机器上也正常。

5.4 找消息:搜索与筛选

  • 全文搜索:支持中文子串(例如搜 示例 能命中”这是一条示例消息”),命中片段高亮。
    单字搜索(如搜 茶)也能用,走退化的字面匹配路径。
  • 会话内筛选:时间范围、消息类型、方向(收到/发出)、关键词。
  • 搜索范围:默认只在未过滤的消息里搜;勾上”包含已过滤消息”后连过滤掉的也一起搜。
  • 搜索结果点一下直接跳到该消息所在会话。

5.5 图片、视频、语音、文件

  • 归档方式是内容寻址:文件按 sha256 命名存放,所以同一张图被多条消息引用也只存一份,
    且任何时候都能校验完整性(qq-archive verify)。
  • 图片:列表里显示缩略图(需要 ffmpeg;没有也能看原图)。
  • 视频:支持拖动进度条播放(HTTP Range),可在网页里直接播放或下载。
  • 语音:可播放;开启了语音转文字后直接显示文本。
  • 文件:显示原始文件名与大小,可下载。
  • 缺失的资源:明确标记为”缺失”并写明原因(例如”直链 403(rkey 已过期)”),
    磁盘上不会生成任何假文件,也不会谎称恢复成功。
  • 媒体与消息的关联会保留原始文件名与声明大小,即使多个消息共用同一个物理文件。

5.6 导出

  • 网页:时间线右上角「导出」,或在「导出」页选择会话与格式。
  • 导出 json/jsonl 会带上全部字段(含元素、提及、引用关系、媒体关联、原始记录)。
  • 导出永远包含被过滤的消息——过滤只是让你少看点,导出是”把数据拿走”,少一条就是真丢数据。

5.7 撤回、以及”清除内容”

别人撤回消息了,我这边还能看到吗?
能 —— 只要撤回发生在采集到它之后。 归档保存的是本地副本,而且采集侧写入用的是
「有则忽略」:一条消息进了库以后,后续采集轮次既不会更新它、也不会删除它。
所以即使上游把这条消息从历史里抹掉、甚至返回一条”同 ID 但内容变成 [已撤回]”的记录,
你库里那一份也原样保留(连原始正文和已下载的媒体文件都在)。

有测试盯着这条性质:tests/purge.test.ts 里模拟了「撤回后上游不再返回这条」和
「返回同 ID 但内容变了」两种情况,断言库里那条必须一字不改。

两处例外要分清:

  • 撤回发生在采集之前(采集端当时不在线)→ 拿不到,上游不会补推;
  • 撤回的标记不会自动出现:采集接口只能看到”当前历史”,看不到撤回事件,
    所以网页上那条不会显示”已撤回”字样——但它仍然可见、可搜索、可导出。
    (导入的导出文件里如果带了撤回标记,会照原样保留并显示。)

怎么清除内容? 三种粒度,都默认”只演练、不动手”:

# 1) 删掉某一个会话(先演练,看清会删多少)
qq-archive purge --session 5
qq-archive purge --session 5 --yes                 # 确认后真删
qq-archive purge --session "某个群名" --yes         # 也可以按名称(重名会要求改用 id)
qq-archive purge --session 5 --yes --media          # 顺带删掉"不再被任何消息引用"的媒体文件

# 2) 清空全部归档数据(消息、会话、媒体索引、联系人、任务、日志)
qq-archive purge --all
qq-archive purge --all --yes --media

# 3) 彻底不留痕:直接删数据目录
#    停机后 rm -rf data config.json 即可(程序不在系统别处留东西)

要点:

  • 默认演练:不加 --yes 只列出”将删除什么、还剩什么”,一条都不动。
  • 删会话只删该会话的消息;媒体的库记录不会级联删(同一张图可能被别的消息引用),
    只重算引用计数。仍在被任何消息引用的媒体文件绝不会被删 —— 这条有测试盯着。
  • 删除会连全文索引一起清(FTS 虚拟表不会自动级联,残留会变成搜得到却打不开的垃圾)。
  • 删除不可撤销。删之前建议先 qq-archive backup(--all --yes 之前尤其如此)。
  • 备份文件不受 purge 影响(它们在 data/backups/)。

5.8 新消息会自动出现(自动刷新)

聊天记录页默认每 5 秒自动刷新:会话列表就地更新(条数、最后一条预览、时间),
已打开的会话只追加新消息(不会打断你往上翻历史,也不重置滚动位置)。
标签页切到后台时会自动暂停,不做无用请求。

想改频率或关掉:页面工具栏上的「自动轮询」下拉框(选项:关闭 / 2 秒 / 5 秒默认 / 30 秒),
设置对所有页面通用。归档管理页也用这个频率做整页轮询。

如果发现”有新消息但页面不动”,先确认这个下拉框不是”关闭”,再确认页面标题栏的版本号
对应的前端是新的(浏览器可能缓存了旧的 JS,按 Ctrl+Shift+R 强刷一次)。

5.9 过滤:不想看的怎么少看点

在 config.json 里加:

"filters": {
  "enabled": true,
  "excludeKeywords": ["广告", "加群"],     // 子串匹配,大小写不敏感
  "excludeSenders": ["123456789", "某昵称"], // 与 QQ号/uid/昵称 精确相等
  "excludeTypes": ["system"],              // 消息类型:text/image/video/audio/file/forward/reply/system/json/other
  "drop": false                            // false = 只标记不丢弃(推荐)
}
  • 默认 drop: false = 只标记不丢弃:命中的消息照样入库,导出也照带,只是网页默认不显示。
  • drop: true 才会让之后采集到的命中消息跳过存储(已入库的不会被删)。
  • 改了规则后,对已有消息重算标记:
qq-archive refilter

refilter 是幂等且可逆的(把 enabled 关掉再跑一次即清空标记),只改标记列,从不删除消息。

提醒:这是防丢失工具。除非你确定”这些消息连存档都不要”,否则保持 drop: false。

5.10 备份与恢复

qq-archive backup                      # 立即备份数据库(含完整性校验)
qq-archive backup --label 升级前        # 带备注
qq-archive backup --with-media         # 连 media/ 媒体目录一起打包(推荐在做迁移前用)
qq-archive backup --no-verify          # 大库赶时间时跳过校验

只备份数据库 vs 连媒体一起备份:默认只打包数据库(媒体往往几十 GB)。
如果这份备份将来要用来还原媒体,就必须加 --with-media——
否则恢复时媒体目录是空的(命令会明确警告你这一点)。

  • 备份是 VACUUM INTO 生成的一致性快照,同时写一份 .manifest.json(含 SHA-256、各表条数、校验结论)。
  • 自动备份:配置里 backup.intervalHours(默认 24 小时),backup.keep 控制保留份数(超出删最旧)。
  • 恢复(永远先演练):
qq-archive restore /path/to/backups                          # dry-run:只校验,不动数据
qq-archive restore /path/to/backups --apply                  # 真正恢复数据库
qq-archive restore /path/to/backups --apply --restore-media  # 连媒体一起恢复(需备份时带 --with-media)

参数可以是备份目录、快照 .db 文件或它的 .manifest.json,三种都行。
传快照文件时程序会自动找同名的 manifest(用来校验 SHA-256 并定位媒体目录)。

真正恢复时会先把当前数据库另存为 .bak-<时间戳>,所以操作本身可回退。
恢复流程经过实测:破坏数据(282→199 条)后恢复,条数、媒体、引用关系全部回来。

备份不依赖 QQ 的本地数据目录——它是本工具自己的数据库与媒体目录的快照。

5.11 语音转文字

默认关闭。需要系统里有 whisper-cli(whisper.cpp)与一个多语言模型
(*.en.bin 是英文专用,中文会转出空文本):

"stt": {
  "enabled": true,
  "model": "/usr/share/whisper.cpp-model-large-v3-turbo/ggml-large-v3-turbo.bin",
  "language": "zh",
  "threads": 4
}
qq-archive transcribe --limit 50      # 转写待办队列
qq-archive transcribe --media 12      # 只转某一条

结果写进数据库并在网页「媒体管理」里直接显示。转写是可选能力:装不上或配置错,
归档主流程完全不受影响(相关接口会返回”不可用”而不是让服务崩掉)。

5.12 扫码登录 QQ

  • 网页:「QQ 登录」页会显示二维码并自动轮询登录状态,登录成功后列出账号。
    也支持”快捷登录”:用本机 NapCat 已保存的登录态一键上线(不用再扫码)。
  • 终端:qq-archive login 直接在终端里画出二维码;--list 只看可登录账号,
    --uin <账号> 直接快捷登录,--no-wait 打印二维码后立即退出。
  • 已登录时不会重复发二维码(接口返回”已登录”,界面会提示)。

安全说明:二维码 URL 本身就是一次性的登录凭据,因此它只在本机流转——
终端渲染不经过网络,网页渲染只下发给已登录的管理界面(不会出现在响应体明文里,有逐像素回归测试)。
所以请不要把 6099(NapCat WebUI)端口暴露到公网。


6. 部署:本机、Linux 服务器、Windows

6.1 本机(最简单)

qq-archive serve

默认只监听 127.0.0.1:8787(只有本机能访问)。日常当桌面工具用,这就够了。

6.2 Linux 服务器(systemd)

# 1. 放好程序(解包到 /opt/qq-archive 之类),准备好配置与数据目录
sudo mkdir -p /opt/qq-archive /var/lib/qq-archive /var/backups/qq-archive
sudo chown -R qqarchive:qqarchive /var/lib/qq-archive /var/backups/qq-archive
sudo chmod 600 /etc/qq-archive/config.json          # 令牌文件

# 2. 装服务
sudo cp deploy/qq-archive.service /etc/systemd/system/
sudo cp deploy/qq-archive-collect.service /etc/systemd/system/   # 采集单独一个 unit
sudo systemctl daemon-reload
sudo systemctl enable --now qq-archive qq-archive-collect

# 3. 看状态与日志
systemctl status qq-archive
journalctl -u qq-archive -n 50
curl -s http://127.0.0.1:8787/api/health            # 健康检查

细节(用户、路径、环境变量覆盖、升级、排障)见 DEPLOYMENT.md。

要从外部访问:默认只听本机是刻意的安全默认。远程访问请改 server.host
并自己加 TLS 反向代理(Nginx/Caddy),同时把 server.token 加长。
聊天记录不该默认暴露在网络上。

6.3 归档端与采集端分离部署

如果服务器上跑不了受支持的 QQ 客户端(Linux 服务器通常如此),可以让
采集端跑在你的个人电脑(Windows 或桌面 Linux)上,把归档好数据同步到服务器:

  • 采集端负责 collect(把新消息与媒体写进 data/);
  • 定期把 data/ 同步到服务器(rsync -a --delete 或直接搬 archive.db + media/);
  • 服务器只负责 serve(网页与检索)。

⚠️ 要明确:服务器上登录了 QQ ≠ 拿到手机端的全部历史。边界见第 2 节。

6.4 Windows

源码是纯 Node(无原生模块),Windows 上能跑;deploy/windows/ 下提供了启动/安装脚本。

但必须如实说明:这些脚本从未在真实 Windows 上执行过(开发机是 Linux、没有 pwsh),
只做过 80 项静态自检。如果你要在 Windows 上正式用,建议先手工验证一遍:

node --version                                    # 需要 >= 22.5
node --experimental-strip-types apps\cli\src\main.ts doctor
node --experimental-strip-types apps\cli\src\main.ts serve

跑通了再用脚本。Docker 镜像本项目没有提供(开发机没装 docker,不交付未验证的东西)。


7. 命令速查表

命令 用途
qq-archive serve 起网页 + API(默认 127.0.0.1:8787)
qq-archive serve --port 9000 --host 0.0.0.0 换端口/地址(远程访问记得配 TLS 反代)
qq-archive import <路径...> 导入导出文件或目录(--account / --format / --limit / --no-media)
qq-archive collector setup [--write-config] 采集端容器编排:拉镜像、起容器、自动读出 token 写进配置
qq-archive collector status 看采集端容器/端口/数据目录现状
qq-archive collect --once 立刻采集一轮
qq-archive collect 常驻定时采集(Ctrl+C 停)
qq-archive stats 归档统计
qq-archive logs --limit 50 最近日志
qq-archive backup [--with-media] [--label 文本] 立即备份(--with-media 连媒体一起打包)
qq-archive restore <备份目录或快照> 恢复演练(dry-run,只校验不动数据)
qq-archive restore <...> --apply [--restore-media] 真正恢复(先另存当前库为 .bak)
qq-archive verify 重新校验所有媒体文件的 SHA-256
qq-archive refilter 按当前 filters 规则重算已有消息的过滤标记
qq-archive purge --session <id|名称> 删除一个会话(默认只演练,--yes 真删,--media 连孤儿媒体一起删)
qq-archive purge --all --yes 清空全部归档数据
qq-archive login [--list|--uin N] 扫码登录 / 列出账号 / 快捷登录
qq-archive transcribe [--limit N] 转写待处理语音(需 stt.enabled=true)
qq-archive token 打印网页登录令牌
qq-archive doctor 环境自检 + 归档现状
qq-archive --version / --help 版本 / 用法

8. 常见问题(FAQ)

Q:网页打不开 / 别的机器访问不了?
默认只监听 127.0.0.1(本机)。要从别的机器访问,改 server.host 并配 TLS 反向代理,
同时把 server.token 加长到 32 位以上。

Q:登录提示”令牌错误”?
1)用 qq-archive token 打印当前令牌(它读 config.json);
2)确认 serve 用的是同一份配置文件(--config 指向同一个文件);
3)确认没在别的机器上复制过旧令牌。令牌在 config.json 的 server.token 里。

Q:采集报”采集端不可用”?
按顺序查:NapCat 容器在跑吗(podman ps)→ 端口通吗 →
两边 token 一致吗(collector.token 要等于 NapCat 的 OneBot httpServers[].token)→
QQ 掉线了吗(NapCat 重启后通常需要重新登录)。
这些失败现在都会记进「归档管理 → 运行日志」,所以”为什么某一轮没采到”查得到。

Q:每轮都显示”重复 N”,是出错了吗?
不是。采集会拉取一个时间窗口(默认回看 5 分钟),窗口内已入库的消息会被去重挡掉,
所以”拉取 27、重复 27、新增 0”是完全正常的状态。

Q:手机上的老消息能补回来吗?
不能。手机端未同步到服务端的记录,QQ 不提供任何可取回的接口。见第 2 节。

Q:为什么有些图片显示”缺失”?
采集到的媒体直链带有时效签名(rkey),过期后就取不到了;本工具会失败后自动用 OneBot
的媒体接口再取一次字节,仍拿不到才标为缺失并写明原因。已归档的图片不会因为这个丢失。

Q:媒体占空间太大怎么办?
可以在 config.json 里把 collector.syncMedia 设为 false(只记消息不下载媒体),
或按需清理:qq-archive verify 查出损坏项,再手工删除;backend 也支持只导出文字。

Q:想换机器 / 换服务器怎么办?
把整个 data/ 目录拷过去即可(里面含 archive.db、media/、backups/),
再在新的 config.json 里指向它。备份记录会自动与新路径对账(不会重复登记)。

Q:升级到新版本会不会丢数据?
不会。数据库迁移在启动时自动执行。稳妥做法是先 qq-archive backup --label 升级前,
再替换源码、重启。降级到旧版本前请先看 DEPLOYMENT.md 的升级章节。

Q:日志页是空的?
日志页只显示 warn/error 级别(info 只写文件,避免刷屏)。正常运行、没有告警时它本来就该是空的;
想看全部日志看 data/logs/archive.log,或 qq-archive logs。

Q:能不能只看某个时间段的聊天?
可以。网页媒体页支持日期范围筛选;会话内也支持时间范围与类型筛选(通过 URL 参数或界面控件)。

Q:点「QQ 登录」没有二维码 / 页面像是没反应?
先分清两种情况:

  1. QQ 已经登录了(最常见):页面会显示「NapCat 连接:已连接」「QQ 登录状态:已登录」和一句
    「QQ 已登录,无需扫码。」——这是正常的,不是故障;要重新扫码得先在 NapCat 侧登出。
    下方「快捷登录」表里会列出本机已保存的账号,点「快速登录」即可让它上线。
  2. 页面内容完全不切换(地址栏变了、右侧还是总览):那是路由缺陷(qq 曾漏登记在路由表里,
    已修复于本版本)。如果你用的是旧包,请更新到最新版本;判定方法:地址栏是 #/qq 但页面标题仍是「总览」。

Q:为什么不直接把 NapCat 集成进这个工具?
因为它不是一个能塞进进程里的库:容器里实际跑的是 /opt/QQ/qq(官方 QQ 客户端本体,Electron)

  • Xvfb 虚拟显示器 + NapCat 的注入加载器,镜像 1.5 GB。内置进来会同时触三件事:
    再分发闭源 QQ 客户端、再分发 NapCat(其许可禁止发布修改版)、让归档进程持有你的 QQ 登录会话,
    而且与 QQ 版本强绑定(客户端一升级就一起失效)。
    所以本项目只在 loopback 上用公开的 OneBot HTTP 接口与它互操作 ——
    但安装与配置已经自动化:qq-archive collector setup --write-config 一条命令搞定。

Q:别人撤回消息后,我这边还能看到吗?
能 —— 只要你在撤回前已经采集到它。归档是本地副本,且采集写入是”有则忽略”,
后续轮次不会更新也不会删除已入库的消息。详见 5.7。
注意两点:撤回发生在采集之前就拿不到(上游不补推);撤回标记不会自动出现(采集接口看不到撤回事件)。

Q:聊天记录页不会自己更新,要手动刷新?
现在默认每 5 秒自动刷新了(会话列表 + 已打开会话的新消息增量追加)。
可在工具栏「自动轮询」里改成 2 秒 / 30 秒 / 关闭。若仍不更新,按 Ctrl+Shift+R 强刷一次清掉旧前端缓存。

Q:界面上的”立即采集”和”开始/停止采集”有什么区别?
「立即采集一轮」= 只跑这一次;「开始采集」= 启动定时轮询(每 collector.intervalSec 秒一轮,默认 60),
「停止采集」= 停掉定时轮询。面板上的徽标会明确显示现在是「运行中」还是「已停止」。
停止只影响轮询,不会删除任何已归档数据;重新开始会从上次游标继续。

Q:我停了一段时间,这期间的消息还能采到吗?
能 —— 只要上游历史里还有。重新开始时会从上次游标继续,中间到达的新消息一并补上。
实测:停机 70 秒后重启,一轮补回了 5 条停机期间的消息。
上游限制:停太久(超过 NapCat 的历史缓存容量,群聊约几千条)的老消息补不回来。

Q:会不会影响我的 QQ 账号?
采集只调用 OneBot 的只读接口(11 个白名单,代码里硬编码,其他 action 在本地直接拒绝、
连 HTTP 请求都不发)。风险与 NapCat 本身相同(属第三方协议实现),建议用小号或隔离环境,
并且不要把 OneBot 端口暴露到公网。

Q:支持多少个账号?
数据结构支持多账号(account_uid 贯穿全表),但当前采集调度是单账号串行。
多账号并发采集还没做。


9. 安全与隐私(必读)

这个软件里装着你的私人聊天记录,所以它的默认值都是”保守”的:

项 默认 说明
监听地址 127.0.0.1 只有本机能访问;远程必须显式配置 + TLS
登录 必须令牌 未被认证的请求一律 401,且响应体不泄露会话名/消息内容
令牌 首次运行自动生成 32 位 存在 config.json(建议 chmod 600)
媒体下载接口 需认证 + 路径边界校验 防路径穿越;未登录不能取媒体
日志脱敏 自动 常见密钥形态会被替换为 <redacted>
写接口 不存在 只读白名单之外的 OneBot 调用在本地被拒绝

你必须自己注意的:

  1. config.json 与 data/ 是私密的——里面有令牌、数据库、媒体、日志。
    分享/备份给别人之前务必确认已排除(见第 10 节的打包脚本)。
  2. 不要把 8787(管理界面)、3000(OneBot)暴露到公网。
  3. 本项目没有端到端加密:数据在磁盘上是明文 SQLite。需要更强保护请用全盘加密(LUKS/BitLocker)。
  4. 备份文件同样是明文,放到别处时注意介质安全。

10. 怎么把它交给别人用

10.1 打包(一条命令,已含安全检查)

项目里已经生成好一份可直接分发的包:dist/qq-archive-0.1.0.tar.gz(538 KB,140 个文件)。
它能这么用:

tar -xzf qq-archive-0.1.0.tar.gz && cd qq-archive-0.1.0
cp config.example.json config.json && chmod 600 config.json
node --experimental-strip-types apps/cli/src/main.ts doctor
node --experimental-strip-types apps/cli/src/main.ts serve

接收方唯一需要的是 Node.js ≥ 22.5(不需要 npm install、不需要编译)。
实测:解包后直接 doctor / 导入 244 条 / 起服务全部正常。

发布包里包含:packages/(源码)、apps/、web/(无构建前端)、deploy/(systemd 与 Windows 脚本)、
docs/(全部文档)、tests/(可自行验证)、config.example.json、README.md、bin/qq-archive.mjs。

发布包里不包含(这是重点):

排除项 原因
data/ 你的数据库、媒体文件、备份、日志——私人聊天记录
config.json 含 server.token、OneBot token、NapCat token
node_modules/ 零生产依赖,不需要
*.log、login-qr.png 日志与一次性登录二维码

我自己打包时会逐项扫描:确认包内没有 data/、没有 .db/.bak/二维码,并且拿当前
config.json 里的三个真实令牌逐个在包内全文搜索,确保没有泄漏。这次的检查结果:

✓ 没有私密文件
✓ server.token 未出现
✓ collector.token 未出现

你自己重新打包时,照这个模式做即可(核心是显式 --exclude 并用真实令牌做一次全文搜索)。

10.2 更省事的两种方式

  • 打个 git 仓库:git init 后把 .gitignore 保留(它已经排除了 data/、config.json),
    推到一个私有仓库,让对方 git clone。
  • 直接拷目录:把解包后的目录拷到 U 盘/NAS,注意别把 data/ 和 config.json 一起拷过去。

10.3 分发前你需要决定的一件事:许可证

本项目当前没有选择许可证(源码在你自己的工作目录里)。发给别人之前建议先定下来,因为:

  • 本项目的代码是原创的(生产依赖 0 个),你可以自由选择(MIT / Apache-2.0 等);
  • 但它通过 HTTP 接口互操作的 NapCat 是自定义许可(禁止商业使用、禁止发布修改版),
    使用者需要自行安装并遵守其条款——这一点建议写进你的分发说明里;
  • 详细的开源复用清单与边界见 README.md 的”开源项目复用清单与许可证说明”一节
    与 qce-reuse-analysis.md。

11. 已知限制清单

已实现且有测试与实测证据:导入 5 种格式、持续采集、媒体内容寻址归档、中文检索、
引用关系、合并转发展开、过滤、网页全部页面、导出、备份与真实恢复、语音转写、扫码/快捷登录。

已实现但有局限:

项 局限
采集完整性 只能采到”采集端在线期间”的消息;离线窗口上游不补推
多账号 数据结构支持,但调度是单账号串行
语音转写质量 未做准确率基准(未覆盖方言、强噪声、多人重叠)
表情/卡片/位置消息 原始 JSON 已保存,但网页上降级为占位文本
媒体缺失 标记为缺失并说明原因(这是诚实,不是缺陷)

未验证(如实列出,不声称可用):

项 状态
Windows 脚本真实执行 只做静态自检,从未在真实 Windows 上跑过
真实 systemd 环境 unit 文件齐备,但没在真实 systemd 主机上验证过
LLOneBot 等其它 OneBot 实现 只真机验证过 NapCat
Docker 部署 未提供
真实第三方 HTML 导出器 HTML 导入只用合成 fixture 验证过

做不到(第 2 节已详述):阻止 QQ 删消息、手机端未同步记录、离线期群消息、已撤回消息、
已解散群、服务端完整性保证、账号密码登录、任何写操作。


12. 数据在哪里、怎么迁移、怎么彻底删掉

config.json                    配置(含令牌,600 权限)
data/
├── archive.db                 SQLite 数据库(会话、消息、媒体元数据、任务、日志)
├── archive.db-wal / -shm      SQLite 的 WAL(正在运行时存在,属正常)
├── media/                     媒体原件(按 sha256 命名,image/video/audio/file 分类)
├── backups/                   备份快照 + .manifest.json + 媒体打包
└── logs/archive.log           文件日志(按体积轮转)
  • 迁移到新机器:整个 data/ 拷过去,把新 config.json 的 storage.dataDir 指过去即可。
    备份记录会自动按文件名与新路径对账,不会重复登记。
  • 只备份数据:qq-archive backup(或用任何文件级备份工具备份 data/,但运行中直接拷
    archive.db 可能拿到不一致的副本
    ,所以推荐用内置 backup)。
  • 彻底删除:停掉服务 → 删除 data/ 与 config.json 即可,程序不在系统其它位置留东西
    (不写注册表、不写用户目录、不联网上报)。
  • 导出后离开:在网页「导出」页(或时间线右上角「导出」)把会话导出成 json/jsonl/txt。
    ⚠️ 导出目前只有网页入口,没有 qq-archive export 命令——这是当前的一个已知空缺,
    脚本化导出需要走 API(GET /api/export/session/<id>?format=jsonl,需带登录令牌)。
    导出的文件是自包含的,不需要本工具也能读。