mcpField
W
wechat-official-account

wechat-official-account

github.com/acedatacloud营销与增长17246 次安装今天更新
代码仓库

关于 wechat-official-account

使用 `curl + jq` 调用微信公众号 API 的 Shell 技能,面向管理公众号内容和账号操作的 Agent。它支持文章草稿与发布、关注者管理、客服消息和数据统计,并说明账号权限、配额及发布前确认要求。

主要功能

  1. 01创建、列出和更新文章草稿
  2. 02发布草稿并查询状态
  3. 03管理菜单和关注者记录
  4. 04发送客服消息
  5. 05获取关注增长和阅读统计
  6. 06处理令牌流程和 API 错误

适用场景

  1. 01准备文章草稿供审核
  2. 02发布已确认的草稿并获取链接
  3. 03查看关注者资料和增长情况
  4. 04向符合条件的关注者发送消息

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/acedatacloud/skills --skill wechat-official-account

安装后在 Claude Code 中输入/wechat-official-account确认已加载。

详细介绍

我们使用 curl + jq 调用 WeChat MP 服务端 API。与使用 OAuth bearer 令牌的连接器不同,WeChat MP 采用两步流程:

  1. 用 AppID + AppSecret 换取 access_token(有效期 7200s,每个应用每天的全局调用上限约为 2000 次)。
  2. 在每次其他调用中,将该 access_token 作为查询字符串参数传入。

用户的凭据位于 $WECHAT_APP_ID 和 $WECHAT_APP_SECRET 中。切勿记录或回显 $WECHAT_APP_SECRET——请像对待密码一样保护它。

WeChat MP API 返回标准 JSON。即使出错,HTTP 状态码也会是 200;响应内容类似 {"errcode": 40013, "errmsg": "invalid appid"}。errcode == 0 表示成功——任何非零代码都应将原始 errmsg 原样展示给用户。

重要限制——请提前告知用户,以免造成意外

  • IP 白名单:每次 API 调用的源 IP 都必须位于此应用的 IP 白名单中(公众平台 → 设置与开发 → 基本配置 → IP 白名单)。如果看到 errcode 40164(“invalid ip”),说明工作进程的出口 IP 不在白名单中;请告知用户将 errmsg 中显示的 IP 加入白名单后重试。
  • 发布需要已认证账号:截至 2025-07,只有已认证的企业主体账号才能调用 freepublish/* 和 mass/*。个人主体账号和未认证的企业账号会收到权限错误。草稿(draft/*)和客服消息(message/custom/*)通常无需认证即可使用。
  • 群发额度严格:服务号每月可发送 4 次,订阅号每天可发送 1 次。请将 freepublish/submit 和 mass/sendall 视为破坏性操作——调用前务必先征得用户确认,即使用户指示“发布它”也不例外。默认创建草稿并粘贴草稿链接。
  • 客服消息窗口为 48 小时:message/custom/send 仅适用于最近 48 小时内与账号互动过的关注者的 openid。超过该时限会收到 errcode 45015。

操作步骤

步骤 0 — 获取 access_token(先执行此步骤,并缓存结果)
# Cache to /tmp so subsequent calls in the same session reuse it.
TOKEN_CACHE="/tmp/wx-mp-token-${WECHAT_APP_ID}.json"

# Reuse cached token if it's still valid (we conservatively refresh
# 5 minutes early to avoid edge-of-window failures).
NOW=$(date +%s)
if [ -f "$TOKEN_CACHE" ] && [ "$(jq -r '.exp_at // 0' "$TOKEN_CACHE")" -gt "$((NOW + 300))" ]; then
  WECHAT_ACCESS_TOKEN=$(jq -r '.access_token' "$TOKEN_CACHE")
else
  RESP=$(curl -sS "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${WECHAT_APP_ID}&secret=${WECHAT_APP_SECRET}")
  WECHAT_ACCESS_TOKEN=$(echo "$RESP" | jq -r '.access_token // empty')
  if [ -z "$WECHAT_ACCESS_TOKEN" ]; then
    echo "Failed to fetch access_token: $RESP" >&2
    exit 1
  fi
  EXPIRES=$(echo "$RESP" | jq -r '.expires_in // 7200')
  jq -nc --arg t "$WECHAT_ACCESS_TOKEN" --argjson e "$((NOW + EXPIRES))" \
    '{access_token:$t, exp_at:$e}' > "$TOKEN_CACHE"
fi
echo "OK token=${WECHAT_ACCESS_TOKEN:0:8}…"

如果响应为 {"errcode": 40164, ...}(IP 无效)或 {"errcode": 40013, ...}(appid 无效),请将该错误告知用户;这表示连接器配置本身有误,而非请求有误。

验证连接(复杂操作前务必先执行一次)
# Pull basic public-account self-info; cheapest call that proves the token works.
curl -sS "https://api.weixin.qq.com/cgi-bin/account/getaccountbasicinfo?access_token=${WECHAT_ACCESS_TOKEN}" | jq
上传用于文章正文中的图片(返回公开 URL)

此操作用于文章 HTML 中的 <img src="..."> 标签。返回的 url 由腾讯托管,可用于已发布的文章。

curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=${WECHAT_ACCESS_TOKEN}" \
  -F "media=@/path/to/your-image.jpg"
# → {"url": "http://mmbiz.qpic.cn/..."}

限制:≤ 1 MB,仅支持 JPG/PNG,此接口不限制上传数量。

上传缩略图(用作文章封面)
curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${WECHAT_ACCESS_TOKEN}&type=thumb" \
  -F "media=@/path/to/cover.jpg" | jq '{media_id, url}'
# → {"media_id": "MEDIA_ID", "url": "http://mmbiz.qpic.cn/..."}

返回的 media_id 将在创建草稿时作为 thumb_media_id 传入。 推荐封面尺寸:900×500 px,JPG,最好小于 64 KB。

创建草稿(安全默认选项——不要直接发布)
TITLE="Q1 product update"
AUTHOR="Acme Inc."
THUMB_MEDIA_ID="MEDIA_ID_FROM_PREVIOUS_STEP"
CONTENT_HTML='<p>欢迎关注我们的最新动态。</p><p><img src="http://mmbiz.qpic.cn/..."></p><p>更多内容请见底部「阅读原文」。</p>'
DIGEST="Q1 has been a wild ride — here's what shipped."
SOURCE_URL="https://example.com/q1-recap"   # optional; populates 「阅读原文」, leave empty to omit

PAYLOAD=$(jq -nc \
  --arg title       "$TITLE" \
  --arg author      "$AUTHOR" \
  --arg thumb       "$THUMB_MEDIA_ID" \
  --arg content     "$CONTENT_HTML" \
  --arg digest      "$DIGEST" \
  --arg source_url  "$SOURCE_URL" \
  '{articles: [{
    article_type: "news",
    title: $title,
    author: $author,
    thumb_media_id: $thumb,
    content: $content,
    digest: $digest,
    content_source_url: $source_url,
    need_open_comment: 0,
    only_fans_can_comment: 0
  }]}')

curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$PAYLOAD" | jq
# → {"media_id": "DRAFT_MEDIA_ID"}

告知用户:"草稿已创建。打开 https://mp.weixin.qq.com → 草稿箱进行审核,然后点击「发表」。"——这是最安全的方式,因为由用户在微信自己的界面中控制发布操作。

列出草稿(按最新到最旧排序)
curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw '{"offset": 0, "count": 20, "no_content": 1}' \
  | jq '.item[] | {media_id, update_time, title: .content.news_item[0].title}'
获取一篇草稿的完整内容
DRAFT_MEDIA_ID="..."
curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/draft/get?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" '{media_id: $m}')" | jq
更新现有草稿
curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/draft/update?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" --arg t "Updated title" --arg c "<p>new body</p>" --arg th "$THUMB_MEDIA_ID" '
    {media_id: $m, index: 0, articles: {
      title: $t, content: $c, thumb_media_id: $th
    }}')" | jq
将草稿发布到「发表记录」——破坏性操作,调用前须确认
# Eats one of the monthly publish slots. Always echo back to the user
# what's about to go live and require an explicit "yes" confirmation
# in conversation BEFORE invoking this.
curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$(jq -nc --arg m "$DRAFT_MEDIA_ID" '{media_id: $m}')" | jq
# → {"errcode": 0, "msg_data_id": ..., "publish_id": "..."}

随后轮询发布状态(发布为异步操作,约需 5-30 秒):

PUBLISH_ID="..."
curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/freepublish/get?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$(jq -nc --arg p "$PUBLISH_ID" '{publish_id: $p}')" | jq '{publish_status, fail_idx, article_id, article_url: .article_detail.item[0].article_url}'
# publish_status: 0 = success, 1 = publishing, 2 = original-check-failed,
#                 3 = failed, 4 = published-but-removed, 5 = unverified-removed

当 publish_status == 0 时,将 article_detail.item[0].article_url 返回给用户——这是规范的 https://mp.weixin.qq.com/s/... URL。

列出已发布文章
curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/freepublish/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw '{"offset": 0, "count": 20, "no_content": 1}' \
  | jq '.item[] | {article_id, update_time, title: .content.news_item[0].title, url: .content.news_item[0].url}'
发送客服消息(发送给单个关注者,须在 48 小时窗口内)
OPENID="oXXXXXXXXXXXXXXXXX"   # the recipient follower's openid
TEXT="Hi! Your subscription has been renewed. Thanks for sticking with us."

curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$(jq -nc --arg u "$OPENID" --arg t "$TEXT" '
    {touser: $u, msgtype: "text", text: {content: $t}}')" | jq

errcode 45015 表示接收者最近 48 小时内没有给该账号发送消息——请向用户说明这一点;API 无法解决此问题。

如需通过客服消息发送文章卡片,请将 msgtype 改为 "mpnews",并设置 mpnews: {media_id: "..."}(先使用 material/add_news 创建永久图文素材 media_id)。

拉取粉丝增长和阅读统计
BEGIN="2026-04-25"
END="2026-05-01"   # max 7-day window for getusersummary

# New / unsubscribed / cumulative followers per day
curl -sS -X POST \
  "https://api.weixin.qq.com/datacube/getusersummary?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$END" '{begin_date: $b, end_date: $e}')" \
  | jq '.list[] | {date: .ref_date, new: .new_user, lost: .cancel_user, source: .user_source}'

# Article reads per day (max 3-day window for getuserread)
curl -sS -X POST \
  "https://api.weixin.qq.com/datacube/getuserread?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$(jq -nc --arg b "$BEGIN" --arg e "$BEGIN" '{begin_date: $b, end_date: $e}')" | jq

datacube/* 需要已认证账号,并且该账号拥有数据立方权限。

管理底部菜单
# Read current menu
curl -sS "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=${WECHAT_ACCESS_TOKEN}" | jq

# Replace the menu (max 3 top-level buttons; each top-level button can have ≤ 5 sub-buttons)
MENU=$(jq -nc '{
  button: [
    {type: "click", name: "今日推荐", key: "DAILY_REC"},
    {name: "更多",
     sub_button: [
       {type: "view", name: "官网",   url: "https://example.com"},
       {type: "view", name: "联系我们", url: "https://example.com/contact"}
     ]}
  ]
}')
curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw "$MENU" | jq
# → {"errcode": 0, "errmsg": "ok"}
获取粉丝列表(通过 next_openid 分页)
NEXT=""
curl -sS "https://api.weixin.qq.com/cgi-bin/user/get?access_token=${WECHAT_ACCESS_TOKEN}&next_openid=${NEXT}" \
  | jq '{total, count, openids: .data.openid, next: .next_openid}'
# Loop: pass the returned `next_openid` as NEXT until count < 10000.

对于每个 openid,批量获取个人资料(每次最多 100 个):

curl -sS -X POST \
  "https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=${WECHAT_ACCESS_TOKEN}" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data-raw '{"user_list": [{"openid": "OPENID1", "lang": "zh_CN"}]}' \
  | jq '.user_info_list[] | {openid, nickname, subscribe_time, tagid_list}'

文章 content 的 HTML 规则

draft/add 中的 content 字段只接受 部分 HTML,而不是完整的网页 HTML:

  • 允许使用:<p>、<span>、<strong>、<em>、<a href>、<img src>、<br>、<h1>–<h3>、<ul>/<ol>/<li>、<blockquote>、<section>。
  • 仅限内联样式(<p style="...">)——不允许使用 <style> 块,也不能通过 <link> 引用外部 CSS。
  • 所有 <img> 的 src URL 必须是之前上传的 mmbiz.qpic.cn URL(来自 media/uploadimg)或已发布的 mmbiz URL。微信会移除托管在第三方域名上的图片。
  • 内容总长度限制:约 20 K 个字符(包括 HTML)。

常见 errcode 速查表

errcode含义告诉用户的内容
0成功—
40001access_token 无效调用过程中 Token 已过期;清空 $TOKEN_CACHE 后重试
40013appid 无效连接器中的 AppID 有误——请重新添加连接
40164源 IP 不在白名单中将 errmsg 中显示的 IP 添加到公众平台 → 设置与开发 → 基本配置 → IP白名单
41001缺少 access_tokenBug——忘记传入 ?access_token=...
45009API 每日配额已超限请明天再试;已达到该应用的每日配额
45015回复消息超出 48 小时此 openid 的客服窗口已关闭;无法通过 API 恢复
48001API 未获授权账号没有此端点的权限(例如,未认证就发布);请参阅 errmsg 中的文档 URL
61450系统错误腾讯端暂时不稳定;等待 1 秒后重试一次

为什么使用裸 curl + jq(而不是 SDK)

技能正文必须是自包含的 shell。沙箱不允许调用 pip install wechatpy,而且此处的 API 数量较少(约 15 个端点),自 2018 年以来一直保持稳定。Python SDK(wechatpy、werobot)可用作参数格式的参考,但上面的每个示例都是直接调用文档中说明的端点——SDK 只是对相同 JSON 的轻量封装。

评论

支持 Markdown