├── .imgs └── example1.png ├── .prettierrc ├── .babelrc ├── .gitignore ├── .eslintrc.js ├── webpack.config.js ├── src ├── web │ ├── views │ │ ├── Block.js │ │ ├── DownButton.js │ │ ├── Tip.js │ │ └── Vcode.js │ └── index.js └── common │ └── api.js ├── package.json ├── manifest.json └── README.md /.imgs/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkeyWie/baiduyun-helper/HEAD/.imgs/example1.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "eslintIntegration": true, 3 | "singleQuote": true, 4 | "semi": false, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMaps": "inline", 3 | "presets": [ 4 | [ 5 | "env", 6 | { 7 | "useBuiltIns": true 8 | } 9 | ], 10 | "stage-2" 11 | ], 12 | "plugins": ["transform-runtime"] 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | package-lock.json 15 | 16 | # config data 17 | .ext_data/ 18 | .config.dat 19 | .config.dat.bak -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | rules: { 4 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 5 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 6 | 'no-alert': process.env.NODE_ENV === 'production' ? 'error' : 'off', 7 | //强制使用单引号 8 | quotes: ['error', 'single'], 9 | //强制不使用分号结尾 10 | semi: ['error', 'never'], 11 | //禁止声明未使用的变量 12 | 'no-unused-vars': ['error', 'all'] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = () => ({ 4 | entry: { 5 | web: './src/web/index.js', 6 | // hook: './src/hook/index.js' 7 | }, 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: '[name].js', 11 | chunkFilename: '[id].chunk.js' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.js/, 17 | use: 'babel-loader', 18 | include: [path.join(__dirname, './src'), path.join(__dirname, './node_modules/proxyee-down-extension-sdk')] 19 | } 20 | ] 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/web/views/Block.js: -------------------------------------------------------------------------------- 1 | $.extend({ 2 | block: function() { 3 | let blockDiv = $('#pdownBlockDiv') 4 | if (blockDiv.size() == 0) { 5 | blockDiv = $(` 6 |
7 |
请求中...
8 |
9 | `) 10 | $('body').append(blockDiv) 11 | } else { 12 | blockDiv.show() 13 | } 14 | }, 15 | unblock: function() { 16 | $('#pdownBlockDiv').hide() 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "baiduyun-helper", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "A proxyee-down extension for baiduyun", 6 | "main": "index.js", 7 | "dependencies": { 8 | "proxyee-down-extension-sdk": "^1.0.7" 9 | }, 10 | "devDependencies": { 11 | "babel-core": "^6.26.3", 12 | "babel-eslint": "^9.0.0", 13 | "babel-loader": "^7.1.5", 14 | "babel-plugin-transform-runtime": "^6.23.0", 15 | "babel-preset-env": "^1.7.0", 16 | "babel-preset-stage-2": "^6.24.1", 17 | "eslint": "^5.5.0", 18 | "prettier": "1.14.2", 19 | "webpack": "^4.18.0", 20 | "webpack-cli": "^3.1.0" 21 | }, 22 | "scripts": { 23 | "dev": "webpack --mode development --watch", 24 | "build": "webpack --mode production --progress --display-modules --display-reasons" 25 | }, 26 | "author": "monkeyWie", 27 | "license": "MIT" 28 | } 29 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "百度云下载", 3 | "version": 0.38, 4 | "require": { 5 | "min": 3.2 6 | }, 7 | "description": "可获取真实下载链接并推送到Proxyee Down下载,支持单文件、多文件、文件夹", 8 | "homepage": "https://github.com/monkeyWie/baiduyun-helper", 9 | "proxyWildcards": ["pan.baidu.com", "yun.baidu.com"], 10 | "contentScripts": [ 11 | { 12 | "matches": ["^(pan|yun).baidu.com/disk/home.*$"], 13 | "scripts": ["dist/web.js"] 14 | }, 15 | { 16 | "matches": ["^(pan|yun).baidu.com/(s/|share/link).*$"], 17 | "scripts": ["dist/web.js"] 18 | } 19 | ], 20 | "settings": [ 21 | { 22 | "name": "randomUa", 23 | "title": "随机UA", 24 | "type": "Boolean", 25 | "value": true, 26 | "description": "随机生成一个UA去下载,可以避免被限速。" 27 | }, 28 | { 29 | "name": "speUa", 30 | "title": "固定UA", 31 | "type": "String", 32 | "description": "当存在固定UA时,则优先使用固定UA去下载。" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 百度云下载扩展 2 | 3 | 基于 proxyee-down 扩展模块开发,在安装成功后访问百度云页面可以看到 PD 下载按钮。 4 | ![效果](https://github.com/monkeyWie/baiduyun-helper/raw/master/.imgs/example1.png) 5 | 6 | ## 直链下载 7 | 8 | 只能用于单个文件下载。 9 | 10 | ## 压缩链接下载 11 | 12 | 可以支持单文件、多文件、文件夹一起下载,使用压缩链接下载时,百度云会将选择的文件打包成一个 zip 文件下载完毕后解压出来就是之前在勾选下载的文件。 13 | 14 | #### 注意 15 | 16 | 1. 由于百度云的 bug,压缩链接下载文件夹的时候文件夹以及父文件夹名称中不能包含`+`号,不然会导致获取下载链接失败,需要把名字中的`+`去掉再下载。 17 | 2. 由于百度云的限制,压缩链接下载文件时有最大文件数量下载限制,选择的文件不能超过 2000。 18 | 3. 由于百度批量下载的 zip 压缩包不是 zip64 格式,在压缩包里有超过 4G 文件的时候普通的解压工具并不能正确的识别文件大小从而导致解压失败,遇到这种情况时可以用[bdy-unpack](https://github.com/monkeyWie/bdy-unpack)进行解压。 19 | 4. 实测 2018 年 9 月 11 号开始,百度云已经限制了压缩链接下载,当选择的文件总大小超过 300M 就不能下载了,建议使用批量推送来下载多个文件和文件夹。 20 | 21 | ## 批量推送下载 22 | 23 | 直接将选中的文件以 Proxyee Down 默认的下载设置推送到任务列表,每个文件单独创建一个任务。 24 | 25 | ## 常见问题 26 | 27 | - [百度云下载速度太慢](https://community.pdown.org/topic/52) 28 | 29 | ## BUG 提交 30 | 31 | [issue](https://github.com/monkeyWie/baiduyun-helper/issues) 32 | 33 | ## 更新日志 34 | 35 | [releases](https://github.com/monkeyWie/baiduyun-helper/releases) 36 | -------------------------------------------------------------------------------- /src/web/views/DownButton.js: -------------------------------------------------------------------------------- 1 | export default $(` 2 | 3 | 4 | 5 | 6 | PD下载 7 | 8 | 9 | 10 | 直链下载 11 | 压缩链接下载 12 | 批量推送下载 13 |
14 | 使用教程 15 |
16 |
17 | `) 18 | .hover( 19 | function() { 20 | $(this).addClass('button-open') 21 | }, 22 | function() { 23 | $(this).removeClass('button-open') 24 | } 25 | ) 26 | .on('click', 'a[data-menu-id]', function() { 27 | $(this) 28 | .parents('span.g-dropdown-button') 29 | .removeClass('button-open') 30 | }) 31 | -------------------------------------------------------------------------------- /src/web/views/Tip.js: -------------------------------------------------------------------------------- 1 | let pdownTipTimer 2 | $.extend({ 3 | tip(tip, color, time) { 4 | let tipDiv = $('#pdownTipDiv') 5 | if (tipDiv.size() == 0) { 6 | tipDiv = $(` 7 |
8 | ${tip} 9 |
10 | `) 11 | $('body').append(tipDiv) 12 | } else { 13 | if (!tipDiv.is(':hidden')) { 14 | clearTimeout(pdownTipTimer) 15 | } 16 | tipDiv 17 | .css({ 18 | 'background-color': color 19 | }) 20 | .find('span') 21 | .text(tip) 22 | .parent() 23 | .show() 24 | } 25 | time = time || 3500 26 | if (time > 0) { 27 | pdownTipTimer = setTimeout(function() { 28 | $('#pdownTipDiv').hide() 29 | }, time) 30 | } 31 | }, 32 | showInfo(msg, time) { 33 | this.tip(msg, '#3b8cff', time) 34 | }, 35 | showError(msg, time) { 36 | this.tip(msg, '#f8645c', time) 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /src/web/views/Vcode.js: -------------------------------------------------------------------------------- 1 | import api from '../../common/api' 2 | 3 | const vcodeBlockDivHtml = ` 4 |
5 | ` 6 | const vcodeDialogHtml = ` 7 |
8 |
9 |

10 | 11 | 提示 12 | 13 |

14 |
15 | 16 | × 17 | 18 |
19 |
20 |
21 |
22 |
23 |
请输入验证码: 24 | 25 | 点击换一张 26 | 换一张 27 |
28 |
29 |
30 |
31 |
32 | 44 |
45 | ` 46 | 47 | $.extend({ 48 | vcodeConfirm(type, downFiles) { 49 | return new Promise(async (resolve, reject) => { 50 | const vcodeBlockDiv = $(vcodeBlockDivHtml) 51 | const vcodeDialog = $(vcodeDialogHtml) 52 | //加载验证码图片 53 | let vcodeResult 54 | const refreshVcode = async () => { 55 | vcodeResult = await api.getVcode(document.cookie) 56 | vcodeDialog.find('img.img-code').attr('src', vcodeResult.img) 57 | } 58 | await refreshVcode() 59 | //显示 60 | $('body').append(vcodeBlockDiv) 61 | $('body').append(vcodeDialog) 62 | 63 | //提交验证码 64 | vcodeDialog.find('[type=confrim]').click(async function() { 65 | const vcodeInput = vcodeDialog.find('input.input-code').val() 66 | if (vcodeInput == '') { 67 | $.showError('请输入验证码') 68 | return 69 | } 70 | if ($(this).attr('wait') == 1) { 71 | return 72 | } else { 73 | $(this) 74 | .css({ 'background-color': '#77afff', border: '1px solid #77afff' }) 75 | .attr('wait', 1) 76 | } 77 | try { 78 | const result = await api.resolveDownLink( 79 | type, 80 | downFiles, 81 | document.cookie, 82 | yunData, 83 | vcodeInput, 84 | vcodeResult.vcode 85 | ) 86 | if (result.errno == 0) { 87 | vcodeBlockDiv.remove() 88 | vcodeDialog.remove() 89 | resolve(result) 90 | } else if (result.errno == -20) { 91 | $.showError('验证码输入错误') 92 | refreshVcode() 93 | } else if (result.errno == 121) { 94 | $.showError('获取压缩链接失败,文件数量过多') 95 | } else { 96 | $.showError('获取下载链接失败,错误码:' + result.errno) 97 | } 98 | } catch (e) { 99 | console.error(e) 100 | } finally { 101 | $(this) 102 | .css({ 'background-color': '', border: '' }) 103 | .attr('wait', 0) 104 | } 105 | }) 106 | 107 | //刷新验证码 108 | vcodeDialog.find('[type=refresh]').click(function() { 109 | refreshVcode() 110 | }) 111 | 112 | //关闭 113 | vcodeDialog.find('[type=close]').click(function() { 114 | vcodeBlockDiv.remove() 115 | vcodeDialog.remove() 116 | reject() 117 | }) 118 | }) 119 | } 120 | }) 121 | -------------------------------------------------------------------------------- /src/web/index.js: -------------------------------------------------------------------------------- 1 | //兼容IE9 2 | import 'core-js' 3 | 4 | //取页面相关信息并在hash切换时重新加载 5 | let PAGE_INFO 6 | 7 | //监听百度云页面和jQuery加载完毕时 8 | const interval = setInterval(async () => { 9 | if (window.$ && $('.g-button').size() > 0) { 10 | clearInterval(interval) 11 | //初始化pdownSdk 12 | const pdownSdk = require('proxyee-down-extension-sdk').default 13 | //初始化API 14 | const api = require('../common/api').default 15 | //取待下载的文件相关信息 16 | const getDownFiles = async () => { 17 | let checkedFileList = [] 18 | let checkedFiles = getCheckedFiles() 19 | if (checkedFiles.length == 0) { 20 | return checkedFileList 21 | } 22 | let fileList = [] 23 | const searchKey = getSearchKey() 24 | if (searchKey) { 25 | fileList = await getSearchFileList(searchKey) 26 | } else { 27 | fileList = PAGE_INFO.fileList 28 | } 29 | $.each(checkedFiles, function(i, checked) { 30 | $.each(fileList, function(j, file) { 31 | if (file.server_filename == checked) { 32 | checkedFileList.push(file) 33 | return false 34 | } 35 | }) 36 | }) 37 | return checkedFileList 38 | } 39 | //解析选中所有文件及文件夹里的文件直链列表 40 | const getDownFilesDeep = async () => { 41 | let downFiles = await getDownFiles() 42 | let fileList = [] 43 | let dirList = [] 44 | for (let i = 0; i < downFiles.length; i++) { 45 | if (downFiles[i].isdir) { 46 | dirList.push(downFiles[i]) 47 | } else { 48 | fileList.push(downFiles[i]) 49 | } 50 | } 51 | for (let i = 0; i < dirList.length; i++) { 52 | fileList = fileList.concat(await api.resolvePathDeep(dirList[i].path, document.cookie, yunData)) 53 | } 54 | return fileList 55 | } 56 | //取当前目录下的文件列表相关信息 57 | const getPageInfo = async () => { 58 | const path = getPath() 59 | return { 60 | path: path, 61 | vmode: getVmode(), 62 | fileList: await api.resolvePath(path, document.cookie, yunData) 63 | } 64 | } 65 | //监听到目录变化时读取目录里的文件信息 66 | window.addEventListener('hashchange', async function() { 67 | PAGE_INFO = await getPageInfo() 68 | }) 69 | PAGE_INFO = await getPageInfo() 70 | //加载pd下载按钮 71 | const btnsDiv = isShare() 72 | ? $('.g-button[title^=下载]:first').parent('div') 73 | : $('.g-button[title=离线下载]').parent('div') 74 | btnsDiv.append(require('./views/DownButton').default) 75 | //初始化Tip组件 76 | require('./views/Tip') 77 | //初始化Block组件 78 | require('./views/Block') 79 | //初始化验证码组件 80 | require('./views/Vcode') 81 | //初始化cookie 82 | const cookie = await pdownSdk.getCookie() 83 | 84 | //解析选择的文件下载地址并处理异常响应 85 | const downHandle = async (type, downFiles, handle) => { 86 | try { 87 | let result = await api.resolveDownLink(type, downFiles, document.cookie, yunData) 88 | $.unblock() 89 | //判断链接是否解析成功 90 | if (result.errno == 0) { 91 | //解析链接成功,调用成Proxyee Down进行下载 92 | handle(result) 93 | } else if (result.errno == -20) { 94 | //解析失败,需要输入验证码 95 | handle(await $.vcodeConfirm(type, downFiles)) 96 | } else if (result.errno == 112) { 97 | $.showError('页面过期,请刷新重试') 98 | } else if (result.errno == 121) { 99 | $.showError('获取压缩链接失败,文件数量过多') 100 | } else { 101 | $.showError('获取下载链接失败,错误码:' + result.errno) 102 | } 103 | } catch (e) { 104 | console.error(e) 105 | } finally { 106 | $.unblock() 107 | } 108 | } 109 | 110 | //构造Proxyee Down下载请求参数 111 | const buildRequest = (downLink, cookieFlag) => { 112 | let ua = null 113 | /* if (pdown.settings) { 114 | if (pdown.settings.speUa) { 115 | ua = pdown.settings.speUa 116 | } else if (pdown.settings.randomUa) { 117 | ua = 'disk' + parseInt(Math.random() * 1000000) 118 | } 119 | } else { 120 | ua = 'disk' + parseInt(Math.random() * 1000000) 121 | } */ 122 | const request = { 123 | url: downLink, 124 | heads: {} 125 | } 126 | if (ua) { 127 | request.heads['User-Agent'] = ua 128 | } 129 | //如果是下载自己网盘的文件,不带上cookie的去访问直接返回400 130 | if (cookieFlag) { 131 | request.heads['Cookie'] = cookie 132 | } 133 | return request 134 | } 135 | 136 | //构造Proxyee Down下载响应参数 137 | const buildResponse = (name, size) => { 138 | return { 139 | fileName: name, 140 | totalSize: size, 141 | supportRange: true 142 | } 143 | } 144 | 145 | //直链下载,只支持单文件 146 | $(document).on('click', 'a[data-menu-id=pd-direct]', async function() { 147 | $.block() 148 | const downFiles = await getDownFiles() 149 | if (downFiles.length == 0) { 150 | $.showError('请选择要下载的文件') 151 | $.unblock() 152 | return 153 | } 154 | if (downFiles.length > 1 || (downFiles.length == 1 && downFiles[0].isdir == 1)) { 155 | $.showError('直链下载只支持单个文件,请勿选择多个文件或文件夹') 156 | $.unblock() 157 | return 158 | } 159 | downHandle('dlink', downFiles, result => { 160 | const downFile = downFiles[0] 161 | let downLink = isShare() ? result.list[0].dlink : result.dlink[0].dlink 162 | // downLink = downLink.replace(/^https:/, 'http:') 163 | downLink = downLink.replace(/d.pcs.baidu.com/g, 'pcs.baidu.com') 164 | const request = buildRequest(downLink, true) 165 | const response = buildResponse(downFile.server_filename, downFile.size) 166 | pdownSdk.createTask({ request, response }) 167 | }) 168 | }) 169 | 170 | //压缩链接下载,支持单文件和多文件,有最大文件数量下载限制 171 | $(document).on('click', 'a[data-menu-id=pd-batch]', async function() { 172 | $.block() 173 | const downFiles = await getDownFiles() 174 | if (downFiles.length == 0) { 175 | $.showError('请选择要下载的文件') 176 | $.unblock() 177 | return 178 | } 179 | downHandle('batch', downFiles, async result => { 180 | const downLink = result.dlink 181 | /* 182 | 压缩链接下载的文件大小和文件名需要通过Proxyee Down解析API解析出来,百度云的api里没有返回对应信息 183 | 压缩链接不需要传递cookie 184 | */ 185 | try { 186 | const taskForm = await pdownSdk.resolve(buildRequest(downLink)) 187 | pdownSdk.createTask(taskForm) 188 | } catch (error) { 189 | const response = JSON.parse(error.responseText) 190 | if (response.code == 4002) { 191 | $.showError('创建任务失败:文件总大小超过300M或文件夹名称中包含+号,请使用批量推送下载') 192 | } else { 193 | $.showError('创建任务失败:' + response.msg) 194 | } 195 | } 196 | }) 197 | }) 198 | 199 | //直接推送下载,把选中的文件全部解析成直链,推送到Proxyee Down下载,不弹下载框使用默认下载路径和连接数 200 | $(document).on('click', 'a[data-menu-id=pd-push]', async function() { 201 | $.block() 202 | const downFiles = await getDownFilesDeep() 203 | if (downFiles.length == 0) { 204 | $.showError('请选择要下载的文件') 205 | $.unblock() 206 | return 207 | } 208 | downHandle('dlink', downFiles, async result => { 209 | //取Proxyee Down下载相关配置信息 210 | const restConfig = await pdownSdk.getDownConfig() 211 | const downLinkArray = isShare() ? result.list : result.dlink 212 | let count = 0 213 | downLinkArray.forEach(async fileLinkInfo => { 214 | //根据fs_id匹配对应的文件名和大小 215 | const fileInfo = downFiles.find(file => file.fs_id == fileLinkInfo.fs_id) 216 | if (fileInfo) { 217 | //推送至Proxyee Down下载 218 | let downLink = fileLinkInfo.dlink 219 | //downLink = downLink.replace(/^https:/, 'http:') 220 | downLink = downLink.replace(/d.pcs.baidu.com/g, 'pcs.baidu.com') 221 | const request = buildRequest(downLink, true) 222 | const response = buildResponse(fileInfo.server_filename, fileInfo.size) 223 | //根据百度云文件的路径来设置默认的文件下载路径 224 | const config = { 225 | ...{}, 226 | ...restConfig, 227 | ...{ filePath: restConfig.filePath + fileInfo.path.substring(0, fileInfo.path.lastIndexOf('/')) } 228 | } 229 | try { 230 | await pdownSdk.pushTask({ request, response, config }) 231 | } catch (error) {} 232 | $.showInfo(`推送任务成功(${++count}/${downFiles.length}):${fileInfo.server_filename}`) 233 | } 234 | }) 235 | }) 236 | }) 237 | } 238 | }, 100) 239 | 240 | const isShare = () => { 241 | return !!yunData.SHARE_ID 242 | } 243 | 244 | /** 245 | * 取当前选中的文件 246 | */ 247 | const getCheckedFiles = () => { 248 | let checkedFiles = [] 249 | const vmode = getVmode() 250 | //网盘个人主页 251 | if (!yunData.SHARE_ID) { 252 | if (vmode == 'list') { 253 | $('span.EOGexf') 254 | .parent() 255 | .each(function() { 256 | if ( 257 | getDefaultStyle( 258 | $(this) 259 | .find('>span>span') 260 | .get(0), 261 | 'display' 262 | ) != 'none' 263 | ) { 264 | let fileName = $(this) 265 | .find('div.file-name div.text>a') 266 | .text() 267 | checkedFiles.push(fileName) 268 | } 269 | }) 270 | } else if (vmode == 'grid') { 271 | $('div.cEefyz').each(function() { 272 | if ( 273 | getDefaultStyle( 274 | $(this) 275 | .find('>span') 276 | .get(0), 277 | 'display' 278 | ) != 'none' 279 | ) { 280 | let fileName = $(this) 281 | .find('div.file-name>a') 282 | .text() 283 | checkedFiles.push(fileName) 284 | } 285 | }) 286 | } 287 | } else { 288 | //分享页面 289 | if ( 290 | PAGE_INFO.path == '/' && 291 | (yunData.FILEINFO.length == 0 || (yunData.FILEINFO.length == 1 && yunData.FILEINFO[0].isdir == 0)) 292 | ) { 293 | checkedFiles = yunData.FILEINFO.length == 0 ? [yunData.FILENAME] : [yunData.FILEINFO[0].server_filename] 294 | } else { 295 | let listType = vmode == 'list' ? 'dd' : 'div' 296 | $(listType + '.JS-item-active').each(function() { 297 | checkedFiles.push( 298 | $(this) 299 | .find('a.filename') 300 | .text() 301 | ) 302 | }) 303 | } 304 | } 305 | return checkedFiles 306 | } 307 | 308 | const getPath = () => { 309 | let hash = location.hash 310 | let regx = /(^|&|\/|\?)path=([^&]*)(&|$)/i 311 | let result = hash.match(regx) 312 | return result && result.length > 2 ? decodeURIComponent(result[2]) : '/' 313 | } 314 | 315 | const getVmode = () => { 316 | let hash = location.hash 317 | let regx = /(^|&|\/|\?)vmode=([^&]*)(&|$)/i 318 | let result = hash.match(regx) 319 | return result && result.length > 2 ? result[2] : 'list' 320 | } 321 | 322 | const getSearchKey = () => { 323 | let hash = location.hash 324 | let regx = /(^|&|\/|\?)key=([^&]*)(&|$)/i 325 | let result = hash.match(regx) 326 | return result && result.length > 2 ? decodeURIComponent(result[2]) : null 327 | } 328 | 329 | const getDefaultStyle = (obj, attribute) => { 330 | return obj.currentStyle ? obj.currentStyle[attribute] : document.defaultView.getComputedStyle(obj, false)[attribute] 331 | } 332 | -------------------------------------------------------------------------------- /src/common/api.js: -------------------------------------------------------------------------------- 1 | import { jQuery as $ } from 'proxyee-down-extension-sdk' 2 | 3 | /** 4 | * 百度云API列表 5 | */ 6 | export default { 7 | /** 8 | * 通过关键字搜索网盘文件列表 9 | */ 10 | getSearchFileList: (searchKey, cookie, yunData) => { 11 | let params = { 12 | recursion: 1, 13 | order: 'time', 14 | desc: 1, 15 | showempty: 0, 16 | web: 1, 17 | page: 1, 18 | num: 100, 19 | key: searchKey, 20 | channel: 'chunlei', 21 | app_id: 250528, 22 | bdstoken: yunData.MYBDSTOKEN, 23 | logid: getLogID(cookie), 24 | clienttype: 0 25 | } 26 | return new Promise((resolve, reject) => { 27 | $.ajax({ 28 | url: 'https://pan.baidu.com/api/search', 29 | async: true, 30 | global: false, 31 | method: 'GET', 32 | data: params, 33 | success(response) { 34 | resolve(0 === response.errno ? response.list : []) 35 | }, 36 | error(request, status, error) { 37 | reject(request, status, error) 38 | } 39 | }) 40 | }) 41 | }, 42 | /** 43 | * 解析文件夹下所有的文件和文件夹 44 | */ 45 | resolvePath(path, cookie, yunData) { 46 | if (!yunData.SHARE_ID) { 47 | let params = { 48 | dir: path, 49 | bdstoken: yunData.MYBDSTOKEN, 50 | logid: getLogID(cookie), 51 | order: 'size', 52 | desc: 0, 53 | clienttype: 0, 54 | showempty: 0, 55 | web: 1, 56 | channel: 'chunlei', 57 | appid: 250528 58 | } 59 | return new Promise((resolve, reject) => { 60 | $.ajax({ 61 | url: 'https://pan.baidu.com/api/list', 62 | async: true, 63 | global: false, 64 | method: 'GET', 65 | data: params, 66 | success: function(response) { 67 | resolve(0 === response.errno ? response.list : []) 68 | }, 69 | error(request, status, error) { 70 | reject(request, status, error) 71 | } 72 | }) 73 | }) 74 | } else { 75 | if (path == '/') { 76 | let fileList = [] 77 | if (yunData.FILEINFO.length != 0) { 78 | fileList = yunData.FILEINFO 79 | } else { 80 | fileList = [ 81 | { 82 | fs_id: yunData.FS_ID, 83 | isdir: 0, 84 | server_filename: yunData.FILENAME 85 | } 86 | ] 87 | } 88 | return new Promise((resolve, reject) => { 89 | if (fileList.length > 0) { 90 | resolve(fileList) 91 | } else { 92 | reject('fileList is empty') 93 | } 94 | }) 95 | } else { 96 | let params = { 97 | uk: yunData.SHARE_UK, 98 | shareid: yunData.SHARE_ID, 99 | order: 'other', 100 | desc: 1, 101 | showempty: 0, 102 | web: 1, 103 | dir: path, 104 | t: Math.random(), 105 | bdstoken: yunData.MYBDSTOKEN, 106 | channel: 'chunlei', 107 | clienttype: 0, 108 | app_id: 250528, 109 | logid: getLogID(cookie) 110 | } 111 | return new Promise((resolve, reject) => { 112 | $.ajax({ 113 | url: 'https://pan.baidu.com/share/list', 114 | method: 'GET', 115 | async: true, 116 | global: false, 117 | data: params, 118 | success: function(response) { 119 | resolve(0 === response.errno ? response.list : []) 120 | }, 121 | error(request, status, error) { 122 | reject(request, status, error) 123 | } 124 | }) 125 | }) 126 | } 127 | } 128 | }, 129 | /** 130 | * 解析文件夹下所有的文件和文件夹,递归遍历 131 | */ 132 | async resolvePathDeep(path, cookie, yunData) { 133 | let fileList = [] 134 | let resFileList = await this.resolvePath(path, cookie, yunData) 135 | if (resFileList && resFileList.length) { 136 | for (let i = 0; i < resFileList.length; i++) { 137 | let fileInfo = resFileList[i] 138 | if (fileInfo.isdir == 1) { 139 | //是目录的话继续递归遍历 140 | fileList = fileList.concat(await this.resolvePathDeep(fileInfo.path, cookie, yunData)) 141 | } else { 142 | //文件的话加入文件列表 143 | fileList.push(fileInfo) 144 | } 145 | } 146 | } 147 | return fileList 148 | }, 149 | /** 150 | * 根据选择的文件解析出下载链接 151 | * @param {string} type 链接获取类型:dlink batch 152 | * @param {Array} downFiles 待下载的文件 153 | * @param {string} vcodeInput 输入的验证码 154 | * @param {string} vcodeStr 验证码vcode 155 | */ 156 | resolveDownLink(type, downFiles, cookie, yunData, vcodeInput, vcodeStr) { 157 | if (!yunData.SHARE_ID) { 158 | if (type === 'dlink') { 159 | return new Promise(resolve => { 160 | const path = downFiles[0].path 161 | const random = Math.random() 162 | resolve({ 163 | errno: 0, 164 | dlink: [ 165 | { 166 | dlink: `http://d.pcs.baidu.com/rest/2.0/pcs/file?method=download&path=${path}&random=${random}&app_id=498065` 167 | } 168 | ] 169 | }) 170 | }) 171 | } else { 172 | const params = { 173 | sign: getSign(yunData), 174 | timestamp: yunData.timestamp, 175 | fidlist: getFidList(downFiles), 176 | type: type, 177 | channel: 'chunlei', 178 | web: 1, 179 | app_id: 250528, 180 | bdstoken: yunData.MYBDSTOKEN, 181 | logid: getLogID(cookie), 182 | clienttype: 0 183 | } 184 | return new Promise((resolve, reject) => { 185 | $.ajax({ 186 | url: 'https://pan.baidu.com/api/download', 187 | async: true, 188 | global: false, 189 | method: 'POST', 190 | data: params, 191 | success(response) { 192 | resolve(response) 193 | }, 194 | error(request, status, error) { 195 | reject(request, status, error) 196 | } 197 | }) 198 | }) 199 | } 200 | } else { 201 | const params = { 202 | encrypt: 0, 203 | product: 'share', 204 | uk: yunData.SHARE_UK, 205 | primaryid: yunData.SHARE_ID, 206 | fid_list: getFidList(downFiles) 207 | } 208 | if (type == 'batch') { 209 | params.type = type 210 | } 211 | if (yunData.SHARE_PUBLIC != 1) { 212 | let seKey = decodeURIComponent(getCookie(cookie, 'BDCLND')) 213 | params.extra = `{"sekey":"${seKey}"}` 214 | } 215 | //带验证码 216 | if (vcodeInput && vcodeStr) { 217 | params.vcode_input = vcodeInput 218 | params.vcode_str = vcodeStr 219 | } 220 | return new Promise((resolve, reject) => { 221 | $.ajax({ 222 | url: 223 | 'https://pan.baidu.com/api/sharedownload?channel=chunlei&clienttype=0&web=1&app_id=250528&sign=' + 224 | yunData.SIGN + 225 | '×tamp=' + 226 | yunData.timestamp + 227 | '&bdstoken=' + 228 | yunData.MYBDSTOKEN + 229 | '&logid=' + 230 | getLogID(cookie), 231 | async: true, 232 | global: false, 233 | method: 'POST', 234 | data: params, 235 | success(response) { 236 | resolve(response) 237 | }, 238 | error(request, status, error) { 239 | reject(request, status, error) 240 | } 241 | }) 242 | }) 243 | } 244 | }, 245 | /** 246 | * 取百度云验证码 247 | */ 248 | getVcode(cookie) { 249 | const params = { 250 | prod: 'pan', 251 | t: Math.random(), 252 | bdstoken: yunData.MYBDSTOKEN, 253 | channel: 'chunlei', 254 | clienttype: 0, 255 | web: 1, 256 | app_id: 250528, 257 | logid: getLogID(cookie) 258 | } 259 | return new Promise((resolve, reject) => { 260 | $.ajax({ 261 | url: 'https://pan.baidu.com/api/getvcode', 262 | method: 'GET', 263 | async: true, 264 | global: false, 265 | data: params, 266 | success(response) { 267 | resolve(response) 268 | }, 269 | error(request, status, error) { 270 | reject(request, status, error) 271 | } 272 | }) 273 | }) 274 | } 275 | } 276 | 277 | const base64Encode = t => { 278 | let a, 279 | r, 280 | e, 281 | n, 282 | i, 283 | s, 284 | o = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 285 | for (e = t.length, r = 0, a = ''; e > r; ) { 286 | if (((n = 255 & t.charCodeAt(r++)), r == e)) { 287 | a += o.charAt(n >> 2) 288 | a += o.charAt((3 & n) << 4) 289 | a += '==' 290 | break 291 | } 292 | if (((i = t.charCodeAt(r++)), r == e)) { 293 | a += o.charAt(n >> 2) 294 | a += o.charAt(((3 & n) << 4) | ((240 & i) >> 4)) 295 | a += o.charAt((15 & i) << 2) 296 | a += '=' 297 | break 298 | } 299 | s = t.charCodeAt(r++) 300 | a += o.charAt(n >> 2) 301 | a += o.charAt(((3 & n) << 4) | ((240 & i) >> 4)) 302 | a += o.charAt(((15 & i) << 2) | ((192 & s) >> 6)) 303 | a += o.charAt(63 & s) 304 | } 305 | return a 306 | } 307 | 308 | /** 309 | * 生成logID 310 | * @param {*} cookie BAIDUID cookie 311 | */ 312 | const getLogID = cookie => { 313 | let u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/~!@#¥%……&' 314 | let d = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g 315 | let f = String.fromCharCode 316 | 317 | function l(e) { 318 | if (e.length < 2) { 319 | let n = e.charCodeAt(0) 320 | return 128 > n 321 | ? e 322 | : 2048 > n 323 | ? f(192 | (n >>> 6)) + f(128 | (63 & n)) 324 | : f(224 | ((n >>> 12) & 15)) + f(128 | ((n >>> 6) & 63)) + f(128 | (63 & n)) 325 | } 326 | let n = 65536 + 1024 * (e.charCodeAt(0) - 55296) + (e.charCodeAt(1) - 56320) 327 | return f(240 | ((n >>> 18) & 7)) + f(128 | ((n >>> 12) & 63)) + f(128 | ((n >>> 6) & 63)) + f(128 | (63 & n)) 328 | } 329 | 330 | function g(e) { 331 | return (e + '' + Math.random()).replace(d, l) 332 | } 333 | 334 | function m(e) { 335 | let n = [0, 2, 1][e.length % 3] 336 | let t = (e.charCodeAt(0) << 16) | ((e.length > 1 ? e.charCodeAt(1) : 0) << 8) | (e.length > 2 ? e.charCodeAt(2) : 0) 337 | let o = [ 338 | u.charAt(t >>> 18), 339 | u.charAt((t >>> 12) & 63), 340 | n >= 2 ? '=' : u.charAt((t >>> 6) & 63), 341 | n >= 1 ? '=' : u.charAt(63 & t) 342 | ] 343 | return o.join('') 344 | } 345 | 346 | function h(e) { 347 | return e.replace(/[\s\S]{1,3}/g, m) 348 | } 349 | 350 | function p() { 351 | return h(g(new Date().getTime())) 352 | } 353 | 354 | function w(e, n) { 355 | return n 356 | ? p(String(e)) 357 | .replace(/[+\/]/g, function(e) { 358 | return '+' == e ? '-' : '_' 359 | }) 360 | .replace(/=/g, '') 361 | : p(String(e)) 362 | } 363 | // return w(getCookie(name)) 364 | return w(getCookie(cookie, 'BAIDUID')) 365 | } 366 | 367 | const getSign = yunData => { 368 | const signFnc = new Function('return ' + yunData.sign2)() 369 | return base64Encode(signFnc(yunData.sign5, yunData.sign1)) 370 | } 371 | 372 | const getFidList = list => { 373 | if (list.length === 0) { 374 | return null 375 | } 376 | return '[' + list.map(e => e.fs_id).join(',') + ']' 377 | } 378 | 379 | const getCookie = (cookie, name) => { 380 | let o, t 381 | let c = decodeURI 382 | return cookie.length > 0 && ((o = cookie.indexOf(name + '=')), -1 != o) 383 | ? ((o = o + name.length + 1), 384 | (t = cookie.indexOf(';', o)), 385 | -1 == t && (t = cookie.length), 386 | c(cookie.substring(o, t))) 387 | : '' 388 | } 389 | --------------------------------------------------------------------------------