百度云批量保存

批量保存百度云文件

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
})();