From 8ac5909db12c92a699d1d1889c9753c303b0aa13 Mon Sep 17 00:00:00 2001 From: amazingzl Date: Sat, 2 Aug 2025 16:24:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=92=89=E9=92=89=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E6=B8=A0=E9=81=93=E6=94=AF=E6=8C=81=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=95=B0=E6=8D=AE=E5=BA=93=E7=BB=93=E6=9E=84=E4=BB=A5?= =?UTF-8?q?=E9=80=82=E5=BA=94=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=9B=B8=E5=85=B3=E6=97=A5=E5=BF=97=E8=AE=B0?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- XianyuAutoAsync.py | 40 ++++++++++++++-- db_manager.py | 111 +++++++++++++++++++++++++++++++++++++++------ static/index.html | 72 +++++++++++++++++++++++++++-- 3 files changed, 201 insertions(+), 22 deletions(-) diff --git a/XianyuAutoAsync.py b/XianyuAutoAsync.py index 7d71fa9..1812fce 100644 --- a/XianyuAutoAsync.py +++ b/XianyuAutoAsync.py @@ -918,10 +918,13 @@ class XianyuLive: channel_config = notification.get('channel_config') try: - if channel_type == 'qq': - await self._send_qq_notification(channel_config, notification_msg) - else: - logger.warning(f"不支持的通知渠道类型: {channel_type}") + match channel_type: + case 'qq': + await self._send_qq_notification(channel_config, notification_msg) + case 'ding_talk': + await self._send_ding_talk_notification(channel_config, notification_msg) + case _: + logger.warning(f"不支持的通知渠道类型: {channel_type}") except Exception as notify_error: logger.error(f"发送通知失败 ({notification.get('channel_name', 'Unknown')}): {self._safe_str(notify_error)}") @@ -958,6 +961,35 @@ class XianyuLive: except Exception as e: logger.error(f"发送QQ通知异常: {self._safe_str(e)}") + async def _send_ding_talk_notification(self, config: str, message: str): + """发送钉钉通知""" + try: + import aiohttp + import json + # 解析配置(钉钉机器人Webhook URL) + webhook_url = config.strip() + if not webhook_url: + logger.warning("钉钉通知配置为空") + return + + data = { + "msgtype": "markdown", + "markdown": { + "title": "闲鱼自动回复通知", + "text": message + } + } + + async with aiohttp.ClientSession() as session: + async with session.post(webhook_url, json=data, timeout=10) as response: + if response.status == 200: + logger.info(f"钉钉通知发送成功: {webhook_url}") + else: + logger.warning(f"钉钉通知发送失败: {response.status}") + + except Exception as e: + logger.error(f"发送钉钉通知异常: {self._safe_str(e)}") + async def send_token_refresh_notification(self, error_message: str, notification_type: str = "token_refresh"): """发送Token刷新异常通知(带防重复机制)""" try: diff --git a/db_manager.py b/db_manager.py index d13b771..e437e12 100644 --- a/db_manager.py +++ b/db_manager.py @@ -295,6 +295,16 @@ class DBManager: ) ''') + # 创建系统设置表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS system_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + description TEXT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + # 创建消息通知配置表 cursor.execute(''' CREATE TABLE IF NOT EXISTS message_notifications ( @@ -310,16 +320,6 @@ class DBManager: ) ''') - # 创建系统设置表 - cursor.execute(''' - CREATE TABLE IF NOT EXISTS system_settings ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - description TEXT, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - ''') - # 创建用户设置表 cursor.execute(''' CREATE TABLE IF NOT EXISTS user_settings ( @@ -341,6 +341,45 @@ class DBManager: ('theme_color', 'blue', '主题颜色') ''') + # 检查并升级数据库 + self.check_and_upgrade_db(cursor) + + self.conn.commit() + logger.info("数据库初始化完成") + except Exception as e: + logger.error(f"数据库初始化失败: {e}") + self.conn.rollback() + raise + + def check_and_upgrade_db(self, cursor): + """检查数据库版本并执行必要的升级""" + try: + # 获取当前数据库版本 + current_version = self.get_system_setting("db_version") or "1.0" + logger.info(f"当前数据库版本: {current_version}") + + if current_version == "1.0": + logger.info("开始升级数据库到版本1.0...") + self.update_admin_user_id(cursor) + self.set_system_setting("db_version", "1.0", "数据库版本号") + logger.info("数据库升级到版本1.0完成") + + # 如果版本低于需要升级的版本,执行升级 + if current_version < "1.1": + logger.info("开始升级数据库到版本1.1...") + self.upgrade_notification_channels_table(cursor) + self.set_system_setting("db_version", "1.1", "数据库版本号") + logger.info("数据库升级到版本1.1完成") + + + except Exception as e: + logger.error(f"数据库版本检查或升级失败: {e}") + raise + + def update_admin_user_id(self, cursor): + """更新admin用户ID""" + try: + logger.info("开始更新admin用户ID...") # 创建默认admin用户(只在首次初始化时创建) cursor.execute('SELECT COUNT(*) FROM users WHERE username = ?', ('admin',)) admin_exists = cursor.fetchone()[0] > 0 @@ -438,11 +477,55 @@ class DBManager: self._migrate_keywords_table_constraints(cursor) self.conn.commit() - logger.info(f"数据库初始化成功: {self.db_path}") + logger.info(f"admin用户ID更新完成") except Exception as e: - logger.error(f"数据库初始化失败: {e}") - if self.conn: - self.conn.close() + logger.error(f"更新admin用户ID失败: {e}") + raise + + def upgrade_notification_channels_table(self, cursor): + """升级notification_channels表的type字段约束""" + try: + logger.info("开始升级notification_channels表...") + + # 检查表是否存在 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='notification_channels'") + if not cursor.fetchone(): + logger.info("notification_channels表不存在,无需升级") + return True + + # 检查表中是否有数据 + cursor.execute("SELECT COUNT(*) FROM notification_channels") + count = cursor.fetchone()[0] + + # 创建临时表 + cursor.execute(''' + CREATE TABLE notification_channels_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + user_id INTEGER NOT NULL, + type TEXT NOT NULL CHECK (type IN ('qq','ding_talk')), + config TEXT NOT NULL, + enabled BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 复制数据 + if count > 0: + logger.info(f"复制 {count} 条通知渠道数据到新表") + cursor.execute("INSERT INTO notification_channels_new SELECT * FROM notification_channels") + + # 删除旧表 + cursor.execute("DROP TABLE notification_channels") + + # 重命名新表 + cursor.execute("ALTER TABLE notification_channels_new RENAME TO notification_channels") + + logger.info("notification_channels表升级完成") + return True + except Exception as e: + logger.error(f"升级notification_channels表失败: {e}") raise def _migrate_keywords_table_constraints(self, cursor): diff --git a/static/index.html b/static/index.html index f6c5706..867ca11 100644 --- a/static/index.html +++ b/static/index.html @@ -1886,6 +1886,33 @@ +
+
+ 添加钉钉通知渠道 +
+
+
+ + 使用说明:请设置钉钉机器人Webhook URL +
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+
@@ -2298,8 +2325,8 @@
- - + +
@@ -4882,7 +4909,7 @@
暂无通知渠道
-

请添加QQ通知渠道

+

请添加通知渠道

`; @@ -4899,7 +4926,7 @@ tr.innerHTML = ` ${channel.id} ${channel.name} - QQ + ${channel.type} ${channel.config} ${statusBadge} @@ -4956,6 +4983,43 @@ } }); } + + const addDingTalkChannelForm = document.getElementById('addDingTalkChannelForm'); + if (addDingTalkChannelForm) { + addDingTalkChannelForm.addEventListener('submit', async function(e) { + e.preventDefault(); + + const name = document.getElementById('dingTalkChannelName').value; + const dingTalkWebHook = document.getElementById('dingTalkWebHook').value; + + try { + const response = await fetch(`${apiBase}/notification-channels`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${authToken}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + name: name, + type: 'ding_talk', + config: dingTalkWebHook + }) + }); + + if (response.ok) { + showToast('通知渠道添加成功', 'success'); + addDingTalkChannelForm.reset(); + loadNotificationChannels(); + } else { + const error = await response.text(); + showToast(`添加失败: ${error}`, 'danger'); + } + } catch (error) { + console.error('添加通知渠道失败:', error); + showToast('添加通知渠道失败', 'danger'); + } + }); + } }); // 删除通知渠道