# -*- 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"})