Greasy Fork

来自缓存

Greasy Fork is available in English.

百度网盘文件转存助手

使用百度网盘的时候经常要将别人分享的文件(夹)转存到自己网盘里。对于非会员用户有每次500个文件的限制,超过500个文件只能自己手动弄,比较麻烦,因此有了这个工具,希望能帮到需要的人,不喜轻喷。(目前支持保存到根目录)

当前为 2024-12-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         百度网盘文件转存助手
// @namespace    https://github.com/hyx00000000007
// @version      1.0.2
// @description  使用百度网盘的时候经常要将别人分享的文件(夹)转存到自己网盘里。对于非会员用户有每次500个文件的限制,超过500个文件只能自己手动弄,比较麻烦,因此有了这个工具,希望能帮到需要的人,不喜轻喷。(目前支持保存到根目录)
// @author       shimmer,Teng(samisold)
// @license      BSD
// @match        *://pan.baidu.com/disk/home*
// @match        *://yun.baidu.com/disk/home*
// @match        *://pan.baidu.com/disk/main*
// @match        *://yun.baidu.com/disk/main*
// @require      https://unpkg.com/[email protected]/dist/jquery.min.js
// @connect      baidu.com
// @connect      baidupcs.com
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    window.BaiduTransfer = function(rootPath) {
        this.ROOT_URL = 'https://pan.baidu.com';
        this.bdstoken = null;
        this.shareId = null;
        this.shareRoot = null;
        this.userId = null;
        this.dirList = [];
        this.fileList = [];
        this.rootPath = rootPath || "";
    };

    BaiduTransfer.prototype = {

        request: async function(path, method, params, data, checkErrno) {
            var url = this.ROOT_URL + path;
            if (params) {
                url += '?' + params;
            }

            try {
                var response = await $.ajax({
                    url: url,
                    type: method,
                    headers: {
                        'X-Requested-With': 'XMLHttpRequest'
                    },
                    data: data,
                    xhrFields: {
                        withCredentials: true
                    }
                });
                if (checkErrno && response.errno && response.errno !== 0) {
                    var errno = response.errno;
                    var errmsg = response.show_msg || "过5分钟重试";
                    var customError = new Error(errmsg);
                    customError.errno = errno;
                    throw customError;
                }
                return response;
            } catch (error) {
                throw error;
            }
        },

        createDirectory: async function(dirPath) {
            try {
                await this.listDir(dirPath);
                return;
            } catch (error) {
                if (error.errno !== -9) {
                    throw error;
                }
            }
            var path = "/api/create";
            var params = "a=commit&bdstoken=" + this.bdstoken;
            var data = "path=" + encodeURIComponent(dirPath) + "&isdir=1&block_list=[]";
            return await this.request(path, "POST", params, data, true);
        },

        listDir: async function(dirPath) {
            var path = "/api/list";
            var params = "order=time&desc=1&showempty=0&page=1&num=1000&dir=" + this.customUrlEncode(dirPath) + "&bdstoken=" + this.bdstoken;
            return await this.request(path, "GET", params, null, true);
        },

        transfer: async function(userId, shareId, fsidList, transferPath) {
            var path = "/share/transfer";
            var params = "shareid=" + shareId + "&from=" + userId + "&ondup=newcopy&channel=chunlei&bdstoken=" + this.bdstoken;
            var data = "fsidlist=[" + fsidList.join(",") + "]&path=" + (transferPath || "/");
            var response = await this.request(path, "POST", params, data, false);
            var errno = response.errno;
            if (errno !== 0) {
                if (errno === 2) {
                    var error = new Error("APIParameterError: url=" + path + " param=" + params);
                    throw error;
                } else if (errno === 12) {
                    var limit = response.target_file_nums_limit
                    var count = response.target_file_nums
                    if(limit&&count){
                        var error = new Error("TransferLimitExceededException: limit=" + limit + " count=" + count);
                        throw error;
                    }
                    var error = new Error(response.show_msg);
                    throw error;
                } else if (errno === 1504) {
                    console.log(`Transfer path ${transferPath} exceeds deadline, retry later...`);
                    await new Promise(resolve => setTimeout(resolve, 1000));
                    this.transfer(userId, shareId, fsidList, transferPath);
                } else if (errno === 111) {
                    console.log(`Transfer path ${transferPath} call api too fast , retry later...`);
                    await new Promise(resolve => setTimeout(resolve, 10000));
                    this.transfer(userId, shareId, fsidList, transferPath);
                } else {
                    var error = new Error("BaiduYunPanAPIException: [" + errno + "] " + response.errmsg);
                    throw error;
                }
            }
        },

        getBdstoken: async function() {
            if (this.bdstoken) {
                return this.bdstoken;
            }

            var path = "/api/gettemplatevariable";
            var params = "fields=[\"bdstoken\"]";
            var response = await this.request(path, "GET", params, null, true);
            this.bdstoken = response.result.bdstoken;
            return this.bdstoken;
        },

        getRandsk: async function(shareKey, pwd) {
            var path = "/share/verify";
            var params = "surl=" + shareKey + "&bdstoken=" + this.bdstoken;
            var data = "pwd=" + pwd;
            var response = await this.request(path, "POST", params, data, true);
            return response.randsk;
        },

        getShareData: async function(shareKey, pwd) {
            var path = "/s/1" + shareKey;
            var response = await this.request(path, "GET", null, null ,false);
            var startTag = 'locals.mset(';
            var endTag = '});';

            var startIndex = response.indexOf(startTag);
            if (startIndex === -1) {
                throw new Error("Invalid response: unable to find locals.mset");
            }
            startIndex += startTag.length;

            var endIndex = response.indexOf(endTag, startIndex);
            if (endIndex === -1) {
                throw new Error("Invalid response: unable to find end of locals.mset");
            }

            var jsonStr = response.substring(startIndex, endIndex + 1);
            var data = JSON.parse(jsonStr);
            return {
                userId: data.share_uk,
                shareId: data.shareid,
                bdstoken: data.bdstoken,
                shareRoot: data.file_list[0].parent_path,
                dirList: data.file_list.filter(e => e.isdir === 1).map(function(file) {
                    return {
                        id: file.fs_id,
                        name: file.server_filename,
                    };
                }),
                fileList: data.file_list.filter(e => e.isdir !== 1).map(function(file) {
                    return {
                        id: file.fs_id,
                        name: file.server_filename,
                    };
                })
            };
        },

        updateRandsk: async function(shareKey, pwd) {
            await this.getBdstoken();
            await this.getRandsk(shareKey, pwd);
        },

        initShareData: async function(shareKey, pwd) {
            if (pwd) {
                await this.updateRandsk(shareKey, pwd);
            }
            try {
                var shareData = await this.getShareData(shareKey, pwd);
                this.userId = shareData.userId;
                this.shareId = shareData.shareId;
                this.bdstoken = shareData.bdstoken;
                this.shareRoot = shareData.shareRoot;
                this.dirList = shareData.dirList;
                this.fileList = shareData.fileList;
            } catch (error) {
                if (error.message.indexOf('/share/init')){
                    if (pwd) {
                        throw new Error("Wrong password: " + pwd);
                    } else {
                        throw new Error("Password not specified");
                    }
                }
            }
        },

        transferFiles: async function(fileList, targetPath) {
            if (targetPath) {
                await this.createDirectory(targetPath);
            }

            var maxTransferCount = 100;

            for (var i = 0; i < fileList.length; i += maxTransferCount) {
                var batch = fileList.slice(i, i + maxTransferCount);
                var fsidList = batch.map(function(file) { return file.id; });
                await this.transfer(this.userId, this.shareId, fsidList, targetPath);
            }
            console.log("Transfer " + fileList.length + " files under directory " + targetPath + " success");
        },

        transferDirs: async function(dirList, targetPath) {
            if (targetPath) {
                await this.createDirectory(targetPath);
            }

            if (dirList.length === 0) {
                return;
            }

            var dirPaths = dirList.map(function(dir) {
                return targetPath + '/' + dir.name;
            });

            try {
                await this.transfer(this.userId, this.shareId, dirList.map(dir => dir.id), targetPath);
                dirPaths.forEach(function(dirPath) {
                    console.log(`Transfer directory ${dirPath} success`);
                });
            } catch (error) {
                if (error.message.includes('TransferLimitExceededException:')) {
                    console.log(`Directory ${dirPaths.join(',')} ${error.message}`);

                    if (dirList.length >= 2) {
                        var mid = Math.floor(dirList.length / 2);
                        await this.transferDirs(dirList.slice(0, mid), targetPath);
                        await this.transferDirs(dirList.slice(mid), targetPath);
                    } else {
                        var dir = dirList[0];
                        var dirPath = this.shareRoot;
                        if (targetPath.length > this.rootPath.length) {
                            dirPath += targetPath.slice(this.rootPath.length);
                        }
                        dirPath += '/' + dir.name;

                        var subFiles = await this.listShareDir(this.userId, this.shareId, dirPath);
                        var subDirList = subFiles.filter(function(file) { return file.isDirectory; });
                        var subFileList = subFiles.filter(function(file) { return !file.isDirectory; });

                        if (subDirList.length > 0) {
                            await this.transferDirs(subDirList, targetPath + '/' + dir.name);
                        }
                        if (subFileList.length > 0) {
                            await this.transferFiles(subFileList, targetPath + '/' + dir.name);
                        }
                    }
                } else {
                    throw error;
                }
            }
        },

        listShareDir: async function(userId, shareId, dirPath) {
            var path = "/share/list";
            var page = 1;
            var limit = 100;
            var result = []
            while(true){
                // bug fix by Teng(samisold)
                var params = `uk=${userId}&shareid=${shareId}&order=name&desc=0&showempty=0&page=${page}&num=${limit}&dir=${this.customUrlEncode(dirPath)}`;
                var response = await this.request(path, "GET", params, null ,true);
                var list = response.list;
                list.forEach(function(item) {
                    result.push({
                        id: item.fs_id,
                        name: item.server_filename,
                        isDirectory: item.isdir === 1
                    });
                });
                if(list.length < 100){
                    break;
                }
                page++;
            }
            return result;
        },

        extractShareKey: function(url) {
            try {
                var decodedUrl = decodeURIComponent(url);
                if (decodedUrl.includes("/s/1")) {
                    return decodedUrl.split("/s/1")[1].split("?")[0];
                } else if (decodedUrl.includes("surl=")) {
                    return decodedUrl.split("surl=")[1].split("&")[0];
                }
            } catch (e) {
                console.error("Error extracting share key:", e);
            }
            return null;
        },

        customUrlEncode: function(input) {
            let encoded = '';
            for (let c of input) {
                if (c === ' ' || c === '"' || c === '\'') {
                    encoded += encodeURIComponent(c);
                } else {
                    encoded += c;
                }
            }
            return encoded;
        },


        transferFinal: async function(url, pwd) {
            var shareKey = this.extractShareKey(url);
            if (!shareKey) {
                throw new Error("Unable to extract share key from URL");
            }

            await this.initShareData(shareKey, pwd);

            if (this.dirList.length > 0) {
                await this.transferDirs(this.dirList, this.rootPath);
            }

            if (this.fileList.length > 0) {
                await this.transferFiles(this.fileList, this.rootPath);
            }
        }
    };

    var button = '<div id="shimmer-draggable-button" style="position: fixed; bottom: 20px; left: 20px; z-index: 1000; cursor: grab;">'
                +'<button style="padding: 10px 20px; font-size: 16px; border: none; background-color: #007bff; color: white; cursor: pointer; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); transition: background-color 0.3s ease; outline: none;" onmouseover="this.style.backgroundColor=\'#0056b3\';" onmouseout="this.style.backgroundColor=\'#007bff\';">转存助手</button>'
                +'</div>'
    $('body').append(button)

    // 动态创建弹窗
    var modal = $('<div>', {
        id: 'shimmer-input-modal',
        style: 'display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 1001;'
    }).append(
        $('<div>', {
            style: 'position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 600px; padding: 20px; background-color: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);'
        }).append(
            $('<h2>', {
                text: '转存',
                style: 'margin-top: 0; color: #007bff;'
            }),
            $('<input>', {
                type: 'text',
                id: 'shimmer-input-modal-url',
                placeholder: '分享链接',
                style: 'width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 5px;'
            }),
            $('<input>', {
                type: 'text',
                id: 'shimmer-input-modal-pwd',
                placeholder: '密码',
                style: 'width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 5px;'
            }),
                        $('<button>', {
                id: 'shimmer-input-modal-confirm-button',
                text: '确认',
                style: 'width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease;',
                on: {
                    mouseover: function() {
                        $(this).css('backgroundColor', '#0056b3');
                    },
                    mouseout: function() {
                        $(this).css('backgroundColor', '#007bff');
                    }
                }
            })
        )
    );

    $('body').append(modal);

    var buttonWidth = $('#shimmer-draggable-button').outerWidth();
    var buttonHeight = $('#shimmer-draggable-button').outerHeight();
    var edgeOffset = 50;

    $('#shimmer-draggable-button').css('left', -buttonWidth + edgeOffset + 'px');

    $('#shimmer-draggable-button').on('mouseenter', function() {
        $(this).css('left', '0');
    });

    $('#shimmer-draggable-button').on('mouseleave', function() {
        $(this).css('left', -buttonWidth + edgeOffset + 'px');
    });

    $('#shimmer-draggable-button').on('click', function(event) {
        $('#shimmer-input-modal').show();
    });

    $('#shimmer-input-modal').on('click', function(event) {
        if (event.target === this) {
            $('#shimmer-input-modal').hide();
        }
    });

    $('#shimmer-input-modal-confirm-button').on('click', async function(event) {
        var rootPath = "";
        var transfer = new BaiduTransfer(rootPath);
        var url = $("#shimmer-input-modal-url").val();
        var pwd = $("#shimmer-input-modal-pwd").val();

        // 检查 url 
        if (!url) {
            alert("请输入分享链接");
            return;
        }

        $('#shimmer-draggable-button').css('left', '0');
        $('#shimmer-draggable-button button').text('转存中...').prop('disabled', true);
        $('#shimmer-input-modal').hide();
        alert("转存在后台运行中,请不要关闭浏览器和刷新当前页面,注意左下角按钮的状态(目前这个弹窗需要点击确认)");

        try {
            await transfer.transferFinal(url, pwd);
            console.log("Transfer completed successfully.");
            alert("转存成功");
            location.reload();
        } catch (error) {
            console.error("Error during transfer:", error);
            alert("发生错误了..." + error);
        } finally {
            $('#shimmer-draggable-button').css('left', -buttonWidth + edgeOffset + 'px')
            $('#shimmer-draggable-button button').text('转存助手').prop('disabled', false);
        }
    });

})();