From e8a86f8df8f98ea1ca15c095c1ae2dab267d71af Mon Sep 17 00:00:00 2001 From: KanouAo <214709230@qq.com> Date: Tue, 20 May 2025 22:48:32 +0800 Subject: [PATCH 1/5] 5.20 --- .gitignore | 11 + test.js | 63 + ...200\205hmjz100\347\232\204\350\257\235.md" | 50 + ...0\275\275\345\212\251\346\211\213.user.js" | 2006 +++++++++++++---- 4 files changed, 1690 insertions(+), 440 deletions(-) create mode 100644 .gitignore create mode 100644 test.js create mode 100644 "\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01d737d --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# 忽略所有文件 +!网盘直链下载助手.user.js # 保留这个文件 +!给开发者hmjz100的话.md + +/config/ +node_modules/ +package-lock.json +*.json +*.css +LICENSE +README.md \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..ca51db1 --- /dev/null +++ b/test.js @@ -0,0 +1,63 @@ +// let callCounter = 0; // 全局计数器 + +// async function getFilesByOnce(file) { +// // 每调用50次暂停3秒 +// if (callCounter >= 50) { +// console.log('已调用50次,暂停3秒...'); +// await new Promise(resolve => setTimeout(resolve, 3000)); // 暂停3秒 +// callCounter = 0; // 重置计数器 +// } + +// console.log(`防反爬计数器: ${callCounter}`); +// callCounter++; // 增加计数器 + +// // 这里模拟获取文件的操作 +// // 实际操作可能包括网络请求等异步任务 +// // 返回文件数据或链接 +// return `链接: ${file}`; +// } + +// // 创建一个异步迭代器,模拟动态生成文件 +// async function* fileGenerator(totalFiles) { +// let index = 0; +// while (index < totalFiles) { +// // 模拟异步生成文件 +// await new Promise(resolve => setTimeout(resolve, 100)); +// yield `file ${index++}`; +// } +// } + +// async function processFilesWithLimit(fileIterator, limit) { +// const executing = new Set(); +// const results = []; + +// for await (const file of fileIterator) { +// const result = getFilesByOnce(file); +// results.push(result); +// executing.add(result); + +// if (executing.size >= limit) { +// await Promise.race(executing); +// } + +// // 当一个文件处理完毕后,从执行集合中移除 +// executing.delete(result); +// } + +// // 等待所有剩余的文件处理完毕 +// await Promise.all(executing); +// return results; +// } + +// (async () => { +// const totalFiles = 500; // 总文件数 +// const limit = 10; // 并发限制 + +// // 创建文件迭代器 +// const files = fileGenerator(totalFiles); + +// // 使用并发限制处理所有文件 +// const results = await processFilesWithLimit(files, limit); + +// console.log('所有文件处理完毕', results); +// })(); \ No newline at end of file diff --git "a/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" "b/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" new file mode 100644 index 0000000..a09ecdb --- /dev/null +++ "b/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" @@ -0,0 +1,50 @@ +我增加了下载文件夹功能,一键下载文件夹下所有的文件(多个)文件夹嵌套文件夹的这种目录组织都能下载下来,测试环境 win11、edge 浏览器、RPC 发 Aira2 + +我只动了(改)网盘直链下载助手.user,其它我都没改 +我虽然会写代码,但油猴我第 1 次做,爬网站数据是第 2 次,上次还是咸鱼捡二手的提醒, +我虽然会 JS 但并不是主 JS 的,对 js 同步异步这边不是很熟悉,你可以看情况修改 +标注 TODO 的都是要注意一下的。 + +我是从 gitee 上找到这项目的,结果我改好了,才看到你 github 也上传了,你 gitee 上没写 github 上的链接,然后发现你 Gitee 上没同步,进度落下几个月……。 +我 Gitee 和 GitHub 都同时推送,你可以选择任意一个合并,Gitee 毕竟是国内的,政策之类的怕删代码我不怎么用。 + +因为目录算是特殊的文件,我始终假设返回的是文件,统一起 file 这系列的名字,后续再判断是否文件夹 + +获取下载链接的时候,我觉得每获取 1 个都展示出来太快,会晃花眼,我调整了下每批次才展示 1 次,你觉得不合适可以改回去 + +有的网盘给的链接只有几小时时效,推荐拉高下载器同时任务数,或者等失败了再用脚本再请求次再下载(需要注意设置不覆盖同名文件), + +我平时不怎么下载东西,所以只是简单测试,不知道有没有问题 + +广度和递归遍历文件夹我犹豫了很久,广度可以改成并发访问多个文件夹加快速度,甚至写了一半了,后来还是觉得递归好点,一是更贴合人类访问逻辑,防反爬,二是一般人分享文件,不会存那么多个文件夹,会是少数几个文件夹里放大量的文件,导致广度并发比递归快不了多少。 + +百度: +已完成 + +阿里: +关于分享的那部分,原作者的就有问题,我就没做 + +移动: +关于分享的那部分,原作者的就有问题,我就没做 +移动网盘文件数量多,比如几百个的时候,发 RPC 会卡住,但是正常发 RPC,发完会才能操作,这边应该是你这边的问题,请看下(测试环境: windows 发本地的 AriaNg Native 客户端)。 + +夸克: +我认为连续访问网盘的接口不如直接拿全部 ID 合适,post 协议本身不作数据长度上限,即使后台限制为 1M,也有 65536 个文件 ID 了,你如果觉得不合适可以修改 +我发觉我下个 37 个 G 的单个文件,只有 30 几 K 的速度,连接数只有 1,其实小点的哪个十多个 G 的文件都是正常的,先骂个夸克。 + +天翼: +拿分享的时候有 1 次不完整,不知是不是我看错了 + +UC: +1、我看到 user-agent 写的是夸克的,怕你写错,你注意下。 +2、我认为连续访问网盘的接口不如直接拿全部 ID 合适,post 协议本身不作数据长度上限,即使后台限制为 1M,也有 65536 个文件 ID 了,你如果觉得不合适可以修改 + +123: +1、我测试时发现大量上传名字按顺序,但内容一样的记事本,它会提示风控,可能抓得严,但名字内容随机就没所谓,要注意下。 +2、遍历文件的请求 URL 中间有 XXX=时间戳-XXX-XXX 的格式,我发觉那放 3 个随机数竟然也行,如果不合适你就找下。 +cookies 有串 CNZZDATA128030xxxx=191082xxxx-174757xxxx 看起来有点像,可以试试 +3、123 网盘的分享站有 3 个,www.123pan.com、www.123684.com、www.123912.com,这3个站请求的url不一样,但都适配www.123pan.com,如果反爬虫你注意下 +4、我想测试下带密码的那种分享的,哎呀妈呀,都没找到网盘资源,好不容易找到个网站,还有 2 个钟就关站了,注册都卡界面 404,BING 找到几个分享,还都不用密码……,最后我分享我自己的,但这样可能会有问题,比如天翼进自己的分享和进别人的分享状况不一样,要不你自己测试下。 +5、123 网盘你没做需要密码的分享的那种适配,不用密码的功能正常。我简单看了看,在 cookies 没找到密码,按理说应该有的,我就懒得看代码了。不过按理来说,123 的分享一般都是没密码的,实在不行转换思维让用户自己输密码。 +6、123 用着夸克的系统?我看着有的就像从夸克那没注意抄过来的我就删除了,你注意下。 +7、然后平台是 IOS 不是 WEB?我就改了下,觉得不行你就改回来。 diff --git "a/\357\274\210\346\224\271\357\274\211\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/\357\274\210\346\224\271\357\274\211\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 25342f7..e14a8ca 100644 --- "a/\357\274\210\346\224\271\357\274\211\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/\357\274\210\346\224\271\357\274\211\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" @@ -2,7 +2,7 @@ // @name LinkSwift // @namespace github.com/hmjz100 // @version 1.1.0.1 -// @author Hmjz100、油小猴 +// @author KanouAo、Hmjz100、油小猴 // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTAzLjYgMTA3LjRjMy41LTIuMiA4LjktNi4xIDEzLjgtMTIuNXM3LjMtMTIuNSA4LjUtMTYuNWMuNS0xLjcgMi4yLTcuNSAyLjItMTQuNyAwLTEwLjEtMy4zLTI1LjEtMTUuNC0zNi44LTE0LjUtMTQtMzIuMS0xNC4zLTM1LjctMTQuMy04IDAtMTUuNyAxLjktMjIuNiA1LjJDNDQgMjMgMzUuNyAzMS40IDMwLjggNDEuN2MtMS4zIDIuOC00IDQuNy03LjEgNS00IC4zLTcuNSA0LjQtOC45IDkuNi0uNSAxLjktMS42IDMuNS0zLjEgNC43QzQuNCA2Ni44IDAgNzUuNyAwIDg1YzAgNi44IDIuMyAxMy4xIDYuMSAxOC4yIDUuNSA3LjQgMTQuMiAxMi4yIDI0IDEyLjJoNDcuMWM0LjQgMCAxMS0uNSAxOC4zLTMuNSAzLjItMS40IDUuOS0zIDguMS00LjV6IiBmaWxsPSIjQTA5OUYwIi8+PHBhdGggZD0iTTExOS44IDY0LjNjLjEtMTcuMS0xMC40LTI4LTEyLjUtMzAuMUM5NSAyMi4xIDc5LjkgMjEuOCA3Ni45IDIxLjhjLTE3LjYgMC0zMy4zIDEwLjUtMzkuOSAyNi43LS42IDEuMy0xLjggMi4zLTMuNCAyLjNoLS40Yy01LjggMC0xMC42IDQuOC0xMC42IDEwLjd2LjVjMCAxLjQtLjggMi42LTEuOSAzLjNDMTMuNCA2OSA4LjggNzYuOCA4LjggODVjMCAxMi4yIDkuOSAyMi4zIDIyLjIgMjIuM2g0NS4yYzMuNi0uMSAxNy42LS45IDI5LjYtMTIgMi45LTIuOCAxMy45LTEzLjcgMTQtMzF6IiBmaWxsPSIjNTc0QUI4Ii8+PHBhdGggZD0iTTExMC44IDU3LjRsLjIgMy4zYzAgMS4zLTEuMSAyLjQtMi4zIDIuNC0xLjMgMC0yLjMtMS4xLTIuMy0yLjRsLS4xLTIuOHYtLjNjMC0xLjIuOS0yLjIgMi4xLTIuM2guM2MuNyAwIDEuMy4zIDEuNy43LS4yLjEuMy41LjQgMS40em0tMy4zLTEwLjNjMCAxLjItMSAyLjMtMi4yIDIuM2gtLjFjLS44IDAtMS42LS41LTItMS4yLTQuNi04LjMtMTMuMy0xMy41LTIyLjgtMTMuNS0xLjIgMC0yLjMtMS0yLjMtMi4ydi0uMWMwLTEuMiAxLTIuMyAyLjItMi4zaC4xYTMwLjM3IDMwLjM3IDAgMCAxIDE1LjggNC40YzQuNiAyLjggOC40IDYuOCAxMS4xIDExLjUuMS4zLjIuNy4yIDEuMXpNODguMyA3My44TDczLjUgOTMuMmMtMS41IDEuOS0zLjUgMy4xLTUuNyAzLjVoLS4yYy0uNC4xLS44LjEtMS4yLjEtLjYgMC0xLjEtLjEtMS42LS4yLTIuMi0uNC00LjItMS43LTUuNi0zLjVMNDQuMyA3My45Yy0yLTIuNi0yLjUtNS40LTEuNC03LjcuMS0uMS4xLS4yLjItLjIgMS4yLTIgMy41LTMuMiA2LjQtMy4yaDYuNnYtNS43YzAtNi44IDQuNy0xMiAxMC45LTEyIDQuOCAwIDguNSAyLjYgMTAuMyA3LjIuNSAxLjMtLjIgMi43LTEuNSAzLjJzLTIuOC0uMS0zLjMtMS40Yy0xLjEtMi43LTIuOS00LTUuNS00LTMuNSAwLTYgMy02IDd2OC4xYzAgLjUtLjIgMS0uNiAxLjQtLjYuNy0xLjcgMS4xLTIuNiAxLjFoLTguNGMtMS4zIDAtMiAuNC0yLjEuNy0uMi40IDAgMS4zLjkgMi40TDYzLjEgOTBjLjkgMS4yIDIuMSAxLjggMy4zIDEuOHMyLjMtLjYgMy4xLTEuN2wxNC44LTE5LjNjLjktMS4xIDEuMS0yIC45LTIuNC0uMi0uMy0uOS0uNy0yLjEtLjdoLTcuNmMtLjkgMC0xLjctLjUtMi4xLTEuMi0uMy0uNC0uNC0uOC0uNC0xLjMgMC0xLjQgMS4xLTIuNSAyLjUtMi41aDcuNmMzLjEgMCA1LjUgMS4zIDYuNiAzLjVsLjMuN2MuNyAyLjEuMSA0LjYtMS43IDYuOXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4= // @description 《也许同类型中最好用?》系列 - 一个基于 JavaScript 的网盘文件下载地址获取工具,基于【网盘直链下载助手】修改 | 支持 百度网盘/阿里云盘/中国移动云盘/天翼云盘/迅雷云盘/夸克网盘/UC网盘/123云盘 八大网盘 | 开源・自用・去广 | 改界面・添功能・修Bug | 既超越原版,亦是同类中最好用版本!👋 // @description:zh-TW 《也許同類型中最好用?》系列 - 一個基於 JavaScript 的網盤檔案下載地址獲取工具,基於【網盤直鏈下載助手】改編 | 支援 百度網盤/阿里雲盤/中國移動雲盤/天翼雲盤/迅雷雲盤/夸克網盤/UC網盤/123雲盤 八大平台 | 開源・自用・除廣 | 改介面・擴功能・修Bug | 既超越原版,亦是同類中最好用版本!👋 @@ -108,10 +108,9 @@ * @license AGPL-3.0-or-later * @see {@link https://github.com/hmjz100/LinkSwift/ Github 仓库} */ -(function linkSwift() { +(function linkSwift() { // 严格模式,确保代码安全执行 'use strict'; - // unsafeWindow 检测 if (typeof unsafeWindow === 'undefined') { window.unsafeWindow = window; @@ -140,7 +139,10 @@ sversion = (scriptInfo?.version?.toString() || "1.1.0.1"), sicon = (scriptInfo?.icon || "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTAzLjYgMTA3LjRjMy41LTIuMiA4LjktNi4xIDEzLjgtMTIuNXM3LjMtMTIuNSA4LjUtMTYuNWMuNS0xLjcgMi4yLTcuNSAyLjItMTQuNyAwLTEwLjEtMy4zLTI1LjEtMTUuNC0zNi44LTE0LjUtMTQtMzIuMS0xNC4zLTM1LjctMTQuMy04IDAtMTUuNyAxLjktMjIuNiA1LjJDNDQgMjMgMzUuNyAzMS40IDMwLjggNDEuN2MtMS4zIDIuOC00IDQuNy03LjEgNS00IC4zLTcuNSA0LjQtOC45IDkuNi0uNSAxLjktMS42IDMuNS0zLjEgNC43QzQuNCA2Ni44IDAgNzUuNyAwIDg1YzAgNi44IDIuMyAxMy4xIDYuMSAxOC4yIDUuNSA3LjQgMTQuMiAxMi4yIDI0IDEyLjJoNDcuMWM0LjQgMCAxMS0uNSAxOC4zLTMuNSAzLjItMS40IDUuOS0zIDguMS00LjV6IiBmaWxsPSIjQTA5OUYwIi8+PHBhdGggZD0iTTExOS44IDY0LjNjLjEtMTcuMS0xMC40LTI4LTEyLjUtMzAuMUM5NSAyMi4xIDc5LjkgMjEuOCA3Ni45IDIxLjhjLTE3LjYgMC0zMy4zIDEwLjUtMzkuOSAyNi43LS42IDEuMy0xLjggMi4zLTMuNCAyLjNoLS40Yy01LjggMC0xMC42IDQuOC0xMC42IDEwLjd2LjVjMCAxLjQtLjggMi42LTEuOSAzLjNDMTMuNCA2OSA4LjggNzYuOCA4LjggODVjMCAxMi4yIDkuOSAyMi4zIDIyLjIgMjIuM2g0NS4yYzMuNi0uMSAxNy42LS45IDI5LjYtMTIgMi45LTIuOCAxMy45LTEzLjcgMTQtMzF6IiBmaWxsPSIjNTc0QUI4Ii8+PHBhdGggZD0iTTExMC44IDU3LjRsLjIgMy4zYzAgMS4zLTEuMSAyLjQtMi4zIDIuNC0xLjMgMC0yLjMtMS4xLTIuMy0yLjRsLS4xLTIuOHYtLjNjMC0xLjIuOS0yLjIgMi4xLTIuM2guM2MuNyAwIDEuMy4zIDEuNy43LS4yLjEuMy41LjQgMS40em0tMy4zLTEwLjNjMCAxLjItMSAyLjMtMi4yIDIuM2gtLjFjLS44IDAtMS42LS41LTItMS4yLTQuNi04LjMtMTMuMy0xMy41LTIyLjgtMTMuNS0xLjIgMC0yLjMtMS0yLjMtMi4ydi0uMWMwLTEuMiAxLTIuMyAyLjItMi4zaC4xYTMwLjM3IDMwLjM3IDAgMCAxIDE1LjggNC40YzQuNiAyLjggOC40IDYuOCAxMS4xIDExLjUuMS4zLjIuNy4yIDEuMXpNODguMyA3My44TDczLjUgOTMuMmMtMS41IDEuOS0zLjUgMy4xLTUuNyAzLjVoLS4yYy0uNC4xLS44LjEtMS4yLjEtLjYgMC0xLjEtLjEtMS42LS4yLTIuMi0uNC00LjItMS43LTUuNi0zLjVMNDQuMyA3My45Yy0yLTIuNi0yLjUtNS40LTEuNC03LjcuMS0uMS4xLS4yLjItLjIgMS4yLTIgMy41LTMuMiA2LjQtMy4yaDYuNnYtNS43YzAtNi44IDQuNy0xMiAxMC45LTEyIDQuOCAwIDguNSAyLjYgMTAuMyA3LjIuNSAxLjMtLjIgMi43LTEuNSAzLjJzLTIuOC0uMS0zLjMtMS40Yy0xLjEtMi43LTIuOS00LTUuNS00LTMuNSAwLTYgMy02IDd2OC4xYzAgLjUtLjIgMS0uNiAxLjQtLjYuNy0xLjcgMS4xLTIuNiAxLjFoLTguNGMtMS4zIDAtMiAuNC0yLjEuNy0uMi40IDAgMS4zLjkgMi40TDYzLjEgOTBjLjkgMS4yIDIuMSAxLjggMy4zIDEuOHMyLjMtLjYgMy4xLTEuN2wxNC44LTE5LjNjLjktMS4xIDEuMS0yIC45LTIuNC0uMi0uMy0uOS0uNy0yLjEtLjdoLTcuNmMtLjkgMC0xLjctLjUtMi4xLTEuMi0uMy0uNC0uNC0uOC0uNC0xLjMgMC0xLjQgMS4xLTIuNSAyLjUtMi41aDcuNmMzLjEgMCA1LjUgMS4zIDYuNiAzLjVsLjMuN2MuNyAyLjEuMSA0LjYtMS43IDYuOXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4="), mhandler = GM_info.scriptHandler, - mversion = GM_info.version; + mversion = GM_info.version, + globalSleep=500, //延时,统一在1个地方修改方便用,可以考虑做个接口给用户修改,做好限制,别让用户做死 + globalSleepRandSeed=100,//延时随机种子,直接在上面的数值上加 + globalBatchsize=50;//每次处理批次的大小 /* 设置选项 */ // Shell类型(用于curl下载) @@ -225,6 +227,7 @@ * 基础配置集合 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ const config = { base: { @@ -270,8 +273,14 @@ downloadLink: "pan.baidu.com" }, getAccessToken: "https://openapi.baidu.com/oauth/2.0/authorize?response_type=token&scope=basic,netdisk&client_id=IlLqBbU3GjQ0t46TRwFateTprHWl39zF&redirect_uri=oob&confirm_login=0", - getLink: "https://pan.baidu.com/rest/2.0/xpan/multimedia?method=filemetas&dlink=1", - getFiles: "https://pan.baidu.com/rest/2.0/xpan/file?method=list&showempty=1", + getLink:{ + home:"https://pan.baidu.com/rest/2.0/xpan/multimedia?method=filemetas&dlink=1", + share:"" + }, + getFiles:{ + home:"https://pan.baidu.com/rest/2.0/xpan/file?method=list&showempty=1", + share:"" + }, getShareLink: "https://pan.baidu.com/api/sharedownload?channel=chunlei&clienttype=0&web=1&app_id=250528", getShareInfo: "https://pan.baidu.com/share/tplconfig?fields=sign,timestamp&channel=chunlei&web=1&app_id=250528&clienttype=0", getShareFiles: "https://pan.baidu.com/rest/2.0/xpan/share?method=list&showempty=1" @@ -284,8 +293,14 @@ }, $aliyun: { api: { - getLink: "https://api.aliyundrive.com/v2/file/get_download_url", - getShareLink: "https://api.aliyundrive.com/v2/file/get_share_link_download_url" + getLink:{ + home:"https://api.aliyundrive.com/v2/file/get_download_url", + share:"https://api.aliyundrive.com/v2/file/get_share_link_download_url" + }, + getFiles:{ + home:"https://api.aliyundrive.com/adrive/v3/file/list?jsonmask=next_marker%2Citems(name%2Cfile_id%2Cdrive_id%2Ctype%2Csize%2Ccreated_at%2Cupdated_at%2Ccategory%2Cfile_extension%2Cparent_file_id%2Cmime_type%2Cstarred%2Cthumbnail%2Curl%2Cstreams_info%2Ccontent_hash%2Cuser_tags%2Cuser_meta%2Ctrashed%2Cvideo_media_metadata%2Cvideo_preview_metadata%2Csync_meta%2Csync_device_flag%2Csync_flag%2Cpunish_flag%2Cfrom_share_id)", + share:"https://api.aliyundrive.com/adrive/v2/file/list_by_share" + }, }, mount: { home: "[class^=\"header--\"]>[class^=\"actions--\"]", @@ -297,7 +312,14 @@ }, $mcloud: { api: { - getLink: "https://personal-kd-njs.yun.139.com/hcy/file/getDownloadUrl" + getLink:{ + home:"https://personal-kd-njs.yun.139.com/hcy/file/getDownloadUrl", + share:"" + }, + getFiles:{ + home:"https://personal-kd-njs.yun.139.com/hcy/file/list", + share:"" + }, }, mount: { home: ".top_button", @@ -307,7 +329,14 @@ $tcloud: { api: { getAccessToken: "https://api.cloud.189.cn/open/oauth2/ssoH5.action", - getLink: "https://api.cloud.189.cn/open/file/getFileDownloadUrl.action" + getLink:{ + home:"https://api.cloud.189.cn/open/file/getFileDownloadUrl.action", + share:"" + }, + getFiles:{ + home:"https://cloud.189.cn/api/open/file/listFiles.action", + share:"https://cloud.189.cn/api/open/share/listShareDir.action" + }, }, mount: { home: "[class*=\"FileHead_file-head-left\"]", @@ -319,7 +348,14 @@ mirror: [ "vod0007-h05-vip-lixian.xunlei.com", "vod0008-h05-vip-lixian.xunlei.com", "vod0009-h05-vip-lixian.xunlei.com", "vod0010-h05-vip-lixian.xunlei.com", "vod0011-h05-vip-lixian.xunlei.com", "vod0012-h05-vip-lixian.xunlei.com", "vod0013-h05-vip-lixian.xunlei.com", "vod0014-h05-vip-lixian.xunlei.com", "vod0067-aliyun08-vip-lixian.xunlei.com", "vod0254-aliyun08-vip-lixian.xunlei.com", "vod0255-aliyun08-vip-lixian.xunlei.com", "vod0256-aliyun08-vip-lixian.xunlei.com", "vod0257-aliyun08-vip-lixian.xunlei.com", "vod0258-aliyun08-vip-lixian.xunlei.com", "vod0259-aliyun08-vip-lixian.xunlei.com", "vod0260-aliyun08-vip-lixian.xunlei.com", "vod0261-aliyun08-vip-lixian.xunlei.com", "vod0262-aliyun08-vip-lixian.xunlei.com", "vod0263-aliyun08-vip-lixian.xunlei.com", "vod0264-aliyun08-vip-lixian.xunlei.com", "vod0265-aliyun08-vip-lixian.xunlei.com", "vod0266-aliyun08-vip-lixian.xunlei.com", "vod0267-aliyun08-vip-lixian.xunlei.com", "vod0554-aliyun06-vip-lixian.xunlei.com", "vod0555-aliyun06-vip-lixian.xunlei.com", "vod0556-aliyun06-vip-lixian.xunlei.com", "vod0680-aliyun08-vip-lixian.xunlei.com", "vod0681-aliyun08-vip-lixian.xunlei.com", "vod0682-aliyun08-vip-lixian.xunlei.com", "vod0683-aliyun08-vip-lixian.xunlei.com", "vod0684-aliyun08-vip-lixian.xunlei.com", "vod0685-aliyun08-vip-lixian.xunlei.com", "vod0686-aliyun08-vip-lixian.xunlei.com", "vod0687-aliyun08-vip-lixian.xunlei.com", "vod0688-aliyun08-vip-lixian.xunlei.com", "vod0689-aliyun08-vip-lixian.xunlei.com", "vod0690-aliyun08-vip-lixian.xunlei.com", "vod0724-aliyun08-vip-lixian.xunlei.com", "vod0725-aliyun08-vip-lixian.xunlei.com", "vod0726-aliyun08-vip-lixian.xunlei.com", "vod0727-aliyun08-vip-lixian.xunlei.com", "vod0728-aliyun08-vip-lixian.xunlei.com", "vod0075.aliyun06.vip.lixian.xunlei.com", "vod0076.aliyun06.vip.lixian.xunlei.com", "vod0077.aliyun06.vip.lixian.xunlei.com", "vod0779-aliyun04-vip-lixian.xunlei.com", "vod0078.aliyun06.vip.lixian.xunlei.com", "vod0780-aliyun04-vip-lixian.xunlei.com", "vod0781-aliyun04-vip-lixian.xunlei.com", "vod0079.aliyun06.vip.lixian.xunlei.com", "vod0080.aliyun06.vip.lixian.xunlei.com", "vod0117.aliyun04.vip.lixian.xunlei.com", "vod0118.aliyun04.vip.lixian.xunlei.com", "vod0119.aliyun04.vip.lixian.xunlei.com", "vod1284-aliyun06-vip-lixian.xunlei.com", "vod1285-aliyun06-vip-lixian.xunlei.com", "vod1363-aliyun06-vip-lixian.xunlei.com", "vod1371-aliyun06-vip-lixian.xunlei.com", "vod1372-aliyun06-vip-lixian.xunlei.com", "vod1426-aliyun06-vip-lixian.xunlei.com", "vod1427-aliyun06-vip-lixian.xunlei.com", "vod1428-aliyun06-vip-lixian.xunlei.com", "vod1429-aliyun06-vip-lixian.xunlei.com", "vod1442-aliyun06-vip-lixian.xunlei.com", "vod1443-aliyun06-vip-lixian.xunlei.com", "vod1444-aliyun06-vip-lixian.xunlei.com", "vod1445-aliyun06-vip-lixian.xunlei.com", "vod1446-aliyun06-vip-lixian.xunlei.com", "vod1447-aliyun06-vip-lixian.xunlei.com", "vod1469-aliyun06-vip-lixian.xunlei.com", "vod1470-aliyun06-vip-lixian.xunlei.com", "vod1471-aliyun06-vip-lixian.xunlei.com", "vod1489-aliyun06-vip-lixian.xunlei.com", "vod1490-aliyun06-vip-lixian.xunlei.com", "vod1491-aliyun06-vip-lixian.xunlei.com", "vod1492-aliyun06-vip-lixian.xunlei.com", "vod1493-aliyun06-vip-lixian.xunlei.com", "vod0215.aliyun06.vip.lixian.xunlei.com", "vod0216.aliyun06.vip.lixian.xunlei.com", "vod0217.aliyun06.vip.lixian.xunlei.com", "vod0218.aliyun06.vip.lixian.xunlei.com", "vod0219.aliyun06.vip.lixian.xunlei.com", "vod0220.aliyun06.vip.lixian.xunlei.com", "vod0241.aliyun08.vip.lixian.xunlei.com", "vod0244.aliyun08.vip.lixian.xunlei.com", "vod0251.aliyun08.vip.lixian.xunlei.com", "vod0252.aliyun08.vip.lixian.xunlei.com", "vod0253.aliyun08.vip.lixian.xunlei.com", "vod0254.aliyun08.vip.lixian.xunlei.com", "vod0255.aliyun08.vip.lixian.xunlei.com", "vod0256.aliyun08.vip.lixian.xunlei.com", "vod0257.aliyun08.vip.lixian.xunlei.com", "vod0260.aliyun08.vip.lixian.xunlei.com", "vod0261.aliyun08.vip.lixian.xunlei.com", "vod0262.aliyun08.vip.lixian.xunlei.com", "vod0263.aliyun08.vip.lixian.xunlei.com", "vod0264.aliyun08.vip.lixian.xunlei.com", "vod0265.aliyun08.vip.lixian.xunlei.com", "vod0266.aliyun08.vip.lixian.xunlei.com", "vod0267.aliyun08.vip.lixian.xunlei.com", "vod3379-aliyun04-vip-lixian.xunlei.com", "vod3380-aliyun04-vip-lixian.xunlei.com", "vod3429-aliyun04-vip-lixian.xunlei.com", "vod3458-aliyun04-vip-lixian.xunlei.com", "vod3459-aliyun04-vip-lixian.xunlei.com", "vod3496-aliyun04-vip-lixian.xunlei.com", "vod3497-aliyun04-vip-lixian.xunlei.com", "vod3498-aliyun04-vip-lixian.xunlei.com", "vod3499-aliyun04-vip-lixian.xunlei.com", "vod3500-aliyun04-vip-lixian.xunlei.com", "vod3501-aliyun04-vip-lixian.xunlei.com", "vod3522-aliyun04-vip-lixian.xunlei.com", "vod3523-aliyun04-vip-lixian.xunlei.com", "vod3533-aliyun04-vip-lixian.xunlei.com", "vod3534-aliyun04-vip-lixian.xunlei.com", "vod3535-aliyun04-vip-lixian.xunlei.com", "vod3536-aliyun04-vip-lixian.xunlei.com", "vod3549-aliyun04-vip-lixian.xunlei.com", "vod3550-aliyun04-vip-lixian.xunlei.com", "vod3551-aliyun04-vip-lixian.xunlei.com", "vod3552-aliyun04-vip-lixian.xunlei.com", "vod3553-aliyun04-vip-lixian.xunlei.com", "vod3554-aliyun04-vip-lixian.xunlei.com", "vod3555-aliyun04-vip-lixian.xunlei.com", "vod0551.aliyun06.vip.lixian.xunlei.com", "vod0552.aliyun06.vip.lixian.xunlei.com", "vod0553.aliyun06.vip.lixian.xunlei.com", "vod0554.aliyun06.vip.lixian.xunlei.com", "vod0555.aliyun06.vip.lixian.xunlei.com", "vod0556.aliyun06.vip.lixian.xunlei.com", "vod0686.aliyun08.vip.lixian.xunlei.com", "vod0687.aliyun08.vip.lixian.xunlei.com", "vod0688.aliyun08.vip.lixian.xunlei.com", "vod0689.aliyun08.vip.lixian.xunlei.com", "vod0724.aliyun08.vip.lixian.xunlei.com", "vod0725.aliyun08.vip.lixian.xunlei.com", "vod0726.aliyun08.vip.lixian.xunlei.com", "vod0727.aliyun08.vip.lixian.xunlei.com", "vod0728.aliyun08.vip.lixian.xunlei.com", "vod0759.aliyun04.vip.lixian.xunlei.com", "vod0760.aliyun04.vip.lixian.xunlei.com", "vod0769.aliyun04.vip.lixian.xunlei.com", "vod0770.aliyun04.vip.lixian.xunlei.com", "vod0771.aliyun04.vip.lixian.xunlei.com", "vod0772.aliyun04.vip.lixian.xunlei.com", "vod0773.aliyun04.vip.lixian.xunlei.com", "vod0774.aliyun04.vip.lixian.xunlei.com", "vod0775.aliyun04.vip.lixian.xunlei.com", "vod0776.aliyun04.vip.lixian.xunlei.com", "vod0777.aliyun04.vip.lixian.xunlei.com", "vod0778.aliyun04.vip.lixian.xunlei.com", "vod0779.aliyun04.vip.lixian.xunlei.com", "vod0780.aliyun04.vip.lixian.xunlei.com", "vod0781.aliyun04.vip.lixian.xunlei.com", "vod3522.aliyun04.vip.lixian.xunlei.com", "vod3523.aliyun04.vip.lixian.xunlei.com", "vod3533.aliyun04.vip.lixian.xunlei.com", "vod3535.aliyun04.vip.lixian.xunlei.com", "vod3550.aliyun04.vip.lixian.xunlei.com", "vod3551.aliyun04.vip.lixian.xunlei.com", "vod3552.aliyun04.vip.lixian.xunlei.com", "vod3553.aliyun04.vip.lixian.xunlei.com", "vod3554.aliyun04.vip.lixian.xunlei.com", "vod3555.aliyun04.vip.lixian.xunlei.com" ], - getLink: "https://api-pan.xunlei.com/drive/v1/files/" + getLink:{ + home:"https://api-pan.xunlei.com/drive/v1/files/", + share:"" + }, + getFiles:{ + home:"https://api-pan.xunlei.com/drive/v1/files?", + share:"https://api.aliyundrive.com/adrive/v2/file/list_by_share" + }, }, mount: { home: "[class^=\"FileMenu__menu--\"]", @@ -331,7 +367,14 @@ ua: { downloadLink: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch" }, - getLink: "https://drive.quark.cn/1/clouddrive/file/download?pr=ucpro&fr=pc" + getLink:{ + home:"https://drive.quark.cn/1/clouddrive/file/download?pr=ucpro&fr=pc", + share:"" + }, + getFiles:{ + home:"https://drive.quark.cn/1/clouddrive/file/sort?pr=ucpro&fr=pc", + share:"" + } }, mount: { home: ".btn-operate .btn-main", @@ -341,9 +384,18 @@ $uc: { api: { ua: { - downloadLink: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) uc-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch" + //TODO 为啥是夸克的?怕你写错改了下 + // downloadLink: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) uc-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch" + downloadLink: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0" + }, + getLink:{ + home:"https://pc-api.uc.cn/1/clouddrive/file/download?pr=UCBrowser&fr=pc", + share:"" + }, + getFiles:{ + home:"https://pc-api.uc.cn/1/clouddrive/file/sort", + share:"" }, - getLink: "https://pc-api.uc.cn/1/clouddrive/file/download?pr=UCBrowser&fr=pc" }, mount: { home: ".btn-operate .btn-main", @@ -352,8 +404,14 @@ }, $123pan: { api: { - getLink: "https://www.123pan.com/api/file/download_info", - getShareLink: "https://www.123pan.com/api/share/download/info" + getLink:{ + home:"https://www.123pan.com/api/file/download_info", + share:"https://www.123pan.com/api/share/download/info" + }, + getFiles:{ + home:"https://www.123pan.com/b/api/file/list/new", + share:"https://www.123pan.com/b/api/share/get" + }, }, mount: { home: "main.ant-layout-content .site-layout-background .homeClass .wenserh", @@ -366,6 +424,7 @@ * 基础工具集合 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let base = { /** @@ -418,19 +477,21 @@ * 发送链接到 RPC 下载器 * @author 油小猴 * @author hmjz100 + * @author KanouAo * @description RPC 下载必备 * @param {string} link - 下载链接 * @param {string} filename - 文件名 * @param {string} [header] - 自定义请求头参数(可选) + * @param {string} path - 下载路径 * @returns {Promise<'success'|'fail'>} 发送态结果 */ - async sendLinkToRPC(link, filename, header) { + async sendLinkToRPC(link, filename, path, header) { let rpc = { domain: base.getValue('setting_rpc_domain'), port: base.getValue('setting_rpc_port'), path: base.getValue('setting_rpc_path'), token: base.getValue('setting_rpc_token'), - dir: base.getValue('setting_rpc_dir'), + dir: base.getValue('setting_rpc_dir')+path, }; let url = `${rpc.domain}:${rpc.port}${rpc.path}`; @@ -1313,6 +1374,14 @@ return new Promise(resolve => setTimeout(resolve, time)); }, + /** + * 延时执行(随机) + * @author KanouAo + */ + customSleep(){ + base.sleep(globalSleep+Math.random()*globalSleepRandSeed); + }, + /** * 判断版本号新旧 * @author hmjz100 @@ -2934,8 +3003,13 @@ * 百度网盘 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let $baidu = { + cnt:0,//计数器,用于控制随机延迟 + fileCount:0,//文件计数 + accessToken:"", + _getExtra() { let seKey = decodeURIComponent(base.getCookie('BDCLND')); return '{' + '"sekey":"' + seKey + '"' + "}"; @@ -3257,7 +3331,7 @@ target.prepend(base.createLoading()); let BDUSS = $baidu.getBDUSS(); - let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename, [`User-Agent: ${config.$baidu.api.ua.downloadLink}`, `Cookie: BDUSS=${BDUSS}`]); + let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename,e.currentTarget.dataset.path, [`User-Agent: ${config.$baidu.api.ua.downloadLink}`, `Cookie: BDUSS=${BDUSS}`]); if (res === 'success') { $('.listener-rpc-task').show(); target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow"); @@ -3591,6 +3665,93 @@ }) }, + async getFilesByOnce(file,params){ + try { + let res; + if(page=="home"||page=="main"){ + //我找的链接获取文件,1页100个,这个能获取文件夹所有文件我就直接用了 + //TODO path属性被占用,原变量就请求下载时用一次,就 path 改成 _path + let url = `${config.$baidu.api.getFiles.home}&dir=${encodeURIComponent(file._path)}&access_token=${this.accessToken}`; + res = await base.get(url, { "User-Agent": config.$baidu.api.ua.downloadLink}); + }else if(page=="share"){ + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error(`提示:
文件获取请求失败`); + } + }, + + async getFileUrlByOnce(file,params) { + try { + let res; + if(page=="home"||page=="main"){ + let url = `${config.$baidu.api.getLink.home}&fsids=${params.fsids}&access_token=${this.accessToken}`; + res = await base.get(url, { "User-Agent": config.$baidu.api.ua.downloadLink }); + }else if(page=="share"){ + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error(`提示:
文件下载链接请求失败`); + } + }, + + async fetchAllPages(file) { + //分页获取文件夹内文件数据 + let files=[];//文件数据列表 + let res; + + //随机停顿防反爬 + this.cnt++; + if (this.cnt >= 50+Math.random()*10) { + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + this.cnt = 0; + } + + //发送请求 + let params={"_path":file._path}; + res=await this.getFilesByOnce(file,params); + + //请求失败 + if(res?.errno){ + return message.error(`提示:
请求失败,失败码: ${res?.errno} 失败信息:${res.errmsg}`); + } + + //请求成功 + files=files.concat(res.list)//注意变量名 + return files; + }, + + async fetchFiles(file,pNode) { + //节点 + let fileNode={ + name:file.server_filename,//注意变量名 + path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + children:[] + } + pNode.children.push(fileNode); + file._path=file.path;//TODO path属性被占用,原变量就请求下载时有用就 path 改成 _path + file.path=fileNode.path; + + //判断是否文件夹 + if (!file?.isdir) {//注意变量名 + this.fileCount++; + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
`); + return file; + } + + //文件夹内文件遍历 + let files=[]; + for (let f of await this.fetchAllPages(file)) { + files= files.concat(await this.fetchFiles(f,fileNode)); + } + return files; + }, + async getLink() { Swal.fire({ showConfirmButton: false, @@ -3615,9 +3776,10 @@ }); // 获取选择的文件列表 selectList = this.getSelectedList(); - let BDUSS = this.getBDUSS(), accessToken = (base.getValue('baidu_access_token') || await $baidu.getToken()); + let BDUSS = this.getBDUSS(); + this.accessToken = (base.getValue('baidu_access_token') || await $baidu.getToken()); - if (!accessToken) { + if (!this.accessToken) { message.info('提示:
稍后请在新标签页中授权助手哦~'); base.deleteValue('baidu_access_token'); setTimeout(() => { @@ -3626,7 +3788,7 @@ let interval = setInterval(function () { if (!!base.getValue('baidu_access_token')) { clearInterval(interval); - accessToken = base.getValue('baidu_access_token') + this.accessToken = base.getValue('baidu_access_token') } attempts++; if (attempts > 120) { @@ -3733,62 +3895,52 @@ } return await getBDUSS(); } + + if(page === 'home' || page === 'main'){ + }else if(page=="share"){ + //分享页面所需的变量 + }else{ + return message.error('提示:
页面错误~'); + } + this.cnt=0; //计数器,用于控制随机延迟 + this.fileCount=0;//文件计数 + let fileList=[];//文件列表,只有文件没有文件夹 + let fileTree = { name: "root", path: ``, children: [] }; // 文件目录树 if (page === 'home' || page === 'main') { if (!selectList.length) { return message.error('提示:
先勾选要下载的文件哦~'); } - let cnt = 0; - let processed = selectList.filter(f => !f.isdir).length; - - async function fetchFiles(dirs) { - let files = []; - - for (let dir of dirs) { - doc.find('.loading-popup .loading-title').html(`文件获取中`); - let url = `${config.$baidu.api.getFiles}&dir=${encodeURIComponent(dir.path)}&access_token=${accessToken}`; - let res = await base.get(url, { "User-Agent": config.$baidu.api.ua.downloadLink }); - cnt++; - - if (res?.list?.length && (res.errno === 0 || res.errmsg === "succ")) { - let subFiles = res.list.filter(f => !f.isdir); - - processed += subFiles.length; - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} 个文件~
${dir.path}
`); - - files = files.concat(subFiles); - if (res.list.some(f => f.isdir)) { - files = files.concat(await fetchFiles(res.list.filter(f => f.isdir))); - } - } - if (cnt >= 50) { - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} 个文件~
休息 3 秒...
`); - await base.sleep(3000); - cnt = 0; - } - } - return files; + //获取文件目录结构(递归) + for (let file of selectList) { + //遍历选中文件 + //统一变量名 + fileList = fileList.concat(await this.fetchFiles(file,fileTree)); } - let files = selectList.filter(f => !f.isdir); - - if (selectList.some(f => f.isdir)) { - files = files.concat(await fetchFiles(selectList.filter(f => f.isdir))); - } - if (!files.length) { + //文件夹为空 + if (!fileList.length) { return message.error('提示:
文件夹是空的哦~'); } + //列表转字典 + const filesDict = fileList.reduce((acc, file) => { + acc[file.fs_id] = file; + return acc; + }, {}); + + //获取下载链接 doc.find('.loading-popup .loading-title').html(`链接获取中`); doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); - - let fidList = files.map(f => f.fs_id); - let batchSize = 100; + let fidList = fileList.map(f => f.fs_id); let linkList = []; - for (let i = 0; i < fidList.length; i += batchSize) { - let url = `${config.$baidu.api.getLink}&fsids=${encodeURIComponent(JSON.stringify(fidList.slice(i, i + batchSize)))}&access_token=${accessToken}`; - let res = await base.get(url, { "User-Agent": config.$baidu.api.ua.downloadLink }); + let batchSize = globalBatchsize+Math.ceil(Math.random()*5); + let i=0; + while (i < fileList.length){ + //url请求 + let params={"fsids":encodeURIComponent(JSON.stringify(fidList.slice(i, i + batchSize)))}; + let res = await this.getFileUrlByOnce(undefined,params); if (res?.list?.length && (res.errno === 0 || res.errmsg === "succ")) { linkList = linkList.concat(res.list); @@ -3811,10 +3963,22 @@ return message.error('提示:
获取下载链接失败,刷新网页后再试试吧~'); } } - await base.sleep(1000); + + // 每次处理完一个批次后,等待 + if (i + batchSize < fileList.length){ + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${i} / ${fileList.length} 个链接,请耐心等待哦~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + }; + + //累加批次 + i += batchSize; + batchSize = globalBatchsize+Math.ceil(Math.random()*5) } if (linkList.length) { + for (let i = 0; i < linkList.length; i++) {//重写下载路径 + linkList[i].path = filesDict[linkList[i].fs_id].path; + } base.showMainDialog(config.base.dom.button[mode].title, this.generateDom(linkList), config.base.dom.button[mode].footer); } else { return message.error('提示:
获取下载链接失败,刷新网页后再试试吧~'); @@ -3835,6 +3999,7 @@ if (v.isdir === 1) return; let filename = v.server_filename || v.filename; let size = base.sizeFormat(v.size); + let dpath=v.path; if (!v?.dlink || !v?.dlink.includes("http")) { content += `
${filename}
@@ -3880,9 +4045,19 @@ } if (mode === 'rpc') { content += `
-
${filename}
- -
`; +
${filename}
+ +
`; } if (mode === 'curl') { let alink = base.convertLinkToCurl(dlink, filename, `-A "${config.$baidu.api.ua.downloadLink}" -b "BDUSS=${BDUSS}"`); @@ -4134,8 +4309,13 @@ * 阿里云盘 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let $aliyun = { + cnt:0,//计数器,用于控制随机延迟 + fileCount:0,//文件计数 + authorization:"", + addPageListener() { /* 防止代码因其他原因被执行多次 @@ -4335,7 +4515,7 @@ target.find('.pl-loading').remove(); target.prepend(base.createLoading()); - let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename, [`Referer: https://${location.host}/`]); + let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename,e.currentTarget.dataset.path, [`Referer: https://${location.host}/`]); if (res === 'success') { $('.listener-rpc-task').show(); target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow"); @@ -4383,26 +4563,6 @@ }, true); }, - async getFileUrlByOnce(d, f) { - let authorization = `${base.getStorage('token').token_type} ${base.getStorage('token').access_token}`; - let res = await base.post(config.$aliyun.api.getLink, { - drive_id: d, - file_id: f - }, { - authorization, - "content-type": "application/json;charset=utf-8", - "referer": "https://www.aliyundrive.com/", - "x-canary": "client=windows,app=adrive,version=v6.0.0" - }); - if (res.code === 'AccessTokenInvalid') { - return message.error('提示:
访问令牌过期了,请刷新网页后再试'); - } - if (res.url) { - return res.url; - } - return ''; - }, - greenerPage() { base.waitForKeyElements('[class*="share-list-banner"]', function (tag) { tag.fadeOut(); @@ -4460,6 +4620,110 @@ }) }, + async getFilesByOnce(file,params){ + let res; + if(page=="home"||page=="main"){ + res = await base.post(config.$aliyun.api.getFiles.home, { + "all": false,//TODO 取得全部文件,默认是false,但测试时突然取不到了,不知道是不是阿里改了 + "drive_id": params.drive_id, + "fields": "*", + "limit": 100,//TODO 第一次20,之后100。省事先统一100看看,以后有问题再改 + "marker":params.marker, + "order_by": "name", + "order_direction": "DESC", + "parent_file_id": params.parent_file_id, + "url_expire_sec": 14400//4小时后链接失效 + }, { + "authorization":this.authorization, + "content-type": "application/json", + "referer": "https://www.aliyundrive.com/", + "x-canary": "client=windows,app=adrive,version=v6.0.0" + } + ); + }else if(page=="share"){ + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + }, + + async getFileUrlByOnce(d, f) { + let res = await base.post(config.$aliyun.api.getLink.home, { + drive_id: d, + file_id: f + }, { + "authorization":this.authorization, + "content-type": "application/json;charset=utf-8", + "referer": "https://www.aliyundrive.com/", + "x-canary": "client=windows,app=adrive,version=v6.0.0" + }); + if (res.code === 'AccessTokenInvalid') { + return message.error('提示:
访问令牌过期了,请刷新网页后再试'); + } + if (res.url) { + return res.url; + } + return ''; + }, + + async fetchAllPages(file) { + //分页获取文件夹内文件数据 + let files=[];//文件数据列表 + let marker='';//文件夹内下一段数据的令牌 + let res; + + while (true) { + //随机停顿防反爬 + this.cnt++; + if (this.cnt >= 50+Math.random()*10) { + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + this.cnt = 0; + } + + //发送请求 + let param={"drive_id":file.drive_id,"parent_file_id":file.file_id,"marker":marker}; + res=await this.getFilesByOnce(file,param); + + //请求失败 + if(!res?.items){ + return message.error(`提示:
请求失败`); + } + + //请求成功 + files=files.concat(res.items) + marker=res.next_marker; + if (!marker) + break; + } + return files; + }, + + async fetchFiles(file,pNode) { + //节点 + let fileNode={ + name:file.name,//注意变量名 + path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + children:[] + } + pNode.children.push(fileNode); + file.path=fileNode.path; + + //判断是否文件夹 + if (file?.type!='folder') {//注意变量名 + this.fileCount++; + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
`); + return file; + } + + //文件夹内文件遍历 + let files=[]; + for (let f of await this.fetchAllPages(file)) { + files= files.concat(await this.fetchFiles(f,fileNode)); + } + return files; + }, + async getLink() { Swal.fire({ showConfirmButton: false, @@ -4486,55 +4750,81 @@ if (selectList.length === 0) { return message.error('提示:
请勾选要下载的文件哦~'); } - if (this.isOnlyFolder()) { - return message.error('提示:
请打开文件夹后再勾选文件~'); + + if(page === 'home'){ + }else if(page=="share"){ + //分享页面所需的变量 + }else{ + return message.error('提示:
页面错误~'); } + + this.authorization= `${base.getStorage('token').token_type} ${base.getStorage('token').access_token}`; + this.cnt=0; //计数器,用于控制随机延迟 + this.fileCount=0;//文件计数 + let fileList=[];//文件列表,只有文件没有文件夹 + let fileTree = { name: "root", path: ``, children: [] }; // 文件目录树 if (page === 'home') { - selectList = selectList.filter(item => item.type === 'file') - let batchSize = 15; - let processed = 0; + //获取文件目录结构(递归) + for (let file of selectList) { + //遍历选中文件 + //统一变量名 + file.drive_id=file.drive_id||file.driveId;//driveId、fileId全统一成drive_id、file_id + file.file_id=file.file_id||file.fileId; + fileList = fileList.concat(await this.fetchFiles(file,fileTree)); + } + //文件夹为空 + if (!fileList.length) { + return message.error('提示:
文件夹是空的哦~'); + } + + //获取下载链接 doc.find('.loading-popup .loading-title').html(`链接获取中`); doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); - for (let i = 0; i < selectList.length; i += batchSize) { - // 当前批次文件 - let batch = selectList.slice(i, i + batchSize); + let batchSize = 15+Math.ceil(Math.random()*5); + let i=0; + while (i < fileList.length){ + // 分批获取链接 + let batch = fileList.slice(i, i + batchSize); // 过滤掉已有 URL 的文件 - let noUrlSelectList = batch.filter(v => !Boolean(v.url)); - let hasUrlSelectList = batch.filter(v => Boolean(v.url)); - let queue = []; + let noUrlList = batch.filter(v => !Boolean(v.url)) // 为没有 URL 的文件生成请求队列 - noUrlSelectList.forEach((item) => { - queue.push(this.getFileUrlByOnce(item.driveId, item.fileId) - .then(val => { - processed++; - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); - return val; - })); - }); - - hasUrlSelectList.forEach((item) => { - processed++; - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); - }); + let queue = []; + noUrlList.forEach( (item, index) => { + queue.push(this.getFileUrlByOnce(item.drive_id, item.file_id));//浏览器千万别在这附近下断点,不然直接过不去,卡在这的。 + } + ); // 等待本批次的请求结果 - const res = await Promise.all(queue); - res.forEach((val, index) => { - noUrlSelectList[index].url = val; - }); + let res = await Promise.all(queue); + res.forEach( (val, index) => { + noUrlList[index].url = val; + } + ); + + // 每次处理完一个批次后,等待 + if (i + batchSize < fileList.length){ + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${i} / ${fileList.length} 个链接,请耐心等待哦~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + }; - // 每次处理完一个批次后,等待 1 秒 - await base.sleep(1000); + //累加批次 + i += batchSize; + batchSize = 15+Math.ceil(Math.random()*5) } } else { return message.error('提示:
页面错误~'); } - let html = this.generateDom(selectList); - base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); + //显示下载GUI + if (fileList.length) { + let html = this.generateDom(fileList); + base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); + } else { + return message.error('提示:
获取下载链接失败,刷新网页后再试试吧~'); + } }, generateDom(list) { @@ -4550,6 +4840,7 @@ let did = v.driveId; let size = base.sizeFormat(v.size); let dlink = v.downloadUrl || v.url; + let dpath=v.path; if (!dlink || !dlink.includes("http")) { content += `
${filename}
@@ -4584,12 +4875,23 @@
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; - } + } if (mode === 'rpc') { + dlink=dlink.replace(' ', '%20')//TODO 阿里的为啥要加这个? content += `
-
${filename}
- -
`; +
${filename}
+ + `; } if (mode === 'curl') { let alink = base.convertLinkToCurl(dlink, filename, `&refer=${encodeURIComponent(`https://${location.host}/`)}`); @@ -4691,8 +4993,13 @@ * 中国移动云盘 / 和彩云 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let $mcloud = { + cnt:0,//计数器,用于控制随机延迟 + fileCount:0,//文件计数 + authorization:"",//cookies + addPageListener() { /* 防止代码因其他原因被执行多次 @@ -4862,7 +5169,7 @@ target.find('.pl-loading').remove(); target.prepend(base.createLoading()); - let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename); + let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename,e.currentTarget.dataset.path); if (res === 'success') { $('.listener-rpc-task').show(); target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow"); @@ -4995,6 +5302,61 @@ return md5(A + l).toUpperCase(); }, + async getFilesByOnce(item,params){ + try { + let res; + if(page=="home"){ + let body = { + "pageInfo": { + "pageSize": 100, + "pageCursor": params.pageCursor, + }, + "orderBy": "updated_at", + "orderDirection": "DESC", + "parentFileId": item.fileId, + "imageThumbnailStyleList": [ + "Small", + "Large" + ] + }; + let time = new Date(+new Date() + 8 * 3600 * 1000).toJSON().substr(0, 19).replace('T', ' '); + let key = this.getRandomString(16); + let sign = this.getSign(undefined, body, time, key); + res = await base.post(config.$mcloud.api.getFiles.home, body, { + 'Authorization': this.authorization, + 'Caller': 'web', + 'CMS-DEVICE': 'default', + 'Content-Type': "application/json;charset=UTF-8", + 'mcloud-channel': '1000101', + 'mcloud-client': '10701', + 'mcloud-sign': time + "," + key + "," + sign, + 'mcloud-version': '7.14.2', + 'Origin': 'https://yun.139.com', + 'Referer': 'https://yun.139.com/', + 'X-DeviceInfo': '||9|7.14.2|chrome|119.0.0.0|||windows 10||zh-CN|||', + 'X-Huawei-ChannelSrc': '10000034', + 'X-Inner-Ntwk': '2', + 'X-M4C-Caller': 'PC', + 'X-M4C-Src': '10002', + 'X-SvcType': '1', + 'X-Yun-Api-Version': 'v1', + 'X-Yun-App-Channel': '10000034', + 'X-Yun-Channel-Source': '10000034', + 'X-Yun-Client-Info': '||9|7.14.2|chrome|119.0.0.0|||windows 10||zh-CN|||||', + 'X-Yun-Module-Type': '100', + 'X-Yun-Svc-Type': '1' + } + ); + }else if(page=="share"){ + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error(`提示:
文件获取请求失败`); + } + }, + async getFileUrlByOnce(item, index) { try { if (item.downloadUrl) return { @@ -5004,13 +5366,13 @@ if (this.detectPage() === 'home') { let body = { - fileId: item.contentID + fileId: item.fileId } let time = new Date(+new Date() + 8 * 3600 * 1000).toJSON().substr(0, 19).replace('T', ' '); let key = this.getRandomString(16); let sign = this.getSign(undefined, body, time, key); - let res = await base.post(config.$mcloud.api.getLink, body, { + let res = await base.post(config.$mcloud.api.getLink.home, body, { 'Authorization': base.getCookie('authorization'), 'Caller': 'web', 'CMS-DEVICE': 'default', @@ -5071,6 +5433,64 @@ }; } }, + + async fetchAllPages(file) { + //分页获取文件夹内文件数据 + let files=[];//文件数据列表 + let nextPageCursor='';//文件夹内下一段数据的令牌 + let res; + while (true) { + //随机停顿防反爬 + this.cnt++; + if (this.cnt >= 50+Math.random()*10) { + doc.find('.loading-popup .swal2-html-container').html(`
正遍历到的大型文件夹已获取 ${files.length} 个文件~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + this.cnt = 0; + } + + //发送请求 + let params={"parent_file_id":file.fileId,"pageCursor":nextPageCursor}; + res=await this.getFilesByOnce(file,params); + + //请求失败 + if(!res.success){//注意变量名 + return message.error(`提示:
请求失败,失败码: ${res?.code} 失败信息:${res.message}`); + } + + //请求成功 + files=files.concat(res.data.items)//注意变量名 + nextPageCursor=res.data.nextPageCursor; + if (!nextPageCursor) + break; + } + return files; + }, + + async fetchFiles(file,pNode) { + //获取文件目录结构(递归) + //节点 + let fileNode={ + name:file.name,//注意变量名 + path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + children:[] + } + pNode.children.push(fileNode); + file.path=fileNode.path; + + //判断是否文件夹 + if (file?.type!="folder") {//注意变量名 + this.fileCount++; + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
`); + return file; + } + + //文件夹内文件遍历 + let files=[]; + for (let f of await this.fetchAllPages(file)) { + files= files.concat(await this.fetchFiles(f,fileNode)); + } + return files; + }, async getLink() { Swal.fire({ @@ -5098,42 +5518,69 @@ if (selectList.length === 0) { return message.error('提示:
请勾选要下载的文件哦~'); } - if (this.isOnlyFolder()) { - return message.error('提示:
请打开文件夹后再勾选文件~'); - } + this.cnt=0; + this.fileCount=0;//文件计数 + this.authorization = base.getCookie('authorization'); + let fileList=[];//文件列表,只有文件没有文件夹 + let fileTree = { name: "root", path: ``, children: [] }; // 文件目录树 if (page === 'home') { - selectList = selectList.filter(item => item.contentID && item.contentName && item.contentSuffix); - let batchSize = 15; - let processed = 0; + //获取文件目录结构(递归) + for (let file of selectList) { + //遍历选中文件 + //统一变量名 + file.name=file.name||file.catalogName; + file.fileId=file.fileId||file.catalogID; + if(file.catalogID)file.type="folder"; + fileList = fileList.concat(await this.fetchFiles(file,fileTree)); + } + + //文件夹为空 + if (!fileList.length) { + return message.error('提示:
文件夹是空的哦~'); + } + + //获取下载链接 doc.find('.loading-popup .loading-title').html(`链接获取中`); doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); - for (let i = 0; i < selectList.length; i += batchSize) { - let batch = selectList.slice(i, i + batchSize); - let queue = []; + let batchSize = 15+Math.ceil(Math.random()*5); + let i=0; + while (i < fileList.length){ + // 分批获取链接 + let batch = fileList.slice(i, i + batchSize); + // 生成请求队列 + let queue = []; batch.forEach((item, localIndex) => { let globalIndex = i + localIndex; queue.push(this.getFileUrlByOnce(item, globalIndex) .then(val => { - processed++; - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); return val; })); }); + // 等待本批次的请求结果 let res = await Promise.all(queue); res.forEach(val => { - selectList[val.index].downloadUrl = val.downloadUrl; + fileList[val.index].downloadUrl = val.downloadUrl; }); - await base.sleep(1000); + // 每次处理完一个批次后,等待 + if (i + batchSize < fileList.length){ + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${i} / ${fileList.length} 个链接,请耐心等待哦~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + }; + + //累加批次 + i += batchSize; + batchSize = 15+Math.ceil(Math.random()*5) } } else { return message.error('提示:
页面错误~'); } - let html = this.generateDom(selectList); + //显示下载GUI + let html = this.generateDom(fileList); base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); }, @@ -5145,9 +5592,10 @@ let alinkAllText = ''; list.forEach((v, i) => { if (v.dirEtag || v.caName) return; - let filename = v.contentName || v.coName; - let size = base.sizeFormat(v.contentSize || v.coSize); + let filename = v.contentName || v.coName||v.name; + let size = base.sizeFormat(v.contentSize || v.coSize||v.size); let dlink = v.downloadUrl; + let dpath=v.path; if (!dlink || !dlink.includes("http")) { content += `
${filename}
@@ -5183,9 +5631,19 @@ } if (mode === 'rpc') { content += `
-
${filename}
- -
`; +
${filename}
+ +
`; } if (mode === 'curl') { let alink = base.convertLinkToCurl(dlink, filename); @@ -5285,8 +5743,15 @@ * 天翼云盘 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let $tcloud = { + cnt:0,//计数器,用于控制随机延迟 + fileCount:0,//文件计数 + shareId:0,//分享id + accessCode:"",//访问码 + + addPageListener() { /* 防止代码因其他原因被执行多次 @@ -5443,7 +5908,7 @@ target.find('.pl-loading').remove(); target.prepend(base.createLoading()); - let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename); + let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename,e.currentTarget.dataset.path); if (res === 'success') { $('.listener-rpc-task').show(); target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow"); @@ -5529,48 +5994,79 @@ return accessToken; }, + async getFilesByOnce(item,params){ + let res; + try { + if(page=="home"){ + //TODO 我调试过程中res出现过check ip error - curIp=2409::xxxx:xxxx:xxxx:xxxx:xxxx:xxxx, cookiesIp=120.xxx.xxx.xxx,不知是不是抓IP注意下 + let url = `${config.$tcloud.api.getFiles.home}?noCache=${Math.random()}&pageSize=${60}&pageNum=${params.pageNum}&mediaType=0&folderId=${params.id}&iconOption=5&orderBy=lastOpTime&descending=true`; + res= await base.get(url, { + "accept": "application/json;charset=UTF-8", + "sign-type": 1, + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + "priority":"u=1, i", + }); + }else if(page=="share"){//别从自己盘进自己的分享,不然都拿不到自己的密钥(不过也没有这么怪的用户吧……) + let url = `${config.$tcloud.api.getFiles.share}?noCache=${Math.random()}&pageSize=${60}&pageNum=${params.pageNum}&fileId=${params.id}&shareDirFileId=${params.id}&isFolder=true&shareId=${this.shareId}&shareMode=1&iconOption=5&orderBy=lastOpTime&descending=true&accessCode=${this.accessCode}`; + res=await base.get(url, { + "accept": "application/json;charset=UTF-8", + "sign-type": 1, + }); + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error(`提示:
文件获取请求失败`); + } + }, + async getFileUrlByOnce(item, index, token) { try { - if (item.downloadUrl) return { - index, - downloadUrl: item.downloadUrl - }; - let time = Date.now(), - fileId = item.fileId, - o = "AccessToken=" + token + "&Timestamp=" + time + "&fileId=" + fileId, - url = config.$tcloud.api.getLink + '?fileId=' + fileId; - if (item.shareId) { - o = "AccessToken=" + token + "&Timestamp=" + time + "&dt=1&fileId=" + fileId + "&shareId=" + item.shareId; - url += '&dt=1&shareId=' + item.shareId; - } - let sign = md5(o).toString(); - let res = await base.get(url, { - "accept": "application/json;charset=UTF-8", - "sign-type": 1, - "accesstoken": token, - "timestamp": time, - "signature": sign - }); - if (res.res_code === 0) { - return { - index, - downloadUrl: res.fileDownloadUrl - }; - } else if (res.errorCode === 'InvalidSessionKey') { - return { - index, - downloadUrl: '提示:
请先登录网盘~' - }; - } else if (res.res_code === 'ShareNotFoundFlatDir') { - return { - index, - downloadUrl: '提示:
请[转存]文件,之后再👉前往[我的网盘]中下载哦~' - }; - } else { - return { + let res; + if(page=="home"){ + if (item.downloadUrl) return { index, - downloadUrl: '获取下载地址失败,刷新后再试试吧~' + (res.res_code ? res.res_code : "") + downloadUrl: item.downloadUrl }; + let time = Date.now(), + fileId = item.id, + o = "AccessToken=" + token + "&Timestamp=" + time + "&fileId=" + fileId, + url = config.$tcloud.api.getLink.home + '?fileId=' + fileId; + if (this.shareId) { + o = "AccessToken=" + token + "&Timestamp=" + time + "&dt=1&fileId=" + fileId + "&shareId=" + this.shareId; + url += '&dt=1&shareId=' + this.shareId; + } + let sign = md5(o).toString(); + res = await base.get(url, { + "accept": "application/json;charset=UTF-8", + "sign-type": 1, + "accesstoken": token, + "timestamp": time, + "signature": sign + }); + if (res.res_code === 0) { + return { + index, + downloadUrl: res.fileDownloadUrl + }; + } else if (res.errorCode === 'InvalidSessionKey') { + return { + index, + downloadUrl: '提示:
请先登录网盘~' + }; + } else if (res.res_code === 'ShareNotFoundFlatDir') { + return { + index, + downloadUrl: '提示:
请[转存]文件,之后再👉前往[我的网盘]中下载哦~' + }; + } else { + return { + index, + downloadUrl: '获取下载地址失败,刷新后再试试吧~' + (res.res_code ? res.res_code : "") + }; + } } } catch (e) { return { @@ -5579,6 +6075,63 @@ }; } }, + + async fetchAllPages(file) { + //分页获取文件夹内文件数据 + let res; + let files=[];//文件数据列表 + let folders=[];//文件夹数据列表 + let path=`${file.path}/${file.name}`; + // let pageCount=Math.ceil(file.fileCount/60);//总页数 + let pageCount=1;//总页数,先设1,之后根据res返回的值修改//share没有file.fileCount,兼容适配 + for (let pageNum=1;pageNum<=pageCount;pageNum++) { + //随机停顿防反爬 + this.cnt++; + if (this.cnt >= 50+Math.random()*10) { + doc.find('#loadingText').html(`
文件获取中
已获取 ${this.fileCount} 个文件,请耐心等待哦~
>休息 ${globalSleep} 毫秒...
`); + await base.sleep(globalSleep+Math.random()*globalSleepRandSeed); + this.cnt = 0; + } + //发送请求 + let param={"pageNum":pageNum,"id":file.id}; + res=await this.getFilesByOnce(file,param); + + //请求失败 + if(!(res?.res_code==0&&res?.res_message=="成功")){ + return message.error(`提示:
请求失败,失败码: ${res?.res_code} 失败信息:${res.res_message}`); + } + //请求成功 + if(pageCount==1)pageCount=Math.ceil(res.fileListAO.count/60); + files=files.concat(res.fileListAO.fileList);//注意别占用fileList的变量名字 + folders=folders.concat(res.fileListAO.folderList); + this.fileCount+=files.length; + files.forEach(f => {f.path=path;}); + } + return {files,folders}; + }, + + async fetchFiles(file,pNode) { + //获取文件目录结构(递归) + //节点 这的节点树因为不怎么需要就没做完整 + let fileNode={ + name:file.name,//注意变量名 + path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + children:[] + } + pNode.children.push(fileNode); + file.path=fileNode.path; + + //判断是否文件夹 + //这个不用判断是否文件夹,返回的数据已经分开了 + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
`); + + //文件夹内文件遍历 + let { files, folders }=await this.fetchAllPages(file); + for (let f of folders) { + files= files.concat(await this.fetchFiles(f,fileNode)); + } + return files; + }, async getLink() { Swal.fire({ @@ -5606,10 +6159,47 @@ if (selectList.length === 0) { return message.error('提示:
请勾选要下载的文件哦~'); } - if (this.isOnlyFolder()) { - return message.error('提示:
请打开文件夹后再勾选文件~'); + + if(page=="home"){ + this.shareId=0; + this.accessCode=""; + }else if(page=="share"){ + //分享页面所需的变量 + this.shareId=selectList[0].shareId;//分享内容的id + let share_link=location.search.substring(6);//分享的链接,地址带上访问码也可比如code=MFNRJ3bAfe6r(访问码:9lik)也太怪了吧? + const percentIndex = share_link.indexOf('%'); + if (percentIndex !== -1) share_link = share_link.substring(0, percentIndex); + this.accessCode=base.getCookie(`share_${share_link}`);//访问密钥,在cookie里有,但不同的分享的密钥都堆一起了 + }else{ + return message.error('提示:
页面错误~'); } - selectList = selectList.filter(item => !item.isFolder) + + this.cnt=0; + this.fileCount=0;//文件计数 + let fileList=[];//文件列表,只有文件没有文件夹 + let fileTree = { name: "root", path: ``, children: [] }; // 文件目录树 + //获取文件目录结构(递归) + for (let file of selectList) { + //遍历选中文件 + //统一变量名 + file.name=file.name||file.fileName; + file.id=file.id||file.fileId; + file.size=file.size||file.fileSize; + if(!file?.isFolder){//非文件夹 + fileList = fileList.concat(file); + this.fileCount++; + continue; + } + file.fileCount=file.fileData; + fileList = fileList.concat(await this.fetchFiles(file,fileTree)); + } + + //文件夹为空 + if (!fileList.length) { + return message.error('提示:
文件夹是空的哦~'); + } + + //获取下载链接 doc.find('.loading-popup .loading-title').html(`令牌获取中`); doc.find('.loading-popup .swal2-html-container').html(`
正在获取状态~
`); let token = base.getStorage('accessToken') || await this.getToken(); @@ -5618,34 +6208,42 @@ } doc.find('.loading-popup .loading-title').html(`令牌获取中`); doc.find('.loading-popup .swal2-html-container').html(`
获取缓存成功~
`); - - let batchSize = 15; - let processed = 0; doc.find('.loading-popup .loading-title').html(`链接获取中`); doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); - for (let i = 0; i < selectList.length; i += batchSize) { - let batch = selectList.slice(i, i + batchSize); - let queue = []; + let batchSize = globalBatchsize+Math.ceil(Math.random()*5); + let i=0; + while (i < fileList.length){ + // 分批获取链接 + let batch = fileList.slice(i, i + batchSize); + // 生成请求队列 + let queue = []; batch.forEach((item, localIndex) => { let globalIndex = i + localIndex; queue.push(this.getFileUrlByOnce(item, globalIndex, token) .then(val => { - processed++; - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); return val; })); }); + // 等待本批次的请求结果 let res = await Promise.all(queue); res.forEach(val => { - selectList[val.index].downloadUrl = val.downloadUrl; + fileList[val.index].downloadUrl = val.downloadUrl; }); - await base.sleep(1000); + // 每次处理完一个批次后,等待 + if (i + batchSize < fileList.length){ + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${i+batch.length} / ${fileList.length} 个链接,请耐心等待哦~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + }; + + //累加批次 + i += batchSize; + batchSize = globalBatchsize+Math.ceil(Math.random()*5) } - let html = this.generateDom(selectList); + let html = this.generateDom(fileList); base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); }, @@ -5657,10 +6255,11 @@ let content = '
'; let alinkAllText = ''; list.forEach((v, i) => { - if (v.isFolder) return; - let filename = v.fileName; + if (v?.isFolder) return; + let filename = v.name; let size = base.sizeFormat(v.size); let dlink = v.downloadUrl; + let dpath=v.path; if (!dlink || !dlink.includes("http")) { content += `
${filename}
@@ -5696,9 +6295,19 @@ } if (mode === 'rpc') { content += `
-
${filename}
- -
`; +
${filename}
+ +
`; } if (mode === 'curl') { let alink = base.convertLinkToCurl(dlink, filename); @@ -5788,8 +6397,14 @@ * 迅雷云盘 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let $xunlei = { + cnt:0,//计数器,用于控制随机延迟 + fileCount:0,//文件计数 + authorization:"", + token:{}, + addPageListener() { /* 防止代码因其他原因被执行多次 @@ -5972,7 +6587,7 @@ target.find('.pl-loading').remove(); target.prepend(base.createLoading()); - let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename); + let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename,e.currentTarget.dataset.path); if (res === 'success') { $('.listener-rpc-task').show(); target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow"); @@ -6077,33 +6692,121 @@ return token; }, + async getFilesByOnce(item,params){ + let res; + try { + if(page=="home"){ + //limit最多只能获取到200,res返回参数里有个 next_page_token 是令牌,如果是空的就说明不需要下一页 + //中间奇怪的那段是URL编码的JSON字符串,第1次请求是不带parent_id之前的&的,后面请求是带parent_id前的&的,但都适用 + let url=`${config.$xunlei.api.getFiles.home}page_token=${params.next_page_token}&parent_id=${params.parent_id}&filters=%7B%22phase%22%3A%7B%22eq%22%3A%22PHASE_TYPE_COMPLETE%22%7D%2C%22trashed%22%3A%7B%22eq%22%3Afalse%7D%7D&with_audit=true&limit=50`; + res = await base.get(url, { + 'Authorization': this.authorization, + 'content-type': "application/json", + 'x-captcha-token': this.token.captcha.token, + 'x-device-id': this.token.deviceid, + }); + }else if(page=="share"){ + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error('提示:
请先登录网盘后再刷新页面呢~'); + } + }, + async getFileUrlByOnce(item, index, token) { + let res; try { - if (item.downloadUrl) return { - index, - downloadUrl: item.downloadUrl - }; - let res = await base.get(config.$xunlei.api.getLink + item.id, { - 'Authorization': `${token.credentials.token_type} ${token.credentials.access_token}`, - 'content-type': "application/json", - 'x-captcha-token': token.captcha.token, - 'x-device-id': token.deviceid, - }); - if (res.web_content_link) { - return { - index, - downloadUrl: res.web_content_link - }; - } else { - return { + if(page=="home"){ + if (item.downloadUrl) return { index, - downloadUrl: '获取下载地址失败,刷新后再试试吧~' + downloadUrl: item.downloadUrl }; + res = await base.get(config.$xunlei.api.getLink.home + item.id, { + 'Authorization': `${token.credentials.token_type} ${token.credentials.access_token}`, + 'content-type': "application/json", + 'x-captcha-token': token.captcha.token, + 'x-device-id': token.deviceid, + }); + if (res.web_content_link) { + return { + index, + downloadUrl: res.web_content_link + }; + } else { + return { + index, + downloadUrl: '获取下载地址失败,刷新后再试试吧~' + }; + } + }else if(page=="share"){ + }else{ + return message.error(`提示:
网站页面错误`); } + return res; } catch (e) { return message.error('提示:
请先登录网盘后再刷新页面呢~'); } }, + + async fetchAllPages(file) { + //分页获取文件夹内文件数据 + let files=[];//文件数据列表 + let next_page_token='';//文件夹内下一段数据的令牌 + let res; + while (true) { + //随机停顿防反爬 + this.cnt++; + if (this.cnt >= 50+Math.random()*10) { + doc.find('.loading-popup .swal2-html-container').html(`
正遍历到的大型文件夹已获取 ${files.length} 个文件~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + this.cnt = 0; + } + + //发送请求 + let param={"next_page_token":next_page_token,"parent_id":file.id}; + res=await this.getFilesByOnce(file,param); + + //请求失败 + if(!res?.files){//注意变量名 + return message.error(`提示:
请求失败,失败码: ${res?.code} 失败信息:${res.message}`); + } + + //请求成功 + files=files.concat(res.files)//注意变量名 + next_page_token=res.next_page_token; + if (!next_page_token) + break; + } + return files; + }, + + async fetchFiles(file,pNode) { + //获取文件目录结构(递归) + //节点 + let fileNode={ + name:file.name,//注意变量名 + path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + children:[] + } + pNode.children.push(fileNode); + file.path=fileNode.path; + + //判断是否文件夹 + if (file?.kind!='drive#folder') {//注意变量名 + this.fileCount++; + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
`); + return file; + } + + //文件夹内文件遍历 + let files=[]; + for (let f of await this.fetchAllPages(file)) { + files= files.concat(await this.fetchFiles(f,fileNode)); + } + return files; + }, async getLink() { Swal.fire({ @@ -6131,40 +6834,72 @@ if (selectList.length === 0) { return message.error('提示:
请勾选要下载的文件哦~'); } - if (this.isOnlyFolder()) { - return message.error('提示:
请打开文件夹后再勾选文件~'); + + if(page=="home"){ + }else if(page=="share"){ + //分享页面所需的变量 + }else{ + return message.error('提示:
页面错误~'); } + this.token = this.getToken(); + this.authorization = `${this.token.credentials.token_type} ${this.token.credentials.access_token}`; + + this.cnt=0; + this.fileCount=0;//文件计数 + let fileList=[];//文件列表,只有文件没有文件夹 + let fileTree = { name: "root", path: ``, children: [] }; // 文件目录树 if (page === 'home') { - let token = this.getToken(); - let batchSize = 15; - let processed = 0; + //获取文件目录结构(递归) + for (let file of selectList) { + //遍历选中文件 + //统一变量名 + fileList = fileList.concat(await this.fetchFiles(file,fileTree)); + } + + //文件夹为空 + if (!fileList.length) { + return message.error('提示:
文件夹是空的哦~'); + } + + //获取下载链接 doc.find('.loading-popup .loading-title').html(`链接获取中`); doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); - for (let i = 0; i < selectList.length; i += batchSize) { - let batch = selectList.slice(i, i + batchSize); - let queue = []; + let batchSize = globalBatchsize+Math.ceil(Math.random()*5); + let i=0; + while (i < fileList.length){ + // 分批获取链接 + let batch = fileList.slice(i, i + batchSize); + // 生成请求队列 + let queue = []; batch.forEach((item, localIndex) => { let globalIndex = i + localIndex; - queue.push(this.getFileUrlByOnce(item, globalIndex, token) + queue.push(this.getFileUrlByOnce(item, globalIndex, this.token) .then(val => { - processed++; - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); + // doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); return val; })); }); let res = await Promise.all(queue); res.forEach(val => { - selectList[val.index].downloadUrl = val.downloadUrl; + fileList[val.index].downloadUrl = val.downloadUrl; }); - await base.sleep(1000); + // 每次处理完一个批次后,等待 + if (i + batchSize < fileList.length){ + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${i+batch.length} / ${fileList.length} 个链接,请耐心等待哦~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + }; + + //累加批次 + i += batchSize; + batchSize = globalBatchsize+Math.ceil(Math.random()*5) } } else { return message.error('提示:
页面错误~'); } - let html = this.generateDom(selectList); + let html = this.generateDom(fileList); base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); }, @@ -6180,6 +6915,7 @@ let filename = v.name; let size = base.sizeFormat(+v.size); let dlink = v.downloadUrl; + let dpath=v.path; if (!dlink || !dlink.includes("http")) { content += `
${filename}
@@ -6216,9 +6952,19 @@ } if (mode === 'rpc') { content += `
-
${filename}
- -
`; +
${filename}
+ +
`; } if (mode === 'curl') { let alink = base.convertLinkToCurl(dlink, filename); @@ -6316,8 +7062,12 @@ * 夸克网盘 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let $quark = { + cnt:0,//计数器,用于控制随机延迟 + fileCount:0,//文件计数 + addPageListener() { /* 防止代码因其他原因被执行多次 @@ -6503,7 +7253,7 @@ target.find('.pl-loading').remove(); target.prepend(base.createLoading()); - let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename, [`Cookie: ${document.cookie}`]); + let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename,e.currentTarget.dataset.path, [`Cookie: ${document.cookie}`]); if (res === 'success') { $('.listener-rpc-task').show(); target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow"); @@ -6629,6 +7379,91 @@ }) }, + async getFilesByOnce(item,params){ + try { + let res; + if(page=="home"){ + let url=`${config.$quark.api.getFiles.home}&uc_param_str=&pdir_fid=${item.fid}&_page=${params.pageNum}&_size=50&_fetch_total=1&_fetch_sub_dirs=0&_sort=file_type:asc,file_name:asc`; + res = await base.get(url, { "User-Agent": config.$quark.api.ua.downloadLink }); + }else if(page=="share"){ + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error(`提示:
文件获取请求失败`); + } + }, + + async getFileUrlByOnce(item, params) { + try { + let res; + if(page=="home"){ + res = await base.post(config.$quark.api.getLink.home, { "fids": params.fids }, { "content-type": "application/json;charset=utf-8", "user-agent": config.$quark.api.ua.downloadLink }); + }else if(page=="share"){ + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error(`提示:
文件下载链接请求失败`); + } + }, + + async fetchAllPages(file) { + //分页获取文件夹内文件数据 + let files=[];//文件数据列表 + let pageCount=Math.ceil(file.include_items/50);//总页数 + for (let pageNum=1;pageNum<=pageCount;pageNum++) { + //随机停顿防反爬 + this.cnt++; + if (this.cnt >= 50+Math.random()*10) { + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + this.cnt = 0; + } + //发送请求 + let param={"page":pageNum}; + let res=await this.getFilesByOnce(file,param); + //请求失败 + if (res.code === 31001) {//TODO 可能是这样,没测试,可能要等很久账号掉了,我没时间等 + return message.error('提示:
请先登录网盘~
代码:' + res.code); + } + if (res.code !== 0) { + return message.error('提示:
获取链接失败了~
代码:' + res.code); + } + //请求成功 + files=files.concat(res.data.list) + } + return files; + }, + + async fetchFiles(file,pNode) { + //获取文件目录结构(递归) + //节点 + let fileNode={ + name:file.file_name, + path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + children:[] + } + pNode.children.push(fileNode) + file.path=fileNode.path; + + //判断是否文件夹 + if (!file?.dir) { + this.fileCount++; + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
`); + return file; + } + + //文件夹内文件遍历 + let files=[]; + for (let f of await this.fetchAllPages(file)) { + files= files.concat(await this.fetchFiles(f,fileNode)); + } + return files; + }, + async getLink() { Swal.fire({ showConfirmButton: false, @@ -6655,46 +7490,53 @@ if (selectList.length === 0) { return message.error('提示:
请勾选要下载的文件哦~'); } - if (this.isOnlyFolder()) { - return message.error('提示:
请打开文件夹后再勾选文件~'); - } - if (page === 'home') { - let data = []; - let batchSize = 15; - let processed = 0; - selectList = selectList.filter(item => item.file === true) - - for (let i = 0; i < selectList.length; i += batchSize) { - // 获取当前批次文件 - let batch = selectList.slice(i, i + batchSize); - console.log() - let fids = batch.map(item => item.fid); - - // 发起请求获取链接 - let res = await base.post(config.$quark.api.getLink, { "fids": fids }, { "content-type": "application/json;charset=utf-8", "user-agent": config.$quark.api.ua.downloadLink }); - - if (res?.code === 31001) { - return message.error('提示:
请先登录网盘~
代码:' + res.code); - } - if (res?.code !== 0) { - return message.error('提示:
获取链接失败了~
代码:' + res.code); - } - // 合并响应数据 - if (res?.data) { - data.push(...res.data); - } + if(page=="home"){ + }else if(page=="share"){ + //分享页面所需的变量 + }else{ + return message.error('提示:
页面错误~'); + } + + this.cnt=0; //计数器,用于控制随机延迟 + this.fileCount=0;//文件计数 + let fileList=[];//文件列表,只有文件没有文件夹 + let fileTree = { name: "root", path: ``, children: [] }; // 文件目录树 - // 更新处理进度 - processed += batch.length; + if (page === 'home') { + //获取文件目录结构(递归) + for (let file of selectList) { + //遍历选中文件 + //统一变量名 + fileList = fileList.concat(await this.fetchFiles(file,fileTree)); + } - // 更新UI显示 - doc.find('.loading-popup .loading-title').html(`链接获取中`); - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); + //文件夹为空 + if (!fileList.length) { + return message.error('提示:
文件夹是空的哦~'); + } - // 请求间隔节流 - await base.sleep(1000); + //获取下载链接 + //TODO 我认为连续访问网盘的接口不如直接拿全部ID合适,post协议本身不作数据长度上限,即使后台限制为1M,也有65536个文件ID了,你如果觉得不合适可以修改 + let fids = fileList.map(item => item.fid); + let params={"fids":fids}; + let res = await this.getFileUrlByOnce(undefined,params); + if (res.code === 31001) { + return message.error('提示:
请先登录网盘~
代码:' + res.code); } + if (res.code !== 0) { + return message.error('提示:
获取链接失败了~
代码:' + res.code); + } + if (res.data.length!=fids.length) + return message.error('提示:
获取下载链接数目不对,请重新尝试,若文件数目太多,如好几万个文件,请联系作者修改
'); + + //显示下载GUI + let data = res.data.map((element, index) => { + return { + ...element, + path: fileList[index].path//TODO 请求的ID和返回的ID的列表顺序是一一对应的,如果有问题请告诉我或自行修改 + }; + }); let html = this.generateDom(data); base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); } else { @@ -6714,6 +7556,7 @@ let fid = v.fid; let size = base.sizeFormat(v.size); let dlink = v.download_url; + let dpath=v.path; if (!dlink || !dlink.includes("http")) { content += `
${filename}
@@ -6749,9 +7592,19 @@ } if (mode === 'rpc') { content += `
-
${filename}
- -
`; +
${filename}
+ +
`; } if (mode === 'curl') { let alink = base.convertLinkToCurl(dlink, filename, `-b "${document.cookie}"`); @@ -6853,8 +7706,12 @@ * UC网盘 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let $uc = { + cnt:0,//计数器,用于控制随机延迟 + fileCount:0,//文件计数 + addPageListener() { /* 防止代码因其他原因被执行多次 @@ -7033,7 +7890,7 @@ target.find('.pl-loading').remove(); target.prepend(base.createLoading()); - let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename, [`Cookie: ${document.cookie}`]); + let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename,e.currentTarget.dataset.path, [`Cookie: ${document.cookie}`]); if (res === 'success') { $('.listener-rpc-task').show(); target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow"); @@ -7128,6 +7985,110 @@ }) }, + async getFilesByOnce(item,params){ + try { + let res; + if(page=="home"){ + let url=`${config.$uc.api.getFiles.home}?pr=UCBrowser&fr=pc&pdir_fid=${params.fid}&_page=${params.pageNum}&_size=50&_fetch_total=1&_fetch_sub_dirs=0&_sort=file_type:asc,updated_at:desc`; + res = await base.get(url, { + "content-type": "application/json;charset=utf-8", + // "user-agent": config.$quark.api.ua.downloadLink //TODO 怎么是夸克的? + "user-agent": config.$uc.api.ua.downloadLink + }); + }else if(page=="share"){ + + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error(`提示:
文件获取请求失败`); + } + }, + + async getFileUrlByOnce(item, index, token) { + try { + if (item.downloadUrl) return { + index, + downloadUrl: item.downloadUrl + }; + let res = await base.get(config.$xunlei.api.getLink.home + item.id, { + 'Authorization': `${token.credentials.token_type} ${token.credentials.access_token}`, + 'content-type': "application/json", + 'x-captcha-token': token.captcha.token, + 'x-device-id': token.deviceid, + }); + if (res.web_content_link) { + return { + index, + downloadUrl: res.web_content_link + }; + } else { + return { + index, + downloadUrl: '获取下载地址失败,刷新后再试试吧~' + }; + } + } catch (e) { + return message.error('提示:
请先登录网盘后再刷新页面呢~'); + } + }, + + async fetchAllPages(file) { + //分页获取文件夹内文件数据 + let res; + let files=[];//文件数据列表 + let pageCount=Math.ceil(file.include_items/50);//总页数 + for (let pageNum=1;pageNum<=pageCount;pageNum++) { + //随机停顿防反爬 + this.cnt++; + if (this.cnt >= 50+Math.random()*10) { + doc.find('.loading-popup .swal2-html-container').html(`
正遍历到的大型文件夹已获取 ${files.length} 个文件~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + this.cnt = 0; + } + + //发送请求 + let param={"fid":file.fid,"pageNum":pageNum}; + res=await this.getFilesByOnce(file,param); + + //请求失败 + if(res?.code){//注意变量名 + return message.error(`提示:
请求失败,失败码: ${res?.code} 失败信息:${res.message}`); + } + + //请求成功 + files=files.concat(res.data.list)//注意变量名 + } + return files; + }, + + async fetchFiles(file,pNode) { + //获取文件目录结构(递归) + //节点 + let fileNode={ + name:file.file_name,//注意变量名 + path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + children:[] + } + pNode.children.push(fileNode); + file.path=fileNode.path; + + //判断是否文件夹 + if (!file?.dir) {//注意变量名 + this.fileCount++; + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
`); + return file; + } + + //文件夹内文件遍历 + let files=[]; + for (let f of await this.fetchAllPages(file)) { + files= files.concat(await this.fetchFiles(f,fileNode)); + } + return files; + }, + async getLink() { Swal.fire({ showConfirmButton: false, @@ -7154,46 +8115,49 @@ if (selectList.length === 0) { return message.error('提示:
请勾选要下载的文件哦~'); } - if (this.isOnlyFolder()) { - return message.error('提示:
请打开文件夹后再勾选文件~'); - } - if (page === 'home') { - let data = []; - let batchSize = 15; - let processed = 0; - selectList = selectList.filter(item => item.file === true) - - for (let i = 0; i < selectList.length; i += batchSize) { - // 获取当前批次文件 - let batch = selectList.slice(i, i + batchSize); - console.log() - let fids = batch.map(item => item.fid); - - // 发起请求获取链接 - let res = await base.post(config.$uc.api.getLink, { "fids": fids }, { "content-type": "application/json;charset=utf-8", "user-agent": config.$quark.api.ua.downloadLink }); - - if (res?.code === 31001) { - return message.error('提示:
请先登录网盘~
代码:' + res.code); - } - if (res?.code !== 0) { - return message.error('提示:
获取链接失败了~
代码:' + res.code); - } - - // 合并响应数据 - if (res?.data) { - data.push(...res.data); - } - - // 更新处理进度 - processed += batch.length; - // 更新UI显示 - doc.find('.loading-popup .loading-title').html(`链接获取中`); - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); + if(page=="home"){ + }else if(page=="share"){ + //分享页面所需的变量 + }else{ + return message.error('提示:
页面错误~'); + } - // 请求间隔节流 - await base.sleep(1000); + this.cnt=0; + this.fileCount=0;//文件计数 + let fileList=[];//文件列表,只有文件没有文件夹 + let fileTree = { name: "root", path: ``, children: [] }; // 文件目录树 + if (page === 'home') { + //获取文件目录结构(递归) + for (let file of selectList) { + //遍历选中文件 + //统一变量名 + fileList = fileList.concat(await this.fetchFiles(file,fileTree)); + } + + //文件夹为空 + if (!fileList.length) { + return message.error('提示:
文件夹是空的哦~'); } + + //获取下载链接 + //TODO 我认为连续访问网盘的接口不如直接拿全部ID合适,post协议本身不作数据长度上限,即使后台限制为1M,也有65536个文件ID了,你如果觉得不合适可以修改 + let fids = fileList.map(item => item.fid); + // 发起请求获取链接 + let res = await base.post(config.$uc.api.getLink.home, { "fids": fids }, { "content-type": "application/json;charset=utf-8", "user-agent": config.$uc.api.ua.downloadLink }); + if (res.code !== 0) { + return message.error(`提示:
请求失败,失败码: ${res?.code} 失败信息:${res.message}`); + } + if (res.data.length!=fids.length) + return message.error('提示:
获取下载链接数目不对,请重新尝试,若文件数目太多,如好几万个文件,请联系作者修改
'); + + //显示下载GUI + let data = res.data.map((element, index) => { + return { + ...element, + path: fileList[index].path//TODO 请求的ID和返回的ID的列表顺序是一一对应的,如果有问题请告诉我或自行修改 + }; + }); let html = this.generateDom(data); base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); } else { @@ -7213,6 +8177,7 @@ let fid = v.fid; let size = base.sizeFormat(v.size); let dlink = v.download_url; + let dpath=v.path; if (!dlink || !dlink.includes("http")) { content += `
${filename}
@@ -7248,9 +8213,19 @@ } if (mode === 'rpc') { content += `
-
${filename}
- -
`; +
${filename}
+ +
`; } if (mode === 'curl') { let alink = base.convertLinkToCurl(dlink, filename, `-b "${document.cookie}"`); @@ -7352,8 +8327,14 @@ * 123云盘 * @author 油小猴 * @author hmjz100 + * @author KanouAo */ let $123pan = { + cnt:0,//计数器,用于控制随机延迟 + fileCount:0,//文件计数 + token:{}, + ShareKey:"", + addPageListener() { /* 防止代码因其他原因被执行多次 @@ -7520,7 +8501,7 @@ target.find('.pl-loading').remove(); target.prepend(base.createLoading()); - let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename); + let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename,e.currentTarget.dataset.path); if (res === 'success') { $('.listener-rpc-task').show(); target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow"); @@ -7609,6 +8590,171 @@ let token = base.getStorage("authorToken"); return token; }, + + getRandomNum(len) { + len = len || 16; + let $chars = '0123456789'; + let maxPos = $chars.length; + let pwd = ''; + for (let i = 0; i < len; i++) { + pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); + } + return pwd; + }, + + async getFilesByOnce(item,params){ + try { + let res; + params.driveId=0;//TODO 看起来是个变量,但我定值是0,也没细看这变量怎么产生的,以后出问题再看。 + params.operateType=8;//文件夹初次打开拿第1页数据时是4,鼠标下滑增加数据是8 + params.next=0;//如何是100以内不用拉数据的是0,超过100的是-1,都填0都适配 + let time = Date.now(); + if(page=="home"){ + //TODO 这URL中间有XXX=时间戳-XXX-XXX的格式,我发觉那放3个随机数竟然也行,如果不合适你就找下。 + let url=`${config.$123pan.api.getFiles.home}?${this.getRandomNum()}=${time}-${this.getRandomNum()}-${this.getRandomNum()}&driveId=${params.driveId}&limit=100&next=${params.next}&orderBy=update_time&orderDirection=desc&parentFileId=${params.FileId}&trashed=false&SearchData=&Page=${params.pageNum}&OnlyLookAbnormalFile=0&event=homeListFile&operateType=${params.operateType}&inDirectSpace=false`; + res = await base.get(url, { + "content-type": "application/json;charset=utf-8", + "authorization": `Bearer ${this.token}`, + "platform": "web"//TODO ios?打错?不合适就换回来 + }); + }else if(page=="share"){ + //TODO 这URL中间有XXX=时间戳-XXX-XXX的格式,我发觉那放3个随机数竟然也行,如果不合适你就找下。 + let url=`${config.$123pan.api.getFiles.share}?${this.getRandomNum()}=${time}-${this.getRandomNum()}-${this.getRandomNum()}&limit=100&next=${params.next}&orderBy=file_name&orderDirection=asc&shareKey=${this.ShareKey}&ParentFileId=${params.FileId}&Page=${params.pageNum}&event=homeListFile&operateType=${params.operateType}`; + res = await base.get(url, { + "content-type": "application/json;charset=utf-8", + "authorization": `Bearer ${this.token}`, + "platform": "web"//TODO ios?打错?不合适就换回来 + }); + }else{ + return message.error(`提示:
网站页面错误`); + } + return res; + } catch (e) { + return message.error(`提示:
文件获取请求失败`); + } + }, + + async getFileUrlByOnce(item, index) { + try { + if (item.DownloadUrl) return { + index, + downloadUrl: item.DownloadUrl + }; + let res = null; + if (this.ShareKey) { + res = await base.post(config.$123pan.api.getLink.share, { + "ShareKey": this.ShareKey, + "FileID": item.FileId, + "S3keyFlag": item.S3KeyFlag, + "Size": item.Size, + "Etag": item.Etag + }, { + "content-type": "application/json;charset=utf-8", + "authorization": `Bearer ${this.token}`, + "platform": "ios"//为啥IOS不是WEB?IOS更好点? + }); + } else { + res = await base.post(config.$123pan.api.getLink.home, { + "driveId": 0, + "etag": item.Etag, + "fileId": item.FileId, + "s3keyFlag": item.S3KeyFlag, + "type": item.Type, + "fileName": item.FileName, + "size": item.Size + }, { + "content-type": "application/json;charset=utf-8", + "authorization": `Bearer ${this.token}`, + "platform": "web"//TODO ios?打错?不合适就换回来 + }); + } + if (res.data?.DownloadUrl) { + let url = res.data.DownloadUrl; + let surl = new URL(url).searchParams.get("params"); + if (surl) url = base.decodeBase(surl); + url = await base.getFinalUrl(url); + return { + index, + downloadUrl: url + }; + } else if (res.data?.DownloadURL) { + let url = res.data.DownloadURL; + let surl = new URL(url).searchParams.get("params"); + if (surl) url = base.decodeBase(surl); + url = await base.getFinalUrl(url); + return { + index, + downloadUrl: url + }; + } else if (res?.code === 5112) { + return message.error('提示:
请先登录网盘后再获取链接呢~'); + } else { + return { + index, + downloadUrl: '获取下载地址失败,刷新后再试试吧~' + }; + } + } catch (e) { + return { + index, + downloadUrl: '获取下载地址失败,刷新后再试试吧~' + }; + } + }, + + async fetchAllPages(file) { + //分页获取文件夹内文件数据 + let res; + let files=[];//文件数据列表 + let pageCount=1;//总页数,先设1,之后根据res返回的值修改//share没有file.fileCount,兼容适配 + for (let pageNum=1;pageNum<=pageCount;pageNum++) { + //随机停顿防反爬 + this.cnt++; + if (this.cnt >= 50+Math.random()*10) { + doc.find('#loadingText').html(`
文件获取中
已获取 ${this.fileCount} 个文件,请耐心等待哦~
>休息 ${globalSleep} 毫秒...
`); + await base.sleep(globalSleep+Math.random()*globalSleepRandSeed); + this.cnt = 0; + } + //发送请求 + let param={"pageNum":pageNum,"FileId":file.FileId}; + res=await this.getFilesByOnce(file,param); + + //请求失败 + if(!(res?.code==0&&res?.message=="ok")){ + return message.error(`提示:
请求失败,失败码: ${res?.res_code} 失败信息:${res.res_message}`); + } + //请求成功 + if(pageCount==1)pageCount=Math.ceil(res.data.Total/100); + files=files.concat(res.data.InfoList);//注意变量名 + } + return files; + }, + + async fetchFiles(file,pNode) { + //获取文件目录结构(递归) + //节点 + let fileNode={ + name:file.FileName,//注意变量名 + path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + children:[] + } + pNode.children.push(fileNode); + file.path=fileNode.path; + + //判断是否文件夹 + if (file.Type === 0) {//注意变量名 + this.fileCount++; + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${this.fileCount} 个文件~
`); + return file; + } + + //文件夹内文件遍历 + let files=[]; + for (let f of await this.fetchAllPages(file)) { + files= files.concat(await this.fetchFiles(f,fileNode)); + } + return files; + }, async getLink() { Swal.fire({ @@ -7636,137 +8782,106 @@ if (selectList.length === 0) { return message.error('提示:
请勾选要下载的文件哦~'); } - if (this.isOnlyFolder()) { - return message.error('提示:
请打开文件夹后再勾选文件~'); - } - console.log(selectList); - if (page === 'home') { - let token = this.getToken(); - let batchSize = 15; - let processed = 0; - selectList = selectList.filter(item => item.Type === 0); - for (let i = 0; i < selectList.length; i += batchSize) { - let batch = selectList.slice(i, i + batchSize); - let queue = []; - doc.find('.loading-popup .loading-title').html(`链接获取中`); - doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); - batch.forEach((item, localIndex) => { - let globalIndex = i + localIndex; - queue.push(this.getFileUrlByOnce(item, globalIndex, token) - .then(val => { - processed++; - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); - return val; - })); - }); - - let res = await Promise.all(queue); - res.forEach(val => { - selectList[val.index].DownloadUrl = val.downloadUrl; - }); - - await base.sleep(1000); - } - let html = this.generateDom(selectList); - base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); - } else if (page === 'share') { - let token = this.getToken(); - let batchSize = 15; - let processed = 0; - selectList = selectList.filter(item => item.Type === 0); + if(page=="home"){ + }else if(page=="share"){ + //分享页面所需的变量 let pathSplit = location.pathname.split('/').filter(Boolean); - let ShareKey = pathSplit[1]; - console.log(selectList) - for (let i = 0; i < selectList.length; i += batchSize) { - let batch = selectList.slice(i, i + batchSize); - let queue = []; + this.ShareKey = pathSplit[1]; + }else{ + return message.error('提示:
页面错误~'); + } + this.token = this.getToken(); + + this.cnt=0; + this.fileCount=0;//文件计数 + let fileList=[];//文件列表,只有文件没有文件夹 + let fileTree = { name: "root", path: ``, children: [] }; // 文件目录树 + // if (page === 'home') { + //获取文件目录结构(递归) + for (let file of selectList) { + //遍历选中文件 + //统一变量名 + fileList = fileList.concat(await this.fetchFiles(file,fileTree)); + } + + //文件夹为空 + if (!fileList.length) { + return message.error('提示:
文件夹是空的哦~'); + } + + //获取下载链接 + doc.find('.loading-popup .loading-title').html(`链接获取中`); + doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); + let batchSize = globalBatchsize+Math.ceil(Math.random()*5); + let i=0; + while (i < fileList.length){ + // 分批获取链接 + let batch = fileList.slice(i, i + batchSize); - doc.find('.loading-popup .loading-title').html(`链接获取中`); - doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); + // 生成请求队列 + let queue = []; batch.forEach((item, localIndex) => { let globalIndex = i + localIndex; - queue.push(this.getFileUrlByOnce(item, globalIndex, token, ShareKey) + queue.push(this.getFileUrlByOnce(item, globalIndex) .then(val => { - processed++; - doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); + // processed++; + // doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); return val; })); }); + // 等待本批次的请求结果 let res = await Promise.all(queue); res.forEach(val => { - selectList[val.index].DownloadUrl = val.downloadUrl; + fileList[val.index].DownloadUrl = val.downloadUrl; }); - await base.sleep(1000); + // 每次处理完一个批次后,等待 + if (i + batchSize < fileList.length){ + doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${i+batch.length} / ${fileList.length} 个链接,请耐心等待哦~
休息 ${globalSleep} 毫秒...
`); + await base.customSleep(); + }; + + //累加批次 + i += batchSize; + batchSize = globalBatchsize+Math.ceil(Math.random()*5) } - let html = this.generateDom(selectList); + let html = this.generateDom(fileList); base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); - } else { - return message.error('提示:
页面错误~'); - } - }, - - async getFileUrlByOnce(item, index, token, ShareKey) { - if (item.DownloadUrl) return { - index, - downloadUrl: item.DownloadUrl - }; - let res = null; - if (ShareKey) { - res = await base.post(config.$123pan.api.getShareLink, { - "ShareKey": ShareKey, - "FileID": item.FileId, - "S3keyFlag": item.S3KeyFlag, - "Size": item.Size, - "Etag": item.Etag - }, { - "content-type": "application/json;charset=utf-8", - "authorization": `Bearer ${token}`, - "platform": "ios" - }); - } else { - res = await base.post(config.$123pan.api.getLink, { - "driveId": 0, - "etag": item.Etag, - "fileId": item.FileId, - "s3keyFlag": item.S3KeyFlag, - "type": item.Type, - "fileName": item.FileName, - "size": item.Size - }, { - "content-type": "application/json;charset=utf-8", - "authorization": `Bearer ${token}`, - "platform": "ios" - }); - } - if (res.data?.DownloadUrl) { - let url = res.data.DownloadUrl; - let surl = new URL(url).searchParams.get("params"); - if (surl) url = base.decodeBase(surl); - url = await base.getFinalUrl(url); - return { - index, - downloadUrl: url - }; - } else if (res.data?.DownloadURL) { - let url = res.data.DownloadURL; - let surl = new URL(url).searchParams.get("params"); - if (surl) url = base.decodeBase(surl); - url = await base.getFinalUrl(url); - return { - index, - downloadUrl: url - }; - } else if (res?.code === 5112) { - return message.error('提示:
请先登录网盘后再获取链接呢~'); - } else { - return { - index, - downloadUrl: '获取下载地址失败,刷新后再试试吧~' - }; - } + // } else if (page === 'share') { + // let batchSize = 15; + // let processed = 0; + // selectList = selectList.filter(item => item.Type === 0); + // console.log(selectList) + // for (let i = 0; i < selectList.length; i += batchSize) { + // let batch = selectList.slice(i, i + batchSize); + // let queue = []; + + // doc.find('.loading-popup .loading-title').html(`链接获取中`); + // doc.find('.loading-popup .swal2-html-container').html(`
正在获取文件对应的下载链接~
`); + // batch.forEach((item, localIndex) => { + // let globalIndex = i + localIndex; + // queue.push(this.getFileUrlByOnce(item, globalIndex) + // .then(val => { + // processed++; + // doc.find('.loading-popup .swal2-html-container').html(`
已获取 ${processed} / ${selectList.length} 个链接~
`); + // return val; + // })); + // }); + + // let res = await Promise.all(queue); + // res.forEach(val => { + // selectList[val.index].DownloadUrl = val.downloadUrl; + // }); + + // await base.sleep(1000); + // } + // let html = this.generateDom(selectList); + // base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer); + // } else { + // return message.error('提示:
页面错误~'); + // } }, generateDom(list) { @@ -7781,6 +8896,7 @@ let fileid = v.FileId; let size = base.sizeFormat(v.Size); let dlink = v.DownloadUrl || v.DownloadURL; + let dpath=v.path; if (!dlink || !dlink.includes("http")) { content += `
${filename}
@@ -7816,9 +8932,19 @@ } if (mode === 'rpc') { content += `
-
${filename}
- -
`; +
${filename}
+ +
`; } if (mode === 'curl') { let alink = base.convertLinkToCurl(dlink, filename); From 489dbf6f2a00c931f14f6b7a3b6c1a148406fa46 Mon Sep 17 00:00:00 2001 From: KanouAo <214709230@qq.com> Date: Tue, 20 May 2025 23:47:22 +0800 Subject: [PATCH 2/5] 5.20 --- ...\350\275\275\345\212\251\346\211\213.user.js" | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git "a/\357\274\210\346\224\271\357\274\211\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/\357\274\210\346\224\271\357\274\211\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 e14a8ca..4cfd11f 100644 --- "a/\357\274\210\346\224\271\357\274\211\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/\357\274\210\346\224\271\357\274\211\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" @@ -3670,8 +3670,8 @@ let res; if(page=="home"||page=="main"){ //我找的链接获取文件,1页100个,这个能获取文件夹所有文件我就直接用了 - //TODO path属性被占用,原变量就请求下载时用一次,就 path 改成 _path - let url = `${config.$baidu.api.getFiles.home}&dir=${encodeURIComponent(file._path)}&access_token=${this.accessToken}`; + //TODO path属性被占用,原来设想的从选中文件夹开始的路径改成_path + let url = `${config.$baidu.api.getFiles.home}&dir=${encodeURIComponent(file.path)}&access_token=${this.accessToken}`; res = await base.get(url, { "User-Agent": config.$baidu.api.ua.downloadLink}); }else if(page=="share"){ }else{ @@ -3713,8 +3713,7 @@ } //发送请求 - let params={"_path":file._path}; - res=await this.getFilesByOnce(file,params); + res=await this.getFilesByOnce(file,undefined); //请求失败 if(res?.errno){ @@ -3730,12 +3729,11 @@ //节点 let fileNode={ name:file.server_filename,//注意变量名 - path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + _path:pNode.name === 'root' ? `` : `${pNode._path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 children:[] } pNode.children.push(fileNode); - file._path=file.path;//TODO path属性被占用,原变量就请求下载时有用就 path 改成 _path - file.path=fileNode.path; + file._path=fileNode._path;//TODO path属性被占用,改成 _path //判断是否文件夹 if (!file?.isdir) {//注意变量名 @@ -3977,7 +3975,7 @@ if (linkList.length) { for (let i = 0; i < linkList.length; i++) {//重写下载路径 - linkList[i].path = filesDict[linkList[i].fs_id].path; + linkList[i]._path = filesDict[linkList[i].fs_id]._path; } base.showMainDialog(config.base.dom.button[mode].title, this.generateDom(linkList), config.base.dom.button[mode].footer); } else { @@ -3999,7 +3997,7 @@ if (v.isdir === 1) return; let filename = v.server_filename || v.filename; let size = base.sizeFormat(v.size); - let dpath=v.path; + let dpath=v._path;//属性被占用 if (!v?.dlink || !v?.dlink.includes("http")) { content += `
${filename}
From 49cfc01c8b8debe13c4d96fc25497c6e925d6d52 Mon Sep 17 00:00:00 2001 From: KanouAo <214709230@qq.com> Date: Wed, 21 May 2025 02:33:00 +0800 Subject: [PATCH 3/5] =?UTF-8?q?5.20=EF=BC=8CAria=E3=80=81RPC=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E8=B7=AF=E5=BE=84=E8=A7=A3=E5=86=B3=EF=BC=8Ccurl?= =?UTF-8?q?=E6=94=AF=E6=8C=81win11=E3=80=81armbian(linux)=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E6=B2=A1=E6=B5=8B=E8=AF=95=E8=8B=B9=E6=9E=9C=E7=94=B5?= =?UTF-8?q?=E8=84=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...200\205hmjz100\347\232\204\350\257\235.md" | 24 +++- ...0\275\275\345\212\251\346\211\213.user.js" | 133 +++++++++--------- 2 files changed, 92 insertions(+), 65 deletions(-) diff --git "a/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" "b/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" index a09ecdb..11cfcd0 100644 --- "a/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" +++ "b/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" @@ -1,7 +1,29 @@ 我增加了下载文件夹功能,一键下载文件夹下所有的文件(多个)文件夹嵌套文件夹的这种目录组织都能下载下来,测试环境 win11、edge 浏览器、RPC 发 Aira2 +这些下载器我基本都不认识,我只能简单测试下,可能要你去仔细试试,路径我都在网页元素那写好了,"dpath"元素就是。 + +API: +Blob 流我不会,老实说,我不会修改下载路径,如果的确没法改,请告诉用户。 + +Aria: +测试环境:是 win11 的 Aira2c 的控制台命令行。 +修改了下载路径。(话说这个有必要吗?不也是 RPC 发到 Aria2?)。 + +RPC: +测试环境:我发到 win11 和轻 nas armbian 上的 Aira2 的都可以 +修改了下载路径。 + +CURL: +测试环境:win11、armbian(linux) +win11 用 curl 命令,如果目录不存在会失败,所以加了创建目录的,但在命令行里测的,不知其它平台和下载器是否适用,如果不行就删掉吧。 +AI 说 为了使命令在 Linux/macOS 上也能工作,可以将其修改为:mkdir -p "下载测试 2" >/dev/null && curl …… +我在 armbian(linux)上测试了上面这条成功,但我没苹果电脑,只有苹果手机,就没测试。 + +BC: +测试环境:win11 +老实说,我不会修改下载路径,如果的确没法改,请告诉用户。 我只动了(改)网盘直链下载助手.user,其它我都没改 -我虽然会写代码,但油猴我第 1 次做,爬网站数据是第 2 次,上次还是咸鱼捡二手的提醒, +我虽然会写代码,但方向不是这方面的,油猴我第 1 次做,爬网站数据是第 2 次,上次还是咸鱼捡二手的提醒, 我虽然会 JS 但并不是主 JS 的,对 js 同步异步这边不是很熟悉,你可以看情况修改 标注 TODO 的都是要注意一下的。 diff --git "a/\357\274\210\346\224\271\357\274\211\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/\357\274\210\346\224\271\357\274\211\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 4cfd11f..f89b864 100644 --- "a/\357\274\210\346\224\271\357\274\211\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/\357\274\210\346\224\271\357\274\211\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" @@ -431,15 +431,17 @@ * 生成 Aria2 下载命令 * @author 油小猴 * @author hmjz100 + * @author KanouAo * @description 将链接转换为 Aria2 格式命令,自动处理文件名特殊字符 * @param {string} link - 下载链接 * @param {string} filename - 文件名 + * @param {string} path - 下载路径 * @param {string} [header] - 自定义请求头参数(可选) * @returns {string} 编码后的 aria2c 命令字符串 */ - convertLinkToAria(link, filename, header) { + convertLinkToAria(link, filename, path, header) { filename = base.fixFilename(filename); - return encodeURIComponent(`aria2c "${link}" --out "${filename}"${header ? (" " + header) : ""}`); + return encodeURIComponent(`aria2c "${link}" --out "${path}/${filename}"${header ? (" " + header) : ""}`); }, /** @@ -461,16 +463,19 @@ * 生成 cURL 下载命令 * @author 油小猴 * @author hmjz100 + * @author KanouAo * @description 根据终端类型生成对应 curl 命令,支持断点续传,自动处理文件名特殊字符 * @param {string} link - 下载链接 * @param {string} filename - 文件名 + * @param {string} path - 下载路径 * @param {string} [header] - 自定义请求头参数(可选) * @returns {string} 编码后的 curl 命令字符串 */ - convertLinkToCurl(link, filename, header) { + convertLinkToCurl(link, filename,path, header) { let terminal = base.getValue('setting_terminal_type'); filename = base.fixFilename(filename); - return encodeURIComponent(`${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${filename}"${header ? (" " + header) : ""}`); + //TODO win11用curl命令,如果目录不存在会失败,所以加了创建目录的,但在命令行里测的,不知其它平台和下载器是否适用 + return encodeURIComponent(`mkdir "${path}" ${(terminal !== 'wp' && terminal !== 'wc') ? '>/dev/null &&' : '2>nul &'} ${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${path}/${filename}"${header ? (" " + header) : ""}`); }, /** @@ -3729,7 +3734,7 @@ //节点 let fileNode={ name:file.server_filename,//注意变量名 - _path:pNode.name === 'root' ? `` : `${pNode._path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + _path:pNode.name === 'root' ? `` : (pNode._path === '' ? pNode.name : `${pNode._path}/${pNode.name}`),//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 children:[] } pNode.children.push(fileNode); @@ -4008,7 +4013,7 @@ if (mode === 'api') { alinkAllText += dlink + '\r\n'; content += `
-
${filename}
+
${filename}
@@ -4027,23 +4032,23 @@ } let BDUSS = this.getBDUSS(); if (mode === 'aria') { - let alink = base.convertLinkToAria(dlink, filename, `--header "User-Agent: ${config.$baidu.api.ua.downloadLink}" --header "Cookie: BDUSS=${BDUSS}"`); + let alink = base.convertLinkToAria(dlink, filename, dpath, `--header "User-Agent: ${config.$baidu.api.ua.downloadLink}" --header "Cookie: BDUSS=${BDUSS}"`); if (typeof (alink) === 'object') { content += ``; } else { alinkAllText += alink + '\r\n'; content += ``; } } if (mode === 'rpc') { content += `
-
${filename}
+
${filename}
@@ -4867,10 +4872,10 @@
`; } if (mode === 'aria') { - let alink = base.convertLinkToAria(dlink, filename, `--header "Referer: https://${location.host}/"`); + let alink = base.convertLinkToAria(dlink, filename, dpath, `--header "Referer: https://${location.host}/"`); alinkAllText += alink + '\r\n'; content += ``; } @@ -4892,10 +4897,10 @@
`; } if (mode === 'curl') { - let alink = base.convertLinkToCurl(dlink, filename, `&refer=${encodeURIComponent(`https://${location.host}/`)}`); + let alink = base.convertLinkToCurl(dlink, filename, dpath, `&refer=${encodeURIComponent(`https://${location.host}/`)}`); alinkAllText += alink + '\r\n'; content += ``; } @@ -4903,7 +4908,7 @@ let alink = base.convertLinkToBC(dlink, filename, `-e "https://${location.host}/"`); alinkAllText += alink + '\r\n'; content += ``; } @@ -5469,7 +5474,7 @@ //节点 let fileNode={ name:file.name,//注意变量名 - path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + path:pNode.name === 'root' ? `` : (pNode.path === '' ? pNode.name : `${pNode.path}/${pNode.name}`),//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 children:[] } pNode.children.push(fileNode); @@ -5603,7 +5608,7 @@ if (mode === 'api') { alinkAllText += dlink + '\r\n'; content += `
-
${filename}
+
${filename}
@@ -5620,10 +5625,10 @@
`; } if (mode === 'aria') { - let alink = base.convertLinkToAria(dlink, filename); + let alink = base.convertLinkToAria(dlink, filename, dpath); alinkAllText += alink + '\r\n'; content += ``; } @@ -5644,10 +5649,10 @@
`; } if (mode === 'curl') { - let alink = base.convertLinkToCurl(dlink, filename); + let alink = base.convertLinkToCurl(dlink, filename, dpath); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -5655,7 +5660,7 @@ let alink = base.convertLinkToBC(dlink, filename); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
下载 ${filename}
`; } @@ -6113,7 +6118,7 @@ //节点 这的节点树因为不怎么需要就没做完整 let fileNode={ name:file.name,//注意变量名 - path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + path:pNode.name === 'root' ? `` : (pNode.path === '' ? pNode.name : `${pNode.path}/${pNode.name}`),//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 children:[] } pNode.children.push(fileNode); @@ -6267,7 +6272,7 @@ if (mode === 'api') { alinkAllText += dlink + '\r\n'; content += `
-
${filename}
+
${filename}
@@ -6284,10 +6289,10 @@
`; } if (mode === 'aria') { - let alink = base.convertLinkToAria(dlink, filename); + let alink = base.convertLinkToAria(dlink, filename, dpath); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -6308,10 +6313,10 @@
`; } if (mode === 'curl') { - let alink = base.convertLinkToCurl(dlink, filename); + let alink = base.convertLinkToCurl(dlink, filename, dpath); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -6319,7 +6324,7 @@ let alink = base.convertLinkToBC(dlink, filename); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
下载 ${filename}
`; } @@ -6785,7 +6790,7 @@ //节点 let fileNode={ name:file.name,//注意变量名 - path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + path:pNode.name === 'root' ? `` : (pNode.path === '' ? pNode.name : `${pNode.path}/${pNode.name}`),//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 children:[] } pNode.children.push(fileNode); @@ -6923,7 +6928,7 @@ if (mode === 'api') { alinkAllText += dlink + '\r\n'; content += `
-
${filename}
+
${filename}
直接下载(基于浏览器链接) @@ -6941,10 +6946,10 @@
`; } if (mode === 'aria') { - let alink = base.convertLinkToAria(dlink, filename); + let alink = base.convertLinkToAria(dlink, filename, dpath); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -6965,10 +6970,10 @@ `; } if (mode === 'curl') { - let alink = base.convertLinkToCurl(dlink, filename); + let alink = base.convertLinkToCurl(dlink, filename, dpath); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -6976,7 +6981,7 @@ let alink = base.convertLinkToBC(dlink, filename); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
下载 ${filename}
`; @@ -7441,7 +7446,7 @@ //节点 let fileNode={ name:file.file_name, - path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + path:pNode.name === 'root' ? `` : (pNode.path === '' ? pNode.name : `${pNode.path}/${pNode.name}`),//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 children:[] } pNode.children.push(fileNode) @@ -7564,7 +7569,7 @@ if (mode === 'api') { alinkAllText += dlink + '\r\n'; content += `
-
${filename}
+
${filename}
@@ -7581,10 +7586,10 @@
`; } if (mode === 'aria') { - let alink = base.convertLinkToAria(dlink, filename, `--header "Cookie: ${document.cookie}"`); + let alink = base.convertLinkToAria(dlink, filename, dpath, `--header "Cookie: ${document.cookie}"`); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -7605,10 +7610,10 @@ `; } if (mode === 'curl') { - let alink = base.convertLinkToCurl(dlink, filename, `-b "${document.cookie}"`); + let alink = base.convertLinkToCurl(dlink, filename, dpath, `-b "${document.cookie}"`); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -7616,7 +7621,7 @@ let alink = base.convertLinkToBC(dlink, filename, `cookie=${encodeURIComponent(document.cookie)}`); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
下载 ${filename}
`; } @@ -8066,7 +8071,7 @@ //节点 let fileNode={ name:file.file_name,//注意变量名 - path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + path:pNode.name === 'root' ? `` : (pNode.path === '' ? pNode.name : `${pNode.path}/${pNode.name}`),//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 children:[] } pNode.children.push(fileNode); @@ -8185,7 +8190,7 @@ if (mode === 'api') { alinkAllText += dlink + '\r\n'; content += `
-
${filename}
+
${filename}
@@ -8202,10 +8207,10 @@
`; } if (mode === 'aria') { - let alink = base.convertLinkToAria(dlink, filename, `--header "Cookie: ${document.cookie}"`); + let alink = base.convertLinkToAria(dlink, filename, dpath, `--header "Cookie: ${document.cookie}"`); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -8226,10 +8231,10 @@ `; } if (mode === 'curl') { - let alink = base.convertLinkToCurl(dlink, filename, `-b "${document.cookie}"`); + let alink = base.convertLinkToCurl(dlink, filename, dpath, `-b "${document.cookie}"`); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -8237,7 +8242,7 @@ let alink = base.convertLinkToBC(dlink, filename, `cookie=${encodeURIComponent(document.cookie)}`); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
下载 ${filename}
`; } @@ -8733,7 +8738,7 @@ //节点 let fileNode={ name:file.FileName,//注意变量名 - path:pNode.name === 'root' ? `` : `${pNode.path}/${pNode.name}`,//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 + path:pNode.name === 'root' ? `` : (pNode.path === '' ? pNode.name : `${pNode.path}/${pNode.name}`),//一般来说,路径不要特别特别长,不然可能会出问题,不过正常是不会的 children:[] } pNode.children.push(fileNode); @@ -8904,7 +8909,7 @@ if (mode === 'api') { alinkAllText += dlink + '\r\n'; content += `
-
${filename}
+
${filename}
@@ -8921,10 +8926,10 @@
`; } if (mode === 'aria') { - let alink = base.convertLinkToAria(dlink, filename); + let alink = base.convertLinkToAria(dlink, filename, dpath); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -8945,10 +8950,10 @@ `; } if (mode === 'curl') { - let alink = base.convertLinkToCurl(dlink, filename); + let alink = base.convertLinkToCurl(dlink, filename, dpath); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
复制 ${filename} 下载命令行
`; } @@ -8956,7 +8961,7 @@ let alink = base.convertLinkToBC(dlink, filename); alinkAllText += alink + '\r\n'; content += `
-
${filename}
+
${filename}
${decodeURIComponent(alink)}
下载 ${filename}
`; } From 720a99d8b95d888d7bae712df1dab64671fd30af Mon Sep 17 00:00:00 2001 From: KanouAo <214709230@qq.com> Date: Fri, 23 May 2025 12:47:42 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=B9=E4=B8=8B=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...200\205hmjz100\347\232\204\350\257\235.md" | 120 ++++++++----- ...0\275\275\345\212\251\346\211\213.user.js" | 160 ++++++++++++++---- 2 files changed, 206 insertions(+), 74 deletions(-) diff --git "a/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" "b/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" index 11cfcd0..8e22eb5 100644 --- "a/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" +++ "b/\347\273\231\345\274\200\345\217\221\350\200\205hmjz100\347\232\204\350\257\235.md" @@ -1,27 +1,6 @@ 我增加了下载文件夹功能,一键下载文件夹下所有的文件(多个)文件夹嵌套文件夹的这种目录组织都能下载下来,测试环境 win11、edge 浏览器、RPC 发 Aira2 这些下载器我基本都不认识,我只能简单测试下,可能要你去仔细试试,路径我都在网页元素那写好了,"dpath"元素就是。 -API: -Blob 流我不会,老实说,我不会修改下载路径,如果的确没法改,请告诉用户。 - -Aria: -测试环境:是 win11 的 Aira2c 的控制台命令行。 -修改了下载路径。(话说这个有必要吗?不也是 RPC 发到 Aria2?)。 - -RPC: -测试环境:我发到 win11 和轻 nas armbian 上的 Aira2 的都可以 -修改了下载路径。 - -CURL: -测试环境:win11、armbian(linux) -win11 用 curl 命令,如果目录不存在会失败,所以加了创建目录的,但在命令行里测的,不知其它平台和下载器是否适用,如果不行就删掉吧。 -AI 说 为了使命令在 Linux/macOS 上也能工作,可以将其修改为:mkdir -p "下载测试 2" >/dev/null && curl …… -我在 armbian(linux)上测试了上面这条成功,但我没苹果电脑,只有苹果手机,就没测试。 - -BC: -测试环境:win11 -老实说,我不会修改下载路径,如果的确没法改,请告诉用户。 - 我只动了(改)网盘直链下载助手.user,其它我都没改 我虽然会写代码,但方向不是这方面的,油猴我第 1 次做,爬网站数据是第 2 次,上次还是咸鱼捡二手的提醒, 我虽然会 JS 但并不是主 JS 的,对 js 同步异步这边不是很熟悉,你可以看情况修改 @@ -34,39 +13,102 @@ BC: 获取下载链接的时候,我觉得每获取 1 个都展示出来太快,会晃花眼,我调整了下每批次才展示 1 次,你觉得不合适可以改回去 -有的网盘给的链接只有几小时时效,推荐拉高下载器同时任务数,或者等失败了再用脚本再请求次再下载(需要注意设置不覆盖同名文件), - -我平时不怎么下载东西,所以只是简单测试,不知道有没有问题 +有的网盘给的链接时效短,推荐拉高下载器同时任务数,或者等失败了再用脚本再请求次再下载(需要注意设置不覆盖同名文件)。 广度和递归遍历文件夹我犹豫了很久,广度可以改成并发访问多个文件夹加快速度,甚至写了一半了,后来还是觉得递归好点,一是更贴合人类访问逻辑,防反爬,二是一般人分享文件,不会存那么多个文件夹,会是少数几个文件夹里放大量的文件,导致广度并发比递归快不了多少。 -百度: -已完成 +我平时不怎么下载东西,所以以下只是简单测试,不知道有没有问题 -阿里: -关于分享的那部分,原作者的就有问题,我就没做 +好像 1 次只能操作 1 个网盘,比如操作阿里的时候操作移动网盘会卡住,不过也没人这么闲我就不细看了。 -移动: -关于分享的那部分,原作者的就有问题,我就没做 -移动网盘文件数量多,比如几百个的时候,发 RPC 会卡住,但是正常发 RPC,发完会才能操作,这边应该是你这边的问题,请看下(测试环境: windows 发本地的 AriaNg Native 客户端)。 +API: +测试环境: win11 的 Edge 浏览器。 +Blob 流,老实说,我不太会修改下载路径,浏览器和用户操作系统平台不同导致调用的 API 可能不同,适配起来麻烦,老实说我不推荐用户用这种不确定的下载方式。 +我姑且做了点:File System Access API 主要支持 Chromium 内核浏览器(Chrome/Edge),测试了下 win11 下的 Edge 浏览器可行。如果的确懒得改,请告诉用户然后不改算了。 +win11 Edge 好像要点下浏览器的是否将所做更改保存到 XXXX,好像每种网盘只用点 1 次就行,而且好像下载完后再点也可以,有时又不行,我不确定别的浏览器会不会也这样,你可能要做下适配。 -夸克: -我认为连续访问网盘的接口不如直接拿全部 ID 合适,post 协议本身不作数据长度上限,即使后台限制为 1M,也有 65536 个文件 ID 了,你如果觉得不合适可以修改 -我发觉我下个 37 个 G 的单个文件,只有 30 几 K 的速度,连接数只有 1,其实小点的哪个十多个 G 的文件都是正常的,先骂个夸克。 +Aria: +测试环境: win11 的 Aira2c 的控制台命令行。 +因为增加了文件夹下载功能,修改了下载路径。(话说这个有必要吗?不也是 RPC 发到 Aria2?)。 -天翼: +RPC: +测试环境:我发到 win11 和轻 nas armbian 上的 Aira2 的都可以 +因为增加了文件夹下载功能,修改了下载路径。 + +cURL: +测试环境:win11、armbian(linux) +win11 用 curl 命令,如果目录不存在会失败,所以加了创建目录的代码,但是在命令行里测的,不知其它平台和下载器是否适用,如果不行就删掉吧。 +AI 说 为了使命令在 Linux/macOS 上也能工作,可以将其修改为:mkdir -p "下载测试 2" >/dev/null && curl …… +我在 armbian(linux)上测试了上面这条成功,但我没苹果电脑,只有苹果手机,就没测试。 +TODO 如果有 header 的话(比如阿里盘),会提示'refer' 不是内部或外部命令,也不是可运行的程序,因为我不知道你测试环境,我不敢改。详见 TODO + +BC: +测试环境:win11 +老实说,我不会修改下载路径,如果的确没法改,请告诉用户。 +我没改什么所以也没测试。 + +百度(853 个文件,文件夹嵌套文件夹,并有大量记事本文件和少量零散文件模拟文件夹): +Blob:正常 +Aira:命令提示符总随机漏 2/853 个,报 errorCode=22 , status=408,链接本身没问题,可能是服务器限制。power shell 正常。 +RPC:正常 +curl:win 平台正常,armbian(linux)不完全测试正常、macOS 没测。 + +阿里(1127 个文件): +Blob、Aira:可能太多太频繁,会有下失败的,只下到约 300/1127 个。下载 124 个文件正常。 +RPC:只支持 1001 个文件,下载 524 个文件正常。 +curl: 如果有 header 的话,提示'refer' 不是内部或外部命令,也不是可运行的程序,因为我不知道你测试环境,我不敢改。详见 TODO + +移动(386 个文件): +Blob:正常 +Aria:正常 +RPC:下载正常。但文件数量多如几百个的时,发 RPC 会卡住,但是正常发 RPC,发完才能操作,这边应该是你这边的问题,请看下(测试环境: windows 发本地的 AriaNg Native 客户端)。 +curl:win 正常,armbian(linux)不完全测试正常、macOS 没测。 + +天翼(553 个文件): 拿分享的时候有 1 次不完整,不知是不是我看错了 +TODO 我调试过程中 res 出现过 check ip error - curIp=2409::xxxx:xxxx:xxxx:xxxx:xxxx:xxxx, cookiesIp=120.xxx.xxx.xxx,不知是抓 IP 还是 cookies 有问题,注意下 +blob:正常。 +aria:链接有效期这么短的吗?5 分钟?我连 power shell aria 都没跑完,链接就失效了。 +rpc:正常 +curl:cmd 正常,power shell 超过 5 分钟了,armbian(linux)不完全测试正常,macOS 没测。 + +迅雷(1015 个文件): +迅雷可能限制得严,大量下载容易被限制。 +1000 个文件可能请求太多,后面就经常大量获取失败。 +blob:正常。 +aria:正常。 +RPC:ariaNG 只显示 1001 个文件(实际应该有下到的,当时没注意看),缩小数目到 524 个文件正常。 +curl:第 1 天 524 、124 个文件都下不全(curl: (52) Empty reply from server)可能已经风控了。 +win cmd 第 2 天尝试下载 124 个文件成功,power shell 时可能还是被风控了缺文件,armbian(linux)不完全测试正常,macOS 没测。 + +夸克(93 个文件): +我认为连续访问网盘的接口不如直接拿全部 ID 合适,post 协议本身不作数据长度上限,即使后台限制为 1M,也有 65536 个文件 ID 了,你如果觉得不合适可以修改 +我发觉我下个 37 个 G 的单个文件,只有 30 几 K 的速度,连接数只有 1,其它小点的十多个 G 的文件都是正常的,先骂个夸克。 +blob:总有几个缺的? +aria:正常 +rpc:正常 +curl:win 正常,armbian(linux)不完全测试正常、macOS 没测。 -UC: +UC(1102 个文件): 1、我看到 user-agent 写的是夸克的,怕你写错,你注意下。 2、我认为连续访问网盘的接口不如直接拿全部 ID 合适,post 协议本身不作数据长度上限,即使后台限制为 1M,也有 65536 个文件 ID 了,你如果觉得不合适可以修改 +blob:正常。 +Aira:正常 +RPC:正常。 +curl:win 正常,armbian(linux)不完全测试正常、macOS 没测。 -123: -1、我测试时发现大量上传名字按顺序,但内容一样的记事本,它会提示风控,可能抓得严,但名字内容随机就没所谓,要注意下。 +123(782 个文件): +1、我测试时发现大量上传名字按顺序,但内容一样的记事本,它会提示风控,但名字内容随机就没所谓,可能抓得严,你注意下。 2、遍历文件的请求 URL 中间有 XXX=时间戳-XXX-XXX 的格式,我发觉那放 3 个随机数竟然也行,如果不合适你就找下。 cookies 有串 CNZZDATA128030xxxx=191082xxxx-174757xxxx 看起来有点像,可以试试 3、123 网盘的分享站有 3 个,www.123pan.com、www.123684.com、www.123912.com,这3个站请求的url不一样,但都适配www.123pan.com,如果反爬虫你注意下 -4、我想测试下带密码的那种分享的,哎呀妈呀,都没找到网盘资源,好不容易找到个网站,还有 2 个钟就关站了,注册都卡界面 404,BING 找到几个分享,还都不用密码……,最后我分享我自己的,但这样可能会有问题,比如天翼进自己的分享和进别人的分享状况不一样,要不你自己测试下。 +4、我想测试下带密码的那种分享的,哎呀妈呀,都没找到网盘资源,好不容易找到个网站,还有 2 个钟就关站了,注册都卡界面 404,BING 找到几个分享,还都不用密码……,最后我分享我自己的,但这样可能会有问题,比如天翼就是进自己的分享和进别人的分享状况不一样,要不你自己测试下。 5、123 网盘你没做需要密码的分享的那种适配,不用密码的功能正常。我简单看了看,在 cookies 没找到密码,按理说应该有的,我就懒得看代码了。不过按理来说,123 的分享一般都是没密码的,实在不行转换思维让用户自己输密码。 6、123 用着夸克的系统?我看着有的就像从夸克那没注意抄过来的我就删除了,你注意下。 7、然后平台是 IOS 不是 WEB?我就改了下,觉得不行你就改回来。 +8、TODO Aria、Aria2 下载约 30M 的 zip 时 HTTP 状态码 210 异常,blob 提示下载完成其实没下载、但 curl 能下载,(win11 cmd 1.00.67.zip),是不是需要特定请求头的问题? +9、TODO 可能限制得严,大量下载容易被限制,测起来困难,我可能已经被风控了,就没继续测下去。 +blob:不知是弹出保存文件的确认框太慢还是风控,没保存到几个。 +aria:TODO +rpc:TODO +curl:728 个文件被限制下载。 diff --git "a/\357\274\210\346\224\271\357\274\211\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/\357\274\210\346\224\271\357\274\211\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 f89b864..a96ab4d 100644 --- "a/\357\274\210\346\224\271\357\274\211\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/\357\274\210\346\224\271\357\274\211\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" @@ -140,6 +140,7 @@ sicon = (scriptInfo?.icon || "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTAzLjYgMTA3LjRjMy41LTIuMiA4LjktNi4xIDEzLjgtMTIuNXM3LjMtMTIuNSA4LjUtMTYuNWMuNS0xLjcgMi4yLTcuNSAyLjItMTQuNyAwLTEwLjEtMy4zLTI1LjEtMTUuNC0zNi44LTE0LjUtMTQtMzIuMS0xNC4zLTM1LjctMTQuMy04IDAtMTUuNyAxLjktMjIuNiA1LjJDNDQgMjMgMzUuNyAzMS40IDMwLjggNDEuN2MtMS4zIDIuOC00IDQuNy03LjEgNS00IC4zLTcuNSA0LjQtOC45IDkuNi0uNSAxLjktMS42IDMuNS0zLjEgNC43QzQuNCA2Ni44IDAgNzUuNyAwIDg1YzAgNi44IDIuMyAxMy4xIDYuMSAxOC4yIDUuNSA3LjQgMTQuMiAxMi4yIDI0IDEyLjJoNDcuMWM0LjQgMCAxMS0uNSAxOC4zLTMuNSAzLjItMS40IDUuOS0zIDguMS00LjV6IiBmaWxsPSIjQTA5OUYwIi8+PHBhdGggZD0iTTExOS44IDY0LjNjLjEtMTcuMS0xMC40LTI4LTEyLjUtMzAuMUM5NSAyMi4xIDc5LjkgMjEuOCA3Ni45IDIxLjhjLTE3LjYgMC0zMy4zIDEwLjUtMzkuOSAyNi43LS42IDEuMy0xLjggMi4zLTMuNCAyLjNoLS40Yy01LjggMC0xMC42IDQuOC0xMC42IDEwLjd2LjVjMCAxLjQtLjggMi42LTEuOSAzLjNDMTMuNCA2OSA4LjggNzYuOCA4LjggODVjMCAxMi4yIDkuOSAyMi4zIDIyLjIgMjIuM2g0NS4yYzMuNi0uMSAxNy42LS45IDI5LjYtMTIgMi45LTIuOCAxMy45LTEzLjcgMTQtMzF6IiBmaWxsPSIjNTc0QUI4Ii8+PHBhdGggZD0iTTExMC44IDU3LjRsLjIgMy4zYzAgMS4zLTEuMSAyLjQtMi4zIDIuNC0xLjMgMC0yLjMtMS4xLTIuMy0yLjRsLS4xLTIuOHYtLjNjMC0xLjIuOS0yLjIgMi4xLTIuM2guM2MuNyAwIDEuMy4zIDEuNy43LS4yLjEuMy41LjQgMS40em0tMy4zLTEwLjNjMCAxLjItMSAyLjMtMi4yIDIuM2gtLjFjLS44IDAtMS42LS41LTItMS4yLTQuNi04LjMtMTMuMy0xMy41LTIyLjgtMTMuNS0xLjIgMC0yLjMtMS0yLjMtMi4ydi0uMWMwLTEuMiAxLTIuMyAyLjItMi4zaC4xYTMwLjM3IDMwLjM3IDAgMCAxIDE1LjggNC40YzQuNiAyLjggOC40IDYuOCAxMS4xIDExLjUuMS4zLjIuNy4yIDEuMXpNODguMyA3My44TDczLjUgOTMuMmMtMS41IDEuOS0zLjUgMy4xLTUuNyAzLjVoLS4yYy0uNC4xLS44LjEtMS4yLjEtLjYgMC0xLjEtLjEtMS42LS4yLTIuMi0uNC00LjItMS43LTUuNi0zLjVMNDQuMyA3My45Yy0yLTIuNi0yLjUtNS40LTEuNC03LjcuMS0uMS4xLS4yLjItLjIgMS4yLTIgMy41LTMuMiA2LjQtMy4yaDYuNnYtNS43YzAtNi44IDQuNy0xMiAxMC45LTEyIDQuOCAwIDguNSAyLjYgMTAuMyA3LjIuNSAxLjMtLjIgMi43LTEuNSAzLjJzLTIuOC0uMS0zLjMtMS40Yy0xLjEtMi43LTIuOS00LTUuNS00LTMuNSAwLTYgMy02IDd2OC4xYzAgLjUtLjIgMS0uNiAxLjQtLjYuNy0xLjcgMS4xLTIuNiAxLjFoLTguNGMtMS4zIDAtMiAuNC0yLjEuNy0uMi40IDAgMS4zLjkgMi40TDYzLjEgOTBjLjkgMS4yIDIuMSAxLjggMy4zIDEuOHMyLjMtLjYgMy4xLTEuN2wxNC44LTE5LjNjLjktMS4xIDEuMS0yIC45LTIuNC0uMi0uMy0uOS0uNy0yLjEtLjdoLTcuNmMtLjkgMC0xLjctLjUtMi4xLTEuMi0uMy0uNC0uNC0uOC0uNC0xLjMgMC0xLjQgMS4xLTIuNSAyLjUtMi41aDcuNmMzLjEgMCA1LjUgMS4zIDYuNiAzLjVsLjMuN2MuNyAyLjEuMSA0LjYtMS43IDYuOXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4="), mhandler = GM_info.scriptHandler, mversion = GM_info.version, + globalDirHandle=undefined,//TODO Blob下载要用,File System Access API下载要用户给个权限 globalSleep=500, //延时,统一在1个地方修改方便用,可以考虑做个接口给用户修改,做好限制,别让用户做死 globalSleepRandSeed=100,//延时随机种子,直接在上面的数值上加 globalBatchsize=50;//每次处理批次的大小 @@ -474,8 +475,21 @@ convertLinkToCurl(link, filename,path, header) { let terminal = base.getValue('setting_terminal_type'); filename = base.fixFilename(filename); - //TODO win11用curl命令,如果目录不存在会失败,所以加了创建目录的,但在命令行里测的,不知其它平台和下载器是否适用 - return encodeURIComponent(`mkdir "${path}" ${(terminal !== 'wp' && terminal !== 'wc') ? '>/dev/null &&' : '2>nul &'} ${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${path}/${filename}"${header ? (" " + header) : ""}`); + //TODO 用curl命令,如果目录不存在会失败,所以加了创建目录,不知其它操作系统和下载器是否适用 + //TODO 如果有 header 的话,我 win11 会提示'refer' 不是内部或外部命令,也不是可运行的程序,因为我不知道你测试环境,我不敢改。 + switch (terminal){ + case 'wc': + return encodeURIComponent(`mkdir "${path}" 2>nul & curl -L -C - "${link}" -o "${path}/${filename}"${header ? (" " + header) : ""}`); + case 'wp': + return encodeURIComponent(`mkdir "${path}" -Force; curl.exe -L -C - "${link}" -o "${path}/${filename}"${header ? (" " + header) : ""}`); + case 'lt': + case 'ls': + case 'mt'://TODO 我没有苹果电脑,只有armbian(linux),虽然AI说都通用的,先这样写,你可能要实际测试下 + return encodeURIComponent(`mkdir -p "${path}" >/dev/null; curl -L -C - "${link}" -o "${path}/${filename}"${header ? (" " + header) : ""}`); + default: + console.log(`检测不到对应终端 ${terminal}.`); + break; + } }, /** @@ -825,6 +839,49 @@ } }, + + /** + * Blob 文件嵌套文件夹下载 + * @author KanouAo + * @description 通过创建临时链接实现嵌套文件夹下载,windows平台使用File System Access API创建文件夹。该API主要支持Chromium内核浏览器(Chrome/Edge),Firefox和Safari需检测API可用性。 + * @param {Blob} blob - 要下载的 Blob 对象 + * @param {string} filename - 下载时提示保存的文件名 + */ + async fsBlobDownload(blob, filename, path) { + try { + if(navigator.userAgent.match(/Chrom(e|ium)/)){//Chromium内核 + // 创建嵌套目录 + async function createNestedDirectory(baseHandle,path) { + const parts = path.split('/').filter(Boolean); + let currentHandle = baseHandle; + for (const part of parts) { + currentHandle = await currentHandle.getDirectoryHandle(part, { create: true }); + } + return currentHandle; + } + const folderHandle = await createNestedDirectory(globalDirHandle, path); + + // 创建文件并写入 + const fileHandle = await folderHandle.getFileHandle(filename, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(blob); + await writable.close(); + + return true; + }else{ + // 降级到基础下载 + this.blobDownload(blob, filename); + return true; + } + } catch (err) { + console.error('文件夹操作失败:', err); + // 降级到基础下载 + this.blobDownload(blob, filename); + return false; + } + }, + + /** * 可跨域 xmlhttpRequest 请求 * @author hmjz100 @@ -907,6 +964,7 @@ * 发送 GET 请求 * @author 油小猴 * @author hmjz100 + * @author KanouAo * @description 支持进度监控、文件下载和自动重试,可处理被下载工具捕获特殊逻辑 * @param {string} url - 请求地址 * @param {Object} headers - 请求头配置 @@ -931,7 +989,7 @@ } if (type === 'blob') { console.log('【LinkSwift】Get(load) Blob\n请求地址:' + url + '\n请求头部:', headers, '\n请求结果:', res); - res.status === 200 && base.blobDownload(res.response, extra.filename); + res.status === 200 && base.fsBlobDownload(res.response, extra.filename,extra.path); resolve(res); } else { // 尝试格式化请求结果以方便调试 @@ -3195,11 +3253,12 @@ const $width = o.item.find('.pl-progress-inner'); const $text = o.item.find('.pl-progress-inner-text'); const filename = o.link[0].dataset.filename; + const path=o.link[0].dataset.path; const index = o.link[0].dataset.index; const size = Number(o.link[0].dataset.size) || 0; base._resetData(index); - base.get(o.link[0].dataset.link, { "User-Agent": config.$baidu.api.ua.downloadLink }, 'blob', { filename, index }); + base.get(o.link[0].dataset.link, { "User-Agent": config.$baidu.api.ua.downloadLink }, 'blob', { filename, index, path}); let startTime = Date.now(); let prevLoaded = 0; @@ -3303,7 +3362,10 @@ o.stop.hide(); o.back.hide(); }); - doc.on('click', '.listener-download-all', function (e) { + doc.on('click', '.listener-download-all',async function (e) { + if(navigator.userAgent.match(/Chrom(e|ium)/)){//Chromium内核 + globalDirHandle = await window.showDirectoryPicker(); + } $('.pl-item-link.blob').each(function () { if ($(this).css('display') !== 'none') { $(this).click(); @@ -4014,7 +4076,7 @@ alinkAllText += dlink + '\r\n'; content += `
${filename}
- + @@ -4408,11 +4470,12 @@ const $width = o.item.find('.pl-progress-inner'); const $text = o.item.find('.pl-progress-inner-text'); const filename = o.link[0].dataset.filename; + const path=o.link[0].dataset.path; const index = o.link[0].dataset.index; const size = Number(o.link[0].dataset.size) || 0; base._resetData(index); - base.get(e.currentTarget.dataset.link, { "Referer": `https://${location.host}/` }, 'blob', { filename, index }); + base.get(e.currentTarget.dataset.link, { "Referer": `https://${location.host}/` }, 'blob', { filename, index, path}); let startTime = Date.now(); let prevLoaded = 0; @@ -4532,7 +4595,10 @@ $('.listener-link-rpc').click(); $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow"); }); - doc.on('click', '.listener-download-all.blob', function (e) { + doc.on('click', '.listener-download-all',async function (e) {//TODO 这你原本写的是//.listener-download-all.blob,怕你写错改了下 + if(navigator.userAgent.match(/Chrom(e|ium)/)){//Chromium内核 + globalDirHandle = await window.showDirectoryPicker(); + } $('.pl-item-link.blob').each(function () { if ($(this).css('display') !== 'none') { $(this).click(); @@ -4854,8 +4920,8 @@ alinkAllText += dlink + '\r\n'; content += `
${filename}
- - + + @@ -4880,7 +4946,7 @@
`; } if (mode === 'rpc') { - dlink=dlink.replace(' ', '%20')//TODO 阿里的为啥要加这个? + dlink=dlink.replace(' ', '%20') content += `
${filename}
+