agentsclimarketplace

Lark to wechat mp

Skill CookiesHaha/ash-claude-skills/plugins/ash-workflow/skills/lark-to-wechat-mp

Use when syncing a Feishu (Lark) document or Wiki page to a WeChat Official Account (微信公众号) draft box. Handles Feishu CDN image migration to WeChat CDN, Markdown-to-WeChat HTML conversion (inline styles, callout blocks), and cover thumbnail creation. Requires WeChat AppID/AppSecret pre-configured.From its SKILL.md

Install
npx -y skills add CookiesHaha/ash-claude-skills --skill lark-to-wechat-mp

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

4 things to look at

  • reads credentialsReads from 1 credential source: `~/.claude/wechat-mp.json`.
  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 8 commands, including `cat > ~/.claude/wechat-mp.json << 'EOF'` and 7 more.
  • fetches URLsInstructs the agent to fetch 4 URLs, including https://api.weixin.qq.com/cgi-bin/token and 3 more.

SKILL.md

6.6 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it

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 认证、权限)

  1. lark-cli configured with user identity
  2. WeChat config at ~/.claude/wechat-mp.json (see Setup)
  3. 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(首次必做,只做一次)

  1. 打开 微信公众平台 → 设置与开发 → 基本配置
  2. IP白名单 → 添加当前机器 IP
  3. 获取当前 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

对文档中每个 ![](feishu-url) 图片:

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-srcsrc
<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 credentialToken 过期(有效期 7200s)或凭证错误重新获取 Token
Thumb upload: errcode:40009缩略图超过 64KB 或非 JPEG降低 sips formatOptions,确认文件格式
图片显示为空微信 <img> 需同时有 srcdata-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()))

What ships with it: 1 file

13.8 KB alongside SKILL.md, 1 of them executable

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.