openclaw/test_scripts/send_feishu_message.py
2026-01-27 21:49:13 +08:00

414 lines
12 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
飞书群消息发送测试脚本
支持两种方式:
1. Webhook 方式(推荐,简单)
2. 飞书开放平台 API 方式
使用方法:
方式1 - Webhook:
python send_feishu_message.py --webhook "YOUR_WEBHOOK_URL" --text "测试消息"
方式2 - 开放平台 API:
python send_feishu_message.py --app-id "YOUR_APP_ID" --app-secret "YOUR_APP_SECRET" --chat-id "YOUR_CHAT_ID" --text "测试消息"
"""
import argparse
import base64
import hashlib
import hmac
import json
import sys
import time
from typing import Optional
import requests
def gen_sign(timestamp: int, secret: str) -> str:
"""
生成飞书 Webhook 签名
Args:
timestamp: 时间戳(秒)
secret: 签名密钥
Returns:
str: 生成的签名
"""
# 拼接 timestamp 和 secret
string_to_sign = '{}\n{}'.format(timestamp, secret)
hmac_code = hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest()
# 对结果进行 base64 处理
sign = base64.b64encode(hmac_code).decode('utf-8')
return sign
def send_webhook_message(
webhook_url: str,
message: str,
msg_type: str = "text",
secret: Optional[str] = None
) -> bool:
"""
通过 Webhook 发送消息到飞书群
Args:
webhook_url: 飞书群机器人的 Webhook URL
message: 要发送的消息内容
msg_type: 消息类型,支持 "text", "post", "interactive"
secret: 可选的签名密钥,如果提供则会添加签名验证
Returns:
bool: 发送是否成功
"""
if msg_type == "text":
payload = {
"msg_type": "text",
"content": {
"text": message
}
}
elif msg_type == "post":
# 富文本消息
payload = {
"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "通知",
"content": [
[
{
"tag": "text",
"text": message
}
]
]
}
}
}
}
else:
print(f"不支持的消息类型: {msg_type}")
return False
# 如果提供了 secret添加签名
if secret:
timestamp = int(time.time())
sign = gen_sign(timestamp, secret)
payload["timestamp"] = str(timestamp)
payload["sign"] = sign
print(f"已添加签名验证 (timestamp: {timestamp})")
try:
response = requests.post(
webhook_url,
json=payload,
headers={"Content-Type": "application/json"},
timeout=10
)
response.raise_for_status()
result = response.json()
if result.get("code") == 0:
print("✓ 消息发送成功!")
return True
else:
print(f"✗ 消息发送失败: {result}")
return False
except requests.exceptions.RequestException as e:
print(f"✗ 请求失败: {e}")
return False
except Exception as e:
print(f"✗ 发生错误: {e}")
return False
def get_tenant_access_token(app_id: str, app_secret: str) -> Optional[str]:
"""
获取飞书开放平台的 tenant_access_token
Args:
app_id: 应用的 App ID
app_secret: 应用的 App Secret
Returns:
str: tenant_access_token失败返回 None
"""
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
payload = {
"app_id": app_id,
"app_secret": app_secret
}
try:
response = requests.post(url, json=payload, timeout=10)
response.raise_for_status()
result = response.json()
if result.get("code") == 0:
return result.get("tenant_access_token")
else:
print(f"✗ 获取 token 失败: {result}")
return None
except Exception as e:
print(f"✗ 获取 token 时发生错误: {e}")
return None
def send_api_message(
app_id: str,
app_secret: str,
chat_id: str,
message: str,
msg_type: str = "text"
) -> bool:
"""
通过飞书开放平台 API 发送消息
Args:
app_id: 应用的 App ID
app_secret: 应用的 App Secret
chat_id: 群聊的 chat_id
message: 要发送的消息内容
msg_type: 消息类型,支持 "text", "post"
Returns:
bool: 发送是否成功
"""
# 获取 access token
token = get_tenant_access_token(app_id, app_secret)
if not token:
return False
# 发送消息
url = "https://open.feishu.cn/open-apis/im/v1/messages"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
if msg_type == "text":
content = {"text": message}
elif msg_type == "post":
content = {
"zh_cn": {
"title": "通知",
"content": [
[
{
"tag": "text",
"text": message
}
]
]
}
}
else:
print(f"不支持的消息类型: {msg_type}")
return False
payload = {
"receive_id": chat_id,
"msg_type": msg_type,
"content": json.dumps(content)
}
params = {"receive_id_type": "chat_id"}
try:
response = requests.post(
url,
headers=headers,
params=params,
json=payload,
timeout=10
)
response.raise_for_status()
result = response.json()
if result.get("code") == 0:
print("✓ 消息发送成功!")
return True
else:
print(f"✗ 消息发送失败: {result}")
return False
except Exception as e:
print(f"✗ 发送消息时发生错误: {e}")
return False
def list_chats(app_id: str, app_secret: str) -> bool:
"""
列出机器人加入的所有群聊
Args:
app_id: 应用的 App ID
app_secret: 应用的 App Secret
Returns:
bool: 是否成功获取列表
"""
# 获取 access token
token = get_tenant_access_token(app_id, app_secret)
if not token:
return False
# 获取群列表
url = "https://open.feishu.cn/open-apis/im/v1/chats"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
result = response.json()
if result.get("code") == 0:
chats = result.get("data", {}).get("items", [])
if not chats:
print("未找到任何群聊")
return True
print(f"\n找到 {len(chats)} 个群聊:\n")
for i, chat in enumerate(chats, 1):
chat_id = chat.get("chat_id", "N/A")
name = chat.get("name", "未命名")
description = chat.get("description", "")
print(f"{i}. 群名称: {name}")
print(f" Chat ID: {chat_id}")
if description:
print(f" 描述: {description}")
print()
return True
else:
print(f"✗ 获取群列表失败: {result}")
return False
except Exception as e:
print(f"✗ 获取群列表时发生错误: {e}")
return False
def main():
parser = argparse.ArgumentParser(
description="飞书群消息发送测试脚本",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
# 方式1: 使用 Webhook推荐
python send_feishu_message.py --webhook "https://open.feishu.cn/open-apis/bot/v2/hook/xxx" --text "测试消息"
# 使用 Webhook + 签名验证
python send_feishu_message.py --webhook "https://open.feishu.cn/open-apis/bot/v2/hook/xxx" --secret "YOUR_SECRET" --text "测试消息"
# 方式2: 使用开放平台 API
python send_feishu_message.py --app-id "cli_xxx" --app-secret "xxx" --chat-id "oc_xxx" --text "测试消息"
# 发送富文本消息
python send_feishu_message.py --webhook "https://open.feishu.cn/open-apis/bot/v2/hook/xxx" --text "测试消息" --type post
# 列出所有群聊(获取 Chat ID
python send_feishu_message.py --app-id "cli_xxx" --app-secret "xxx" --list-chats
"""
)
# Webhook 方式参数
parser.add_argument(
"--webhook",
help="飞书群机器人的 Webhook URL"
)
parser.add_argument(
"--secret",
help="Webhook 签名密钥(如果机器人配置了签名验证)"
)
# 开放平台 API 方式参数
parser.add_argument(
"--app-id",
help="飞书应用的 App ID"
)
parser.add_argument(
"--app-secret",
help="飞书应用的 App Secret"
)
parser.add_argument(
"--chat-id",
help="群聊的 chat_id"
)
# 通用参数
parser.add_argument(
"--text",
help="要发送的消息内容"
)
parser.add_argument(
"--type",
choices=["text", "post"],
default="text",
help="消息类型text(纯文本) 或 post(富文本),默认 text"
)
# 辅助功能
parser.add_argument(
"--list-chats",
action="store_true",
help="列出机器人加入的所有群聊(需要 --app-id 和 --app-secret"
)
args = parser.parse_args()
# 处理列出群聊功能
if args.list_chats:
if not (args.app_id and args.app_secret):
print("✗ 错误:使用 --list-chats 需要提供 --app-id 和 --app-secret")
sys.exit(1)
print("正在获取群聊列表...")
success = list_chats(args.app_id, args.app_secret)
sys.exit(0 if success else 1)
# 验证发送消息的参数
if not args.text:
print("✗ 错误:发送消息需要提供 --text 参数")
parser.print_help()
sys.exit(1)
# 验证参数
if args.webhook:
# Webhook 方式
print(f"使用 Webhook 方式发送消息...")
print(f"消息内容: {args.text}")
print(f"消息类型: {args.type}")
success = send_webhook_message(args.webhook, args.text, args.type, args.secret)
elif args.app_id and args.app_secret and args.chat_id:
# 开放平台 API 方式
print(f"使用开放平台 API 方式发送消息...")
print(f"消息内容: {args.text}")
print(f"消息类型: {args.type}")
success = send_api_message(
args.app_id,
args.app_secret,
args.chat_id,
args.text,
args.type
)
else:
print("✗ 错误:必须提供以下参数之一:")
print(" 1. --webhook (Webhook URL)")
print(" 2. --app-id, --app-secret, --chat-id (开放平台 API)")
parser.print_help()
sys.exit(1)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()