开发飞书消息技能必要性    

在数字化协同与智能办公深化的当下,飞书已成为企业信息流转核心枢纽,承载内部协作、客户对接、业务监控等关键沟通需求。OpenClaw 作为多场景智能体开发框架,与飞书消息能力的结合,已从“可选功能”升级为提升效率、打通业务闭环、降低协同成本的“必备技能”。对AI开发者而言,开发该技能就是为智能体搭建高效信息输出通道,无需跨平台操作,即可通过OpenClaw驱动飞书,实现客户响应、定时推送、异常预警、智能回复等场景落地,摆脱重复操作内耗,避免沟通延迟损耗。依托飞书开放平台2500+标准化API的底层支撑,结合OpenClaw便捷的技能扩展能力,无需深耕复杂接口开发,就能快速适配多场景消息推送,这也是其成为OpenClaw高频开发需求的核心原因,更是连接技术与业务、实现“技术赋能效率”的关键桥梁。

第一章:基础认知——飞书消息发送技能核心

1.1 技能定义:

        OpenClaw飞书消息发送技能的核心的是,通过OpenClaw驱动飞书API,实现多场景消息推送的智能技能。

1.2 核心优势:

        相较于原生飞书消息及其他开发框架,其核心价值在于简洁集成、灵活扩展,可快速适配多业务场景。

1.3 开发前提:

        完成OpenClaw框架部署,以及飞书开放平台账号注册、机器人创建与配置。

1.4 核心依赖:

        解析飞书消息API核心接口,明确消息类型、调用规范及所需权限,为后续开发奠定基础。

第二章:开发前置——飞书开放平台与 OpenClaw 环境配置

参考https://www.feishu.cn/content/article/7602519239445974205 完成下面四项配置

2.1 飞书开放平台操作:

创建应用、开启机器人消息发送能力,配置发送消息、通讯录等核心权限。

2.2 密钥配置:

        获取tenant_access_token、app_secret,做好加密存储,规避安全风险。

2.3 OpenClaw环境适配:

        配置技能目录,安装飞书SDK等依赖包,完成框架与飞书的初步对接。

2.4 连通测试:

        发送简单文本消息,验证环境配置正确性,确保API调用正常。

第三章:核心开发——OpenClaw 飞书消息发送技能编码实现

3.1 技能创建

1.通过init_skill.py创建feishu-messenger的技能

scripts/init_skill.py feishu-messenger --path skills/public --resources scripts,references

2、SKILL.md编写

---
name: feishu-messenger
description: 飞书消息自动发送工具,支持通过自建应用 API 发送纯文本、富文本、卡片、文件、图片、视频消息。使用当需要:(1) 向个人或群聊发送通知,(2) 发送富文本或交互式卡片,(3) 上传发送文件/图片/视频,(4) 批量发送消息,(5) 定时或事件触发消息推送
---

# 飞书消息发送技能

## 前置准备

### 1. 创建飞书自建应用

1. 访问 [飞书开放平台](https://open.feishu.cn/app)
2. 点击「创建企业自建应用」
3. 填写应用名称和图标
4. 发布应用获取 `App ID` 和 `App Secret`

### 2. 配置应用权限

在「权限管理」中添加以下权限:

- `message:send_as_bot` - 以机器人身份发送消息
- `im:message` - 发送消息
- `im:chat` - 获取群组信息
- `contact:user:readonly` - 获取用户信息(可选,用于通过手机号/邮箱查找用户)

### 3. 启用机器人

- 进入「功能配置」→「机器人」
- 启用机器人功能
- 将机器人添加到需要发送消息的群聊

### 4. 配置凭证

在 `TOOLS.md` 中记录你的飞书应用凭证:

```markdown
### Feishu

- App ID: `cli_xxxxxxxxxxxxx`
- App Secret: `xxxxxxxxxxxxx`
- 默认接收人:(可选)
```

或在环境变量中设置:
- `FEISHU_APP_ID`
- `FEISHU_APP_SECRET`

## 使用方法

### 发送文本消息

```bash
python scripts/send_message.py \
  --app-id "cli_xxx" \
  --app-secret "xxx" \
  --receiver "ou_xxx" \
  --content "Hello, 飞书!" \
  --msg-type "text"
```

### 发送富文本

```bash
python scripts/send_message.py \
  --app-id "cli_xxx" \
  --app-secret "xxx" \
  --receiver "ou_xxx" \
  --content-json '{"elements": [{"tag": "text", "text": "Hello"}]}' \
  --msg-type "post"
```

### 发送卡片消息

```bash
python scripts/send_card.py \
  --app-id "cli_xxx" \
  --app-secret "xxx" \
  --receiver "ou_xxx" \
  --template "interactive" \
  --title "通知标题" \
  --content "卡片内容"
```

### 发送文件/图片/视频

```bash
# 发送图片
python scripts/send_file.py \
  --app-id "cli_xxx" \
  --app-secret "xxx" \
  --receiver "ou_xxx" \
  --file-path "/path/to/image.png" \
  --file-type "image"

# 发送文件
python scripts/send_file.py \
  --app-id "cli_xxx" \
  --app-secret "xxx" \
  --receiver "ou_xxx" \
  --file-path "/path/to/document.pdf" \
  --file-type "file"
```

## 接收人 ID 类型

飞书支持多种接收人标识:

| 类型 | 前缀 | 说明 |
|------|------|------|
| 用户 ID | `ou_` | 推荐,通过 API 获取 |
| 群聊 ID | `oc_` | 群会话 ID |
| 手机号 | `tel:` | 需开启通讯录权限 |
| 邮箱 | `email:` | 需开启通讯录权限 |
| 机器人 | `bot:` | 发送给其他机器人 |

## 消息类型说明

### text - 纯文本
简单文本消息,支持 `@` 用户(需在 content 中添加 `@user_id`)

### post - 富文本
支持标题、多语言、文本样式(加粗、链接等)

### interactive - 交互卡片
支持按钮、表单、模板等交互元素

### image - 图片
需先上传获取 `image_key`

### file - 文件
支持各种文件格式,需先上传获取 `file_key`

### share_chat - 分享群聊
分享指定群聊

### share_user - 分享名片
分享用户名片

## 脚本说明

- **scripts/send_message.py** - 通用消息发送(文本/富文本)
- **scripts/send_card.py** - 卡片消息发送(支持多种模板)
- **scripts/send_file.py** - 文件/图片/视频上传发送
- **references/api_docs.md** - 完整 API 参考文档
- **references/card_templates.md** - 卡片模板示例库

## 常见问题

### 获取用户/群聊 ID

使用飞书 API 查询:

```bash
# 获取用户 ID(通过邮箱)
curl -X GET "https://open.feishu.cn/open-apis/contact/v3/users/find_by_department?dept_name=xxx" \
  -H "Authorization: Bearer <access_token>"

# 获取群聊 ID
curl -X GET "https://open.feishu.cn/open-apis/im/v1/chats" \
  -H "Authorization: Bearer <access_token>"
```

### 权限错误

确保:
1. 应用已发布(开发版本只能发给开发者)
2. 机器人已添加到目标群聊
3. 权限已正确配置并生效

### 消息发送失败

检查:
1. access_token 是否过期(有效期 2 小时)
2. 接收人 ID 是否正确
3. 消息格式是否符合规范

## 安全提示

- 不要将 `App Secret` 提交到代码仓库
- 生产环境使用环境变量存储凭证
- 定期轮换应用密钥
- 限制应用的权限范围到最小必需
3.2 发送消息

1、文本消息

#!/usr/bin/env python3
"""
飞书消息发送脚本 - 支持文本和富文本消息
"""

import argparse
import json
import requests
import sys
from typing import Optional


def get_access_token(app_id: str, app_secret: str) -> str:
    """获取飞书 access_token"""
    url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    payload = {
        "app_id": app_id,
        "app_secret": app_secret
    }
    
    response = requests.post(url, json=payload)
    data = response.json()
    
    if data.get("code") != 0:
        raise Exception(f"获取 access_token 失败:{data.get('msg', '未知错误')}")
    
    return data["tenant_access_token"]


def send_text_message(
    access_token: str,
    receiver: str,
    content: str,
    msg_type: str = "text",
    receive_id_type: str = "open_id",
    uuid: Optional[str] = None
) -> dict:
    """发送消息"""
    url = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id"
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    # 根据消息类型构建 content
    if msg_type == "text":
        content_json = {"text": content}
    elif msg_type == "post":
        # 富文本格式
        content_json = json.loads(content) if isinstance(content, str) else content
    else:
        content_json = json.loads(content) if isinstance(content, str) else content
    
    payload = {
        "receive_id": receiver,
        "msg_type": msg_type,
        "content": json.dumps(content_json),
        "receive_id_type": receive_id_type
    }
    
    if uuid:
        payload["uuid"] = uuid
    
    response = requests.post(url, headers=headers, json=payload)
    data = response.json()
    
    return data


def main():
    parser = argparse.ArgumentParser(description="飞书消息发送工具")
    
    parser.add_argument("--app-id", required=True, help="飞书应用 App ID")
    parser.add_argument("--app-secret", required=True, help="飞书应用 App Secret")
    parser.add_argument("--receiver", required=True, help="接收人 ID (open_id/user_id/手机号/邮箱)")
    parser.add_argument("--content", required=True, help="消息内容")
    parser.add_argument("--msg-type", default="text", 
                       choices=["text", "post", "interactive", "image", "file", "audio", "media", "sticker"],
                       help="消息类型")
    parser.add_argument("--receive-id-type", default="open_id",
                       choices=["open_id", "user_id", "union_id", "email", "chat_id"],
                       help="接收人 ID 类型")
    parser.add_argument("--uuid", help="消息唯一标识(用于去重)")
    parser.add_argument("--json-output", action="store_true", help="输出 JSON 格式结果")
    
    args = parser.parse_args()
    
    try:
        # 获取 access_token
        if not args.json_output:
            print("正在获取 access_token...")
        
        access_token = get_access_token(args.app_id, args.app_secret)
        
        # 发送消息
        if not args.json_output:
            print(f"正在发送 {args.msg_type} 消息给 {args.receiver}...")
        
        result = send_text_message(
            access_token=access_token,
            receiver=args.receiver,
            content=args.content,
            msg_type=args.msg_type,
            receive_id_type=args.receive_id_type,
            uuid=args.uuid
        )
        
        if args.json_output:
            print(json.dumps(result, ensure_ascii=False, indent=2))
        else:
            if result.get("code") == 0:
                print(f"✅ 消息发送成功!")
                print(f"消息 ID: {result.get('data', {}).get('message_id', 'N/A')}")
            else:
                print(f"❌ 消息发送失败:{result.get('msg', '未知错误')}")
                print(f"错误码:{result.get('code', 'N/A')}")
                sys.exit(1)
                
    except Exception as e:
        if args.json_output:
            print(json.dumps({"error": str(e)}, ensure_ascii=False))
        else:
            print(f"❌ 错误:{e}")
        sys.exit(1)


if __name__ == "__main__":
    main()

2、发送图片消息

#!/usr/bin/env python3
"""
飞书文件发送脚本 - 支持图片、文件、视频上传发送
"""

import argparse
import json
import requests
import sys
import os
from typing import Optional


def get_access_token(app_id: str, app_secret: str) -> str:
    """获取飞书 access_token"""
    url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    payload = {
        "app_id": app_id,
        "app_secret": app_secret
    }
    
    response = requests.post(url, json=payload)
    data = response.json()
    
    if data.get("code") != 0:
        raise Exception(f"获取 access_token 失败:{data.get('msg', '未知错误')}")
    
    return data["tenant_access_token"]


def upload_file(
    access_token: str,
    file_path: str,
    file_type: str = "file"
) -> str:
    """
    上传文件到飞书
    返回 file_key 或 image_key
    """
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"文件不存在:{file_path}")
    
    # 根据文件类型选择上传接口
    if file_type == "image":
        url = "https://open.feishu.cn/open-apis/im/v1/images"
        key_field = "image_key"
    elif file_type == "video":
        url = "https://open.feishu.cn/open-apis/im/v1/videos"
        key_field = "video_key"
    else:  # file
        url = "https://open.feishu.cn/open-apis/im/v1/files"
        key_field = "file_key"
    
    headers = {
        "Authorization": f"Bearer {access_token}"
    }
    
    with open(file_path, "rb") as f:
        files = {
            "image" if file_type == "image" else "file": f
        }
        response = requests.post(url, headers=headers, files=files)
    
    data = response.json()
    
    if data.get("code") != 0:
        raise Exception(f"上传文件失败:{data.get('msg', '未知错误')}")
    
    return data["data"][key_field]


def send_image_message(
    access_token: str,
    receiver: str,
    image_key: str,
    receive_id_type: str = "open_id"
) -> dict:
    """发送图片消息"""
    url = "https://open.feishu.cn/open-apis/im/v1/messages"
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    content = {
        "image_key": image_key
    }
    
    payload = {
        "receive_id": receiver,
        "msg_type": "image",
        "content": json.dumps(content),
        "receive_id_type": receive_id_type
    }
    
    response = requests.post(url, headers=headers, json=payload)
    return response.json()


def send_file_message(
    access_token: str,
    receiver: str,
    file_key: str,
    file_name: str,
    receive_id_type: str = "open_id"
) -> dict:
    """发送文件消息"""
    url = "https://open.feishu.cn/open-apis/im/v1/messages"
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    content = {
        "file_key": file_key,
        "file_name": file_name
    }
    
    payload = {
        "receive_id": receiver,
        "msg_type": "file",
        "content": json.dumps(content),
        "receive_id_type": receive_id_type
    }
    
    response = requests.post(url, headers=headers, json=payload)
    return response.json()


def send_video_message(
    access_token: str,
    receiver: str,
    video_key: str,
    receive_id_type: str = "open_id"
) -> dict:
    """发送视频消息"""
    url = "https://open.feishu.cn/open-apis/im/v1/messages"
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    content = {
        "video_key": video_key
    }
    
    payload = {
        "receive_id": receiver,
        "msg_type": "media",
        "content": json.dumps(content),
        "receive_id_type": receive_id_type
    }
    
    response = requests.post(url, headers=headers, json=payload)
    return response.json()


def main():
    parser = argparse.ArgumentParser(description="飞书文件发送工具")
    
    parser.add_argument("--app-id", required=True, help="飞书应用 App ID")
    parser.add_argument("--app-secret", required=True, help="飞书应用 App Secret")
    parser.add_argument("--receiver", required=True, help="接收人 ID")
    parser.add_argument("--file-path", required=True, help="文件路径")
    parser.add_argument("--file-type", default="file",
                       choices=["file", "image", "video"],
                       help="文件类型")
    parser.add_argument("--file-name", help="文件名(可选,默认使用原文件名)")
    parser.add_argument("--receive-id-type", default="open_id",
                       choices=["open_id", "user_id", "union_id", "email", "chat_id"],
                       help="接收人 ID 类型")
    parser.add_argument("--json-output", action="store_true", help="输出 JSON 格式结果")
    
    args = parser.parse_args()
    
    try:
        # 获取 access_token
        if not args.json_output:
            print("正在获取 access_token...")
        
        access_token = get_access_token(args.app_id, args.app_secret)
        
        # 上传文件
        if not args.json_output:
            print(f"正在上传 {args.file_type}:{args.file_path}...")
        
        file_key = upload_file(
            access_token=access_token,
            file_path=args.file_path,
            file_type=args.file_type
        )
        
        if not args.json_output:
            print(f"✅ 上传成功,key: {file_key[:20]}...")
            print(f"正在发送消息...")
        
        # 发送消息
        if args.file_type == "image":
            result = send_image_message(
                access_token=access_token,
                receiver=args.receiver,
                image_key=file_key,
                receive_id_type=args.receive_id_type
            )
        elif args.file_type == "video":
            result = send_video_message(
                access_token=access_token,
                receiver=args.receiver,
                video_key=file_key,
                receive_id_type=args.receive_id_type
            )
        else:  # file
            file_name = args.file_name or os.path.basename(args.file_path)
            result = send_file_message(
                access_token=access_token,
                receiver=args.receiver,
                file_key=file_key,
                file_name=file_name,
                receive_id_type=args.receive_id_type
            )
        
        if args.json_output:
            print(json.dumps(result, ensure_ascii=False, indent=2))
        else:
            if result.get("code") == 0:
                print(f"✅ 文件发送成功!")
                print(f"消息 ID: {result.get('data', {}).get('message_id', 'N/A')}")
            else:
                print(f"❌ 文件发送失败:{result.get('msg', '未知错误')}")
                print(f"错误码:{result.get('code', 'N/A')}")
                sys.exit(1)
                
    except FileNotFoundError as e:
        if args.json_output:
            print(json.dumps({"error": str(e)}, ensure_ascii=False))
        else:
            print(f"❌ 错误:{e}")
        sys.exit(1)
    except Exception as e:
        if args.json_output:
            print(json.dumps({"error": str(e)}, ensure_ascii=False))
        else:
            print(f"❌ 错误:{e}")
        sys.exit(1)


if __name__ == "__main__":
    main()

3、发送图文消息

#!/usr/bin/env python3
"""
飞书卡片消息发送脚本 - 支持交互式卡片模板
"""

import argparse
import json
import requests
import sys
from typing import Optional, Dict, Any


def get_access_token(app_id: str, app_secret: str) -> str:
    """获取飞书 access_token"""
    url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    payload = {
        "app_id": app_id,
        "app_secret": app_secret
    }
    
    response = requests.post(url, json=payload)
    data = response.json()
    
    if data.get("code") != 0:
        raise Exception(f"获取 access_token 失败:{data.get('msg', '未知错误')}")
    
    return data["tenant_access_token"]


def build_interactive_card(
    title: str,
    content: str,
    color: str = "blue",
    buttons: Optional[list] = None
) -> Dict[str, Any]:
    """构建交互式卡片"""
    card = {
        "config": {
            "wide_screen_mode": True
        },
        "header": {
            "title": {
                "tag": "plain_text",
                "content": title
            },
            "template": color
        },
        "elements": [
            {
                "tag": "div",
                "text": {
                    "tag": "lark_md",
                    "content": content
                }
            }
        ]
    }
    
    if buttons:
        card["elements"].append({
            "tag": "action",
            "actions": buttons
        })
    
    return card


def build_notification_card(
    title: str,
    content: str,
    status: str = "info"
) -> Dict[str, Any]:
    """构建通知卡片"""
    status_colors = {
        "success": "green",
        "warning": "orange",
        "error": "red",
        "info": "blue"
    }
    
    status_emojis = {
        "success": "✅",
        "warning": "⚠️",
        "error": "❌",
        "info": "ℹ️"
    }
    
    return {
        "config": {
            "wide_screen_mode": True
        },
        "header": {
            "title": {
                "tag": "plain_text",
                "content": f"{status_emojis.get(status, 'ℹ️')} {title}"
            },
            "template": status_colors.get(status, "blue")
        },
        "elements": [
            {
                "tag": "div",
                "text": {
                    "tag": "lark_md",
                    "content": content
                }
            }
        ]
    }


def build_markdown_card(markdown_content: str) -> Dict[str, Any]:
    """构建 Markdown 卡片"""
    return {
        "config": {
            "wide_screen_mode": True
        },
        "elements": [
            {
                "tag": "markdown",
                "content": markdown_content
            }
        ]
    }


def send_card_message(
    access_token: str,
    receiver: str,
    card_content: Dict[str, Any],
    receive_id_type: str = "open_id"
) -> dict:
    """发送卡片消息"""
    url = "https://open.feishu.cn/open-apis/im/v1/messages"
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "receive_id": receiver,
        "msg_type": "interactive",
        "content": json.dumps(card_content),
        "receive_id_type": receive_id_type
    }
    
    response = requests.post(url, headers=headers, json=payload)
    data = response.json()
    
    return data


def main():
    parser = argparse.ArgumentParser(description="飞书卡片消息发送工具")
    
    parser.add_argument("--app-id", required=True, help="飞书应用 App ID")
    parser.add_argument("--app-secret", required=True, help="飞书应用 App Secret")
    parser.add_argument("--receiver", required=True, help="接收人 ID")
    parser.add_argument("--template", default="interactive",
                       choices=["interactive", "notification", "markdown"],
                       help="卡片模板类型")
    parser.add_argument("--title", help="卡片标题(interactive/notification 模板)")
    parser.add_argument("--content", help="卡片内容")
    parser.add_argument("--markdown", help="Markdown 内容(markdown 模板)")
    parser.add_argument("--color", default="blue",
                       choices=["blue", "red", "orange", "green", "grey", "purple"],
                       help="卡片头部颜色")
    parser.add_argument("--status", default="info",
                       choices=["info", "success", "warning", "error"],
                       help="通知状态(notification 模板)")
    parser.add_argument("--receive-id-type", default="open_id",
                       choices=["open_id", "user_id", "union_id", "email", "chat_id"],
                       help="接收人 ID 类型")
    parser.add_argument("--json-output", action="store_true", help="输出 JSON 格式结果")
    
    args = parser.parse_args()
    
    try:
        # 获取 access_token
        if not args.json_output:
            print("正在获取 access_token...")
        
        access_token = get_access_token(args.app_id, args.app_secret)
        
        # 构建卡片内容
        if args.template == "interactive":
            if not args.title or not args.content:
                raise ValueError("interactive 模板需要 --title 和 --content 参数")
            
            buttons = []
            if args.json_output:
                # 从 stdin 读取按钮配置
                import sys
                try:
                    button_config = json.load(sys.stdin)
                    buttons = button_config.get("buttons", [])
                except:
                    pass
            
            card_content = build_interactive_card(
                title=args.title,
                content=args.content,
                color=args.color,
                buttons=buttons
            )
            
        elif args.template == "notification":
            if not args.title or not args.content:
                raise ValueError("notification 模板需要 --title 和 --content 参数")
            
            card_content = build_notification_card(
                title=args.title,
                content=args.content,
                status=args.status
            )
            
        elif args.template == "markdown":
            if not args.markdown:
                raise ValueError("markdown 模板需要 --markdown 参数")
            
            card_content = build_markdown_card(args.markdown)
        
        # 发送消息
        if not args.json_output:
            print(f"正在发送 {args.template} 卡片给 {args.receiver}...")
        
        result = send_card_message(
            access_token=access_token,
            receiver=args.receiver,
            card_content=card_content,
            receive_id_type=args.receive_id_type
        )
        
        if args.json_output:
            print(json.dumps(result, ensure_ascii=False, indent=2))
        else:
            if result.get("code") == 0:
                print(f"✅ 卡片发送成功!")
                print(f"消息 ID: {result.get('data', {}).get('message_id', 'N/A')}")
            else:
                print(f"❌ 卡片发送失败:{result.get('msg', '未知错误')}")
                print(f"错误码:{result.get('code', 'N/A')}")
                sys.exit(1)
                
    except Exception as e:
        if args.json_output:
            print(json.dumps({"error": str(e)}, ensure_ascii=False))
        else:
            print(f"❌ 错误:{e}")
        sys.exit(1)


if __name__ == "__main__":
    main()
3.3 添加references

1、api_docs.md

# 飞书 API 参考文档

## 基础信息

### API 域名
- 生产环境:`https://open.feishu.cn`
- 测试环境:`https://open.feishu.cn`

### 请求格式
- Content-Type: `application/json`
- 认证方式:`Authorization: Bearer <access_token>`

---

## 认证 API

### 获取应用 Access Token(内部应用)

**请求**
```http
POST /open-apis/auth/v3/tenant_access_token/internal
Content-Type: application/json

{
  "app_id": "cli_xxxxx",
  "app_secret": "xxxxx"
}
```

**响应**
```json
{
  "code": 0,
  "msg": "success",
  "tenant_access_token": "t-xxxxxxxxxxxx",
  "expire": 7200
}
```

**说明**
- token 有效期 2 小时
- 需要缓存,过期前刷新

---

## 消息 API

### 发送消息

**请求**
```http
POST /open-apis/im/v1/messages
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "receive_id": "ou_xxxxx",
  "msg_type": "text",
  "content": "{\"text\": \"Hello\"}",
  "receive_id_type": "open_id"
}
```

**参数说明**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| receive_id | string | 是 | 接收者 ID |
| msg_type | string | 是 | 消息类型:text/post/interactive/image/file/audio/media/sticker/share_chat/share_user |
| content | string | 是 | 消息内容(JSON 字符串) |
| receive_id_type | string | 是 | ID 类型:open_id/user_id/union_id/email/chat_id |
| uuid | string | 否 | 消息唯一标识(用于去重) |

**响应**
```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "message_id": "om_xxxxx"
  }
}
```

### 回复消息

**请求**
```http
POST /open-apis/im/v1/messages
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "receive_id": "ou_xxxxx",
  "msg_type": "text",
  "content": "{\"text\": \"Re: Hello\"}",
  "receive_id_type": "open_id",
  "reply_id": "om_xxxxx"
}
```

---

## 消息类型格式

### text - 纯文本

```json
{
  "text": "Hello @ou_xxxxx"
}
```

支持 @ 用户,格式:`@user_id`

### post - 富文本

```json
{
  "zh_cn": {
    "title": "标题",
    "content": [
      [
        {
          "tag": "text",
          "text": "普通文本"
        },
        {
          "tag": "a",
          "text": "链接文本",
          "href": "https://example.com"
        },
        {
          "tag": "at",
          "user_id": "ou_xxxxx",
          "text": "@用户"
        }
      ],
      [
        {
          "tag": "text",
          "text": "第二行"
        }
      ]
    ]
  }
}
```

**支持的标签**
- `text` - 普通文本
- `a` - 链接(需 href)
- `at` - @用户(需 user_id)
- `img` - 图片(需 image_key)
- `media` - 媒体
- `i` - 斜体

### interactive - 交互卡片

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "标题"
    },
    "template": "blue"
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "tag": "lark_md",
        "content": "**加粗** _斜体_ [链接](url)"
      }
    },
    {
      "tag": "action",
      "actions": [
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "按钮"
          },
          "type": "primary",
          "url": "https://example.com"
        }
      ]
    }
  ]
}
```

**卡片元素类型**
- `div` - 文本区域
- `markdown` - Markdown 内容
- `img` - 图片
- `action` - 按钮组
- `hr` - 分割线
- `note` - 备注

**按钮类型**
- `primary` - 主按钮
- `default` - 默认按钮
- `danger` - 危险按钮

### image - 图片

```json
{
  "image_key": "img_xxxxx"
}
```

需先上传图片获取 image_key

### file - 文件

```json
{
  "file_key": "file_xxxxx",
  "file_name": "document.pdf"
}
```

需先上传文件获取 file_key

### media - 视频

```json
{
  "video_key": "video_xxxxx"
}
```

需先上传视频获取 video_key

---

## 文件上传 API

### 上传图片

**请求**
```http
POST /open-apis/im/v1/images
Authorization: Bearer <access_token>
Content-Type: multipart/form-data

file: [图片文件]
```

**响应**
```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "image_key": "img_xxxxx"
  }
}
```

### 上传文件

**请求**
```http
POST /open-apis/im/v1/files
Authorization: Bearer <access_token>
Content-Type: multipart/form-data

file: [文件]
```

**响应**
```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "file_key": "file_xxxxx"
  }
}
```

### 上传视频

**请求**
```http
POST /open-apis/im/v1/videos
Authorization: Bearer <access_token>
Content-Type: multipart/form-data

file: [视频文件]
```

**响应**
```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "video_key": "video_xxxxx"
  }
}
```

---

## 通讯录 API

### 获取用户 ID(通过邮箱)

**请求**
```http
GET /open-apis/contact/v3/users/find_by_department?dept_name=xxx&user_id_type=open_id
Authorization: Bearer <access_token>
```

### 获取用户信息

**请求**
```http
GET /open-apis/contact/v3/users/:user_id?user_id_type=open_id
Authorization: Bearer <access_token>
```

### 获取部门列表

**请求**
```http
GET /open-apis/contact/v3/department?user_id_type=open_id
Authorization: Bearer <access_token>
```

---

## 群聊 API

### 获取群聊列表

**请求**
```http
GET /open-apis/im/v1/chats?page_size=50
Authorization: Bearer <access_token>
```

### 获取群聊详情

**请求**
```http
GET /open-apis/im/v1/chats/:chat_id
Authorization: Bearer <access_token>
```

### 创建群聊

**请求**
```http
POST /open-apis/im/v1/chats
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "name": "群名称",
  "avatar": "头像 URL",
  "owner_id": "ou_xxxxx",
  "user_ids": ["ou_xxx1", "ou_xxx2"]
}
```

---

## 错误码

| 错误码 | 说明 |
|--------|------|
| 0 | 成功 |
| 99991663 | app access token 无效 |
| 99991665 | 没有权限 |
| 10011 | 参数错误 |
| 99991661 | 应用未发布 |
| 99991668 | 用户不存在 |
| 99991669 | 消息类型不支持 |

---

## 最佳实践

### 1. Token 管理
```python
# 缓存 token,过期前刷新
token_cache = {
    "token": None,
    "expire_at": 0
}

def get_cached_token():
    if time.time() < token_cache["expire_at"] - 300:
        return token_cache["token"]
    
    token = fetch_new_token()
    token_cache["token"] = token
    token_cache["expire_at"] = time.time() + 7200
    return token
```

### 2. 消息去重
使用 uuid 参数防止重复发送:
```python
import uuid
message_uuid = str(uuid.uuid4())
```

### 3. 批量发送
控制频率,避免触发限流:
```python
import time
for receiver in receivers:
    send_message(receiver)
    time.sleep(0.1)  # 100ms 间隔
```

### 4. 错误处理
```python
def send_with_retry(func, max_retries=3):
    for i in range(max_retries):
        try:
            return func()
        except Exception as e:
            if i == max_retries - 1:
                raise
            time.sleep(2 ** i)  # 指数退避
```

2、card_templates.md

# 飞书卡片模板示例库

## 通知类卡片

### 成功通知

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "✅ 部署成功"
    },
    "template": "green"
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "tag": "lark_md",
        "content": "**服务**: user-service\n**环境**: production\n**版本**: v1.2.3\n**时间**: 2024-01-15 10:30:00"
      }
    }
  ]
}
```

### 警告通知

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "⚠️ 内存使用率过高"
    },
    "template": "orange"
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "tag": "lark_md",
        "content": "**服务器**: prod-01\n**当前使用**: 85%\n**阈值**: 80%\n\n请检查是否有内存泄漏或考虑扩容。"
      }
    },
    {
      "tag": "action",
      "actions": [
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "查看监控"
          },
          "type": "default",
          "url": "https://grafana.example.com"
        }
      ]
    }
  ]
}
```

### 错误通知

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "❌ 服务异常"
    },
    "template": "red"
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "tag": "lark_md",
        "content": "**服务**: payment-service\n**错误**: Connection timeout\n**影响**: 支付功能不可用\n**开始时间**: 2024-01-15 10:30:00"
      }
    },
    {
      "tag": "action",
      "actions": [
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "查看日志"
          },
          "type": "danger",
          "url": "https://logs.example.com"
        },
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "重启服务"
          },
          "type": "primary",
          "url": "https://k8s.example.com/restart"
        }
      ]
    }
  ]
}
```

---

## 日报/周报卡片

### 日报模板

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "📅 工作日报 - 2024-01-15"
    },
    "template": "blue"
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "tag": "lark_md",
        "content": "**今日完成**\n• 完成用户登录功能开发\n• 修复支付接口 bug\n• Code review 3 个 PR\n\n**明日计划**\n• 开始订单模块开发\n• 参加技术分享会\n\n**问题与风险**\n• 无"
      }
    }
  ]
}
```

### 周报模板

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "📊 周报 - 第 3 周 (01.15-01.19)"
    },
    "template": "purple"
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "tag": "lark_md",
        "content": "**本周重点**\n1. 用户系统重构完成\n2. 性能优化:接口响应时间降低 40%\n3. 新增单元测试覆盖率 15%\n\n**数据指标**\n- 完成需求:5 个\n- 修复 bug: 12 个\n- Code review: 8 个\n\n**下周计划**\n- 订单模块开发\n- 技术文档完善"
      }
    }
  ]
}
```

---

## 审批/确认卡片

### 审批请求

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "📝 审批申请 - 采购申请"
    },
    "template": "blue"
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "tag": "lark_md",
        "content": "**申请人**: 张三\n**部门**: 技术部\n**金额**: ¥5,000\n**用途**: 购买开发设备\n**说明**: MacBook Pro 用于新项目开发"
      }
    },
    {
      "tag": "action",
      "actions": [
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "同意"
          },
          "type": "primary",
          "url": "https://approval.example.com/approve/123"
        },
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "拒绝"
          },
          "type": "danger",
          "url": "https://approval.example.com/reject/123"
        }
      ]
    }
  ]
}
```

### 会议确认

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "header": {
    "title": {
      "tag": "plain_text",
      "content": "📅 会议邀请 - 项目评审会"
    },
    "template": "blue"
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "tag": "lark_md",
        "content": "**时间**: 2024-01-16 14:00-15:00\n**地点**: 会议室 A / 飞书会议\n**主持人**: 李四\n**议程**:\n1. 项目进度汇报\n2. 技术方案评审\n3. 风险讨论"
      }
    },
    {
      "tag": "action",
      "actions": [
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "参加"
          },
          "type": "primary",
          "url": "https://meeting.feishu.cn/xxx"
        },
        {
          "tag": "button",
          "text": {
            "tag": "plain_text",
            "content": "拒绝"
          },
          "type": "default"
        }
      ]
    }
  ]
}
```

---

## Markdown 卡片

### 代码片段

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "elements": [
    {
      "tag": "markdown",
      "content": "## 错误日志\n\n```python\nTraceback (most recent call last):\n  File \"main.py\", line 42, in <module>\n    result = process_data()\n  File \"utils.py\", line 15, in process_data\n    raise ConnectionError(\"Database timeout\")\n```\n\n**建议**: 检查数据库连接状态"
    }
  ]
}
```

### 数据报表

```json
{
  "config": {
    "wide_screen_mode": true
  },
  "elements": [
    {
      "tag": "markdown",
      "content": "# 今日数据概览\n\n| 指标 | 数值 | 环比 |\n|------|------|------|\n| PV | 125,432 | +5.2% |\n| UV | 45,678 | +3.1% |\n| 转化率 | 2.34% | +0.12% |\n| GMV | ¥234,567 | +8.7% |\n\n**亮点**: GMV 创历史新高 🎉"
    }
  ]
}
```

---

## 使用示例

### Python 调用

```python
from send_card import send_card_message, get_access_token, build_notification_card

# 获取 token
token = get_access_token("cli_xxx", "secret")

# 构建通知卡片
card = build_notification_card(
    title="部署成功",
    content="服务 user-service 已成功部署到 production 环境",
    status="success"
)

# 发送
result = send_card_message(
    access_token=token,
    receiver="ou_xxxxx",
    card_content=card
)
```

### 命令行调用

```bash
# 发送成功通知
python scripts/send_card.py \
  --app-id "cli_xxx" \
  --app-secret "secret" \
  --receiver "ou_xxx" \
  --template "notification" \
  --title "部署成功" \
  --content "服务已成功部署" \
  --status "success"

# 发送 Markdown 卡片
python scripts/send_card.py \
  --app-id "cli_xxx" \
  --app-secret "secret" \
  --receiver "ou_xxx" \
  --template "markdown" \
  --markdown "# 日报\n\n今日完成:\n- 功能 A\n- 功能 B"
```

---

## 模板颜色说明

| 颜色 | 用途 |
|------|------|
| blue | 普通信息、默认 |
| green | 成功、完成 |
| orange | 警告、注意 |
| red | 错误、危险 |
| grey | 中性、次要信息 |
| purple | 重要、特别通知 |

3.4 打包skill

scripts/package_skill.py skills/public/feishu-messenger

3.4 配置密钥

在 TOOLS.md 中添加:

### Feishu

- App ID: `xxxx`

- App Secret: `xxxxxx`

- 默认接收人:`xxxxx`


或设置环境变量:

export FEISHU_APP_ID="cli_xxx"

export FEISHU_APP_SECRET="xxx"

export FEISHU_RECEIVER="ou_xxx"

3.5  调试

1、发送消息:通过飞书测试发送消息

2、发送通知卡片

3、发送图片消息

第四章:场景落地——四大核心应用场景实操

OpenClaw飞书消息发送技能的核心价值,在于将技术能力转化为实际业务效能,以下四大场景为企业高频需求,将详细拆解应用场景、实操逻辑、核心代码片段及落地优势,帮助开发者快速将技能落地到业务中,实现“技术赋能业务”的核心目标。

4.1 场景一:客户对接场景——自动推送客户跟进提醒、业务通知

        客户对接是企业核心业务环节,传统模式下,销售人员需手动记录客户跟进节点、手动发送业务通知,不仅效率低下,还易出现遗漏、延迟等问题,影响客户体验。基于OpenClaw飞书消息发送技能,可实现客户对接全流程消息自动化推送,覆盖客户跟进提醒、业务进度通知、需求反馈同步等核心场景,助力销售人员高效跟进客户,提升客户满意度。

核心应用场景包括:新客户添加后自动推送欢迎消息及业务介绍;客户跟进节点(如首次沟通、需求确认、合同签订)触发提醒,推送至对应销售人员飞书账号;客户需求反馈、投诉等信息,自动同步至销售主管及相关负责人;业务成交后,自动推送成交通知至销售团队及财务部门,打通客户对接闭环。

4.2 场景二:定时消息场景——每日/每周定时推送报表、会议提醒、工作复盘

        企业日常办公中,存在大量需要定时推送的消息场景,如每日业务报表推送、每周工作复盘通知、会议提醒、考勤统计通知等,传统模式下需专人负责定时发送,耗时耗力且易出现忘记推送、推送不及时等问题。基于OpenClaw飞书消息发送技能,结合OpenClaw定时任务能力,可实现各类消息的自动化定时推送,无需人工干预,确保信息按时、准确传递,提升办公协同效率。

核心应用场景包括:每日早上9点推送前一日业务数据报表(如销售额、订单量、活跃用户数)至管理层及相关业务部门;每周一早上推送本周会议安排、每周五下午推送本周工作复盘通知至全员;每月月底推送月度业绩总结、下月工作规划至各部门;特定时间(如会议前10分钟)推送会议提醒至参会人员,避免遗漏会议。

4.3 场景三:自动回复场景——关键词触发回复、用户咨询自动响应

        企业在飞书使用过程中,经常面临大量重复的咨询需求,如员工咨询考勤规则、报销流程、系统使用方法,外部客户咨询产品价格、业务办理流程等,人工回复不仅效率低下,还易出现回复不统一、不及时的问题。基于OpenClaw飞书消息发送技能,可实现关键词触发自动回复、用户咨询智能响应,快速解答高频咨询问题,减轻人工回复压力,提升咨询响应效率和体验。

核心应用场景包括:员工在飞书私聊机器人或群聊中发送关键词(如“考勤规则”“报销流程”),自动回复对应详细说明;外部客户咨询“产品价格”“办理流程”,自动推送产品介绍、价格表、办理指引;群聊中发送“会议纪要”,自动推送最新会议纪要链接;针对常见问题(如“密码重置”“系统登录问题”),自动回复解决方法,无法解决的问题自动转接至对应负责人。

4.4 场景四:监控预警场景——业务异常、系统故障实时推送预警消息

        企业业务系统、服务器、数据库等核心资产的稳定运行,是业务正常开展的基础,一旦出现异常(如系统故障、数据异常、业务指标异常),若不能及时发现和处理,可能导致业务中断、数据丢失、经济损失等严重后果。基于OpenClaw飞书消息发送技能,可对接各类监控系统,实现异常情况实时推送预警消息,让相关负责人第一时间知晓异常、处理异常,降低异常带来的损失,保障业务稳定运行。

核心应用场景包括:服务器负载过高、内存溢出、宕机时,实时推送预警消息至运维人员;数据库连接异常、数据丢失、查询超时,推送预警至技术开发人员;业务指标异常(如销售额骤降、订单量异常波动、用户流失率过高),推送预警至管理层及业务负责人;系统接口调用失败、接口响应超时,推送预警至开发人员,及时排查问题。参考华住集团告警解决方案,可实现预警消息分级推送、异常闭环处理,提升监控预警效能。

第五章:总结与规划

        本章总结OpenClaw飞书消息发送技能开发全流程,核心是依托OpenClaw简化飞书API集成,实现多场景消息自动化推送,落地四大核心场景。落地需规范权限与密钥管理、控制消息推送频率、适配业务迭代并做好监控复盘。后续可拓展消息接收与智能交互、飞书群聊及联系人管理技能,结合行业需求开发定制化功能,深化OpenClaw与飞书生态融合,打造更全面的智能协同解决方案。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐