我用 TRAE 做了个 Obsidian 插件,把掘金接进分发流水线(本文就是它发的)
参赛赛道:全链路开发实战
选题方向:真实工作场景提效
先把我的写作环境交代清楚
我的技术文章全部产在两个 Obsidian 库里。HarmonyOSTech 放鸿蒙内容,两条主力连载《HarmonyOS 6.1.1 全栈实战录》更到一百零一篇、《HarmonyOS7 更新亮点实录》更到四十三篇;酷AI社放 AI 方向的稿子。两个库都是 PARA 结构,文章按内容域进 3.Areas,配图就近放文章目录的 attachments/,粘贴图片时 paste-image-rename 插件会自动按笔记名重命名,所以一篇文的图都是 ![[标题-1.png]]、![[标题-2.png]] 这种 wikilink 引用,自己画的 mermaid 流程图和 Excalidraw 源文件也在库里跟正文一起管理版本。
每篇文章头部有一套固定的 frontmatter,标题、csdn标题、作者、摘要、category、tags、progress、发布平台、原文地址这些,再加 source、原创度、改造记录一组溯源字段。progress 从初稿走到成稿再走到已发布,库里有一页靠 Dataview 把所有带 progress 的笔记自动聚成「在制」和「已发布」两张表,平台链接直接可点,这页就是我的写作仪表盘,新发一篇、状态一变索引自己就更新了,从来不用手动登记。库里另有发布 SOP,把两道闸门写死在流程里,第一道查原创度,溯源字段不达标不发布,第二道是去 AI 腔文风自检,加粗密度、过渡词、列表骨架这些特征逐条过,读者对机器腔的识别能力比作者强得多。
分发那一头,公众号走 NoteToMP,样式是自定义的 Less 文件,封面配了一组 Python 脚本,一条命令同时出列表页的 940×400 大图和分享卡片的 380×380 小图,传进微信素材库之后 media_id 也记回 frontmatter。CSDN、InfoQ、51CTO、开源中国走的是我早前自己写的 obsidian-publisher,puppeteer 驱动 Chrome 模拟人工点击,那套方案跟各家 WAF、DOM 选择器、登录态打了一场消耗战,最后的结论是 UI 自动化这条路个人耗不起,知乎和掘金从那天起就没接上。整条流水线摊开是这样,掘金就是断在最右边的那一路:
一条分发流水线,就差掘金这一环
这样一圈看下来,公众号、CSDN、InfoQ、51CTO、开源中国都有自动化路子,唯独掘金一直是我手工发。手工发一篇掘金的动作我数过一遍,打开编辑器、粘贴正文、把 ![[标题-1.png]] 这种 wikilink 一张张换成平台认识的图片链接并手动上传、清理 callout 和高亮语法、挑分类、在一堆标签里搜对应 tag、最后填摘要,前后六个动作。掘金接口对摘要有 50 到 100 字的硬限制,这一项经常要人肉返工,改两遍字数才能提交过去。两条连载连着更的时候,光分发一篇文章就要二十来分钟,这个成本比写番外还累人,所以每次都是先把掘金那一份放着,等哪天有空了再补,越积越多。
掘金的缺口一直没补,不是没想到,是上次 puppeteer 的消耗战把人打怕了,同样的弯路不想再走。这次 TRAE 征文活动页面里那句「它可以是一个能替你省掉重复工作的工具」,我想了想手上最重复的就是这件事,而接口直连和 puppeteer 是两种性质,不用模拟人,也就不存在跟风控赛跑的问题。于是决定用 TRAE 把掘金的最后一环接上,做一个 Obsidian 插件,选中笔记一键发到掘金,图片自动上传,分类标签自动映射,发布完把链接和状态回写进 frontmatter,让那页 Dataview 索引自动多出一行。这篇文章本身也是用它发到掘金的,算是把自己踩过的坑顺手填了。
动代码之前先把接口口径核实清楚
掘金没有面向个人的官方发布 API,社区里所有自动化工具走的都是 Cookie 模拟浏览器请求的路子,所以我开工第一件事不是写代码,是让 TRAE 先做一轮调研把接口口径交叉验证出来,就怕照着一份过期的文档写完发现全是错的,这种返工比不会写更耗人。
调研的结论来自三份独立材料,一份是 2026 年 9 月那篇《我把自己的掘金账号交给 AI 打理》,作者在真账号上把草稿创建、发布、标签查询整套接口跑通了,请求体字段是原样贴出来的;第二份是还在维护的开源插件 Obsidian-PublishX,它的掘金适配器给出了发布链路里每个字段的实际用法;第三份是 anyesu/blog 里的 JueJinPlugin,它把掘金编辑器粘贴图片时走的上传链路完整逆向了一遍,而这条链路恰好是我之前所有手工方案里最缺的一块,PublishX 只处理了发布没讲图片怎么走。
材料之间也有互相打架的地方,这就得用只读请求当场验。TRAE 的 Agent 在终端里对着 api.juejin.cn 挨个探测路由,只读接口随便调,写接口一律不碰,这条规矩是从那篇调研文章的踩坑记录里抄的,那位作者用空请求体去试探创建接口,结果真的建出了一份空草稿,后来专门补了删除接口来收拾。标签搜索接口这里探到了一个很具体的坑,第一次按直觉传了字符串形式的 cursor,服务器直接回了这么一段:
json: invalid use of ,string struct tag, trying to unmarshal unquoted value into int64
Go 服务端把字段类型都写在错误信息里了,等于当面告诉你 cursor 和 limit 必须是数字。参数改对之后搜「鸿蒙」返回了 tag_id 为 6809641209204572174 的 HarmonyOS 标签,分类列表接口也确认了前端、后端、人工智能在内的八个分类 ID。顺带把网上流传的一个说法验证掉了,有个发布 skill 的文档写着图片走 POST /content_api/v1/upload_pic,探测结果是「请求路由不存在」,掘金的图片上传其实走的是另一条通道,后面会讲到。
这一步的产出是一张写进 README 的接口核实表,每个接口标了口径来源和验证方式,后面所有实现都建立在这张表上,没有一处接口名是猜的。
插件的骨架:网络层收口,纯逻辑都能在 Obsidian 外面跑
插件要干的事其实分四块,读懂 Obsidian 的笔记格式、把掘金不认识的语法转换掉、把本地图片换成 CDN 链接、拼出合法的草稿请求,我定的第一个原则就是别让这四块互相缠在一起。
所有外部 I/O 收进一个 Transport 接口,运行期用 Obsidian 官方的 requestUrl 实现,单测里注入 mock,语法转换器、frontmatter 解析、摘要构造、签名算法这些全部写成零依赖的纯函数,发布流水线只依赖这些抽象。这个决定是在写第一个模块的时候就定下来的,好处是整个工程可以不打开 Obsidian、不碰掘金服务器就完整跑通测试,我在终端里跑 npm test 的时候不用担心任何网络副作用。
整条流水线的走向是这样,掘金不渲染 mermaid,我发布前把它导出成了 PNG:
实现里有两处细节值得一提。语法转换器要先按围栏代码块把全文切成段,所有转换只作用于非代码段,因为文章里写代码示例的时候很容易出现 ==不等于高亮== 或者 > [!NOTE] 这种字符串,不先分段就会被一起改掉,而且改完看不出来,是在掘金预览时才炸。另外 wikilink 图片解析失败的时候保留原样并记进 unresolved 列表,不悄悄删掉,发布前弹窗会显示有几张图解析失败,人自己决定要不要发。
图片上传:掘金编辑器自己在走的那条链
这是整条链路里资料最少的一部分,掘金的图片上传不在 content_api 下面,网页编辑器里粘贴一张图,浏览器实际走了四步,先拿登录态向 imagex/gen_token 换一组临时的 STS 三元组凭据,然后用这组凭据对字节跳动的 veImageX 服务做 AWS SigV4 签名申请上传会话,拿到目标主机和存储路径之后把图片二进制直接传上去(请求头要带 Content-CRC32 做校验),最后用返回的 uri 换正式的 CDN 地址。
麻烦在第二步的签名,一般做法是引 aws-sdk 来算,但 Obsidian 插件是跑在渲染进程里的 JavaScript,为一次签名背几百 KB 的 SDK 不值当,所以改成用 Web Crypto 手写,构造 canonical request 的时候查询参数按字典序排、路径用 RFC3986 编码、时间戳和 security token 进头,再四轮 HMAC-SHA256 派生出 signing key 拼出 Authorization 头。CRC32 也只有一个查表函数的事,同样手写。整包构建出来 23 KB,一个运行时依赖都没引。
写 mock 测试的时候抓到一个行为差异挺坑人。掘金的几个业务接口都返回 {err_no, err_msg, data} 的统一壳,第一版 mock 顺手把 imagex 的响应也包进了这个壳里,结果流水线怎么都取不到上传地址,对着参考实现核实以后才发现 veImageX 走的是云服务自己的响应格式,Result 直接在最顶层,根本没有 err_no 这一层。两类响应壳的区别现在写进了代码注释,这算这次开发里最典型的一个想当然被测试拦下来的例子。
写接口之前先弹确认窗
插件给了两个命令,发布当前笔记到掘金(存草稿)和直接公开,不管哪一个,在真正调写接口之前都会弹一个确认窗,把掘金会收到的一切摊开让人过目,标题、匹配到的分类、每个 tag 对应上哪个掘金标签(没匹配上被丢掉的会黄字提示)、摘要及其字数、图片传了几张失败了几张、正文前 400 字的预览,点确认之前掘金侧不发生任何写入。
这个设计是从那份调研文章收尾那里得来的,Agent 拿到的应该是能力不是权限,能力可以随时撤销而且有闸门。摘要 50 到 100 字的校验也放在弹窗之前,长度不够当场就报错,而不是把非法请求发给掘金再去解读它语焉不详的错误码。发布成功之后插件把 progress、原文地址、发布平台: 掘金 回写进笔记 frontmatter,仓库里那个靠 Dataview 聚合的文章索引页就自动多出一行,不用再手动登记。
算一下账,也说下没做完的
同一条分发动作,手工走完全程六个动作二十分钟,插件化之后是敲快捷键、看一眼确认窗、点确认三步,图片多的时候再多等十几秒上传。省下的时间其实不是最要紧的,要紧的是这件事从只有有空才做变成了写完顺手就做,两条连载跟掘金的同步率这几天肉眼可见地变了上来。
工程本身的数据摆在这里,TypeScript 类型检查零错误,35 个单元测试覆盖语法转换、frontmatter 处理、SigV4 签名、CRC32 校验和整条发布流水线,全程离线跑 mock,构建产物 23 KB。踩过的坑都留在了代码注释和 README 的核实表里,article_draft/query_list 路径不是 list、标签接口在 tag_api 域不在 content_api、html_content 字段已经弃用不要再发、跨分类的 tag_id 会让草稿创建失败。
我用 TRAE 做了个 Obsidian 插件,把掘金接进分发流水线(本文就是它发的)-5.png
没做完的也一并交代,掘金不渲染 mermaid,导 PNG 这一步还没做进插件,专栏挂载(column_ids)没实现,Cookie 的实测有效期各家口径从三十天到一年不等,按三十天预期、失败时去设置里重新粘一次就行,发布频率太高会触发限流,批量分发要逐篇留间隔。
代码放在 ~/juejin-publisher,装完依赖 npm run build 就能构建,三个文件拷进库的 .obsidian/plugins/ 即可使用。下一步想把发布后的阅读点赞数据拉回本地,接到选题复盘里去,哪类标题在掘金跑得动,数据说话比我自己猜要准。