import re import json import threading import concurrent.futures from datetime import datetime import lark_oapi as lark from lark_oapi.api.im.v1 import ( CreateMessageRequest, CreateMessageRequestBody, ) from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTrigger from framework.config.settings import Config as config from framework.core.logger import get_logger from framework.business.cloud_desktop import CloudDesktopService from framework.business.dev_machine import DevMachineService from framework.business.billing import BillingService from framework.business.meal_reminder import MealReminderService from framework.business.boilerplate import BoilerplateService from framework.core.exceptions import TokenExpiredError logger = get_logger("FeishuBot") # ---------- 飞书卡片常量 ---------- TOKEN_UPDATE_CARD_ID = "AAq2uZZvyWY01" RESULT_CARD_ID = "AAqKcs3OrZBnJ" INTRO_CARD_ID = "AAq2uZZvyWY06" # 线程池 executor = concurrent.futures.ThreadPoolExecutor(max_workers=10) # ---------- 业务服务实例化 ---------- cd_service = CloudDesktopService() dm_service = DevMachineService() billing_service = BillingService() meal_service = MealReminderService() bp_service = BoilerplateService() # ---------- 全局计数器 ---------- _counter_lock = threading.Lock() _global_counter = 0 def _next_default_name() -> str: global _global_counter with _counter_lock: _global_counter += 1 return f"验证{_global_counter:02d}" # ---------- 飞书 API Client ---------- _lark_client = lark.Client.builder() \ .app_id(config.FEISHU_APP_ID) \ .app_secret(config.FEISHU_APP_SECRET) \ .build() def send_feishu_message(chat_id: str, text: str): body = CreateMessageRequestBody.builder().receive_id(chat_id).msg_type("text").content(json.dumps({"text": text})).build() request = CreateMessageRequest.builder().receive_id_type("chat_id").request_body(body).build() _lark_client.im.v1.message.create(request) def send_card_json(chat_id: str, card_json: dict): body = CreateMessageRequestBody.builder().receive_id(chat_id).msg_type("interactive").content(json.dumps(card_json)).build() request = CreateMessageRequest.builder().receive_id_type("chat_id").request_body(body).build() _lark_client.im.v1.message.create(request) def _handle_single_task(chat_id, name, task_type): try: if task_type == "cd_post": results, instance_id = cd_service.run_lifecycle_test(name, flow_type="postpaid_to_prepaid") label = "云桌面(按转包)" elif task_type == "cd_pre": results, instance_id = cd_service.run_lifecycle_test(name, flow_type="prepaid_to_postpaid") label = "云桌面(包转按)" elif task_type == "bp": results, instance_id = bp_service.run_lifecycle_test(name) label = "样本工程" else: results, instance_id = dm_service.run_lifecycle_test(name) label = "开发机" send_feishu_message(chat_id, f"✅ {label} [{name}] 验证完成!") except TokenExpiredError: send_feishu_message(chat_id, "⚠️ 认证过期,请更新 Token") except Exception as e: logger.error(f"Task error: {e}") send_feishu_message(chat_id, f"❌ {label} 验证异常: {e}") def _parse_command(text: str): patterns = { "cd_post": r"robogo-云桌面[ \-:=]+(.+)$", "cd_pre": r"robogo-云桌面包月[ \-:=]+(.+)$", "dm": r"robogo-开发机[ \-:=]+(.+)$", "bp": r"robogo-样本工程[ \-:=]+(.+)$", "billing": r"账单[ \-:=]*(.*)$", } # 辅助匹配模式(不带名称的情况) fallback_patterns = { "cd_post": r"robogo-云桌面\s*$", "cd_pre": r"robogo-云桌面包月\s*$", "dm": r"robogo-开发机\s*$", "bp": r"robogo-样本工程\s*$", } text_clean = text.strip() logger.info(f"Parsing command from text: '{text_clean}'") # 先试带有名称的模式 for cmd, p in patterns.items(): m = re.search(p, text_clean) if m: logger.info(f"Matched named pattern: {cmd}, name: {m.group(1).strip()}") return cmd, m.group(1).strip() # 再试默认模式 for cmd, p in fallback_patterns.items(): m = re.search(p, text_clean) if m: logger.info(f"Matched fallback pattern: {cmd}") return cmd, None logger.warning(f"No pattern matched for text: '{text_clean}'") return None, None def _on_message(data): msg = data.event.message chat_id = msg.chat_id content = json.loads(msg.content) text = content.get("text", "") clean_text = re.sub(r'@[^ ]+', '', text).strip() if "饭否" in clean_text: send_card_json(chat_id, meal_service.build_card()) return cmd, name = _parse_command(clean_text) if cmd == "billing": items = billing_service.fetch_billing_data(name) send_feishu_message(chat_id, f"共找到 {len(items)} 条账单记录") elif cmd: name = name or _next_default_name() executor.submit(_handle_single_task, chat_id, name, cmd) else: send_feishu_message(chat_id, "未能识别指令,请尝试 [robogo-云桌面] 或 [账单]") def main(): # 使用 WebSocket (Long Polling) 模式,无需公网 IP event_handler = lark.EventDispatcherHandler.builder(config.FEISHU_APP_ID, config.FEISHU_APP_SECRET) \ .register_p2_im_message_receive_v1(_on_message) \ .build() ws_client = lark.ws.Client(config.FEISHU_APP_ID, config.FEISHU_APP_SECRET, event_handler=event_handler) logger.info("Bot started in WebSocket mode...") ws_client.start() if __name__ == "__main__": main()