添加 js/ky.js
This commit is contained in:
parent
08c38b61fa
commit
15c8c5f354
410
js/ky.js
Normal file
410
js/ky.js
Normal file
@ -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)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user