function MagicJS(scriptName = "MagicJS", logLevel = "INFO") {
    // HTTP/1.1协议头部header字段名大小写不敏感, 但是HTTP/2头部header字段名必须使用小写
    const headerFieldMap = {
        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"
    };
    return new class {
        constructor() {
            this._startTime = Date.now();
            this.version = "2.2.3.7";
            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 = "INFO";
            this.logLevel = logLevel;
            this._barkUrl = "";
            this._barkKey = "";
            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"
                    });
                }
            }
            this.log(`${scriptName}, 开始执行!`);
        }
        /**
         * @param {string} url
         */
        set barkUrl(url) {
            try {
                let _url = url.replace(/\/+$/g, "");
                this._barkUrl = `${/^https?:\/\/([^/]*)/.exec(_url)[0]}/push`;
                this._barkKey = /\/([^\/]+)\/?$/.exec(_url)[1];
            } catch (err) {
                this.logDebug("Bark config error.");
            }
        }
        set logLevel(level) {
            let magic_loglevel = this.read("magicjs_loglevel");
            this._logLevel = magic_loglevel ? magic_loglevel : level.toUpperCase();
        }
        get logLevel() {
            return this._logLevel;
        }
        get isRequest() {
            return typeof $request !== "undefined" && typeof $response === "undefined";
        }
        get isResponse() {
            return typeof $response !== "undefined";
        }
        get isDebug() {
            return this.logLevel === "DEBUG";
        }
        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") {
                    var obj = JSON.parse(val);
                    if (typeof obj == "object" && obj) {
                        val = obj;
                    }
                }
            } 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[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 = "") {
            let convertOptions = _opts => {
                let newOpts = {};
                if (typeof _opts === "string") {
                    if(_opts.length > 0){
                        if (this.isLoon){
                            newOpts = {
                                openUrl: _opts
                            };
                        }else if (this.isQuanX){
                            newOpts = {
                                "open-url": _opts
                            };
                        }else if (this.isSurge){
                            newOpts = {
                                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 : {};
                    } else if (this.isSurge) {
                        let openUrl = _opts["open-url"] || _opts["openUrl"];
                        newOpts = openUrl ? {
                            url: openUrl
                        } : {};
                    }
                }
                return newOpts;
            };
            opts = convertOptions(opts);
            // 支持单个参数通知
            if (arguments.length == 1) {
                title = this.scriptName;
                subTitle = "", body = arguments[0];
            }
            // 生成通知日志
            this.logNotify('\n'+`title:${title}`+'\n'+`subTitle:${subTitle}`+'\n'+`body:${body}`+'\n'+`options:${typeof opts === "object" ? JSON.stringify(opts) : opts}`);
            if (this.isSurge) {
                $notification.post(title, subTitle, body, opts);
            } 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.isJSBox) {
                let push = {
                    title: title,
                    body: !!subTitle ? `${subTitle}`+'\n'+`${body}` : body
                };
                $push.schedule(push);
            }
            // 跨设备统一推送
            if (this._barkUrl && this._barkKey) {
                this.notifyBark(title, subTitle, body);
            }
        }
        /**
         * iOS系统通知，仅在DEBUG模式下推送
         * @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
         */
        notifyDebug(title = this.scriptName, subTitle = "", body = "", opts = "") {
            if (this.logLevel === "DEBUG") {
                // 适配单个参数通知
                if (arguments.length == 1) {
                    title = this.scriptName;
                    subTitle = "", body = arguments[0];
                }
                this.notify(title, subTitle, body, opts);
            }
        }
        notifyBark(title = this.scriptName, subTitle = "", body = "", opts = "") {
            let options = {
                url: this._barkUrl,
                headers: {
                    "Content-Type": "application/json; charset=utf-8"
                },
                body: {
                    title: title,
                    body: subTitle ? `${subTitle}`+'\n'+`${body}` : body,
                    device_key: this._barkKey
                }
            };
            this.post(options, err => {});
        }
        log(msg, level = "INFO") {
            if (!(this.logLevels[this._logLevel] < this.logLevels[level.toUpperCase()])) console.log(`██[${this.scriptName}][${level}]`+''+`${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");
        }
        logRetry(msg) {
            this.log(msg, "RETRY");
        }
        /**
         * 对传入的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"];
            }
            // // 将HTTP/2头部header字段统一转成
            // if (typeof _options.headers === "object" && !!s) {
            //     for (let key in _options.headers) {
            //         if (headerFieldMap[key]) {
            //             _options.headers[headerFieldMap[key]] = _options.headers[key];
            //             delete _options.headers[key];
            //         }
            //     }
            // }
            // 自动补完User-Agent，减少请求特征
            if (!!!_options.headers || typeof _options.headers !== "object" || !!!_options.headers["User-Agent"] && !!!_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;
        }
        adapterHttpResponse(resp) {
            let _resp = {
                body: resp.body,
                headers: resp.headers,
                json: () => {
                    return JSON.parse(_resp.body);
                }
            };
            if (resp.hasOwnProperty("statusCode") && resp.statusCode) {
                _resp["status"] = resp.statusCode;
            }
            return _resp;
        }
        /**
         * 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) {
                this.node.request.get(_options, (err, resp, data) => {
                    resp = this.adapterHttpResponse(resp);
                    callback(err, resp, data);
                });
            } 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);
            }
        }
        getPromise(options) {
            return new Promise((resolve, reject) => {
                magicJS.get(options, (err, resp) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(resp);
                    }
                });
            });
        }
        /**
         * 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) {
                let resp = this.node.request.post(_options, callback);
                resp["status"] = resp.statusCode;
                delete resp.statusCode;
            } 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, {});
            }
        }
        costTime() {
            let info = `${this.scriptName}执行完毕！`
            // if (this.isNode && this.isExecComm) {
            //     info = `指令【${this.comm[1]}】执行完毕！`
            // }
            this._endTime = new Date().getTime();
            const ms = this._endTime - this._startTime;
            const costTime = ms / 1000;
            this.log(`${info}耗时【${costTime}】秒`);
        }
        done(value = {}) {
            // this._endTime = Date.now();
            // let span = (this._endTime - this._startTime) / 1e3;
            // this.log(`SCRIPT COMPLETED: ${span}S.`);
            this.costTime();
            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 => {
                                    if (retries >= 1) {
                                        if (interval > 0) setTimeout(() => _retry.apply(this, args), interval); else _retry.apply(this, args);
                                    } else {
                                        reject(ex);
                                    }
                                    retries--;
                                });
                            } else {
                                resolve(result);
                            }
                        }).catch(ex => {
                            this.logRetry(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));
        }
        objToQueryStr(obj, encode) {
            let str = '';
            for (const key in obj) {
                let value = obj[key];
                if (value != null && value !== '') {
                    if (typeof value === 'object') {
                        value = JSON.stringify(value);
                    } else if (encode) {
                        value = encodeURIComponent(value);
                    }
                    str += `${key}=${value}&`;
                }
            }
            str = str.substring(0, str.length - 1);
            return str;
        }
        parseQueryStr(str) {
            let obj = {};
            if (str.indexOf("?") > -1) {
                str = str.split("?")[1];
            }
            let arr = str.split("&");
            for (let i = 0; i < arr.length; i++) {
                let kv = arr[i].split("=");
                obj[kv[0]] = kv[1];
            }
            return obj;
        }
        deepClone(obj, newObj) {
            newObj = newObj || {};
            for (let key in obj) {
                if (typeof obj[key] == 'object') {
                    newObj[key] = (obj[key].constructor === Array) ? [] : {};
                    this.deepClone(obj[key], newObj[key]);
                } else {
                    newObj[key] = obj[key];
                }
            }
            return newObj;
        }

        convertToObject(input) {
            if (typeof input === "string") {
                let retObj = {};
                try {
                    retObj = JSON.parse(input);
                    const resultType = typeof retObj;
                    if (resultType !== "object" || retObj instanceof Array || resultType === "boolean" || retObj === null) {
                        retObj = {};
                    }
                } catch {
                }
                return retObj;
            }
            else if (input instanceof Array || input === null || typeof input === "undefined" || input !== input || typeof input === "boolean") {
                return {};
            } else {
                return input;
            }
        };

    }(scriptName);
}