lark-to-wechat-mp
Sync a Feishu doc/wiki → WeChat Official Account draft in one workflow. Images are migrated, formatting is preserved.
Prerequisites
CRITICAL — 执行前 MUST 先用 Read 工具读取 ../lark-shared/SKILL.md(lark-cli 认证、权限)
- lark-cli configured with user identity
- WeChat config at
~/.claude/wechat-mp.json(see Setup) - Current machine IP in WeChat IP Whitelist
Setup
1. WeChat Config File
cat > ~/.claude/wechat-mp.json << 'EOF'
{
"app_id": "wx...",
"app_secret": "YOUR_APP_SECRET"
}
EOF
chmod 600 ~/.claude/wechat-mp.json
2. IP Whitelist(首次必做,只做一次)
- 打开 微信公众平台 → 设置与开发 → 基本配置
- IP白名单 → 添加当前机器 IP
- 获取当前 IP:
curl -s ifconfig.me
Workflow(每次同步执行以下步骤)
Step 1 — 读取配置,获取 Access Token
APP_ID=$(python3 -c "import json; c=json.load(open(os.path.expanduser('~/.claude/wechat-mp.json'))); print(c['app_id'])" 2>/dev/null || \
python3 -c "import json,os; c=json.load(open(os.path.expanduser('~/.claude/wechat-mp.json'))); print(c['app_id'])")
APP_SECRET=$(python3 -c "import json,os; c=json.load(open(os.path.expanduser('~/.claude/wechat-mp.json'))); print(c['app_secret'])")
TOKEN=$(curl -s "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APP_ID}&secret=${APP_SECRET}" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
echo "Token OK: ${TOKEN:0:20}..."
Step 2 — 拉取飞书文档(Markdown 格式)
DOC_URL="https://xxx.feishu.cn/wiki/..."
lark-cli docs +fetch --api-version v2 --doc "$DOC_URL" --doc-format markdown > /tmp/lark_doc.json
⚠️ 飞书图片 URL 有时效性,必须立即进行 Step 3,不可搁置。
Step 3 — 下载飞书图片 & 上传到微信 CDN
对文档中每个  图片:
mkdir -p /tmp/wx_imgs
# 下载
curl -L -o /tmp/wx_imgs/imgN.png "FEISHU_IMAGE_URL" -s
# 上传到微信(返回 wechat_cdn_url)
curl -s -X POST "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=${TOKEN}" \
-F "media=@/tmp/wx_imgs/imgN.png;type=image/png"
# → {"url":"http://mmbiz.qpic.cn/..."}
记录 {feishu_url: wechat_cdn_url} 映射表,后续替换用。
Step 4 — 制作封面缩略图(首张图片)
# 缩放为 JPEG,控制在 64KB 以内(微信 thumb 上限)
sips -Z 700 /tmp/wx_imgs/img0.png -s format jpeg -s formatOptions 60 --out /tmp/wx_imgs/thumb.jpg
# 检查大小
wc -c /tmp/wx_imgs/thumb.jpg # 应 < 64000 bytes,否则降低 formatOptions
# 上传为永久素材(type=thumb),获取 media_id
curl -s -X POST "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${TOKEN}&type=thumb" \
-F "media=@/tmp/wx_imgs/thumb.jpg;type=image/jpeg"
# → {"media_id":"xxx","url":"..."}
Step 5 — 转换 HTML & 创建草稿
使用本 skill 目录的 draft.py 脚本一键完成转换 + 创建:
python3 ~/.claude/skills/lark-to-wechat-mp/draft.py \
--doc "$DOC_URL" \
--title "文章标题(可选,自动提取)" \
--author "" \
--digest "摘要(可选,自动生成)"
# → ✓ Draft created successfully! media_id: xxx
或手动创建(当需要精细控制 HTML 时):见文末「手动创建草稿」。
HTML 格式规范(微信兼容)
微信公众号文章必须使用 inline style,class 样式会被过滤。
| 元素 | 样式要点 |
|---|---|
全局容器 <section> |
font-family:'PingFang SC',...;line-height:1.8;color:#333; |
<h1> 一级标题 |
font-size:22px;border-bottom:2px solid #07C160; |
<h2> 二级标题 |
font-size:18px;border-left:4px solid #07C160;padding-left:10px; |
<p> 正文 |
margin:12px 0; |
<img> 图片 |
max-width:100%;display:block;margin:20px auto; + 必须同时设置 data-src 和 src |
<a> 链接 |
color:#07C160; |
<code> 行内代码 |
background:#f0f0f0;padding:2px 6px;font-family:monospace;color:#e83e8c; |
Callout 块(飞书高亮块)→ 黄色信息框
<div style="margin:16px 0;padding:14px 18px;background:#fffbe6;border:1px solid #ffe58f;border-radius:6px;color:#333;">
<p style="margin:0 0 8px;font-weight:700;">🏖️ 标题</p>
<ul>...</ul>
</div>
引用块 > → 灰色左边框
<blockquote style="margin:16px 0;padding:14px 18px;background:#f5f5f5;border-left:4px solid #07C160;color:#555;">
引用内容
</blockquote>
草稿 API 结构
payload = {
"articles": [{
"title": "标题",
"author": "",
"digest": "摘要(120字内)",
"content": "<section>...HTML...</section>",
"content_source_url": "飞书原文链接",
"thumb_media_id": "封面图 media_id(必填)",
"need_open_comment": 1,
"only_fans_can_comment": 0
}]
}
# POST https://api.weixin.qq.com/cgi-bin/draft/add?access_token=TOKEN
常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
errcode:40164 invalid ip |
当前 IP 未加白名单 | 微信公众平台 → IP白名单 → 添加 curl ifconfig.me 返回的 IP |
errcode:40001 invalid credential |
Token 过期(有效期 7200s)或凭证错误 | 重新获取 Token |
Thumb upload: errcode:40009 |
缩略图超过 64KB 或非 JPEG | 降低 sips formatOptions,确认文件格式 |
| 图片显示为空 | 微信 <img> 需同时有 src 和 data-src |
两个属性都填 WeChat CDN URL |
| 飞书图片下载失败 | URL 已过期(搁置太久) | 重新 docs +fetch 获取新 URL |
手动创建草稿(精细控制)
当 draft.py 转换效果不满意时,手动在 Python 中构建 HTML 字符串并 POST:
import json, urllib.request
TOKEN = "..."
payload = {"articles": [{"title": "...", "content": "<section>...</section>", "thumb_media_id": "..."}]}
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(
f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={TOKEN}",
data=data, headers={"Content-Type": "application/json; charset=utf-8"}
)
with urllib.request.urlopen(req) as r:
print(json.loads(r.read()))