From 277ad71042896e863edd63d3b35d2dd44a011640 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 20 May 2026 14:43:43 +0800 Subject: [PATCH] fix(qdreader): use Beijing time for logs --- README.md | 2 +- js/qdreader_sign_autman.js | 31 +++++++++++++++--------- plugins/qdreader/README.md | 2 +- plugins/qdreader/qdreader_sign_autman.js | 31 +++++++++++++++--------- tests/qdreader_plugin.test.mjs | 13 ++++++++++ 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 2c9e265..9c21a6e 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ GET /api/sms/stats - 定时运行:`cron: 1 7 * * *`,每日 07:01 自动签到;签到结束后立即归档并清空最近运行日志 - 手动运行:`启点签到` / `启点签到 ` - Cookie 维护:`启点账号` / `启点ck ` / `启点查询` -- v2.5.8:保持单插件;定时只负责 07:01 签到,每次签到结束后立即归档运行日志;查询结果按批次/来源/汇总/明细排版并截断过长原始消息。 +- v2.5.9:保持单插件;定时只负责 07:01 签到,每次签到结束后立即归档运行日志;查询结果按批次/来源/汇总/明细排版并截断过长原始消息;所有运行日志和归档日期统一使用北京时间。 Raw 导入: diff --git a/js/qdreader_sign_autman.js b/js/qdreader_sign_autman.js index c742e24..cf28707 100644 --- a/js/qdreader_sign_autman.js +++ b/js/qdreader_sign_autman.js @@ -4,7 +4,7 @@ //[author: Hermes] //[service: BOSS] //[class: 工具类] -//[version: 2.5.8] +//[version: 2.5.9] //[platform: web,qq,wx,tg,tb,fs,we] //[public: false] //[price: 0] @@ -260,7 +260,7 @@ function contentText() { } function usage() { return [ - "启点读书签到插件 v2.5.8", + "启点读书签到插件 v2.5.9", "【一级菜单】", "账号管理:启点账号", "执行查询:启点查询", @@ -395,7 +395,17 @@ function resultMsg(obj) { if (!obj) return "" return obj.Message || obj.Msg || obj.message || obj.msg || obj.toast || (obj.data && (obj.data.message || obj.data.msg || obj.data.toast)) || "" } -function nowIso() { try { return new Date().toISOString() } catch (e) { return String(new Date()) } } +function pad2(n) { n = String(n); return n.length < 2 ? "0" + n : n } +function beijingDate(dateLike) { + var d = dateLike ? new Date(dateLike) : new Date() + if (!d || isNaN(d.getTime())) d = new Date() + return new Date(d.getTime() + 8 * 60 * 60 * 1000) +} +function beijingTime(dateLike) { + var d = beijingDate(dateLike) + return d.getUTCFullYear() + "-" + pad2(d.getUTCMonth() + 1) + "-" + pad2(d.getUTCDate()) + " " + pad2(d.getUTCHours()) + ":" + pad2(d.getUTCMinutes()) + ":" + pad2(d.getUTCSeconds()) +} +function nowIso() { return beijingTime() } function setLastResult(uid, result) { var obj = cleanStringMap(safeJsonParse(cfgGet(LAST_RESULT_KEY, "{}"), {})) obj[String(uid)] = JSON.stringify(result || {}) @@ -412,12 +422,11 @@ function getLastRunLog() { return safeJsonParse(cfgGet(LAST_RUN_KEY, "{}"), {}) function getArchiveStore() { return safeJsonParse(cfgGet(ARCHIVE_KEY, "{}"), {}) || {} } function setArchiveStore(obj) { cfgSet(ARCHIVE_KEY, JSON.stringify(obj || {})) } function localDateKey(iso) { - var d = iso ? new Date(iso) : new Date() - if (!d || isNaN(d.getTime())) d = new Date() - var y = d.getFullYear() - var m = String(d.getMonth() + 1); if (m.length < 2) m = "0" + m - var day = String(d.getDate()); if (day.length < 2) day = "0" + day - return y + "-" + m + "-" + day + var s = String(iso || "") + var m0 = s.match(/^(\d{4}-\d{2}-\d{2})/) + if (m0) return m0[1] + var d = beijingDate(iso) + return d.getUTCFullYear() + "-" + pad2(d.getUTCMonth() + 1) + "-" + pad2(d.getUTCDate()) } function archiveRunLog(reason) { var run = getLastRunLog() @@ -530,9 +539,9 @@ function signAll(targetUid, allUsers, source, archiveAfterRun) { function shortTime(iso) { if (!iso) return "未知时间" var s = String(iso) - var m = s.match(/(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})/) + var m = s.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/) if (m) return m[1] + " " + m[2] - return s.replace(/\.\d{3}Z$/, "Z") + return beijingTime(iso).slice(0, 16) } function displayMsg(msg) { msg = trim(msg || "无结果") diff --git a/plugins/qdreader/README.md b/plugins/qdreader/README.md index e37e56a..b889103 100644 --- a/plugins/qdreader/README.md +++ b/plugins/qdreader/README.md @@ -147,7 +147,7 @@ qdreader_last_result_json # 每个 uid 当日/当前批次结果 qdreader_archive_json # 每次签到后立即归档的历史批次 ``` -因此“查询”语义是“最近签到记录/最后执行日志”。刚添加但从未执行过的账号会显示“暂无执行记录”。因为签到完成后最近日志会立即归档并清空,`启点查询` 会回退到最近归档,根据归档中的账号结果判断最近签到情况并反馈给用户。查询输出会加工成“批次/来源/汇总/结果明细”结构,时间压缩到分钟,过长的接口错误/原始响应会截断,避免刷屏。 +因此“查询”语义是“最近签到记录/最后执行日志”。刚添加但从未执行过的账号会显示“暂无执行记录”。因为签到完成后最近日志会立即归档并清空,`启点查询` 会回退到最近归档,根据归档中的账号结果判断最近签到情况并反馈给用户。查询输出会加工成“批次/来源/汇总/结果明细”结构,时间压缩到分钟,过长的接口错误/原始响应会截断,避免刷屏。所有日志时间和归档日期统一按北京时间计算,避免 UTC 跨日导致日期错位。 ## 本地验证 diff --git a/plugins/qdreader/qdreader_sign_autman.js b/plugins/qdreader/qdreader_sign_autman.js index c742e24..cf28707 100644 --- a/plugins/qdreader/qdreader_sign_autman.js +++ b/plugins/qdreader/qdreader_sign_autman.js @@ -4,7 +4,7 @@ //[author: Hermes] //[service: BOSS] //[class: 工具类] -//[version: 2.5.8] +//[version: 2.5.9] //[platform: web,qq,wx,tg,tb,fs,we] //[public: false] //[price: 0] @@ -260,7 +260,7 @@ function contentText() { } function usage() { return [ - "启点读书签到插件 v2.5.8", + "启点读书签到插件 v2.5.9", "【一级菜单】", "账号管理:启点账号", "执行查询:启点查询", @@ -395,7 +395,17 @@ function resultMsg(obj) { if (!obj) return "" return obj.Message || obj.Msg || obj.message || obj.msg || obj.toast || (obj.data && (obj.data.message || obj.data.msg || obj.data.toast)) || "" } -function nowIso() { try { return new Date().toISOString() } catch (e) { return String(new Date()) } } +function pad2(n) { n = String(n); return n.length < 2 ? "0" + n : n } +function beijingDate(dateLike) { + var d = dateLike ? new Date(dateLike) : new Date() + if (!d || isNaN(d.getTime())) d = new Date() + return new Date(d.getTime() + 8 * 60 * 60 * 1000) +} +function beijingTime(dateLike) { + var d = beijingDate(dateLike) + return d.getUTCFullYear() + "-" + pad2(d.getUTCMonth() + 1) + "-" + pad2(d.getUTCDate()) + " " + pad2(d.getUTCHours()) + ":" + pad2(d.getUTCMinutes()) + ":" + pad2(d.getUTCSeconds()) +} +function nowIso() { return beijingTime() } function setLastResult(uid, result) { var obj = cleanStringMap(safeJsonParse(cfgGet(LAST_RESULT_KEY, "{}"), {})) obj[String(uid)] = JSON.stringify(result || {}) @@ -412,12 +422,11 @@ function getLastRunLog() { return safeJsonParse(cfgGet(LAST_RUN_KEY, "{}"), {}) function getArchiveStore() { return safeJsonParse(cfgGet(ARCHIVE_KEY, "{}"), {}) || {} } function setArchiveStore(obj) { cfgSet(ARCHIVE_KEY, JSON.stringify(obj || {})) } function localDateKey(iso) { - var d = iso ? new Date(iso) : new Date() - if (!d || isNaN(d.getTime())) d = new Date() - var y = d.getFullYear() - var m = String(d.getMonth() + 1); if (m.length < 2) m = "0" + m - var day = String(d.getDate()); if (day.length < 2) day = "0" + day - return y + "-" + m + "-" + day + var s = String(iso || "") + var m0 = s.match(/^(\d{4}-\d{2}-\d{2})/) + if (m0) return m0[1] + var d = beijingDate(iso) + return d.getUTCFullYear() + "-" + pad2(d.getUTCMonth() + 1) + "-" + pad2(d.getUTCDate()) } function archiveRunLog(reason) { var run = getLastRunLog() @@ -530,9 +539,9 @@ function signAll(targetUid, allUsers, source, archiveAfterRun) { function shortTime(iso) { if (!iso) return "未知时间" var s = String(iso) - var m = s.match(/(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})/) + var m = s.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/) if (m) return m[1] + " " + m[2] - return s.replace(/\.\d{3}Z$/, "Z") + return beijingTime(iso).slice(0, 16) } function displayMsg(msg) { msg = trim(msg || "无结果") diff --git a/tests/qdreader_plugin.test.mjs b/tests/qdreader_plugin.test.mjs index ebc36c1..89e5c82 100644 --- a/tests/qdreader_plugin.test.mjs +++ b/tests/qdreader_plugin.test.mjs @@ -268,6 +268,19 @@ test('manual sign also archives immediately after run', () => { assert.deepEqual(JSON.parse(r.store.qdreader_last_result_json), {}); }); +test('logs and archive dates use Beijing time even when runtime date is UTC', () => { + const store = { + qdreader_cookie_json: JSON.stringify({ '12345': cookie }), + qdreader_user_bind_json: JSON.stringify({ '12345': 'user-a' }), + }; + const r = runPlugin({ content: '', store, userId: 'cron', globals: { Date: fixedDateClass('2026-05-18T23:30:00.000Z') } }); + assert.match(r.replies[0], /记录时间:2026-05-19 07:30:00/); + assert.match(r.replies[0], /日志已归档:2026-05-19/); + const archive = JSON.parse(r.store.qdreader_archive_json); + assert.equal(archive['2026-05-19'].finishedAt, '2026-05-19 07:30:00'); + assert.equal(archive['2026-05-18'], undefined); +}); + test('query falls back to archived sign results after daily log was cleared', () => { const archiveRun = { source: 'cron',