feishu_fabu/modules/feishu_api.py
wp-publish-bot 1fb93e34c6 feat: 初始化 WordPress 自动发布系统(飞书机器人集成)
- 飞书消息接收与处理(文字、图片、Word 文档)
- WordPress REST API 文章发布
- 图片自动上传到媒体库
- Word 文档解析与发布
- HTML 格式化与分类自动匹配
- Python CLI 工具(避免 shell 引号冲突)
- Webhook 服务器(8080 端口)
- 完整日志系统
2026-05-12 15:09:30 +08:00

290 lines
9.7 KiB
Python
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
# -*- coding: utf-8 -*-
"""
飞书 API 客户端模块
提供飞书图片、文件下载功能
"""
import os
import json
import base64
import logging
import requests
from datetime import datetime
# 添加项目根目录到 Python 路径
import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, BASE_DIR)
logger = logging.getLogger('feishu_api')
class FeishuAPIClient:
"""飞书 API 客户端"""
def __init__(self, app_id=None, app_secret=None):
"""
初始化飞书 API 客户端
Args:
app_id: 飞书应用 App ID
app_secret: 飞书应用 App Secret
"""
# 加载配置
self.config = self._load_config()
self.app_id = app_id or self.config.get('FEISHU_APP_ID', '')
self.app_secret = app_secret or self.config.get('FEISHU_APP_SECRET', '')
self.api_base = 'https://open.feishu.cn/open-apis'
self.access_token = None
self.token_expires_at = 0
logger.info(f"🔑 飞书 API 客户端初始化 - App ID: {self.app_id[:10]}...")
def _load_config(self):
"""加载配置"""
try:
from feishu_config import FEISHU_APP_ID, FEISHU_APP_SECRET
return {'FEISHU_APP_ID': FEISHU_APP_ID, 'FEISHU_APP_SECRET': FEISHU_APP_SECRET}
except ImportError:
return {}
def get_access_token(self):
"""
获取访问令牌
Returns:
str: 访问令牌
"""
# 检查令牌是否过期
if self.access_token and datetime.now().timestamp() < self.token_expires_at:
return self.access_token
try:
url = f'{self.api_base}/auth/v3/tenant_access_token/internal'
response = requests.post(
url,
json={
'app_id': self.app_id,
'app_secret': self.app_secret
},
timeout=30
)
if response.status_code == 200:
result = response.json()
if result.get('code') == 0:
self.access_token = result.get('tenant_access_token', '')
self.token_expires_at = datetime.now().timestamp() + result.get('expire', 7200) - 300
logger.info("✅ 获取飞书访问令牌成功")
return self.access_token
else:
logger.error(f"获取飞书令牌失败:{result.get('msg', '未知错误')}")
return None
else:
logger.error(f"获取飞书令牌失败 - HTTP {response.status_code}")
return None
except Exception as e:
logger.error(f"获取飞书令牌异常:{str(e)}")
return None
def download_image(self, image_key, save_dir=None):
"""
下载飞书图片
Args:
image_key: 图片 key
save_dir: 保存目录
Returns:
str: 图片本地路径
"""
if not image_key:
logger.warning("图片 key 为空")
return None
# 获取访问令牌
token = self.get_access_token()
if not token:
logger.error("无法获取飞书访问令牌")
return None
# 构建下载 URL
url = f'{self.api_base}/im/v1/images/{image_key}'
try:
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 200:
result = response.json()
if result.get('code') == 0:
image_data = result.get('image', '')
image_type = result.get('image_type', 'jpg')
# 生成文件名
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"feishu_image_{timestamp}.{image_type}"
# 保存目录
if not save_dir:
save_dir = os.path.join(BASE_DIR, 'temp')
os.makedirs(save_dir, exist_ok=True)
file_path = os.path.join(save_dir, filename)
# 保存图片base64 解码)
try:
image_bytes = base64.b64decode(image_data)
with open(file_path, 'wb') as f:
f.write(image_bytes)
logger.info(f"✅ 图片解码成功:{len(image_bytes)} 字节")
except Exception as e:
logger.error(f"图片解码失败:{str(e)}")
return None
logger.info(f"✅ 图片下载成功:{file_path}")
return file_path
else:
logger.error(f"下载图片失败:{result.get('msg', '未知错误')}")
return None
else:
logger.error(f"下载图片失败 - HTTP {response.status_code}")
return None
except Exception as e:
logger.error(f"下载图片异常:{str(e)}")
return None
def download_file(self, file_key, file_name, save_dir=None):
"""
下载飞书文件
Args:
file_key: 文件 key
file_name: 文件名
save_dir: 保存目录
Returns:
str: 文件本地路径
"""
if not file_key:
logger.warning("文件 key 为空")
return None
# 获取访问令牌
token = self.get_access_token()
if not token:
logger.error("无法获取飞书访问令牌")
return None
# 构建下载 URL
url = f'{self.api_base}/im/v1/files/{file_key}'
try:
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
response = requests.get(url, headers=headers, timeout=60)
if response.status_code == 200:
result = response.json()
if result.get('code') == 0:
file_data = result.get('file', '')
# 生成文件名
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"feishu_file_{timestamp}_{file_name}"
# 保存目录
if not save_dir:
save_dir = os.path.join(BASE_DIR, 'temp')
os.makedirs(save_dir, exist_ok=True)
file_path = os.path.join(save_dir, filename)
# 保存文件base64 解码)
try:
file_bytes = base64.b64decode(file_data)
with open(file_path, 'wb') as f:
f.write(file_bytes)
logger.info(f"✅ 文件解码成功:{len(file_bytes)} 字节")
except Exception as e:
logger.error(f"文件解码失败:{str(e)}")
return None
logger.info(f"✅ 文件下载成功:{file_path}")
return file_path
else:
logger.error(f"下载文件失败:{result.get('msg', '未知错误')}")
return None
else:
logger.error(f"下载文件失败 - HTTP {response.status_code}")
return None
except Exception as e:
logger.error(f"下载文件异常:{str(e)}")
return None
def get_message_images(self, message_id):
"""
获取消息中的图片列表
Args:
message_id: 消息 ID
Returns:
list: 图片列表
"""
token = self.get_access_token()
if not token:
return []
url = f'{self.api_base}/im/v1/messages/{message_id}/resources'
try:
headers = {
'Authorization': f'Bearer {token}'
}
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 200:
result = response.json()
if result.get('code') == 0:
return result.get('items', [])
else:
logger.error(f"获取消息资源失败:{result.get('msg', '未知错误')}")
return []
else:
logger.error(f"获取消息资源失败 - HTTP {response.status_code}")
return []
except Exception as e:
logger.error(f"获取消息资源异常:{str(e)}")
return []
def create_feishu_client(app_id=None, app_secret=None):
"""创建飞书 API 客户端实例"""
return FeishuAPIClient(app_id, app_secret)
if __name__ == '__main__':
# 测试
client = create_feishu_client()
token = client.get_access_token()
if token:
print(f"✅ 飞书 API 客户端初始化成功")
print(f" 访问令牌:{token[:20]}...")
else:
print("❌ 飞书 API 客户端初始化失败")