From 47e9535c9e8ad0085b1091dc61bbd244d0c076c2 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 31 May 2026 12:46:27 +0800 Subject: [PATCH] feat: add pingme qx capture rewrite --- README.md | 8 +++ conf/pingme.conf | 5 ++ docs/pingme-capture.md | 72 +++++++++++++++++++++++ js/pingme_capture.js | 129 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 conf/pingme.conf create mode 100644 docs/pingme-capture.md create mode 100644 js/pingme_capture.js diff --git a/README.md b/README.md index 489aa2a..a482684 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,10 @@ ```text conf/qdreader.conf # 起点读书 QX 远程重写订阅文本,参考 chickliu/qx 老仓库风格 conf/kyapp_qinglong.conf # 快音 CK → 青龙 KYAPP 自动更新重写订阅 +conf/pingme.conf # PingMe → NiuPanel pingme_multi.js 抓包重写订阅 js/qdreader_cookie.js # 起点读书 QX Rewrite 脚本入口 js/kyapp_qinglong_update.js # 快音 CK 抓取并更新青龙 KYAPP +js/pingme_capture.js # PingMe 抓取 pingme_multi.js 所需账号 JSON docs/qx-rewrite-lessons.md # 本次制作经验 source/qdreader.upstream.js # 上游 QDReader 脚本备份 ``` @@ -27,6 +29,12 @@ https://gitea.chickliu.fun/Hermes/qx/raw/branch/main/conf/qdreader.conf https://gitea.chickliu.fun/Hermes/qx/raw/branch/main/conf/kyapp_qinglong.conf ``` +PingMe → NiuPanel 抓包订阅地址: + +```text +https://gitea.chickliu.fun/Hermes/qx/raw/branch/main/conf/pingme.conf +``` + ## 订阅内容 ```text diff --git a/conf/pingme.conf b/conf/pingme.conf new file mode 100644 index 0000000..0514c6c --- /dev/null +++ b/conf/pingme.conf @@ -0,0 +1,5 @@ +hostname = api.pingmeapp.net + +# PingMe:抓取 NiuPanel pingme_multi.js 所需账号配置 +# 使用说明见 docs/pingme-capture.md +^https?:\/\/api\.pingmeapp\.net\/app\/(?:queryBalanceAndBonus|checkIn|videoBonus)(?:\?|$) url script-request-header https://gitea.chickliu.fun/Hermes/qx/raw/branch/main/js/pingme_capture.js diff --git a/docs/pingme-capture.md b/docs/pingme-capture.md new file mode 100644 index 0000000..cbb9b08 --- /dev/null +++ b/docs/pingme-capture.md @@ -0,0 +1,72 @@ +# PingMe → NiuPanel 抓包配置(Quantumult X) + +## 订阅地址 + +```text +https://gitea.chickliu.fun/Hermes/qx/raw/branch/main/conf/pingme.conf +``` + +## 功能 + +这个 QX 重写监听 PingMe 官方接口: + +```text +api.pingmeapp.net/app/queryBalanceAndBonus +api.pingmeapp.net/app/checkIn +api.pingmeapp.net/app/videoBonus +``` + +抓到后生成 NiuPanel 任务 `pingme_multi.js` 所需的账号 JSON 对象: + +```json +{"url":"...","paramsRaw":{"...":"..."},"headers":{"User-Agent":"..."}} +``` + +NiuPanel 当前任务读取位置: + +```text +/app/data/scripts/pingme_ck.json +``` + +宿主机路径: + +```text +/Users/chick/.openclaw/workspace/niupanel/niupanel-data/scripts/pingme_ck.json +``` + +## 使用流程 + +1. Quantumult X 添加重写订阅: + + ```text + https://gitea.chickliu.fun/Hermes/qx/raw/branch/main/conf/pingme.conf + ``` + +2. 开启 MitM 并信任证书。 +3. 确认 hostname 包含: + + ```text + api.pingmeapp.net + ``` + +4. 打开 PingMe App,触发余额、签到或视频奖励接口。 +5. QX 弹窗标题显示 `CK 获取成功`,正文是完整 JSON 对象。 +6. 将 JSON 对象追加到 NiuPanel 的 `pingme_ck.json`。 + +多账号格式仍按 `pingme_multi.js` 当前逻辑:每个账号一个 JSON 对象,多个账号可使用英文逗号分隔: + +```text +{账号1JSON},{账号2JSON} +``` + +也支持 JSON 数组: + +```json +[{"url":"...","paramsRaw":{},"headers":{}},{"url":"...","paramsRaw":{},"headers":{}}] +``` + +## 安全说明 + +- 仓库不提交任何真实 CK、token、设备 ID 或 headers 明文。 +- 脚本不上传数据,不请求第三方,只在 QX 本地保存最近一次抓取结果 `pingme_ck_last` 并弹窗展示。 +- 重写范围只包含 `api.pingmeapp.net` 的三个任务相关接口。 diff --git a/js/pingme_capture.js b/js/pingme_capture.js new file mode 100644 index 0000000..86980d5 --- /dev/null +++ b/js/pingme_capture.js @@ -0,0 +1,129 @@ +/* + * PingMe CK 抓取 - Quantumult X Rewrite + * + * 订阅地址: + * https://gitea.chickliu.fun/Hermes/qx/raw/branch/main/conf/pingme.conf + * + * 功能: + * - 监听 api.pingmeapp.net/app/queryBalanceAndBonus、checkIn、videoBonus + * - 抓取 pingme_multi.js 需要的 url / paramsRaw / headers + * - 弹窗输出可直接追加到 NiuPanel /app/data/scripts/pingme_ck.json 的 JSON 对象 + * + * 安全:不上传 CK,不请求第三方;仅在 QX 本地保存最近一次抓取结果。 + */ + +const SCRIPT_NAME = 'PingMe' + +function notify(subtitle, message) { + $notify(SCRIPT_NAME, subtitle || '', message || '') +} + +function done() { + $done({}) +} + +function setPref(key, value) { + try { return $prefs.setValueForKey(String(value), key) } catch (e) { return false } +} + +function getHeader(headers, name) { + const lower = String(name).toLowerCase() + for (const k in (headers || {})) { + if (String(k).toLowerCase() === lower) return headers[k] + } + return '' +} + +function parseRawQuery(url) { + const raw = String(url || '') + const qIndex = raw.indexOf('?') + if (qIndex < 0) return {} + const hashIndex = raw.indexOf('#', qIndex + 1) + const query = raw.slice(qIndex + 1, hashIndex >= 0 ? hashIndex : undefined) + const out = {} + query.split('&').forEach(pair => { + if (!pair) return + const idx = pair.indexOf('=') + if (idx < 0) return + const k = pair.slice(0, idx) + const v = pair.slice(idx + 1) + if (k) out[k] = v + }) + return out +} + +function pickHeaders(headers) { + const names = [ + 'User-Agent', + 'Accept', + 'Accept-Language', + 'Authorization', + 'Cookie', + 'X-Requested-With', + 'Content-Type', + 'Origin', + 'Referer' + ] + const out = {} + names.forEach(name => { + const v = getHeader(headers, name) + if (v !== undefined && v !== null && String(v) !== '') out[name] = String(v) + }) + + // 保留 PingMe/客户端自定义头,避免后续接口校验客户端参数。 + Object.keys(headers || {}).forEach(k => { + const lk = String(k).toLowerCase() + if ( + lk.startsWith('x-') || + lk.indexOf('device') >= 0 || + lk.indexOf('token') >= 0 || + lk.indexOf('client') >= 0 || + lk.indexOf('app') >= 0 + ) { + if (!/^content-length$/i.test(k) && !/^host$/i.test(k)) out[k] = String(headers[k]) + } + }) + return out +} + +function buildRecord(req) { + const url = String(req.url || '') + const paramsRaw = parseRawQuery(url) + const headers = pickHeaders(req.headers || {}) + return { url, paramsRaw, headers } +} + +function hasRequiredParams(record) { + const p = record && record.paramsRaw || {} + return Object.keys(p).length > 0 +} + +function formatRecord(record) { + return JSON.stringify(record) +} + +function main() { + const req = typeof $request !== 'undefined' ? $request : null + if (!req || !req.url) { + notify('抓取失败', '没有 request 上下文') + return done() + } + + const record = buildRecord(req) + if (!hasRequiredParams(record)) { + notify('抓取失败', '当前请求缺少 URL 查询参数,请打开 PingMe 触发余额/签到/视频接口') + return done() + } + + const text = formatRecord(record) + setPref('pingme_ck_last', text) + notify('CK 获取成功', text) + done() +} + +try { + main() +} catch (e) { + notify('脚本异常', String(e && e.message || e).slice(0, 220)) + done() +}