mirror of
https://github.com/fmz200/wool_scripts.git
synced 2025-12-20 13:43:58 +08:00
Initial commit: new history
This commit is contained in:
413
Scripts/qinglong/ql_sync.js
Normal file
413
Scripts/qinglong/ql_sync.js
Normal file
@ -0,0 +1,413 @@
|
||||
/**
|
||||
* 作者:fmz200,修改自dompling的ql_cookie_sync.js
|
||||
* 作用:定时同步BoxJS中的数据到青龙环境变量,每日自动同步
|
||||
* 配置:40 0 * * * https://raw.githubusercontent.com/fmz200/wool_scripts/main/Scripts/qinglong/ql_sync.js
|
||||
* 定时:QX导入订阅 https://raw.githubusercontent.com/fmz200/wool_scripts/main/boxjs/fmz200_gallery.json
|
||||
* 使用:详见BoxJS页面 https://raw.githubusercontent.com/fmz200/wool_scripts/main/boxjs/fmz200.boxjs.json
|
||||
* 更新:2023-06-04 13:30
|
||||
*/
|
||||
|
||||
const $ = new API('ql', true);
|
||||
|
||||
const title = '🐉 同步通知';
|
||||
|
||||
const sync_keys = $.read('#ql_sync_keys').replace(/\s/g, '').split(',') || [];
|
||||
if (sync_keys.length === 0) {
|
||||
$.notify(title, '', `未填写需要同步的keys,请在BoxJS填写正确`);
|
||||
$.done();
|
||||
}
|
||||
|
||||
let remark = {};
|
||||
|
||||
!(async () => {
|
||||
// 只登陆一次
|
||||
const ql_script = (await getScriptUrl()) || '';
|
||||
eval(ql_script);
|
||||
await $.ql.login();
|
||||
// 开始同步数据
|
||||
for await (const key of sync_keys) {
|
||||
await autoSync(key);
|
||||
}
|
||||
const keyText = sync_keys.map((item) => item).join(`\n`);
|
||||
if ($.read('ql_sync_notify') !== 'true') {
|
||||
$.notify(title, '', `已同步以下keys的数据:\n${keyText}`);
|
||||
}
|
||||
$.done();
|
||||
})();
|
||||
|
||||
async function autoSync(key_remark) {
|
||||
$.log(`--------------------`);
|
||||
try {
|
||||
// key可能包含两部分:key@remark
|
||||
let key;
|
||||
let remark;
|
||||
if (key_remark.includes('@')) {
|
||||
[key, remark] = key_remark.split('@');
|
||||
} else {
|
||||
key = key_remark;
|
||||
remark = 'BoxJS同步的数据'; // 如果没有备注,可以设置为 null 或其他默认值
|
||||
}
|
||||
const values = await $.ql.select(key); // 同一个key可能有多个值,暂时只做一个的同步
|
||||
await $.ql.delete(values.data.map((item) => item.id));
|
||||
$.log(`已清空${key}的数据`);
|
||||
|
||||
const addData = [];
|
||||
const key_value = $.read(`#${key}`);
|
||||
$.log(`已读取${key}的数据`);
|
||||
addData.push({name: key, value: key_value, remarks: remark});
|
||||
if (addData.length) await $.ql.add(addData);
|
||||
$.log(`已同步${key}的数据`);
|
||||
} catch (e) {
|
||||
$.log(`同步${key_remark}的数据时发生错误:` + JSON.stringify(e));
|
||||
}
|
||||
$.log(`--------------------`);
|
||||
}
|
||||
|
||||
async function getScriptUrl() {
|
||||
const response = await $.http.get({
|
||||
url: 'https://raw.githubusercontent.com/fmz200/wool_scripts/main/Scripts/qinglong/ql_api.js',
|
||||
});
|
||||
return response.body;
|
||||
}
|
||||
|
||||
function getURL(api, key = 'api') {
|
||||
return `${baseURL}/${key}/${api}`;
|
||||
}
|
||||
|
||||
function login() {
|
||||
const opt = {
|
||||
headers,
|
||||
url: getURL('login'),
|
||||
body: JSON.stringify(account),
|
||||
};
|
||||
return $.http.post(opt).then((response) => JSON.parse(response.body));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
function ENV() {
|
||||
const isQX = typeof $task !== 'undefined';
|
||||
const isLoon = typeof $loon !== 'undefined';
|
||||
const isSurge = typeof $httpClient !== 'undefined' && !isLoon;
|
||||
const isJSBox = typeof require == 'function' && typeof $jsbox != 'undefined';
|
||||
const isNode = typeof require == 'function' && !isJSBox;
|
||||
const isRequest = typeof $request !== 'undefined';
|
||||
const isScriptable = typeof importModule !== 'undefined';
|
||||
return {isQX, isLoon, isSurge, isNode, isJSBox, isRequest, isScriptable};
|
||||
}
|
||||
|
||||
function HTTP(defaultOptions = {baseURL: ''}) {
|
||||
const {isQX, isLoon, isSurge, isScriptable, isNode} = ENV();
|
||||
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH'];
|
||||
const URL_REGEX =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
|
||||
|
||||
function send(method, options) {
|
||||
options = typeof options === 'string' ? {url: options} : options;
|
||||
const baseURL = defaultOptions.baseURL;
|
||||
if (baseURL && !URL_REGEX.test(options.url || '')) {
|
||||
options.url = baseURL ? baseURL + options.url : options.url;
|
||||
}
|
||||
options = {...defaultOptions, ...options};
|
||||
const timeout = options.timeout;
|
||||
const events = {
|
||||
...{
|
||||
onRequest: () => {
|
||||
},
|
||||
onResponse: (resp) => resp,
|
||||
onTimeout: () => {
|
||||
},
|
||||
},
|
||||
...options.events,
|
||||
};
|
||||
|
||||
events.onRequest(method, options);
|
||||
|
||||
let worker;
|
||||
if (isQX) {
|
||||
worker = $task.fetch({method, ...options});
|
||||
} else if (isLoon || isSurge || isNode) {
|
||||
worker = new Promise((resolve, reject) => {
|
||||
const request = isNode ? require('request') : $httpClient;
|
||||
request[method.toLowerCase()](options, (err, response, body) => {
|
||||
if (err) reject(err);
|
||||
else
|
||||
resolve({
|
||||
statusCode: response.status || response.statusCode,
|
||||
headers: response.headers,
|
||||
body,
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if (isScriptable) {
|
||||
const request = new Request(options.url);
|
||||
request.method = method;
|
||||
request.headers = options.headers;
|
||||
request.body = options.body;
|
||||
worker = new Promise((resolve, reject) => {
|
||||
request
|
||||
.loadString()
|
||||
.then((body) => {
|
||||
resolve({
|
||||
statusCode: request.response.statusCode,
|
||||
headers: request.response.headers,
|
||||
body,
|
||||
});
|
||||
})
|
||||
.catch((err) => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
let timeoutid;
|
||||
const timer = timeout
|
||||
? new Promise((_, reject) => {
|
||||
timeoutid = setTimeout(() => {
|
||||
events.onTimeout();
|
||||
return reject(
|
||||
`${method} URL: ${options.url} exceeds the timeout ${timeout} ms`
|
||||
);
|
||||
}, timeout);
|
||||
})
|
||||
: null;
|
||||
|
||||
return (
|
||||
timer
|
||||
? Promise.race([timer, worker]).then((res) => {
|
||||
clearTimeout(timeoutid);
|
||||
return res;
|
||||
})
|
||||
: worker
|
||||
).then((resp) => events.onResponse(resp));
|
||||
}
|
||||
|
||||
const http = {};
|
||||
methods.forEach(
|
||||
(method) =>
|
||||
(http[method.toLowerCase()] = (options) => send(method, options))
|
||||
);
|
||||
return http;
|
||||
}
|
||||
|
||||
function API(name = 'untitled', debug = false) {
|
||||
const {isQX, isLoon, isSurge, isNode, isJSBox, isScriptable} = ENV();
|
||||
return new (class {
|
||||
constructor(name, debug) {
|
||||
this.name = name;
|
||||
this.debug = debug;
|
||||
|
||||
this.http = HTTP();
|
||||
this.env = ENV();
|
||||
|
||||
this.node = (() => {
|
||||
if (isNode) {
|
||||
const fs = require('fs');
|
||||
|
||||
return {
|
||||
fs,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
this.initCache();
|
||||
|
||||
const delay = (t, v) =>
|
||||
new Promise(function (resolve) {
|
||||
setTimeout(resolve.bind(null, v), t);
|
||||
});
|
||||
|
||||
Promise.prototype.delay = function (t) {
|
||||
return this.then(function (v) {
|
||||
return delay(t, v);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// persistance
|
||||
|
||||
// initialize cache
|
||||
initCache() {
|
||||
if (isQX) this.cache = JSON.parse($prefs.valueForKey(this.name) || '{}');
|
||||
if (isLoon || isSurge)
|
||||
this.cache = JSON.parse($persistentStore.read(this.name) || '{}');
|
||||
|
||||
if (isNode) {
|
||||
// create a json for root cache
|
||||
let fpath = 'root.json';
|
||||
if (!this.node.fs.existsSync(fpath)) {
|
||||
this.node.fs.writeFileSync(
|
||||
fpath,
|
||||
JSON.stringify({}),
|
||||
{flag: 'wx'},
|
||||
(err) => console.log(err)
|
||||
);
|
||||
}
|
||||
this.root = {};
|
||||
|
||||
// create a json file with the given name if not exists
|
||||
fpath = `${this.name}.json`;
|
||||
if (!this.node.fs.existsSync(fpath)) {
|
||||
this.node.fs.writeFileSync(
|
||||
fpath,
|
||||
JSON.stringify({}),
|
||||
{flag: 'wx'},
|
||||
(err) => console.log(err)
|
||||
);
|
||||
this.cache = {};
|
||||
} else {
|
||||
this.cache = JSON.parse(
|
||||
this.node.fs.readFileSync(`${this.name}.json`)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store cache
|
||||
persistCache() {
|
||||
const data = JSON.stringify(this.cache);
|
||||
if (isQX) $prefs.setValueForKey(data, this.name);
|
||||
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
||||
if (isNode) {
|
||||
this.node.fs.writeFileSync(
|
||||
`${this.name}.json`,
|
||||
data,
|
||||
{flag: 'w'},
|
||||
(err) => console.log(err)
|
||||
);
|
||||
this.node.fs.writeFileSync(
|
||||
'root.json',
|
||||
JSON.stringify(this.root),
|
||||
{flag: 'w'},
|
||||
(err) => console.log(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
write(data, key) {
|
||||
this.log(`SET ${key}`);
|
||||
if (key.indexOf('#') !== -1) {
|
||||
key = key.substr(1);
|
||||
if (isSurge || isLoon) {
|
||||
return $persistentStore.write(data, key);
|
||||
}
|
||||
if (isQX) {
|
||||
return $prefs.setValueForKey(data, key);
|
||||
}
|
||||
if (isNode) {
|
||||
this.root[key] = data;
|
||||
}
|
||||
} else {
|
||||
this.cache[key] = data;
|
||||
}
|
||||
this.persistCache();
|
||||
}
|
||||
|
||||
read(key) {
|
||||
this.log(`READ ${key}`);
|
||||
if (key.indexOf('#') !== -1) {
|
||||
key = key.substr(1);
|
||||
if (isSurge || isLoon) {
|
||||
return $persistentStore.read(key);
|
||||
}
|
||||
if (isQX) {
|
||||
return $prefs.valueForKey(key);
|
||||
}
|
||||
if (isNode) {
|
||||
return this.root[key];
|
||||
}
|
||||
} else {
|
||||
return this.cache[key];
|
||||
}
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
this.log(`DELETE ${key}`);
|
||||
if (key.indexOf('#') !== -1) {
|
||||
key = key.substr(1);
|
||||
if (isSurge || isLoon) {
|
||||
return $persistentStore.write(null, key);
|
||||
}
|
||||
if (isQX) {
|
||||
return $prefs.removeValueForKey(key);
|
||||
}
|
||||
if (isNode) {
|
||||
delete this.root[key];
|
||||
}
|
||||
} else {
|
||||
delete this.cache[key];
|
||||
}
|
||||
this.persistCache();
|
||||
}
|
||||
|
||||
// notification
|
||||
notify(title, subtitle = '', content = '', options = {}) {
|
||||
const openURL = options['open-url'];
|
||||
const mediaURL = options['media-url'];
|
||||
|
||||
if (isQX) $notify(title, subtitle, content, options);
|
||||
if (isSurge) {
|
||||
$notification.post(
|
||||
title,
|
||||
subtitle,
|
||||
content + `${mediaURL ? '\n多媒体:' + mediaURL : ''}`,
|
||||
{
|
||||
url: openURL,
|
||||
}
|
||||
);
|
||||
}
|
||||
if (isLoon) {
|
||||
let opts = {};
|
||||
if (openURL) opts['openUrl'] = openURL;
|
||||
if (mediaURL) opts['mediaUrl'] = mediaURL;
|
||||
if (JSON.stringify(opts) == '{}') {
|
||||
$notification.post(title, subtitle, content);
|
||||
} else {
|
||||
$notification.post(title, subtitle, content, opts);
|
||||
}
|
||||
}
|
||||
if (isNode || isScriptable) {
|
||||
const content_ =
|
||||
content +
|
||||
(openURL ? `\n点击跳转: ${openURL}` : '') +
|
||||
(mediaURL ? `\n多媒体: ${mediaURL}` : '');
|
||||
if (isJSBox) {
|
||||
const push = require('push');
|
||||
push.schedule({
|
||||
title: title,
|
||||
body: (subtitle ? subtitle + '\n' : '') + content_,
|
||||
});
|
||||
} else {
|
||||
console.log(`${title}\n${subtitle}\n${content_}\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// other helper functions
|
||||
log(msg) {
|
||||
if (this.debug) console.log(msg);
|
||||
}
|
||||
|
||||
info(msg) {
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
error(msg) {
|
||||
console.log('ERROR: ' + msg);
|
||||
}
|
||||
|
||||
wait(millisec) {
|
||||
return new Promise((resolve) => setTimeout(resolve, millisec));
|
||||
}
|
||||
|
||||
done(value = {}) {
|
||||
if (isQX || isLoon || isSurge) {
|
||||
$done(value);
|
||||
} else if (isNode && !isJSBox) {
|
||||
if (typeof $context !== 'undefined') {
|
||||
$context.headers = value.headers;
|
||||
$context.statusCode = value.statusCode;
|
||||
$context.body = value.body;
|
||||
}
|
||||
}
|
||||
}
|
||||
})(name, debug);
|
||||
}
|
||||
Reference in New Issue
Block a user