diff --git a/js/qdreader_sign_autman.js b/js/qdreader_sign_autman.js index 9d0e993..912980d 100644 --- a/js/qdreader_sign_autman.js +++ b/js/qdreader_sign_autman.js @@ -4,7 +4,7 @@ //[author: Hermes] //[service: BOSS] //[class: 工具类] -// [version: 2.15.2] +// [version: 2.15.3] // [platform: web,qq,wx,tg,tb,fs,we] // [public: false] // [price: 0] @@ -91,6 +91,22 @@ function reply(msg) { try { if (typeof push === "function") { push({ content: out }); return } } catch (e) {} try { console.log(out) } catch (e) {} } +function pushToUser(userId, content) { + userId = trim(userId || "") + content = typeof content === "string" ? content : JSON.stringify(content) + if (!userId || !content) return false + if (typeof push !== "function") return false + var payloads = [ + { imType: "wx", userID: userId, content: content }, + { imType: "we", userID: userId, content: content }, + { imType: "web", userID: userId, content: content }, + { userID: userId, content: content } + ] + for (var i = 0; i < payloads.length; i++) { + try { push(payloads[i]); return true } catch (e) {} + } + return false +} function debug(msg) { try { if (typeof Debug === "function") Debug(String(msg)); else console.log(String(msg)) } catch (e) {} } function cleanStringMap(obj) { @@ -286,7 +302,7 @@ function contentText() { } function usage() { return [ - "启点读书签到插件 v2.15.2", + "启点读书签到插件 v2.15.3", "【一级菜单】", "账号管理:启点账号", "执行查询:启点查询", @@ -874,6 +890,7 @@ function signAll(targetUid, allUsers, source, archiveAfterRun) { var failCount = 0 var items = [] var accountReplies = [] + var accountNotices = [] for (var i = 0; i < uids.length; i++) { var uid = uids[i] var prefix = "启点读书签到结果:\n" @@ -883,14 +900,18 @@ function signAll(targetUid, allUsers, source, archiveAfterRun) { var r = doCheckin(uid, store[uid], source, runId) if (r.ok) okCount++; else failCount++ items.push({ uid: String(uid), ok: !!r.ok, msg: String(r.msg || ""), time: r.log.time, remark: remarks[uid] || "" }) - accountReplies.push(prefix + (r.ok ? "✅ " : "❌ ") + maskUid(uid) + suffix + ":" + r.msg) + var msg = prefix + (r.ok ? "✅ " : "❌ ") + maskUid(uid) + suffix + ":" + r.msg + accountReplies.push(msg) + accountNotices.push({ uid: String(uid), owner: bind[String(uid)] || "", msg: msg }) } catch (e) { failCount++ var emsg = e && e.message ? e.message : String(e) var er = { uid: String(uid), ok: false, msg: emsg, time: nowIso(), startedAt: startedAt, source: source, runId: runId, error: true, remark: remarks[uid] || "" } setLastResult(uid, er) items.push(er) - accountReplies.push(prefix + "❌ " + maskUid(uid) + suffix + ":" + emsg) + var errMsg = prefix + "❌ " + maskUid(uid) + suffix + ":" + emsg + accountReplies.push(errMsg) + accountNotices.push({ uid: String(uid), owner: bind[String(uid)] || "", msg: errMsg }) } } var finishedAt = nowIso() @@ -898,9 +919,18 @@ function signAll(targetUid, allUsers, source, archiveAfterRun) { var tail = [] tail.push("汇总:成功 " + okCount + " / 失败 " + failCount + " / 总计 " + uids.length) tail.push("记录时间:" + finishedAt + "(" + sourceName(source) + ")") + var archiveLine = "" if (archiveAfterRun) { var ar = archiveRunLog(source + "-after-sign") - if (ar && ar.archived) tail.push("日志已归档:" + ar.key) + if (ar && ar.archived) { archiveLine = "日志已归档:" + ar.key; tail.push(archiveLine) } + } + if (allUsers && source === "cron") { + for (var pi = 0; pi < accountNotices.length; pi++) { + var notice = accountNotices[pi] + var body = notice.msg + "\n" + tail.join("\n") + if (!pushToUser(notice.owner, body)) debug("QDReader cron push skipped for uid " + notice.uid + ": empty owner or push unavailable") + } + return tail.join("\n") } if (accountReplies.length > 1) { for (var ai = 0; ai < accountReplies.length; ai++) reply(accountReplies[ai]) diff --git a/plugins/qdreader/qdreader_sign_autman.js b/plugins/qdreader/qdreader_sign_autman.js index 9d0e993..912980d 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.15.2] +// [version: 2.15.3] // [platform: web,qq,wx,tg,tb,fs,we] // [public: false] // [price: 0] @@ -91,6 +91,22 @@ function reply(msg) { try { if (typeof push === "function") { push({ content: out }); return } } catch (e) {} try { console.log(out) } catch (e) {} } +function pushToUser(userId, content) { + userId = trim(userId || "") + content = typeof content === "string" ? content : JSON.stringify(content) + if (!userId || !content) return false + if (typeof push !== "function") return false + var payloads = [ + { imType: "wx", userID: userId, content: content }, + { imType: "we", userID: userId, content: content }, + { imType: "web", userID: userId, content: content }, + { userID: userId, content: content } + ] + for (var i = 0; i < payloads.length; i++) { + try { push(payloads[i]); return true } catch (e) {} + } + return false +} function debug(msg) { try { if (typeof Debug === "function") Debug(String(msg)); else console.log(String(msg)) } catch (e) {} } function cleanStringMap(obj) { @@ -286,7 +302,7 @@ function contentText() { } function usage() { return [ - "启点读书签到插件 v2.15.2", + "启点读书签到插件 v2.15.3", "【一级菜单】", "账号管理:启点账号", "执行查询:启点查询", @@ -874,6 +890,7 @@ function signAll(targetUid, allUsers, source, archiveAfterRun) { var failCount = 0 var items = [] var accountReplies = [] + var accountNotices = [] for (var i = 0; i < uids.length; i++) { var uid = uids[i] var prefix = "启点读书签到结果:\n" @@ -883,14 +900,18 @@ function signAll(targetUid, allUsers, source, archiveAfterRun) { var r = doCheckin(uid, store[uid], source, runId) if (r.ok) okCount++; else failCount++ items.push({ uid: String(uid), ok: !!r.ok, msg: String(r.msg || ""), time: r.log.time, remark: remarks[uid] || "" }) - accountReplies.push(prefix + (r.ok ? "✅ " : "❌ ") + maskUid(uid) + suffix + ":" + r.msg) + var msg = prefix + (r.ok ? "✅ " : "❌ ") + maskUid(uid) + suffix + ":" + r.msg + accountReplies.push(msg) + accountNotices.push({ uid: String(uid), owner: bind[String(uid)] || "", msg: msg }) } catch (e) { failCount++ var emsg = e && e.message ? e.message : String(e) var er = { uid: String(uid), ok: false, msg: emsg, time: nowIso(), startedAt: startedAt, source: source, runId: runId, error: true, remark: remarks[uid] || "" } setLastResult(uid, er) items.push(er) - accountReplies.push(prefix + "❌ " + maskUid(uid) + suffix + ":" + emsg) + var errMsg = prefix + "❌ " + maskUid(uid) + suffix + ":" + emsg + accountReplies.push(errMsg) + accountNotices.push({ uid: String(uid), owner: bind[String(uid)] || "", msg: errMsg }) } } var finishedAt = nowIso() @@ -898,9 +919,18 @@ function signAll(targetUid, allUsers, source, archiveAfterRun) { var tail = [] tail.push("汇总:成功 " + okCount + " / 失败 " + failCount + " / 总计 " + uids.length) tail.push("记录时间:" + finishedAt + "(" + sourceName(source) + ")") + var archiveLine = "" if (archiveAfterRun) { var ar = archiveRunLog(source + "-after-sign") - if (ar && ar.archived) tail.push("日志已归档:" + ar.key) + if (ar && ar.archived) { archiveLine = "日志已归档:" + ar.key; tail.push(archiveLine) } + } + if (allUsers && source === "cron") { + for (var pi = 0; pi < accountNotices.length; pi++) { + var notice = accountNotices[pi] + var body = notice.msg + "\n" + tail.join("\n") + if (!pushToUser(notice.owner, body)) debug("QDReader cron push skipped for uid " + notice.uid + ": empty owner or push unavailable") + } + return tail.join("\n") } if (accountReplies.length > 1) { for (var ai = 0; ai < accountReplies.length; ai++) reply(accountReplies[ai]) diff --git a/tests/qdreader_plugin.test.mjs b/tests/qdreader_plugin.test.mjs index ff8a1b4..cf897dc 100644 --- a/tests/qdreader_plugin.test.mjs +++ b/tests/qdreader_plugin.test.mjs @@ -20,12 +20,16 @@ function fixedDateClass(iso) { function runPlugin({ pluginCode = code, filename = pluginPath, content = '', store = {}, bucketStore = {}, requests = [], userId = 'user-a', listens = [], senderOverrides = {}, globals = {}, admin = true } = {}) { const replies = []; + const pushes = []; const calls = []; let requestIndex = 0; let listenIndex = 0; const baseSender = { reply: (msg) => replies.push(String(msg)), - push: (msg) => replies.push(typeof msg === 'string' ? msg : String((msg && (msg.content || msg.message || msg.text)) || JSON.stringify(msg))), + push: (msg) => { + pushes.push(msg); + replies.push(typeof msg === 'string' ? msg : String((msg && (msg.content || msg.message || msg.text)) || JSON.stringify(msg))); + }, sendText: (msg) => replies.push(String(msg)), getMessage: () => content, getContent: () => content, @@ -38,7 +42,10 @@ function runPlugin({ pluginCode = code, filename = pluginPath, content = '', sto Buffer, sender: { ...baseSender, ...senderOverrides }, sendText: (msg) => replies.push(String(msg)), - push: (msg) => replies.push(typeof msg === 'string' ? msg : String((msg && (msg.content || msg.message || msg.text)) || JSON.stringify(msg))), + push: (msg) => { + pushes.push(msg); + replies.push(typeof msg === 'string' ? msg : String((msg && (msg.content || msg.message || msg.text)) || JSON.stringify(msg))); + }, GetUserID: () => userId, GetContent: () => content, getContent: () => content, @@ -60,7 +67,7 @@ function runPlugin({ pluginCode = code, filename = pluginPath, content = '', sto }; vm.createContext(ctx); vm.runInContext(pluginCode, ctx, { filename }); - return { replies, calls, store, bucketStore }; + return { replies, pushes, calls, store, bucketStore }; } const cookie = 'uid=12345; QDHeader=MTIzNDV8eA==; qdh=qdh; ywguid=guid; qimei=qimei; qimei36=qimei36; sessionyw=s; appId=10'; @@ -769,9 +776,13 @@ test('cron detection uses ImType cron/fake and does not consume input as command }, }); assert.equal(r.calls.length, 4, imType); - assert.equal(r.replies.length, 1, imType); - assert.match(r.replies[0], /汇总:成功 1 \/ 失败 0 \/ 总计 1/, imType); - assert.match(r.replies[0], /定时任务\/零点任务/, imType); + assert.equal(r.replies.length, 2, imType); + assert.equal(r.pushes.length, 1, imType); + assert.equal(r.pushes[0].userID, 'user-a', imType); + assert.match(r.pushes[0].content, /汇总:成功 1 \/ 失败 0 \/ 总计 1/, imType); + assert.match(r.pushes[0].content, /日志已归档:2026-05-19/, imType); + assert.match(r.replies.join('\n'), /汇总:成功 1 \/ 失败 0 \/ 总计 1/, imType); + assert.match(r.replies.join('\n'), /定时任务\/零点任务/, imType); } }); @@ -786,7 +797,9 @@ test('manual sign only runs current user accounts while cron runs all accounts', const cron = runPlugin({ content: '', store: { ...store }, userId: 'cron', globals: { Date: fixedDateClass('2026-05-19T07:01:00.000+08:00') } }); assert.equal(cron.calls.length, 8); assert.equal(cron.replies.length, 3); - assert.match(cron.replies[2], /总计 2/); + assert.equal(cron.pushes.length, 2); + assert.deepEqual(cron.pushes.map((p) => p.userID).sort(), ['user-a', 'user-b']); + assert.match(cron.replies.join('\n'), /总计 2/); }); test('unbound global cookies are not visible or usable by normal users', () => {