批量保存百度云文件
// ==UserScript== // @name 百度云批量保存 // @name:en_US BDY Batch Saver // @name:zh-CN 百度云批量保存 // @namespace System233 // @version 0.3 // @description 批量保存百度云文件 // @author System233 // @match *://pan.baidu.com/s/* // @match *://yun.baidu.com/s/* // @icon https://t0.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://pan.baidu.com&size=64 // @grant none // @license GPL-3.0-only // @run-at document-start // @source https://github.com/System233/PIGCATS // @notes 20231226 v0.3 修复不识别新弹窗的问题 // @notes 20221117 v0.2 修复嵌套文件夹保存问题 // ==/UserScript== // Copyright (c) 2022 System233 // // This software is released under the GPL-3.0 License. // https://opensource.org/licenses/GPL-3.0 (() => { const logger = Object.assign({}, console); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const waitForSelector = async (selector, node, timeout) => new Promise((resolve, reject) => { node = node || document; timeout = timeout || 10000; const interval = 50; const limit = timeout / interval; ; let times = 0; const handler = () => { const el = node.querySelector(selector); if (el) { resolve(el); } else if (times++ > limit) { reject(new Error("waitForSelector timeout: " + selector)); } else { setTimeout(handler, interval); } }; handler(); }); function getSelectedFileList() { return Array.from(document.querySelectorAll('dd.JS-item-active')); } function getFileList() { return Array.from(document.querySelectorAll('dd[_position]')); } function select(node, selected) { const current = node.matches('.JS-item-active'); if (current == selected) { return; } node.querySelector('span')?.click(); } function isDir(el) { return el.querySelector('div[class*=dir]'); } const getFileName = (node) => { return node.querySelector('a.filename').title; }; const doSave = async (path) => { logger.log('正在保存', path); await sleep(2000); await waitForSelector('[node-type="shareSave"]', document).then(el => el.click()); const waitForLoading = async () => { const list = await waitForSelector('.treeview-root-content', document); while (document.querySelector('.treeview-leaf-loading') != null || list.children.length == 0) { await sleep(100); } }; let lastIndex = 0, index = 0; while (index < path.length) { index = path.indexOf('/', index + 1); if (index == -1) { index = path.length; } const current = path.substring(0, index); await waitForLoading(); let node = document.querySelector(`[node-path="${current}"]`); if (node == null) { const name = path.substring(lastIndex + 1, index); await waitForSelector('.g-button[title="新建文件夹"]', document).then(el => el.click()); await waitForSelector('input.shareFolderInput', document).then(el => el.value = name); await waitForSelector('span.shareFolderConfirm', document).then(el => el.click()); node = await waitForSelector(`[node-path="${current}"]`, document); } lastIndex = index; node.click(); node.scrollIntoView(); } await waitForSelector('[node-type="confirm"]', document).then(el => el.click()); await sleep(100); await waitForSelector('.module-canvas-special-cancel', document).then(el => el.click()); while (true) { if (document.querySelector('.after-trans-dialog')) { logger.log('保存成功', path); return true; } const iframe = document.querySelector('iframe.buy-guide-iframe-coupon[src*=buy]'); if (iframe && iframe.contentDocument.querySelector('[class*=close]')) { logger.log('保存失败', path); Array.from(iframe.contentDocument.querySelectorAll('[class*=close]'), (e) => e.click()); return false; } if (document.querySelector('.vip-guide-intro-tip')) { logger.log('保存失败.old', path); await waitForSelector('.dialog-close', document).then(el => el.click()); return false; } await sleep(50); } }; const doJoinTransfer = async (file, path) => { const name = getFileName(file); const newPath = `${path}${path.endsWith('/') ? '' : '/'}${name}`; logger.log("进入目录", newPath); await waitForSelector('.filename', file).then(x => x.click()); await sleep(100); let files = [], times = 0; for (let i = 0; i < 20 && times < 3; ++i) { await waitForSelector('[style*="visibility: hidden;"] .spinner', document); await sleep(100); let next = getFileList(); if (next.length == files.length) { times++; } else { times = 0; } files = next; } logger.log("目录内容", files.length); // await doTransfer(files, newpath); const start = 0; const end = files.length - 1; const mid = Math.floor((start + end) / 2); await doTransfer(files, newPath, start, mid); await doTransfer(files, newPath, mid + 1, end); await waitForSelector('a[data-deep="-1"]', document).then(x => x.click()); await sleep(50); logger.log("离开目录", newPath); }; const doTransfer = async (files, path, start, end) => { if (start == null) { start = 0; } if (end == null) { end = files.length - 1; } if (end - start < 0) { return; } logger.log("保存路径", path, files.length, `[${start}:${end}]`); files.forEach((file, i) => select(file, i >= start && i <= end)); if (!await doSave(path)) { logger.log("正在切分", path); if (files.length == 1 || start == end) { await doJoinTransfer(files[start], path); } else { const mid = Math.floor((start + end) / 2); await doTransfer(files, path, start, mid); await doTransfer(files, path, mid + 1, end); } } else { logger.log("保存成功", path); } }; const getLastPath = async () => { const name = await waitForSelector('.user-name', document).then(x => x.innerHTML); return localStorage.getItem(`${name}_transfer_save_path`).split('?')[0]; }; const setLastPath = async (value) => { const name = await waitForSelector('.user-name', document).then(x => x.innerHTML); localStorage.setItem(`${name}_transfer_save_path`, `${value}?${Date.now()}`); }; const getSelectedPath = async () => { if (document.querySelector('.save-path-item.check')) { return await getLastPath(); } return await waitForSelector('.treeview-node-on [node-path]', document).then(x => x.getAttribute('node-path')); }; const transfer = async () => { await waitForSelector('[node-type="shareSave"]', document).then(el => el.click()); const confirm = await waitForSelector('[node-type="confirm"]', document); confirm.addEventListener('click', async (e) => { e.stopImmediatePropagation(); waitForSelector('.dialog-control span', document).then(x => x.click()).catch(logger.error); try { const files = getSelectedFileList(); const path = await getSelectedPath(); logger.log("开始转存", files.length); await doTransfer(files, path); await setLastPath(path); } catch (err) { logger.error('发生错误', err); } }, true); }; const load = () => { const html = `<a class="g-button" href="javascript:;" title="批量保存到网盘"><span class="g-button-right"><em class="icon icon-save-disk" title="批量保存到网盘"></em><span class="text" style="width: auto;">批量保存到网盘</span></span></a>`; const div = document.createElement('div'); div.innerHTML = html; const a = div.children[0]; a.addEventListener('click', transfer); waitForSelector('[node-type="shareSave"]', document).then(node => node.after(a)); }; load(); })();