From e2ba9233702c4e0f73a58e2d51e2a9ca0a77792e Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sat, 30 Aug 2025 22:11:45 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E5=8F=91=E5=B8=83=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=97=B6=E5=8C=BA=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在前端 formatPublishDate 函数中添加时区修复逻辑 - 智能识别 UTC 时间(包含 T 或 Z)并自动 +8 小时转换为北京时间 - 保持标准北京时间格式不变 - 解决发布时间显示提早 8 小时的问题 --- app/templates/index.html | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/app/templates/index.html b/app/templates/index.html index 55e7c94..3e8fef1 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -6628,18 +6628,52 @@ formatPublishDate(value) { if (!value) return ''; const s = String(value).trim(); + // 已是标准格式则直接返回 if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/.test(s)) return s; + // 优先匹配 ISO 主体部分 const m = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})/.exec(s); if (m) { const [, y, mo, d, h, mi, se] = m; + // 修复时区问题:如果时间以Z结尾或包含T,说明是UTC时间,需要+8小时 + if (s.includes('T') || s.includes('Z')) { + // 创建Date对象并加上8小时 + const date = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(se)); + date.setHours(date.getHours() + 8); + return date.getFullYear() + '-' + + String(date.getMonth() + 1).padStart(2, '0') + '-' + + String(date.getDate()).padStart(2, '0') + ' ' + + String(date.getHours()).padStart(2, '0') + ':' + + String(date.getMinutes()).padStart(2, '0') + ':' + + String(date.getSeconds()).padStart(2, '0'); + } return `${y}-${mo}-${d} ${h}:${mi}:${se}`; } + // 回退:简单替换T为空格并去除尾部Z/时区偏移 let out = s.replace('T', ' '); out = out.replace(/Z$/i, ''); out = out.replace(/([+-]\d{2}:?\d{2})$/i, ''); + + // 如果原始时间包含T或Z,说明是UTC时间,需要+8小时 + if (s.includes('T') || s.includes('Z')) { + try { + const date = new Date(out); + if (!isNaN(date.getTime())) { + date.setHours(date.getHours() + 8); + return date.getFullYear() + '-' + + String(date.getMonth() + 1).padStart(2, '0') + '-' + + String(date.getDate()).padStart(2, '0') + ' ' + + String(date.getHours()).padStart(2, '0') + ':' + + String(date.getMinutes()).padStart(2, '0') + ':' + + String(date.getSeconds()).padStart(2, '0'); + } + } catch (e) { + // 如果转换失败,返回原始处理结果 + } + } + return out; }, changeFolderPage(page) { From 502a3c21c0ab5ad38d18846e55938e19eba9136b Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sun, 31 Aug 2025 03:02:25 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=B8=BA=E7=B3=BB=E7=BB=9F=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E9=9D=A2=E7=9A=84=E6=8F=92=E4=BB=B6=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=92=8C=20Cookie=20=E5=A2=9E=E5=8A=A0=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D=E6=8F=90=E7=A4=BA=E4=B8=8E=E6=82=AC=E5=81=9C=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为插件配置添加详细的占位符文本和悬停帮助信息 - 为 Cookie 配置部分添加智能状态悬停提示 --- app/templates/index.html | 125 +++++++++++++++++++++++++++++++++------ 1 file changed, 107 insertions(+), 18 deletions(-) diff --git a/app/templates/index.html b/app/templates/index.html index 3e8fef1..a5009f4 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -463,12 +463,14 @@

所有账号都会进行签到(纯签到只需填写移动端参数),只有第一个账号会进行转存,请自行确认账号顺序;所有填写了 Cookie 的账号均支持文件整理,如需签到请在 Cookie 后方添加签到参数。

- + {{ userInfoList[index].nickname || (userInfoList[index].has_mparam ? '仅签到' : '未登录') }}
- 未验证 + 未验证
@@ -2847,22 +2849,80 @@ // 获取插件配置的占位符文本 getPluginConfigPlaceholder(pluginName, key) { - if (pluginName === 'plex' && key === 'quark_root_path') { - return '输入夸克根目录相对于 Plex 媒体库目录的路径,多个路径用逗号分隔'; - } else if (pluginName === 'alist' && key === 'storage_id') { - return '输入 AList 服务器夸克存储的 ID,多个 ID 用逗号分隔'; - } - return ''; + const placeholders = { + aria2: { + host_port: "Aria2 RPC 地址,如:192.168.1.100:6800", + secret: "Aria2 RPC 密钥", + dir: "下载目录,需要 Aria2 拥有读写权限,如:/downloads" + }, + alist: { + url: "AList 服务器地址,如:http://192.168.1.100:5244", + token: "AList 访问令牌,在 AList 的管理 - 设置 - 其他中获取", + storage_id: "AList 服务器夸克存储的 ID,多个 ID 用逗号分隔,如:1, 2, 3" + }, + alist_strm: { + url: "AList Strm 服务器地址,如:http://192.168.1.100:5000", + cookie: "AList Strm 的 Cookie,通过 F12 抓取,关键参数:session=ey***", + config_id: "要触发运行的配置 ID,多个 ID 用逗号分隔,如:1, 2, 3" + }, + alist_strm_gen: { + url: "AList 服务器地址,如:http://192.168.1.100:5244", + token: "AList 访问令牌,在 AList 的管理 - 设置 - 其他中获取", + storage_id: "AList 服务器夸克存储的 ID", + strm_save_dir: "生成的 strm 文件的保存路径,如:/volume1/media/strm,请确保宿主机路径和容器路径映射一致", + strm_replace_host: "strm 文件内链接的主机地址,可选,缺省时使用 url" + }, + plex: { + url: "Plex 服务器地址,如:http://192.168.1.100:32400", + token: "Plex 访问令牌,通过 F12 抓取(或通过 XML 地址获取)", + quark_root_path: "夸克根目录在 Plex 中的路径,多个路径用逗号分隔,如:/volume1/media/quark1, /volume1/media/quark2" + }, + emby: { + url: "Emby 服务器地址,如:http://192.168.1.100:8096", + token: "Emby 的 API 密钥,在 Emby 的管理 - 高级 - API 密钥中生成" + } + }; + + return placeholders[pluginName]?.[key] || ''; }, // 获取插件配置的帮助文本 getPluginConfigHelp(pluginName, key) { - if (pluginName === 'plex' && key === 'quark_root_path') { - return '多账号支持:多个路径用逗号分隔,顺序与Cookie顺序对应,如:/path1, /path2'; - } else if (pluginName === 'alist' && key === 'storage_id') { - return '多账号支持:多个存储ID用逗号分隔,顺序与Cookie顺序对应,如:1, 2, 3'; - } - return ''; + const helpTexts = { + aria2: { + host_port: "Aria2 RPC服务地址,确保网络可达且端口开放", + secret: "Aria2 RPC密钥,用于安全认证", + dir: "下载文件的保存目录,确保Aria2有读写权限" + }, + alist: { + url: "AList服务器地址,用于获取存储信息", + token: "AList访问令牌,用于API调用", + storage_id: "多账号支持:多个存储ID用逗号分隔,顺序与Cookie顺序对应,如:1, 2, 3" + }, + alist_strm: { + url: "AList Strm服务器地址", + cookie: "AList Strm的Cookie", + config_id: "要触发运行的配置ID,多个ID用逗号分隔,如:1, 2, 3" + }, + alist_strm_gen: { + url: "AList服务器地址,用于获取存储信息", + token: "AList访问令牌,用于API调用", + storage_id: "夸克网盘在AList中的存储ID,在AList的存储管理中查看", + strm_save_dir: "生成的strm文件的保存路径,确保有写入权限", + strm_replace_host: "strm文件内链接的主机地址,用于替换默认的AList地址" + }, + plex: { + url: "Plex服务器地址", + token: "Plex访问令牌", + quark_root_path: "夸克根目录在Plex中的路径,多个路径用逗号分隔,如:/volume1/media/quark1, /volume1/media/quark2" + }, + emby: { + url: "Emby服务器地址", + token: "Emby的API密钥" + } + }; + + return helpTexts[pluginName]?.[key] || ''; }, // 获取插件任务配置 @@ -2898,11 +2958,11 @@ auto_delete_quark_files: "是否在添加下载任务后自动删除夸克网盘文件" }, alist_strm_gen: { - auto_gen: "是否自动生成 strm 文件" + auto_gen: "是否自动生成strm文件" }, emby: { - try_match: "是否尝试匹配", - media_id: "媒体ID,当为0时不刷新" + try_match: "是否尝试自动匹配媒体", + media_id: "指定要刷新的媒体ID,留空则自动匹配,0表示不刷新" } }; return helpTexts[pluginName]?.[key] || ''; @@ -2921,7 +2981,7 @@ }, emby: { try_match: "", - media_id: "输入媒体ID,留空则自动匹配" + media_id: "输入媒体 ID,留空则自动匹配,0 表示不刷新" } }; return placeholders[pluginName]?.[key] || ''; @@ -2998,6 +3058,35 @@ return "单个任务的插件配置,具体键值由插件定义,查阅Wiki了解详情"; }, + // 获取Cookie状态悬停提示信息 + getCookieStatusTooltip(userInfo) { + if (!userInfo) { + return "账号未验证,请获取Cookie"; + } + + // 有昵称且账号激活 + if (userInfo.nickname && userInfo.is_active) { + if (userInfo.has_mparam) { + return "账号已登录,支持转存和签到"; + } else { + return "账号已登录,仅支持转存功能"; + } + } + + // 有昵称但账号未激活 + if (userInfo.nickname && !userInfo.is_active) { + return "账号已失效,请重新获取Cookie"; + } + + // 没有昵称但有移动端参数 + if (!userInfo.nickname && userInfo.has_mparam) { + return "仅支持签到功能"; + } + + // 没有昵称也没有移动端参数 + return "账号未登录,请获取Cookie"; + }, + fetchUserInfo() { // 获取所有cookie对应的用户信息 axios.get('/get_user_info') From a89ad08ae8c55dc9eeec59db584eb17c14f1c743 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sun, 31 Aug 2025 03:23:17 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20Cookie=20=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91=EF=BC=8C=E5=87=86?= =?UTF-8?q?=E7=A1=AE=E5=8F=8D=E6=98=A0=E8=B4=A6=E5=8F=B7=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修正后端get_user_info接口 - 为成功获取账号信息的情况添加has_mparam字段 - 确保前端能正确判断账号是否包含移动端参数 2. 修正前端Cookie状态判断逻辑 - 根据账号索引位置判断功能支持情况 - 第一个账号(index=0)支持转存和签到 - 其他账号只支持签到功能 - 修正了第一个账号被错误识别为'仅支持转存'的问题 3. 完善状态提示信息 - 账号已登录,支持转存和签到 (第一个账号,有移动端参数) - 账号已登录,仅支持转存功能 (第一个账号,无移动端参数) - 账号已登录,仅支持签到功能 (其他账号,有移动端参数) - 账号已登录,不支持转存和签到 (其他账号,无移动端参数) - 账号已失效,请重新获取Cookie - 仅支持签到功能 (无昵称,有移动端参数) - 账号未登录,请获取Cookie (无昵称,无移动端参数) --- app/run.py | 5 ++++- app/templates/index.html | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/run.py b/app/run.py index abe6ed3..92502cf 100644 --- a/app/run.py +++ b/app/run.py @@ -2088,10 +2088,13 @@ def get_user_info(): account = Quark(cookie, idx) account_info = account.init() if account_info: + # 检查是否有移动端参数 + has_mparam = bool(account.mparam) user_info_list.append({ "index": idx, "nickname": account_info["nickname"], - "is_active": account.is_active + "is_active": account.is_active, + "has_mparam": has_mparam }) else: # 检查是否有移动端参数 diff --git a/app/templates/index.html b/app/templates/index.html index a5009f4..539c794 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -3067,9 +3067,19 @@ // 有昵称且账号激活 if (userInfo.nickname && userInfo.is_active) { if (userInfo.has_mparam) { - return "账号已登录,支持转存和签到"; + // 第一个账号支持转存和签到,其他账号只支持签到 + if (userInfo.index === 0) { + return "账号已登录,支持转存和签到"; + } else { + return "账号已登录,仅支持签到功能"; + } } else { - return "账号已登录,仅支持转存功能"; + // 第一个账号支持转存,其他账号不支持任何功能 + if (userInfo.index === 0) { + return "账号已登录,仅支持转存功能"; + } else { + return "账号已登录,不支持转存和签到"; + } } } From 967a363d8745373d172f31c743420f76e846c881 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Sun, 31 Aug 2025 07:59:50 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20PanSou=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E8=B5=84=E6=BA=90=E5=8F=91=E5=B8=83=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 简化时间处理规则:只有 TG 来源需要 +8 小时 - 其他所有来源(Unknown、Plugin 等)都不 +8 小时 - 修正前端 formatPublishDate 函数的判断逻辑 - 确保时间显示准确,避免错误的时间转换 --- app/sdk/pansou.py | 41 ++++++++++++++++++++++++++++++++++++---- app/templates/index.html | 23 +++++++++++++++------- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/app/sdk/pansou.py b/app/sdk/pansou.py index 2adf95d..9184217 100644 --- a/app/sdk/pansou.py +++ b/app/sdk/pansou.py @@ -24,10 +24,33 @@ class PanSou: except Exception as e: return {"success": False, "message": str(e)} + def _get_pansou_source(self, result_item: dict, merged_item: dict = None) -> str: + """ + 获取PanSou内部来源信息 + 返回格式: + - "tg:频道名称" - 来自Telegram频道,需要+8小时 + - "plugin:插件名" - 来自指定插件,不+8小时 + - "unknown" - 未知来源,不+8小时 + """ + # 优先从 results 的 channel 字段判断 + if result_item and result_item.get("channel"): + channel = result_item.get("channel", "").strip() + if channel: + return f"tg:{channel}" + + # 从 merged_by_type 的 source 字段获取 + if merged_item and merged_item.get("source"): + source = merged_item.get("source", "").strip() + if source: + return source + + # 默认返回 unknown + return "unknown" + def search(self, keyword: str): """ 搜索资源(仅返回夸克网盘结果) - 返回:{"success": True, "data": [{taskname, content, shareurl, tags[]}]} + 返回:{"success": True, "data": [{taskname, content, shareurl, tags[], pansou_source}]} """ if not self.server: return {"success": False, "message": "PanSou未配置服务器"} @@ -82,6 +105,9 @@ class PanSou: content = result_item.get("content", "") datetime_str = result_item.get("datetime", "") # 获取发布日期 + # 获取PanSou内部来源 + pansou_source = self._get_pansou_source(result_item) + # 从 links 获取具体链接 links = result_item.get("links", []) if isinstance(links, list): @@ -96,7 +122,8 @@ class PanSou: "shareurl": url, "tags": [link_type] if link_type else (result_item.get("tags", []) or []), "publish_date": datetime_str, # 原始时间(可能是 ISO) - "source": "PanSou" # 添加来源标识 + "source": "PanSou", # 添加来源标识 + "pansou_source": pansou_source # 添加PanSou内部来源 }) # 2) merged_by_type: 兜底解析,使用 note 字段作为标题 @@ -112,6 +139,10 @@ class PanSou: note = link.get("note", "") # 使用 note 字段作为标题 note = strip_links(note) datetime_str = link.get("datetime", "") # 获取发布日期 + + # 获取PanSou内部来源 + pansou_source = self._get_pansou_source(None, link) + if url: cleaned.append({ "taskname": note, @@ -119,7 +150,8 @@ class PanSou: "shareurl": url, "tags": [cloud_type] if cloud_type else [], "publish_date": datetime_str, # 原始时间 - "source": "PanSou" # 添加来源标识 + "source": "PanSou", # 添加来源标识 + "pansou_source": pansou_source # 添加PanSou内部来源 }) # 3) 直接 data 数组兜底 @@ -132,7 +164,8 @@ class PanSou: "shareurl": item.get("url", ""), "tags": item.get("tags", []) or [], "publish_date": item.get("datetime", ""), # 原始时间 - "source": "PanSou" # 添加来源标识 + "source": "PanSou", # 添加来源标识 + "pansou_source": "unknown" # 兜底来源 }) except Exception as e: diff --git a/app/templates/index.html b/app/templates/index.html index 539c794..6d0c6f5 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1038,7 +1038,7 @@ {{ suggestion.taskname }} · {{ suggestion.shareurl.replace(/^https?:\/\/pan\.quark\.cn\/s\//, '') }} - +
@@ -1966,7 +1966,7 @@ {{ suggestion.taskname }} · {{ suggestion.shareurl.replace(/^https?:\/\/pan\.quark\.cn\/s\//, '') }} - + @@ -6724,7 +6724,7 @@ return isNaN(ts) ? 0 : ts; }, // 规范化资源发布日期展示:将 ISO 格式(含 T/Z/偏移)转为 "YYYY-MM-DD HH:mm:ss" - formatPublishDate(value) { + formatPublishDate(value, pansouSource = null, source = null) { if (!value) return ''; const s = String(value).trim(); @@ -6735,8 +6735,14 @@ const m = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})/.exec(s); if (m) { const [, y, mo, d, h, mi, se] = m; - // 修复时区问题:如果时间以Z结尾或包含T,说明是UTC时间,需要+8小时 - if (s.includes('T') || s.includes('Z')) { + // 判断是否需要+8小时: + // 1. 如果是PanSou的tg来源,需要+8小时 + // 2. 如果是CloudSaver来源,需要+8小时 + // 3. 其他来源都不+8小时 + const needAdd8Hours = (pansouSource && pansouSource.startsWith('tg:')) || + (source === 'CloudSaver'); + + if (needAdd8Hours) { // 创建Date对象并加上8小时 const date = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(se)); date.setHours(date.getHours() + 8); @@ -6755,8 +6761,11 @@ out = out.replace(/Z$/i, ''); out = out.replace(/([+-]\d{2}:?\d{2})$/i, ''); - // 如果原始时间包含T或Z,说明是UTC时间,需要+8小时 - if (s.includes('T') || s.includes('Z')) { + // 判断是否需要+8小时 + const needAdd8Hours = (pansouSource && pansouSource.startsWith('tg:')) || + (source === 'CloudSaver'); + + if (needAdd8Hours) { try { const date = new Date(out); if (!isNaN(date.getTime())) {