feat: harden QDReader Autman plugin

This commit is contained in:
2026-05-17 03:57:11 +08:00
parent b5c9c18840
commit d9ff1d94b2
5 changed files with 593 additions and 166 deletions
+205 -80
View File
@@ -4,54 +4,101 @@
//[author: Hermes]
//[service: BOSS]
//[class: 工具类]
//[version: 2.1.0]
//[platform: web,qq,wx,tg,tb]
//[version: 2.2.0]
//[platform: web,qq,wx,tg,tb,fs,we]
//[public: false]
//[price: 0]
//[disable: false]
//[description: 起点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,支持手动“起点签到”和每日 07:15 定时签到。签名沿用上游 QDReader 的 api.120399.xyz 签名网关。]
//[description: 起点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,支持手动“起点签到”和每日 07:15 定时签到;带多环境存储兼容、请求重试、签名响应兼容、结果摘要和安全脱敏。]
//[rule: ^起点(ck|CK|cookie|Cookie|签到|读书)(.*)$]
//[rule: ^qdreader(.*)$]
//[admin: true]
//[priority: 600]
//[cron: 15 7 * * *]
//[param: {"required":false,"key":"otto.qdreader_cookie_json","bool":false,"placeholder":"{\"uid\":\"cookie\"}","name":"QDReader Cookie JSON","desc":"机器人交互维护,JSON Map 格式"}]
//[param: {"required":false,"key":"otto.qdreader_sign_api","bool":false,"placeholder":"https://api.120399.xyz","name":"QDReader 签名网关","desc":"默认 https://api.120399.xyz"}]
//[param: {"required":false,"key":"otto.qdreader_sign_api","bool":false,"placeholder":"https://api.120399.xyz","name":"QDReader 签名网关","desc":"默认 https://api.120399.xyz,可替换为兼容 /sign 的网关"}]
//[param: {"required":false,"key":"otto.qdreader_retry_count","bool":false,"placeholder":"1","name":"请求失败重试次数","desc":"默认 1,最大 3,仅对网络异常/非业务异常重试"}]
//[param: {"required":false,"key":"otto.qdreader_cron_silent","bool":true,"placeholder":"false","name":"定时无账号时静默","desc":"true 时定时触发且没有账号不回复"}]
var BUCKET = "otto"
var STORE_KEY = "qdreader_cookie_json"
var SIGN_API_KEY = "qdreader_sign_api"
var RETRY_KEY = "qdreader_retry_count"
var CRON_SILENT_KEY = "qdreader_cron_silent"
var DEFAULT_SIGN_API = "https://api.120399.xyz"
var QIDIAN_BASE = "https://h5.if.qidian.com"
var CHECKIN_PATH = "/argus/api/v3/checkin/checkin"
function defined(name) { try { return typeof this[name] !== "undefined" } catch (e) { return false } }
function globalValue(name) { try { return this[name] } catch (e) { return undefined } }
function safeJsonParse(s, def) { try { return JSON.parse(s) } catch (e) { return def } }
function isObject(x) { return !!x && typeof x === "object" && !Array.isArray(x) }
function trim(s) { return String(s === undefined || s === null ? "" : s).replace(/^\s+|\s+$/g, "") }
function lower(s) { return String(s || "").toLowerCase() }
function cfgGet(key, def) {
try { if (typeof get === "function") { var v = get(key); if (v !== undefined && v !== null && String(v) !== "") return v } } catch (e) {}
try { if (typeof bucketGet === "function") { var bv = bucketGet(BUCKET, key); if (bv !== undefined && bv !== null && String(bv) !== "") return bv } } catch (e) {}
return def
}
function cfgSet(key, value) {
var s = String(value)
var ok = false
try { if (typeof set === "function") { set(key, s); ok = true } } catch (e) {}
try { if (typeof bucketSet === "function") { bucketSet(BUCKET, key, s); ok = true } } catch (e) {}
if (!ok) throw new Error("当前 Autman 环境没有 set/bucketSet,无法保存配置")
return s
}
function getBool(key, def) {
var v = cfgGet(key, def ? "true" : "false")
if (typeof v === "boolean") return v
v = lower(trim(v))
if (v === "1" || v === "true" || v === "yes" || v === "on" || v === "启用" || v === "是") return true
if (v === "0" || v === "false" || v === "no" || v === "off" || v === "禁用" || v === "否") return false
return !!def
}
function getInt(key, def, min, max) {
var n = parseInt(cfgGet(key, String(def)), 10)
if (isNaN(n)) n = def
if (min !== undefined && n < min) n = min
if (max !== undefined && n > max) n = max
return n
}
function reply(msg) {
var out = typeof msg === "string" ? msg : JSON.stringify(msg)
if (typeof sender !== "undefined" && sender.reply) sender.reply(out)
else if (typeof sendText === "function") sendText(out)
else if (typeof push === "function") push({ content: out })
else console.log(out)
if (!out) return
try { if (typeof sender !== "undefined" && sender && sender.reply) { sender.reply(out); return } } catch (e) {}
try { if (typeof sendText === "function") { sendText(out); return } } catch (e) {}
try { if (typeof push === "function") { push({ content: out }); return } } catch (e) {}
try { console.log(out) } catch (e) {}
}
function safeJsonParse(s, def) { try { return JSON.parse(s) } catch (e) { return def } }
function debug(msg) { try { if (typeof Debug === "function") Debug(String(msg)); else console.log(String(msg)) } catch (e) {} }
function getStore() {
var raw = get(STORE_KEY) || "{}"
var raw = cfgGet(STORE_KEY, "{}")
var obj = safeJsonParse(raw, {})
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return {}
return obj
if (!isObject(obj)) return {}
var clean = {}
for (var uid in obj) if (obj.hasOwnProperty(uid) && obj[uid]) clean[String(uid)] = String(obj[uid])
return clean
}
function setStore(obj) { var s = JSON.stringify(obj); set(STORE_KEY, s); return s }
function setStore(obj) { return cfgSet(STORE_KEY, JSON.stringify(obj || {})) }
function parseCookieString(cookie) {
var jar = {}
String(cookie || "").split(";").forEach(function (part) {
var i = part.indexOf("=")
if (i <= 0) return
var k = part.slice(0, i).trim()
var v = part.slice(i + 1).trim()
var k = trim(part.slice(0, i))
var v = trim(part.slice(i + 1))
if (k) jar[k] = v
})
return jar
}
function cookieToString(jar) {
var arr = []
for (var k in jar) if (jar[k] !== undefined && jar[k] !== null && jar[k] !== "") arr.push(k + "=" + jar[k])
for (var k in jar) if (jar.hasOwnProperty(k) && jar[k] !== undefined && jar[k] !== null && jar[k] !== "") arr.push(k + "=" + jar[k])
return arr.join("; ")
}
function b64decode(s) {
@@ -59,41 +106,62 @@ function b64decode(s) {
try { if (typeof Buffer !== "undefined") return Buffer.from(s, "base64").toString("utf8") } catch (e) {}
try { return decodeURIComponent(escape(atob(s))) } catch (e) { return "" }
}
function uidFromQDHeader(qdheader) { var decoded = b64decode(qdheader); return decoded ? String(decoded.split("|")[0] || "").trim() : "" }
function uidFromQDHeader(qdheader) {
var decoded = b64decode(qdheader)
var uid = decoded ? trim(decoded.split("|")[0] || "") : ""
return /^\d+$/.test(uid) ? uid : ""
}
function extractUid(cookie) {
var jar = parseCookieString(cookie)
var uid = uidFromQDHeader(jar.QDHeader || jar.qdheader || jar.QDHEADER || "")
if (uid) return uid
if (jar.uid) return String(jar.uid)
if (jar.userid) return String(jar.userid)
return ""
}
function maskCookie(cookie) { cookie = String(cookie || ""); return cookie.length <= 24 ? cookie : cookie.slice(0, 12) + "..." + cookie.slice(-8) }
function maskCookie(cookie) {
cookie = String(cookie || "")
if (!cookie) return ""
var jar = parseCookieString(cookie)
var keys = []
for (var k in jar) if (jar.hasOwnProperty(k)) keys.push(k)
var prefix = keys.slice(0, 5).join(",")
return "[cookie keys:" + prefix + (keys.length > 5 ? ",..." : "") + "; len=" + cookie.length + "]"
}
function maskUid(uid) {
uid = String(uid || "")
if (uid.length <= 4) return uid
return uid.slice(0, 2) + "***" + uid.slice(-2)
}
function contentText() {
var s = ""
try { if (typeof getContent === "function") s = getContent() || "" } catch (e) {}
try { if (!s && typeof GetContent === "function") s = GetContent() || "" } catch (e) {}
try { if (!s && typeof sender !== "undefined" && sender.getContent) s = sender.getContent() || "" } catch (e) {}
try { if (!s && typeof sender !== "undefined" && sender.getMessage) s = sender.getMessage() || "" } catch (e) {}
try { if (!s && typeof input === "function") s = input() || "" } catch (e) {}
try { if (!s && typeof sender !== "undefined" && sender && sender.getContent) s = sender.getContent() || "" } catch (e) {}
try { if (!s && typeof sender !== "undefined" && sender && sender.getMessage) s = sender.getMessage() || "" } catch (e) {}
try { if (!s && typeof message !== "undefined") s = String(message) } catch (e) {}
return String(s || "").trim()
return trim(s)
}
function usage() {
return [
"起点读书签到插件",
"起点读书签到插件 v2.2.0",
"提交CK:起点ck <QX抓到的cookie>",
"签到全部:起点签到",
"签到单号:起点签到 <uid>",
"查询CK:起点ck 查询",
"导出CK:起点ck 导出",
"删除CK:起点ck 删除 <uid>",
"说明:QX只负责抓cookie;签到由本 Autman 插件执行。"
"导出CK:起点ck 导出(会输出完整 Cookie,仅管理员自用)",
"删除CK:起点ck 删除 <uid|all>",
"配置签名网关:Autman 参数 otto.qdreader_sign_api,默认 " + DEFAULT_SIGN_API,
"定时:每日 07:15 自动执行;QX 只负责抓 cookie。"
].join("\n")
}
function stripCommand(text) {
var s = String(text || "")
if (/^起点(?:签到|读书)(?:\s|$)/i.test(s)) return s.replace(/^起点(?:签到|读书)\s*/i, "").trim()
if (/^起点(?:ck|CK|cookie|Cookie)(?:\s|$)/i.test(s)) return s.replace(/^起点(?:ck|CK|cookie|Cookie)\s*/i, "").trim()
return s.replace(/^qdreader\s*/i, "").trim()
if (/^起点(?:签到|读书)(?:\s|$)/i.test(s)) return trim(s.replace(/^起点(?:签到|读书)\s*/i, ""))
if (/^起点(?:ck|CK|cookie|Cookie)(?:\s|$)/i.test(s)) return trim(s.replace(/^起点(?:ck|CK|cookie|Cookie)\s*/i, ""))
return trim(s.replace(/^qdreader\s*/i, ""))
}
function commandName(text) {
if (/^起点(?:签到|读书)(?:\s|$)/i.test(text)) return "sign"
@@ -103,10 +171,11 @@ function commandName(text) {
function qs(obj) {
var parts = []
for (var k in (obj || {})) {
if (!obj.hasOwnProperty(k)) continue
var v = obj[k]
if (v === undefined || v === null) continue
if (Array.isArray(v)) { for (var i = 0; i < v.length; i++) parts.push(encodeURIComponent(k + "[" + i + "]") + "=" + encodeURIComponent(String(v[i]))) }
else if (typeof v === "object") { for (var sk in v) parts.push(encodeURIComponent(k + "[" + sk + "]") + "=" + encodeURIComponent(String(v[sk]))) }
else if (typeof v === "object") { for (var sk in v) if (v.hasOwnProperty(sk)) parts.push(encodeURIComponent(k + "[" + sk + "]") + "=" + encodeURIComponent(String(v[sk]))) }
else parts.push(encodeURIComponent(k) + "=" + encodeURIComponent(String(v)))
}
return parts.join("&")
@@ -117,9 +186,11 @@ function normalizeCookie(cookie) {
if (session) jar.sessionyw = session
var appid = jar.appId || jar.appid || ""
if (appid) jar.appId = appid
return { jar: jar, cookie: cookieToString(jar), qdh: jar.QDHeader || jar.qdh || "", qdheader: jar.QDHeader || jar.qdheader || "", uid: extractUid(cookie), guid: jar.gwguid || jar.guid || "", qimei: jar.qimei || "", qimei36: jar.qimei36 || jar.QIMEI36 || "" }
var guid = jar.ywguid || jar.gwguid || jar.guid || ""
return { jar: jar, cookie: cookieToString(jar), qdh: jar.QDHeader || jar.qdh || "", qdheader: jar.QDHeader || jar.qdheader || "", uid: extractUid(cookie), guid: guid, qimei: jar.qimei || jar.QIMEI || "", qimei36: jar.qimei36 || jar.QIMEI36 || "" }
}
function httpReq(opt) {
if (!opt.headers) opt.headers = {}
if (typeof request === "function") return request(opt)
if (typeof $request === "function") return $request(opt)
throw new Error("当前 Autman 环境没有 request/$request 方法,无法发起签到请求")
@@ -129,21 +200,43 @@ function parseResp(res) {
if (typeof body === "string") return safeJsonParse(body, body)
return body
}
function requestWithRetry(opt, retryCount) {
var lastErr = null
var attempts = Math.max(1, (retryCount || 0) + 1)
for (var i = 0; i < attempts; i++) {
try {
return httpReq(opt)
} catch (e) {
lastErr = e
debug("QDReader request retry " + (i + 1) + "/" + attempts + " failed: " + (e && e.message ? e.message : String(e)))
}
}
throw lastErr || new Error("请求失败")
}
function postJson(url, data) {
return parseResp(httpReq({ url: url, method: "post", dataType: "json", timeOut: 10000, headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }))
var retry = getInt(RETRY_KEY, 1, 0, 3)
return parseResp(requestWithRetry({ url: url, method: "post", dataType: "json", timeOut: 10000, headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }, retry))
}
function normalizeSignHeaders(signed) {
if (typeof signed === "string") signed = safeJsonParse(signed, {})
if (!signed) throw new Error("签名网关无返回")
var code = signed.code !== undefined ? signed.code : (signed.retcode !== undefined ? signed.retcode : signed.status)
var ok = code === undefined || code === 0 || code === "0" || code === 200 || code === "200" || code === "ok" || code === true
var data = signed.data || signed.result || signed.headers || signed
var headers = data && (data.headers || data.header || data)
if (!ok || !isObject(headers)) throw new Error("签名失败:" + JSON.stringify(signed).slice(0, 500))
return headers
}
function qidianRequest(path, method, data, cookie) {
method = String(method || "GET").toUpperCase()
var info = normalizeCookie(cookie)
var signApi = (get(SIGN_API_KEY) || DEFAULT_SIGN_API).replace(/\/$/, "")
if (!info.uid) throw new Error("Cookie 缺少 uid/QDHeader")
var signApi = String(cfgGet(SIGN_API_KEY, DEFAULT_SIGN_API)).replace(/\/+$/, "")
var payload = { qdh: info.qdh, qdheader: info.qdheader, url: QIDIAN_BASE + path, method: method, data: data || {}, uid: info.uid, guid: info.guid, qimei: info.qimei, qimei36: info.qimei36 }
var signed = postJson(signApi + "/sign", payload)
if (typeof signed === "string") signed = safeJsonParse(signed, {})
if (!signed || (signed.code !== 200 && signed.code !== "200" && signed.code !== 0 && signed.code !== "0") || !signed.data) throw new Error("签名失败:" + JSON.stringify(signed))
var headers = signed.data.headers || signed.data
var headers = normalizeSignHeaders(postJson(signApi + "/sign", payload))
headers.Cookie = info.cookie
if (!headers["User-Agent"]) headers["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/QDReaderiOS/5.9.456/728/QDReaderAppstore"
var req = { url: QIDIAN_BASE + path, method: method.toLowerCase(), dataType: "json", timeOut: 10000, headers: headers }
var req = { url: QIDIAN_BASE + path, method: lower(method), dataType: "json", timeOut: 10000, headers: headers }
if (method === "GET") {
var query = qs(data || {})
if (query) req.url += (req.url.indexOf("?") >= 0 ? "&" : "?") + query
@@ -151,81 +244,113 @@ function qidianRequest(path, method, data, cookie) {
req.headers["Content-Type"] = req.headers["Content-Type"] || "application/x-www-form-urlencoded"
req.body = qs(data || {})
}
var out = parseResp(httpReq(req))
var retry = getInt(RETRY_KEY, 1, 0, 3)
var out = parseResp(requestWithRetry(req, retry))
if (typeof out === "string") out = safeJsonParse(out, out)
return out
}
function resultCode(obj) { return obj && (obj.Result !== undefined ? obj.Result : obj.Code !== undefined ? obj.Code : obj.code !== undefined ? obj.code : obj.retcode) }
function resultOk(obj) {
var code = obj && (obj.Result !== undefined ? obj.Result : obj.Code !== undefined ? obj.Code : obj.code)
return code === undefined || code === 0 || code === "0" || code === 200 || code === "200"
var code = resultCode(obj)
var status = obj && (obj.status || obj.Status)
return code === undefined || code === 0 || code === "0" || code === 200 || code === "200" || status === "ok" || status === "success"
}
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 resultMsg(obj) { return (obj && (obj.Message || obj.Msg || obj.message || obj.msg)) || "" }
function doCheckin(uid, cookie) {
var res = qidianRequest("/argus/api/v3/checkin/checkin", "POST", {}, cookie)
var res = qidianRequest(CHECKIN_PATH, "POST", {}, cookie)
var ok = resultOk(res)
var msg = resultMsg(res) || (ok ? "签到请求成功" : "签到失败")
var msg = resultMsg(res) || (ok ? "签到请求成功" : ("签到失败" + (resultCode(res) !== undefined ? " code=" + resultCode(res) : "")))
return { uid: uid, ok: ok, msg: msg, raw: res }
}
function signAll(targetUid) {
var store = getStore()
var uids = Object.keys(store)
var uids = Object.keys(store).sort()
if (targetUid) uids = uids.filter(function (u) { return u === targetUid })
if (!uids.length) return targetUid ? ("未找到 uid: " + targetUid) : "还没有保存起点读书CK"
var okCount = 0
var failCount = 0
var lines = ["起点读书签到结果:"]
for (var i = 0; i < uids.length; i++) {
var uid = uids[i]
try {
var r = doCheckin(uid, store[uid])
lines.push((r.ok ? "✅ " : "❌ ") + uid + "" + r.msg)
if (r.ok) okCount++; else failCount++
lines.push((r.ok ? "✅ " : "❌ ") + maskUid(uid) + "" + r.msg)
} catch (e) {
lines.push("❌ " + uid + "" + (e && e.message ? e.message : String(e)))
failCount++
lines.push("❌ " + maskUid(uid) + "" + (e && e.message ? e.message : String(e)))
}
}
lines.push("汇总:成功 " + okCount + " / 失败 " + failCount + " / 总计 " + uids.length)
return lines.join("\n")
}
function main() {
var text = contentText()
var isCronRun = !text
var cmd = isCronRun ? "sign" : commandName(text)
var arg = isCronRun ? "" : stripCommand(text).replace(/^sign\s*/i, "").trim()
if (!arg && cmd !== "sign") { reply(usage()); return }
if (/^(帮助|help|h)$/i.test(arg)) { reply(usage()); return }
var store = getStore()
if (cmd === "sign" || /^(签到|sign|run)(?:\s+|$)/i.test(arg)) {
var target = arg.replace(/^(签到|sign|run)\s*/i, "").trim()
reply(signAll(target))
return
function importJson(arg, store) {
var obj = safeJsonParse(arg, null)
if (!isObject(obj)) return "JSON格式错误,示例:起点ck json {\"uid\":\"cookie\"}"
var count = 0
for (var uid in obj) {
if (!obj.hasOwnProperty(uid) || !obj[uid]) continue
var cookie = String(obj[uid])
var realUid = extractUid(cookie) || uid
store[String(realUid)] = cookie
count++
}
if (/^(查询|状态|status|list)$/i.test(arg)) {
var uids = Object.keys(store)
reply(uids.length ? ("起点读书CK:共 " + uids.length + " 个账号\n" + uids.join("\n") + "\n发送 起点签到 可执行签到") : "还没有保存起点读书CK")
return
}
if (/^(导出|export)$/i.test(arg)) { reply("QDREADER_COOKIE=" + JSON.stringify(store)); return }
setStore(store)
return "起点读书CK JSON已导入/更新 " + count + " 个账号,当前共 " + Object.keys(store).length + " 个账号\n发送 起点签到 可执行签到"
}
function handleDelete(arg, store) {
var del = arg.match(/^(删除|delete|del|rm)\s+(\S+)/i)
if (del) { var duid = del[2]; var existed = !!store[duid]; delete store[duid]; setStore(store); reply(existed ? ("已删除 uid: " + duid) : ("未找到 uid: " + duid)); return }
var jsonMatch = arg.match(/^json\s+([\s\S]+)$/i)
if (jsonMatch) {
var obj = safeJsonParse(jsonMatch[1], null)
if (!obj || typeof obj !== "object" || Array.isArray(obj)) { reply("JSON格式错误,示例:起点ck json {\"uid\":\"cookie\"}"); return }
var count = 0
for (var uid in obj) { if (!obj[uid]) continue; store[uid] = String(obj[uid]); count++ }
setStore(store); reply("起点读书CK JSON已导入/更新 " + count + " 个账号,当前共 " + Object.keys(store).length + " 个账号\n发送 起点签到 可执行签到"); return
if (!del) return null
var duid = del[2]
if (/^(all|全部|所有)$/i.test(duid)) {
var n = Object.keys(store).length
setStore({})
return "已删除全部起点读书CK,共 " + n + " 个账号"
}
var existed = !!store[duid]
delete store[duid]
setStore(store)
return existed ? ("已删除 uid: " + duid) : ("未找到 uid: " + duid)
}
function handleSaveCookie(arg, store) {
var cookie = arg
if (cookie.indexOf("=") < 0 || cookie.indexOf(";") < 0) { reply("未识别到 Cookie。请发送:起点ck <QX抓到的完整cookie>"); return }
if (cookie.indexOf("=") < 0 || cookie.indexOf(";") < 0) return "未识别到 Cookie。请发送:起点ck <QX抓到的完整cookie>"
var uid = extractUid(cookie)
if (!uid) { reply("无法从 Cookie 识别 uid。请确认 QX 抓的是起点读书福利中心 getlogininfo 请求,Cookie 内应包含 QDHeader 或 uid。"); return }
if (!uid) return "无法从 Cookie 识别 uid。请确认 QX 抓的是起点读书福利中心 getlogininfo 请求,Cookie 内应包含 QDHeader 或 uid。"
var isNew = !store[uid]
var changed = store[uid] !== cookie
store[uid] = cookie
setStore(store)
reply(["起点读书CK" + (isNew ? "新增" : (changed ? "更新" : "未变化")) + "成功", "uid: " + uid, "账号数: " + Object.keys(store).length, "cookie: " + maskCookie(cookie), "签到:发送 起点签到"].join("\n"))
return ["起点读书CK" + (isNew ? "新增" : (changed ? "更新" : "未变化")) + "成功", "uid: " + uid, "账号数: " + Object.keys(store).length, "cookie: " + maskCookie(cookie), "签到:发送 起点签到"].join("\n")
}
function main() {
var text = contentText()
var isCronRun = !text
var cmd = isCronRun ? "sign" : commandName(text)
var arg = isCronRun ? "" : trim(stripCommand(text).replace(/^sign\s*/i, ""))
if (!arg && cmd !== "sign") { reply(usage()); return }
if (/^(帮助|help|h)$/i.test(arg)) { reply(usage()); return }
var store = getStore()
if (cmd === "sign" || /^(签到|sign|run)(?:\s+|$)/i.test(arg)) {
var target = trim(arg.replace(/^(签到|sign|run)\s*/i, ""))
var out = signAll(target)
if (!(isCronRun && getBool(CRON_SILENT_KEY, false) && out === "还没有保存起点读书CK")) reply(out)
return
}
if (/^(查询|状态|status|list)$/i.test(arg)) {
var uids = Object.keys(store).sort()
reply(uids.length ? ("起点读书CK:共 " + uids.length + " 个账号\n" + uids.map(maskUid).join("\n") + "\n发送 起点签到 可执行签到") : "还没有保存起点读书CK")
return
}
if (/^(导出|export)$/i.test(arg)) { reply("QDREADER_COOKIE=" + JSON.stringify(store)); return }
var delMsg = handleDelete(arg, store)
if (delMsg !== null) { reply(delMsg); return }
var jsonMatch = arg.match(/^json\s+([\s\S]+)$/i)
if (jsonMatch) { reply(importJson(jsonMatch[1], store)); return }
reply(handleSaveCookie(arg, store))
}
main()