815 lines
39 KiB
JavaScript
815 lines
39 KiB
JavaScript
//[open_source: true]
|
||
//[create_at: 2026-05-17 03:00:00]
|
||
//[title: 启点读书日志归档]
|
||
//[author: Hermes]
|
||
//[service: BOSS]
|
||
//[class: 工具类]
|
||
//[version: 2.5.6]
|
||
//[platform: web,qq,wx,tg,tb,fs,we]
|
||
//[public: false]
|
||
//[price: 0]
|
||
//[disable: false]
|
||
//[description: 启点读书(QDReader) 日志归档插件:仅用于独立定时 `1 0 * * *`,每日 00:01 归档上一批签到日志并清空当日日志;不暴露手动规则,避免与签到插件共用逗号 hour cron 导致 Autman 定时兼容问题。]
|
||
//[admin: true]
|
||
//[priority: 600]
|
||
//[cron: 1 0 * * *]
|
||
|
||
|
||
var BUCKET = "hermes_qidian"
|
||
var STORE_KEY = "qdreader_cookie_json"
|
||
var BIND_KEY = "qdreader_user_bind_json"
|
||
var REMARK_KEY = "qdreader_remark_json"
|
||
var SIGN_API_KEY = "qdreader_sign_api"
|
||
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"
|
||
|
||
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 bucketGet === "function") { var bv = bucketGet(BUCKET, key); if (bv !== undefined && bv !== null && String(bv) !== "") return bv } } catch (e) {}
|
||
try { if (typeof get === "function") { var v = get(key); if (v !== undefined && v !== null && String(v) !== "") return v } } catch (e) {}
|
||
return def
|
||
}
|
||
function cfgSet(key, value) {
|
||
var s = String(value)
|
||
var ok = false
|
||
try { if (typeof bucketSet === "function") { bucketSet(BUCKET, key, s); ok = true } } catch (e) {}
|
||
try { if (typeof set === "function") { set(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 (!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 debug(msg) { try { if (typeof Debug === "function") Debug(String(msg)); else console.log(String(msg)) } catch (e) {} }
|
||
|
||
function cleanStringMap(obj) {
|
||
if (!isObject(obj)) return {}
|
||
var clean = {}
|
||
for (var k in obj) if (obj.hasOwnProperty(k) && obj[k] !== undefined && obj[k] !== null && String(obj[k]) !== "") clean[String(k)] = String(obj[k])
|
||
return clean
|
||
}
|
||
function getStore() { return cleanStringMap(safeJsonParse(cfgGet(STORE_KEY, "{}"), {})) }
|
||
function setStore(obj) { return cfgSet(STORE_KEY, JSON.stringify(obj || {})) }
|
||
function getBindStore() { return cleanStringMap(safeJsonParse(cfgGet(BIND_KEY, "{}"), {})) }
|
||
function setBindStore(obj) { return cfgSet(BIND_KEY, JSON.stringify(obj || {})) }
|
||
function getRemarkStore() { return cleanStringMap(safeJsonParse(cfgGet(REMARK_KEY, "{}"), {})) }
|
||
function setRemarkStore(obj) { return cfgSet(REMARK_KEY, JSON.stringify(obj || {})) }
|
||
function cookieGuid(cookie) {
|
||
var jar = parseCookieString(cookie)
|
||
return trim(jar.ywguid || jar.gwguid || jar.guid || "")
|
||
}
|
||
function remarksForStore(store) {
|
||
var remarks = getRemarkStore()
|
||
var changed = false
|
||
for (var uid in (store || {})) if (store.hasOwnProperty(uid)) {
|
||
var guid = cookieGuid(store[uid])
|
||
if (guid && guid !== uid && remarks[guid] && !remarks[uid]) {
|
||
remarks[uid] = remarks[guid]
|
||
delete remarks[guid]
|
||
changed = true
|
||
}
|
||
}
|
||
if (changed) setRemarkStore(remarks)
|
||
return remarks
|
||
}
|
||
function callNoArg(obj, name) {
|
||
try { if (obj && typeof obj[name] === "function") return obj[name]() } catch (e) {}
|
||
return ""
|
||
}
|
||
function currentUserId() {
|
||
var v = ""
|
||
try { if (typeof GetUserID === "function") v = GetUserID() || "" } catch (e) {}
|
||
try { if (!v && typeof Sender !== "undefined" && Sender && typeof Sender.GetUserID === "function") v = Sender.GetUserID() || "" } catch (e) {}
|
||
var names = ["getUserID", "getUserId", "getUserid", "getUser", "getSenderID", "getSenderId", "getSender", "getFromUserID", "getFromUserId"]
|
||
try { if (!v && typeof sender !== "undefined" && sender) for (var i = 0; i < names.length && !v; i++) v = callNoArg(sender, names[i]) || "" } catch (e) {}
|
||
var globals = ["userId", "userID", "senderID", "senderId", "fromUserId", "fromUserID"]
|
||
for (var j = 0; j < globals.length && !v; j++) { try { if (typeof this[globals[j]] !== "undefined") v = this[globals[j]] || "" } catch (e) {} }
|
||
for (var k = 0; k < names.length && !v; k++) { try { if (typeof this[names[k]] === "function") v = this[names[k]]() || "" } catch (e) {} }
|
||
return trim(v)
|
||
}
|
||
function currentOwnerId() {
|
||
return currentUserId()
|
||
}
|
||
function currentIsAdmin() {
|
||
try { if (typeof isAdmin === "function" && isAdmin()) return true } catch (e) {}
|
||
try { if (typeof sender !== "undefined" && sender && typeof sender.isAdmin === "function" && sender.isAdmin()) return true } catch (e2) {}
|
||
try { if (typeof Sender !== "undefined" && Sender && typeof Sender.isAdmin === "function" && Sender.isAdmin()) return true } catch (e3) {}
|
||
return false
|
||
}
|
||
function looksLikeLegacyFallbackOwner(owner) {
|
||
owner = trim(owner)
|
||
if (!owner) return false
|
||
if (owner === "__qdreader_default_owner__") return true
|
||
if (/^[A-Za-z0-9._-]{1,32}$/.test(owner) && !/^\d{5,}$/.test(owner)) return true
|
||
return false
|
||
}
|
||
function canRepairBinding(oldOwner, me) {
|
||
if (!oldOwner || oldOwner === me) return true
|
||
return currentIsAdmin() && looksLikeLegacyFallbackOwner(oldOwner)
|
||
}
|
||
function currentUserDebug() {
|
||
var parts = []
|
||
var fns = ["GetUserID", "GetChatID", "GetImType", "ImType", "GetUsername"]
|
||
for (var f = 0; f < fns.length; f++) try { if (typeof this[fns[f]] === "function") { var gv = this[fns[f]](); parts.push(fns[f] + "=" + (gv ? maskUid(String(gv)) : "空")) } } catch (e) {}
|
||
try { if (typeof Sender !== "undefined" && Sender) {
|
||
var snames = ["GetUserID", "GetChatID", "GetImType", "GetUsername"]
|
||
for (var s = 0; s < snames.length; s++) if (typeof Sender[snames[s]] === "function") {
|
||
var sv = Sender[snames[s]](); parts.push("Sender." + snames[s] + "=" + (sv ? maskUid(String(sv)) : "空"))
|
||
}
|
||
} } catch (e) {}
|
||
try { if (typeof sender !== "undefined" && sender) {
|
||
var names = ["getUserID", "getUserId", "getUserid", "getUser", "getSenderID", "getSenderId", "getSender", "getFromUserID", "getFromUserId", "getChatID", "getChatId", "getImtype"]
|
||
for (var i = 0; i < names.length; i++) if (typeof sender[names[i]] === "function") {
|
||
var v = callNoArg(sender, names[i]); parts.push("sender." + names[i] + "=" + (v ? maskUid(String(v)) : "空"))
|
||
}
|
||
} } catch (e) {}
|
||
return parts.length ? parts.join("; ") : "未发现可用用户方法"
|
||
}
|
||
function boundToCurrent(uid, bind) {
|
||
var owner = bind && bind[String(uid)] ? String(bind[String(uid)]) : ""
|
||
var me = currentOwnerId()
|
||
return !!owner && !!me && owner === me
|
||
}
|
||
function visibleUids(store, bind, allUsers) {
|
||
var uids = Object.keys(store || {}).sort()
|
||
if (allUsers) return uids
|
||
return uids.filter(function (uid) { return boundToCurrent(uid, bind || {}) })
|
||
}
|
||
|
||
function parseCookieString(cookie) {
|
||
var jar = {}
|
||
String(cookie || "").split(";").forEach(function (part) {
|
||
var i = part.indexOf("=")
|
||
if (i <= 0) return
|
||
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.hasOwnProperty(k) && jar[k] !== undefined && jar[k] !== null && jar[k] !== "") arr.push(k + "=" + jar[k])
|
||
return arr.join("; ")
|
||
}
|
||
function b64decode(s) {
|
||
s = String(s || "").trim()
|
||
try { s = decodeURIComponent(s) } catch (e) {}
|
||
s = s.replace(/-/g, "+").replace(/_/g, "/")
|
||
while (s.length % 4) s += "="
|
||
try { if (typeof Buffer !== "undefined") return Buffer.from(s, "base64").toString("utf8") } catch (e) {}
|
||
try { if (typeof atob === "function") return decodeURIComponent(escape(atob(s))) } catch (e) {}
|
||
try {
|
||
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
|
||
var output = "", buffer = 0, bits = 0
|
||
for (var i = 0; i < s.length; i++) {
|
||
var c = chars.indexOf(s.charAt(i))
|
||
if (c < 0) continue
|
||
if (c === 64) break
|
||
buffer = (buffer << 6) | c
|
||
bits += 6
|
||
if (bits >= 8) {
|
||
bits -= 8
|
||
output += String.fromCharCode((buffer >> bits) & 0xff)
|
||
}
|
||
}
|
||
try { return decodeURIComponent(escape(output)) } catch (e2) { return output }
|
||
} catch (e3) { return "" }
|
||
}
|
||
function isRealUid(uid) {
|
||
uid = String(uid === undefined || uid === null ? "" : uid).trim()
|
||
return /^\d{5,}$/.test(uid) ? uid : ""
|
||
}
|
||
function uidFromQDHeader(qdheader) {
|
||
var decoded = b64decode(qdheader)
|
||
if (!decoded) return ""
|
||
var parts = decoded.split("|").map(function (x) { return trim(x || "") })
|
||
for (var i = 0; i < [0, 11].length; i++) {
|
||
var uid = isRealUid(parts[[0, 11][i]])
|
||
if (uid) return uid
|
||
}
|
||
return ""
|
||
}
|
||
function extractUid(cookie) {
|
||
var jar = parseCookieString(cookie)
|
||
var uid = uidFromQDHeader(jar.QDHeader || jar.qdheader || jar.QDHEADER || "")
|
||
if (uid) return uid
|
||
uid = isRealUid(jar.uid || jar.userid || jar.userId || jar.UserId)
|
||
if (uid) return uid
|
||
return isRealUid(jar.ywguid || jar.gwguid || jar.guid)
|
||
}
|
||
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 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 trim(s)
|
||
}
|
||
function usage() {
|
||
return [
|
||
"启点读书签到插件 v2.5.6",
|
||
"【一级菜单】",
|
||
"账号管理:启点账号",
|
||
"执行查询:启点查询",
|
||
"【快速通道】",
|
||
"快速提交CK:启点ck <QX抓到的cookie>",
|
||
"直接签到:启点签到",
|
||
"指定签到:启点签到 <uid>",
|
||
"【配置】",
|
||
"签名网关:Autman 参数 hermes_qidian.qdreader_sign_api,默认 " + DEFAULT_SIGN_API,
|
||
"定时:每日 00:01 只归档并清空日志;每日 07:01 只自动签到;QX 只负责抓 cookie。"
|
||
].join("\n")
|
||
}
|
||
function stripCommand(text) {
|
||
var s = String(text || "")
|
||
if (/^启点(?:签到|读书)(?:\s|$)/i.test(s)) return trim(s.replace(/^启点(?:签到|读书)\s*/i, ""))
|
||
if (/^启点(?:归档|日志归档)(?:\s|$)/i.test(s)) return trim(s.replace(/^启点(?:归档|日志归档)\s*/i, ""))
|
||
if (/^启点(?:查询)(?:\s|$)/i.test(s)) return trim(s.replace(/^启点(?:查询)\s*/i, ""))
|
||
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"
|
||
if (/^启点(?:归档|日志归档)(?:\s|$)/i.test(text)) return "archive"
|
||
if (/^qdreader\s+sign(?:\s|$)/i.test(text)) return "sign"
|
||
if (/^qdreader\s+archive(?:\s|$)/i.test(text)) return "archive"
|
||
if (/^启点(?:查询)(?:\s|$)/i.test(text)) return "status"
|
||
if (/^启点(?:账号)(?:\s|$)/i.test(text)) return "account"
|
||
return "ck"
|
||
}
|
||
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) if (v.hasOwnProperty(sk)) parts.push(encodeURIComponent(k + "[" + sk + "]") + "=" + encodeURIComponent(String(v[sk]))) }
|
||
else parts.push(encodeURIComponent(k) + "=" + encodeURIComponent(String(v)))
|
||
}
|
||
return parts.join("&")
|
||
}
|
||
function normalizeCookie(cookie) {
|
||
var jar = parseCookieString(cookie)
|
||
var session = jar.sessionyw || jar.session || jar.sessionKey || ""
|
||
if (session) jar.sessionyw = session
|
||
var appid = jar.appId || jar.appid || ""
|
||
if (appid) jar.appId = appid
|
||
var guid = jar.ywguid || jar.gwguid || jar.guid || ""
|
||
var qdh = jar.qdh || jar.QDH || ""
|
||
var qdheader = jar.QDHeader || jar.qdheader || jar.QDHEADER || ""
|
||
return { jar: jar, cookie: cookieToString(jar), qdh: qdh, qdheader: 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 方法,无法发起签到请求")
|
||
}
|
||
function parseResp(res) {
|
||
if (res === undefined || res === null) return null
|
||
var body = null
|
||
if (res && typeof res === "object") {
|
||
if (res.body !== undefined) body = res.body
|
||
else if (res.data !== undefined) body = res.data
|
||
else if (res.content !== undefined) body = res.content
|
||
else if (res.responseText !== undefined) body = res.responseText
|
||
else if (res.statusCode !== undefined || res.status !== undefined || res.headers !== undefined) body = ""
|
||
else body = res
|
||
} else {
|
||
body = 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) {
|
||
var retry = getInt(RETRY_KEY, 1, 0, 3)
|
||
return parseResp(requestWithRetry({ url: url, method: "post", dataType: "json", timeOut: 10000, headers: { "Content-Type": "application/json", "Accept": "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)
|
||
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 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: lower(method), dataType: "json", timeOut: 10000, headers: headers }
|
||
if (method === "GET") {
|
||
var query = qs(data || {})
|
||
if (query) req.url += (req.url.indexOf("?") >= 0 ? "&" : "?") + query
|
||
} else {
|
||
req.headers["Content-Type"] = req.headers["Content-Type"] || "application/x-www-form-urlencoded"
|
||
req.body = qs(data || {})
|
||
}
|
||
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 = 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 nowIso() { try { return new Date().toISOString() } catch (e) { return String(new Date()) } }
|
||
function setLastResult(uid, result) {
|
||
var obj = cleanStringMap(safeJsonParse(cfgGet(LAST_RESULT_KEY, "{}"), {}))
|
||
obj[String(uid)] = JSON.stringify(result || {})
|
||
cfgSet(LAST_RESULT_KEY, JSON.stringify(obj))
|
||
}
|
||
function getLastResults() {
|
||
var raw = cleanStringMap(safeJsonParse(cfgGet(LAST_RESULT_KEY, "{}"), {}))
|
||
var out = {}
|
||
for (var uid in raw) if (raw.hasOwnProperty(uid)) out[uid] = safeJsonParse(raw[uid], { msg: raw[uid] })
|
||
return out
|
||
}
|
||
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 cronMode() {
|
||
var d = new Date()
|
||
var h = d.getHours ? d.getHours() : 7
|
||
// Compatibility fallback: the sign plugin is scheduled at 07:01 only.
|
||
// If Autman manually/debug-runs this file around 00:xx, keep archive-only behavior.
|
||
// The normal midnight archive is handled by qdreader_archive_autman.js with cron 1 0 * * *.
|
||
if (h === 0) return "archive"
|
||
return "sign"
|
||
}
|
||
function formatArchiveReply(ret) {
|
||
if (ret && ret.archived) {
|
||
var run = ret.run || {}
|
||
return "启点读书日志归档完成:" + ret.key + "\n" +
|
||
"来源:" + sourceName(run.source) + "\n" +
|
||
"成功 " + (run.okCount || 0) + " / 失败 " + (run.failCount || 0) + " / 总计 " + (run.total || 0) + "\n" +
|
||
"已清空当日运行日志,后续查询可从归档判断签到情况"
|
||
}
|
||
return "启点读书日志归档完成:无可归档批次;已清空当日运行日志"
|
||
}
|
||
function sourceName(source) {
|
||
if (source === "cron") return "定时任务/零点任务"
|
||
if (source === "menu") return "菜单立即执行"
|
||
if (source === "manual") return "手动签到"
|
||
return source || "未知"
|
||
}
|
||
function doCheckin(uid, cookie, source, runId) {
|
||
var startedAt = nowIso()
|
||
var res = qidianRequest(CHECKIN_PATH, "POST", {}, cookie)
|
||
var ok = resultOk(res)
|
||
var msg = resultMsg(res) || (ok ? "签到请求成功" : ("签到失败" + (resultCode(res) !== undefined ? " code=" + resultCode(res) : "")))
|
||
var result = { uid: String(uid), ok: !!ok, msg: String(msg || ""), time: nowIso(), startedAt: startedAt, source: source || "manual", runId: runId || "" }
|
||
setLastResult(uid, result)
|
||
return { uid: uid, ok: ok, msg: msg, raw: res, log: result }
|
||
}
|
||
function signAll(targetUid, allUsers, source) {
|
||
source = source || (allUsers ? "cron" : "manual")
|
||
var startedAt = nowIso()
|
||
var runId = startedAt.replace(/[-:.TZ]/g, "").slice(0, 14) + "_" + source
|
||
var store = getStore()
|
||
var bind = getBindStore()
|
||
var remarks = remarksForStore(store)
|
||
var uids = visibleUids(store, bind, !!allUsers)
|
||
if (targetUid) uids = uids.filter(function (u) { return u === targetUid })
|
||
if (!uids.length) {
|
||
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: [] })
|
||
return emptyMsg
|
||
}
|
||
var okCount = 0
|
||
var failCount = 0
|
||
var items = []
|
||
var lines = ["启点读书签到结果:"]
|
||
for (var i = 0; i < uids.length; i++) {
|
||
var uid = uids[i]
|
||
try {
|
||
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] || "" })
|
||
lines.push((r.ok ? "✅ " : "❌ ") + maskUid(uid) + (remarks[uid] ? "(" + remarks[uid] + ")" : "") + ":" + r.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)
|
||
lines.push("❌ " + maskUid(uid) + (remarks[uid] ? "(" + remarks[uid] + ")" : "") + ":" + emsg)
|
||
}
|
||
}
|
||
var finishedAt = nowIso()
|
||
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("记录时间:" + finishedAt + "(" + sourceName(source) + ")")
|
||
return lines.join("\n")
|
||
}
|
||
|
||
function formatStatusLine(uid, last, remarks) {
|
||
var name = maskUid(uid) + (remarks && remarks[uid] ? "(" + remarks[uid] + ")" : "")
|
||
var r = last[uid]
|
||
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()
|
||
var last = getLastResults()
|
||
var run = getLastRunLog()
|
||
var remarks = remarksForStore(store)
|
||
var uids = visibleUids(store, bind, false)
|
||
if (targetUid) {
|
||
if (uids.indexOf(targetUid) < 0) return store[targetUid] ? ("无权查询 uid: " + targetUid) : ("未找到 uid: " + targetUid)
|
||
uids = [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 (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")
|
||
}
|
||
|
||
function listenInput(timeoutMs) {
|
||
try { if (typeof sender !== "undefined" && sender && sender.listen) return trim(sender.listen(timeoutMs || 60000)) } catch (e) {}
|
||
return ""
|
||
}
|
||
function accountMenuText() {
|
||
return [
|
||
"启点账号管理",
|
||
"1. 查看账号",
|
||
"2. 添加/更新账号",
|
||
"3. 账号详情/修改备注",
|
||
"4. 删除账号",
|
||
"5. 清空账号",
|
||
"q. 退出",
|
||
"请回复序号"
|
||
].join("\n")
|
||
}
|
||
function queryMenuText() {
|
||
return [
|
||
"启点查询(最近签到记录)",
|
||
"1. 查看最近签到记录",
|
||
"2. 查询单个账号最近记录",
|
||
"3. 立即执行签到",
|
||
"q. 退出",
|
||
"请回复序号"
|
||
].join("\n")
|
||
}
|
||
function accountListText(store, bind) {
|
||
var uids = visibleUids(store, bind, false)
|
||
var remarks = remarksForStore(store)
|
||
if (!uids.length) return "当前用户还没有保存启点读书CK"
|
||
var lines = ["启点账号:当前用户共 " + uids.length + " 个账号"]
|
||
for (var i = 0; i < uids.length; i++) {
|
||
var uid = uids[i]
|
||
lines.push(maskUid(uid) + (remarks[uid] ? "(" + remarks[uid] + ")" : ""))
|
||
}
|
||
lines.push("发送 启点签到 可执行签到")
|
||
return lines.join("\n")
|
||
}
|
||
|
||
|
||
function accountSelectText(store, bind, title) {
|
||
var uids = visibleUids(store, bind, false)
|
||
var remarks = remarksForStore(store)
|
||
if (!uids.length) return { text: "当前用户还没有保存启点读书CK", uids: [] }
|
||
var lines = [title || "请选择账号:"]
|
||
for (var i = 0; i < uids.length; i++) {
|
||
var uid = uids[i]
|
||
lines.push((i + 1) + ". " + maskUid(uid) + (remarks[uid] ? "(" + remarks[uid] + ")" : ""))
|
||
}
|
||
lines.push("q. 退出")
|
||
lines.push("请回复编号")
|
||
return { text: lines.join("\n"), uids: uids }
|
||
}
|
||
function chooseUid(store, bind, title) {
|
||
var sel = accountSelectText(store, bind, title)
|
||
reply(sel.text)
|
||
if (!sel.uids.length) return ""
|
||
var op = listenInput(60000)
|
||
if (!op || /^(q|退出|取消)$/i.test(op)) return ""
|
||
var n = parseInt(op, 10)
|
||
if (isNaN(n) || n < 1 || n > sel.uids.length) { reply("编号无效,已退出"); return "" }
|
||
return sel.uids[n - 1]
|
||
}
|
||
function maybePromptRemark(uid, store) {
|
||
if (!uid) return ""
|
||
reply("是否为该账号添加备注?回复备注名,发送 n/跳过 则不添加")
|
||
var remark = listenInput(60000)
|
||
if (!remark || /^(n|no|否|不|跳过|q|退出|取消)$/i.test(remark)) return ""
|
||
var remarks = store ? remarksForStore(store) : getRemarkStore()
|
||
remarks[uid] = remark.slice(0, 30)
|
||
setRemarkStore(remarks)
|
||
return "\n备注: " + remarks[uid]
|
||
}
|
||
function accountDetailText(uid, store, bind) {
|
||
var uids = visibleUids(store, bind, false)
|
||
if (uids.indexOf(uid) < 0) return store[uid] ? ("无权查看 uid: " + uid) : ("未找到 uid: " + uid)
|
||
var remarks = remarksForStore(store)
|
||
var last = getLastResults()
|
||
return [
|
||
"启点账号详情:",
|
||
"uid: " + maskUid(uid),
|
||
"备注: " + (remarks[uid] || "未设置"),
|
||
"cookie: " + maskCookie(store[uid]),
|
||
formatStatusLine(uid, last, remarks),
|
||
"修改备注请回复新备注名,发送 清空 可删除备注,发送 q 退出"
|
||
].join("\n")
|
||
}
|
||
function accountDetailMenu(store, bind) {
|
||
var uid = chooseUid(store, bind, "请选择要查看/修改备注的账号:")
|
||
if (!uid) return reply("已退出")
|
||
var detail = accountDetailText(uid, store, bind)
|
||
reply(detail)
|
||
if (/^(无权|未找到)/.test(detail)) return
|
||
var remark = listenInput(60000)
|
||
if (!remark || /^(q|退出|取消)$/i.test(remark)) return reply("已退出")
|
||
var remarks = remarksForStore(store)
|
||
if (/^(清空|删除|none)$/i.test(remark)) delete remarks[uid]; else remarks[uid] = remark.slice(0, 30)
|
||
setRemarkStore(remarks)
|
||
return reply("已更新备注:" + maskUid(uid) + (remarks[uid] ? "(" + remarks[uid] + ")" : ""))
|
||
}
|
||
function accountMenu(store, bind) {
|
||
reply(accountMenuText())
|
||
var op = listenInput(60000)
|
||
if (!op || /^(q|退出|取消)$/i.test(op)) return reply("已退出")
|
||
if (/^(1|查看|查询|列表|账号)$/i.test(op)) return reply(accountListText(store, bind))
|
||
if (/^(2|添加|新增|更新)$/i.test(op)) {
|
||
reply("请在60秒内发送 QX 抓到的完整 Cookie(q:退出)")
|
||
var ck = listenInput(60000)
|
||
if (!ck || /^(q|退出|取消)$/i.test(ck)) return reply("已退出")
|
||
var save = handleSaveCookie(ck, store, bind)
|
||
var remarkMsg = (save.ok && save.uids.length === 1) ? maybePromptRemark(save.uids[0], store) : ""
|
||
return reply(save.text + remarkMsg)
|
||
}
|
||
if (/^(3|详情|备注|改名|名称)$/i.test(op)) return accountDetailMenu(store, bind)
|
||
if (/^(4|删除)$/i.test(op)) {
|
||
var uid = chooseUid(store, bind, "请选择要删除的账号:")
|
||
if (!uid) return reply("已退出")
|
||
return reply(handleDelete("删除 " + uid, store, bind))
|
||
}
|
||
if (/^(5|清空|全部)$/i.test(op)) return reply(handleDelete("删除 all", store, bind))
|
||
return reply("未识别选项,已退出")
|
||
}
|
||
function queryMenu() {
|
||
reply(queryMenuText())
|
||
var op = listenInput(60000)
|
||
if (!op || /^(q|退出|取消)$/i.test(op)) return reply("已退出")
|
||
if (/^(1|查看|查询|状态|结果)$/i.test(op)) return reply(queryStatus())
|
||
if (/^(2|单个|单号|账号)$/i.test(op)) {
|
||
var store = getStore()
|
||
var bind = getBindStore()
|
||
var uid = chooseUid(store, bind, "请选择要查询的账号:")
|
||
if (!uid) return reply("已退出")
|
||
return reply(queryStatus(uid))
|
||
}
|
||
if (/^(3|执行|签到|立即)$/i.test(op)) return reply(signAll("", false, "menu"))
|
||
return reply("未识别选项,已退出")
|
||
}
|
||
function handleDelete(arg, store, bind) {
|
||
var del = arg.match(/^(删除|delete|del|rm)\s+(\S+)/i)
|
||
if (!del) return null
|
||
var duid = del[2]
|
||
var uids = visibleUids(store, bind, false)
|
||
if (/^(all|全部|所有)$/i.test(duid)) {
|
||
for (var i = 0; i < uids.length; i++) { delete store[uids[i]]; delete bind[uids[i]] }
|
||
var remarks = remarksForStore(store); for (var j = 0; j < uids.length; j++) delete remarks[uids[j]]
|
||
setStore(store); setBindStore(bind); setRemarkStore(remarks)
|
||
return "已删除全部当前用户绑定的启点读书CK,共 " + uids.length + " 个账号"
|
||
}
|
||
if (uids.indexOf(duid) < 0) return store[duid] ? ("无权删除 uid: " + duid) : ("未找到 uid: " + duid)
|
||
delete store[duid]; delete bind[duid]
|
||
var remarks = remarksForStore(store); delete remarks[duid]
|
||
setStore(store); setBindStore(bind); setRemarkStore(remarks)
|
||
return "已删除 uid: " + duid
|
||
}
|
||
function normalizeCookieInput(arg) {
|
||
var s = trim(arg)
|
||
var obj = safeJsonParse(s, null)
|
||
if (isObject(obj)) {
|
||
var out = []
|
||
for (var uid in obj) if (obj.hasOwnProperty(uid)) {
|
||
var realUid = isRealUid(uid)
|
||
var cookie = trim(obj[uid])
|
||
if (!realUid || !cookie) continue
|
||
out.push({ uid: realUid, cookie: cookie })
|
||
}
|
||
return out
|
||
}
|
||
return [{ uid: "", cookie: s }]
|
||
}
|
||
function handleSaveCookie(arg, store, bind) {
|
||
var entries = normalizeCookieInput(arg)
|
||
if (!entries.length) return { ok: false, text: "未识别到 Cookie。请发送:启点ck {\"uid\":\"cookie\"} 或 启点ck <QX抓到的完整cookie>", uids: [] }
|
||
var me = currentOwnerId()
|
||
if (!me) return { ok: false, text: "无法识别当前用户,已拒绝保存CK,避免错误绑定。请确认插件在聊天会话中触发;诊断: " + currentUserDebug(), uids: [] }
|
||
var lines = []
|
||
var savedUids = []
|
||
for (var i = 0; i < entries.length; i++) {
|
||
var cookie = entries[i].cookie
|
||
if (cookie.indexOf("=") < 0 || cookie.indexOf(";") < 0) return { ok: false, text: "未识别到 Cookie。请发送:启点ck {\"uid\":\"cookie\"} 或 启点ck <QX抓到的完整cookie>", uids: savedUids }
|
||
var parsedUid = extractUid(cookie)
|
||
var uid = entries[i].uid || parsedUid
|
||
if (!uid) return { ok: false, text: "无法从 Cookie 识别 uid。请确认 QX 抓的是启点读书福利中心 getlogininfo 请求,Cookie 内应包含 QDHeader 或 uid。", uids: savedUids }
|
||
var isNew = !store[uid]
|
||
var oldOwner = bind[uid] || ""
|
||
if (!store[uid] && oldOwner && oldOwner !== me) {
|
||
delete bind[uid]
|
||
oldOwner = ""
|
||
lines.push("检测到 uid: " + uid + " 只有旧绑定没有CK,已自动清理残留绑定")
|
||
}
|
||
if (store[uid] && oldOwner && oldOwner !== me && canRepairBinding(oldOwner, me)) {
|
||
lines.push("检测到 uid: " + uid + " 旧绑定来自历史兜底标识 " + maskUid(oldOwner) + ",已修复为当前用户")
|
||
oldOwner = ""
|
||
}
|
||
if (store[uid] && oldOwner && me && oldOwner !== me) {
|
||
lines.push("无权更新 uid: " + uid + "(该账号已绑定其他用户)")
|
||
continue
|
||
}
|
||
var changed = store[uid] !== cookie
|
||
store[uid] = cookie
|
||
bind[uid] = me
|
||
savedUids.push(uid)
|
||
lines.push("启点读书CK" + (isNew ? "新增" : (changed ? "更新" : "未变化")) + "成功")
|
||
lines.push("uid: " + uid)
|
||
if (entries.length > 1 && parsedUid && parsedUid !== uid) lines.push("提示: JSON uid 与 Cookie 解析 uid 不一致,已按 JSON uid 保存")
|
||
}
|
||
setStore(store); setBindStore(bind)
|
||
lines.push(me ? ("绑定用户: " + maskUid(me)) : "绑定用户: 未识别")
|
||
lines.push("账号数: " + visibleUids(store, bind, false).length)
|
||
lines.push("cookie: " + maskCookie(entries[0].cookie))
|
||
lines.push("签到:发送 启点签到")
|
||
return { ok: savedUids.length > 0, text: lines.join("\n"), uids: savedUids }
|
||
}
|
||
function main() {
|
||
var text = contentText()
|
||
var isCronRun = !text
|
||
var cmd = isCronRun ? cronMode() : commandName(text)
|
||
var arg = isCronRun ? "" : trim(stripCommand(text).replace(/^(sign|archive)\s*/i, ""))
|
||
if (!arg && cmd !== "sign" && cmd !== "archive" && cmd !== "account" && cmd !== "status") { reply(usage()); return }
|
||
if (/^(帮助|help|h)$/i.test(arg)) { reply(usage()); return }
|
||
|
||
var store = getStore()
|
||
var bind = getBindStore()
|
||
if (cmd === "archive" || /^(归档|日志归档|archive)(?:\s+|$)/i.test(arg)) {
|
||
reply(formatArchiveReply(archiveRunLog(isCronRun ? "midnight-archive" : "manual-archive")))
|
||
return
|
||
}
|
||
if (cmd === "sign" || /^(签到|sign|run)(?:\s+|$)/i.test(arg)) {
|
||
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)
|
||
return
|
||
}
|
||
if (cmd === "account") { accountMenu(store, bind); return }
|
||
if (cmd === "status") { queryMenu(); return }
|
||
if (cmd === "ck" && /^(查询|状态|status|list|账号|管理)$/i.test(arg)) { reply("请使用一级菜单:启点账号 或 启点查询;快速提交CK:启点ck <cookie>"); return }
|
||
var delMsg = handleDelete(arg, store, bind)
|
||
if (delMsg !== null) { reply(delMsg); return }
|
||
if (cmd === "ck") {
|
||
var save = handleSaveCookie(arg, store, bind)
|
||
if (save.ok && save.uids.length === 1) save.text += maybePromptRemark(save.uids[0], store)
|
||
reply(save.text)
|
||
return
|
||
}
|
||
var fallbackSave = handleSaveCookie(arg, store, bind)
|
||
reply(fallbackSave.text)
|
||
}
|
||
main()
|