ios_rule_script/script/magicjs/magic.js
2021-04-17 01:38:57 +08:00

747 lines
26 KiB
JavaScript
Raw 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.

const SET_VALUE_REGEX = /http:\/\/(www\.)?magic\.js\/value\/write/
const GET_VALUE_REGEX = /http:\/\/(www\.)?magic\.js\/value\/read/
const DEL_VALUE_REGEX = /http:\/\/(www\.)?magic\.js\/value\/del/
const SCRIPT_NAME = "MagicJS";
let body = {}
let magicJS = MagicJS(SCRIPT_NAME, "INFO");
async function Main(){
if (magicJS.isRequest){
if (SET_VALUE_REGEX.test(magicJS.request.url)){
try{
let key = magicJS.request.url.match(/key=([^&]*)/)[1]
let val = magicJS.request.url.match(/val=([^&]*)/)[1]
let session = magicJS.request.url.match(/session=([^&]*)/)
session = !!session? session[1] : '';
magicJS.write(key, val, session);
if (magicJS.read(key, session) == val){
magicJS.notify('变量写入成功');
body = {'success': true, 'msg': '变量写入成功', 'key': key, 'val': val, 'session': session}
}
else{
magicJS.notify('变量写入失败');
body = {'success': false, 'msg': '变量写入失败', 'key': key, 'val': magicJS.read(key, session), 'session': session}
}
}
catch (err){
magicJS.notify('变量写入失败');
body = {'success': false, 'msg': '变量写入失败'};
}
}
else if (GET_VALUE_REGEX.test(magicJS.request.url)){
try{
let key = magicJS.request.url.match(/key=([^&]*)/)[1]
let session = magicJS.request.url.match(/session=([^&]*)/)
session = !!session? session[1] : '';
val = magicJS.read(key, session);
magicJS.notify('读取变量成功');
body = {'success': true, 'msg': '读取变量成功', 'key': key, 'val': val, 'session': session}
}
catch (err){
magicJS.notify('读取变量失败');
body = {'success': false, 'msg': '读取变量失败'};
}
}
else if (DEL_VALUE_REGEX.test(magicJS.request.url)){
try{
let key = magicJS.request.url.match(/key=([^&]*)/)[1]
let session = magicJS.request.url.match(/session=([^&]*)/)
session = !!session? session[1] : '';
val = magicJS.del(key, session);
if (!!magicJS.read(key, session)){
magicJS.notify('删除变量失败');
body = {'success': true, 'msg': '删除变量失败', 'key': key, 'session': session}
}
else{
magicJS.notify('删除变量成功');
body = {'success': true, 'msg': '删除变量成功', 'key': key, 'session': session}
}
}
catch (err){
magicJS.notify('删除变量失败');
body = {'success': false, 'msg': '删除变量失败'};
}
}
else{
magicJS.notify('请求格式错误');
body = {'success': false, 'msg': '请求格式错误'};
}
body = JSON.stringify(body);
let resp = {}
if (magicJS.isSurge || magicJS.isLoon){
resp = {
response: {
status: 200,
body: body,
headers: {
'Content-type': 'application/json;charset=utf-8'
}
}
}
}
if (magicJS.isQuanX){
resp = {
body: body,
headers: {
'Content-type': 'application/json;charset=utf-8'
},
status: "HTTP/1.1 200 OK"
}
}
magicJS.done(resp);
}
}
Main();
function MagicJS(scriptName='MagicJS', logLevel='INFO'){
return new class{
constructor(){
this.version = '2.2.3.1'
this.scriptName = scriptName;
this.logLevels = {DEBUG: 5, INFO: 4, NOTIFY: 3, WARNING: 2, ERROR: 1, CRITICAL: 0, NONE: -1};
this.isLoon = typeof $loon !== 'undefined';
this.isQuanX = typeof $task !== 'undefined';
this.isJSBox = typeof $drive !== 'undefined';
this.isNode = typeof module !== 'undefined' && !this.isJSBox;
this.isSurge = typeof $httpClient !== 'undefined' && !this.isLoon;
this.node = {'request': undefined, 'fs': undefined, 'data': {}};
this.iOSUserAgent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1';
this.pcUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 Edg/84.0.522.59';
this.logLevel = logLevel;
this._barkUrl = '';
if (this.isNode){
this.node.fs = require('fs');
this.node.request = require('request');
try{
this.node.fs.accessSync('./magic.json', this.node.fs.constants.R_OK | this.node.fs.constants.W_OK);
}
catch(err){
this.node.fs.writeFileSync('./magic.json', '{}', {encoding: 'utf8'})
}
this.node.data = require('./magic.json');
}
else if (this.isJSBox){
if (!$file.exists('drive://MagicJS')){
$file.mkdir('drive://MagicJS');
}
if (!$file.exists('drive://MagicJS/magic.json')){
$file.write({
data: $data({string: '{}'}),
path: 'drive://MagicJS/magic.json'
})
}
}
}
set barkUrl(url){this._barkUrl = url.replace(/\/+$/g, '');}
set logLevel(level) {this._logLevel = typeof level === 'string'? level.toUpperCase(): 'DEBUG'};
get logLevel() {return this._logLevel};
get isRequest() {return typeof $request !== 'undefined' && typeof $response === 'undefined'}
get isResponse() {return typeof $response !== 'undefined' }
get request() {return typeof $request !== 'undefined' ? $request : undefined }
get response() {
if (typeof $response !== 'undefined'){
if ($response.hasOwnProperty('status')) $response['statusCode'] = $response['status']
if ($response.hasOwnProperty('statusCode')) $response['status'] = $response['statusCode']
return $response;
}
else{
return undefined;
}
}
get platform(){
if (this.isSurge) return "Surge"
else if (this.isQuanX) return "Quantumult X"
else if (this.isLoon) return "Loon"
else if (this.isJSBox) return "JSBox"
else if (this.isNode) return "Node.js"
else return "unknown"
}
read(key, session=''){
let val = '';
// 读取原始数据
if (this.isSurge || this.isLoon) {
val = $persistentStore.read(key);
}
else if (this.isQuanX) {
val = $prefs.valueForKey(key);
}
else if (this.isNode){
val = this.node.data;
}
else if (this.isJSBox){
val = $file.read('drive://MagicJS/magic.json').string;
}
try {
// Node 和 JSBox数据处理
if (this.isNode) val = val[key]
if (this.isJSBox) val = JSON.parse(val)[key];
// 带Session的情况
if (!!session){
if(typeof val === 'string') val = JSON.parse(val);
val = !!val && typeof val === 'object' ? val[session]: null;
}
}
catch (err){
this.logError(err);
val = !!session? {} : null;
this.del(key);
}
if (typeof val === 'undefined') val = null;
try {if(!!val && typeof val === 'string') val = JSON.parse(val)} catch(err) {}
this.logDebug(`READ DATA [${key}]${!!session? `[${session}]`: ''}(${typeof val})\n${JSON.stringify(val)}`);
return val;
};
write(key, val, session=''){
let data = !!session ? {} : '';
// 读取原先存储的JSON格式数据
if (!!session && (this.isSurge || this.isLoon)) {
data = $persistentStore.read(key);
}
else if (!!session && this.isQuanX) {
data = $prefs.valueForKey(key);
}
else if (this.isNode){
data = this.node.data;
}
else if (this.isJSBox){
data = JSON.parse($file.read('drive://MagicJS/magic.json').string);
}
if (!!session){
// 有Session所有数据都是Object
try {
if (typeof data === 'string') data = JSON.parse(data)
data = typeof data === 'object' && !!data ? data : {};
}
catch(err){
this.logError(err);
this.del(key);
data = {};
};
if (this.isJSBox || this.isNode){
// 构造数据
if (!data.hasOwnProperty(key) || typeof data[key] != 'object'){
data[key] = {};
}
if (!data[key].hasOwnProperty(session)){
data[key][session] = null;
}
// 写入或删除数据
if (typeof val === 'undefined'){
delete data[key][session];
}
else{
data[key][session] = val;
}
}
else {
// 写入或删除数据
if (typeof val === 'undefined'){
delete data[session];
}
else{
data[session] = val;
}
}
}
// 没有Session时
else{
if (this.isNode || this.isJSBox){
// 删除数据
if (typeof val === 'undefined'){
delete data[key];
}
else{
data[key] = val;
}
}
else{
// 删除数据
if (typeof val === 'undefined'){
data = null;
}
else{
data = val;
}
}
}
// 数据回写
if (typeof data === 'object') data = JSON.stringify(data);
if (this.isSurge || this.isLoon) {
$persistentStore.write(data, key);
}
else if (this.isQuanX) {
$prefs.setValueForKey(data, key);
}
else if (this.isNode){
this.node.fs.writeFileSync('./magic.json', data)
}
else if (this.isJSBox){
$file.write({data: $data({string: data}), path: 'drive://MagicJS/magic.json'});
}
this.logDebug(`WRITE DATA [${key}]${!!session? `[${session}]`: ''}(${typeof val})\n${JSON.stringify(val)}`);
};
del(key, session=''){
this.logDebug(`DELETE KEY [${key}]${!!session ? `[${session}]`:''}`);
this.write(key, null, session);
}
/**
* iOS系统通知
* @param {*} title 通知标题
* @param {*} subTitle 通知副标题
* @param {*} body 通知内容
* @param {*} opts 通知选项目前支持传入超链接或Object
* Surge不支持通知选项Loon和QuantumultX支持打开URL和多媒体通知
* opts "applestore://" 打开Apple Store
* opts "https://www.apple.com.cn/" 打开Apple.com.cn
* opts {'open-url': 'https://www.apple.com.cn/'} 打开Apple.com.cn
* opts {'open-url': 'https://www.apple.com.cn/', 'media-url': 'https://raw.githubusercontent.com/Orz-3/mini/master/Apple.png'} 打开Apple.com.cn显示一个苹果Logo
*/
notify(title=this.scriptName, subTitle='', body='', opts=''){
this.logNotify(`title:${title}\nsubTitle:${subTitle}\nbody:${body}\noptions:${typeof opts === 'object'? JSON.stringify(opts) : opts}`);
let convertOptions = (_opts) =>{
let newOpts = {};
if (typeof _opts === 'string'){
if (this.isLoon) newOpts = {'openUrl': _opts};
else if (this.isQuanX) newOpts = {'open-url': _opts};
}
else if (typeof _opts === 'object'){
if (this.isLoon){
newOpts['openUrl'] = !!_opts['open-url']? _opts['open-url']: '';
newOpts['mediaUrl'] = !!_opts['media-url']? _opts['media-url']: '';
}
else if (this.isQuanX) newOpts = !!_opts['open-url'] || !!_opts['media-url'] ? _opts : {};
}
return newOpts;
}
opts = convertOptions(opts);
// 支持单个参数通知
if (arguments.length == 1){
title = this.scriptName;
subTitle = '',
body = arguments[0];
}
if (this.isSurge){
$notification.post(title, subTitle, body);
}
else if (this.isLoon){
if (!!opts) $notification.post(title, subTitle, body, opts);
else $notification.post(title, subTitle, body);
}
else if (this.isQuanX) {
$notify(title, subTitle, body, opts);
}
else if (this.isNode) {
if (!!this._barkUrl){
let content = encodeURI(`${title}/${subTitle}\n${body}`)
this.get(`${this._barkUrl}/${content}`, ()=>{});
}
}
else if (this.isJSBox){
let push = {
title: title,
body: !!subTitle ? `${subTitle}\n${body}` : body,
}
$push.schedule(push);
}
}
log(msg, level="INFO"){
if (!(this.logLevels[this._logLevel] < this.logLevels[level.toUpperCase()])) console.log(`[${level}] [${this.scriptName}]\n${msg}\n`);
}
logDebug(msg){
this.log(msg, "DEBUG");
}
logInfo(msg){
this.log(msg, "INFO");
}
logNotify(msg){
this.log(msg, "NOTIFY");
}
logWarning(msg){
this.log(msg, "WARNING");
}
logError(msg){
this.log(msg, "ERROR");
}
/**
* 对传入的Http Options根据不同环境进行适配
* @param {*} options
*/
adapterHttpOptions(options, method){
let _options = typeof options === 'object'? Object.assign({}, options): {'url': options, 'headers': {}};
if (_options.hasOwnProperty('header') && !_options.hasOwnProperty('headers')){
_options['headers'] = _options['header'];
delete _options['header'];
}
// 规范化的headers
const headersMap = {
'accept': 'Accept',
'accept-ch': 'Accept-CH',
'accept-charset': 'Accept-Charset',
'accept-features': 'Accept-Features',
'accept-encoding': 'Accept-Encoding',
'accept-language': 'Accept-Language',
'accept-ranges': 'Accept-Ranges',
'access-control-allow-credentials': 'Access-Control-Allow-Credentials',
'access-control-allow-origin': 'Access-Control-Allow-Origin',
'access-control-allow-methods': 'Access-Control-Allow-Methods',
'access-control-allow-headers': 'Access-Control-Allow-Headers',
'access-control-max-age': 'Access-Control-Max-Age',
'access-control-expose-headers': 'Access-Control-Expose-Headers',
'access-control-request-method': 'Access-Control-Request-Method',
'access-control-request-headers': 'Access-Control-Request-Headers',
'age': 'Age',
'allow': 'Allow',
'alternates': 'Alternates',
'authorization': 'Authorization',
'cache-control': 'Cache-Control',
'connection': 'Connection',
'content-encoding': 'Content-Encoding',
'content-language': 'Content-Language',
'content-length': 'Content-Length',
'content-location': 'Content-Location',
'content-md5': 'Content-MD5',
'content-range': 'Content-Range',
'content-security-policy': 'Content-Security-Policy',
'content-type': 'Content-Type',
'cookie': 'Cookie',
'dnt': 'DNT',
'date': 'Date',
'etag': 'ETag',
'expect': 'Expect',
'expires': 'Expires',
'from': 'From',
'host': 'Host',
'if-match': 'If-Match',
'if-modified-since': 'If-Modified-Since',
'if-none-match': 'If-None-Match',
'if-range': 'If-Range',
'if-unmodified-since': 'If-Unmodified-Since',
'last-event-id': 'Last-Event-ID',
'last-modified': 'Last-Modified',
'link': 'Link',
'location': 'Location',
'max-forwards': 'Max-Forwards',
'negotiate': 'Negotiate',
'origin': 'Origin',
'pragma': 'Pragma',
'proxy-authenticate': 'Proxy-Authenticate',
'proxy-authorization': 'Proxy-Authorization',
'range': 'Range',
'referer': 'Referer',
'retry-after': 'Retry-After',
'sec-websocket-extensions': 'Sec-Websocket-Extensions',
'sec-websocket-key': 'Sec-Websocket-Key',
'sec-websocket-origin': 'Sec-Websocket-Origin',
'sec-websocket-protocol': 'Sec-Websocket-Protocol',
'sec-websocket-version': 'Sec-Websocket-Version',
'server': 'Server',
'set-cookie': 'Set-Cookie',
'set-cookie2': 'Set-Cookie2',
'strict-transport-security': 'Strict-Transport-Security',
'tcn': 'TCN',
'te': 'TE',
'trailer': 'Trailer',
'transfer-encoding': 'Transfer-Encoding',
'upgrade': 'Upgrade',
'user-agent': 'User-Agent',
'variant-vary': 'Variant-Vary',
'vary': 'Vary',
'via': 'Via',
'warning': 'Warning',
'www-authenticate': 'WWW-Authenticate',
'x-content-duration': 'X-Content-Duration',
'x-content-security-policy': 'X-Content-Security-Policy',
'x-dnsprefetch-control': 'X-DNSPrefetch-Control',
'x-frame-options': 'X-Frame-Options',
'x-requested-with': 'X-Requested-With',
'x-surge-skip-scripting':'X-Surge-Skip-Scripting'
}
if (typeof _options.headers === 'object'){
for (let key in _options.headers){
if (headersMap[key]) {
_options.headers[headersMap[key]] = _options.headers[key];
delete _options.headers[key];
}
}
}
// 自动补完User-Agent减少请求特征
if (!!!_options.headers || typeof _options.headers !== 'object' || !!!_options.headers['User-Agent']){
if (!!!_options.headers || typeof _options.headers !== 'object') _options.headers = {};
if (this.isNode) _options.headers['User-Agent'] = this.pcUserAgent;
else _options.headers['User-Agent'] = this.iOSUserAgent
}
// 判断是否跳过脚本处理
let skipScripting = false;
if ((typeof _options['opts'] === 'object' && (_options['opts']['hints'] === true || _options['opts']['Skip-Scripting'] === true)) ||
(typeof _options['headers'] === 'object' && _options['headers']['X-Surge-Skip-Scripting'] === true)){
skipScripting = true;
}
if (!skipScripting){
if (this.isSurge) _options.headers['X-Surge-Skip-Scripting'] = false;
else if (this.isLoon) _options.headers['X-Requested-With'] = 'XMLHttpRequest';
else if (this.isQuanX){
if (typeof _options['opts'] !== 'object') _options.opts = {};
_options.opts['hints'] = false;
}
}
// 对请求数据做清理
if (!this.isSurge || skipScripting) delete _options.headers['X-Surge-Skip-Scripting'];
if (!this.isQuanX && _options.hasOwnProperty('opts')) delete _options['opts'];
if (this.isQuanX && _options.hasOwnProperty('opts')) delete _options['opts']['Skip-Scripting'];
// GET请求将body转换成QueryString(beta)
if (method === 'GET' && !this.isNode && !!_options.body){
let qs = Object.keys(_options.body).map(key=>{
if (typeof _options.body === 'undefined') return ''
return `${encodeURIComponent(key)}=${encodeURIComponent(_options.body[key])}`
}).join('&');
if (_options.url.indexOf('?') < 0) _options.url += '?'
if (_options.url.lastIndexOf('&')+1 != _options.url.length && _options.url.lastIndexOf('?')+1 != _options.url.length) _options.url += '&'
_options.url += qs;
delete _options.body;
}
// 适配多环境
if (this.isQuanX){
if (_options.hasOwnProperty('body') && typeof _options['body'] !== 'string') _options['body'] = JSON.stringify(_options['body']);
_options['method'] = method;
}
else if (this.isNode){
delete _options.headers['Accept-Encoding'];
if (typeof _options.body === 'object'){
if (method === 'GET'){
_options.qs = _options.body;
delete _options.body
}
else if (method === 'POST'){
_options['json'] = true;
_options.body = _options.body;
}
}
}
else if (this.isJSBox){
_options['header'] = _options['headers'];
delete _options['headers']
}
return _options;
}
/**
* Http客户端发起GET请求
* @param {*} options
* @param {*} callback
* options可配置参数headers和opts用于判断由脚本发起的http请求是否跳过脚本处理。
* 支持Surge和Quantumult X两种配置方式。
* 以下几种配置会跳过脚本处理options没有opts或opts的值不匹配则不跳过脚本处理
* {opts:{"hints": true}}
* {opts:{"Skip-Scripting": true}}
* {headers: {"X-Surge-Skip-Scripting": true}}
*/
get(options, callback){
let _options = this.adapterHttpOptions(options, 'GET');
this.logDebug(`HTTP GET: ${JSON.stringify(_options)}`);
if (this.isSurge || this.isLoon) {
$httpClient.get(_options, callback);
}
else if (this.isQuanX) {
$task.fetch(_options).then(
resp => {
resp['status'] = resp.statusCode
callback(null, resp, resp.body)
},
reason => callback(reason.error, null, null),
)
}
else if(this.isNode){
return this.node.request.get(_options, callback);
}
else if(this.isJSBox){
_options['handler'] = (resp)=>{
let err = resp.error? JSON.stringify(resp.error) : undefined;
let data = typeof resp.data === 'object' ? JSON.stringify(resp.data) : resp.data;
callback(err, resp.response, data);
}
$http.get(_options);
}
}
/**
* Http客户端发起POST请求
* @param {*} options
* @param {*} callback
* options可配置参数headers和opts用于判断由脚本发起的http请求是否跳过脚本处理。
* 支持Surge和Quantumult X两种配置方式。
* 以下几种配置会跳过脚本处理options没有opts或opts的值不匹配则不跳过脚本处理
* {opts:{"hints": true}}
* {opts:{"Skip-Scripting": true}}
* {headers: {"X-Surge-Skip-Scripting": true}}
*/
post(options, callback){
let _options = this.adapterHttpOptions(options, 'POST');
this.logDebug(`HTTP POST: ${JSON.stringify(_options)}`);
if (this.isSurge || this.isLoon) {
$httpClient.post(_options, callback);
}
else if (this.isQuanX) {
$task.fetch(_options).then(
resp => {
resp['status'] = resp.statusCode
callback(null, resp, resp.body)
},
reason => {callback(reason.error, null, null)}
)
}
else if(this.isNode){
return this.node.request.post(_options, callback);
}
else if(this.isJSBox){
_options['handler'] = (resp)=>{
let err = resp.error? JSON.stringify(resp.error) : undefined;
let data = typeof resp.data === 'object' ? JSON.stringify(resp.data) : resp.data;
callback(err, resp.response, data);
}
$http.post(_options);
}
}
done(value = {}){
if (typeof $done !== 'undefined'){
$done(value);
}
}
isToday(day){
if (day == null){
return false;
}
else{
let today = new Date();
if (typeof day == 'string'){
day = new Date(day);
}
if (today.getFullYear() == day.getFullYear() && today.getMonth() == day.getMonth() && today.getDay() == day.getDay()){
return true;
}
else{
return false;
}
}
}
isNumber(val) {
return parseFloat(val).toString() === "NaN"? false: true;
}
/**
* 对await执行中出现的异常进行捕获并返回避免写过多的try catch语句
* 示例let [err,val] = await magicJS.attempt(func(), 'defaultvalue');
* 或者let [err, [val1,val2]] = await magicJS.attempt(func(), ['defaultvalue1', 'defaultvalue2']);
* @param {*} promise Promise 对象
* @param {*} defaultValue 出现异常时返回的默认值
* @returns 返回两个值,第一个值为异常,第二个值为执行结果
*/
attempt(promise, defaultValue=null){ return promise.then((args)=>{return [null, args]}).catch(ex=>{this.logError(ex); return [ex, defaultValue]})};
/**
* 重试方法
* @param {*} fn 需要重试的函数
* @param {number} [retries=5] 重试次数
* @param {number} [interval=0] 每次重试间隔
* @param {function} [callback=null] 函数没有异常时的回调会将函数执行结果result传入callback根据result的值进行判断如果需要再次重试在callback中throw一个异常适用于函数本身没有异常但仍需重试的情况。
* @returns 返回一个Promise对象
*/
retry(fn, retries=5, interval=0, callback=null) {
return (...args)=>{
return new Promise((resolve, reject) =>{
function _retry(...args){
Promise.resolve().then(()=>fn.apply(this,args)).then(
result => {
if (typeof callback === 'function'){
Promise.resolve().then(()=>callback(result)).then(()=>{resolve(result)}).catch(ex=>{
this.logError(ex);
if (retries >= 1 && interval > 0){
setTimeout(() => _retry.apply(this, args), interval);
}
else if (retries >= 1) {
_retry.apply(this, args);
}
else{
reject(ex);
}
retries --;
});
}
else{
resolve(result);
}
}
).catch(ex=>{
this.logError(ex);
if (retries >= 1 && interval > 0){
setTimeout(() => _retry.apply(this, args), interval);
}
else if (retries >= 1) {
_retry.apply(this, args);
}
else{
reject(ex);
}
retries --;
})
}
_retry.apply(this, args);
});
};
}
formatTime(time, fmt="yyyy-MM-dd hh:mm:ss") {
var o = {
"M+": time.getMonth() + 1,
"d+": time.getDate(),
"h+": time.getHours(),
"m+": time.getMinutes(),
"s+": time.getSeconds(),
"q+": Math.floor((time.getMonth() + 3) / 3),
"S": time.getMilliseconds()
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (time.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;
};
now(){
return this.formatTime(new Date(), "yyyy-MM-dd hh:mm:ss");
}
today(){
return this.formatTime(new Date(), "yyyy-MM-dd");
}
sleep(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
}(scriptName);
}