diff --git a/.env.example b/.env.example index 4c219609..38f94e72 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,8 @@ OPENAI_API_KEY=your_api_key_here -COOKIES_STR=your_cookies_here \ No newline at end of file +COOKIES_STR=your_cookies_here +# 飞书通知配置 +ENABLE_FEISHU_NOTIFY=true +FEISHU_APP_ID=xxx +FEISHU_APP_SECRET=xxx +FEISHU_RECEIVE_ID=xxx +FEISHU_RECEIVE_ID_TYPE=open_id \ No newline at end of file diff --git a/main.py b/main.py index e5a735de..2b2a2038 100644 --- a/main.py +++ b/main.py @@ -13,6 +13,7 @@ from XianyuAgent import XianyuReplyBot from context_manager import ChatContextManager +from utils.notifier import NotificationManager, FeishuNotifier class XianyuLive: def __init__(self, cookies_str): @@ -32,6 +33,46 @@ def __init__(self, cookies_str): self.heartbeat_task = None self.ws = None + # 添加通知管理器 + self.notification_manager = self._init_notification_manager() + + # 添加Cookie过期检测 + self.cookie_check_interval = 3600 # 每小时检查一次Cookie + self.last_cookie_check_time = time.time() + + def _init_notification_manager(self): + """初始化通知管理器""" + manager = NotificationManager() + + # 飞书通知 + if os.getenv("ENABLE_FEISHU_NOTIFY", "false").lower() == "true": + feishu_notifier = FeishuNotifier( + app_id=os.getenv("FEISHU_APP_ID", ""), + app_secret=os.getenv("FEISHU_APP_SECRET", ""), + receive_id_type=os.getenv("FEISHU_RECEIVE_ID_TYPE", "chat_id"), + receive_id=os.getenv("FEISHU_RECEIVE_ID", "") + ) + manager.add_notifier(feishu_notifier) + logger.info("已启用飞书通知") + + return manager + + async def check_cookie_valid(self): + """检查Cookie是否有效""" + try: + # 尝试获取token,如果失败则说明Cookie已过期 + token_response = self.xianyu.get_token(self.cookies, self.device_id) + if token_response.get('ret') and token_response['ret'][0] != "SUCCESS::调用成功": + logger.error(f"Cookie已过期: {token_response}") + self.notification_manager.notify_cookie_expired() + return False + return True + except Exception as e: + logger.error(f"检查Cookie时出错: {e}") + self.notification_manager.notify_error("Cookie检查错误", str(e)) + return False + + async def send_msg(self, ws, cid, toid, text): text = { "contentType": 1, @@ -340,8 +381,20 @@ async def handle_heartbeat_response(self, message_data): return False async def main(self): + # 系统启动通知 + self.notification_manager.notify_system_start() + while True: try: + # 检查Cookie是否有效 + current_time = time.time() + if current_time - self.last_cookie_check_time >= self.cookie_check_interval: + self.last_cookie_check_time = current_time + if not await self.check_cookie_valid(): + logger.warning("Cookie无效,等待30分钟后重试...") + await asyncio.sleep(1800) # 等待30分钟后重试 + continue + headers = { "Cookie": self.cookies_str, "Host": "wss-goofish.dingtalk.com", @@ -399,6 +452,7 @@ async def main(self): except websockets.exceptions.ConnectionClosed: logger.warning("WebSocket连接已关闭") + self.notification_manager.notify_error("连接错误", "WebSocket连接已关闭", "系统将在5秒后尝试重连") if self.heartbeat_task: self.heartbeat_task.cancel() try: @@ -409,6 +463,7 @@ async def main(self): except Exception as e: logger.error(f"连接发生错误: {e}") + self.notification_manager.notify_error("连接错误", str(e), "系统将在5秒后尝试重连") if self.heartbeat_task: self.heartbeat_task.cancel() try: diff --git a/requirements.txt b/requirements.txt index d6eb70b5..238407c3 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/utils/notifier.py b/utils/notifier.py new file mode 100644 index 00000000..2038801e --- /dev/null +++ b/utils/notifier.py @@ -0,0 +1,119 @@ +import os +import smtplib +import requests +import json +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from abc import ABC, abstractmethod +from loguru import logger +from typing import Dict, Any, List, Optional +from datetime import datetime +import json + +import lark_oapi as lark +from lark_oapi.api.im.v1 import * + + + +class BaseNotifier(ABC): + """通知器基类""" + + @abstractmethod + def send(self, title: str, content: str) -> bool: + """ + 发送通知 + + Args: + title: 通知标题 + content: 通知内容 + + Returns: + bool: 是否发送成功 + """ + pass + + +class FeishuNotifier(BaseNotifier): + """飞书通知器""" + + def __init__(self, app_id: str, app_secret: str, receive_id_type: str, receive_id: str): + """ + 初始化飞书通知器 + + Args: + app_id: 飞书应用的App ID + app_secret: 飞书应用的App Secret + receive_id_type: 接收者ID类型,可选值: "open_id", "user_id", "union_id", "email", "chat_id" + receive_id: 接收者ID + """ + try: + self.client = lark.Client.builder().app_id(app_id).app_secret(app_secret).build() + self.receive_id_type = receive_id_type + self.receive_id = receive_id + except ImportError: + logger.error("未安装lark_oapi库,请使用pip install lark_oapi安装") + raise + + def send(self, title: str, content: str) -> bool: + try: + # 构建消息请求 + req_content = json.dumps({"text": title + "\n" + content}) + req: CreateMessageRequest = CreateMessageRequest.builder() \ + .receive_id_type(self.receive_id_type) \ + .request_body(CreateMessageRequestBody.builder() + .receive_id(self.receive_id) + .msg_type("text") + .content(req_content) + .build()) \ + .build() + + logger.debug(f"Receive ID : {self.receive_id}") + # 发送消息 + resp: CreateMessageResponse = self.client.im.v1.message.create(req) + + if resp.success(): + logger.info(f"飞书通知发送成功: {title}") + return True + else: + logger.error(f"飞书通知发送失败: {resp.msg}") + return False + except Exception as e: + logger.error(f"飞书通知发送失败: {e}") + return False + + +class NotificationManager: + """通知管理器""" + + def __init__(self): + self.notifiers: List[BaseNotifier] = [] + + def add_notifier(self, notifier: BaseNotifier): + """添加通知器""" + self.notifiers.append(notifier) + + def notify(self, title: str, content: str) -> bool: + """发送通知到所有通知器""" + success = False + for notifier in self.notifiers: + if notifier.send(title, content): + success = True + return success + + def notify_error(self, error_type: str, error_msg: str, details: str = "") -> bool: + """发送错误通知""" + title = f"闲鱼自动客服系统错误: {error_type}" + content = f"错误信息: {error_msg}\n\n详细信息: {details}" + return self.notify(title, content) + + def notify_cookie_expired(self) -> bool: + """发送Cookie过期通知""" + title = "闲鱼自动客服系统警告: Cookie已过期" + content = "您的闲鱼Cookie已过期,请尽快更新Cookie以保持系统正常运行。" + return self.notify(title, content) + + def notify_system_start(self) -> bool: + """发送系统启动通知""" + title = "闲鱼自动客服系统: 已启动" + content = f"系统已成功启动,当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + return self.notify(title, content) \ No newline at end of file