/* * @name 青龙 KYAPP 变量自动上传 * @version v1.0.0 * @description 用于自动提取 device-id 和 refresh_token 并上传到青龙面板 KYAPP 变量 * @author Antigravity * * [rewrite_local] * ^https:\/\/h5\.kaixinyf\.cn\/api\/window\/windows url script-request-header ky_qx_upload.js * * [mitm] * hostname = h5.kaixinyf.cn * * ⚠️ 使用说明: * 1. 请并在下方配置青龙面板信息 QL_URL, QL_CLIENT_ID, QL_CLIENT_SECRET * 2. 将脚本添加到 Rewrite * 3. 触发请求即可自动上传 */ const $ = new Env("KYAPP自动上传"); // ---------------------- 用户配置区域 ---------------------- // 请在下方填写您的青龙面板信息 const QL_URL = "http://chickliu.store:5732"; // 例如 http://192.168.1.1:5700 const QL_CLIENT_ID = "M2HkEY-8RRWq"; // 应用ID const QL_CLIENT_SECRET = "-LHulEYdGa2TR7_th6oX4Zxb"; // 应用密钥 // --------------------------------------------------------- const TARGET_URL_KEY = "/api/window/windows"; (async () => { try { if (typeof $request === "undefined") { $.msg($.name, "脚本运行环境错误", "请在 Quantumult X Rewrite 中使用 script-request-header"); return; } if ($request.url.indexOf(TARGET_URL_KEY) === -1) { return; } $.log(`[${$.name}] 开始捕获...`); // 1. 提取信息 const headers = $request.headers; const cookieStr = headers['Cookie'] || headers['cookie']; // 兼容不同写法 const deviceId = headers['device-id'] || headers['Device-Id']; if (!deviceId) { // $.msg($.name, "捕获失败", "未找到 device-id"); return; } if (!cookieStr) { // $.msg($.name, "捕获失败", "未找到 Cookie"); return; } const refreshToken = getCookieValue(cookieStr, "refresh_token"); if (!refreshToken) { // $.msg($.name, "捕获失败", "Cookie 中未找到 refresh_token"); return; } $.log(`获取到 DeviceID: ${deviceId}`); $.log(`获取到 RefreshToken: ${refreshToken.substring(0, 10)}...`); // 2. 登录青龙 const token = await qlLogin(); if (!token) return; // 3. 获取现有变量 const envs = await qlGetEnvs(token); let targetEnvId = null; let targetEnvValue = "[]"; // 默认为空数组字符串 const existingEnv = envs.find(e => e.name === "KYAPP"); if (existingEnv) { targetEnvId = existingEnv.id; targetEnvValue = existingEnv.value || "[]"; } // 4. 处理数据逻辑 let dataList = []; try { dataList = JSON.parse(targetEnvValue); if (!Array.isArray(dataList)) dataList = []; } catch (e) { $.log("原有变量格式不是JSON,重置为空数组"); dataList = []; } const newData = { "device_id": deviceId, "token_body": refreshToken }; const existingIndex = dataList.findIndex(item => item.device_id === deviceId); let successMsg = ""; if (existingIndex !== -1) { // 更新 $.log(`检测到设备 ${deviceId} 已存在,更新 token`); dataList[existingIndex] = newData; successMsg = `${deviceId} 更新成功`; } else { // 新增 $.log(`检测到新设备 ${deviceId},添加数据`); dataList.push(newData); successMsg = `${deviceId} 添加成功`; } const newEnvValue = JSON.stringify(dataList); // 5. 上传/更新变量 if (targetEnvId) { await qlUpdateEnv(token, targetEnvId, "KYAPP", newEnvValue, successMsg); } else { await qlAddEnv(token, [{ name: "KYAPP", value: newEnvValue }], successMsg); } } catch (e) { $.logErr(e); $.msg($.name, "运行时错误", e.message); } finally { $.done(); } })(); // ---------------------- 辅助函数 ---------------------- function getCookieValue(cookie, name) { const match = cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? match[2] : null; } // 青龙 API: 登录 async function qlLogin() { const options = { url: `${QL_URL}/open/auth/token?client_id=${QL_CLIENT_ID}&client_secret=${QL_CLIENT_SECRET}`, timeout: 10000 }; return new Promise((resolve) => { $.get(options, (err, resp, data) => { try { if (err) throw new Error(JSON.stringify(err)); const body = JSON.parse(data); if (body.code === 200) { resolve(body.data.token); } else { $.msg($.name, "青龙登录失败", body.message); resolve(null); } } catch (e) { $.msg($.name, "青龙登录异常", "请检查 URL 和 Client ID/Secret 设置"); $.logErr(e); resolve(null); } }); }); } // 青龙 API: 获取变量 async function qlGetEnvs(token) { const options = { url: `${QL_URL}/open/envs?searchValue=KYAPP`, headers: { "Authorization": `Bearer ${token}` }, timeout: 10000 }; return new Promise((resolve) => { $.get(options, (err, resp, data) => { try { if (err) throw new Error(JSON.stringify(err)); const body = JSON.parse(data); if (body.code === 200) { resolve(body.data); } else { resolve([]); } } catch (e) { $.logErr(e); resolve([]); } }); }); } // 青龙 API: 更新变量 async function qlUpdateEnv(token, id, name, value, msg) { const options = { url: `${QL_URL}/open/envs`, method: "PUT", headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, body: JSON.stringify({ id: id, name: name, value: value }), timeout: 10000 }; return new Promise((resolve) => { $.put(options, (err, resp, data) => { try { if (err) throw new Error(JSON.stringify(err)); const body = JSON.parse(data); if (body.code === 200) { $.msg($.name, "操作成功", msg || `设备 ${name} 数据已更新`); } else { $.msg($.name, "更新失败", body.message); } } catch (e) { $.logErr(e); $.msg($.name, "更新异常", e.message); } resolve(); }); }); } // 青龙 API: 新增变量 async function qlAddEnv(token, envsArray, msg) { const options = { url: `${QL_URL}/open/envs`, method: "POST", headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, body: JSON.stringify(envsArray), timeout: 10000 }; return new Promise((resolve) => { $.post(options, (err, resp, data) => { try { if (err) throw new Error(JSON.stringify(err)); const body = JSON.parse(data); if (body.code === 200) { $.msg($.name, "操作成功", msg || "KYAPP 变量已创建"); } else { $.msg($.name, "上传失败", body.message); } } catch (e) { $.logErr(e); $.msg($.name, "上传异常", e.message); } resolve(); }); }); } // ---------------------- Env Class ---------------------- function Env(name, env) { "undefined" != typeof process && JSON.stringify(process.env).indexOf("GITHUB") > -1 && process.exit(0); return new class { constructor(name, env) { this.name = name; this.notifyStr = ''; this.startTime = (new Date).getTime(); Object.assign(this, env); this.log("", `🔔${this.name}, 开始!`); } isNode() { return "undefined" != typeof module && !!module.exports } isQuanX() { return "undefined" != typeof $task } isSurge() { return "undefined" != typeof $httpClient && "undefined" == typeof $loon } isLoon() { return "undefined" != typeof $loon } get(options, callback) { if (this.isQuanX()) { if (typeof options == "string") options = { url: options } options["method"] = "GET" $task.fetch(options).then(response => { callback(null, response, response.body) }, reason => callback(reason.error, null, null)) } if (this.isSurge() || this.isLoon()) { $httpClient.get(options, (error, response, body) => { callback(error, response, body) }) } if (this.isNode()) { this.got = this.got ? this.got : require("got"); const { url, ...otherOptions } = options; this.got.get(url, otherOptions).then(response => { const { statusCode, headers, body } = response; callback(null, { status: statusCode, statusCode, headers, body }, body) }, error => { const { message, response } = error; callback(message, response, response && response.body) }) } } post(options, callback) { if (this.isQuanX()) { if (typeof options == "string") options = { url: options } options["method"] = "POST" $task.fetch(options).then(response => { callback(null, response, response.body) }, reason => callback(reason.error, null, null)) } if (this.isSurge() || this.isLoon()) { $httpClient.post(options, (error, response, body) => { callback(error, response, body) }) } if (this.isNode()) { this.got = this.got ? this.got : require("got"); const { url, ...otherOptions } = options; this.got.post(url, otherOptions).then(response => { const { statusCode, headers, body } = response; callback(null, { status: statusCode, statusCode, headers, body }, body) }, error => { const { message, response } = error; callback(message, response, response && response.body) }) } } put(options, callback) { // Added put method for standard HTTP PUT if (this.isQuanX()) { if (typeof options == "string") options = { url: options } options["method"] = "PUT" $task.fetch(options).then(response => { callback(null, response, response.body) }, reason => callback(reason.error, null, null)) } if (this.isSurge() || this.isLoon()) { options.method = 'PUT'; $httpClient.put(options, (error, response, body) => { // Surge/Loon might not have .put shorthand, using general request usually or strict method callback(error, response, body) }) } if (this.isNode()) { this.got = this.got ? this.got : require("got"); const { url, ...otherOptions } = options; this.got.put(url, otherOptions).then(response => { const { statusCode, headers, body } = response; callback(null, { status: statusCode, statusCode, headers, body }, body) }, error => { const { message, response } = error; callback(message, response, response && response.body) }) } } msg(title = name, subtitle = '', body = '') { const opts = (val) => { if (this.isLoon()) return { openUrl: val } if (this.isQuanX()) return { 'open-url': val } if (this.isSurge()) return { url: val } return {} } if (this.isSurge() || this.isLoon()) { $notification.post(title, subtitle, body) } if (this.isQuanX()) { $notify(title, subtitle, body) } this.log('==============📣系统通知📣==============') this.log(title) if (subtitle) this.log(subtitle) if (body) this.log(body) } log(...logs) { if (logs.length > 0) { this.logs = [...(this.logs || []), ...logs] } console.log(logs.join(this.logSeparator || "\n")) } logErr(err, msg) { const isSurge = this.isSurge() const isQuanX = this.isQuanX() const isLoon = this.isLoon() if (!isSurge && !isQuanX && !isLoon) { this.log('', `❗️${this.name}, 错误!`, err.stack) } else { this.log('', `❗️${this.name}, 错误!`, err) } } wait(time) { return new Promise(resolve => setTimeout(resolve, time)) } done(val = {}) { const endTime = (new Date).getTime() const costTime = (endTime - this.startTime) / 1000 this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`) this.log() if (this.isSurge() || this.isQuanX() || this.isLoon()) { $done(val) } } }(name, env) }