mcpField
O
ops-whatsapp-biz

ops-whatsapp-biz

github.com/davepoon营销与增长3.7k79 次安装1 天前更新
代码仓库

关于 ops-whatsapp-biz

ops-whatsapp-biz 是用于管理 WhatsApp Business Cloud API 消息的技能,涵盖模板操作、凭据配置和关联商品目录管理;它与 wacli 的个人 WhatsApp 消息用途不同。

主要功能

  1. 01查看模板及审批状态
  2. 02发送已批准的模板消息
  3. 03创建模板并查询审批状态
  4. 04查看和管理关联目录
  5. 05配置并测试 API 凭据

适用场景

  1. 01查看消息模板审批情况
  2. 02向客户发送模板消息
  3. 03创建 WhatsApp Business 模板
  4. 04检查关联的商品目录

安装

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

$npx skills add https://github.com/davepoon/buildwithclaude --skill ops-whatsapp-biz

安装后在 Claude Code 中输入/ops-whatsapp-biz确认已加载。

详细介绍

OPS ► WHATSAPP BUSINESS

WhatsApp Business Cloud API — 与个人 WhatsApp 的 wacli 不同。

主要区别: 此技能使用 WhatsApp Business Cloud API(Meta Graph API)进行大规模企业对客户消息发送。wacli 用于个人 WhatsApp 消息发送。两者的凭据、电话号码和使用场景均不同。

凭据解析

PREFS_PATH="${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json"

# WhatsApp Business credentials (separate from wacli personal)
WABA_TOKEN="${WHATSAPP_BUSINESS_TOKEN:-$(claude plugin config get whatsapp_business_token 2>/dev/null)}"
WABA_PHONE_ID="${WHATSAPP_PHONE_NUMBER_ID:-$(claude plugin config get whatsapp_phone_number_id 2>/dev/null)}"
WABA_ACCOUNT_ID="${WHATSAPP_BUSINESS_ACCOUNT_ID:-$(claude plugin config get whatsapp_business_account_id 2>/dev/null)}"

# Doppler fallback
if [ -z "$WABA_TOKEN" ]; then
  WABA_TOKEN="$(doppler secrets get WHATSAPP_BUSINESS_TOKEN --plain 2>/dev/null)"
fi
if [ -z "$WABA_PHONE_ID" ]; then
  WABA_PHONE_ID="$(doppler secrets get WHATSAPP_PHONE_NUMBER_ID --plain 2>/dev/null)"
fi
if [ -z "$WABA_ACCOUNT_ID" ]; then
  WABA_ACCOUNT_ID="$(doppler secrets get WHATSAPP_BUSINESS_ACCOUNT_ID --plain 2>/dev/null)"
fi

凭据检查:如果 WABA_TOKEN 或 WABA_PHONE_ID 为空,则打印: WhatsApp Business 未配置。运行 /ops:whatsapp-biz setup 以配置凭据。 然后停止。


子命令路由

路由 $ARGUMENTS:

输入操作
(空)、list-templates列出所有模板及其审批状态
send-template向一个或多个收件人发送已获批的模板消息
create-template引导式模板创建向导
check-template <NAME>查询特定模板的审批状态
catalog查看和管理已关联的产品目录
setup配置 WhatsApp Business API 凭据

list-templates

列出 WhatsApp Business 账户的所有消息模板。

RESULT=$(curl -s "https://graph.facebook.com/v20.0/${WABA_ACCOUNT_ID}/message_templates?fields=name,status,category,language,components&limit=50" \
  -H "Authorization: Bearer ${WABA_TOKEN}")

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  WHATSAPP BUSINESS TEMPLATES"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
printf "| %-30s | %-10s | %-12s | %-8s |\n" "Template Name" "Status" "Category" "Language"
printf "|%s|%s|%s|%s|\n" "--------------------------------" "------------" "--------------" "----------"

echo "$RESULT" | jq -r '.data[]? | [.name, .status, .category, .language] | @tsv' 2>/dev/null | \
  while IFS=$'\t' read -r name status category language; do
    STATUS_ICON="○"
    [ "$status" = "APPROVED" ] && STATUS_ICON="✓"
    [ "$status" = "REJECTED" ] && STATUS_ICON="✗"
    [ "$status" = "PENDING" ] && STATUS_ICON="…"
    printf "| %-30s | %s %-9s | %-12s | %-8s |\n" "${name:0:30}" "$STATUS_ICON" "$status" "${category:0:12}" "$language"
  done

TOTAL=$(echo "$RESULT" | jq '.data | length // 0')
APPROVED=$(echo "$RESULT" | jq '[.data[]? | select(.status == "APPROVED")] | length')
echo ""
echo "Total: ${TOTAL} templates  |  Approved: ${APPROVED}"
echo ""
echo "Note: Per-template pricing applies since July 2025. Check Meta pricing page for current rates."

send-template

向一个或多个收件人发送已获批的模板消息。

发送前,通过 AskUserQuestion 收集:

  1. 模板名称(自由文本——先使用 list-templates 查看可用模板)
  2. 收件人电话号码——自由文本(单个号码或以逗号分隔的号码列表,包含国家代码,例如 +14155552671)

如果模板包含变量,通过 AskUserQuestion 逐一询问每个参数值(自由文本)。

费用说明:发送前始终显示预估费用。实用工具模板:约 $0.005/条消息;营销模板:约 $0.025/条消息(美国费率,因国家/地区而异)。向超过 10 位收件人批量发送前,使用 AskUserQuestion 请求确认。

# Parse recipients
IFS=',' read -ra RECIPIENTS <<< "$PHONE_NUMBERS"
RECIPIENT_COUNT=${#RECIPIENTS[@]}

# For bulk sends > 10, confirm
if [ $RECIPIENT_COUNT -gt 10 ]; then
  # AskUserQuestion: "Send to ${RECIPIENT_COUNT} recipients? Estimated cost: ~$$(awk "BEGIN {printf \"%.2f\", $RECIPIENT_COUNT * 0.025}") for marketing templates." options [Send, Cancel]
  [ "$CONFIRM" = "Cancel" ] && echo "Cancelled." && exit 0
fi

# Send to each recipient
SUCCESS=0; FAILED=0
for PHONE in "${RECIPIENTS[@]}"; do
  PHONE=$(echo "$PHONE" | tr -d ' ')
  
  RESP=$(curl -s -X POST "https://graph.facebook.com/v20.0/${WABA_PHONE_ID}/messages" \
    -H "Authorization: Bearer ${WABA_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "{
      \"messaging_product\": \"whatsapp\",
      \"to\": \"${PHONE}\",
      \"type\": \"template\",
      \"template\": {
        \"name\": \"${TEMPLATE_NAME}\",
        \"language\": {\"code\": \"en_US\"},
        \"components\": ${TEMPLATE_COMPONENTS_JSON:-[]}
      }
    }")
  
  MSG_ID=$(echo "$RESP" | jq -r '.messages[0].id // empty')
  if [ -n "$MSG_ID" ]; then
    SUCCESS=$((SUCCESS + 1))
  else
    FAILED=$((FAILED + 1))
    ERR=$(echo "$RESP" | jq -r '.error.message // "unknown error"')
    echo "  Failed for ${PHONE}: ${ERR}"
  fi
done

echo ""
echo "Sent: ${SUCCESS}/${RECIPIENT_COUNT} messages delivered"
[ $FAILED -gt 0 ] && echo "Failed: ${FAILED} — check phone number format (+country_code + number)"

模板组件格式(根据用户输入构建 TEMPLATE_COMPONENTS_JSON):

对于包含标题和正文变量的模板:

[
  {
    "type": "header",
    "parameters": [{"type": "text", "text": "{{header_value}}"}]
  },
  {
    "type": "body",
    "parameters": [
      {"type": "text", "text": "{{var1}}"},
      {"type": "text", "text": "{{var2}}"}
    ]
  }
]

对于不包含变量的模板:TEMPLATE_COMPONENTS_JSON=[]


create-template

引导式模板创建向导。

第 1 步——收集基本信息:

AskUserQuestion:“模板类别?”选项 [MARKETING, UTILITY, AUTHENTICATION, More...]

如果选择 More:AskUserQuestion:[AUTHENTICATION, UTILITY, Skip]

第 2 步——AskUserQuestion:“模板名称(仅限小写字母和下划线,例如 order_confirmation):”(自由文本)

第 3 步——AskUserQuestion:“语言?”选项 [en_US, en_GB, es, fr]

第 4 步——收集正文文本(自由文本)。提示用户:“使用 {{1}}、{{2}} 等作为变量。”

第 5 步——AskUserQuestion:“添加标题?”选项 [Text header, Image header, No header, Video header]

第 6 步——AskUserQuestion:“添加行动号召按钮?”选项 [URL button, Phone button, No button, Quick reply]

# Build components array
COMPONENTS='[{"type":"BODY","text":"'"${BODY_TEXT}"'"}]'

# Add header if selected
if [ "$HEADER_TYPE" = "Text header" ]; then
  HEADER_TEXT_INPUT="..."  # collected via AskUserQuestion
  COMPONENTS=$(echo "$COMPONENTS" | jq '. + [{"type":"HEADER","format":"TEXT","text":"'"${HEADER_TEXT_INPUT}"'"}]')
elif [ "$HEADER_TYPE" = "Image header" ]; then
  COMPONENTS=$(echo "$COMPONENTS" | jq '. + [{"type":"HEADER","format":"IMAGE","example":{"header_handle":["<upload_handle>"]}}]')
fi

# Add button if selected
if [ "$BUTTON_TYPE" = "URL button" ]; then
  BUTTON_URL="..."  # collected via AskUserQuestion: "Button URL:"
  BUTTON_TEXT="..."  # collected via AskUserQuestion: "Button text:"
  COMPONENTS=$(echo "$COMPONENTS" | jq '. + [{"type":"BUTTONS","buttons":[{"type":"URL","text":"'"${BUTTON_TEXT}"'","url":"'"${BUTTON_URL}"'"}]}]')
elif [ "$BUTTON_TYPE" = "Phone button" ]; then
  BUTTON_PHONE="..."  # collected via AskUserQuestion: "Phone number for button:"
  BUTTON_TEXT="..."
  COMPONENTS=$(echo "$COMPONENTS" | jq '. + [{"type":"BUTTONS","buttons":[{"type":"PHONE_NUMBER","text":"'"${BUTTON_TEXT}"'","phone_number":"'"${BUTTON_PHONE}"'"}]}]')
fi

RESP=$(curl -s -X POST "https://graph.facebook.com/v20.0/${WABA_ACCOUNT_ID}/message_templates" \
  -H "Authorization: Bearer ${WABA_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"${TEMPLATE_NAME}\",
    \"category\": \"${CATEGORY}\",
    \"language\": \"${LANGUAGE}\",
    \"components\": ${COMPONENTS}
  }")

TEMPLATE_ID=$(echo "$RESP" | jq -r '.id // empty')
STATUS=$(echo "$RESP" | jq -r '.status // empty')

if [ -n "$TEMPLATE_ID" ]; then
  echo "Template submitted (ID: ${TEMPLATE_ID}, status: ${STATUS})."
  echo "Approval typically takes 24-48 hours. Check status with: /ops:whatsapp-biz check-template ${TEMPLATE_NAME}"
else
  echo "Template creation failed: $(echo "$RESP" | jq -r '.error.message // "unknown error"')"
fi

check-template <NAME>

按名称查询特定模板的审批状态。

TEMPLATE_NAME_ARG=$(echo "$ARGUMENTS" | awk '{print $2}')
if [ -z "$TEMPLATE_NAME_ARG" ]; then
  # AskUserQuestion: "Template name to check:" (free text)
  TEMPLATE_NAME_ARG="$USER_INPUT"
fi

RESULT=$(curl -s "https://graph.facebook.com/v20.0/${WABA_ACCOUNT_ID}/message_templates?name=${TEMPLATE_NAME_ARG}&fields=name,status,rejected_reason,quality_score" \
  -H "Authorization: Bearer ${WABA_TOKEN}")

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  TEMPLATE STATUS: ${TEMPLATE_NAME_ARG}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

echo "$RESULT" | jq -r '.data[]? | "Status: \(.status)\nQuality: \(.quality_score.score // "N/A")\nRejection reason: \(.rejected_reason // "—")"' 2>/dev/null

STATUS=$(echo "$RESULT" | jq -r '.data[0].status // "NOT_FOUND"')
case "$STATUS" in
  APPROVED)  echo "Template is ready to use with send-template." ;;
  REJECTED)  echo "Template was rejected. Review the rejection reason and create a revised template." ;;
  PENDING)   echo "Template is pending review (typically 24-48h)." ;;
  NOT_FOUND) echo "Template '${TEMPLATE_NAME_ARG}' not found. Run list-templates to see available templates." ;;
esac

catalog

查看和管理已关联到 WhatsApp Business 账户的产品目录。

# List linked catalogs
CATALOGS=$(curl -s "https://graph.facebook.com/v20.0/${WABA_ACCOUNT_ID}?fields=catalog_id" \
  -H "Authorization: Bearer ${WABA_TOKEN}")
CATALOG_ID=$(echo "$CATALOGS" | jq -r '.catalog_id // empty')

if [ -z "$CATALOG_ID" ]; then
  echo "No product catalog linked to this WhatsApp Business Account."
  echo "To link a catalog: go to Meta Business Manager → Commerce Manager → link your catalog to WhatsApp."
  exit 0
fi

# List products in catalog
PRODUCTS=$(curl -s "https://graph.facebook.com/v20.0/${CATALOG_ID}/products?fields=id,name,retailer_id,price,availability,url&limit=20" \
  -H "Authorization: Bearer ${WABA_TOKEN}")

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  WHATSAPP PRODUCT CATALOG"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Catalog ID: ${CATALOG_ID}"
echo ""
printf "| %-25s | %-12s | %-10s | %-12s |\n" "Product Name" "SKU" "Price" "Availability"
printf "|%s|%s|%s|%s|\n" "---------------------------" "--------------" "------------" "--------------"
echo "$PRODUCTS" | jq -r '.data[]? | [.name, (.retailer_id // "—"), (.price // "—"), (.availability // "—")] | @tsv' 2>/dev/null | \
  while IFS=$'\t' read -r name sku price avail; do
    printf "| %-25s | %-12s | %-10s | %-12s |\n" "${name:0:25}" "${sku:0:12}" "$price" "$avail"
  done

PRODUCT_COUNT=$(echo "$PRODUCTS" | jq '.data | length // 0')
echo ""
echo "Total products shown: ${PRODUCT_COUNT} (use catalog in message templates with product cards)"

MM Lite 说明:Marketing Messages Lite(AI 优化发送)截至 2026 年仍是 Beta API。此处尚未实现——请查阅 Meta 开发者文档,了解当前可用性和注册要求。


设置

配置 WhatsApp Business API 凭据。

询问之前,自动扫描是否已有凭据:

# Scan env vars
printenv WHATSAPP_BUSINESS_TOKEN WHATSAPP_PHONE_NUMBER_ID WHATSAPP_BUSINESS_ACCOUNT_ID 2>/dev/null

# Scan Doppler
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
  for cfg in dev stg prd; do
    doppler secrets --project "$proj" --config "$cfg" --json 2>/dev/null | \
      jq -r --arg p "$proj" --arg c "$cfg" 'to_entries[] | select(.key | test("WHATSAPP_BUSINESS|WHATSAPP_PHONE"; "i")) | "\(.key)=\(.value.computed) (doppler:\($p)/\($c))"'
  done
done

# Shell profiles
grep -h 'WHATSAPP_BUSINESS\|WHATSAPP_PHONE' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'

# Existing plugin config
claude plugin config get whatsapp_business_token 2>/dev/null && echo "✓ whatsapp_business_token already configured"
claude plugin config get whatsapp_phone_number_id 2>/dev/null && echo "✓ whatsapp_phone_number_id already configured"
claude plugin config get whatsapp_business_account_id 2>/dev/null && echo "✓ whatsapp_business_account_id already configured"

如果未找到凭据,请引导用户:

  1. AskUserQuestion:「你的 WhatsApp Business 凭据在哪里?」选项 [现在粘贴令牌, 在 Meta 控制面板中查找, 跳过]

在 Meta 中查找凭据的方法:

  • WHATSAPP_BUSINESS_TOKEN:Meta Developer Portal → 你的应用 → WhatsApp → API Setup → 临时访问令牌(或生成永久的 System User 令牌)
  • WHATSAPP_PHONE_NUMBER_ID:同一页面 →「From」电话号码 → Phone Number ID
  • WHATSAPP_BUSINESS_ACCOUNT_ID:Meta Business Manager → Business Settings → WhatsApp Accounts → Account ID
  1. 通过 AskUserQuestion 收集各项值(自由文本):

    • WhatsApp Business 令牌
    • Phone Number ID
    • Business Account ID(WABA ID)
  2. 通过插件配置保存:

claude plugin config set whatsapp_business_token "$WABA_TOKEN"
claude plugin config set whatsapp_phone_number_id "$WABA_PHONE_ID"
claude plugin config set whatsapp_business_account_id "$WABA_ACCOUNT_ID"
  1. 冒烟测试:
TEST=$(curl -s "https://graph.facebook.com/v20.0/${WABA_PHONE_ID}" \
  -H "Authorization: Bearer ${WABA_TOKEN}")
NAME=$(echo "$TEST" | jq -r '.display_phone_number // empty')
if [ -n "$NAME" ]; then
  echo "WhatsApp Business ✓ connected — Phone: ${NAME}"
else
  echo "WhatsApp Business ✗ connection failed: $(echo "$TEST" | jq -r '.error.message // "invalid token or phone ID"')"
fi

报告:WhatsApp Business ✓ 已连接 或 ✗ 无效 — [error]。

评论

支持 Markdown