Files
qx/autman/qdreader_sign_autman.js
T

219 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//[open_source: true]
//[create_at: 2026-05-17 03:00:00]
//[title: 起点读书签到]
//[author: Hermes]
//[service: BOSS]
//[class: 工具类]
//[version: 2.0.0]
//[platform: web,qq,wx,tg,tb]
//[public: false]
//[price: 0]
//[disable: false]
//[description: 起点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,并在机器人内执行签到/查询/导出/删除。签名沿用上游 QDReader 的 api.120399.xyz 签名网关。]
//[rule: ^起点(ck|CK|cookie|Cookie|签到|读书)(.*)$]
//[rule: ^qdreader(.*)$]
//[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"}]
var STORE_KEY = "qdreader_cookie_json"
var SIGN_API_KEY = "qdreader_sign_api"
var DEFAULT_SIGN_API = "https://api.120399.xyz"
var QIDIAN_BASE = "https://h5.if.qidian.com"
function reply(msg) {
if (typeof sender !== "undefined" && sender.reply) sender.reply(msg)
else if (typeof sendText === "function") sendText(typeof msg === "string" ? msg : JSON.stringify(msg))
else console.log(typeof msg === "string" ? msg : JSON.stringify(msg))
}
function safeJsonParse(s, def) { try { return JSON.parse(s) } catch (e) { return def } }
function getStore() {
var raw = get(STORE_KEY) || "{}"
var obj = safeJsonParse(raw, {})
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return {}
return obj
}
function setStore(obj) { var s = JSON.stringify(obj); set(STORE_KEY, s); return s }
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()
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])
return arr.join("; ")
}
function b64decode(s) {
s = String(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 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)
return ""
}
function maskCookie(cookie) { cookie = String(cookie || ""); return cookie.length <= 24 ? cookie : cookie.slice(0, 12) + "..." + cookie.slice(-8) }
function contentText() {
var s = ""
try { if (typeof getContent === "function") s = getContent() || "" } catch (e) {}
try { if (!s && typeof sender !== "undefined" && sender.getContent) s = sender.getContent() || "" } catch (e) {}
try { if (!s && typeof message !== "undefined") s = String(message) } catch (e) {}
return String(s || "").trim()
}
function usage() {
return [
"起点读书签到插件",
"提交CK:起点ck <QX抓到的cookie>",
"签到全部:起点签到",
"签到单号:起点签到 <uid>",
"查询CK:起点ck 查询",
"导出CK:起点ck 导出",
"删除CK:起点ck 删除 <uid>",
"说明:QX只负责抓cookie;签到由本 Autman 插件执行。"
].join("\n")
}
function stripCommand(text) {
return String(text || "")
.replace(/^起点(?:ck|CK|cookie|Cookie|签到|读书)\s*/i, "")
.replace(/^qdreader\s*/i, "")
.trim()
}
function commandName(text) {
if (/^起点(?:签到|读书)(?:\s|$)/i.test(text)) return "sign"
if (/^qdreader\s+sign(?:\s|$)/i.test(text)) return "sign"
return "ck"
}
function qs(obj) {
var parts = []
for (var k in (obj || {})) {
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 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
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 || "" }
}
function httpReq(opt) {
if (typeof request === "function") return request(opt)
if (typeof $request === "function") return $request(opt)
throw new Error("当前 Autman 环境没有 request/$request 方法,无法发起签到请求")
}
function parseResp(res) {
var body = res && (res.body !== undefined ? res.body : (res.data !== undefined ? res.data : res))
if (typeof body === "string") return safeJsonParse(body, body)
return body
}
function postJson(url, data) {
return parseResp(httpReq({ url: url, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }))
}
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(/\/$/, "")
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.data) throw new Error("签名失败:" + JSON.stringify(signed))
var headers = signed.data
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, 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 out = parseResp(httpReq(req))
if (typeof out === "string") out = safeJsonParse(out, out)
return out
}
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"
}
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 ok = resultOk(res)
var msg = resultMsg(res) || (ok ? "签到请求成功" : "签到失败")
return { uid: uid, ok: ok, msg: msg, raw: res }
}
function signAll(targetUid) {
var store = getStore()
var uids = Object.keys(store)
if (targetUid) uids = uids.filter(function (u) { return u === targetUid })
if (!uids.length) return targetUid ? ("未找到 uid: " + targetUid) : "还没有保存起点读书CK"
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)
} catch (e) {
lines.push("❌ " + uid + "" + (e && e.message ? e.message : String(e)))
}
}
return lines.join("\n")
}
var text = contentText()
var cmd = commandName(text)
var arg = 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
}
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 }
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
}
var cookie = arg
if (cookie.indexOf("=") < 0 || cookie.indexOf(";") < 0) { reply("未识别到 Cookie。请发送:起点ck <QX抓到的完整cookie>"); return }
var uid = extractUid(cookie)
if (!uid) { reply("无法从 Cookie 识别 uid。请确认 QX 抓的是起点读书福利中心 getlogininfo 请求,Cookie 内应包含 QDHeader 或 uid。"); return }
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"))