From 4850ebbcbbbc32e8385efdfd88a7c6ab5f64218c Mon Sep 17 00:00:00 2001 From: Wjavan <219539974+Wjavan@users.noreply.github.com> Date: Tue, 10 Nov 2026 19:11:57 +0800 Subject: [PATCH 01/20] =?UTF-8?q?fix:=20RPC=20=E6=8E=A8=E9=80=81=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E4=B8=8B=E5=8F=91=E9=9D=9E=E6=B3=95=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=EF=BC=8C=E6=96=87=E4=BB=B6=E5=90=8D=E6=8C=89?= =?UTF-8?q?=20Windows=20=E8=A7=84=E5=88=99=E6=B8=85=E6=B4=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保存路径只填盘符或相对路径时不下发 dir,改用下载器默认目录;留空同样不下发, 不再落进 aria2 的工作目录。文件名统一处理 Windows 非法字符、结尾点空格与设备名, 超长按码点截断并保留扩展名。七家推送收口到 _rpcAddUri,面板改说「已提交」。 --- ...0\275\275\345\212\251\346\211\213.user.js" | 233 +++++++----------- 1 file changed, 83 insertions(+), 150 deletions(-) diff --git "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" index 9620360..25410f8 100644 --- "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" +++ "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" @@ -1,7 +1,7 @@ // ==UserScript== // @name FerryLink // @namespace https://github.com/Wjavan/FerryLink -// @version 1.6.3 +// @version 1.6.4 // @author Wjavan // @description 支持百度/阿里/天翼/迅雷/夸克/移动/城通网盘直链下载。支持 HTTP/JSON-RPC/cURL,推送至 IDM/XDown/Aria2/NDM/Motrix/终端。基于油小猴(youxiaohou.com)的网盘直链下载助手修改。 // @copyright 2026, Wjavan (https://github.com/Wjavan) @@ -491,16 +491,77 @@ base.panelNotice('RPC ' + partErr, 'error'); return null; } + rpc.dir = base.normalizeRpcDir(rpc.dir); + if (rpc.dir) { + const dirErr = base.rpcDirProblem(rpc.dir); + if (dirErr) { base.warnRpcDir(rpc.dir); rpc.dir = undefined; } + } else { + rpc.dir = undefined; // 不发这个键,下载器才会用自己的默认目录 + } rpc.url = `${rpc.domain}:${rpc.port}${rpc.path}`; return rpc; }, safeRpcFilename(name) { - return String(name || '') + let s = String(name || '') .replace(/[\x00-\x1F\x7F]/g, '') - .replace(/[/\\]/g, '_') + .replace(/[\\/:*?"<>|]/g, '_') .replace(/\.\./g, '_') .replace(/^\.+/, '_') - .slice(0, 255); + .replace(/[. ]+$/, ''); // Windows 会吞掉结尾的点和空格 + if (!s) return 'download'; + if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(s)) s = '_' + s; // 设备名不能直接做文件名 + const m = /^(.*?)(\.[A-Za-z0-9_]{1,16})$/.exec(s); + let stem = m ? m[1] : s, ext = m ? m[2] : ''; + // 全路径要留在 260 以内,aria2 还会多写一个 .aria2 控制文件 + let cps = Array.from(stem); + while (cps.length > 1 && (cps.length + ext.length > 100 || new TextEncoder().encode(cps.join('') + ext).length > 200)) cps.pop(); + return cps.join('') + ext; + }, + // 资源管理器「复制路径」带引号,中文输入法的冒号是全角 + normalizeRpcDir(dir) { + return String(dir == null ? '' : dir) + .trim() + .replace(/^["']+|["']+$/g, '') + .replace(/:/g, ':') + .trim(); + }, + // 只收绝对路径;裸盘符在 Windows 下不是目录,aria2 会拼成 C:/文件名 + rpcDirProblem(dir) { + const s = String(dir == null ? '' : dir) + .trim() + .replace(/^["']+|["']+$/g, '') + .replace(/:/g, ':') + .trim(); + if (!s) return ''; + if (/^[A-Za-z]:$/.test(s)) return '保存路径「' + s + '」只有盘符,本次改用下载器默认目录;请填完整路径'; + if (!/^(?:[A-Za-z]:[\\/]|\\\\|\/)/.test(s)) return '保存路径「' + s + '」不是绝对路径,本次改用下载器默认目录;请填完整路径'; + return ''; + }, + // 批量时每个文件都会过一次,同一个值只提醒一次 + warnRpcDir(dir) { + if (base._rpcDirWarned === dir) return; + base._rpcDirWarned = dir; + base.panelNotice(base.rpcDirProblem(dir), 'warning', 6000); + }, + // 七家共用:dir 省略与文件名清洗只写一处 + async _rpcAddUri(rpc, filename, link, options, httpHeaders) { + const ariaOptions = Object.assign({}, options || {}, { out: base.safeRpcFilename(filename) }); + if (rpc.dir) ariaOptions.dir = rpc.dir; + const rpcData = { + id: new Date().getTime(), + jsonrpc: '2.0', + method: 'aria2.addUri', + params: [`token:${rpc.token}`, [link], ariaOptions], + }; + try { + const res = await base.post(rpc.url, rpcData, httpHeaders || {}, ''); + if (res && res.result) return 'success'; + base.panelNotice('Aria2 拒绝请求:' + ((res && res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); + return 'fail'; + } catch (e) { + base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); + return 'fail'; + } }, getRandomString(len) { len = len || 16; @@ -689,7 +750,7 @@ } return compFiber.stateNode || compFiber; }, - rpcDefaults: { domain: 'http://localhost', port: '16800', path: '/jsonrpc', token: '', dir: 'C:' }, + rpcDefaults: { domain: 'http://localhost', port: '16800', path: '/jsonrpc', token: '', dir: '' }, bcDefaults: { domain: 'http://localhost', port: '8080', path: '/panel/task_add_httpftp_result', user: '', pass: '', dir: '' }, initDefaultConfig() { let value = [ @@ -781,7 +842,7 @@ - +
`; Swal.fire({ @@ -1795,7 +1856,7 @@ else { fail++; $cell.html(MARK_NO); base._markFailWithReason($cell, base._lastNotice); } } $btn.text('推送完成:成功 ' + ok + ',失败 ' + fail); - if (fail > 0) base.panelNotice('部分文件推送失败,原因见各行说明', 'error'); else if (ok > 0) base.panelNotice('已推送至 Aria2,下载已开始', 'success'); + if (fail > 0) base.panelNotice('部分文件推送失败,原因见各行说明', 'error'); else if (ok > 0) base.panelNotice('已提交 ' + ok + ' 个任务给 Aria2,进度请看下载器', 'success'); setTimeout(() => base._updateMainButton(), 4000); } else if (pid === 'bc-rpc') { // 先探远程,通就静默推送,不通调本地 BC @@ -2923,26 +2984,9 @@ let rpc = base._rpcConfig(); if (!rpc) return 'fail'; let BDUSS = this.getBDUSS(); - let url = rpc.url; - let rpcData = { - id: new Date().getTime(), - jsonrpc: '2.0', - method: 'aria2.addUri', - params: [`token:${rpc.token}`, [link], { - dir: rpc.dir, - out: base.safeRpcFilename(filename), - header: [`User-Agent: ${panInfo.ua}`, ...(BDUSS ? [`Cookie: BDUSS=${BDUSS}`] : [])], - }] - }; - try { - let res = await base.post(url, rpcData, {"User-Agent": panInfo.ua}, ''); - if (res.result) return 'success'; - base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); - return 'fail'; - } catch (e) { - base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); - return 'fail'; - } + return base._rpcAddUri(rpc, filename, link, { + header: [`User-Agent: ${panInfo.ua}`, ...(BDUSS ? [`Cookie: ***`] : [])], + }, { "User-Agent": panInfo.ua }); }, getSelectedList() { try { @@ -3142,27 +3186,9 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - let url = rpc.url; - const UA = navigator.userAgent; - let rpcData = { - id: new Date().getTime(), - jsonrpc: '2.0', - method: 'aria2.addUri', - params: [`token:${rpc.token}`, [link], { - dir: rpc.dir, - out: base.safeRpcFilename(filename), - header: [`Referer: ${location.origin}/`, `Origin: ${location.origin}`, `User-Agent: ${UA}`] - }] - }; - try { - let res = await base.post(url, rpcData, {}, ''); - if (res.result) return 'success'; - base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); - return 'fail'; - } catch (e) { - base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); - return 'fail'; - } + return base._rpcAddUri(rpc, filename, link, { + header: [`Referer: ${location.origin}/`, `Origin: ${location.origin}`, `User-Agent: ${navigator.userAgent}`], + }, {}); }, getSelectedList() { try { @@ -3353,26 +3379,7 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - let url = rpc.url; - let rpcData = { - id: new Date().getTime(), - jsonrpc: '2.0', - method: 'aria2.addUri', - params: [`token:${rpc.token}`, [link], { - dir: rpc.dir, - out: base.safeRpcFilename(filename), - header: [] - }] - }; - try { - let res = await base.post(url, rpcData, {}, ''); - if (res.result) return 'success'; - base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); - return 'fail'; - } catch (e) { - base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); - return 'fail'; - } + return base._rpcAddUri(rpc, filename, link, { header: [] }, {}); }, getSelectedList() { try { @@ -3639,30 +3646,11 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - let url = rpc.url; let authCookie = this._getAuthCookie ? this._getAuthCookie() : ''; let rpcHeader = authCookie ? [`Cookie: ${authCookie}`, `User-Agent: ${navigator.userAgent}`] : []; - let rpcData = { - id: new Date().getTime(), - jsonrpc: '2.0', - method: 'aria2.addUri', - params: [`token:${rpc.token}`, [link], { - dir: rpc.dir, - out: base.safeRpcFilename(filename), - header: rpcHeader - }] - }; - try { - let res = await base.post(url, rpcData, {}, ''); - if (res.result) return 'success'; - base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); - return 'fail'; - } catch (e) { - base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); - return 'fail'; - } + return base._rpcAddUri(rpc, filename, link, { header: rpcHeader }, {}); }, getSelectedList() { try { @@ -3859,28 +3847,11 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - let url = rpc.url; - let rpcData = { - id: new Date().getTime(), - jsonrpc: '2.0', - method: 'aria2.addUri', - params: [`token:${rpc.token}`, [link], { - dir: rpc.dir, - out: base.safeRpcFilename(filename), - header: [`User-Agent: ${panInfo.ua}`, `Cookie: ${String(document.cookie)}`], - 'user-agent': panInfo.ua, - 'referer': `${location.origin}/` - }] - }; - try { - let res = await base.post(url, rpcData, {}, ''); - if (res.result) return 'success'; - base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); - return 'fail'; - } catch (e) { - base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); - return 'fail'; - } + return base._rpcAddUri(rpc, filename, link, { + header: [`User-Agent: ${panInfo.ua}`, `Cookie: ${String(document.cookie)}`], + 'user-agent': panInfo.ua, + 'referer': `${location.origin}/` + }, {}); }, getSelectedList() { try { @@ -4070,26 +4041,7 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - let url = rpc.url; - let rpcData = { - id: new Date().getTime(), - jsonrpc: '2.0', - method: 'aria2.addUri', - params: [`token:${rpc.token}`, [link], { - dir: rpc.dir, - out: base.safeRpcFilename(filename), - header: [] - }] - }; - try { - let res = await base.post(url, rpcData, {}, ''); - if (res.result) return 'success'; - base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); - return 'fail'; - } catch (e) { - base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); - return 'fail'; - } + return base._rpcAddUri(rpc, filename, link, { header: [] }, {}); }, getSelectedList() { let normalize = (item, vue) => { @@ -4706,26 +4658,7 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - let url = rpc.url; - let rpcData = { - id: new Date().getTime(), - jsonrpc: '2.0', - method: 'aria2.addUri', - params: [`token:${rpc.token}`, [link], { - dir: rpc.dir, - out: base.safeRpcFilename(filename), - header: [] - }] - }; - try { - let res = await base.post(url, rpcData, {}, ''); - if (res.result) return 'success'; - base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); - return 'fail'; - } catch (e) { - base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); - return 'fail'; - } + return base._rpcAddUri(rpc, filename, link, { header: [] }, {}); }, detectPage() { let m = /^\/(f|d)\//.exec(location.pathname); From c5d5dc1662d7e6eb80195e794cb80dffeea66096 Mon Sep 17 00:00:00 2001 From: Wjavan <219539974+Wjavan@users.noreply.github.com> Date: Tue, 10 Nov 2026 22:46:07 +0800 Subject: [PATCH 02/20] =?UTF-8?q?fix:=20=E7=99=BE=E5=BA=A6=20RPC=20?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E7=9A=84=20Cookie=20=E5=A4=B4=E8=A1=A5?= =?UTF-8?q?=E5=9B=9E=20BDUSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 收口时这一行被写成了占位符,推送会带一条无效 Cookie。 --- ...276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" index 25410f8..3a1dd24 100644 --- "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" +++ "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" @@ -2985,7 +2985,7 @@ if (!rpc) return 'fail'; let BDUSS = this.getBDUSS(); return base._rpcAddUri(rpc, filename, link, { - header: [`User-Agent: ${panInfo.ua}`, ...(BDUSS ? [`Cookie: ***`] : [])], + header: [`User-Agent: ${panInfo.ua}`, ...(BDUSS ? [`Cookie: BDUSS=${BDUSS}`] : [])], }, { "User-Agent": panInfo.ua }); }, getSelectedList() { From c0a1c8c8c42fa0a8b7837a03965bbee38bc23719 Mon Sep 17 00:00:00 2001 From: Wjavan <219539974+Wjavan@users.noreply.github.com> Date: Tue, 10 Nov 2026 22:53:32 +0800 Subject: [PATCH 03/20] =?UTF-8?q?refactor:=20=E6=92=A4=E5=9B=9E=20RPC=20?= =?UTF-8?q?=E5=85=B1=E7=94=A8=E5=85=A5=E5=8F=A3=EF=BC=8C=E4=B8=83=E5=AE=B6?= =?UTF-8?q?=E5=90=84=E8=87=AA=E4=BF=9D=E7=95=99=E6=8E=A8=E9=80=81=E6=8A=A5?= =?UTF-8?q?=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\275\275\345\212\251\346\211\213.user.js" | 178 ++++++++++++++---- 1 file changed, 143 insertions(+), 35 deletions(-) diff --git "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" index 3a1dd24..3348a16 100644 --- "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" +++ "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" @@ -543,26 +543,6 @@ base._rpcDirWarned = dir; base.panelNotice(base.rpcDirProblem(dir), 'warning', 6000); }, - // 七家共用:dir 省略与文件名清洗只写一处 - async _rpcAddUri(rpc, filename, link, options, httpHeaders) { - const ariaOptions = Object.assign({}, options || {}, { out: base.safeRpcFilename(filename) }); - if (rpc.dir) ariaOptions.dir = rpc.dir; - const rpcData = { - id: new Date().getTime(), - jsonrpc: '2.0', - method: 'aria2.addUri', - params: [`token:${rpc.token}`, [link], ariaOptions], - }; - try { - const res = await base.post(rpc.url, rpcData, httpHeaders || {}, ''); - if (res && res.result) return 'success'; - base.panelNotice('Aria2 拒绝请求:' + ((res && res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); - return 'fail'; - } catch (e) { - base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); - return 'fail'; - } - }, getRandomString(len) { len = len || 16; let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; @@ -2984,9 +2964,26 @@ let rpc = base._rpcConfig(); if (!rpc) return 'fail'; let BDUSS = this.getBDUSS(); - return base._rpcAddUri(rpc, filename, link, { - header: [`User-Agent: ${panInfo.ua}`, ...(BDUSS ? [`Cookie: BDUSS=${BDUSS}`] : [])], - }, { "User-Agent": panInfo.ua }); + let url = rpc.url; + let rpcData = { + id: new Date().getTime(), + jsonrpc: '2.0', + method: 'aria2.addUri', + params: [`token:${rpc.token}`, [link], { + dir: rpc.dir, + out: base.safeRpcFilename(filename), + header: [`User-Agent: ${panInfo.ua}`, ...(BDUSS ? [`Cookie: BDUSS=${BDUSS}`] : [])], + }] + }; + try { + let res = await base.post(url, rpcData, {"User-Agent": panInfo.ua}, ''); + if (res.result) return 'success'; + base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); + return 'fail'; + } catch (e) { + base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); + return 'fail'; + } }, getSelectedList() { try { @@ -3186,9 +3183,27 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - return base._rpcAddUri(rpc, filename, link, { - header: [`Referer: ${location.origin}/`, `Origin: ${location.origin}`, `User-Agent: ${navigator.userAgent}`], - }, {}); + let url = rpc.url; + const UA = navigator.userAgent; + let rpcData = { + id: new Date().getTime(), + jsonrpc: '2.0', + method: 'aria2.addUri', + params: [`token:${rpc.token}`, [link], { + dir: rpc.dir, + out: base.safeRpcFilename(filename), + header: [`Referer: ${location.origin}/`, `Origin: ${location.origin}`, `User-Agent: ${UA}`] + }] + }; + try { + let res = await base.post(url, rpcData, {}, ''); + if (res.result) return 'success'; + base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); + return 'fail'; + } catch (e) { + base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); + return 'fail'; + } }, getSelectedList() { try { @@ -3379,7 +3394,26 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - return base._rpcAddUri(rpc, filename, link, { header: [] }, {}); + let url = rpc.url; + let rpcData = { + id: new Date().getTime(), + jsonrpc: '2.0', + method: 'aria2.addUri', + params: [`token:${rpc.token}`, [link], { + dir: rpc.dir, + out: base.safeRpcFilename(filename), + header: [] + }] + }; + try { + let res = await base.post(url, rpcData, {}, ''); + if (res.result) return 'success'; + base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); + return 'fail'; + } catch (e) { + base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); + return 'fail'; + } }, getSelectedList() { try { @@ -3646,11 +3680,30 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; + let url = rpc.url; let authCookie = this._getAuthCookie ? this._getAuthCookie() : ''; let rpcHeader = authCookie ? [`Cookie: ${authCookie}`, `User-Agent: ${navigator.userAgent}`] : []; - return base._rpcAddUri(rpc, filename, link, { header: rpcHeader }, {}); + let rpcData = { + id: new Date().getTime(), + jsonrpc: '2.0', + method: 'aria2.addUri', + params: [`token:${rpc.token}`, [link], { + dir: rpc.dir, + out: base.safeRpcFilename(filename), + header: rpcHeader + }] + }; + try { + let res = await base.post(url, rpcData, {}, ''); + if (res.result) return 'success'; + base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); + return 'fail'; + } catch (e) { + base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); + return 'fail'; + } }, getSelectedList() { try { @@ -3847,11 +3900,28 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - return base._rpcAddUri(rpc, filename, link, { - header: [`User-Agent: ${panInfo.ua}`, `Cookie: ${String(document.cookie)}`], - 'user-agent': panInfo.ua, - 'referer': `${location.origin}/` - }, {}); + let url = rpc.url; + let rpcData = { + id: new Date().getTime(), + jsonrpc: '2.0', + method: 'aria2.addUri', + params: [`token:${rpc.token}`, [link], { + dir: rpc.dir, + out: base.safeRpcFilename(filename), + header: [`User-Agent: ${panInfo.ua}`, `Cookie: ${String(document.cookie)}`], + 'user-agent': panInfo.ua, + 'referer': `${location.origin}/` + }] + }; + try { + let res = await base.post(url, rpcData, {}, ''); + if (res.result) return 'success'; + base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); + return 'fail'; + } catch (e) { + base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); + return 'fail'; + } }, getSelectedList() { try { @@ -4041,7 +4111,26 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - return base._rpcAddUri(rpc, filename, link, { header: [] }, {}); + let url = rpc.url; + let rpcData = { + id: new Date().getTime(), + jsonrpc: '2.0', + method: 'aria2.addUri', + params: [`token:${rpc.token}`, [link], { + dir: rpc.dir, + out: base.safeRpcFilename(filename), + header: [] + }] + }; + try { + let res = await base.post(url, rpcData, {}, ''); + if (res.result) return 'success'; + base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); + return 'fail'; + } catch (e) { + base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); + return 'fail'; + } }, getSelectedList() { let normalize = (item, vue) => { @@ -4658,7 +4747,26 @@ async sendLinkToRPC(filename, link) { let rpc = base._rpcConfig(); if (!rpc) return 'fail'; - return base._rpcAddUri(rpc, filename, link, { header: [] }, {}); + let url = rpc.url; + let rpcData = { + id: new Date().getTime(), + jsonrpc: '2.0', + method: 'aria2.addUri', + params: [`token:${rpc.token}`, [link], { + dir: rpc.dir, + out: base.safeRpcFilename(filename), + header: [] + }] + }; + try { + let res = await base.post(url, rpcData, {}, ''); + if (res.result) return 'success'; + base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)')); + return 'fail'; + } catch (e) { + base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启'); + return 'fail'; + } }, detectPage() { let m = /^\/(f|d)\//.exec(location.pathname); From 4867169c46b527e6c48b63007935184a03490d11 Mon Sep 17 00:00:00 2001 From: Wjavan <219539974+Wjavan@users.noreply.github.com> Date: Tue, 10 Nov 2026 23:09:45 +0800 Subject: [PATCH 04/20] =?UTF-8?q?refactor:=20=E4=BF=9D=E5=AD=98=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E7=9A=84=E5=BD=92=E4=B8=80=E4=B8=8E=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E5=90=88=E6=88=90=E4=B8=80=E4=B8=AA=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\275\275\345\212\251\346\211\213.user.js" | 44 +++++-------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" index 3348a16..c6c1a86 100644 --- "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" +++ "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" @@ -491,13 +491,9 @@ base.panelNotice('RPC ' + partErr, 'error'); return null; } - rpc.dir = base.normalizeRpcDir(rpc.dir); - if (rpc.dir) { - const dirErr = base.rpcDirProblem(rpc.dir); - if (dirErr) { base.warnRpcDir(rpc.dir); rpc.dir = undefined; } - } else { - rpc.dir = undefined; // 不发这个键,下载器才会用自己的默认目录 - } + const dir = base.rpcDir(rpc.dir); + rpc.dir = dir.dir; // 空或非法就不发这个键,下载器会用自己的默认目录 + if (dir.err) base._rpcDirNote = dir.err; rpc.url = `${rpc.domain}:${rpc.port}${rpc.path}`; return rpc; }, @@ -517,31 +513,12 @@ while (cps.length > 1 && (cps.length + ext.length > 100 || new TextEncoder().encode(cps.join('') + ext).length > 200)) cps.pop(); return cps.join('') + ext; }, - // 资源管理器「复制路径」带引号,中文输入法的冒号是全角 - normalizeRpcDir(dir) { - return String(dir == null ? '' : dir) - .trim() - .replace(/^["']+|["']+$/g, '') - .replace(/:/g, ':') - .trim(); - }, - // 只收绝对路径;裸盘符在 Windows 下不是目录,aria2 会拼成 C:/文件名 - rpcDirProblem(dir) { - const s = String(dir == null ? '' : dir) - .trim() - .replace(/^["']+|["']+$/g, '') - .replace(/:/g, ':') - .trim(); - if (!s) return ''; - if (/^[A-Za-z]:$/.test(s)) return '保存路径「' + s + '」只有盘符,本次改用下载器默认目录;请填完整路径'; - if (!/^(?:[A-Za-z]:[\\/]|\\\\|\/)/.test(s)) return '保存路径「' + s + '」不是绝对路径,本次改用下载器默认目录;请填完整路径'; - return ''; - }, - // 批量时每个文件都会过一次,同一个值只提醒一次 - warnRpcDir(dir) { - if (base._rpcDirWarned === dir) return; - base._rpcDirWarned = dir; - base.panelNotice(base.rpcDirProblem(dir), 'warning', 6000); + // 只认绝对路径;裸盘符在 Windows 下不是目录,aria2 会拼成 C:/文件名 + rpcDir(dir) { + const s = String(dir == null ? '' : dir).trim().replace(/^["']+|["']+$/g, '').replace(/:/g, ':').trim(); + if (!s) return { dir: undefined, err: '' }; + if (!/^(?:[A-Za-z]:[\\/]|\\\\|\/)/.test(s)) return { dir: undefined, err: '保存路径「' + s + '」不是完整路径,本次改用下载器默认目录' }; + return { dir: s, err: '' }; }, getRandomString(len) { len = len || 16; @@ -1827,6 +1804,7 @@ const d = base.getValue('setting_rpc_domain'), p = base.getValue('setting_rpc_port'), pa = base.getValue('setting_rpc_path'); if (!d || !p || !pa) { base.showRPCSetting(false); return; } $btn.text('正在推送 ' + files.length + ' 个任务至 RPC…').prop('disabled', true); + base._rpcDirNote = ''; let ok = 0, fail = 0; for (let f of files) { const $cell = $('.fl-dl-file-status[data-status-index="' + f._idx + '"]'); @@ -1836,7 +1814,7 @@ else { fail++; $cell.html(MARK_NO); base._markFailWithReason($cell, base._lastNotice); } } $btn.text('推送完成:成功 ' + ok + ',失败 ' + fail); - if (fail > 0) base.panelNotice('部分文件推送失败,原因见各行说明', 'error'); else if (ok > 0) base.panelNotice('已提交 ' + ok + ' 个任务给 Aria2,进度请看下载器', 'success'); + if (fail > 0) base.panelNotice('部分文件推送失败,原因见各行说明', 'error'); else if (ok > 0) base.panelNotice('已提交 ' + ok + ' 个任务给 Aria2,进度请看下载器' + (base._rpcDirNote ? ';' + base._rpcDirNote : ''), base._rpcDirNote ? 'warning' : 'success'); setTimeout(() => base._updateMainButton(), 4000); } else if (pid === 'bc-rpc') { // 先探远程,通就静默推送,不通调本地 BC From 55c35db1786094d7ade12739f1f557379e0b6968 Mon Sep 17 00:00:00 2001 From: Wjavan <219539974+Wjavan@users.noreply.github.com> Date: Tue, 10 Nov 2026 23:12:56 +0800 Subject: [PATCH 05/20] =?UTF-8?q?style:=20=E7=B2=BE=E7=AE=80=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=BB=A3=E7=A0=81=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...344\270\213\350\275\275\345\212\251\346\211\213.user.js" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" index c6c1a86..4180e7c 100644 --- "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" +++ "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" @@ -492,7 +492,7 @@ return null; } const dir = base.rpcDir(rpc.dir); - rpc.dir = dir.dir; // 空或非法就不发这个键,下载器会用自己的默认目录 + rpc.dir = dir.dir; if (dir.err) base._rpcDirNote = dir.err; rpc.url = `${rpc.domain}:${rpc.port}${rpc.path}`; return rpc; @@ -508,12 +508,12 @@ if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(s)) s = '_' + s; // 设备名不能直接做文件名 const m = /^(.*?)(\.[A-Za-z0-9_]{1,16})$/.exec(s); let stem = m ? m[1] : s, ext = m ? m[2] : ''; - // 全路径要留在 260 以内,aria2 还会多写一个 .aria2 控制文件 + // 给全路径与 .aria2 留余量 let cps = Array.from(stem); while (cps.length > 1 && (cps.length + ext.length > 100 || new TextEncoder().encode(cps.join('') + ext).length > 200)) cps.pop(); return cps.join('') + ext; }, - // 只认绝对路径;裸盘符在 Windows 下不是目录,aria2 会拼成 C:/文件名 + // 裸盘符不是目录,aria2 会拼成 C:/文件名 rpcDir(dir) { const s = String(dir == null ? '' : dir).trim().replace(/^["']+|["']+$/g, '').replace(/:/g, ':').trim(); if (!s) return { dir: undefined, err: '' }; From 5bfaa901ebf31460c2181a6234c73c3018017a79 Mon Sep 17 00:00:00 2001 From: Wjavan <219539974+Wjavan@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:14:04 +0800 Subject: [PATCH 06/20] =?UTF-8?q?fix:=20=E6=94=B6=E7=B4=A7=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E8=B7=AF=E5=BE=84=E6=A0=A1=E9=AA=8C=EF=BC=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=90=8D=E6=88=AA=E6=96=AD=E5=90=8E=E5=86=8D=E5=8E=BB?= =?UTF-8?q?=E5=B0=BE=E9=83=A8=E7=82=B9=E7=A9=BA=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\270\213\350\275\275\345\212\251\346\211\213.user.js" | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" index 4180e7c..f334a62 100644 --- "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" +++ "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" @@ -511,13 +511,14 @@ // 给全路径与 .aria2 留余量 let cps = Array.from(stem); while (cps.length > 1 && (cps.length + ext.length > 100 || new TextEncoder().encode(cps.join('') + ext).length > 200)) cps.pop(); - return cps.join('') + ext; + return (cps.join('') + ext).replace(/[. ]+$/, '') || 'download'; }, // 裸盘符不是目录,aria2 会拼成 C:/文件名 rpcDir(dir) { - const s = String(dir == null ? '' : dir).trim().replace(/^["']+|["']+$/g, '').replace(/:/g, ':').trim(); + const s = String(dir == null ? '' : dir).trim().replace(/^["']+|["']+$/g, '').replace(/^([A-Za-z]):/, '$1:').trim(); if (!s) return { dir: undefined, err: '' }; - if (!/^(?:[A-Za-z]:[\\/]|\\\\|\/)/.test(s)) return { dir: undefined, err: '保存路径「' + s + '」不是完整路径,本次改用下载器默认目录' }; + if (/[\x00-\x1F\x7F]/.test(s)) return { dir: undefined, err: '保存路径含不可见字符,本次改用下载器默认目录' }; + if (!/^(?:[A-Za-z]:[\\/]|\\\\[^\\/]+[\\/]|\/)/.test(s)) return { dir: undefined, err: '保存路径「' + s + '」不是完整路径,本次改用下载器默认目录;请填完整路径' }; return { dir: s, err: '' }; }, getRandomString(len) { @@ -2190,6 +2191,7 @@ .fl-dl-warning-bar { background: #3f2226; color: #e06c75; } .fl-dl-toast { background: #2a2b2c; color: #e8eaed; border-color: #3f4041; } .fl-dl-toast-success { border-color: #67c23a; color: #95d475; } + .fl-dl-toast-warning { border-color: #e6a23c; color: #ebb563; } .fl-dl-toast-error { border-color: #f56c6c; color: #f89898; } .fl-dl-empty { color: #8b8e93; } .fl-dl-empty svg { color: #5e6165; } @@ -2357,6 +2359,7 @@ .fl-dl-toast { position: fixed; top: 10vh; left: 50%; transform: translateX(-50%); z-index: 2147483647; display: flex; align-items: center; background: #fff; color: #303133; border: 1px solid #e6e8eb; padding: 12px 24px; border-radius: 8px; font-size: 15px; font-weight: 500; box-shadow: 0 4px 16px rgb(28 28 32 / 18%), 0 1px 3px rgb(28 28 32 / 12%); max-width: 80vw; } .fl-dl-toast span { word-break: break-all; } .fl-dl-toast-success { border-color: #67c23a; color: #2f7a17; } + .fl-dl-toast-warning { border-color: #e6a23c; color: #8a5a00; } .fl-dl-toast-error { border-color: #f56c6c; color: #c03639; } .fl-dl-empty { padding: 14px 28px; text-align: center; color: #6b7078; font-size: 14.5px; } .fl-dl-empty svg { display: block; margin: 0 auto 10px; color: #c3c8cf; } From 2ee1e3f886b6cf88c38d0627e7cd7f341bd3d58b Mon Sep 17 00:00:00 2001 From: Wjavan <219539974+Wjavan@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:17:28 +0800 Subject: [PATCH 07/20] =?UTF-8?q?style:=20=E6=B3=A8=E9=87=8A=E4=B8=8E?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=AE=9E=E9=99=85=E8=83=BD=E5=8A=9B=E5=AF=B9?= =?UTF-8?q?=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" index f334a62..82a82aa 100644 --- "a/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" +++ "b/\347\275\221\347\233\230\347\233\264\351\223\276\344\270\213\350\275\275\345\212\251\346\211\213.user.js" @@ -508,7 +508,7 @@ if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(s)) s = '_' + s; // 设备名不能直接做文件名 const m = /^(.*?)(\.[A-Za-z0-9_]{1,16})$/.exec(s); let stem = m ? m[1] : s, ext = m ? m[2] : ''; - // 给全路径与 .aria2 留余量 + // 给 .aria2 留余量 let cps = Array.from(stem); while (cps.length > 1 && (cps.length + ext.length > 100 || new TextEncoder().encode(cps.join('') + ext).length > 200)) cps.pop(); return (cps.join('') + ext).replace(/[. ]+$/, '') || 'download'; From 6ad74584b059922dffcfb2aba2e23011a6b2cef7 Mon Sep 17 00:00:00 2001 From: Wjavan <219539974+Wjavan@users.noreply.github.com> Date: Wed, 11 Nov 2026 01:39:53 +0800 Subject: [PATCH 08/20] =?UTF-8?q?fix:=20=E6=96=87=E4=BB=B6=E5=90=8D?= =?UTF-8?q?=E5=8F=AA=E6=8C=A1=E8=B7=AF=E5=BE=84=E5=88=86=E9=9A=94=E7=AC=A6?= =?UTF-8?q?=E4=B8=8E=20255=20=E7=A0=81=E5=85=83=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 字符集归网盘管:七家上传时就拒掉 :*?"<>| 这类字符, 这边再改一遍只是白改一遍名字。留下的是网盘管不到的: 路径分隔符、长度(NTFS 按码元算 255,aria2 还要写