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}
用户消息
+
+ 只回复一次:开启后,每个对话只会触发一次默认回复,避免重复回复同一用户。