ks-q/js/ksjsb_ck_x.js
2025-11-11 12:41:50 +08:00

704 lines
19 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: 快手 Salt 获取
* @description: 抓取快手登录响应以获取 Salt
* @author:
* @version: 1.0.1
*
* 脚本使用方法:
* 1. 将此脚本保存到 Quantumult X 的脚本目录中 (例如 iCloud/QuantumultX/Scripts/)。
* 2. 在 Quantumult X 配置文件 ([rewrite_local] 部分) 中添加以下行:
* https:\/\/txjp\.gifshow\.com\/rest\/nebula\/user\/login\/mobileVerifyCode url script-response-body https://raw.githubusercontent.com/your/repo/ks_salt_notify.js
* (请将上面的 URL 替换为您自己的脚本路径)
* 3. 在 Quantumult X 配置文件 ([mitm] 部分) 中添加主机名:
* hostname = txjp.gifshow.com
* 4. 打开快手 App触发登录或刷新登录状态 (访问上述 URL)。
* 5. 成功后,脚本会自动弹出通知显示获取到的 "kuaishou.api_client_salt" 值。
*/
// ###############################
// ###### 脚本主逻辑 ######
// ###############################
const $ = new Env();
const targetUrl = 'https://txjp.gifshow.com/rest/nebula/user/login';
(async () => {
// 仅当 URL 匹配时执行
if ($request.url.includes(targetUrl)) {
$.log(' 捕获到目标URL: ' + $request.url);
try {
// 1. 解析响应体
const body = JSON.parse($response.body);
// 2. 从响应体中获取 kuaishou.api_client_salt (根据你的要求)
// 使用方括号语法来访问带点的key
const salt = body?.['kuaishou.api_client_salt'];
if (salt) {
// 3. 获取成功,发出通知
$.log(`✅ 成功获取 kuaishou.api_client_salt: ${salt}`);
// Quantumult X 的通知点击后body 内容会自动复制到剪贴板
$.notify("快手Salt获取成功", "点击通知可复制", salt);
} else {
// 4. 获取失败,发出通知
$.log('❌ 未在响应体中找到 kuaishou.api_client_salt');
$.notify("快手Salt获取失败", "响应体中未找到", "请检查响应数据或脚本。");
}
} catch (e) {
$.logErr(e);
$.notify("快手Salt脚本出错", "解析响应体失败", e.message);
}
} else {
$.log('⚠️ 当前URL不匹配脚本不执行。');
}
})().catch((e) => {
$.logErr(e);
$.notify("快手Salt脚本异常", "发生未知错误", e.message);
}).finally(() => {
$.done();
});
// 兼容多环境的 Env 类
function Env() {
const isQuanX = typeof $task !== 'undefined';
const isSurge = typeof $httpClient !== 'undefined' && !isQuanX;
const isLoon = typeof $loon !== 'undefined';
const isNode = typeof require === 'function';
const http = {
get: (options) => {
return new Promise((resolve, reject) => {
if (isQuanX) $task.fetch(options).then(resp => resolve(resp), err => reject(err));
else if (isSurge || isLoon) $httpClient.get(options, (err, resp, body) => err ? reject(err) : resolve({ body, status: resp.statusCode, headers: resp.headers }));
else if (isNode) {
const request = require('request');
request(options, (err, resp, body) => err ? reject(err) : resolve({ body, status: resp.statusCode, headers: resp.headers }));
}
});
},
post: (options) => {
return new Promise((resolve, reject) => {
if (isQuanX) $task.fetch(options).then(resp => resolve(resp), err => reject(err));
else if (isSurge || isLoon) $httpClient.post(options, (err, resp, body) => err ? reject(err) : resolve({ body, status: resp.statusCode, headers: resp.headers }));
else if (isNode) {
const request = require('request');
request.post(options, (err, resp, body) => err ? reject(err) : resolve({ body, status: resp.statusCode, headers: resp.headers }));
}
});
},
put: (options) => {
return new Promise((resolve, reject) => {
if (isQuanX) $task.fetch({ ...options, method: 'PUT' }).then(resp => resolve(resp), err => reject(err));
// ... 其他环境的实现
});
}
};
const notify = (title, subtitle = '', body = '') => {
if (isQuanX) $notify(title, subtitle, body);
else console.log(`${title}\n${subtitle}\n${body}`);
};
const log = (msg) => console.log(msg);
const logErr = (e) => console.log(e);
const done = (value = {}) => isQuanX ? $done(value) : null;
return { http, notify, log, logErr, done };
}
//From chavyleung's Env.js
function Env(name, opts) {
class Http {
constructor(env) {
this.env = env;
}
send(opts, method = "GET") {
opts = typeof opts === "string" ? {
url: opts
} : opts;
let sender = this.get;
if (method === "POST") {
sender = this.post;
}
return new Promise((resolve, reject) => {
sender.call(this, opts, (err, resp, body) => {
if (err) reject(err);
else resolve(resp);
});
});
}
get(opts) {
return this.send.call(this.env, opts);
}
post(opts) {
return this.send.call(this.env, opts, "POST");
}
}
return new(class {
constructor(name, opts) {
this.name = name;
this.http = new Http(this);
this.data = null;
this.dataFile = "box.dat";
this.logs = [];
this.isMute = false;
this.isNeedRewrite = false;
this.logSeparator = "\n";
this.startTime = new Date().getTime();
Object.assign(this, opts);
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;
}
toObj(str, defaultValue = null) {
try {
return JSON.parse(str);
} catch {
return defaultValue;
}
}
toStr(obj, defaultValue = null) {
try {
return JSON.stringify(obj);
} catch {
return defaultValue;
}
}
getjson(key, defaultValue) {
let json = defaultValue;
const val = this.getdata(key);
if (val) {
try {
json = JSON.parse(this.getdata(key));
} catch {}
}
return json;
}
setjson(val, key) {
try {
return this.setdata(JSON.stringify(val), key);
} catch {
return false;
}
}
getScript(url) {
return new Promise((resolve) => {
this.get({
url
}, (err, resp, body) => resolve(body));
});
}
runScript(script, runOpts) {
return new Promise((resolve) => {
let httpapi = this.getdata("@chavy_boxjs_userCfgs.httpapi");
httpapi = httpapi ? httpapi.replace(/\n/g, "").trim() : httpapi;
let httpapi_timeout = this.getdata(
"@chavy_boxjs_userCfgs.httpapi_timeout"
);
httpapi_timeout = httpapi_timeout ? httpapi_timeout * 1 : 20;
httpapi_timeout =
runOpts && runOpts.timeout ? runOpts.timeout : httpapi_timeout;
const [key, addr] = httpapi.split("@");
const opts = {
url: `http://${addr}/v1/scripting/evaluate`,
body: {
script_text: script,
mock_type: "cron",
timeout: httpapi_timeout,
},
headers: {
"X-Key": key,
Accept: "*/*"
},
};
this.post(opts, (err, resp, body) => resolve(body));
}).catch((e) => this.logErr(e));
}
loaddata() {
if (this.isNode()) {
this.fs = this.fs ? this.fs : require("fs");
this.path = this.path ? this.path : require("path");
const curDirDataFilePath = this.path.resolve(this.dataFile);
const rootDirDataFilePath = this.path.resolve(
process.cwd(),
this.dataFile
);
const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath);
const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath);
if (isCurDirDataFile || isRootDirDataFile) {
const datPath = isCurDirDataFile ?
curDirDataFilePath :
rootDirDataFilePath;
try {
return JSON.parse(this.fs.readFileSync(datPath));
} catch (e) {
return {};
}
} else return {};
} else return {};
}
writedata() {
if (this.isNode()) {
this.fs = this.fs ? this.fs : require("fs");
this.path = this.path ? this.path : require("path");
const curDirDataFilePath = this.path.resolve(this.dataFile);
const rootDirDataFilePath = this.path.resolve(
process.cwd(),
this.dataFile
);
const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath);
const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath);
const jsondata = JSON.stringify(this.data);
if (isCurDirDataFile) {
this.fs.writeFileSync(curDirDataFilePath, jsondata);
} else if (isRootDirDataFile) {
this.fs.writeFileSync(rootDirDataFilePath, jsondata);
} else {
this.fs.writeFileSync(curDirDataFilePath, jsondata);
}
}
}
lodash_get(source, path, defaultValue = undefined) {
const paths = path.replace(/\[(\d+)\]/g, ".$1").split(".");
let result = source;
for (const p of paths) {
result = Object(result)[p];
if (result === undefined) {
return defaultValue;
}
}
return result;
}
lodash_set(obj, path, value) {
if (Object(obj) !== obj) return obj;
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
path
.slice(0, -1)
.reduce(
(a, c, i) =>
Object(a[c]) === a[c] ?
a[c] :
(a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}),
obj
)[path[path.length - 1]] = value;
return obj;
}
getdata(key) {
let val = this.getval(key);
// 如果以 @
if (/^@/.test(key)) {
const [, objkey, paths] = /^@(.*?)\.(.*?)$/.exec(key);
const objval = objkey ? this.getval(objkey) : "";
if (objval) {
try {
const objedval = JSON.parse(objval);
val = objedval ? this.lodash_get(objedval, paths, "") : val;
} catch (e) {
val = "";
}
}
}
return val;
}
setdata(val, key) {
let issuc = false;
if (/^@/.test(key)) {
const [, objkey, paths] = /^@(.*?)\.(.*?)$/.exec(key);
const objdat = this.getval(objkey);
const objval = objkey ?
objdat === "null" ?
null :
objdat || "{}" :
"{}";
try {
const objedval = JSON.parse(objval);
this.lodash_set(objedval, paths, val);
issuc = this.setval(JSON.stringify(objedval), objkey);
} catch (e) {
const objedval = {};
this.lodash_set(objedval, paths, val);
issuc = this.setval(JSON.stringify(objedval), objkey);
}
} else {
issuc = this.setval(val, key);
}
return issuc;
}
getval(key) {
if (this.isSurge() || this.isLoon()) {
return $persistentStore.read(key);
} else if (this.isQuanX()) {
return $prefs.valueForKey(key);
} else if (this.isNode()) {
this.data = this.loaddata();
return this.data[key];
} else {
return (this.data && this.data[key]) || null;
}
}
setval(val, key) {
if (this.isSurge() || this.isLoon()) {
return $persistentStore.write(val, key);
} else if (this.isQuanX()) {
return $prefs.setValueForKey(val, key);
} else if (this.isNode()) {
this.data = this.loaddata();
this.data[key] = val;
this.writedata();
return true;
} else {
return (this.data && this.data[key]) || null;
}
}
initGotEnv(opts) {
this.got = this.got ? this.got : require("got");
this.cktough = this.cktough ? this.cktough : require("tough-cookie");
this.ckjar = this.ckjar ? this.ckjar : new this.cktough.CookieJar();
if (opts) {
opts.headers = opts.headers ? opts.headers : {};
if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) {
opts.cookieJar = this.ckjar;
}
}
}
get(opts, callback = () => {}) {
if (opts.headers) {
delete opts.headers["Content-Type"];
delete opts.headers["Content-Length"];
}
if (this.isSurge() || this.isLoon()) {
if (this.isSurge() && this.isNeedRewrite) {
opts.headers = opts.headers || {};
Object.assign(opts.headers, {
"X-Surge-Skip-Scripting": false
});
}
$httpClient.get(opts, (err, resp, body) => {
if (!err && resp) {
resp.body = body;
resp.statusCode = resp.status;
}
callback(err, resp, body);
});
} else if (this.isQuanX()) {
if (this.isNeedRewrite) {
opts.opts = opts.opts || {};
Object.assign(opts.opts, {
hints: false
});
}
$task.fetch(opts).then(
(resp) => {
const {
statusCode: status,
statusCode,
headers,
body
} = resp;
callback(null, {
status,
statusCode,
headers,
body
}, body);
},
(err) => callback(err)
);
} else if (this.isNode()) {
this.initGotEnv(opts);
this.got(opts)
.on("redirect", (resp, nextOpts) => {
try {
if (resp.headers["set-cookie"]) {
const ck = resp.headers["set-cookie"]
.map(this.cktough.Cookie.parse)
.toString();
if (ck) {
this.ckjar.setCookieSync(ck, null);
}
nextOpts.cookieJar = this.ckjar;
}
} catch (e) {
this.logErr(e);
}
// this.ckjar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString())
})
.then(
(resp) => {
const {
statusCode: status,
statusCode,
headers,
body
} = resp;
callback(null, {
status,
statusCode,
headers,
body
}, body);
},
(err) => {
const {
message: error,
response: resp
} = err;
callback(error, resp, resp && resp.body);
}
);
}
}
post(opts, callback = () => {}) {
// 如果指定了请求体, 但没指定`Content-Type`, 则自动生成
if (opts.body && opts.headers && !opts.headers["Content-Type"]) {
opts.headers["Content-Type"] = "application/x-www-form-urlencoded";
}
if (opts.headers) delete opts.headers["Content-Length"];
if (this.isSurge() || this.isLoon()) {
if (this.isSurge() && this.isNeedRewrite) {
opts.headers = opts.headers || {};
Object.assign(opts.headers, {
"X-Surge-Skip-Scripting": false
});
}
$httpClient.post(opts, (err, resp, body) => {
if (!err && resp) {
resp.body = body;
resp.statusCode = resp.status;
}
callback(err, resp, body);
});
} else if (this.isQuanX()) {
opts.method = "POST";
if (this.isNeedRewrite) {
opts.opts = opts.opts || {};
Object.assign(opts.opts, {
hints: false
});
}
$task.fetch(opts).then(
(resp) => {
const {
statusCode: status,
statusCode,
headers,
body
} = resp;
callback(null, {
status,
statusCode,
headers,
body
}, body);
},
(err) => callback(err)
);
} else if (this.isNode()) {
this.initGotEnv(opts);
const {
url,
..._opts
} = opts;
this.got.post(url, _opts).then(
(resp) => {
const {
statusCode: status,
statusCode,
headers,
body
} = resp;
callback(null, {
status,
statusCode,
headers,
body
}, body);
},
(err) => {
const {
message: error,
response: resp
} = err;
callback(error, resp, resp && resp.body);
}
);
}
}
/**
*
* 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S')
* :$.time('yyyyMMddHHmmssS')
* y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒
* 其中y可选0-4位占位符、S可选0-1位占位符其余可选0-2位占位符
* @param {string} fmt 格式化参数
* @param {number} 可选: 根据指定时间戳返回格式化日期
*
*/
time(fmt, ts = null) {
const date = ts ? new Date(ts) : new Date();
let o = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"H+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
"q+": Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds(),
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(
RegExp.$1,
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
);
for (let k in o)
if (new RegExp("(" + k + ")").test(fmt))
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length == 1 ?
o[k] :
("00" + o[k]).substr(("" + o[k]).length)
);
return fmt;
}
/**
* 系统通知
*
* > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知
*
* 示例:
* $.msg(title, subt, desc, 'twitter://')
* $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' })
* $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' })
*
* @param {*} title 标题
* @param {*} subt 副标题
* @param {*} desc 通知详情
* @param {*} opts 通知参数
*
*/
msg(title = name, subt = "", desc = "", opts) {
const toEnvOpts = (rawopts) => {
if (!rawopts) return rawopts;
if (typeof rawopts === "string") {
if (this.isLoon()) return rawopts;
else if (this.isQuanX()) return {
"open-url": rawopts
};
else if (this.isSurge()) return {
url: rawopts
};
else return undefined;
} else if (typeof rawopts === "object") {
if (this.isLoon()) {
let openUrl = rawopts.openUrl || rawopts.url || rawopts["open-url"];
let mediaUrl = rawopts.mediaUrl || rawopts["media-url"];
return {
openUrl,
mediaUrl
};
} else if (this.isQuanX()) {
let openUrl = rawopts["open-url"] || rawopts.url || rawopts.openUrl;
let mediaUrl = rawopts["media-url"] || rawopts.mediaUrl;
return {
"open-url": openUrl,
"media-url": mediaUrl
};
} else if (this.isSurge()) {
let openUrl = rawopts.url || rawopts.openUrl || rawopts["open-url"];
return {
url: openUrl
};
}
} else {
return undefined;
}
};
if (!this.isMute) {
if (this.isSurge() || this.isLoon()) {
$notification.post(title, subt, desc, toEnvOpts(opts));
} else if (this.isQuanX()) {
$notify(title, subt, desc, toEnvOpts(opts));
}
}
if (!this.isMuteLog) {
let logs = ["", "==============📣系统通知📣=============="];
logs.push(title);
subt ? logs.push(subt) : "";
desc ? logs.push(desc) : "";
console.log(logs.join("\n"));
this.logs = this.logs.concat(logs);
}
}
log(...logs) {
if (logs.length > 0) {
this.logs = [...this.logs, ...logs];
}
console.log(logs.join(this.logSeparator));
}
logErr(err, msg) {
const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon();
if (!isPrintSack) {
this.log("", `❗️${this.name}, 错误!`, err);
} else {
this.log("", `❗️${this.name}, 错误!`, err.stack);
}
}
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, opts);
}