fix(qdreader): archive daily sign logs at midnight

This commit is contained in:
2026-05-19 15:05:55 +08:00
parent 169710f129
commit 0e985c790a
5 changed files with 202 additions and 23 deletions
+12 -9
View File
@@ -1,6 +1,6 @@
# 启点读书 QDReader 签到 Autman 插件
QDReader/起点读书 Autman 签到插件。用户侧触发词统一使用“启点”,避免平台关键词麻烦;QX 只负责抓取 Cookie,账号管理、执行查询和签到都在 Autman 插件内完成。
QDReader/起点读书 Autman 签到插件。用户侧触发词统一使用“启点”,避免平台关键词麻烦;QX 只负责抓取 Cookie,账号管理、执行查询、日志归档和签到都在 Autman 插件内完成。
## 文件位置
@@ -93,8 +93,9 @@ hermes_qidian
qdreader_cookie_json # uid -> full cookie
qdreader_user_bind_json # uid -> sender.getUserID(),用户绑定索引
qdreader_remark_json # uid -> 备注名
qdreader_last_result_json # uid -> 单账号最后执行结果 JSON 字符串
qdreader_last_run_json # 最近一次执行批次日志、来源和汇总
qdreader_last_result_json # uid -> 单账号当日/当前批次结果 JSON 字符串(每日零点归档前清空)
qdreader_last_run_json # 当日/当前批次日志、来源和汇总(每日零点归档前清空)
qdreader_archive_json # yyyy-mm-dd -> 已归档批次日志,用于判断历史签到结果
qdreader_sign_api # 签名网关
qdreader_retry_count # 请求失败重试次数
qdreader_cron_silent # 定时无账号静默
@@ -127,20 +128,22 @@ set(key, value)
- 同时提取 `qdh/QDHeader/guid/qimei/qimei36` 供签名网关使用,其中 `qdh` 只取 Cookie 的 `qdh/QDH`,不再用 `QDHeader` 顶替,避免签名网关 `decodeQDHAuto` 解码失败。
2. `POST <qdreader_sign_api>/sign` 获取请求签名头。默认签名网关基址是 `https://api.120399.xyz/qdreader`
3. `POST https://h5.if.qidian.com/argus/api/v3/checkin/checkin` 完成签到。
4. 写入单账号最后结果 `qdreader_last_result_json`,包括 `ok/msg/time/source/runId`
5. 写入批次日志 `qdreader_last_run_json`,包括触发来源、开始/结束时间、成功/失败/总数和每账号结果;即使账号执行异常也会记录失败日志。
6. 汇总每个账号签到结果。
4. 写入单账号当日结果 `qdreader_last_result_json`,包括 `ok/msg/time/source/runId`
5. 写入当日批次日志 `qdreader_last_run_json`,包括触发来源、开始/结束时间、成功/失败/总数和每账号结果;即使账号执行异常也会记录失败日志。
6. 每日 00:00 的 cron 触发时,先把上一份 `qdreader_last_run_json` 按完成日期写入 `qdreader_archive_json`,再清空 `qdreader_last_result_json` / `qdreader_last_run_json`,然后执行新一天签到并反馈结果。
7. 汇总每个账号签到结果。
## 查询数据来源
`启点查询` 不请求起点实时接口,只读取最后执行日志:
```text
qdreader_last_run_json # 最近一次执行批次
qdreader_last_result_json # 每个 uid 最近一次执行结果
qdreader_last_run_json # 当日/当前批次
qdreader_last_result_json # 每个 uid 当日/当前批次结果
qdreader_archive_json # 每日零点归档后的历史批次
```
因此“查询”语义是“最近签到记录/最后执行日志”。刚添加但从未执行过的账号会显示“暂无执行记录”。
因此“查询”语义是“最近签到记录/最后执行日志”。刚添加但从未执行过的账号会显示“暂无执行记录”。如果当日日志已被零点任务清空,`启点查询` 会回退到最近归档,根据归档中的账号结果判断昨日/历史签到情况并反馈给用户。
## 本地验证
+67 -7
View File
@@ -4,7 +4,7 @@
//[author: Hermes]
//[service: BOSS]
//[class: 工具类]
//[version: 2.4.2]
//[version: 2.5.0]
//[platform: web,qq,wx,tg,tb,fs,we]
//[public: false]
//[price: 0]
@@ -14,7 +14,7 @@
//[rule: ^qdreader(.*)$]
//[admin: true]
//[priority: 600]
//[cron: 15 7 * * *]
//[cron: 0 0 * * *]
var BUCKET = "hermes_qidian"
@@ -26,6 +26,7 @@ var RETRY_KEY = "qdreader_retry_count"
var CRON_SILENT_KEY = "qdreader_cron_silent"
var LAST_RESULT_KEY = "qdreader_last_result_json"
var LAST_RUN_KEY = "qdreader_last_run_json"
var ARCHIVE_KEY = "qdreader_archive_json"
var DEFAULT_SIGN_API = "https://api.120399.xyz/qdreader"
var QIDIAN_BASE = "https://h5.if.qidian.com"
var CHECKIN_PATH = "/argus/api/v3/checkin/checkin"
@@ -259,7 +260,7 @@ function contentText() {
}
function usage() {
return [
"启点读书签到插件 v2.4.2",
"启点读书签到插件 v2.5.0",
"【一级菜单】",
"账号管理:启点账号",
"执行查询:启点查询",
@@ -269,7 +270,7 @@ function usage() {
"指定签到:启点签到 <uid>",
"【配置】",
"签名网关:Autman 参数 hermes_qidian.qdreader_sign_api,默认 " + DEFAULT_SIGN_API,
"定时:每日 07:15 自动执行;QX 只负责抓 cookie。"
"定时:每日 00:00 自动执行、归档上一日日志并清空当日日志QX 只负责抓 cookie。"
].join("\n")
}
function stripCommand(text) {
@@ -405,8 +406,50 @@ function getLastResults() {
}
function setLastRunLog(log) { cfgSet(LAST_RUN_KEY, JSON.stringify(log || {})) }
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
}
function archiveRunLog(reason) {
var run = getLastRunLog()
var archive = getArchiveStore()
var archived = false
var key = ""
if (run && run.finishedAt && run.total !== undefined) {
key = localDateKey(run.finishedAt)
archive[key] = run
archive[key].archivedAt = nowIso()
archive[key].archiveReason = reason || "daily-rollover"
archived = true
}
setArchiveStore(archive)
cfgSet(LAST_RESULT_KEY, "{}")
cfgSet(LAST_RUN_KEY, "{}")
return { archived: archived, key: key, archive: archive, run: run || {} }
}
function latestArchiveForVisible(uids) {
var archive = getArchiveStore()
var keys = []
for (var k in archive) if (archive.hasOwnProperty(k)) keys.push(k)
keys.sort()
for (var i = keys.length - 1; i >= 0; i--) {
var log = archive[keys[i]] || {}
var items = log.items || []
var map = {}
for (var j = 0; j < items.length; j++) map[String(items[j].uid)] = items[j]
if (!uids || !uids.length) return { key: keys[i], log: log, itemMap: map }
for (var u = 0; u < uids.length; u++) if (map[String(uids[u])]) return { key: keys[i], log: log, itemMap: map }
}
return null
}
function sourceName(source) {
if (source === "cron") return "定时任务"
if (source === "cron") return "定时任务/零点任务"
if (source === "menu") return "菜单立即执行"
if (source === "manual") return "手动签到"
return source || "未知"
@@ -467,6 +510,12 @@ function formatStatusLine(uid, last, remarks) {
if (!r) return "- " + name + ":暂无执行记录"
return "- " + name + "" + (r.ok ? "✅ " : "❌ ") + (r.msg || "无结果") + (r.time ? " @ " + r.time : "")
}
function formatArchiveStatusLine(uid, archiveInfo, remarks) {
var name = maskUid(uid) + (remarks && remarks[uid] ? "" + remarks[uid] + "" : "")
var r = archiveInfo && archiveInfo.itemMap ? archiveInfo.itemMap[String(uid)] : null
if (!r) return "- " + name + ":归档中暂无该账号记录"
return "- " + name + "" + (r.ok ? "✅ " : "❌ ") + (r.msg || "无结果") + (r.time ? " @ " + r.time : "")
}
function queryStatus(targetUid) {
var store = getStore()
var bind = getBindStore()
@@ -480,9 +529,19 @@ function queryStatus(targetUid) {
}
if (!uids.length) return "当前用户还没有保存启点读书CK"
var title = targetUid ? "启点单账号最近签到记录:" : "启点最近签到记录:"
var archiveInfo = latestArchiveForVisible(uids)
var useArchive = archiveInfo && (!run || !run.finishedAt)
if (useArchive) title = targetUid ? "启点单账号最近归档签到记录:" : "启点最近归档签到记录:"
var lines = [title]
if (!targetUid && run && run.finishedAt) lines.push("最近批次:" + sourceName(run.source) + " @ " + run.finishedAt + ";成功 " + (run.okCount || 0) + " / 失败 " + (run.failCount || 0) + " / 总计 " + (run.total || 0))
for (var i = 0; i < uids.length; i++) lines.push(formatStatusLine(uids[i], last, remarks))
if (useArchive) {
var arun = archiveInfo.log || {}
lines.push("最近归档:" + archiveInfo.key + "" + sourceName(arun.source) + " @ " + (arun.finishedAt || "未知时间") + ";成功 " + (arun.okCount || 0) + " / 失败 " + (arun.failCount || 0) + " / 总计 " + (arun.total || 0))
for (var ai = 0; ai < uids.length; ai++) lines.push(formatArchiveStatusLine(uids[ai], archiveInfo, remarks))
} else {
if (!targetUid && run && run.finishedAt) lines.push("最近批次:" + sourceName(run.source) + " @ " + run.finishedAt + ";成功 " + (run.okCount || 0) + " / 失败 " + (run.failCount || 0) + " / 总计 " + (run.total || 0))
for (var i = 0; i < uids.length; i++) lines.push(formatStatusLine(uids[i], last, remarks))
if (archiveInfo) lines.push("最近归档:" + archiveInfo.key + "(发送启点查询可按归档判断昨日签到)")
}
return lines.join("\n")
}
@@ -709,6 +768,7 @@ function main() {
var store = getStore()
var bind = getBindStore()
if (cmd === "sign" || /^(签到|sign|run)(?:\s+|$)/i.test(arg)) {
if (isCronRun) archiveRunLog("midnight-before-sign")
var target = trim(arg.replace(/^(签到|sign|run)\s*/i, ""))
var out = signAll(target, isCronRun, isCronRun ? "cron" : "manual")
if (!(isCronRun && getBool(CRON_SILENT_KEY, false) && out === "还没有保存启点读书CK")) reply(out)