// ==UserScript== // @name 百度网盘转存(重试机制) // @namespace Wang // @version 1.0.0 // @author Wang // @description 百度网盘转存(重试机制),解决部分因为特殊路径导致报错的问题 // @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* // @match https://pan.baidu.com/s/* // @require https://unpkg.com/jquery@3.7.0/dist/jquery.min.js // @connect baidu.com // @connect baidupcs.com // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // @downloadURL https://update.greasyfork.icu/scripts/550110/%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98%E8%BD%AC%E5%AD%98%EF%BC%88%E9%87%8D%E8%AF%95%E6%9C%BA%E5%88%B6%EF%BC%89.user.js // @updateURL https://update.greasyfork.icu/scripts/550110/%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98%E8%BD%AC%E5%AD%98%EF%BC%88%E9%87%8D%E8%AF%95%E6%9C%BA%E5%88%B6%EF%BC%89.meta.js // ==/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 || ""; this.maxRetries = 5; // 统一的重试次数 }; 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, retryCount = 0) { var path = "/share/transfer"; var params = "shareid=" + shareId + "&from=" + userId + "&ondup=newcopy&channel=chunlei&bdstoken=" + this.bdstoken; var data = "fsidlist=[" + fsidList.join(",") + "]&path=" + encodeURIComponent(transferPath || "/"); try { 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... (${retryCount + 1}/${this.maxRetries})`); if (retryCount < this.maxRetries) { await new Promise(resolve => setTimeout(resolve, 2000)); return await this.transfer(userId, shareId, fsidList, transferPath, retryCount + 1); } else { var error = new Error("Transfer timeout after maximum retries"); throw error; } } else if (errno === 111) { console.log(`Transfer path ${transferPath} call api too fast, retry later... (${retryCount + 1}/${this.maxRetries})`); if (retryCount < this.maxRetries) { await new Promise(resolve => setTimeout(resolve, 15000)); return await this.transfer(userId, shareId, fsidList, transferPath, retryCount + 1); } else { var error = new Error("API rate limit exceeded after maximum retries"); throw error; } } else if (errno === -6 || errno === -9 || errno === 130) { // 网络错误或服务器错误,重试 console.log(`Network/Server error (errno: ${errno}), retrying... (${retryCount + 1}/${this.maxRetries})`); if (retryCount < this.maxRetries) { await new Promise(resolve => setTimeout(resolve, 3000)); return await this.transfer(userId, shareId, fsidList, transferPath, retryCount + 1); } else { var error = new Error(`Network/Server error after maximum retries: errno=${errno}`); throw error; } } else { var error = new Error("BaiduYunPanAPIException: [" + errno + "] " + response.errmsg); throw error; } } console.log(`Successfully transferred ${fsidList.length} files to ${transferPath}`); return response; } catch (error) { // 如果是网络错误且还有重试次数,则重试 if (retryCount < this.maxRetries && (error.message.includes('timeout') || error.message.includes('Network'))) { console.log(`Network error, retrying... (${retryCount + 1}/${this.maxRetries}): ${error.message}`); await new Promise(resolve => setTimeout(resolve, 3000)); return await this.transfer(userId, shareId, fsidList, transferPath, retryCount + 1); } 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, retryCount = 0) { if (targetPath) { await this.createDirectory(targetPath); } var maxTransferCount = 100; try { 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"); } catch (error) { if (retryCount < this.maxRetries) { console.log(`Files transfer failed, retrying... (${retryCount + 1}/${this.maxRetries}): ${error.message}`); await new Promise(resolve => setTimeout(resolve, 5000)); return await this.transferFiles(fileList, targetPath, retryCount + 1); } else { throw error; } } }, transferDirs: async function (dirList, targetPath, retryCount = 0) { 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 { // 其他错误进行重试 if (retryCount < this.maxRetries) { console.log(`Directory transfer failed, retrying... (${retryCount + 1}/${this.maxRetries}): ${error.message}`); await new Promise(resolve => setTimeout(resolve, 10000)); return await this.transferDirs(dirList, targetPath, retryCount + 1); } else { throw error; } } } }, listShareDir: async function (userId, shareId, dirPath, retryCount = 0) { var path = "/share/list"; var page = 1; var limit = 100; var result = [] try { while (true) { var params = `uk=${userId}&shareid=${shareId}&order=name&desc=0&showempty=0&page=${page}&num=100&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; } catch (error) { if (retryCount < this.maxRetries) { console.log(`List share dir failed, retrying... (${retryCount + 1}/${this.maxRetries}): ${error.message}`); await new Promise(resolve => setTimeout(resolve, 3000)); return await this.listShareDir(userId, shareId, dirPath, retryCount + 1); } else { throw error; } } }, 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 if (c === '+') { encoded += '%2B'; // 特殊处理+号,防止被解释为空格 } 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 = '
' $('body').append(button) // 动态创建弹窗 var modal = $('