QX/js/ky.js
2025-12-26 21:56:52 +08:00

414 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @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)
}