From 15c8c5f3540f86ecd39c535d64642f98e2bcc3dd Mon Sep 17 00:00:00 2001 From: chickliu Date: Fri, 26 Dec 2025 21:16:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20js/ky.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/ky.js | 410 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 410 insertions(+) create mode 100644 js/ky.js diff --git a/js/ky.js b/js/ky.js new file mode 100644 index 0000000..43e36fd --- /dev/null +++ b/js/ky.js @@ -0,0 +1,410 @@ +/* + * @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); + + if (existingIndex !== -1) { + // 更新 + $.log(`检测到设备 ${deviceId} 已存在,更新 token`); + dataList[existingIndex] = newData; + } else { + // 新增 + $.log(`检测到新设备 ${deviceId},添加数据`); + dataList.push(newData); + } + + const newEnvValue = JSON.stringify(dataList); + + // 5. 上传/更新变量 + if (targetEnvId) { + await qlUpdateEnv(token, targetEnvId, "KYAPP", newEnvValue); + } else { + await qlAddEnv(token, [{ name: "KYAPP", value: newEnvValue }]); + } + + } 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) { + 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, "更新成功", `设备 ${name} 数据已更新`); + } else { + $.msg($.name, "更新失败", body.message); + } + } catch (e) { + $.logErr(e); + $.msg($.name, "更新异常", e.message); + } + resolve(); + }); + }); +} + +// 青龙 API: 新增变量 +async function qlAddEnv(token, envsArray) { + 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, "上传成功", "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) +}