From 0b9b81bc4ef10b29873c147b637e66e8cfd9de4c Mon Sep 17 00:00:00 2001 From: zhinianboke <115088296+zhinianboke@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:26:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=9B=9E=E5=A4=8D=E5=8F=AA=E5=9B=9E=E5=A4=8D=E4=B8=80?= =?UTF-8?q?=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- XianyuAutoAsync.py | 19 +++++++++-- db_manager.py | 85 ++++++++++++++++++++++++++++++++++++++++------ reply_server.py | 43 +++++++++++++++++++++-- static/index.html | 20 +++++++++-- static/js/app.js | 50 ++++++++++++++++++++++++--- 5 files changed, 194 insertions(+), 23 deletions(-) diff --git a/XianyuAutoAsync.py b/XianyuAutoAsync.py index f08ba37..e73943a 100644 --- a/XianyuAutoAsync.py +++ b/XianyuAutoAsync.py @@ -961,8 +961,8 @@ class XianyuLive: except Exception as e: logger.error(f"调试消息结构时发生错误: {self._safe_str(e)}") - async def get_default_reply(self, send_user_name: str, send_user_id: str, send_message: str) -> str: - """获取默认回复内容,支持变量替换""" + async def get_default_reply(self, send_user_name: str, send_user_id: str, send_message: str, chat_id: str = None) -> str: + """获取默认回复内容,支持变量替换和只回复一次功能""" try: from db_manager import db_manager @@ -973,6 +973,13 @@ class XianyuLive: logger.debug(f"账号 {self.cookie_id} 未启用默认回复") return None + # 检查"只回复一次"功能 + if default_reply_settings.get('reply_once', False) and chat_id: + # 检查是否已经回复过这个chat_id + if db_manager.has_default_reply_record(self.cookie_id, chat_id): + logger.info(f"【{self.cookie_id}】chat_id {chat_id} 已使用过默认回复,跳过(只回复一次)") + return None + reply_content = default_reply_settings.get('reply_content', '') if not reply_content: logger.warning(f"账号 {self.cookie_id} 默认回复内容为空") @@ -985,6 +992,12 @@ class XianyuLive: send_user_id=send_user_id, send_message=send_message ) + + # 如果开启了"只回复一次"功能,记录这次回复 + if default_reply_settings.get('reply_once', False) and chat_id: + db_manager.add_default_reply_record(self.cookie_id, chat_id) + logger.info(f"【{self.cookie_id}】记录默认回复: chat_id={chat_id}") + logger.info(f"【{self.cookie_id}】使用默认回复: {formatted_reply}") return formatted_reply except Exception as format_error: @@ -2896,7 +2909,7 @@ class XianyuLive: reply_source = 'AI' # 标记为AI回复 else: # 3. 最后使用默认回复 - reply = await self.get_default_reply(send_user_name, send_user_id, send_message) + reply = await self.get_default_reply(send_user_name, send_user_id, send_message, chat_id) reply_source = '默认' # 标记为默认回复 # 注意:这里只有商品ID,没有标题和详情,根据新的规则不保存到数据库 diff --git a/db_manager.py b/db_manager.py index e597ba0..3e3cb84 100644 --- a/db_manager.py +++ b/db_manager.py @@ -281,12 +281,34 @@ class DBManager: cookie_id TEXT PRIMARY KEY, enabled BOOLEAN DEFAULT FALSE, reply_content TEXT, + reply_once BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (cookie_id) REFERENCES cookies(id) ON DELETE CASCADE ) ''') + # 添加 reply_once 字段(如果不存在) + try: + cursor.execute('ALTER TABLE default_replies ADD COLUMN reply_once BOOLEAN DEFAULT FALSE') + self.conn.commit() + logger.info("已添加 reply_once 字段到 default_replies 表") + except sqlite3.OperationalError as e: + if "duplicate column name" not in str(e).lower(): + logger.warning(f"添加 reply_once 字段失败: {e}") + + # 创建默认回复记录表(记录已回复的chat_id) + cursor.execute(''' + CREATE TABLE IF NOT EXISTS default_reply_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cookie_id TEXT NOT NULL, + chat_id TEXT NOT NULL, + replied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(cookie_id, chat_id), + FOREIGN KEY (cookie_id) REFERENCES cookies(id) ON DELETE CASCADE + ) + ''') + # 创建通知渠道表 cursor.execute(''' CREATE TABLE IF NOT EXISTS notification_channels ( @@ -1588,17 +1610,17 @@ class DBManager: return {} # -------------------- 默认回复操作 -------------------- - def save_default_reply(self, cookie_id: str, enabled: bool, reply_content: str = None): + def save_default_reply(self, cookie_id: str, enabled: bool, reply_content: str = None, reply_once: bool = False): """保存默认回复设置""" with self.lock: try: cursor = self.conn.cursor() cursor.execute(''' - INSERT OR REPLACE INTO default_replies (cookie_id, enabled, reply_content, updated_at) - VALUES (?, ?, ?, CURRENT_TIMESTAMP) - ''', (cookie_id, enabled, reply_content)) + INSERT OR REPLACE INTO default_replies (cookie_id, enabled, reply_content, reply_once, updated_at) + VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) + ''', (cookie_id, enabled, reply_content, reply_once)) self.conn.commit() - logger.debug(f"保存默认回复设置: {cookie_id} -> {'启用' if enabled else '禁用'}") + logger.debug(f"保存默认回复设置: {cookie_id} -> {'启用' if enabled else '禁用'}, 只回复一次: {'是' if reply_once else '否'}") except Exception as e: logger.error(f"保存默认回复设置失败: {e}") raise @@ -1609,14 +1631,15 @@ class DBManager: try: cursor = self.conn.cursor() cursor.execute(''' - SELECT enabled, reply_content FROM default_replies WHERE cookie_id = ? + SELECT enabled, reply_content, reply_once FROM default_replies WHERE cookie_id = ? ''', (cookie_id,)) result = cursor.fetchone() if result: - enabled, reply_content = result + enabled, reply_content, reply_once = result return { 'enabled': bool(enabled), - 'reply_content': reply_content or '' + 'reply_content': reply_content or '', + 'reply_once': bool(reply_once) if reply_once is not None else False } return None except Exception as e: @@ -1628,14 +1651,15 @@ class DBManager: with self.lock: try: cursor = self.conn.cursor() - cursor.execute('SELECT cookie_id, enabled, reply_content FROM default_replies') + cursor.execute('SELECT cookie_id, enabled, reply_content, reply_once FROM default_replies') result = {} for row in cursor.fetchall(): - cookie_id, enabled, reply_content = row + cookie_id, enabled, reply_content, reply_once = row result[cookie_id] = { 'enabled': bool(enabled), - 'reply_content': reply_content or '' + 'reply_content': reply_content or '', + 'reply_once': bool(reply_once) if reply_once is not None else False } return result @@ -1643,6 +1667,45 @@ class DBManager: logger.error(f"获取所有默认回复设置失败: {e}") return {} + def add_default_reply_record(self, cookie_id: str, chat_id: str): + """记录已回复的chat_id""" + with self.lock: + try: + cursor = self.conn.cursor() + cursor.execute(''' + INSERT OR IGNORE INTO default_reply_records (cookie_id, chat_id) + VALUES (?, ?) + ''', (cookie_id, chat_id)) + self.conn.commit() + logger.debug(f"记录默认回复: {cookie_id} -> {chat_id}") + except Exception as e: + logger.error(f"记录默认回复失败: {e}") + + def has_default_reply_record(self, cookie_id: str, chat_id: str) -> bool: + """检查是否已经回复过该chat_id""" + with self.lock: + try: + cursor = self.conn.cursor() + cursor.execute(''' + SELECT 1 FROM default_reply_records WHERE cookie_id = ? AND chat_id = ? + ''', (cookie_id, chat_id)) + result = cursor.fetchone() + return result is not None + except Exception as e: + logger.error(f"检查默认回复记录失败: {e}") + return False + + def clear_default_reply_records(self, cookie_id: str): + """清空指定账号的默认回复记录""" + with self.lock: + try: + cursor = self.conn.cursor() + cursor.execute('DELETE FROM default_reply_records WHERE cookie_id = ?', (cookie_id,)) + self.conn.commit() + logger.debug(f"清空默认回复记录: {cookie_id}") + except Exception as e: + logger.error(f"清空默认回复记录失败: {e}") + def delete_default_reply(self, cookie_id: str) -> bool: """删除指定账号的默认回复设置""" with self.lock: diff --git a/reply_server.py b/reply_server.py index 2438579..152e456 100644 --- a/reply_server.py +++ b/reply_server.py @@ -858,13 +858,22 @@ async def register(request: RegisterRequest): @app.post("/xianyu/reply", response_model=ResponseModel) async def xianyu_reply(req: RequestModel): msg_template = match_reply(req.cookie_id, req.send_message) + is_default_reply = False + if not msg_template: # 从数据库获取默认回复 from db_manager import db_manager default_reply_settings = db_manager.get_default_reply(req.cookie_id) if default_reply_settings and default_reply_settings.get('enabled', False): + # 检查是否开启了"只回复一次"功能 + if default_reply_settings.get('reply_once', False): + # 检查是否已经回复过这个chat_id + if db_manager.has_default_reply_record(req.cookie_id, req.chat_id): + raise HTTPException(status_code=404, detail="该对话已使用默认回复,不再重复回复") + msg_template = default_reply_settings.get('reply_content', '') + is_default_reply = True # 如果数据库中没有设置或为空,返回错误 if not msg_template: @@ -881,6 +890,13 @@ async def xianyu_reply(req: RequestModel): # 如果格式化失败,返回原始内容 send_msg = msg_template + # 如果是默认回复且开启了"只回复一次",记录回复记录 + if is_default_reply: + from db_manager import db_manager + default_reply_settings = db_manager.get_default_reply(req.cookie_id) + if default_reply_settings and default_reply_settings.get('reply_once', False): + db_manager.add_default_reply_record(req.cookie_id, req.chat_id) + return {"code": 200, "data": {"send_msg": send_msg}} # ------------------------- 账号 / 关键字管理接口 ------------------------- @@ -898,6 +914,7 @@ class CookieStatusIn(BaseModel): class DefaultReplyIn(BaseModel): enabled: bool reply_content: Optional[str] = None + reply_once: bool = False class NotificationChannelIn(BaseModel): @@ -1179,7 +1196,7 @@ def get_default_reply(cid: str, current_user: Dict[str, Any] = Depends(get_curre result = db_manager.get_default_reply(cid) if result is None: # 如果没有设置,返回默认值 - return {'enabled': False, 'reply_content': ''} + return {'enabled': False, 'reply_content': '', 'reply_once': False} return result except HTTPException: raise @@ -1199,8 +1216,8 @@ def update_default_reply(cid: str, reply_data: DefaultReplyIn, current_user: Dic if cid not in user_cookies: raise HTTPException(status_code=403, detail="无权限操作该Cookie") - db_manager.save_default_reply(cid, reply_data.enabled, reply_data.reply_content) - return {'msg': 'default reply updated', 'enabled': reply_data.enabled} + db_manager.save_default_reply(cid, reply_data.enabled, reply_data.reply_content, reply_data.reply_once) + return {'msg': 'default reply updated', 'enabled': reply_data.enabled, 'reply_once': reply_data.reply_once} except HTTPException: raise except Exception as e: @@ -1247,6 +1264,26 @@ def delete_default_reply(cid: str, current_user: Dict[str, Any] = Depends(get_cu raise HTTPException(status_code=500, detail=str(e)) +@app.post('/default-replies/{cid}/clear-records') +def clear_default_reply_records(cid: str, current_user: Dict[str, Any] = Depends(get_current_user)): + """清空指定账号的默认回复记录""" + from db_manager import db_manager + try: + # 检查cookie是否属于当前用户 + user_id = current_user['user_id'] + user_cookies = db_manager.get_all_cookies(user_id) + + if cid not in user_cookies: + raise HTTPException(status_code=403, detail="无权限操作该Cookie") + + db_manager.clear_default_reply_records(cid) + return {'msg': 'default reply records cleared'} + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + # ------------------------- 通知渠道管理接口 ------------------------- @app.get('/notification-channels') diff --git a/static/index.html b/static/index.html index 63c6f22..1a28dc1 100644 --- a/static/index.html +++ b/static/index.html @@ -1888,6 +1888,8 @@ {send_user_name} 用户昵称、 {send_user_id} 用户ID、 {send_message} 用户消息 +

+ 只回复一次:开启后,每个对话只会触发一次默认回复,避免重复回复同一用户。
@@ -1895,8 +1897,9 @@ 账号ID - 状态 - 默认回复内容 + 状态 + 只回复一次 + 默认回复内容 操作 @@ -1946,6 +1949,19 @@
+
+
+ + +
+ + 开启后,每个对话只会触发一次默认回复,避免重复回复 +
+
+
+