用 Rust 重写了 QQ 聊天导出工具
起因
事情是这样的。
去年我想导出 QQ 聊天记录,找到了一个叫 QQFlow 的开源工具。用了一下,能用,但有两个问题:一是软件太大了,装完一百多兆;二是有点卡,打开数据库的时候要等好久。
我看了一下源码,是 Electron + Python 写的。Electron 嘛,懂的都懂,一个 Hello World 都能打包出一百多兆。
当时我正在学 Rust,就想,要不用 Rust 重写一个?
为什么是 Rust
说实话,用 Rust 写桌面应用不是最方便的选择。生态不如 JavaScript/Python 成熟,很多东西要自己造轮子。
但 Rust 有两个好处:一是快,二是小。
快就不说了,Rust 的性能比 Python 强几条街。小呢,同样功能的应用,Electron 打包一百多兆,Tauri(Rust 的桌面框架)打包才十兆左右。
对于一个本地工具来说,这两点很重要。你不会想为了导出聊天记录装一个一百多兆的软件。
技术栈
最后选的技术栈是:
- 前端:React + TypeScript + Vite
- 后端:Rust + Tauri 2
- 数据库:rusqlite(SQLCipher 解密)
- 打包:Tauri CLI
前端用 React 是因为我熟,后端用 Rust 是因为要处理加密数据库。Tauri 负责把两者粘在一起。
核心功能
密钥提取
QQ NT 的聊天记录存在 SQLite 数据库里,但数据库是加密的。加密的密钥存在 QQ 进程的内存里。
所以第一步是提取密钥。
我用了 Windows Debug API 注入 QQ 进程,读取内存,找到密钥。听起来很黑客,其实就是调了几个 Windows API。
这部分代码写得我头大,因为 QQ 更新一次内存地址就变一次,得动态搜索。最后用了模式匹配,算是稳定下来了。
数据库解密
拿到密钥之后,就是解密数据库。
原版用的是 Python 的 pysqlcipher3,解密一个 190MB 的数据库要好几分钟。我用 Rust 的 rusqlite,加上流式处理,十几秒就搞定了。
这大概是整个项目里最有成就感的部分。同样的功能,Python 要几分钟,Rust 几秒钟。不是 Rust 多厉害,是 Python 在这种场景下确实慢。
聊天导出
导出功能很简单,就是把数据库里的聊天记录读出来,写到文件里。支持 TXT 和 CSV 两种格式。
这里有个坑:QQ 的消息类型很多,文字、图片、表情、红包、小程序等等。每种类型的处理方式都不一样。我写了十几个解析器,才把常见的消息类型都覆盖了。
聊天分析
分析功能是后来加的。
主要是统计:24小时分布、消息类型饼图、成员排行、高频短语。用 ECharts 做的可视化,放在前端页面里。
这个功能本来是给自己用的,想看看群里谁最话痨。后来发现挺有意思的,就保留了。
踩过的坑
坑一:SQLCipher 版本
QQ 用的 SQLCipher 版本和 rusqlite 默认编译的不一样,导致解密失败。我折腾了两天,最后发现是 pragma 设置的问题。
坑二:前端样式
我不擅长 CSS,但 Tauri 的前端是普通的 Web 技术栈,所以样式得自己写。我抄了几个开源项目的样式,拼拼凑凑,勉强能看。
坑三:Windows 权限
注入 QQ 进程需要管理员权限,但 Tauri 默认不以管理员身份运行。我改了 manifest 文件,加了 requireAdministrator,才解决这个问题。
最终效果
软件打包出来 10MB 左右,安装完也是 10MB。打开数据库 190MB 的话,十几秒搞定。内存占用 30MB 左右。
对比原版 Electron 版:
- 安装包:150MB → 10MB
- 打开数据库:几分钟 → 十几秒
- 内存占用:200MB → 30MB
我觉得这个重写是值得的。
开源
项目已经开源了:yfgug/qqflow-rust
如果你也有导出 QQ 聊天记录的需求,可以试试。目前只支持 Windows,因为密钥提取用的是 Windows API。Mac 和 Linux 的话,密钥提取方式不一样,我还没研究。
写在最后
这个项目前前后后写了两个月。中间有好几次想放弃,因为 Rust 的编译错误真的让人崩溃。有时候一个生命周期的问题能卡我半天。
但最后还是写完了。
写完之后的感觉是:Rust 写桌面应用确实不如 JavaScript 方便,但如果你对性能有要求,Rust 是个好选择。
至少不用担心内存泄漏。
“代码是写给人看的,顺便让机器执行。”