From 6155ad42094dcd2537a944eedca8e370bd06477d Mon Sep 17 00:00:00 2001 From: Hmjz100 <98228280+hmjz100@users.noreply.github.com> Date: Sun, 19 Apr 2026 03:30:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20eslint=20=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + eslint.config.mjs | 45 + package.json | 34 + (改)网盘直链下载助手.user.js | 1899 ++++++++++++++++---------------- 4 files changed, 1037 insertions(+), 943 deletions(-) create mode 100644 .gitignore create mode 100644 eslint.config.mjs create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e6e33c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..c94a475 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,45 @@ +import js from "@eslint/js"; + +export default [ + js.configs.recommended, + { + files: ["**/*.user.js"], + languageOptions: { + ecmaVersion: 2022, + sourceType: "script", // 适配油猴脚本的非模块化特性 + globals: { + // 基础环境 + window: "readonly", document: "readonly", console: "readonly", + location: "readonly", navigator: "readonly", history: "readonly", + localStorage: "readonly", sessionStorage: "readonly", + setTimeout: "readonly", setInterval: "readonly", + clearTimeout: "readonly", clearInterval: "readonly", + fetch: "readonly", URL: "readonly", URLSearchParams: "readonly", + Blob: "readonly", File: "readonly", FormData: "readonly", + alert: "readonly", confirm: "readonly", prompt: "readonly", + requestAnimationFrame: "readonly", cancelAnimationFrame: "readonly", + MutationObserver: "readonly", HTMLElement: "readonly", + btoa: "readonly", atob: "readonly", structuredClone: "readonly", + + // GM 环境 + GM: "readonly", GM_info: "readonly", GM_getValue: "readonly", GM_setValue: "readonly", + GM_deleteValue: "readonly", GM_listValues: "readonly", GM_addStyle: "readonly", + GM_xmlhttpRequest: "readonly", GM_registerMenuCommand: "readonly", + GM_openInTab: "readonly", GM_setClipboard: "readonly", + GM_getResourceText: "readonly", unsafeWindow: "readonly", + + // 第三方库 + $: "readonly", jQuery: "readonly", Swal: "readonly", md5: "readonly" + } + }, + rules: { + "prefer-const": ["error", { "destructuring": "all" }], + "no-unused-vars": "warn", + "no-empty": "off", + "no-useless-escape": "off", + "no-prototype-builtins": "off", + "no-control-regex": "off", + "no-irregular-whitespace": "off" + } + } +]; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..9644344 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "linkswift", + "version": "1.1.3.1", + "description": "一个基于 JavaScript 的网盘文件下载地址获取工具", + "main": "eslint.config.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "check": "npx eslint . --fix" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hmjz100/LinkSwift.git" + }, + "keywords": [ + "linkswift", + "link", + "swift", + "download", + "file", + "address", + "get" + ], + "author": "hmjz100", + "license": "AGPL-3.0", + "type": "commonjs", + "bugs": { + "url": "https://github.com/hmjz100/LinkSwift/issues" + }, + "homepage": "https://github.com/hmjz100/LinkSwift#readme", + "devDependencies": { + "@eslint/js": "^10.0.1", + "eslint": "^10.2.1" + } +} diff --git a/(改)网盘直链下载助手.user.js b/(改)网盘直链下载助手.user.js index 21e161d..fee139c 100644 --- a/(改)网盘直链下载助手.user.js +++ b/(改)网盘直链下载助手.user.js @@ -108,7 +108,7 @@ * @author hmjz100 * @namespace github.com/hmjz100 * @description 一个基于 JavaScript 盘的文件下载地址获取工具 支持 百度网盘/阿里云盘/中国移动云盘/天翼云盘/迅雷云盘/夸克网盘/UC网盘/123云盘 八大网盘 代码改自 “网盘直链下载助手”,作者油小猴 - * @version 1.1.3.1-Canary + * @version 1.1.3.1 * @license AGPL-3.0-or-later * @see {@link https://github.com/hmjz100/LinkSwift/ Github 仓库} */ @@ -118,11 +118,11 @@ // unsafeWindow 检测,适用于 Via 这类无 unsafeWindow 的浏览器 if (typeof (unsafeWindow) === "undefined") window.unsafeWindow = window; // 重复执行检测,适用于部分浏览器;出自 “Via 轻插件”,作者谷花泰 - let key = encodeURIComponent("LinkSwift:主代码"); if (window[key]) return; window[key] = true; + const key = encodeURIComponent("LinkSwift:主代码"); if (window[key]) return; window[key] = true; // 全局参数 - let mount = idontknow("LinkSwift"); - let info = { + const mount = idontknow("LinkSwift"); + const info = { author: GM_info.script?.author, name: GM_info.script?.name, version: (GM_info.script?.version || "1.1.3"), @@ -130,8 +130,8 @@ mhandler: GM_info.scriptHandler, mversion: GM_info.version, }; - let $doc = $(document); - let temp = { + const $doc = $(document); + const temp = { mount: $(`.${mount}`), main: {}, page: "", @@ -166,7 +166,7 @@ * * @type{Sweetalert2.Toast} */ - let toast = Swal.mixin({ + const toast = Swal.mixin({ toast: true, position: "top-end", showConfirmButton: false, @@ -189,7 +189,7 @@ * @description 提供统一的提示信息展示方法,基于 SweetAlert2 的 Toast 实现; * 包含 success / error / warning / info / question 等类型。 */ - let message = { + const message = { success: function (text) { toast.fire({ title: text, icon: "success" }); }, @@ -212,7 +212,7 @@ * @author 油小猴 * @author hmjz100 */ - let config = { + const config = { base: { num: "865746", license: "AGPL3", @@ -413,7 +413,7 @@ * @author 油小猴 * @author hmjz100 */ - let base = { + const base = { /** * 注册 GreaseMonkey-Compatible-Manager 扩展菜单命令 * @author 油小猴 @@ -470,11 +470,11 @@ GM_setValue(path, value); return; } - let key = path[0]; - let obj = this.getValue(key) || {}; + const key = path[0]; + const obj = this.getValue(key) || {}; let current = obj; for (let i = 1; i < path.length - 1; i++) { - let keyPart = path[i]; + const keyPart = path[i]; if (!current[keyPart]) current[keyPart] = ""; current = current[keyPart]; } @@ -501,7 +501,7 @@ getStorage(key) { try { return JSON.parse(localStorage.getItem(key)); - } catch (e) { + } catch { return localStorage.getItem(key); } }, @@ -589,9 +589,9 @@ * @returns {String} 大写的文件扩展名(如 `TXT`) */ getExtension(name) { - let reg = /(?!\.)\w+$/; + const reg = /(?!\.)\w+$/; if (reg.test(name)) { - let match = name.match(reg); + const match = name.match(reg); return match[0].toUpperCase(); } return ""; @@ -605,25 +605,25 @@ * @returns {String} 可读格式的大小描述 */ sizeFormat(value = 0) { - var sizeUnitBase = 1024 + const sizeUnitBase = 1024 try { value = Number(value) } catch { } if (typeof value === "number" && !isNaN(value) && value >= 0) { - var units = sizeUnitBase === 1024 + const units = sizeUnitBase === 1024 ? ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] : ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - var unitNames = ["字节", "千字节", "兆字节", "吉字节", "太字节", "拍字节", "艾字节", "泽字节", "尧字节"]; + const unitNames = ["字节", "千字节", "兆字节", "吉字节", "太字节", "拍字节", "艾字节", "泽字节", "尧字节"]; if (value === 0) return "0B(字节)"; // 计算单位层级(取整数部分) - var index = Math.min( + const index = Math.min( Math.floor(Math.log(value) / Math.log(sizeUnitBase)), units.length - 1 ); - var size = value / Math.pow(sizeUnitBase, index); - var formattedSize = size % 1 === 0 ? size.toFixed(0) : size.toFixed(2); + const size = value / Math.pow(sizeUnitBase, index); + const formattedSize = size % 1 === 0 ? size.toFixed(0) : size.toFixed(2); return `${formattedSize}${unitNames[index]}(${units[index]})`; } @@ -652,12 +652,12 @@ if (!Number.isFinite(remainingTimeSeconds) || remainingTimeSeconds < 0) { return "计算中..."; } - let remainingDays = Math.floor(remainingTimeSeconds / (60 * 60 * 24)); + const remainingDays = Math.floor(remainingTimeSeconds / (60 * 60 * 24)); remainingTimeSeconds %= (60 * 60 * 24); - let remainingHours = Math.floor(remainingTimeSeconds / (60 * 60)); + const remainingHours = Math.floor(remainingTimeSeconds / (60 * 60)); remainingTimeSeconds %= (60 * 60); - let remainingMinutes = Math.floor(remainingTimeSeconds / 60); - let remainingSeconds = Math.floor(remainingTimeSeconds % 60); + const remainingMinutes = Math.floor(remainingTimeSeconds / 60); + const remainingSeconds = Math.floor(remainingTimeSeconds % 60); if (remainingDays > 0) { return `${remainingDays}天 ${base.timeFormat(remainingHours)}时:${base.timeFormat(remainingMinutes)}分:${base.timeFormat(remainingSeconds)}秒`; } else if (remainingHours > 0) { @@ -682,8 +682,8 @@ sortByName(arr) { arr.sort(() => { return (a, b) => { - let p1 = a.filename ? a.filename : a.server_filename; - let p2 = b.filename ? b.filename : b.server_filename; + const p1 = a.filename ? a.filename : a.server_filename; + const p2 = b.filename ? b.filename : b.server_filename; return p1.localeCompare(p2, "zh-CN"); }; }); @@ -697,7 +697,7 @@ * @returns {String} 修正后的安全文件名 */ fixFilename(name) { - let replace = /[!?&|`"'*\/:<>\\]/g + const replace = /[!?&|`"'*\/:<>\\]/g return name.replace(replace, "_"); }, @@ -721,8 +721,8 @@ }); headers = rawHeaders; } - let newHeaders = {}; - for (let key in headers) { + const newHeaders = {}; + for (const key in headers) { let value if (this.isType(headers[key]) === "object") value = JSON.stringify(headers[key]); else value = String(headers[key]); @@ -749,7 +749,7 @@ * @returns {String} 编码后的 curl 命令字符串 */ convertLinkToCurl(link, filename, headers) { - let terminal = base.getValue("setting_curl_terminal"); + const terminal = base.getValue("setting_curl_terminal"); filename = base.fixFilename(filename); return `${terminal !== "wp" ? "curl" : "curl.exe"} -L -C - "${link}" -o "${filename}"${headers ? (" " + headers) : ""}`; }, @@ -781,7 +781,7 @@ */ convertLinkToBitComet(link, filename, headers) { filename = base.fixFilename(filename); - let bc = `AA/${encodeURIComponent(filename)}/?url=${encodeURIComponent(link)}${headers ? ("&" + headers) : ""}ZZ`; + const bc = `AA/${encodeURIComponent(filename)}/?url=${encodeURIComponent(link)}${headers ? ("&" + headers) : ""}ZZ`; return `bc://http/${base.encodeBase(bc)}`; }, @@ -796,27 +796,27 @@ * @returns {Promise<"success"|"fail">} 发送态结果 */ async sendLinkToIDM(link, filename, filesize, headers = {}) { - let rpc = base.getValue("setting_idm_rpc").find(i => i.default); + const rpc = base.getValue("setting_idm_rpc").find(i => i.default); if (!this.sendLinkToIDM.lock) this.sendLinkToIDM.lock = Promise.resolve(); return this.sendLinkToIDM.lock = this.sendLinkToIDM.lock.then(async () => { headers = this.standHeaders(headers, true, false); if (!this.sendLinkToIDM.seq) this.sendLinkToIDM.seq = 1; - let seq = this.sendLinkToIDM.seq; - let time = Date.now(); - let url = `http://127.0.0.1:1001/client/${rpc.id}?seq=${seq}`; - let ext = base.getExtension(filename); + const seq = this.sendLinkToIDM.seq; + const time = Date.now(); + const url = `http://127.0.0.1:1001/client/${rpc.id}?seq=${seq}`; + const ext = base.getExtension(filename); - let headersText = Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n") + "\n"; // 坑1:IDM 对 Header 的解码比较死板,最后不加换行不肯解析 + const headersText = Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join("\n") + "\n"; // 坑1:IDM 对 Header 的解码比较死板,最后不加换行不肯解析 function format(key, val) { if (val === undefined || val === null) return ""; - var strVal = String(val); - var len = new Blob([strVal]).size; // 坑2:使用 blob.size,而不是 length + const strVal = String(val); + const len = new Blob([strVal]).size; // 坑2:使用 blob.size,而不是 length return `${key}=${len}:${strVal}`; }; - let fields = [ + const fields = [ format(4, ext), // 4: 文件类型 format(6, link), // 6: 链接 format(7, location.origin), // 7: 来源页面(“该文件来自网页”) @@ -827,17 +827,17 @@ // 坑3:神秘的请求格式 // MSG# {请求指示} #13#1# {10241/20xx}(是/否 使用扩展提供的文件信息) : {?}(可能是距离扩展启动的时间?) :0: {当前时间戳} :0:1: {2/1}(是/否 优先弹窗,再获取文件信息) : {文件大小} :0,{表单}(格式如上); - let data = `MSG#${seq}#13#1#10241:${seq + 1000}:0:${time}:0:1:2:${filesize}:0,${fields.join(",")};`; + const data = `MSG#${seq}#13#1#10241:${seq + 1000}:0:${time}:0:1:2:${filesize}:0,${fields.join(",")};`; - let request = base.post(url, data, {}, "text").catch(() => false); - let timeout = new Promise((_, reject) => { + const request = base.post(url, data, {}, "text").catch(() => false); + const timeout = new Promise((_, reject) => { setTimeout(() => { if (request.abort) request.abort(); reject(new Error("timeout")); }, 15 * 1000); }) - let res = await Promise.race([request, timeout]).catch(() => false); + const res = await Promise.race([request, timeout]).catch(() => false); if (res && res.endsWith(`${seq}:3;`)) { this.sendLinkToIDM.seq++; @@ -860,18 +860,18 @@ async sendLinkToAria2(link, filename, headers) { if (!this.sendLinkToAria2.lock) this.sendLinkToAria2.lock = Promise.resolve(); return this.sendLinkToAria2.lock = this.sendLinkToAria2.lock.then(async () => { - let list = base.getValue("setting_aria2_rpc"); - let selected = list.find(i => i.default); - let rpc = { + const list = base.getValue("setting_aria2_rpc"); + const selected = list.find(i => i.default); + const rpc = { domain: selected.domain, port: selected.port, path: selected.path, dir: selected.dir, token: selected.token }; - let url = `${rpc.domain}:${rpc.port}${rpc.path}`; - let dir = (rpc.dir !== null && rpc.dir !== "") ? rpc.dir : undefined; - let data = { + const url = `${rpc.domain}:${rpc.port}${rpc.path}`; + const dir = (rpc.dir !== null && rpc.dir !== "") ? rpc.dir : undefined; + const data = { id: new Date().getTime(), jsonrpc: "2.0", method: "aria2.addUri", @@ -882,10 +882,10 @@ }] }; try { - let res = await base.post(url, data, {}, "", false); + const res = await base.post(url, data, {}, "", false); if (res.result) return "success"; return "fail"; - } catch (e) { + } catch { return "fail"; } }); @@ -903,9 +903,9 @@ async sendLinkToBitcomet(link, filename, headers) { if (!this.sendLinkToBitcomet.lock) this.sendLinkToBitcomet.lock = Promise.resolve(); return this.sendLinkToBitcomet.lock = this.sendLinkToBitcomet.lock.then(async () => { - let list = base.getValue("setting_bitcomet_rpc"); - let selected = list.find(i => i.default); - let rpc = { + const list = base.getValue("setting_bitcomet_rpc"); + const selected = list.find(i => i.default); + const rpc = { domain: selected.domain, port: selected.port, path: selected.path, @@ -913,19 +913,19 @@ authName: selected.authName, authPass: selected.authPass, }; - let url = `${rpc.domain}:${rpc.port}${rpc.path}`; - let data = new URLSearchParams(); + const url = `${rpc.domain}:${rpc.port}${rpc.path}`; + const data = new URLSearchParams(); data.append("url", link); if (rpc.dir !== null && rpc.dir !== "") data.append("save_path", rpc.dir); data.append("file_name", filename); data.append("connection", 200); if (headers && base.isType(headers) === "object") { - for (var [key, value] of Object.entries(headers)) { + for (const [key, value] of Object.entries(headers)) { data.append(key, value); } } try { - let res = await base.post(url, data, { + const res = await base.post(url, data, { "Authorization": `Basic ${base.encodeBase(rpc.authName + ":" + rpc.authPass)}`, "Content-Type": "application/x-www-form-urlencoded", "Cache-Control": "max-age=0", @@ -937,7 +937,7 @@ } else { return "success"; } - } catch (e) { + } catch { return "success"; } }); @@ -955,20 +955,20 @@ async sendLinkToABDM(link, filename, headers) { if (!this.sendLinkToABDM.lock) this.sendLinkToABDM.lock = Promise.resolve(); return this.sendLinkToABDM.lock = this.sendLinkToABDM.lock.then(async () => { - let newHeaders = {}; - for (let key in headers) { + const newHeaders = {}; + for (const key in headers) { newHeaders[key.toLowerCase().split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join("-")] = headers[key]; } headers = { "User-Agent": navigator.userAgent, "Origin": location.origin, "Referer": `${location.origin}/`, "DNT": "1", ...newHeaders }; - let list = base.getValue("setting_abdm_rpc"); - let selected = list.find(i => i.default); - let rpc = { + const list = base.getValue("setting_abdm_rpc"); + const selected = list.find(i => i.default); + const rpc = { domain: selected.domain, port: selected.port, dir: selected.dir }; - let url = `${rpc.domain}:${rpc.port}/start-headless-download`; - let data = { + const url = `${rpc.domain}:${rpc.port}/start-headless-download`; + const data = { "downloadSource": { "name": filename, "description": "LinkSwift", @@ -980,10 +980,10 @@ } if (rpc.dir) data.folder = rpc.dir; try { - let res = await base.post(url, data, { "Content-Type": "text/plain;charset=UTF-8" }, "text", false); + const res = await base.post(url, data, { "Content-Type": "text/plain;charset=UTF-8" }, "text", false); if (res === "OK") return "success"; return "fail"; - } catch (e) { + } catch { return "fail"; } }); @@ -998,8 +998,8 @@ */ blobDownload(blob, filename) { if (blob instanceof Blob) { - let url = URL.createObjectURL(blob); - let a = document.createElement("a"); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); a.href = url; a.download = filename; a.rel = "noopener" @@ -1016,7 +1016,7 @@ * @returns {XMLHttpRequest|Promise} 请求对象实例或 Promise */ xmlHttpRequest(option) { - let xmlHttpRequest = (typeof GM_xmlhttpRequest === "function") ? GM_xmlhttpRequest : (typeof GM?.xmlHttpRequest === "function") ? GM.xmlHttpRequest : null; + const xmlHttpRequest = (typeof GM_xmlhttpRequest === "function") ? GM_xmlhttpRequest : (typeof GM?.xmlHttpRequest === "function") ? GM.xmlHttpRequest : null; if (!xmlHttpRequest || base.isType(xmlHttpRequest) !== "function") throw new Error("GreaseMonkey 兼容 XMLHttpRequest 不可用。"); return xmlHttpRequest({ withCredentials: true, ...option });; @@ -1044,11 +1044,11 @@ headers = this.standHeaders(headers, true, withOrigin); headers = { "Accept": "*/*,application/json;charset=utf-8", ...headers }; let request - let promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve, reject) => { request = base.xmlHttpRequest({ url, headers, data, method: "POST", responseType: type, - onloadstart: (res) => { + onloadstart: () => { base.console.log("【LinkSwift】Post(start)\n请求地址:" + url + "\n请求数据:", _data, "\n请求头部:", headers); }, onload: (res) => { @@ -1085,7 +1085,7 @@ }); }) if (request) { - var methods = Object.getOwnPropertyNames(request).filter(key => typeof request[key] === 'function' && !promise.hasOwnProperty(key) && !['then', 'catch', 'finally'].includes(key)); // 自动收集 request 上的函数属性进行绑定,并能智能排除 Promise 原生方法 + const methods = Object.getOwnPropertyNames(request).filter(key => typeof request[key] === 'function' && !promise.hasOwnProperty(key) && !['then', 'catch', 'finally'].includes(key)); // 自动收集 request 上的函数属性进行绑定,并能智能排除 Promise 原生方法 methods.forEach(method => { promise[method] = (...args) => request[method](...args); }); // 动态绑定到 Promise } return promise; @@ -1105,11 +1105,11 @@ async get(url, headers, type = "json", withOrigin = true) { headers = this.standHeaders(headers, true, withOrigin); let request - let promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve, reject) => { request = base.xmlHttpRequest({ url, headers, method: "GET", responseType: type, - onloadstart: (res) => { + onloadstart: () => { base.console.log("【LinkSwift】Get(start)\n请求地址:" + url + "\n请求头部:", headers); }, onload: (res) => { @@ -1143,7 +1143,7 @@ }); }) if (request) { - var methods = Object.getOwnPropertyNames(request).filter(key => typeof request[key] === 'function' && !promise.hasOwnProperty(key) && !['then', 'catch', 'finally'].includes(key)); // 自动收集 request 上的函数属性进行绑定,并能智能排除 Promise 原生方法 + const methods = Object.getOwnPropertyNames(request).filter(key => typeof request[key] === 'function' && !promise.hasOwnProperty(key) && !['then', 'catch', 'finally'].includes(key)); // 自动收集 request 上的函数属性进行绑定,并能智能排除 Promise 原生方法 methods.forEach(method => { promise[method] = (...args) => request[method](...args); }); // 动态绑定到 Promise } return promise; @@ -1160,9 +1160,9 @@ async head(url, headers, usingGET) { headers = this.standHeaders(headers); return new Promise((resolve, reject) => { - var method = usingGET ? "Get" : "Head"; + const method = usingGET ? "Get" : "Head"; let _aborted = false; - let request = base.xmlHttpRequest({ + const request = base.xmlHttpRequest({ method: method.toUpperCase(), url, headers, @@ -1227,17 +1227,22 @@ * @returns {Promise} 最终 URL 地址 */ getFinal(url, headers = {}, usingGET = false, returnURL = true) { - return new Promise(async (resolve, reject) => { - var res = await this.head(url, headers, usingGET).catch(reject); - if (!res?.finalUrl) return reject(res); - if (res?.status == 204 && res?.statusText === "IDM") return reject(res); - if (res?.status >= 300 && res?.status < 400) { - base.getFinal(res.finalUrl, headers, usingGET, returnURL).then(resolve).catch(reject); - return; - } - if (returnURL) return resolve(res.finalUrl); - else return resolve(res); - }); + return this.head(url, headers, usingGET) + .then(res => { + if (!res?.finalUrl) throw res; + if (res?.status == 204 && res?.statusText === "IDM") throw res; + + // 如果是重定向,递归调用 + if (res?.status >= 300 && res?.status < 400) { + return this.getFinal(res.finalUrl, headers, usingGET, returnURL); + } + + // 返回最终结果 + return returnURL ? res.finalUrl : res; + }) + .catch(err => { + throw err; + }); }, /** @@ -1255,12 +1260,12 @@ // 初始化全局共享状态 this.download.active = this.download.active || 0; // 全局活跃线程数 this.download.taskCount = this.download.taskCount || 0; // 当前正在运行的 download 任务数 - var global_maxThreads = 8; // 整个允许的最大并发数 + const global_maxThreads = 8; // 整个允许的最大并发数 if (extra) base.console.log(`【LinkSwift】Download\n收到数据:`, extra); if (!extra || !extra.index || !extra.name || !extra.size) throw new Error("extra 缺少内容。"); - let status = { + const status = { aborted: false, requests: new Set(), results: [], @@ -1268,183 +1273,185 @@ maxSpeed: 0 }; - let promise = new Promise(async (resolve, reject) => { - this.download.taskCount++; // 任务进入 + const promise = new Promise((resolve, reject) => { + (async () => { + this.download.taskCount++; // 任务进入 - try { - var finalHead = await base.getFinal(url, headers, false, false).catch(reject); - if (!finalHead) return; - url = finalHead.finalUrl; + try { + const finalHead = await base.getFinal(url, headers, false, false).catch(reject); + if (!finalHead) return; + url = finalHead.finalUrl; - var responseHeaders = finalHead.responseHeaders; - let size = parseInt(extra.size || responseHeaders?.["Content-Length"] || 0, 10); - if (responseHeaders?.["Content-Range"]) { - size = parseInt((responseHeaders["Content-Range"]?.match(/\/(\d+)$/)?.[1] || size), 10); - } + const responseHeaders = finalHead.responseHeaders; + let size = parseInt(extra.size || responseHeaders?.["Content-Length"] || 0, 10); + if (responseHeaders?.["Content-Range"]) { + size = parseInt((responseHeaders["Content-Range"]?.match(/\/(\d+)$/)?.[1] || size), 10); + } - if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress(0, 0, size); - if (!(finalHead.status >= 200 && finalHead.status < 400)) return reject(finalHead); - if (finalHead.status == 204 && finalHead.statusText === "IDM") return reject(finalHead); + if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress(0, 0, size); + if (!(finalHead.status >= 200 && finalHead.status < 400)) return reject(finalHead); + if (finalHead.status == 204 && finalHead.statusText === "IDM") return reject(finalHead); - var supportRange = finalHead.status == 206 && (responseHeaders?.["Accept-Ranges"]?.includes("bytes") || responseHeaders?.["Content-Range"]?.includes("bytes")); + const supportRange = finalHead.status == 206 && (responseHeaders?.["Accept-Ranges"]?.includes("bytes") || responseHeaders?.["Content-Range"]?.includes("bytes")); - if (!!supportRange || size > 0) { - base.console.log(`【LinkSwift】Download(Start)\n文件名称:${extra.name}\n断点续传:支持`); + if (!!supportRange || size > 0) { + base.console.log(`【LinkSwift】Download(Start)\n文件名称:${extra.name}\n断点续传:支持`); - var maxRetry = extra.retry || 10; - let index = 0; - let offset = 0; - let totalLoaded = 0; + const maxRetry = extra.retry || 10; + let index = 0; + let offset = 0; + let totalLoaded = 0; - var worker = async () => { - var minChunk = extra.minChunk || 50 * 1024; // 最小 50KB - var maxChunk = extra.maxChunk || 1 * 1024 * 1024; // 最大 1MB - let chunk = Math.floor(minChunk + (maxChunk - minChunk) * 0.37); + const worker = async () => { + const minChunk = extra.minChunk || 50 * 1024; // 最小 50KB + const maxChunk = extra.maxChunk || 1 * 1024 * 1024; // 最大 1MB + let chunk = Math.floor(minChunk + (maxChunk - minChunk) * 0.37); - while (offset < size && !status.aborted) { - // 如果全局线程满了,且当前任务已经抢到了 1 条以上的线程,则 “让路” 给后来的任务 - let fairShare = Math.max(1, Math.floor(global_maxThreads / this.download.taskCount)); - while (!status.aborted && this.download.active >= global_maxThreads && status.active >= fairShare) { - await new Promise(r => setTimeout(r, 200)); // 等待,直到其他任务释放或有空位 - } + while (offset < size && !status.aborted) { + // 如果全局线程满了,且当前任务已经抢到了 1 条以上的线程,则 “让路” 给后来的任务 + const fairShare = Math.max(1, Math.floor(global_maxThreads / this.download.taskCount)); + while (!status.aborted && this.download.active >= global_maxThreads && status.active >= fairShare) { + await new Promise(r => setTimeout(r, 200)); // 等待,直到其他任务释放或有空位 + } - if (status.aborted || offset >= size) break; + if (status.aborted || offset >= size) break; - var _index = index++; - var start = offset; - var end = Math.min(start + chunk - 1, size - 1); - var _size = end - start + 1; - offset += _size; + const _index = index++; + const start = offset; + const end = Math.min(start + chunk - 1, size - 1); + const _size = end - start + 1; + offset += _size; - let attempt = 0; - while (attempt <= maxRetry && !status.aborted) { - // 占用线程计数 - status.active++; - this.download.active++; + let attempt = 0; + while (attempt <= maxRetry && !status.aborted) { + // 占用线程计数 + status.active++; + this.download.active++; - try { - var startTime = Date.now(); - let lastLoaded = 0; + try { + let startTime = Date.now(); + let lastLoaded = 0; - var res = await new Promise((s, j) => { - var xhr = base.xmlHttpRequest({ - url, method: "GET", responseType: "arraybuffer", - headers: { ...headers, "Range": `bytes=${start}-${end}` }, - onloadstart() { - startTime = Date.now(); - }, - onprogress: (progress) => { - totalLoaded += (progress.loaded - lastLoaded); - lastLoaded = progress.loaded; - let prog = (totalLoaded * 100 / size); - if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress(prog, totalLoaded, size); - }, - onload: (load) => { - status.requests.delete(xhr); - if (load.status == 204 && load.statusText === "IDM") return j(load); - if (load.status >= 200 && load.status < 300) s(load.response); - else j(load); - }, - onerror: (error) => { - status.requests.delete(xhr); - j(error); - } + let res = await new Promise((s, j) => { + const xhr = base.xmlHttpRequest({ + url, method: "GET", responseType: "arraybuffer", + headers: { ...headers, "Range": `bytes=${start}-${end}` }, + onloadstart() { + startTime = Date.now(); + }, + onprogress: (progress) => { + totalLoaded += (progress.loaded - lastLoaded); + lastLoaded = progress.loaded; + const prog = (totalLoaded * 100 / size); + if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress(prog, totalLoaded, size); + }, + onload: (load) => { + status.requests.delete(xhr); + if (load.status == 204 && load.statusText === "IDM") return j(load); + if (load.status >= 200 && load.status < 300) s(load.response); + else j(load); + }, + onerror: (error) => { + status.requests.delete(xhr); + j(error); + } + }); + status.requests.add(xhr); }); - status.requests.add(xhr); - }); - // 智能分块调整 - var _duration = extra.duration || 1.5; // 目标 - var duration = (Date.now() - startTime) / 1000 || 0.1; - var speed = _size / duration; + // 智能分块调整 + const _duration = extra.duration || 1.5; // 目标 + const duration = (Date.now() - startTime) / 1000 || 0.1; + const speed = _size / duration; - let nextChunk; - if (speed > status.maxSpeed * 0.9) { - // 如果速度在提升或维持高位,说明大块是有效的,即便超时也要大胆增加 - // 目标是找到能让 speed 最大化的 chunk 大小 - nextChunk = chunk * 1.5; - status.maxSpeed = Math.max(status.maxSpeed, speed); - } else if (duration < _duration * 0.5) { - // 跑得太快了,可以尝试再加一点 - nextChunk = chunk * 1.2; - } else if (duration > _duration * 2) { - // 只有当耗时严重超过目标(比如超过 2 倍)且速度下降时,才收缩 - nextChunk = chunk * 0.8; - } else { - // 稳定期 - nextChunk = chunk; + let nextChunk; + if (speed > status.maxSpeed * 0.9) { + // 如果速度在提升或维持高位,说明大块是有效的,即便超时也要大胆增加 + // 目标是找到能让 speed 最大化的 chunk 大小 + nextChunk = chunk * 1.5; + status.maxSpeed = Math.max(status.maxSpeed, speed); + } else if (duration < _duration * 0.5) { + // 跑得太快了,可以尝试再加一点 + nextChunk = chunk * 1.2; + } else if (duration > _duration * 2) { + // 只有当耗时严重超过目标(比如超过 2 倍)且速度下降时,才收缩 + nextChunk = chunk * 0.8; + } else { + // 稳定期 + nextChunk = chunk; + } + + chunk = Math.max(minChunk, Math.min(maxChunk, chunk * 0.7 + nextChunk * 0.3)); + chunk = Math.floor(chunk); + + status.results.push({ index: _index, data: res }); + res = null; + break; + } catch (e) { + await new Promise(r => setTimeout(r, 1000 * attempt)); + attempt++; + if (attempt > maxRetry) throw e; + } finally { + // 释放线程计数 + status.active--; + this.download.active--; } - - chunk = Math.max(minChunk, Math.min(maxChunk, chunk * 0.7 + nextChunk * 0.3)); - chunk = Math.floor(chunk); - - status.results.push({ index: _index, data: res }); - res = null; - break; - } catch (e) { - await new Promise(r => setTimeout(r, 1000 * attempt)); - attempt++; - if (attempt > maxRetry) throw e; - } finally { - // 释放线程计数 - status.active--; - this.download.active--; } } - } - }; + }; - // 启动当前任务的并发线程,单任务最高 3 个 - var maxThreads = Math.min(extra.thread || 3, 3); - await Promise.all(Array(maxThreads).fill(0).map(worker)); + // 启动当前任务的并发线程,单任务最高 3 个 + const maxThreads = Math.min(extra.thread || 3, 3); + await Promise.all(Array(maxThreads).fill(0).map(worker)); - if (status.aborted) return; - if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress(100, size, size); + if (status.aborted) return; + if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress(100, size, size); - await new Promise(resolve => setTimeout(resolve, 0)); - status.results.sort((a, b) => a.index - b.index); + await new Promise(resolve => setTimeout(resolve, 0)); + status.results.sort((a, b) => a.index - b.index); - // 分段提取数据 - async function getBlobData(results) { - var dataList = []; - var batchSize = 100; // 每处理 100 个分块释放一次主线程 - for (let i = 0; i < results.length; i++) { - dataList.push(results[i].data); - if (i % batchSize === 0) { - await new Promise(resolve => setTimeout(resolve, 0)); + // 分段提取数据 + async function getBlobData(results) { + const dataList = []; + const batchSize = 100; // 每处理 100 个分块释放一次主线程 + for (let i = 0; i < results.length; i++) { + dataList.push(results[i].data); + if (i % batchSize === 0) { + await new Promise(resolve => setTimeout(resolve, 0)); + } } - } - return dataList; - }; + return dataList; + }; - var finalData = await getBlobData(status.results); - status.results = null; // 释放内存引用 + const finalData = await getBlobData(status.results); + status.results = null; // 释放内存引用 - resolve({ - status: 200, - statusText: "Ok!", - readyState: 4, - response: new Blob(finalData), - finalUrl: url - }); - } else { - // 不支持 Range,回退 - var xhr = base.xmlHttpRequest({ - url: url, headers, method: "GET", responseType: "blob", - onprogress: (progress) => { - if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress((progress.loaded * 100 / progress.total), progress.loaded, progress.total); - }, - onload: (load) => resolve(load), - onerror: (error) => reject(error) - }); - status.requests.add(xhr); + resolve({ + status: 200, + statusText: "Ok!", + readyState: 4, + response: new Blob(finalData), + finalUrl: url + }); + } else { + // 不支持 Range,回退 + const xhr = base.xmlHttpRequest({ + url: url, headers, method: "GET", responseType: "blob", + onprogress: (progress) => { + if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress((progress.loaded * 100 / progress.total), progress.loaded, progress.total); + }, + onload: (load) => resolve(load), + onerror: (error) => reject(error) + }); + status.requests.add(xhr); + } + } catch (e) { + status.aborted = true; + reject(e); + } finally { + this.download.taskCount--; // 无论成功失败,任务退出 } - } catch (e) { - status.aborted = true; - reject(e); - } finally { - this.download.taskCount--; // 无论成功失败,任务退出 - } + })(); }); promise.abort = () => { @@ -1469,10 +1476,10 @@ * @returns {Promise<"success"|"fail">} 连接状态结果 */ async testConnectToAria2(domain, port, path, token) { - return new Promise((resolve, reject) => { - let rpc = { domain, port, path, token }; - let url = `${rpc.domain}:${rpc.port}${rpc.path}`; - let rpcData = { + return new Promise((resolve) => { + const rpc = { domain, port, path, token }; + const url = `${rpc.domain}:${rpc.port}${rpc.path}`; + const rpcData = { id: new Date().getTime(), jsonrpc: "2.0", method: "aria2.getVersion", @@ -1510,9 +1517,9 @@ * @returns {Promise<"success"|"fail">} 连接状态结果 */ async testConnectToABDM(domain, port) { - return new Promise((resolve, reject) => { - let rpc = { domain, port }; - let url = `${rpc.domain}:${rpc.port}/ping`; + return new Promise((resolve) => { + const rpc = { domain, port }; + const url = `${rpc.domain}:${rpc.port}/ping`; base.xmlHttpRequest({ method: "POST", url, headers: {}, data: new Date().getTime(), responseType: "text", @@ -1563,9 +1570,9 @@ */ stringify(obj) { let str = ""; - for (let key in obj) { + for (const key in obj) { if (obj.hasOwnProperty(key)) { - let value = obj[key]; + const value = obj[key]; if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { str += encodeURIComponent(key) + "=" + encodeURIComponent(value[i]) + "&"; @@ -1591,8 +1598,8 @@ */ addStyle(id, tag = "style", css, element = `.${mount}`, position = "append") { base.waitForKeyElements(element, (element) => { - let $styleDom = $(`[${mount}="${id}"], #${id}`); - let $style = $(`<${tag}>`, { + const $styleDom = $(`[${mount}="${id}"], #${id}`); + const $style = $(`<${tag}>`, { rel: "stylesheet", id: id, [mount]: id @@ -1631,9 +1638,9 @@ hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3]; } // 解析 RGB 分量 - let r = parseInt(hex.substring(0, 2), 16); - let g = parseInt(hex.substring(2, 4), 16); - let b = parseInt(hex.substring(4, 6), 16); + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); let a = ""; // 如果是八位十六进制颜色值,解析 alpha 通道 if (hex.length === 8) { @@ -1687,9 +1694,9 @@ cssText = cssText.replace(/url\s*\(\s*(['"]?)(.*?)\1\s*\)/g, (match, quote, url) => { if (url && !/^(data:|https?:|\/\/)/i.test(url)) { try { - let absoluteURL = new URL(url, baseURI).href; + const absoluteURL = new URL(url, baseURI).href; return `url(${absoluteURL})`; - } catch (e) { + } catch { return match; } } @@ -1698,25 +1705,25 @@ } // 处理默认颜色列表 config.base.dom.themes.forEach(item => { - let oldColor = item.color; + const oldColor = item.color; cssText = cssText.replace(new RegExp(base.hexToRgba(oldColor), "ig"), base.hexToRgba(temp.color)); cssText = cssText.replace(new RegExp(oldColor, "ig"), temp.color); }); // 处理 colorMap if (type === "other") { colorMap.forEach(function (colorPair) { - let oldColor = colorPair[0]; - let newColor = colorPair[1]; + const oldColor = colorPair[0]; + const newColor = colorPair[1]; // 生成旧颜色的三种形式:原样、全大写、全小写 - var variants = [ + const variants = [ oldColor, oldColor.toUpperCase(), oldColor.toLowerCase() ]; // 使用 Set 去重 - var uniqueVariants = [...new Set(variants)]; + const uniqueVariants = [...new Set(variants)]; uniqueVariants.forEach(variant => { - var regex = new RegExp(variant, "g"); + const regex = new RegExp(variant, "g"); cssText = cssText.replace(regex, newColor); }); }); @@ -1724,19 +1731,19 @@ } if (colorMap) { colorMap.forEach(function (colorPair) { - let oldColor = colorPair[0]; - let newColor = colorPair[1]; + const oldColor = colorPair[0]; + const newColor = colorPair[1]; // 生成三种形式 - var variants = [ + const variants = [ oldColor, oldColor.toUpperCase(), oldColor.toLowerCase() ]; - var uniqueVariants = [...new Set(variants)]; + const uniqueVariants = [...new Set(variants)]; if (oldColor.includes("#")) { // 替换带属性块的情况(添加 transition) uniqueVariants.forEach(variant => { - var regexWithBlock = new RegExp(variant + "(.*?)}", "gi"); + const regexWithBlock = new RegExp(variant + "(.*?)}", "gi"); cssText = cssText.replace(regexWithBlock, newColor + "$1; transition:all.2s}"); }); // 最后再统一替换剩下的 @@ -1764,15 +1771,15 @@ adaptiveThemeOverride(colorMap, type) { base.waitForKeyElements(`[${mount}^="${mount}-ColorUI-"], [id^="${mount}-ColorUI-"]`, function (tag) { if (tag.html() === base.adaptiveStyleOverride(tag.text(), "", type, colorMap)) return; - let cssText = base.adaptiveStyleOverride(tag.text(), "", type, colorMap); + const cssText = base.adaptiveStyleOverride(tag.text(), "", type, colorMap); base.addStyle(tag.attr(mount), "style", cssText, tag[0]); return true; }, true) base.waitForKeyElements(`[data-pl-colored]`, function (tag) { if (tag.attr("data-pl-colored") === temp.color) return; - let originalStyle = tag.attr("style"); + const originalStyle = tag.attr("style"); if (!originalStyle) return; - let newStyle = base.adaptiveStyleOverride(originalStyle, "", type, colorMap); + const newStyle = base.adaptiveStyleOverride(originalStyle, "", type, colorMap); if (newStyle !== originalStyle) { tag.attr("style", newStyle); } @@ -1785,41 +1792,41 @@ let href = tag.attr("href"); try { href = new URL(href, location.href).href; - } catch (e) { + } catch { return; } fetch(href) .then(response => response.text()) .then(responseText => { - let id = `${mount}-ColorUI-` + href.replace(/[^\w]/g, "_"); - let cssText = base.adaptiveStyleOverride(responseText, href, type, colorMap); + const id = `${mount}-ColorUI-` + href.replace(/[^\w]/g, "_"); + const cssText = base.adaptiveStyleOverride(responseText, href, type, colorMap); if (responseText === base.adaptiveStyleOverride(responseText, href, type, colorMap)) return; base.addStyle(id, "style", cssText, tag[0], "after"); }) }, true); base.waitForKeyElements(`style:not([${mount}^="${mount}-"],[id^="swal-pub"],[class^="darkreader"])`, function (tag) { let id = tag.attr(mount); - let text = tag.html() + const text = tag.html() if (tag.data("styles") === text) return; tag.data("styles", text); // 替换颜色并添加样式 - let cssText = base.adaptiveStyleOverride(text, "", type, colorMap); + const cssText = base.adaptiveStyleOverride(text, "", type, colorMap); if (text === cssText) return; id = id ? id : `${mount}-ColorUI-${count++}` base.addStyle(id, "style", cssText, tag[0], "after"); }, true) base.waitForKeyElements("svg", function (element) { element.find("*").each((index, element) => { - let fill = $(element).attr("fill"); - let stroke = $(element).attr("stroke"); + const fill = $(element).attr("fill"); + const stroke = $(element).attr("stroke"); if (fill) { - let newFill = base.adaptiveStyleOverride(fill, "", type, colorMap); + const newFill = base.adaptiveStyleOverride(fill, "", type, colorMap); if (newFill !== fill) { $(element).attr("fill", newFill); } } if (stroke) { - let newStroke = base.adaptiveStyleOverride(stroke, "", type, colorMap); + const newStroke = base.adaptiveStyleOverride(stroke, "", type, colorMap); if (newStroke !== stroke) { $(element).attr("stroke", newStroke); } @@ -1829,9 +1836,9 @@ base.waitForKeyElements(`[style]:not([${mount}^="${mount}-"],[class*="listener-"])`, function (element) { if (element.parent(`[class*="pl-"]`).length) return; if (element.attr("data-pl-colored") === temp.color) return; - let originalStyle = element.attr("style"); + const originalStyle = element.attr("style"); if (!originalStyle) return; - let newStyle = base.adaptiveStyleOverride(originalStyle, "", type, colorMap); + const newStyle = base.adaptiveStyleOverride(originalStyle, "", type, colorMap); if (newStyle !== originalStyle) { element.attr("style", newStyle); element.attr("data-pl-colored", temp.color); @@ -1863,12 +1870,12 @@ * @returns {Boolean} - 若 a 比 b 更新,返回 true;否则返回 false */ isNewerVersion(a, b) { - let partsA = a.split(".").map(Number); - let partsB = b.split(".").map(Number); - let maxLength = Math.max(partsA.length, partsB.length); + const partsA = a.split(".").map(Number); + const partsB = b.split(".").map(Number); + const maxLength = Math.max(partsA.length, partsB.length); for (let i = 0; i < maxLength; i++) { - let numA = partsA[i] || 0; - let numB = partsB[i] || 0; + const numA = partsA[i] || 0; + const numB = partsB[i] || 0; if (numA > numB) return true; if (numA < numB) return false; } @@ -1882,7 +1889,7 @@ * @returns {String|null} 主版本号(如 `1`)或 `null`(格式错误时) */ getMajorVersion(version) { - let [major] = (version || "").split("."); + const [major] = (version || "").split("."); return /^\d+$/.test(major) ? major : null; }, @@ -1895,11 +1902,11 @@ * @returns {Object|null} React 组件实例或 `null` */ findReact(dom, traverseUp = 0) { - let key = Object.keys(dom).find(key => { + const key = Object.keys(dom).find(key => { return key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$"); }); - let domFiber = dom[key]; + const domFiber = dom[key]; if (domFiber == null) return null; if (domFiber._currentElement) { let compFiber = domFiber._currentElement._owner; @@ -1908,7 +1915,7 @@ } return compFiber._instance; } - let GetCompFiber = fiber => { + const GetCompFiber = fiber => { let parentFiber = fiber.return; while (base.isType(parentFiber.type) == "string") { parentFiber = parentFiber.return; @@ -1952,7 +1959,7 @@ initConfigMigration(latest) { try { if (latest === 1) { - let mapping = { + const mapping = { "setting_rpc_domain": ["setting_aria2_rpc", 0, "domain"], "setting_rpc_port": ["setting_aria2_rpc", 0, "port"], "setting_rpc_path": ["setting_aria2_rpc", 0, "path"], @@ -1973,27 +1980,27 @@ "setting_theme_123": ["setting_ui_theme", "custom", "$123pan"] }; // 旧版配置执行迁移 - for (let oldKey in mapping) { + for (const oldKey in mapping) { let val = base.getValue(oldKey); if (val === undefined || val === null) continue; val = (val === "no" ? false : val === "yes" ? true : val); - let path = mapping[oldKey]; + const path = mapping[oldKey]; if (path.length === 1) { base.setValue(path[0], val); } else { - let [root, ...keys] = path; + const [root, ...keys] = path; let obj = base.getValue(root); if (obj === undefined || obj === null) { - let firstKeyType = typeof keys[0]; - let isIndex = firstKeyType === "number" || (firstKeyType === "string" && /^\d+$/.test(keys[0])); + const firstKeyType = typeof keys[0]; + const isIndex = firstKeyType === "number" || (firstKeyType === "string" && /^\d+$/.test(keys[0])); obj = isIndex ? [] : {}; } let ref = obj; for (let i = 0; i < keys.length - 1; i++) { - let key = keys[i]; + const key = keys[i]; if (!ref[key]) { - let nextKey = keys[i + 1]; - let hasNextIndex = nextKey !== undefined && (base.isType(nextKey === "number" || (typeof nextKey) === "string" && /^\d+$/.test(nextKey))); + const nextKey = keys[i + 1]; + const hasNextIndex = nextKey !== undefined && (base.isType(nextKey === "number" || (typeof nextKey) === "string" && /^\d+$/.test(nextKey))); ref[key] = hasNextIndex ? [] : {}; } ref = ref[key]; @@ -2018,7 +2025,7 @@ initDefaultConfig() { if (base.getValue("setting_config_version") !== "1") base.initConfigMigration(1); // 设置新结构的默认值(仅当未设置时) - let defaults = [ + const defaults = [ { name: "setting_idm_rpc", value: [ @@ -2116,8 +2123,8 @@ if (typeof target !== "object" || Array.isArray(target)) { return cloneDeep(source); } - let result = { ...target }; - for (let key in source) { + const result = { ...target }; + for (const key in source) { if (!source.hasOwnProperty(key)) continue; // 跳过 default 的自动合并 if (key === "default") continue; @@ -2134,9 +2141,9 @@ if (!Array.isArray(target)) { return cloneDeep(source); } - let result = [...target]; + const result = [...target]; if (source.length > 0 && base.isType(source[0]) === "object" && source[0] !== null) { - let template = source[0]; + const template = source[0]; // 填充字段 for (let i = 0; i < result.length; i++) { if (base.isType(result[i]) === "object" && result[i] !== null) { @@ -2160,7 +2167,7 @@ return target; } defaults.forEach(({ name, value }) => { - let current = base.getValue(name); + const current = base.getValue(name); if ( current === null || current === undefined || @@ -2181,7 +2188,7 @@ * @see {@link https://www.youxiaohou.com/zh-cn/motrix.html#使用指南 RPC 配置说明}、 {@link https://www.youxiaohou.com/zh-cn/curl.html cURL 使用教程} */ showSetting(event) { - let setting = $(`
+ const setting = $(`
带星号的设置项目将在网页刷新后生效