docs: capture qdreader plugin menu patterns
This commit is contained in:
+86
-48
@@ -1,6 +1,6 @@
|
||||
# 起点读书签到 Autman 插件
|
||||
# 启点读书 QDReader 签到 Autman 插件
|
||||
|
||||
起点读书(QDReader)Autman 签到插件。QX 只负责抓取 Cookie,签到由本插件在 Autman 内完成。
|
||||
QDReader/起点读书 Autman 签到插件。用户侧触发词统一使用“启点”,避免平台关键词麻烦;QX 只负责抓取 Cookie,账号管理、执行查询和签到都在 Autman 插件内完成。
|
||||
|
||||
## 文件位置
|
||||
|
||||
@@ -23,66 +23,104 @@ https://gitea.chickliu.fun/Hermes/autman-plugins/raw/branch/main/plugins/qdreade
|
||||
https://gitea.chickliu.fun/Hermes/autman-plugins/raw/branch/main/js/qdreader_sign_autman.js
|
||||
```
|
||||
|
||||
## 功能
|
||||
## 当前交互设计
|
||||
|
||||
- QX 抓到 Cookie 后,把 `起点ck <cookie>` 发给 Autman 保存。
|
||||
- 手动全部签到:`起点签到`。
|
||||
- 手动单账号签到:`起点签到 <uid>`。
|
||||
- 定时签到:头注 `//[cron: 15 7 * * *]`,每日 07:15 自动签到。
|
||||
- 查询/维护:`起点ck 查询`、`起点ck 导出`、`起点ck 删除 <uid|all>`。
|
||||
- 签到结果按成功/失败汇总;普通状态输出 uid 会脱敏,导出命令除外。
|
||||
|
||||
## v2.2.0 优化点
|
||||
|
||||
- 存储兼容:同时兼容 `get/set` 和 `bucketGet/bucketSet("otto", key)`。
|
||||
- 消息兼容:兼容 `getContent/GetContent/input/sender.getContent/sender.getMessage`。
|
||||
- 请求兼容:兼容 `request/$request`,并统一解析 `body/data` JSON。
|
||||
- 签名兼容:兼容 `{code:0,data:{headers:{...}}}`、`{code:200,data:{...}}`、`{headers:{...}}` 等签名网关返回形态。
|
||||
- 网络健壮性:新增 `otto.qdreader_retry_count`,默认重试 1 次,最大 3 次。
|
||||
- 定时体验:新增 `otto.qdreader_cron_silent`,可让定时触发且无账号时静默。
|
||||
- 安全脱敏:保存/查询/签到结果不直接输出完整 Cookie;`导出` 是管理员主动行为,会输出完整 JSON。
|
||||
- 测试覆盖:`tests/qdreader_plugin.test.mjs` 覆盖手动、定时、bucket 存储、重试、查询脱敏、删除全部。
|
||||
|
||||
## 配置
|
||||
|
||||
Autman 参数:
|
||||
### 一级菜单
|
||||
|
||||
```text
|
||||
otto.qdreader_cookie_json = {"uid":"cookie"}
|
||||
otto.qdreader_sign_api = https://api.120399.xyz
|
||||
otto.qdreader_retry_count = 1
|
||||
otto.qdreader_cron_silent = false
|
||||
启点账号
|
||||
启点查询
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `qdreader_cookie_json`:机器人命令维护,JSON Map 格式。
|
||||
- `qdreader_sign_api`:兼容 `/sign` 的签名网关,默认 `https://api.120399.xyz`。
|
||||
- `qdreader_retry_count`:请求异常重试次数,默认 1,范围 0-3。
|
||||
- `qdreader_cron_silent`:`true` 时,如果定时触发但没有账号,不发送提醒。
|
||||
|
||||
## 命令
|
||||
`启点账号` 只负责账号管理:
|
||||
|
||||
```text
|
||||
起点ck <QX抓到的完整cookie>
|
||||
起点ck json {"uid":"cookie"}
|
||||
起点ck 查询
|
||||
起点ck 导出
|
||||
起点ck 删除 <uid>
|
||||
起点ck 删除 all
|
||||
起点签到
|
||||
起点签到 <uid>
|
||||
启点账号管理
|
||||
1. 查看账号
|
||||
2. 添加/更新账号
|
||||
3. 账号详情/修改备注
|
||||
4. 删除账号
|
||||
5. 清空账号
|
||||
q. 退出
|
||||
请回复序号
|
||||
```
|
||||
|
||||
`启点查询` 只负责执行情况和执行动作:
|
||||
|
||||
```text
|
||||
启点查询
|
||||
1. 查看最近执行情况
|
||||
2. 查询单个账号
|
||||
3. 立即执行签到
|
||||
q. 退出
|
||||
请回复序号
|
||||
```
|
||||
|
||||
### 快速通道
|
||||
|
||||
```text
|
||||
启点ck <QX抓到的完整cookie>
|
||||
启点签到
|
||||
启点签到 <uid>
|
||||
qdreader sign
|
||||
```
|
||||
|
||||
`启点ck` 只保留快速提交 Cookie,不再承担查询/导出/批量导入等管理功能。
|
||||
|
||||
## 账号与备注
|
||||
|
||||
- 保存 Cookie 后自动绑定当前 `sender.getUserID()`。
|
||||
- 查询、删除、手动签到默认只操作当前用户绑定的账号。
|
||||
- 账号详情、删除、单账号查询都使用编号选择,不要求用户复制/输入 UID。
|
||||
- 添加/更新账号后会提示是否添加备注。
|
||||
- 修改备注在“账号详情/修改备注”中完成。
|
||||
- 删除账号或清空当前用户账号时,会同步清理对应备注。
|
||||
- 普通回复只展示脱敏 UID 和备注,不输出完整 Cookie。
|
||||
|
||||
## 存储
|
||||
|
||||
主数据桶:
|
||||
|
||||
```text
|
||||
hermes_qidian
|
||||
```
|
||||
|
||||
主要 key:
|
||||
|
||||
```text
|
||||
qdreader_cookie_json # uid -> full cookie
|
||||
qdreader_user_bind_json # uid -> sender.getUserID()
|
||||
qdreader_remark_json # uid -> 备注名
|
||||
qdreader_last_result_json # uid -> 最近执行结果 JSON 字符串
|
||||
qdreader_sign_api # 签名网关
|
||||
qdreader_retry_count # 请求失败重试次数
|
||||
qdreader_cron_silent # 定时无账号静默
|
||||
```
|
||||
|
||||
读取兼容顺序:
|
||||
|
||||
```text
|
||||
bucketGet("hermes_qidian", key)
|
||||
↓ fallback
|
||||
get(key)
|
||||
↓ fallback
|
||||
bucketGet("otto", key) # 旧数据兼容
|
||||
```
|
||||
|
||||
写入顺序:
|
||||
|
||||
```text
|
||||
bucketSet("hermes_qidian", key, value)
|
||||
set(key, value)
|
||||
```
|
||||
|
||||
## 签名与签到流程
|
||||
|
||||
签名与签到流程沿用上游 QDReader:
|
||||
|
||||
1. 从 Cookie 解析 `QDHeader/qdh/uid/guid/qimei/qimei36`。
|
||||
2. `POST https://api.120399.xyz/sign` 获取起点请求签名头。
|
||||
2. `POST <qdreader_sign_api>/sign` 获取请求签名头。
|
||||
3. `POST https://h5.if.qidian.com/argus/api/v3/checkin/checkin` 完成签到。
|
||||
4. 汇总每个账号签到结果。
|
||||
4. 写入 `qdreader_last_result_json`。
|
||||
5. 汇总每个账号签到结果。
|
||||
|
||||
## 本地验证
|
||||
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
//[open_source: true]
|
||||
//[create_at: 2026-05-17 03:00:00]
|
||||
//[title: 起点读书签到]
|
||||
//[title: 启点读书签到]
|
||||
//[author: Hermes]
|
||||
//[service: BOSS]
|
||||
//[class: 工具类]
|
||||
//[version: 2.2.0]
|
||||
//[version: 2.3.0]
|
||||
//[platform: web,qq,wx,tg,tb,fs,we]
|
||||
//[public: false]
|
||||
//[price: 0]
|
||||
//[disable: false]
|
||||
//[description: 起点读书(QDReader) Autman 签到插件:保存 QX 抓到的 H5 Cookie,支持手动“起点签到”和每日 07:15 定时签到;带多环境存储兼容、请求重试、签名响应兼容、结果摘要和安全脱敏。]
|
||||
//[rule: ^起点(ck|CK|cookie|Cookie|签到|读书)(.*)$]
|
||||
//[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,可替换为兼容 /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 时定时触发且没有账号不回复"}]
|
||||
//[param: {"required":false,"key":"hermes_qidian.qdreader_cookie_json","bool":false,"placeholder":"{\"uid\":\"cookie\"}","name":"QDReader Cookie JSON","desc":"机器人交互维护,JSON Map 格式;兼容读取旧 otto bucket"}]
|
||||
//[param: {"required":false,"key":"hermes_qidian.qdreader_sign_api","bool":false,"placeholder":"https://api.120399.xyz","name":"QDReader 签名网关","desc":"默认 https://api.120399.xyz,可替换为兼容 /sign 的网关"}]
|
||||
//[param: {"required":false,"key":"hermes_qidian.qdreader_retry_count","bool":false,"placeholder":"1","name":"请求失败重试次数","desc":"默认 1,最大 3,仅对网络异常/非业务异常重试"}]
|
||||
//[param: {"required":false,"key":"hermes_qidian.qdreader_cron_silent","bool":true,"placeholder":"false","name":"定时无账号时静默","desc":"true 时定时触发且没有账号不回复"}]
|
||||
|
||||
var BUCKET = "otto"
|
||||
var BUCKET = "hermes_qidian"
|
||||
var LEGACY_BUCKET = "otto"
|
||||
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"
|
||||
@@ -37,15 +40,16 @@ function trim(s) { return String(s === undefined || s === null ? "" : s).replace
|
||||
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) {}
|
||||
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 lv = bucketGet(LEGACY_BUCKET, key); if (lv !== undefined && lv !== null && String(lv) !== "") return lv } } 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) {}
|
||||
try { if (typeof set === "function") { set(key, s); ok = true } } catch (e) {}
|
||||
if (!ok) throw new Error("当前 Autman 环境没有 set/bucketSet,无法保存配置")
|
||||
return s
|
||||
}
|
||||
@@ -75,15 +79,36 @@ function reply(msg) {
|
||||
}
|
||||
function debug(msg) { try { if (typeof Debug === "function") Debug(String(msg)); else console.log(String(msg)) } catch (e) {} }
|
||||
|
||||
function getStore() {
|
||||
var raw = cfgGet(STORE_KEY, "{}")
|
||||
var obj = safeJsonParse(raw, {})
|
||||
function cleanStringMap(obj) {
|
||||
if (!isObject(obj)) return {}
|
||||
var clean = {}
|
||||
for (var uid in obj) if (obj.hasOwnProperty(uid) && obj[uid]) clean[String(uid)] = String(obj[uid])
|
||||
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 currentUserId() {
|
||||
var v = ""
|
||||
try { if (typeof sender !== "undefined" && sender && sender.getUserID) v = sender.getUserID() || "" } catch (e) {}
|
||||
try { if (!v && typeof sender !== "undefined" && sender && sender.getUserId) v = sender.getUserId() || "" } catch (e) {}
|
||||
try { if (!v && typeof userId !== "undefined") v = String(userId || "") } catch (e) {}
|
||||
try { if (!v && typeof getUserID === "function") v = getUserID() || "" } catch (e) {}
|
||||
return trim(v)
|
||||
}
|
||||
function boundToCurrent(uid, bind) {
|
||||
var owner = bind && bind[String(uid)] ? String(bind[String(uid)]) : ""
|
||||
var me = currentUserId()
|
||||
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 = {}
|
||||
@@ -146,26 +171,32 @@ function contentText() {
|
||||
}
|
||||
function usage() {
|
||||
return [
|
||||
"起点读书签到插件 v2.2.0",
|
||||
"提交CK:起点ck <QX抓到的cookie>",
|
||||
"签到全部:起点签到",
|
||||
"签到单号:起点签到 <uid>",
|
||||
"查询CK:起点ck 查询",
|
||||
"导出CK:起点ck 导出(会输出完整 Cookie,仅管理员自用)",
|
||||
"删除CK:起点ck 删除 <uid|all>",
|
||||
"配置签名网关:Autman 参数 otto.qdreader_sign_api,默认 " + DEFAULT_SIGN_API,
|
||||
"启点读书签到插件 v2.3.0",
|
||||
"【一级菜单】",
|
||||
"账号管理:启点账号",
|
||||
"执行查询:启点查询",
|
||||
"【快速通道】",
|
||||
"快速提交CK:启点ck <QX抓到的cookie>",
|
||||
"直接签到:启点签到",
|
||||
"指定签到:启点签到 <uid>",
|
||||
"【配置】",
|
||||
"签名网关:Autman 参数 hermes_qidian.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 trim(s.replace(/^起点(?:签到|读书)\s*/i, ""))
|
||||
if (/^起点(?:ck|CK|cookie|Cookie)(?:\s|$)/i.test(s)) return trim(s.replace(/^起点(?:ck|CK|cookie|Cookie)\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 "sign"
|
||||
if (/^qdreader\s+sign(?:\s|$)/i.test(text)) return "sign"
|
||||
if (/^启点(?:查询)(?:\s|$)/i.test(text)) return "status"
|
||||
if (/^启点(?:账号)(?:\s|$)/i.test(text)) return "account"
|
||||
return "ck"
|
||||
}
|
||||
function qs(obj) {
|
||||
@@ -259,20 +290,34 @@ 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 setLastResult(uid, ok, msg) {
|
||||
var key = "qdreader_last_result_json"
|
||||
var obj = cleanStringMap(safeJsonParse(cfgGet(key, "{}"), {}))
|
||||
obj[String(uid)] = JSON.stringify({ ok: !!ok, msg: String(msg || ""), time: new Date().toISOString() })
|
||||
cfgSet(key, JSON.stringify(obj))
|
||||
}
|
||||
function getLastResults() {
|
||||
var raw = cleanStringMap(safeJsonParse(cfgGet("qdreader_last_result_json", "{}"), {}))
|
||||
var out = {}
|
||||
for (var uid in raw) if (raw.hasOwnProperty(uid)) out[uid] = safeJsonParse(raw[uid], { msg: raw[uid] })
|
||||
return out
|
||||
}
|
||||
function doCheckin(uid, cookie) {
|
||||
var res = qidianRequest(CHECKIN_PATH, "POST", {}, cookie)
|
||||
var ok = resultOk(res)
|
||||
var msg = resultMsg(res) || (ok ? "签到请求成功" : ("签到失败" + (resultCode(res) !== undefined ? " code=" + resultCode(res) : "")))
|
||||
setLastResult(uid, ok, msg)
|
||||
return { uid: uid, ok: ok, msg: msg, raw: res }
|
||||
}
|
||||
function signAll(targetUid) {
|
||||
function signAll(targetUid, allUsers) {
|
||||
var store = getStore()
|
||||
var uids = Object.keys(store).sort()
|
||||
var bind = getBindStore()
|
||||
var uids = visibleUids(store, bind, !!allUsers)
|
||||
if (targetUid) uids = uids.filter(function (u) { return u === targetUid })
|
||||
if (!uids.length) return targetUid ? ("未找到 uid: " + targetUid) : "还没有保存起点读书CK"
|
||||
if (!uids.length) return targetUid ? ("未找到 uid: " + targetUid) : "还没有保存启点读书CK"
|
||||
var okCount = 0
|
||||
var failCount = 0
|
||||
var lines = ["起点读书签到结果:"]
|
||||
var lines = ["启点读书签到结果:"]
|
||||
for (var i = 0; i < uids.length; i++) {
|
||||
var uid = uids[i]
|
||||
try {
|
||||
@@ -287,70 +332,218 @@ function signAll(targetUid) {
|
||||
lines.push("汇总:成功 " + okCount + " / 失败 " + failCount + " / 总计 " + uids.length)
|
||||
return lines.join("\n")
|
||||
}
|
||||
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++
|
||||
}
|
||||
setStore(store)
|
||||
return "起点读书CK JSON已导入/更新 " + count + " 个账号,当前共 " + Object.keys(store).length + " 个账号\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 handleDelete(arg, store) {
|
||||
function queryStatus(targetUid) {
|
||||
var store = getStore()
|
||||
var bind = getBindStore()
|
||||
var last = getLastResults()
|
||||
var remarks = getRemarkStore()
|
||||
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 lines = [targetUid ? "启点单账号执行情况:" : "启点执行情况:"]
|
||||
for (var i = 0; i < uids.length; i++) lines.push(formatStatusLine(uids[i], last, remarks))
|
||||
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 = getRemarkStore()
|
||||
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 = getRemarkStore()
|
||||
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) {
|
||||
if (!uid) return ""
|
||||
reply("是否为该账号添加备注?回复备注名,发送 n/跳过 则不添加")
|
||||
var remark = listenInput(60000)
|
||||
if (!remark || /^(n|no|否|不|跳过|q|退出|取消)$/i.test(remark)) return ""
|
||||
var remarks = 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 = getRemarkStore()
|
||||
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 = getRemarkStore()
|
||||
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 uid = extractUid(ck)
|
||||
var msg = handleSaveCookie(ck, store, bind)
|
||||
var remarkMsg = uid ? maybePromptRemark(uid) : ""
|
||||
return reply(msg + 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))
|
||||
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)) {
|
||||
var n = Object.keys(store).length
|
||||
setStore({})
|
||||
return "已删除全部起点读书CK,共 " + n + " 个账号"
|
||||
for (var i = 0; i < uids.length; i++) { delete store[uids[i]]; delete bind[uids[i]] }
|
||||
var remarks = getRemarkStore(); for (var j = 0; j < uids.length; j++) delete remarks[uids[j]]
|
||||
setStore(store); setBindStore(bind); setRemarkStore(remarks)
|
||||
return "已删除全部当前用户绑定的启点读书CK,共 " + uids.length + " 个账号"
|
||||
}
|
||||
var existed = !!store[duid]
|
||||
delete store[duid]
|
||||
setStore(store)
|
||||
return existed ? ("已删除 uid: " + duid) : ("未找到 uid: " + duid)
|
||||
if (uids.indexOf(duid) < 0) return store[duid] ? ("无权删除 uid: " + duid) : ("未找到 uid: " + duid)
|
||||
delete store[duid]; delete bind[duid]
|
||||
var remarks = getRemarkStore(); delete remarks[duid]
|
||||
setStore(store); setBindStore(bind); setRemarkStore(remarks)
|
||||
return "已删除 uid: " + duid
|
||||
}
|
||||
function handleSaveCookie(arg, store) {
|
||||
function handleSaveCookie(arg, store, bind) {
|
||||
var cookie = arg
|
||||
if (cookie.indexOf("=") < 0 || cookie.indexOf(";") < 0) return "未识别到 Cookie。请发送:起点ck <QX抓到的完整cookie>"
|
||||
if (cookie.indexOf("=") < 0 || cookie.indexOf(";") < 0) return "未识别到 Cookie。请发送:启点ck <QX抓到的完整cookie>"
|
||||
var uid = extractUid(cookie)
|
||||
if (!uid) return "无法从 Cookie 识别 uid。请确认 QX 抓的是起点读书福利中心 getlogininfo 请求,Cookie 内应包含 QDHeader 或 uid。"
|
||||
if (!uid) return "无法从 Cookie 识别 uid。请确认 QX 抓的是启点读书福利中心 getlogininfo 请求,Cookie 内应包含 QDHeader 或 uid。"
|
||||
var isNew = !store[uid]
|
||||
var changed = store[uid] !== cookie
|
||||
var me = currentUserId()
|
||||
store[uid] = cookie
|
||||
setStore(store)
|
||||
return ["起点读书CK" + (isNew ? "新增" : (changed ? "更新" : "未变化")) + "成功", "uid: " + uid, "账号数: " + Object.keys(store).length, "cookie: " + maskCookie(cookie), "签到:发送 起点签到"].join("\n")
|
||||
if (me) bind[uid] = me
|
||||
setStore(store); setBindStore(bind)
|
||||
return ["启点读书CK" + (isNew ? "新增" : (changed ? "更新" : "未变化")) + "成功", "uid: " + uid, me ? ("绑定用户: " + maskUid(me)) : "绑定用户: 未识别", "账号数: " + visibleUids(store, bind, false).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 (!arg && cmd !== "sign" && cmd !== "account" && cmd !== "status") { reply(usage()); return }
|
||||
if (/^(帮助|help|h)$/i.test(arg)) { reply(usage()); return }
|
||||
|
||||
var store = getStore()
|
||||
var bind = getBindStore()
|
||||
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)
|
||||
var out = signAll(target, isCronRun)
|
||||
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 (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 }
|
||||
var jsonMatch = arg.match(/^json\s+([\s\S]+)$/i)
|
||||
if (jsonMatch) { reply(importJson(jsonMatch[1], store)); return }
|
||||
reply(handleSaveCookie(arg, store))
|
||||
reply(handleSaveCookie(arg, store, bind))
|
||||
}
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user