百度云批量保存

批量保存百度云文件

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