Files
aiagent/backend/app/services/renshenguo2_app_service.py

132 lines
5.1 KiB
Python
Raw Normal View History

"""人参果1号飞书应用 API 服务 — 通过人参果1号应用发送消息到用户"""
from __future__ import annotations
import json
import logging
import time
from typing import Optional
import httpx
from app.core.config import settings
logger = logging.getLogger(__name__)
_token_cache: dict = {"token": None, "expires_at": 0}
def _get_tenant_access_token() -> Optional[str]:
now = time.time()
if _token_cache["token"] and now < _token_cache["expires_at"] - 300:
return _token_cache["token"]
app_id = settings.RENSHENGUO2_APP_ID
app_secret = settings.RENSHENGUO2_APP_SECRET
if not app_id or not app_secret:
logger.warning("人参果1号应用未配置RENSHENGUO2_APP_ID / RENSHENGUO2_APP_SECRET")
return None
try:
with httpx.Client(timeout=10) as client:
resp = client.post(
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
json={"app_id": app_id, "app_secret": app_secret},
)
result = resp.json()
if resp.is_success and result.get("code") == 0:
token = result["tenant_access_token"]
expire = result.get("expire", 7200)
_token_cache["token"] = token
_token_cache["expires_at"] = now + expire
logger.info("人参果1号 tenant_access_token 获取成功")
return token
else:
logger.warning("人参果1号 token 获取失败: %s", result)
return None
except Exception as e:
logger.warning("人参果1号 token 获取异常: %s", e)
return None
def send_message_to_user(
open_id: str, title: str, content: str,
status: str = "info", detail_link: Optional[str] = None,
) -> bool:
token = _get_tenant_access_token()
if not token:
return False
color_map = {"success": "green", "failed": "red", "info": "blue"}
color = color_map.get(status, "blue")
elements = [{"tag": "markdown", "content": content}]
if detail_link:
elements.append({
"tag": "action",
"actions": [{"tag": "button", "text": {"tag": "plain_text", "content": "查看详情"}, "url": detail_link, "type": "default"}],
})
card = {
"config": {"wide_screen_mode": True},
"header": {"title": {"tag": "plain_text", "content": title}, "template": color},
"elements": elements,
}
try:
with httpx.Client(timeout=10) as client:
resp = client.post(
"https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id",
headers={"Authorization": f"Bearer {token}"},
json={"receive_id": open_id, "msg_type": "interactive", "content": json.dumps(card, ensure_ascii=False)},
)
result = resp.json()
if resp.is_success and result.get("code") == 0:
logger.info("人参果1号消息发送成功: open_id=%s title=%s", open_id[:20], title)
return True
else:
logger.warning("人参果1号消息发送失败: code=%s msg=%s", result.get("code"), result.get("msg"))
return False
except Exception as e:
logger.warning("人参果1号消息发送异常: %s", e)
return False
def download_image_from_feishu(message_id: str, image_key: str) -> Optional[bytes]:
"""从飞书下载图片内容使用人参果1号应用凭证"""
token = _get_tenant_access_token()
if not token:
return None
try:
with httpx.Client(timeout=30) as client:
resp = client.get(
f"https://open.feishu.cn/open-apis/im/v1/messages/{message_id}/resources/{image_key}",
headers={"Authorization": f"Bearer {token}"},
params={"type": "image"},
)
if resp.is_success and resp.content:
logger.info("人参果1号 飞书图片下载成功: image_key=%s size=%d",
image_key[:20], len(resp.content))
return resp.content
else:
result = resp.json() if resp.content else {}
logger.warning("人参果1号 图片下载失败: code=%s msg=%s",
result.get("code"), result.get("msg"))
return None
except Exception as e:
logger.warning("人参果1号 图片下载异常: %s", e)
return None
def send_plain_text(open_id: str, text: str) -> bool:
token = _get_tenant_access_token()
if not token:
return False
try:
with httpx.Client(timeout=10) as client:
resp = client.post(
"https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id",
headers={"Authorization": f"Bearer {token}"},
json={"receive_id": open_id, "msg_type": "text", "content": json.dumps({"text": text}, ensure_ascii=False)},
)
result = resp.json()
return resp.is_success and result.get("code") == 0
except Exception as e:
logger.warning("人参果1号文本消息发送异常: %s", e)
return False