124 lines
5.2 KiB
Python
124 lines
5.2 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
飞书机器人蓝图
|
|||
|
|
处理来自飞书机器人的事件回调
|
|||
|
|
"""
|
|||
|
|
import logging
|
|||
|
|
import json
|
|||
|
|
import threading
|
|||
|
|
from flask import Blueprint, request, jsonify
|
|||
|
|
from src.integrations.feishu_service import FeishuService
|
|||
|
|
from src.web.service_manager import service_manager
|
|||
|
|
|
|||
|
|
# 初始化日志
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
# 创建蓝图
|
|||
|
|
feishu_bot_bp = Blueprint('feishu_bot', __name__, url_prefix='/api/feishu/bot')
|
|||
|
|
|
|||
|
|
# 在模块级别实例化飞书服务,以便复用
|
|||
|
|
# 注意:这假设配置在启动时是固定的。如果配置可热更新,则需要调整。
|
|||
|
|
feishu_service = FeishuService()
|
|||
|
|
|
|||
|
|
def _process_message_in_background(app_context, event_data: dict):
|
|||
|
|
"""
|
|||
|
|
在后台线程中处理消息,避免阻塞飞书的回调请求。
|
|||
|
|
"""
|
|||
|
|
with app_context:
|
|||
|
|
try:
|
|||
|
|
# 1. 解析事件数据
|
|||
|
|
message_id = event_data['event']['message']['message_id']
|
|||
|
|
chat_id = event_data['event']['message']['chat_id']
|
|||
|
|
# 内容是一个JSON字符串,需要再次解析
|
|||
|
|
content_json = json.loads(event_data['event']['message']['content'])
|
|||
|
|
text_content = content_json.get('text', '').strip()
|
|||
|
|
|
|||
|
|
logger.info(f"[Feishu Bot] 后台开始处理消息ID: {message_id}, 内容: '{text_content}'")
|
|||
|
|
|
|||
|
|
# 2. 移除@机器人的部分
|
|||
|
|
# 飞书的@消息格式通常是 "@机器人名 实际内容"
|
|||
|
|
if event_data['event']['message'].get('mentions'):
|
|||
|
|
for mention in event_data['event']['message']['mentions']:
|
|||
|
|
# mention['key']是@内容,例如"@_user_1"
|
|||
|
|
# mention['name']是显示的名字
|
|||
|
|
bot_mention_text = f"@{mention['name']}"
|
|||
|
|
if text_content.startswith(bot_mention_text):
|
|||
|
|
text_content = text_content[len(bot_mention_text):].strip()
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
if not text_content:
|
|||
|
|
logger.warning(f"[Feishu Bot] 移除@后内容为空,不处理。消息ID: {message_id}")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
logger.info(f"[Feishu Bot] 清理后的消息内容: '{text_content}'")
|
|||
|
|
|
|||
|
|
# 3. 调用核心服务获取回复
|
|||
|
|
assistant = service_manager.get_assistant()
|
|||
|
|
# 注意:process_message_agent 是一个异步方法,需要处理
|
|||
|
|
# 在同步线程中运行异步方法
|
|||
|
|
import asyncio
|
|||
|
|
try:
|
|||
|
|
loop = asyncio.get_running_loop()
|
|||
|
|
except RuntimeError: # 'RuntimeError: There is no current event loop...'
|
|||
|
|
loop = asyncio.new_event_loop()
|
|||
|
|
asyncio.set_event_loop(loop)
|
|||
|
|
|
|||
|
|
# 调用对话服务
|
|||
|
|
logger.info(f"[Feishu Bot] 调用Agent服务处理消息...")
|
|||
|
|
response_data = loop.run_until_complete(
|
|||
|
|
assistant.process_message_agent(message=text_content, user_id=f"feishu_{chat_id}")
|
|||
|
|
)
|
|||
|
|
logger.info(f"[Feishu Bot] Agent服务返回结果: {response_data}")
|
|||
|
|
|
|||
|
|
# 4. 提取回复并发送
|
|||
|
|
reply_text = response_data.get("message", "抱歉,我暂时无法回答这个问题。")
|
|||
|
|
if isinstance(reply_text, dict): # 有时候返回的可能是字典
|
|||
|
|
reply_text = reply_text.get('content', str(reply_text))
|
|||
|
|
|
|||
|
|
logger.info(f"[Feishu Bot] 准备发送回复到飞书: '{reply_text}'")
|
|||
|
|
success = feishu_service.reply_to_message(message_id, reply_text)
|
|||
|
|
|
|||
|
|
if success:
|
|||
|
|
logger.info(f"[Feishu Bot] 成功回复消息到飞书。消息ID: {message_id}")
|
|||
|
|
else:
|
|||
|
|
logger.error(f"[Feishu Bot] 回复消息到飞书失败。消息ID: {message_id}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"[Feishu Bot] 后台处理消息时发生严重错误: {e}", exc_info=True)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@feishu_bot_bp.route('/event', methods=['POST'])
|
|||
|
|
def handle_feishu_event():
|
|||
|
|
"""
|
|||
|
|
接收并处理飞书事件回调
|
|||
|
|
"""
|
|||
|
|
# 1. 解析请求
|
|||
|
|
data = request.json
|
|||
|
|
logger.info(f"[Feishu Bot] 收到飞书事件回调:\n{json.dumps(data, indent=2)}")
|
|||
|
|
|
|||
|
|
# 2. 安全校验 (如果配置了)
|
|||
|
|
# 此处可以添加Verification Token的校验逻辑
|
|||
|
|
# headers = request.headers
|
|||
|
|
# ...
|
|||
|
|
|
|||
|
|
# 3. 处理URL验证挑战
|
|||
|
|
if data and data.get("type") == "url_verification":
|
|||
|
|
challenge = data.get("challenge", "")
|
|||
|
|
logger.info(f"[Feishu Bot] 收到URL验证请求,返回challenge: {challenge}")
|
|||
|
|
return jsonify({"challenge": challenge})
|
|||
|
|
|
|||
|
|
# 4. 处理事件回调
|
|||
|
|
if data and data.get("header", {}).get("event_type") == "im.message.receive_v1":
|
|||
|
|
# 立即响应飞书,防止超时重试
|
|||
|
|
threading.Thread(
|
|||
|
|
target=_process_message_in_background,
|
|||
|
|
args=(request.environ['werkzeug.request'].environ['flask.app'].app_context(), data)
|
|||
|
|
).start()
|
|||
|
|
|
|||
|
|
logger.info("[Feishu Bot] 已将消息处理任务推送到后台线程,并立即响应200 OK")
|
|||
|
|
return jsonify({"status": "processing"})
|
|||
|
|
|
|||
|
|
# 5. 对于其他未知事件,也返回成功,避免飞书重试
|
|||
|
|
logger.warning(f"[Feishu Bot] 收到未知类型的事件: {data.get('header', {}).get('event_type')}")
|
|||
|
|
return jsonify({"status": "ignored"})
|