fix(qdreader): archive immediately after sign
This commit is contained in:
@@ -66,10 +66,10 @@ GET /api/sms/stats
|
|||||||
|
|
||||||
- 正式源码:`plugins/qdreader/qdreader_sign_autman.js`
|
- 正式源码:`plugins/qdreader/qdreader_sign_autman.js`
|
||||||
- 实机 JS 副本:`js/qdreader_sign_autman.js`
|
- 实机 JS 副本:`js/qdreader_sign_autman.js`
|
||||||
- 定时运行:`cron: 15 7 * * *`,每日 07:15 自动签到
|
- 定时运行:`cron: 1 7 * * *`,每日 07:01 自动签到;签到结束后立即归档并清空最近运行日志
|
||||||
- 手动运行:`起点签到` / `起点签到 <uid>`
|
- 手动运行:`启点签到` / `启点签到 <uid>`
|
||||||
- Cookie 维护:`起点ck <cookie>` / `起点ck 查询` / `起点ck 导出` / `起点ck 删除 <uid|all>`
|
- Cookie 维护:`启点账号` / `启点ck <cookie>` / `启点查询`
|
||||||
- v2.2.0:增强 Autman 存储/消息/请求兼容,增加重试、签名返回兼容、定时静默开关、输出脱敏和 Node VM 测试。
|
- v2.5.7:保持单插件;定时只负责 07:01 签到,每次签到结束后立即归档运行日志,不再依赖零点归档定时。
|
||||||
|
|
||||||
Raw 导入:
|
Raw 导入:
|
||||||
|
|
||||||
@@ -88,5 +88,3 @@ node tests/qdreader_plugin.test.mjs
|
|||||||
- [Autman 插件开发规则与仓库约定](docs/autman-plugin-rules.md)
|
- [Autman 插件开发规则与仓库约定](docs/autman-plugin-rules.md)
|
||||||
- [JS 微服务路由插件模板](templates/js-router-plugin-template.js)
|
- [JS 微服务路由插件模板](templates/js-router-plugin-template.js)
|
||||||
- [Python middleware 插件模板](templates/python-middleware-plugin-template.py)
|
- [Python middleware 插件模板](templates/python-middleware-plugin-template.py)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+12
-10
@@ -4,17 +4,17 @@
|
|||||||
//[author: Hermes]
|
//[author: Hermes]
|
||||||
//[service: BOSS]
|
//[service: BOSS]
|
||||||
//[class: 工具类]
|
//[class: 工具类]
|
||||||
//[version: 2.5.5]
|
//[version: 2.5.7]
|
||||||
//[platform: web,qq,wx,tg,tb,fs,we]
|
//[platform: web,qq,wx,tg,tb,fs,we]
|
||||||
//[public: false]
|
//[public: false]
|
||||||
//[price: 0]
|
//[price: 0]
|
||||||
//[disable: false]
|
//[disable: false]
|
||||||
//[description: 启点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,支持手动“启点签到”;定时使用 `1 0,7 * * *`,每日 00:01 归档并清空日志、每日 07:01 签到;带独立 bucket、多用户隔离、请求重试、签名响应解析、结果摘要和安全脱敏。]
|
//[description: 启点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,支持手动“启点签到”;定时使用 `1 7 * * *` 每日 07:01 签到;每次签到运行结束后立即归档并清空最近运行日志,不再依赖单独归档定时;带独立 bucket、多用户隔离、请求重试、签名响应解析、结果摘要和安全脱敏。]
|
||||||
//[rule: ^启点(账号|查询|ck|CK|cookie|Cookie|签到|读书|归档|日志归档)(.*)$]
|
//[rule: ^启点(账号|查询|ck|CK|cookie|Cookie|签到|读书|归档|日志归档)(.*)$]
|
||||||
//[rule: ^qdreader(.*)$]
|
//[rule: ^qdreader(.*)$]
|
||||||
//[admin: true]
|
//[admin: true]
|
||||||
//[priority: 600]
|
//[priority: 600]
|
||||||
//[cron: 1 0,7 * * *]
|
//[cron: 1 7 * * *]
|
||||||
|
|
||||||
|
|
||||||
var BUCKET = "hermes_qidian"
|
var BUCKET = "hermes_qidian"
|
||||||
@@ -260,7 +260,7 @@ function contentText() {
|
|||||||
}
|
}
|
||||||
function usage() {
|
function usage() {
|
||||||
return [
|
return [
|
||||||
"启点读书签到插件 v2.5.5",
|
"启点读书签到插件 v2.5.7",
|
||||||
"【一级菜单】",
|
"【一级菜单】",
|
||||||
"账号管理:启点账号",
|
"账号管理:启点账号",
|
||||||
"执行查询:启点查询",
|
"执行查询:启点查询",
|
||||||
@@ -452,11 +452,7 @@ function latestArchiveForVisible(uids) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function cronMode() {
|
function cronMode() {
|
||||||
var d = new Date()
|
// Cron only runs the signing branch. Archive is performed immediately after each sign run.
|
||||||
var h = d.getHours ? d.getHours() : 7
|
|
||||||
// Single cron: 1 0,7 * * *
|
|
||||||
// 00:01 system run archives only; 07:01 system run signs only.
|
|
||||||
if (h === 0) return "archive"
|
|
||||||
return "sign"
|
return "sign"
|
||||||
}
|
}
|
||||||
function formatArchiveReply(ret) {
|
function formatArchiveReply(ret) {
|
||||||
@@ -484,8 +480,9 @@ function doCheckin(uid, cookie, source, runId) {
|
|||||||
setLastResult(uid, result)
|
setLastResult(uid, result)
|
||||||
return { uid: uid, ok: ok, msg: msg, raw: res, log: result }
|
return { uid: uid, ok: ok, msg: msg, raw: res, log: result }
|
||||||
}
|
}
|
||||||
function signAll(targetUid, allUsers, source) {
|
function signAll(targetUid, allUsers, source, archiveAfterRun) {
|
||||||
source = source || (allUsers ? "cron" : "manual")
|
source = source || (allUsers ? "cron" : "manual")
|
||||||
|
if (archiveAfterRun === undefined) archiveAfterRun = true
|
||||||
var startedAt = nowIso()
|
var startedAt = nowIso()
|
||||||
var runId = startedAt.replace(/[-:.TZ]/g, "").slice(0, 14) + "_" + source
|
var runId = startedAt.replace(/[-:.TZ]/g, "").slice(0, 14) + "_" + source
|
||||||
var store = getStore()
|
var store = getStore()
|
||||||
@@ -496,6 +493,7 @@ function signAll(targetUid, allUsers, source) {
|
|||||||
if (!uids.length) {
|
if (!uids.length) {
|
||||||
var emptyMsg = targetUid ? ("未找到 uid: " + targetUid) : "还没有保存启点读书CK"
|
var emptyMsg = targetUid ? ("未找到 uid: " + targetUid) : "还没有保存启点读书CK"
|
||||||
setLastRunLog({ runId: runId, source: source, startedAt: startedAt, finishedAt: nowIso(), targetUid: targetUid || "", total: 0, okCount: 0, failCount: 0, message: emptyMsg, items: [] })
|
setLastRunLog({ runId: runId, source: source, startedAt: startedAt, finishedAt: nowIso(), targetUid: targetUid || "", total: 0, okCount: 0, failCount: 0, message: emptyMsg, items: [] })
|
||||||
|
if (archiveAfterRun) archiveRunLog(source + "-after-sign")
|
||||||
return emptyMsg
|
return emptyMsg
|
||||||
}
|
}
|
||||||
var okCount = 0
|
var okCount = 0
|
||||||
@@ -522,6 +520,10 @@ function signAll(targetUid, allUsers, source) {
|
|||||||
setLastRunLog({ runId: runId, source: source, startedAt: startedAt, finishedAt: finishedAt, targetUid: targetUid || "", allUsers: !!allUsers, total: uids.length, okCount: okCount, failCount: failCount, items: items })
|
setLastRunLog({ runId: runId, source: source, startedAt: startedAt, finishedAt: finishedAt, targetUid: targetUid || "", allUsers: !!allUsers, total: uids.length, okCount: okCount, failCount: failCount, items: items })
|
||||||
lines.push("汇总:成功 " + okCount + " / 失败 " + failCount + " / 总计 " + uids.length)
|
lines.push("汇总:成功 " + okCount + " / 失败 " + failCount + " / 总计 " + uids.length)
|
||||||
lines.push("记录时间:" + finishedAt + "(" + sourceName(source) + ")")
|
lines.push("记录时间:" + finishedAt + "(" + sourceName(source) + ")")
|
||||||
|
if (archiveAfterRun) {
|
||||||
|
var ar = archiveRunLog(source + "-after-sign")
|
||||||
|
if (ar && ar.archived) lines.push("日志已归档:" + ar.key)
|
||||||
|
}
|
||||||
return lines.join("\n")
|
return lines.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -1,13 +1,13 @@
|
|||||||
# 启点读书 QDReader 签到 Autman 插件
|
# 启点读书 QDReader 签到 Autman 插件
|
||||||
|
|
||||||
QDReader/起点读书 Autman 签到插件。用户侧触发词统一使用“启点”,避免平台关键词麻烦;QX 只负责抓取 Cookie。账号管理、执行查询、签到和日志归档都在同一个 Autman 插件内完成;业务上使用一个 cron 表达式 `1 0,7 * * *`:每日 00:01 只归档清日志,每日 07:01 只执行签到。
|
QDReader/起点读书 Autman 签到插件。用户侧触发词统一使用“启点”,避免平台关键词麻烦;QX 只负责抓取 Cookie。账号管理、执行查询、签到和日志归档都在同一个 Autman 插件内完成;只使用一个简单 cron 表达式 `1 7 * * *`:每日 07:01 执行签到,签到运行结束后马上把本次运行日志写入归档并清空最近运行日志,不再依赖零点归档定时。
|
||||||
|
|
||||||
## 文件位置
|
## 文件位置
|
||||||
|
|
||||||
正式插件文件:
|
正式插件文件:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
plugins/qdreader/qdreader_sign_autman.js # 单插件单 cron:00:01 归档清日志;07:01 运行签到
|
plugins/qdreader/qdreader_sign_autman.js # 单插件单 cron:07:01 签到;运行后立即归档清日志
|
||||||
```
|
```
|
||||||
|
|
||||||
实机 JS 导入副本:
|
实机 JS 导入副本:
|
||||||
@@ -95,8 +95,8 @@ hermes_qidian
|
|||||||
qdreader_cookie_json # uid -> full cookie
|
qdreader_cookie_json # uid -> full cookie
|
||||||
qdreader_user_bind_json # uid -> sender.getUserID(),用户绑定索引
|
qdreader_user_bind_json # uid -> sender.getUserID(),用户绑定索引
|
||||||
qdreader_remark_json # uid -> 备注名
|
qdreader_remark_json # uid -> 备注名
|
||||||
qdreader_last_result_json # uid -> 单账号当日/当前批次结果 JSON 字符串(每日零点归档前清空)
|
qdreader_last_result_json # uid -> 单账号当前批次结果 JSON 字符串(签到运行后归档并清空)
|
||||||
qdreader_last_run_json # 当日/当前批次日志、来源和汇总(每日零点归档前清空)
|
qdreader_last_run_json # 当前批次日志、来源和汇总(签到运行后归档并清空)
|
||||||
qdreader_archive_json # yyyy-mm-dd -> 已归档批次日志,用于判断历史签到结果
|
qdreader_archive_json # yyyy-mm-dd -> 已归档批次日志,用于判断历史签到结果
|
||||||
qdreader_sign_api # 签名网关
|
qdreader_sign_api # 签名网关
|
||||||
qdreader_retry_count # 请求失败重试次数
|
qdreader_retry_count # 请求失败重试次数
|
||||||
@@ -132,10 +132,10 @@ set(key, value)
|
|||||||
3. `POST https://h5.if.qidian.com/argus/api/v3/checkin/checkin` 完成签到。
|
3. `POST https://h5.if.qidian.com/argus/api/v3/checkin/checkin` 完成签到。
|
||||||
4. 写入单账号当日结果 `qdreader_last_result_json`,包括 `ok/msg/time/source/runId`。
|
4. 写入单账号当日结果 `qdreader_last_result_json`,包括 `ok/msg/time/source/runId`。
|
||||||
5. 写入当日批次日志 `qdreader_last_run_json`,包括触发来源、开始/结束时间、成功/失败/总数和每账号结果;即使账号执行异常也会记录失败日志。
|
5. 写入当日批次日志 `qdreader_last_run_json`,包括触发来源、开始/结束时间、成功/失败/总数和每账号结果;即使账号执行异常也会记录失败日志。
|
||||||
6. 插件内只有一个 cron:`1 0,7 * * *`。
|
6. 插件内只有一个 cron:`1 7 * * *`。
|
||||||
7. 每日 00:01 触发时只做日志归档;系统触发按小时分流,0 点分支归档:把上一份 `qdreader_last_run_json` 按完成日期写入 `qdreader_archive_json`,再清空 `qdreader_last_result_json` / `qdreader_last_run_json`;不请求签名网关、不执行签到。
|
7. 每日 07:01 触发时执行签到。
|
||||||
8. 每日 07:01 触发时只执行签到并反馈结果,不做归档。
|
8. 签到运行结束后,马上把本次 `qdreader_last_run_json` 按完成日期写入 `qdreader_archive_json`,再清空 `qdreader_last_result_json` / `qdreader_last_run_json`;手动签到同样会立即归档。
|
||||||
9. 汇总每个账号签到结果。
|
9. 汇总每个账号签到结果,并在回复中提示归档日期。
|
||||||
|
|
||||||
## 查询数据来源
|
## 查询数据来源
|
||||||
|
|
||||||
@@ -144,10 +144,10 @@ set(key, value)
|
|||||||
```text
|
```text
|
||||||
qdreader_last_run_json # 当日/当前批次
|
qdreader_last_run_json # 当日/当前批次
|
||||||
qdreader_last_result_json # 每个 uid 当日/当前批次结果
|
qdreader_last_result_json # 每个 uid 当日/当前批次结果
|
||||||
qdreader_archive_json # 每日零点归档后的历史批次
|
qdreader_archive_json # 每次签到后立即归档的历史批次
|
||||||
```
|
```
|
||||||
|
|
||||||
因此“查询”语义是“最近签到记录/最后执行日志”。刚添加但从未执行过的账号会显示“暂无执行记录”。如果当日日志已被零点任务清空,`启点查询` 会回退到最近归档,根据归档中的账号结果判断昨日/历史签到情况并反馈给用户。
|
因此“查询”语义是“最近签到记录/最后执行日志”。刚添加但从未执行过的账号会显示“暂无执行记录”。因为签到完成后最近日志会立即归档并清空,`启点查询` 会回退到最近归档,根据归档中的账号结果判断最近签到情况并反馈给用户。
|
||||||
|
|
||||||
## 本地验证
|
## 本地验证
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,17 @@
|
|||||||
//[author: Hermes]
|
//[author: Hermes]
|
||||||
//[service: BOSS]
|
//[service: BOSS]
|
||||||
//[class: 工具类]
|
//[class: 工具类]
|
||||||
//[version: 2.5.5]
|
//[version: 2.5.7]
|
||||||
//[platform: web,qq,wx,tg,tb,fs,we]
|
//[platform: web,qq,wx,tg,tb,fs,we]
|
||||||
//[public: false]
|
//[public: false]
|
||||||
//[price: 0]
|
//[price: 0]
|
||||||
//[disable: false]
|
//[disable: false]
|
||||||
//[description: 启点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,支持手动“启点签到”;定时使用 `1 0,7 * * *`,每日 00:01 归档并清空日志、每日 07:01 签到;带独立 bucket、多用户隔离、请求重试、签名响应解析、结果摘要和安全脱敏。]
|
//[description: 启点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,支持手动“启点签到”;定时使用 `1 7 * * *` 每日 07:01 签到;每次签到运行结束后立即归档并清空最近运行日志,不再依赖单独归档定时;带独立 bucket、多用户隔离、请求重试、签名响应解析、结果摘要和安全脱敏。]
|
||||||
//[rule: ^启点(账号|查询|ck|CK|cookie|Cookie|签到|读书|归档|日志归档)(.*)$]
|
//[rule: ^启点(账号|查询|ck|CK|cookie|Cookie|签到|读书|归档|日志归档)(.*)$]
|
||||||
//[rule: ^qdreader(.*)$]
|
//[rule: ^qdreader(.*)$]
|
||||||
//[admin: true]
|
//[admin: true]
|
||||||
//[priority: 600]
|
//[priority: 600]
|
||||||
//[cron: 1 0,7 * * *]
|
//[cron: 1 7 * * *]
|
||||||
|
|
||||||
|
|
||||||
var BUCKET = "hermes_qidian"
|
var BUCKET = "hermes_qidian"
|
||||||
@@ -260,7 +260,7 @@ function contentText() {
|
|||||||
}
|
}
|
||||||
function usage() {
|
function usage() {
|
||||||
return [
|
return [
|
||||||
"启点读书签到插件 v2.5.5",
|
"启点读书签到插件 v2.5.7",
|
||||||
"【一级菜单】",
|
"【一级菜单】",
|
||||||
"账号管理:启点账号",
|
"账号管理:启点账号",
|
||||||
"执行查询:启点查询",
|
"执行查询:启点查询",
|
||||||
@@ -452,11 +452,7 @@ function latestArchiveForVisible(uids) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function cronMode() {
|
function cronMode() {
|
||||||
var d = new Date()
|
// Cron only runs the signing branch. Archive is performed immediately after each sign run.
|
||||||
var h = d.getHours ? d.getHours() : 7
|
|
||||||
// Single cron: 1 0,7 * * *
|
|
||||||
// 00:01 system run archives only; 07:01 system run signs only.
|
|
||||||
if (h === 0) return "archive"
|
|
||||||
return "sign"
|
return "sign"
|
||||||
}
|
}
|
||||||
function formatArchiveReply(ret) {
|
function formatArchiveReply(ret) {
|
||||||
@@ -484,8 +480,9 @@ function doCheckin(uid, cookie, source, runId) {
|
|||||||
setLastResult(uid, result)
|
setLastResult(uid, result)
|
||||||
return { uid: uid, ok: ok, msg: msg, raw: res, log: result }
|
return { uid: uid, ok: ok, msg: msg, raw: res, log: result }
|
||||||
}
|
}
|
||||||
function signAll(targetUid, allUsers, source) {
|
function signAll(targetUid, allUsers, source, archiveAfterRun) {
|
||||||
source = source || (allUsers ? "cron" : "manual")
|
source = source || (allUsers ? "cron" : "manual")
|
||||||
|
if (archiveAfterRun === undefined) archiveAfterRun = true
|
||||||
var startedAt = nowIso()
|
var startedAt = nowIso()
|
||||||
var runId = startedAt.replace(/[-:.TZ]/g, "").slice(0, 14) + "_" + source
|
var runId = startedAt.replace(/[-:.TZ]/g, "").slice(0, 14) + "_" + source
|
||||||
var store = getStore()
|
var store = getStore()
|
||||||
@@ -496,6 +493,7 @@ function signAll(targetUid, allUsers, source) {
|
|||||||
if (!uids.length) {
|
if (!uids.length) {
|
||||||
var emptyMsg = targetUid ? ("未找到 uid: " + targetUid) : "还没有保存启点读书CK"
|
var emptyMsg = targetUid ? ("未找到 uid: " + targetUid) : "还没有保存启点读书CK"
|
||||||
setLastRunLog({ runId: runId, source: source, startedAt: startedAt, finishedAt: nowIso(), targetUid: targetUid || "", total: 0, okCount: 0, failCount: 0, message: emptyMsg, items: [] })
|
setLastRunLog({ runId: runId, source: source, startedAt: startedAt, finishedAt: nowIso(), targetUid: targetUid || "", total: 0, okCount: 0, failCount: 0, message: emptyMsg, items: [] })
|
||||||
|
if (archiveAfterRun) archiveRunLog(source + "-after-sign")
|
||||||
return emptyMsg
|
return emptyMsg
|
||||||
}
|
}
|
||||||
var okCount = 0
|
var okCount = 0
|
||||||
@@ -522,6 +520,10 @@ function signAll(targetUid, allUsers, source) {
|
|||||||
setLastRunLog({ runId: runId, source: source, startedAt: startedAt, finishedAt: finishedAt, targetUid: targetUid || "", allUsers: !!allUsers, total: uids.length, okCount: okCount, failCount: failCount, items: items })
|
setLastRunLog({ runId: runId, source: source, startedAt: startedAt, finishedAt: finishedAt, targetUid: targetUid || "", allUsers: !!allUsers, total: uids.length, okCount: okCount, failCount: failCount, items: items })
|
||||||
lines.push("汇总:成功 " + okCount + " / 失败 " + failCount + " / 总计 " + uids.length)
|
lines.push("汇总:成功 " + okCount + " / 失败 " + failCount + " / 总计 " + uids.length)
|
||||||
lines.push("记录时间:" + finishedAt + "(" + sourceName(source) + ")")
|
lines.push("记录时间:" + finishedAt + "(" + sourceName(source) + ")")
|
||||||
|
if (archiveAfterRun) {
|
||||||
|
var ar = archiveRunLog(source + "-after-sign")
|
||||||
|
if (ar && ar.archived) lines.push("日志已归档:" + ar.key)
|
||||||
|
}
|
||||||
return lines.join("\n")
|
return lines.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,67 +238,34 @@ test('quick submit rejects save when Autman JS has no user identity', () => {
|
|||||||
assert.equal(r.store.qdreader_user_bind_json, undefined);
|
assert.equal(r.store.qdreader_user_bind_json, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('midnight cron archives previous run log and clears daily logs without running sign', () => {
|
test('cron signs then immediately archives and clears recent logs', () => {
|
||||||
const previousRun = {
|
|
||||||
runId: '20260518_cron',
|
|
||||||
source: 'cron',
|
|
||||||
startedAt: '2026-05-18T07:15:00.000Z',
|
|
||||||
finishedAt: '2026-05-18T07:15:02.000Z',
|
|
||||||
total: 1,
|
|
||||||
okCount: 1,
|
|
||||||
failCount: 0,
|
|
||||||
items: [{ uid: '12345', ok: true, msg: '昨日签到成功', time: '2026-05-18T07:15:01.000Z' }],
|
|
||||||
};
|
|
||||||
const store = {
|
const store = {
|
||||||
qdreader_cookie_json: JSON.stringify({ '12345': cookie }),
|
qdreader_cookie_json: JSON.stringify({ '12345': cookie }),
|
||||||
qdreader_user_bind_json: JSON.stringify({ '12345': 'user-a' }),
|
qdreader_user_bind_json: JSON.stringify({ '12345': 'user-a' }),
|
||||||
qdreader_last_run_json: JSON.stringify(previousRun),
|
|
||||||
qdreader_last_result_json: JSON.stringify({ '12345': JSON.stringify({ ok: true, msg: '昨日签到成功' }) }),
|
|
||||||
};
|
|
||||||
const r = runPlugin({ content: '', store, userId: 'cron', globals: { Date: fixedDateClass('2026-05-19T00:00:00.000+08:00') } });
|
|
||||||
assert.equal(r.calls.length, 0);
|
|
||||||
assert.match(r.replies[0], /日志归档完成/);
|
|
||||||
const archive = JSON.parse(r.store.qdreader_archive_json);
|
|
||||||
assert.equal(archive['2026-05-18'].runId, '20260518_cron');
|
|
||||||
assert.equal(archive['2026-05-18'].items[0].msg, '昨日签到成功');
|
|
||||||
assert.deepEqual(JSON.parse(r.store.qdreader_last_run_json), {});
|
|
||||||
assert.deepEqual(JSON.parse(r.store.qdreader_last_result_json), {});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test('single cron midnight branch archives without signing', () => {
|
|
||||||
const previousRun = {
|
|
||||||
runId: '20260518_cron',
|
|
||||||
source: 'cron',
|
|
||||||
finishedAt: '2026-05-18T07:15:02.000Z',
|
|
||||||
total: 1,
|
|
||||||
okCount: 1,
|
|
||||||
failCount: 0,
|
|
||||||
items: [{ uid: '12345', ok: true, msg: '昨日签到成功' }],
|
|
||||||
};
|
|
||||||
const store = {
|
|
||||||
qdreader_cookie_json: JSON.stringify({ '12345': cookie }),
|
|
||||||
qdreader_user_bind_json: JSON.stringify({ '12345': 'user-a' }),
|
|
||||||
qdreader_last_run_json: JSON.stringify(previousRun),
|
|
||||||
qdreader_last_result_json: JSON.stringify({ '12345': JSON.stringify({ ok: true, msg: '昨日签到成功' }) }),
|
|
||||||
};
|
|
||||||
const r = runPlugin({ content: '', store, userId: 'cron', globals: { Date: fixedDateClass('2026-05-19T00:01:00.000+08:00') } });
|
|
||||||
assert.equal(r.calls.length, 0);
|
|
||||||
assert.match(r.replies[0], /日志归档完成/);
|
|
||||||
assert.equal(JSON.parse(r.store.qdreader_archive_json)['2026-05-18'].runId, '20260518_cron');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test('single cron morning branch signs without archiving', () => {
|
|
||||||
const store = {
|
|
||||||
qdreader_cookie_json: JSON.stringify({ '12345': cookie }),
|
|
||||||
qdreader_user_bind_json: JSON.stringify({ '12345': 'user-a' }),
|
|
||||||
qdreader_last_run_json: JSON.stringify({ source: 'cron', finishedAt: '2026-05-18T07:15:02.000Z', total: 1, okCount: 1, failCount: 0, items: [{ uid: '12345', ok: true }] }),
|
|
||||||
};
|
};
|
||||||
const r = runPlugin({ content: '', store, userId: 'cron', globals: { Date: fixedDateClass('2026-05-19T07:01:00.000+08:00') } });
|
const r = runPlugin({ content: '', store, userId: 'cron', globals: { Date: fixedDateClass('2026-05-19T07:01:00.000+08:00') } });
|
||||||
assert.equal(r.calls.length, 2);
|
assert.equal(r.calls.length, 2);
|
||||||
assert.match(r.replies[0], /启点读书签到结果/);
|
assert.match(r.replies[0], /启点读书签到结果/);
|
||||||
assert.equal(r.store.qdreader_archive_json, undefined);
|
assert.match(r.replies[0], /日志已归档:2026-05-19/);
|
||||||
|
const archive = JSON.parse(r.store.qdreader_archive_json);
|
||||||
|
assert.equal(archive['2026-05-19'].source, 'cron');
|
||||||
|
assert.equal(archive['2026-05-19'].okCount, 1);
|
||||||
|
assert.equal(archive['2026-05-19'].items[0].uid, '12345');
|
||||||
|
assert.deepEqual(JSON.parse(r.store.qdreader_last_run_json), {});
|
||||||
|
assert.deepEqual(JSON.parse(r.store.qdreader_last_result_json), {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('manual sign also archives immediately after run', () => {
|
||||||
|
const store = {
|
||||||
|
qdreader_cookie_json: JSON.stringify({ '12345': cookie }),
|
||||||
|
qdreader_user_bind_json: JSON.stringify({ '12345': 'user-a' }),
|
||||||
|
};
|
||||||
|
const r = runPlugin({ content: '启点签到', store, userId: 'user-a', globals: { Date: fixedDateClass('2026-05-19T08:01:00.000+08:00') } });
|
||||||
|
assert.equal(r.calls.length, 2);
|
||||||
|
assert.match(r.replies[0], /日志已归档:2026-05-19/);
|
||||||
|
assert.equal(JSON.parse(r.store.qdreader_archive_json)['2026-05-19'].source, 'manual');
|
||||||
|
assert.deepEqual(JSON.parse(r.store.qdreader_last_run_json), {});
|
||||||
|
assert.deepEqual(JSON.parse(r.store.qdreader_last_result_json), {});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('query falls back to archived sign results after daily log was cleared', () => {
|
test('query falls back to archived sign results after daily log was cleared', () => {
|
||||||
@@ -339,9 +306,10 @@ test('manual sign and cron sign both call sign gateway then qidian checkin', ()
|
|||||||
assert.match(manual.calls[1].url, /h5\.if\.qidian\.com\/argus\/api\/v3\/checkin\/checkin$/);
|
assert.match(manual.calls[1].url, /h5\.if\.qidian\.com\/argus\/api\/v3\/checkin\/checkin$/);
|
||||||
assert.match(manual.replies[0], /✅ 12\*\*\*45/);
|
assert.match(manual.replies[0], /✅ 12\*\*\*45/);
|
||||||
assert.match(manual.replies[0], /记录时间:/);
|
assert.match(manual.replies[0], /记录时间:/);
|
||||||
assert.equal(JSON.parse(manual.store.qdreader_last_run_json).source, 'manual');
|
assert.equal(JSON.parse(manual.store.qdreader_archive_json)['2026-05-20'].source, 'manual');
|
||||||
assert.equal(JSON.parse(manual.store.qdreader_last_run_json).okCount, 1);
|
assert.equal(JSON.parse(manual.store.qdreader_archive_json)['2026-05-20'].okCount, 1);
|
||||||
assert.equal(JSON.parse(JSON.parse(manual.store.qdreader_last_result_json)['12345']).source, 'manual');
|
assert.deepEqual(JSON.parse(manual.store.qdreader_last_run_json), {});
|
||||||
|
assert.deepEqual(JSON.parse(manual.store.qdreader_last_result_json), {});
|
||||||
const payload = JSON.parse(manual.calls[0].body);
|
const payload = JSON.parse(manual.calls[0].body);
|
||||||
assert.equal(payload.qdh, 'qdh');
|
assert.equal(payload.qdh, 'qdh');
|
||||||
assert.equal(payload.qdheader, 'MTIzNDV8eA==');
|
assert.equal(payload.qdheader, 'MTIzNDV8eA==');
|
||||||
@@ -349,8 +317,8 @@ test('manual sign and cron sign both call sign gateway then qidian checkin', ()
|
|||||||
const cron = runPlugin({ content: '', store: { ...store }, globals: { Date: fixedDateClass('2026-05-19T07:01:00.000+08:00') } });
|
const cron = runPlugin({ content: '', store: { ...store }, globals: { Date: fixedDateClass('2026-05-19T07:01:00.000+08:00') } });
|
||||||
assert.equal(cron.calls.length, 2);
|
assert.equal(cron.calls.length, 2);
|
||||||
assert.match(cron.replies[0], /启点读书签到结果/);
|
assert.match(cron.replies[0], /启点读书签到结果/);
|
||||||
assert.equal(JSON.parse(cron.store.qdreader_last_run_json).source, 'cron');
|
assert.equal(JSON.parse(cron.store.qdreader_archive_json)['2026-05-19'].source, 'cron');
|
||||||
assert.equal(cron.store.qdreader_archive_json, undefined);
|
assert.deepEqual(JSON.parse(cron.store.qdreader_last_run_json), {});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('bucketGet/bucketSet fallback rejects when user identity is absent', () => {
|
test('bucketGet/bucketSet fallback rejects when user identity is absent', () => {
|
||||||
@@ -477,12 +445,12 @@ test('failed sign exceptions are written to last execution log', () => {
|
|||||||
};
|
};
|
||||||
const r = runPlugin({ content: '启点签到', store, userId: 'user-a', requests: [new Error('sign api down'), new Error('sign api down')] });
|
const r = runPlugin({ content: '启点签到', store, userId: 'user-a', requests: [new Error('sign api down'), new Error('sign api down')] });
|
||||||
assert.match(r.replies[0], /sign api down/);
|
assert.match(r.replies[0], /sign api down/);
|
||||||
const lastRun = JSON.parse(r.store.qdreader_last_run_json);
|
const archived = JSON.parse(r.store.qdreader_archive_json);
|
||||||
|
const lastRun = archived[Object.keys(archived)[0]];
|
||||||
assert.equal(lastRun.failCount, 1);
|
assert.equal(lastRun.failCount, 1);
|
||||||
assert.equal(lastRun.items[0].error, true);
|
assert.equal(lastRun.items[0].error, true);
|
||||||
const lastResult = JSON.parse(JSON.parse(r.store.qdreader_last_result_json)['12345']);
|
assert.deepEqual(JSON.parse(r.store.qdreader_last_run_json), {});
|
||||||
assert.equal(lastResult.ok, false);
|
assert.deepEqual(JSON.parse(r.store.qdreader_last_result_json), {});
|
||||||
assert.equal(lastResult.msg, 'sign api down');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('legacy ck query points to separated commands', () => {
|
test('legacy ck query points to separated commands', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user