我把「自动发文章到掘金」做成了一个 Agent 技能:juejin-skills

我把「自动发文章到掘金」做成了一个 Agent 技能:juejin-skills

仓库地址:github.com/liuleihang/... (MIT,Node ≥ 18,发布脚本零依赖)

我平时写技术文章的工作流是:本地 Markdown 写作 → 贴到掘金编辑器 → 手动传图 → 手动贴标签 → 点发布。一篇文章的「搬运」环节要花好几分钟,而且图片一张张拖上传最烦。

于是我做了这件事:把「发文章到掘金」封装成一个 Agent 技能------在 WorkBuddy、Claude Code、Cursor 这些 AI Agent 里说一句「把 ./article.md 存为掘金草稿,分类前端,标签 Vue.js」,它就自己完成登录态检查、标签 id 解析、图片上传、正文改写、建草稿,最后把草稿链接递给我确认。

这篇文章复盘实现过程中的几个关键点:非官方接口的抓包适配、字节 ImageX 三段式图床上传、以及「草稿优先」的安全设计。

整体结构

Agent Skill 的结构很简单:一份 SKILL.md 描述能力与工作流(Agent 据此自主调用),加一组零依赖的 Node 脚本:

bash 复制代码
juejin-skills/
├── SKILL.md                 # 技能定义:frontmatter + 工作流 + API 参考
├── README.md
└── scripts/
    ├── config.mjs           # 配置集中管理(~/.juejin/config.json 可覆盖)
    ├── login.mjs            # Playwright 弹浏览器人工登录,Cookie 落盘
    ├── check.mjs            # 会话体检
    ├── tags.mjs             # 分类/标签全量翻页查询与缓存
    ├── upload-images.mjs    # ImageX 三段式传图 + 正文改写
    └── publish.mjs          # frontmatter 解析 + 自动传图 + 草稿/发布

设计上有两条硬约束:

  1. 发布脚本零依赖 ------只用 Node ≥ 18 的原生 fetch 和 crypto,只有 login.mjs 需要 Playwright(首次登录时引导安装);
  2. 凭据外置 ------Cookie 只存 ~/.juejin/session.json,绝不入库、绝不出现在日志里。

关键点一:非官方接口的抓包适配

掘金没有公开 API 文档,接口只能抓包 + 实测。过程中踩到的「文档根本没有、只能实测才知道」的坑:

旧接口下线 。网上能搜到的教程里图片上传用的是 POST /images_upload/v1/create------实测已经 404 下线 了。标签查询的 query_category_tags 同样下线,返回「请求路由不存在」,现行的翻页接口换成了 query_tag_list。

cate_id 参数是摆设 。query_tag_list 带 cate_id 过滤参数,但实测根本不过滤,只能全量翻页拉回来(约 700+ 个标签)自己缓存匹配:

js 复制代码
// {cursor:"0", limit:100} 翻页,data 元素形如 {tag_id, tag:{tag_name}}
// cate_id 实测不过滤,只能全量拉取后本地缓存

所以 tags.mjs 的策略是全量翻页一次,缓存到 ~/.juejin/tag-ids.json,之后按名查 id 走本地缓存。但缓存不能一劳永逸------掘金随时可能新增或改名标签,陈旧缓存会让发布流程卡死在「找不到标签」。所以缓存文件记录了拉取时间,超过 7 天(可配置)自动重新全量刷新;发布时如果标签解析未命中,也会先强制刷新自愈一次,仍然找不到才报错。

摘要长度是硬校验 。brief_content 不在 50--100 字之间接口直接拒绝。脚本会在缺失或超限时自动补齐(截取正文前 N 字),但更建议在 frontmatter 里显式写 brief。

永远原样上报 err_no 。非官方接口随时可能再变,脚本对 err_no != 0 只做原样报告,不猜测、不盲目重试------把判断权留给上层(Agent 或人)。

关键点二:字节 ImageX 三段式图床上传

图片上传是整个技能里最复杂的一环。掘金编辑器现在走的是字节 ImageX 的三段式链路:

ini 复制代码
1. GET /imagex/v2/gen_token?aid=2608&uuid=<19位数字>&client=web
   (带 Cookie)→ 拿到 STS 临时凭证

2. ApplyImageUpload(imagex.bytedanceapi.com)
   ?Action=ApplyImageUpload&Version=2018-08-01&ServiceId=73owjymdk6
   → AWS SigV4 签名请求,SignedHeaders=x-amz-date;x-amz-security-token
   → 返回 StoreUri / StoreInfos(含 Auth 串)/ uploadHost

3. POST https://<uploadHost>/<StoreUri>
   头:authorization: <StoreInfos[0].Auth>、Content-CRC32: <8位hex>
   → 直传文件内容

4. CommitImageUpload(同一个 SessionKey)
   → 偶发 201007 bad gateway,脚本自动重试一次

5. GET /imagex/v2/get_img_url?...&img_type=private
   → data.main_url 即编辑器同款签名 URL,写进正文即可

三个值得记录的细节:

AWS SigV4 要自己签 。第 2 步的 ApplyImageUpload 不是普通 JSON 接口,是 AWS 风格签名请求。Credential 形如 AK/<yyyymmdd>/cn-north-1/imagex/aws4_request,用 Node 原生 crypto 手撸 HMAC 链即可,不需要引 AWS SDK。

缺 CRC32 头会报 MismatchChecksum 。第 3 步直传必须带 Content-CRC32 头(8 位 hex,小端序的 IEEE CRC32),否则服务端返回 4007 校验失败。这是全网几乎搜不到答案的坑,对照编辑器请求才定位到。

上传完的 URL 是 img_type=private 的签名 URL ,写进 mark_content 之后掘金渲染层会动态重签,所以不用担心签名过期------实测发布后图片全部 HTTP 200。

publish.mjs 会自动扫描正文里的行内图片 ![alt](path),逐张走上面这条链路,把返回的 CDN URL 原地改写进 Markdown。任何一张失败就中止发布------宁可发不出去,不发一篇带死链的文章。

关键点三:草稿优先 + 双标志互锁

让 Agent 替你发文章,最大的风险是「它理解错了但已经发出去了」。所以安全设计放在第一位:

css 复制代码
默认行为:node publish.mjs article.md        → 只建草稿,返回草稿链接
公开发布:node publish.mjs article.md --publish --yes   → 缺一不可
  • 草稿优先 :Agent 的工作流是「先建草稿 → 把链接给用户看格式 → 用户明确确认 → 才允许加 --publish --yes」;
  • 双标志互锁 :--publish 和 --yes 必须同时出现,互锁写在脚本里而不是提示词里------提示词可能被改,代码不会被;
  • 单次一篇:一次调用只处理一个文件,不做无人值守批量发布(频控与风控风险)。

这个思路对所有「让 AI 代替我执行对外发布类操作」的场景都适用:把危险动作拆成两段,中间必须有人确认,且确认机制放在代码层。

怎么用

把仓库 clone 到本地,放进任意 Agent 的技能目录(Windows / macOS / Linux 均可,发布脚本只依赖 Node ≥ 18),首次使用时说「登录掘金账号」完成一次扫码登录(Cookie 约 30 天有效),之后:

你说 它做
「把 ./article.md 存为掘金草稿,分类前端,标签 Vue.js」 体检会话 → 查标签 id → 传图 → 建草稿 → 给你链接
「把这篇文章里的本地图片上传到掘金图床」 只跑传图 + 正文改写
「确认无误,发布这篇草稿到掘金」 公开发布(你会看到它复述标题/分类/标签)

脚本也可以脱离 Agent 单独用,比如只传图:

bash 复制代码
node scripts/upload-images.mjs article.md          # 输出 article.juejin.md
node scripts/upload-images.mjs article.md --in-place  # 原地改写(自动留 .bak)

后续功能待办

当前版本只做了文章发布这一条链路。技能包的定位是「掘金社区技能包」,以下功能按优先级排队中:

# 功能 状态 说明
F1 文章发布(含本地图片上传) ✅ 已开源 本文内容
F2 掘金热榜 📋 规划中 按分类拉热门文章榜;只读操作,无需登录
F3 掘金沸点发布 📋 规划中 短内容 + 话题 + 配图(复用 ImageX 链路);发布前回显预览确认
F4 掘金签到 📋 规划中 每日签到领矿石,幂等设计(已签不重签)
F5 文章下载(单篇) 📋 规划中 掘金文章 → 本地 Markdown,保留代码块与元数据
F6 文章批量下载 📋 规划中 备份某用户的全部文章;限速防频控 + 断点续传

仓库在此,欢迎 star / issue / PR:github.com/liuleihang/...

相关推荐
前端双越老师3 小时前
程序员 1v1 访谈系列第十期:纯前端坚持不了多久了
前端·ai编程·全栈
threerocks4 小时前
运营 X(Twitter)的 Chrome 插件,看这篇就够了
aigc·openai·ai编程
谟松5 小时前
Claude Code 接 DeepSeek 一直刷 unrecognized_model?4 组对照实验,一次讲清
ai编程
lifallen5 小时前
ub-mode skill:软件工程原则
人工智能·学习·ai·软件工程·ai编程
TimeFine6 小时前
AI 编码工作流的设计(二)
android·ai编程
Dumbness7 小时前
二十行手写的 SKILL.md 头部解析,遇到 YAML 的折叠写法、单引号和嵌套键就读错:和 js-yaml 逐条对照
node.js·ai编程
ZzT8 小时前
vibe coding 做小游戏·问题排查篇:解决包体积、引擎适配与 iPhone 机型适配问题
ai编程·游戏开发
VIP_CQCRE9 小时前
Claude Code 如何接入 Ace Data Cloud?终端与 VS Code 配置指南
api·ai编程·vs code·claude code·ace data cloud
濮水大叔9 小时前
AI Vibe Coding 实战(续):SMS 套件产品决策与开发方案
ai编程·全栈·vibecoding