├── LICENSE ├── README.md ├── _bpcs_files_ ├── common.inc.php ├── config │ ├── .gitignore │ └── readme.txt └── core.php └── bpcs_uploader.php /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, oott123 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bpcs_uploader 2 | ============= 3 | 4 | 百度pcs上传脚本 5 | 6 | ## 系统要求 7 | 8 | Linux (or cygwin) with php & curl installed. 9 | 10 | ## 使用方法 11 | 12 | chmod +x bpcs_uploader.php 13 | ./bpcs_uploader.php 14 | 15 | 由于VPS上安装的php可能存在于各种地方,因此运行很可能不正常。请以使用`which php`得到你的php绝对路径,修改bpcs_uploader.php的头一句#!后的路径。 16 | 如果你的php是为了网站环境安装的,那么很有可能你会得到下面这条错误消息: 17 | 18 | > xxx() has been disabled for security reasons 19 | 20 | 那么说明你的环境由于安全原因禁止了部分函数的执行。请看FAQs的1。请使用那条长长的命令代替./bpcs_uploader.php。eg:`php -d disable_functions -d safe_mode=Off -f bpcs_uploader.php quota` 21 | 22 | ### 快速初始化 23 | 24 | ./bpcs_uploader.php quickinit 25 | 26 | 敲下命令直接进入快速初始化流程,输入y,然后打开浏览器访问 https://openapi.baidu.com/device ,在“请输入设备上显示的用户授权码:”文本框中输入上面显示的授权码(这里是`12abcxyz`),并点击继续。 27 | 看到网页上显示“请返回设备继续操作!”后,返回ssh上按下回车后,即完成了初始化配置。 28 | 29 | ### 初始化 30 | 31 | ./bpcs_uploader.php init 32 | 33 | 敲下命令之后会进入初始化流程,这里分段详述设置方法。 34 | 35 | > Uploader initialization will be begin. If you have already configured the uploader before, your old settings will be overwritten. 36 | > Continue? [y/N] y 37 | 38 | 确认初始化。如果之前有初始化过,那么以前的配置将会被覆盖。 39 | 40 | > Please enter your PSC App API Key. You can get this key by visiting http://developer.baidu.com/dev#/create 41 | > If you have already created an app, you can visit http://developer.baidu.com/console#/app and get it in your app's info. 42 | > If you don't want to bother creating an app, you can press Enter to use the demo API Key. 43 | > Doing so (without your own API Key/Secret) will cause the access-token to expire every 30 days, and you'll have to re-initialize when it expires. 44 | > App API KEY [uFBSHEwWE6DD94SQx9z77vgG] : 45 | 46 | 第一步,输入App key。这里需要输入一个有PCS权限的API KEY,如果没有的话直接敲回车就好了,这里会默认使用内置的一组app key,app secret和app folder name,所以只要敲下回车就能直接跳到第四步。如果你没有App secret(例如使用L6g70tBRRIXLsY0Z3HwKqlRE这个Key时),只能获取一个有效期为一个月的access token。如果有一个有PSC权限的API KEY和secret,那么就能获得一个有效期为10年的refresh token,以便长期使用。 47 | 48 | > App API Key has been set to uFBSHEwWE6DD94SQx9z77vgG . 49 | > Please enter your Baidu PSC App API Secret. If you have no idea what it is, keep it blank. 50 | > App API SECRET [] : 51 | 52 | 第二步,输入App secret。如果输入了app secret,将会转到device code模式验证;或者直接输入回车使用oob模式验证。先直接回车: 53 | 54 | > Please enter your app's folder name. You can choose to input this later in the file [ /root/_bpcs_files_/config/appname ]. 55 | > ** Why do I have to enter the app's folder name? Please check the FAQs. ** 56 | > If your app's name has Chinese characters, please ensure that your client supports UTF-8 encoding. 57 | > Below are some Chinese characters for testing. Please make sure that you can read them before you enter Chinese here. 58 | > 这里是一些中文字符。 59 | > If you can't read the characters above, please press Enter and change it manually within the file [ /root/_bpcs_files_/config/appname ]. 60 | > If you have Enter the key [L6g70tBRRIXLsY0Z3HwKqlRE] (by default) , just press Enter. 61 | > App Name [pcstest_oauth] : 62 | 63 | 第三步,这里需要输入app folder name,也就是你申请API时填写的文件夹名字。详情见FAQ 2。因为是使用的默认的key,所以直接回车即可。 64 | 65 | > In the next step, you'll have to grab the access_token generated by Baidu. 66 | > You can check out this link for more information on this procedure: 67 | > http://developer.baidu.com/wiki/index.php?title=docs/pcs/guide/usage_example 68 | > 69 | > Easy Guide: 70 | > 1. Visit https://openapi.baidu.com/oauth/2.0/authorize?response_type=token&client_id=$appkey&redirect_uri=oob&scope=netdisk 71 | > 2. After the page is being redirected (it should show something like OAuth 2.0), copy the current URL to your favorite text editor. 72 | > 3. Grab the access_token part, take only the part between "access_token=" and the next "&" symbol (without quotes). 73 | > 4. Copy it and paste here, then press Enter. 74 | > access_token[] : 75 | 76 | 第四步,获取access token。在浏览器中打开上述URL( https://openapi.baidu.com/oauth/2.0/authorize?response_type=token&client_id=L6g70tBRRIXLsY0Z3HwKqlRE&redirect_uri=oob&scope=netdisk ),进行授权。 77 | 授权完毕后,将会跳到一个写着“百度 Oauth2.0”的页面。复制出其中的网页URL,找到access_token=和&之间的字符串,例如: 78 | `3.**05c2ea85d52c2***************a5.2592000.136***9032.3089166538-23**47` 79 | 将其复制到shell中粘贴并回车。使用这种方式初始化的用户,需要每月重新初始化。 80 | 81 | 如果第三步输入app secret的时候没有留空,将会得到下面的消息: 82 | 83 | > Launch your favorite web browser and visit https://openapi.baidu.com/device 84 | > Input 12abcxyz as the user code if asked. 85 | > After granting access to the application, come back here and press Enter to continue. 86 | 87 | 来到这里,打开浏览器访问 https://openapi.baidu.com/device ,在“请输入设备上显示的用户授权码:”文本框中输入上面显示的授权码(这里是`12abcxyz`),并点击继续。 88 | 看到网页上显示“请返回设备继续操作!”后,返回ssh上按下回车后,即可继续。 89 | 90 | > curl -X GET -k -L "...." 91 | > % Total % Received % Xferd Average Speed Time Time Time Current 92 | > Dload Upload Total Spent Left Speed 93 | > 0 62 0 62 0 0 40 0 --:--:-- 0:00:01 --:--:-- 235 94 | > Access Granted. Your Storage Status: 0.06G/115.00G (0.05%) 95 | > Enjoy! 96 | 97 | 你所看到的输出可能和这里给出的不一样,但是只要看到了存储空间的剩余量,和【Have fun !】提示,即说明成功初始化。 98 | 99 | ### 查询容量(配额) 100 | 101 | ./bpcs_uploader.php quota 102 | 103 | 结果: 104 | > Your Storage Status : 0.06G/115.00G (0.05%) 105 | 106 | ### 上传文件 107 | 108 | ./bpcs_uploader.php upload [path_local] [path_remote] 109 | 110 | 路径格式:`foo/bar/file.ext`(路径中一定要包括文件名) 111 | 上传后,能在百度网盘/我的应用数据/应用名/foo/bar下找到一个叫file.ext的文件。 112 | 113 | ### 下载文件 114 | 115 | ./bpcs_uploader.php download [path_local] [path_remote] 116 | 117 | ### 删除文件 118 | 119 | ./bpcs_uploader.php delete [path_remote] 120 | 121 | ### 离线下载 122 | 123 | ./bpcs_uploader.php fetch [path_remote] [path_to_fetch] 124 | 125 | 注:离线下载已经可以正常使用。 126 | 127 | ## FAQs 128 | 129 | 1. 各种错误提示 130 | 131 | 试试`php -d disable_functions -d safe_mode=Off -f bpcs_uploader.php`。 132 | 133 | 2. 为什么要输入文件夹名? 134 | 135 | 因为百度PCS的权限被限制在了/apps/文件夹名/下。如果发现输入文件夹名后仍然无法上传文件,请通过网页版找到【我的应用数据】找到对应的文件夹名,写入/config/appname文件。上传文件的时候会自动帮您处理文件夹,无需手动写出完整路径。 136 | 137 | 3. 杀软报毒 138 | 139 | 某些PHP后门(比如PHPSpy)会调用shell命令,所以杀软以此作为判断PHP病毒的依据。bpcs_uploader只是调用shell命令来实现一些必要的操作(curl、wget等),可以放心使用。 140 | -------------------------------------------------------------------------------- /_bpcs_files_/common.inc.php: -------------------------------------------------------------------------------- 1 | $access_token, 116 | 'refresh_token' => $refresh_token, 117 | ); 118 | } 119 | function do_oauth_token($appkey) { 120 | echo << $access_token, 144 | 'refresh_token' => $refresh_token, 145 | ); 146 | } 147 | function get_quota($access_token) { 148 | $quota = do_api('https://pcs.baidu.com/rest/2.0/pcs/quota', "method=info&access_token=" . $access_token, 'GET'); 149 | $quota = json_decode($quota, 1); 150 | apierr($quota); 151 | return $quota; 152 | } 153 | function upload_file($access_token, $path, $localfile, $ondup = 'newcopy') { 154 | $path = getpath($path); 155 | $url = "https://c.pcs.baidu.com/rest/2.0/pcs/file?method=upload&access_token=$access_token&path=$path&ondup=$ondup"; 156 | $localfile = escapeshellarg($localfile); 157 | $add = "--form file=@$localfile"; 158 | $cmd = "curl -X POST -k -L $add \"$url\""; 159 | $cmd = cmd($cmd); 160 | $cmd = json_decode($cmd, 1); 161 | apierr($cmd); 162 | return $cmd; 163 | } 164 | function delete_file($access_token, $path) { 165 | $path = getpath($path); 166 | $dele = do_api('https://pcs.baidu.com/rest/2.0/pcs/file', "method=delete&access_token=" . $access_token . '&path=' . $path, 'GET'); 167 | $dele = json_decode($dele, 1); 168 | apierr($dele); 169 | return $dele; 170 | } 171 | function fetch_file($access_token, $path, $url) { 172 | $path = getpath($path); 173 | $fetch = do_api('https://pcs.baidu.com/rest/2.0/pcs/services/cloud_dl', "method=add_task&access_token=" . $access_token . '&save_path=' . $path . '&source_url=' . $url, 'GET'); 174 | $fetch = json_decode($fetch, 1); 175 | apierr($fetch); 176 | return $fetch; 177 | } 178 | //分片上传 179 | function super_file($access_token, $path, $localfile, $ondup = 'newcopy', $sbyte = 1073741824, $temp_dir = '/tmp/') { 180 | //调用split命令进行切割 181 | //split -b200 --verbose rubygems-1.8.25.zip rg/rg1 182 | if (filesize($localfile) <= $sbyte) { 183 | echon('The file isn\'t big enough to split up. Proceed to upload normally.'); 184 | upload_file($access_token, $path, $localfile, $ondup); //直接上传 185 | } 186 | $tempfdir = rtrim($temp_dir, '/') . '/' . uniqid('bpcs_to_upload_'); 187 | if (!mkdir($tempfdir, 0700, true)) { 188 | echon('Cannot create temp dir:' . $tempfdir); 189 | die(9009); 190 | } 191 | $splitcmd = "split -b{$sbyte} $localfile $tempfdir/bpcs_toupload_"; 192 | $splitresult = cmd($splitcmd); 193 | if (trim($splitresult)) { 194 | echon('Split exited with message:' . $splitresult); 195 | } 196 | //遍历临时文件目录 197 | $tempfiles = glob($tempfdir . '/bpcs_toupload_*'); 198 | if (count($tempfiles) < 1) { 199 | //没有生成文件 200 | echon('There are no files to be upload.'); 201 | die(9010); 202 | } elseif (count($tempfiles) == 1) { 203 | //只有一个文件 204 | unlink($tempfiles[0]); //删除它 205 | echon('The file isn\'t big enough to split up. Proceed to upload normally.'); 206 | upload_file($access_token, $path, $localfile, $ondup); //直接上传 207 | return; 208 | } 209 | //开始上传进程 210 | $block_list = array(); 211 | $count = 0; 212 | foreach ($tempfiles as $tempfile) { 213 | //上传临时文件,上传API与上传普通文件无异,只是多一个参数type=tmpfile,取消了其它几个参数。此处将“&type=tmpfile”作为ondup传递,将参数带在请求尾部。 214 | echon('Uploading file in pieces, ' . ($count + 1) . ' out of ' . count($tempfiles) . ' parts... '); 215 | $count++; 216 | $upload_res = upload_file($access_token, '', $tempfile, $ondup . '&type=tmpfile'); 217 | $block_list[] = $upload_res['md5']; 218 | //删除临时文件 219 | unlink($tempfile); 220 | } 221 | //删除临时文件夹 222 | rmdir($tempfdir); 223 | //准备提交API 224 | $block_list = json_encode($block_list); 225 | $param = '{"block_list":' . $block_list . '}'; 226 | $param = 'param=' . urlencode($param); 227 | $path = getpath($path); 228 | $url = "https://pcs.baidu.com/rest/2.0/file?method=createsuperfile&path={$path}&access_token={$access_token}"; 229 | $res = do_api($url, $param); 230 | } 231 | -------------------------------------------------------------------------------- /bpcs_uploader.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php -d disable_functions -d safe_mode=Off 2 | 1G) 136 | $argv[4] = 1073741824; 137 | //因为需要继续下面的操作所以这里没有break 138 | case 5: //设置默认值(临时文件目录->/tmp/) 139 | $argv[5] = '/tmp/'; 140 | //因为需要继续下面的操作所以这里没有break 141 | default: //开始上传操作 142 | super_file($access_token, $argv[3], $argv[2], 'newcopy', $argv[4], $argv[5]); 143 | } 144 | default: 145 | echo <<(optional) 149 | NOTE: 150 | 1.Do not inter "/app/". e.g:if path_remote is 151 | "/app/bpcs_uploader/1.txt",just use "/1.txt". 152 | 2.If you know remote file's MD5(e.g 153 | "/app/bpcs_uploader/1.txt"'s MD5 ),you can inter it after 154 | the path_remote phrase,the app will check while 155 | downloading.(only use in downoading) 156 | Usage: $argv[0] dirdown dir_loacl dir_remote 157 | NOTE: 158 | the app will copy all the file in remote to local. 159 | e.g:If there are 160 | /app/bpcs_uploader/a/1.txt,/app/bpcs_uploader/a/b/2.txt, 161 | /app/bpcs_uploader/a/c/3.txt 162 | and you use "folderdown /home /a",you will find /home/a/1.txt 163 | /home/a/b/2.txt,/home/a/c/3.txt.(in another word,the structure 164 | of the directory and all the files will be copied.) 165 | Usage: $argv[0] delete path_remote 166 | Usage: $argv[0] uploadbig path_local path_remote [slice_size(default:1073741824)] [temp_dir(def:/tmp/)] 167 | Usage: $argv[0] fetch path_remote path_to_fetch 168 | ======================================================================== 169 | 170 | EOF; 171 | break; 172 | } 173 | 174 | function decode($argv, $access_token) { 175 | echon("--" . date("Y-m-d") . " " . date("h:i:sa") . "-- start downloading dir \n", false, 0); //显示开始下载 176 | //获取网盘文件列表 177 | $path = '/apps/' . urlencode(file_get_contents(CONFIG_DIR . '/appname') . '/' . $argv[3]); 178 | $url = "https://pcs.baidu.com/rest/2.0/pcs/file"; 179 | $para = "method=list&access_token=" . $access_token . "&path=" . $path; 180 | $output = do_api($url, $para, 'GET'); 181 | $decode_result = json_decode($output); //解码json 182 | foreach ($decode_result->list as $i) { 183 | if ($i->isdir == 0/*如果是文件的话*/) { 184 | $repeat = str_replace('/apps/' . file_get_contents(CONFIG_DIR . '/appname') . '/', "", $i->path); //挑出网盘文件地址中去掉“/app/bpcs_uploader/”的部分 185 | echon("--" . date("Y-m-d") . " " . date("h:i:sa") . "-- start downloading: " . $argv[2] . $repeat . "\n", false, 0); //显示开始下载 186 | $cmd = $argv[0] . ' download "' . $argv[2] . $repeat . '" "' . $repeat . '" ' . $i->md5; //递归调用此脚本的下载方法 187 | cmd($cmd, true); 188 | echon("--" . date("Y-m-d") . " " . date("h:i:sa") . "-- downloading finished: " . $argv[2] . $repeat . "\n", false, 0); 189 | } else/*是目录的话*/ { 190 | $repeat = str_replace('/apps/' . file_get_contents(CONFIG_DIR . '/appname') . '/', "", $i->path); //挑出网盘文件地址中去掉“/app/bpcs_uploader/”的部分 191 | cmd('mkdir -p ' . '"' . $argv[2] . $repeat . '"'); 192 | $argv3 = str_replace('/apps/' . file_get_contents(CONFIG_DIR . '/appname') . '/', "", $i->path); 193 | decode(array($argv[0], $argv[1], $argv[2], $argv3), $access_token); //递归获取下一层目录 194 | } 195 | } 196 | } 197 | 198 | function md5check($argv2, $argv4) { 199 | if (is_file($argv2)) { 200 | $r = md5_file($argv2); 201 | if ($r == $argv4) { 202 | echon("--" . date("Y-m-d") . " " . date("h:i:sa") . "-- local file's MD5 is " . $r . ",the remote flie's MD5 is " . $argv4 . ",they are equal,so no need to download.", false, 32); 203 | return TRUE; 204 | } else { 205 | echon(chr(34) . "--" . date("Y-m-d") . " " . date("h:i:sa") . "-- local file's MD5 is " . $r . ",the remote flie's MD5 is " . $argv4 . ",they are NOT equal.Need to download.", false, 31); 206 | return FALSE; 207 | } 208 | } else { 209 | echon("--" . date("Y-m-d") . " " . date("h:i:sa") . "-- local file does not exist.Need to download.", false, 31); 210 | return FALSE; 211 | } 212 | } 213 | --------------------------------------------------------------------------------