refactor: switch qx to manual autman bot submission

This commit is contained in:
2026-05-17 02:58:28 +08:00
parent 88381c3487
commit cb770dbcb6
5 changed files with 190 additions and 201 deletions
+103 -110
View File
@@ -1,63 +1,38 @@
//[open_source: true]
//[create_at: 2026-05-17 02:50:00]
//[title: 起点读书签到]
//[create_at: 2026-05-17 03:00:00]
//[title: 起点读书CK]
//[author: Hermes]
//[service: BOSS]
//[class: 工具类]
//[version: 1.0.0]
//[version: 1.1.0]
//[platform: web,qq,wx,tg,tb]
//[public: false]
//[price: 0]
//[disable: false]
//[description: 起点读书(QDReader) Autman 路由插件:接收 Quantumult X 重写上传的 Cookie,按 uid 自动新增/更新;也可查看 Cookie 状态。实际每日签到建议仍由 QX 定时运行仓库内 qdreader.upstream.js,或把 QDREADER_COOKIE JSON 同步到 Node/青龙环境执行。]
//[router: /qdreader/cookie]
//[method: get]
//[method: post]
//[param: {"required":false,"key":"otto.qdreader_token","bool":false,"placeholder":"访问Token","name":"接口鉴权Token","desc":"外部请求携带 access_token 或 token;留空则不校验"}]
//[param: {"required":false,"key":"otto.qdreader_cookie_json","bool":false,"placeholder":"{\"uid\":\"cookie\"}","name":"QDReader Cookie JSON","desc":"由 QX 重写自动维护,JSON Map 格式"}]
//[description: 起点读书(QDReader) Autman 机器人交互插件:把 QX 抓到的 Cookie 发给机器人即可保存,按 uid 自动新增/更新;支持查询、删除、导出 QDREADER_COOKIE。]
//[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 格式"}]
var STORE_KEY = "qdreader_cookie_json"
function replyJSON(obj) {
if (typeof response === "function") response(obj)
else if (typeof sender !== "undefined" && sender.reply) sender.reply(obj)
else if (typeof sendText === "function") sendText(JSON.stringify(obj))
else console.log(JSON.stringify(obj))
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 fail(code, msg, data) {
replyJSON({ ok: false, status: "fail", retcode: code, msg: msg, data: data || {} })
}
function ok(data, msg) {
replyJSON({ ok: true, status: "ok", retcode: 0, msg: msg || "success", data: data || {} })
}
function safeJsonParse(s, def) {
if (!s) return def
try { return JSON.parse(s) } catch (e) { return def }
}
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 getHeader(obj, name) {
if (!obj) return ""
var lower = String(name).toLowerCase()
for (var k in obj) if (String(k).toLowerCase() === lower) return obj[k]
return ""
}
function parseCookieString(cookie) {
var jar = {}
String(cookie || "").split(";").forEach(function (part) {
@@ -69,98 +44,116 @@ function parseCookieString(cookie) {
})
return jar
}
function b64decode(s) {
s = String(s || "")
try {
if (typeof Buffer !== "undefined") return Buffer.from(s, "base64").toString("utf8")
} catch (e) {}
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)
if (!decoded) return ""
var parts = decoded.split("|")
return String(parts[0] || "").trim()
return String(decoded.split("|")[0] || "").trim()
}
function extractUid(cookie, body) {
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)
var obj = safeJsonParse(body, null)
try {
uid = obj && obj.Data && obj.Data.UserInfo && (obj.Data.UserInfo.UserId || obj.Data.UserInfo.userId)
if (uid) return String(uid)
} catch (e) {}
return ""
}
function readRequest() {
var method = typeof getMethod === "function" ? getMethod() : "get"
var params = typeof getRouterParams === "function" ? (getRouterParams() || {}) : {}
var body = {}
var raw = ""
if (String(method).toLowerCase() === "post") {
raw = typeof getRouterData === "function" ? (getRouterData() || "") : ""
body = safeJsonParse(raw, {})
}
return { method: method, params: params, body: body, raw: raw }
function maskCookie(cookie) {
cookie = String(cookie || "")
if (cookie.length <= 24) return cookie
return 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>",
"提交JSON:起点ck json {\"uid\":\"cookie\"}",
"查询:起点ck 查询",
"导出:起点ck 导出",
"删除:起点ck 删除 <uid>",
"说明:QX只负责抓cookie,把通知/变量里的“起点ck ...”整段发给机器人即可。"
].join("\n")
}
function stripCommand(text) {
return String(text || "")
.replace(/^起点(?:ck|CK|cookie|Cookie)\s*/i, "")
.replace(/^qdreader\s*/i, "")
.trim()
}
var req = readRequest()
var token = req.body.access_token || req.body.token || req.params.access_token || req.params.token || ""
var configuredToken = get("qdreader_token") || ""
if (configuredToken && configuredToken !== token) {
fail(-1, "token错误")
return
}
var text = contentText()
var arg = stripCommand(text)
if (!arg) { reply(usage()); return }
var action = req.body.action || req.params.action || "status"
var cookie = req.body.cookie || req.body.ck || req.params.cookie || req.params.ck || ""
var uid = req.body.uid || req.params.uid || ""
var responseBody = req.body.response_body || req.body.body || ""
if (/^(帮助|help|h)$/i.test(arg)) { reply(usage()); return }
if (action === "status") {
var store = getStore()
ok({ count: Object.keys(store).length, uids: Object.keys(store), envName: "QDREADER_COOKIE" }, "起点读书 Cookie 接收插件已启用")
return
}
if (action === "delete") {
var storeDel = getStore()
if (!uid) { fail(-2, "uid不能为空"); return }
var existed = !!storeDel[uid]
delete storeDel[uid]
var rawDel = setStore(storeDel)
ok({ uid: uid, existed: existed, count: Object.keys(storeDel).length, qdreader_cookie: rawDel }, existed ? "已删除" : "原本不存在")
return
}
if (action === "replace_all") {
var incoming = req.body.cookies || req.body.qdreader_cookie || req.body.QDREADER_COOKIE || req.params.cookies || ""
var obj = typeof incoming === "object" ? incoming : safeJsonParse(incoming, null)
if (!obj || typeof obj !== "object" || Array.isArray(obj)) { fail(-3, "cookies 必须是 JSON Map"); return }
var rawAll = setStore(obj)
ok({ count: Object.keys(obj).length, uids: Object.keys(obj), qdreader_cookie: rawAll }, "已整体替换")
return
}
// default: update cookie
if (!cookie) {
fail(-4, "cookie不能为空", { usage: "POST JSON: {action:'update', uid:'可选', cookie:'Cookie字符串', response_body:'可选'}" })
return
}
uid = uid || extractUid(cookie, responseBody)
if (!uid) {
fail(-5, "无法识别 uidCookie 中缺少 QDHeader/uid,或 response_body 无 UserInfo.UserId")
return
}
var store = getStore()
if (/^(查询|状态|status|list)$/i.test(arg)) {
var uids = Object.keys(store)
reply(uids.length ? ("起点读书CK:共 " + uids.length + " 个账号\n" + uids.join("\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 + " 个账号")
return
}
// 直接把 cookie 发过来;兼容“uid=xxx; ...”或含 QDHeader 的 Cookie。
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
var rawStore = setStore(store)
ok({ uid: uid, is_new: isNew, changed: changed, count: Object.keys(store).length, envName: "QDREADER_COOKIE", qdreader_cookie: rawStore }, (isNew ? "新增" : (changed ? "更新" : "未变化")) + "成功")
setStore(store)
reply([
"起点读书CK" + (isNew ? "新增" : (changed ? "更新" : "未变化")) + "成功",
"uid: " + uid,
"账号数: " + Object.keys(store).length,
"cookie: " + maskCookie(cookie),
"导出环境变量:发送 起点ck 导出"
].join("\n"))