baidu-netdisk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ea47d1e606bc5f1569f6e2e5b2af19cdc15c996cb5d40f3fb4fd2c1190c043f3
4
+ data.tar.gz: 8d30769f5856ad1844beaa1a72e6784f4f6ec8b83509fd52b8a833fde746a04d
5
+ SHA512:
6
+ metadata.gz: 6f715c3b60ce547a64b5590413102692247481c4e9d6d273fbc42d2d501c1187dce3ff5452edb13e903f51e8410406a7f706775cfe262a30f4155e4395913c10
7
+ data.tar.gz: 8165697d3fe2f35ae4e484de731a42678e9930a60b93788827e13baf75b33071220d60a530088b0c47c15cc2d1fa5373262ebd7012f6f7ecc95a19b3d6be050b
data/.env.example ADDED
@@ -0,0 +1,5 @@
1
+ APP_ID='10000'
2
+ APP_KEY='app-key'
3
+ SECRET_KEY='screct-key'
4
+ ACCESS_TOKEN='1234.wasd'
5
+ REFRESH_TOKEN = 4321.qte'
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --require spec_helper
3
+ --order rand
4
+ --format doc
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Hegwin Wang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # Baidu Netdisk
2
+
3
+ [![CI](https://github.com/hegwin/baidu-netdisk/actions/workflows/test.yml/badge.svg)](https://github.com/hegwin/baidu-netdisk/actions/workflows/test.yml)
4
+ [![codecov](https://codecov.io/gh/hegwin/baidu-netdisk/branch/main/graph/badge.svg?token=HCUJ4QDMH6)](https://codecov.io/gh/hegwin/baidu-netdisk)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/75bf545e0efd8f0b24e1/maintainability)](https://codeclimate.com/github/hegwin/baidu-netdisk/maintainability)
6
+
7
+ A Ruby client to upload files to Baidu NetDisk (PCS). It'll auto-split big files and upload slices parallelly.
8
+
9
+
10
+ ## Installation
11
+
12
+ Install with bundler:
13
+
14
+ ```
15
+ bundle add baidu-netdisk
16
+ ```
17
+
18
+ Or install with ruby gem
19
+
20
+ ```
21
+ gem install baidu-netdisk
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ ```ruby
27
+ BaiduNetDisk.config do |c|
28
+ # Required configs
29
+ c.app_id = 'your_app_id'
30
+ c.app_key = 'your_app_key'
31
+ c.secret_key = 'your_secret_key'
32
+
33
+ # Max threads for uploading file slices, default to 1
34
+ c.max_uploading_threads = 4
35
+
36
+ # The following two are optional;
37
+ # Fill them in if you want to explicitly indicate uploading to someone else's storage space
38
+ c.access_token = 'your_access_token'
39
+ c.refresh_token = 'your_refresh_token'
40
+ end
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ This client is designed for the two following scenarios.
46
+
47
+ ### Scenario 1: Using Baidu NetDisk as a backup file storage
48
+
49
+ You always save files to your net disk storage. You don't need a callback to receive auth codes or tokens from Baidu; Instead, you can just get them from your Ruby console.
50
+
51
+ ```ruby
52
+ require 'baidu-netdisk'
53
+
54
+ # It opens a browser to ask for access to Baidu OAuth;
55
+ # you will get your auth code after you log in to your Baidu NetDisk
56
+ BaiduNetDisk::Auth.get_auth_code
57
+
58
+ # Pass the code as the argument your received from the previous step
59
+ BaiduNetDisk::Auth.get_token(auth_code)
60
+
61
+ uploader = BaiduNetDisk::Uploader.new(source_path, target_path)
62
+ uploader.execute
63
+
64
+ # => {"category"=>6, "ctime"=>1676019860, "from_type"=>1, "fs_id"=>121127634951625, "isdir"=>0, "md5"=>"79835de6btc0b3482f51b49088c8ccfb", "mtime"=>1676019860, "path"=>"<target_path>", "server_filename"=>"<file_name>", "size"=>76267, "errno"=>0, "name"=>"<target_path>"}
65
+ ```
66
+
67
+ ### Scenario 2: Upload files to your clients' Baidu NetDisk
68
+
69
+ You should have a web server and need to implement your webhook to receive a callback from Baidu NetDisk after auth.
70
+
71
+ Say your callback URL is `https://www.example.com/webhook`, you should have the:
72
+
73
+ ```ruby
74
+ BaiduNetDisk::Auth.get_auth_code('https://www.example.com/webhook')
75
+
76
+
77
+ # Auth code is fetched in your callbacks
78
+ BaiduNetDisk::Auth.get_token(auth_code, 'https://www.example.com/webhook')
79
+
80
+ uploader = BaiduNetDisk::Uploader.new(source_path, target_path,
81
+ { access_token: 'client.access_token', refresh_token: 'client.refresh_token' })
82
+
83
+ uploader.execute
84
+ ```
85
+
86
+ It provides a hook after token refreshed so you can save tokens after refreshing.
87
+
88
+ ```ruby
89
+ BaiduNetDisk.config do |c|
90
+ c.after_token_refreshed = -> (access_token, refresh_token) {
91
+ user.update access_token: access_token, refresh_token: refresh_token
92
+ }
93
+ end
94
+ ```
95
+
96
+ ## Development
97
+
98
+ ### Copy the env file
99
+
100
+ It reads ENV from .env for testing.
101
+
102
+ ```
103
+ cp .env.example .env
104
+ ```
105
+
106
+ ### Run tests
107
+
108
+ ```
109
+ bundle install
110
+ bundle exec rake
111
+ ```
112
+
113
+ ### Todo
114
+
115
+ 1. Improve MAINTAINABILITY
116
+
117
+ 2. To use other HTTP clients instead of RestClient:
118
+
119
+ 1. RestClient can't catch response body when response is `400 BadRequest`.
120
+
121
+ 2. Currently, we implemented multi-threaded uploading by hands. Consider to use typhoeus with its native hydra to run HTTP requests in parallel.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ # require "rubocop/rake_task"
9
+ # RuboCop::RakeTask.new
10
+
11
+ task default: %i[spec]
@@ -0,0 +1,64 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+
4
+ class BaiduNetDisk::Auth
5
+ class << self
6
+ # This is a debug tool to get auth code for your own Baidu account
7
+ def get_auth_code(redirect_uri = 'oob')
8
+ check_required_configs if redirect_uri == 'oob'
9
+
10
+ url = "https://openapi.baidu.com/oauth/2.0/authorize?response_type=code&client_id=#{BaiduNetDisk.app_key}&redirect_uri=#{redirect_uri}&scope=basic,netdisk&device_id=#{BaiduNetDisk.app_id}"
11
+
12
+ if redirect_uri == 'oob'
13
+ system('open', url)
14
+ else
15
+ RestClient.get url
16
+ end
17
+ end
18
+
19
+ def get_token(auth_code, redirect_uri = 'oob')
20
+ response = RestClient.get "https://openapi.baidu.com/oauth/2.0/token?grant_type=authorization_code&code=#{auth_code}&client_id=#{BaiduNetDisk.app_key}&client_secret=#{BaiduNetDisk.secret_key}&redirect_uri=#{redirect_uri}"
21
+
22
+ if redirect_uri == 'oob'
23
+ response_body = JSON.parse response.body
24
+ BaiduNetDisk.access_token, BaiduNetDisk.refresh_token = response_body.fetch_values('access_token', 'refresh_token')
25
+ end
26
+ end
27
+
28
+ def refresh_access_token(refresh_token)
29
+ response = RestClient.get "https://openapi.baidu.com/oauth/2.0/token?grant_type=refresh_token&refresh_token=#{refresh_token}&client_id=#{BaiduNetDisk.app_key}&client_secret=#{BaiduNetDisk.secret_key}"
30
+
31
+ response_body = JSON.parse response.body
32
+
33
+ access_token, refresh_token = response_body.fetch_values('access_token', 'refresh_token')
34
+
35
+ if BaiduNetDisk.after_token_refreshed&.respond_to? :call
36
+ BaiduNetDisk.after_token_refreshed.call(access_token, refresh_token)
37
+ end
38
+
39
+ [access_token, refresh_token]
40
+ rescue RestClient::BadRequest
41
+ $stdout.puts "Refresh token failed."
42
+ raise BaiduNetDisk::Exception::RefreshTokenFailed
43
+ end
44
+
45
+ private
46
+
47
+ def check_required_configs
48
+ while BaiduNetDisk.app_key.nil? || BaiduNetDisk.app_key.strip.empty?
49
+ $stdout.print "Please enter your App Key:\n"
50
+ BaiduNetDisk.app_key = $stdin.gets.chomp
51
+ end
52
+
53
+ while BaiduNetDisk.app_id.nil? || BaiduNetDisk.app_id.strip.empty?
54
+ $stdout.print "Please enter your App ID:\n"
55
+ BaiduNetDisk.app_id = $stdin.gets.chomp
56
+ end
57
+
58
+ while BaiduNetDisk.secret_key.nil? || BaiduNetDisk.secret_key.strip.empty?
59
+ $stdout.print "Please enter your Secret Key:\n"
60
+ BaiduNetDisk.secret_key = $stdin.gets.chomp
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,35 @@
1
+ module BaiduNetDisk
2
+ module Exception
3
+ # Exceptions following Baidu error codes
4
+ # https://openauth.baidu.com/doc/appendix.html#_4-openapi%E9%94%99%E8%AF%AF%E7%A0%81%E5%88%97%E8%A1%A8
5
+ class PermissionDenied < StandardError; end
6
+ class SliceMissing < StandardError; end
7
+ class FileExisted < StandardError; end
8
+ class SpaceNotEnough < StandardError; end
9
+ class AuthenticationFailed < StandardError; end
10
+ class TooManyRequests < StandardError; end
11
+ class AccessTokenExpired < StandardError; end
12
+ class UnknownError < StandardError; end
13
+ class ArgumentError < StandardError; end
14
+ class UserAuthorizationRequired < StandardError; end
15
+
16
+ # Custom exception
17
+ class UploadDirectoryNotSupported < StandardError; end
18
+ class RefreshTokenNotProvided < StandardError; end
19
+ class RefreshTokenFailed < StandardError; end
20
+
21
+ MAPPING = {
22
+ -10 => SpaceNotEnough,
23
+ -8 => FileExisted,
24
+ -7 => PermissionDenied,
25
+ -6 => AuthenticationFailed,
26
+ 1 => UnknownError,
27
+ 2 => ArgumentError,
28
+ 3 => PermissionDenied,
29
+ 6 => UserAuthorizationRequired,
30
+ 111 => AccessTokenExpired,
31
+ 31034 => TooManyRequests,
32
+ 31363 => SliceMissing
33
+ }
34
+ end
35
+ end
@@ -0,0 +1,167 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+
4
+ class BaiduNetDisk::Uploader
5
+ SLICE_SIZE = 4 * 1024 * 1024
6
+
7
+ def initialize(source_path, target_path, options = {})
8
+ @is_dir = File.directory?(source_path) ? true : false
9
+ raise BaiduNetDisk::Exception::UploadDirectoryNotSupported, 'Uploading directory is not supported at present.' if @is_dir
10
+
11
+ @source_path = source_path
12
+ @target_path = target_path
13
+
14
+ @verbose = options[:verbose]
15
+ @slice_prefix = ".tmp_#{Random.hex(6)}_"
16
+
17
+ @rtype = options[:overwrite] ? 3 : 1
18
+ @file_size = File.size @source_path
19
+ @content_md5 = Digest::MD5.hexdigest(File.read @source_path)
20
+ @upload_id = nil
21
+ @slices = []
22
+ @refresh_token = options[:refresh_token] || BaiduNetDisk.refresh_token
23
+ @access_token = options[:access_token] || BaiduNetDisk.access_token
24
+ end
25
+
26
+ def execute
27
+ prepare
28
+ pre_upload
29
+ upload_by_slices
30
+ create_file
31
+ rescue BaiduNetDisk::Exception::PermissionDenied
32
+ $stderr.puts 'You are not authorised to upload files to your target path.'
33
+ return false
34
+ rescue BaiduNetDisk::Exception::AuthenticationFailed, BaiduNetDisk::Exception::AccessTokenExpired => e
35
+ raise e if @tried_refresh_token
36
+ retry if refresh_token!
37
+ ensure
38
+ clear_up
39
+ end
40
+
41
+ private
42
+
43
+ def prepare
44
+ return if @slices.any?
45
+
46
+ if @file_size > SLICE_SIZE
47
+ $stdout.puts 'Splitting file into slices...' if @verbose
48
+
49
+ `split -b #{SLICE_SIZE} "#{@source_path}" #{@slice_prefix}`
50
+
51
+ Dir["#{Dir.getwd}/#{@slice_prefix}*"].sort.each do |slice_file_path|
52
+ @slices << {
53
+ md5: Digest::MD5.hexdigest(File.read slice_file_path),
54
+ slice_file_path: slice_file_path,
55
+ block_id: nil
56
+ }
57
+ end
58
+
59
+ $stdout.puts "Split file into #{@slices.size} slices. Done." if @verbose
60
+ else
61
+ $stdout.puts 'File size is smaller than 4MB, no split.' if @verbose
62
+
63
+ @slices << { md5: @content_md5, slice_file_path: @source_path, block_id: 0}
64
+ end
65
+ end
66
+
67
+ # API doc in Baidu:
68
+ # https://pan.baidu.com/union/doc/3ksg0s9r7
69
+ def pre_upload
70
+ response = RestClient.post "https://pan.baidu.com/rest/2.0/xpan/file?method=precreate&access_token=#{@access_token}", {
71
+ path: @target_path,
72
+ size: @file_size,
73
+ isdir: @is_dir,
74
+ autoinit: 1,
75
+ rtype: @rtype,
76
+ block_list: @slices.map { |slice| slice[:md5] }.to_json,
77
+ :'content-md5' => @content_md5
78
+ }, { 'User-Agent' => 'pan.baidu.com' }
79
+
80
+ response_body = JSON.parse response.body
81
+
82
+ if response_body['errno'].zero?
83
+ @upload_id = response_body['uploadid']
84
+ $stdout.print "Got upload ID #{@upload_id}\n" if @verbose
85
+
86
+ response_body['block_list'].each.with_index do |block_id, index|
87
+ @slices[index][:block_id] = block_id
88
+ end
89
+ else
90
+ raise BaiduNetDisk::Exception::MAPPING[response_body['errno']] || StandardError
91
+ end
92
+ end
93
+
94
+ # API doc in Baidu:
95
+ # https://pan.baidu.com/union/doc/nksg0s9vi
96
+ def upload_by_slices
97
+ queue = @slices.dup
98
+
99
+ # TODO Consider `typhoeus` rather than invoking multiple threads manually
100
+ BaiduNetDisk.max_uploading_threads.times.map do
101
+ Thread.new do
102
+ while queue.length > 0
103
+ slice = queue.pop
104
+ if slice
105
+ upload_slice_file(slice[:slice_file_path], slice[:block_id])
106
+ end
107
+ end
108
+ end
109
+ end.each(&:join)
110
+
111
+ $stdout.puts 'Slices upload complete.' if @verbose
112
+ end
113
+
114
+ def upload_slice_file(slice_file_path, block_id = 0)
115
+ response = RestClient.post "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2?access_token=#{@access_token}&method=upload&type=tmpfile&path=#{@target_path}&uploadid=#{@upload_id}&partseq=#{block_id}", { file: File.new(slice_file_path, 'rb') }
116
+
117
+ $stdout.puts "Slice ##{block_id} uploaded!" if @verbose
118
+
119
+ response
120
+ end
121
+
122
+ # API doc in Baidu:
123
+ # https://pan.baidu.com/union/doc/rksg0sa17
124
+ def create_file
125
+ response = RestClient.post "https://pan.baidu.com/rest/2.0/xpan/file?method=create&access_token=#{@access_token}", {
126
+ path: @target_path,
127
+ size: @file_size,
128
+ isdir: @is_dir,
129
+ rtype: @rtype,
130
+ uploadid: @upload_id,
131
+ block_list: @slices.map { |slice| slice[:md5] }.to_json
132
+ }, { 'User-Agent' => 'pan.baidu.com' }
133
+
134
+ response_body = JSON.parse response.body
135
+
136
+ if response_body['errno'].zero?
137
+ $stdout.puts "File was successfully created at #{Time.at(response_body['ctime'])}!"
138
+ response_body
139
+ else
140
+ raise BaiduNetDisk::Exception::MAPPING[response_body['errno']] || StandardError, response.body
141
+ end
142
+ end
143
+
144
+ def clear_up
145
+ return if @slices.length < 2
146
+
147
+ $stdout.puts 'Cleaning tmp slice files...' if @verbose
148
+ @slices.each do |slice|
149
+ File.delete(slice[:slice_file_path]) if File.exist?(slice[:slice_file_path])
150
+ end
151
+ $stdout.puts 'Cleaning tmp slice files. Done.' if @verbose
152
+ end
153
+
154
+ def refresh_token!
155
+ @tried_refresh_token = true
156
+
157
+ if @refresh_token
158
+ $stdout.puts 'Access token expired. Trying to refresh...' if @verbose
159
+ @access_token, @refresh_token = BaiduNetDisk::Auth.refresh_access_token(@refresh_token)
160
+ $stdout.puts 'Access token refreshed!' if @verbose
161
+
162
+ true
163
+ else
164
+ raise BaiduNetDisk::Exception::RefreshTokenNotProvided, 'Access token expired. Please provide a refresh token.'
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,3 @@
1
+ module BaiduNetDisk
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,20 @@
1
+ module BaiduNetDisk
2
+ class << self
3
+ attr_accessor :app_id, :app_key, :secret_key, :access_token, :refresh_token, :after_token_refreshed
4
+
5
+ attr_writer :max_uploading_threads
6
+
7
+ def config
8
+ yield self
9
+ end
10
+
11
+ def max_uploading_threads
12
+ @max_uploading_threads || 1
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ require 'baidu-netdisk/exception'
19
+ require 'baidu-netdisk/auth'
20
+ require 'baidu-netdisk/uploader'
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: baidu-netdisk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hegwin Wang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-02-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: codecov
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: dotenv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.8.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.8.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.14.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.14.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 13.0.6
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 13.0.6
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.12.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.12.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.21.2
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.21.2
111
+ - !ruby/object:Gem::Dependency
112
+ name: vcr
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 6.1.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 6.1.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: webmock
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 3.18.1
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 3.18.1
139
+ description: 百度网盘文件上传客户端Ruby版
140
+ email:
141
+ - zwt315@163.com
142
+ - hegwin@hegwin.me
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".env.example"
148
+ - ".rspec"
149
+ - Gemfile
150
+ - LICENSE
151
+ - README.md
152
+ - Rakefile
153
+ - lib/baidu-netdisk.rb
154
+ - lib/baidu-netdisk/auth.rb
155
+ - lib/baidu-netdisk/exception.rb
156
+ - lib/baidu-netdisk/uploader.rb
157
+ - lib/baidu-netdisk/version.rb
158
+ homepage: https://github.com/hegwin/baidu-netdisk
159
+ licenses:
160
+ - MIT
161
+ metadata: {}
162
+ post_install_message:
163
+ rdoc_options: []
164
+ require_paths:
165
+ - lib
166
+ required_ruby_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: 2.6.0
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ requirements: []
177
+ rubygems_version: 3.1.6
178
+ signing_key:
179
+ specification_version: 4
180
+ summary: A client to upload files to Baidu NetDisk
181
+ test_files: []