x 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +6 -0
- data/lib/x/authenticator.rb +3 -1
- data/lib/x/bearer_token_authenticator.rb +1 -1
- data/lib/x/client.rb +52 -16
- data/lib/x/errors/client_error.rb +2 -2
- data/lib/x/errors/error.rb +1 -6
- data/lib/x/errors/http_error.rb +42 -0
- data/lib/x/errors/server_error.rb +2 -2
- data/lib/x/errors/too_many_redirects.rb +2 -2
- data/lib/x/errors/too_many_requests.rb +4 -9
- data/lib/x/media_uploader.rb +24 -22
- data/lib/x/oauth_authenticator.rb +1 -1
- data/lib/x/redirect_handler.rb +5 -6
- data/lib/x/response_parser.rb +10 -33
- data/lib/x/version.rb +1 -1
- data/sig/x.rbs +35 -23
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c6252a6765246fe6ca0820e5ce96f9e66d9611e91b2d59753730fad6b87f67e
|
4
|
+
data.tar.gz: 7a7dc55ece60d848cde08c151e887a1cc86808408a864c05934c4be906b2021a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4216ad5a466dd1a714a638a45d9c0996f40c16247f7fc6eb0ff756d301c3cd63aac5ca95fd421940634e3661db5cb41e19300b73f8ef01056c14e21ad89edea1
|
7
|
+
data.tar.gz: a27036e7dd594f9b13b5cd66267c57b63679352ea3eeeaada13adbba838c6cdad5d8f841fe35b808950fc610808c219679e72334091b3d8de1dca5899ba386b5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## [0.12.0] - 2023-11-02
|
2
|
+
* Ensure Authenticator is passed to RedirectHandler (fc8557b)
|
3
|
+
* Add AUTHENTICATION_HEADER to X::Authenticator base class (85a2818)
|
4
|
+
* Introduce X::HTTPError (90ae132)
|
5
|
+
* Add `code` attribute to error classes (b003639)
|
6
|
+
|
1
7
|
## [0.11.0] - 2023-10-24
|
2
8
|
|
3
9
|
* Add base Authenticator class (8c66ce2)
|
data/README.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
![Tests](https://github.com/sferik/x-ruby/actions/workflows/test.yml/badge.svg)
|
2
|
+
![Linter](https://github.com/sferik/x-ruby/actions/workflows/lint.yml/badge.svg)
|
3
|
+
![Mutant](https://github.com/sferik/x-ruby/actions/workflows/mutant.yml/badge.svg)
|
4
|
+
![Typer Checker](https://github.com/sferik/x-ruby/actions/workflows/type_check.yml/badge.svg)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/x.svg)](https://rubygems.org/gems/x)
|
6
|
+
|
1
7
|
# A [Ruby](https://www.ruby-lang.org) interface to the [X API](https://developer.x.com)
|
2
8
|
|
3
9
|
## Follow
|
data/lib/x/authenticator.rb
CHANGED
data/lib/x/client.rb
CHANGED
@@ -14,9 +14,8 @@ module X
|
|
14
14
|
DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze
|
15
15
|
|
16
16
|
attr_accessor :base_url
|
17
|
+
attr_reader :api_key, :api_key_secret, :access_token, :access_token_secret, :bearer_token
|
17
18
|
|
18
|
-
def_delegators :@authenticator, :bearer_token, :api_key, :api_key_secret, :access_token, :access_token_secret
|
19
|
-
def_delegators :@authenticator, :bearer_token=, :api_key=, :api_key_secret=, :access_token=, :access_token_secret=
|
20
19
|
def_delegators :@connection, :open_timeout, :read_timeout, :write_timeout, :proxy_url, :debug_output
|
21
20
|
def_delegators :@connection, :open_timeout=, :read_timeout=, :write_timeout=, :proxy_url=, :debug_output=
|
22
21
|
def_delegators :@redirect_handler, :max_redirects
|
@@ -24,25 +23,27 @@ module X
|
|
24
23
|
def_delegators :@response_parser, :array_class, :object_class
|
25
24
|
def_delegators :@response_parser, :array_class=, :object_class=
|
26
25
|
|
27
|
-
def initialize(
|
28
|
-
|
26
|
+
def initialize(api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
|
27
|
+
bearer_token: nil,
|
29
28
|
base_url: DEFAULT_BASE_URL,
|
30
29
|
open_timeout: Connection::DEFAULT_OPEN_TIMEOUT,
|
31
30
|
read_timeout: Connection::DEFAULT_READ_TIMEOUT,
|
32
31
|
write_timeout: Connection::DEFAULT_WRITE_TIMEOUT,
|
32
|
+
debug_output: Connection::DEFAULT_DEBUG_OUTPUT,
|
33
33
|
proxy_url: nil,
|
34
|
-
debug_output: nil,
|
35
34
|
array_class: nil,
|
36
35
|
object_class: nil,
|
37
36
|
max_redirects: RedirectHandler::DEFAULT_MAX_REDIRECTS)
|
38
37
|
|
38
|
+
initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
|
39
|
+
@bearer_token = bearer_token
|
40
|
+
initialize_authenticator
|
39
41
|
@base_url = base_url
|
40
|
-
initialize_authenticator(bearer_token, api_key, api_key_secret, access_token, access_token_secret)
|
41
42
|
@connection = Connection.new(open_timeout: open_timeout, read_timeout: read_timeout,
|
42
43
|
write_timeout: write_timeout, debug_output: debug_output, proxy_url: proxy_url)
|
43
44
|
@request_builder = RequestBuilder.new
|
44
|
-
@redirect_handler = RedirectHandler.new(
|
45
|
-
|
45
|
+
@redirect_handler = RedirectHandler.new(connection: @connection, request_builder: @request_builder,
|
46
|
+
max_redirects: max_redirects)
|
46
47
|
@response_parser = ResponseParser.new(array_class: array_class, object_class: object_class)
|
47
48
|
end
|
48
49
|
|
@@ -62,25 +63,60 @@ module X
|
|
62
63
|
execute_request(:delete, endpoint, headers: headers)
|
63
64
|
end
|
64
65
|
|
66
|
+
def api_key=(api_key)
|
67
|
+
@api_key = api_key
|
68
|
+
initialize_authenticator
|
69
|
+
end
|
70
|
+
|
71
|
+
def api_key_secret=(api_key_secret)
|
72
|
+
@api_key_secret = api_key_secret
|
73
|
+
initialize_authenticator
|
74
|
+
end
|
75
|
+
|
76
|
+
def access_token=(access_token)
|
77
|
+
@access_token = access_token
|
78
|
+
initialize_authenticator
|
79
|
+
end
|
80
|
+
|
81
|
+
def access_token_secret=(access_token_secret)
|
82
|
+
@access_token_secret = access_token_secret
|
83
|
+
initialize_authenticator
|
84
|
+
end
|
85
|
+
|
86
|
+
def bearer_token=(bearer_token)
|
87
|
+
@bearer_token = bearer_token
|
88
|
+
initialize_authenticator
|
89
|
+
end
|
90
|
+
|
65
91
|
private
|
66
92
|
|
67
|
-
def
|
68
|
-
@
|
69
|
-
|
70
|
-
|
93
|
+
def initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
|
94
|
+
@api_key = api_key
|
95
|
+
@api_key_secret = api_key_secret
|
96
|
+
@access_token = access_token
|
97
|
+
@access_token_secret = access_token_secret
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize_authenticator
|
101
|
+
@authenticator = if api_key && api_key_secret && access_token && access_token_secret
|
71
102
|
OAuthAuthenticator.new(api_key: api_key, api_key_secret: api_key_secret, access_token: access_token,
|
72
103
|
access_token_secret: access_token_secret)
|
73
|
-
|
104
|
+
elsif bearer_token
|
105
|
+
BearerTokenAuthenticator.new(bearer_token: bearer_token)
|
106
|
+
elsif @authenticator.nil?
|
74
107
|
Authenticator.new
|
108
|
+
else
|
109
|
+
@authenticator
|
75
110
|
end
|
76
111
|
end
|
77
112
|
|
78
113
|
def execute_request(http_method, endpoint, headers:, body: nil)
|
79
114
|
uri = URI.join(base_url, endpoint)
|
80
|
-
request = @request_builder.build(
|
81
|
-
|
115
|
+
request = @request_builder.build(http_method: http_method, uri: uri, body: body, headers: headers,
|
116
|
+
authenticator: @authenticator)
|
82
117
|
response = @connection.perform(request: request)
|
83
|
-
response = @redirect_handler.handle(response: response, request: request, base_url: base_url
|
118
|
+
response = @redirect_handler.handle(response: response, request: request, base_url: base_url,
|
119
|
+
authenticator: @authenticator)
|
84
120
|
@response_parser.parse(response: response)
|
85
121
|
end
|
86
122
|
end
|
data/lib/x/errors/error.rb
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "json"
|
2
|
+
require_relative "error"
|
3
|
+
|
4
|
+
module X
|
5
|
+
# Base HTTP error class
|
6
|
+
class HTTPError < Error
|
7
|
+
JSON_CONTENT_TYPE_REGEXP = %r{application/(problem\+|)json}
|
8
|
+
|
9
|
+
attr_reader :response, :code
|
10
|
+
|
11
|
+
def initialize(response:)
|
12
|
+
super(error_message(response))
|
13
|
+
@response = response
|
14
|
+
@code = response.code
|
15
|
+
end
|
16
|
+
|
17
|
+
def error_message(response)
|
18
|
+
if json?(response)
|
19
|
+
message_from_json_response(response)
|
20
|
+
else
|
21
|
+
response.message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def message_from_json_response(response)
|
26
|
+
response_object = JSON.parse(response.body)
|
27
|
+
if response_object.key?("title") && response_object.key?("detail")
|
28
|
+
"#{response_object.fetch("title")}: #{response_object.fetch("detail")}"
|
29
|
+
elsif response_object.key?("error")
|
30
|
+
response_object.fetch("error")
|
31
|
+
elsif response_object["errors"].instance_of?(Array)
|
32
|
+
response_object.fetch("errors").map { |error| error.fetch("message") }.join(", ")
|
33
|
+
else
|
34
|
+
response.message
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def json?(response)
|
39
|
+
JSON_CONTENT_TYPE_REGEXP.match?(response["content-type"])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,23 +1,18 @@
|
|
1
1
|
require_relative "client_error"
|
2
2
|
|
3
3
|
module X
|
4
|
-
# Rate limit error
|
4
|
+
# Rate limit error class
|
5
5
|
class TooManyRequests < ClientError
|
6
|
-
def initialize(msg, response)
|
7
|
-
@response = response
|
8
|
-
super(msg)
|
9
|
-
end
|
10
|
-
|
11
6
|
def limit
|
12
|
-
|
7
|
+
response["x-rate-limit-limit"].to_i
|
13
8
|
end
|
14
9
|
|
15
10
|
def remaining
|
16
|
-
|
11
|
+
response["x-rate-limit-remaining"].to_i
|
17
12
|
end
|
18
13
|
|
19
14
|
def reset_at
|
20
|
-
Time.at(
|
15
|
+
Time.at(response["x-rate-limit-reset"].to_i)
|
21
16
|
end
|
22
17
|
|
23
18
|
def reset_in
|
data/lib/x/media_uploader.rb
CHANGED
@@ -19,7 +19,7 @@ module X
|
|
19
19
|
boundary: SecureRandom.hex)
|
20
20
|
validate!(file_path: file_path, media_category: media_category)
|
21
21
|
upload_client = client.dup.tap { |c| c.base_url = "https://upload.twitter.com/1.1/" }
|
22
|
-
upload_body = construct_upload_body(file_path, media_type, boundary)
|
22
|
+
upload_body = construct_upload_body(file_path: file_path, media_type: media_type, boundary: boundary)
|
23
23
|
headers = {"Content-Type" => "multipart/form-data, boundary=#{boundary}"}
|
24
24
|
upload_client.post("media/upload.json?media_category=#{media_category}", upload_body, headers: headers)
|
25
25
|
end
|
@@ -28,10 +28,11 @@ module X
|
|
28
28
|
media_category), boundary: SecureRandom.hex, chunk_size_mb: 8)
|
29
29
|
validate!(file_path: file_path, media_category: media_category)
|
30
30
|
upload_client = client.dup.tap { |c| c.base_url = "https://upload.twitter.com/1.1/" }
|
31
|
-
media = init(upload_client, file_path, media_type,
|
31
|
+
media = init(upload_client: upload_client, file_path: file_path, media_type: media_type,
|
32
|
+
media_category: media_category)
|
32
33
|
chunk_size = chunk_size_mb * BYTES_PER_MB
|
33
|
-
|
34
|
-
|
34
|
+
append(upload_client: upload_client, file_paths: split(file_path, chunk_size), media: media,
|
35
|
+
media_type: media_type, boundary: boundary)
|
35
36
|
upload_client.post("media/upload.json?command=FINALIZE&media_id=#{media["media_id"]}")
|
36
37
|
end
|
37
38
|
|
@@ -64,19 +65,13 @@ module X
|
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
67
|
-
def init(upload_client, file_path, media_type, media_category)
|
68
|
-
total_bytes = File.size(file_path)
|
69
|
-
query = "command=INIT&media_type=#{media_type}&media_category=#{media_category}&total_bytes=#{total_bytes}"
|
70
|
-
upload_client.post("media/upload.json?#{query}")
|
71
|
-
end
|
72
|
-
|
73
68
|
def split(file_path, chunk_size)
|
74
69
|
file_number = -1
|
75
70
|
|
76
|
-
[].tap do |
|
71
|
+
[].tap do |file_paths|
|
77
72
|
File.open(file_path, "rb") do |f|
|
78
73
|
while (chunk = f.read(chunk_size))
|
79
|
-
|
74
|
+
file_paths << "#{Dir.mktmpdir}/x#{format("%03d", file_number += 1)}".tap do |path|
|
80
75
|
File.write(path, chunk)
|
81
76
|
end
|
82
77
|
end
|
@@ -84,34 +79,41 @@ module X
|
|
84
79
|
end
|
85
80
|
end
|
86
81
|
|
87
|
-
def
|
88
|
-
|
82
|
+
def init(upload_client:, file_path:, media_type:, media_category:)
|
83
|
+
total_bytes = File.size(file_path)
|
84
|
+
query = "command=INIT&media_type=#{media_type}&media_category=#{media_category}&total_bytes=#{total_bytes}"
|
85
|
+
upload_client.post("media/upload.json?#{query}")
|
86
|
+
end
|
87
|
+
|
88
|
+
def append(upload_client:, file_paths:, media:, media_type:, boundary: SecureRandom.hex)
|
89
|
+
threads = file_paths.map.with_index do |file_path, index|
|
89
90
|
Thread.new do
|
90
|
-
upload_body = construct_upload_body(
|
91
|
+
upload_body = construct_upload_body(file_path: file_path, media_type: media_type, boundary: boundary)
|
91
92
|
query = "command=APPEND&media_id=#{media["media_id"]}&segment_index=#{index}"
|
92
93
|
headers = {"Content-Type" => "multipart/form-data, boundary=#{boundary}"}
|
93
|
-
upload_chunk(upload_client, query, upload_body,
|
94
|
+
upload_chunk(upload_client: upload_client, query: query, upload_body: upload_body, file_path: file_path,
|
95
|
+
headers: headers)
|
94
96
|
end
|
95
97
|
end
|
96
98
|
threads.each(&:join)
|
97
99
|
end
|
98
100
|
|
99
|
-
def upload_chunk(upload_client
|
101
|
+
def upload_chunk(upload_client:, query:, upload_body:, file_path:, headers: {})
|
100
102
|
upload_client.post("media/upload.json?#{query}", upload_body, headers: headers)
|
101
103
|
rescue NetworkError, ServerError
|
102
104
|
retries ||= 0
|
103
105
|
((retries += 1) < MAX_RETRIES) ? retry : raise
|
104
106
|
ensure
|
105
|
-
|
107
|
+
cleanup_file(file_path)
|
106
108
|
end
|
107
109
|
|
108
|
-
def
|
109
|
-
dirname = File.dirname(
|
110
|
-
File.delete(
|
110
|
+
def cleanup_file(file_path)
|
111
|
+
dirname = File.dirname(file_path)
|
112
|
+
File.delete(file_path)
|
111
113
|
Dir.delete(dirname) if Dir.empty?(dirname)
|
112
114
|
end
|
113
115
|
|
114
|
-
def construct_upload_body(file_path
|
116
|
+
def construct_upload_body(file_path:, media_type:, boundary: SecureRandom.hex)
|
115
117
|
"--#{boundary}\r\n" \
|
116
118
|
"Content-Disposition: form-data; name=\"media\"; filename=\"#{File.basename(file_path)}\"\r\n" \
|
117
119
|
"Content-Type: #{media_type}\r\n\r\n" \
|
data/lib/x/redirect_handler.rb
CHANGED
@@ -11,23 +11,22 @@ module X
|
|
11
11
|
DEFAULT_MAX_REDIRECTS = 10
|
12
12
|
|
13
13
|
attr_accessor :max_redirects
|
14
|
-
attr_reader :
|
14
|
+
attr_reader :connection, :request_builder
|
15
15
|
|
16
|
-
def initialize(
|
16
|
+
def initialize(connection: Connection.new, request_builder: RequestBuilder.new,
|
17
17
|
max_redirects: DEFAULT_MAX_REDIRECTS)
|
18
|
-
@authenticator = authenticator
|
19
18
|
@connection = connection
|
20
19
|
@request_builder = request_builder
|
21
20
|
@max_redirects = max_redirects
|
22
21
|
end
|
23
22
|
|
24
|
-
def handle(response:, request:, base_url:, redirect_count: 0)
|
23
|
+
def handle(response:, request:, base_url:, authenticator: Authenticator.new, redirect_count: 0)
|
25
24
|
if response.is_a?(Net::HTTPRedirection)
|
26
25
|
raise TooManyRedirects, "Too many redirects" if redirect_count > max_redirects
|
27
26
|
|
28
27
|
new_uri = build_new_uri(response, base_url)
|
29
28
|
|
30
|
-
new_request = build_request(request, new_uri, Integer(response.code))
|
29
|
+
new_request = build_request(request, new_uri, Integer(response.code), authenticator)
|
31
30
|
new_response = connection.perform(request: new_request)
|
32
31
|
|
33
32
|
handle(response: new_response, request: new_request, base_url: base_url, redirect_count: redirect_count + 1)
|
@@ -44,7 +43,7 @@ module X
|
|
44
43
|
URI.join(base_url, location)
|
45
44
|
end
|
46
45
|
|
47
|
-
def build_request(request, new_uri, response_code)
|
46
|
+
def build_request(request, new_uri, response_code, authenticator)
|
48
47
|
http_method, body = case response_code
|
49
48
|
in 307 | 308
|
50
49
|
[request.method.downcase.to_sym, request.body]
|
data/lib/x/response_parser.rb
CHANGED
@@ -3,7 +3,7 @@ require "net/http"
|
|
3
3
|
require_relative "errors/bad_gateway"
|
4
4
|
require_relative "errors/bad_request"
|
5
5
|
require_relative "errors/connection_exception"
|
6
|
-
require_relative "errors/
|
6
|
+
require_relative "errors/http_error"
|
7
7
|
require_relative "errors/forbidden"
|
8
8
|
require_relative "errors/gateway_timeout"
|
9
9
|
require_relative "errors/gone"
|
@@ -19,7 +19,7 @@ require_relative "errors/unprocessable_entity"
|
|
19
19
|
module X
|
20
20
|
# Process HTTP responses
|
21
21
|
class ResponseParser
|
22
|
-
|
22
|
+
ERROR_MAP = {
|
23
23
|
400 => BadRequest,
|
24
24
|
401 => Unauthorized,
|
25
25
|
403 => Forbidden,
|
@@ -35,7 +35,7 @@ module X
|
|
35
35
|
503 => ServiceUnavailable,
|
36
36
|
504 => GatewayTimeout
|
37
37
|
}.freeze
|
38
|
-
JSON_CONTENT_TYPE_REGEXP = %r{application/
|
38
|
+
JSON_CONTENT_TYPE_REGEXP = %r{application/json}
|
39
39
|
|
40
40
|
attr_accessor :array_class, :object_class
|
41
41
|
|
@@ -45,48 +45,25 @@ module X
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def parse(response:)
|
48
|
-
raise error(response) unless
|
48
|
+
raise error(response) unless response.is_a?(Net::HTTPSuccess)
|
49
49
|
|
50
|
-
|
50
|
+
return unless json?(response)
|
51
|
+
|
52
|
+
JSON.parse(response.body, array_class: array_class, object_class: object_class)
|
51
53
|
end
|
52
54
|
|
53
55
|
private
|
54
56
|
|
55
|
-
def success?(response)
|
56
|
-
response.is_a?(Net::HTTPSuccess)
|
57
|
-
end
|
58
|
-
|
59
57
|
def error(response)
|
60
|
-
error_class(response).new(
|
58
|
+
error_class(response).new(response: response)
|
61
59
|
end
|
62
60
|
|
63
61
|
def error_class(response)
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
def error_message(response)
|
68
|
-
if json?(response)
|
69
|
-
message_from_json_response(response)
|
70
|
-
else
|
71
|
-
response.message
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def message_from_json_response(response)
|
76
|
-
response_object = JSON.parse(response.body)
|
77
|
-
if response_object.key?("title") && response_object.key?("detail")
|
78
|
-
"#{response_object.fetch("title")}: #{response_object.fetch("detail")}"
|
79
|
-
elsif response_object.key?("error")
|
80
|
-
response_object.fetch("error")
|
81
|
-
elsif response_object["errors"].instance_of?(Array)
|
82
|
-
response_object.fetch("errors").map { |error| error.fetch("message") }.join(", ")
|
83
|
-
else
|
84
|
-
response.message
|
85
|
-
end
|
62
|
+
ERROR_MAP[Integer(response.code)] || HTTPError
|
86
63
|
end
|
87
64
|
|
88
65
|
def json?(response)
|
89
|
-
|
66
|
+
JSON_CONTENT_TYPE_REGEXP.match?(response["content-type"])
|
90
67
|
end
|
91
68
|
end
|
92
69
|
end
|
data/lib/x/version.rb
CHANGED
data/sig/x.rbs
CHANGED
@@ -2,16 +2,18 @@ module X
|
|
2
2
|
VERSION: Gem::Version
|
3
3
|
|
4
4
|
class Authenticator
|
5
|
+
AUTHENTICATION_HEADER: String
|
6
|
+
|
5
7
|
def header: (Net::HTTPRequest? request) -> Hash[String, String]
|
6
8
|
end
|
7
9
|
|
8
|
-
class BearerTokenAuthenticator
|
10
|
+
class BearerTokenAuthenticator < Authenticator
|
9
11
|
attr_accessor bearer_token: String
|
10
12
|
def initialize: (bearer_token: String) -> void
|
11
13
|
def header: (Net::HTTPRequest? request) -> Hash[String, String]
|
12
14
|
end
|
13
15
|
|
14
|
-
class OAuthAuthenticator
|
16
|
+
class OAuthAuthenticator < Authenticator
|
15
17
|
OAUTH_VERSION: String
|
16
18
|
OAUTH_SIGNATURE_METHOD: String
|
17
19
|
OAUTH_SIGNATURE_ALGORITHM: String
|
@@ -37,7 +39,10 @@ module X
|
|
37
39
|
def escape: (String value) -> String
|
38
40
|
end
|
39
41
|
|
40
|
-
class
|
42
|
+
class Error < StandardError
|
43
|
+
end
|
44
|
+
|
45
|
+
class ClientError < HTTPError
|
41
46
|
end
|
42
47
|
|
43
48
|
class BadGateway < ClientError
|
@@ -49,8 +54,18 @@ module X
|
|
49
54
|
class ConnectionException < ClientError
|
50
55
|
end
|
51
56
|
|
52
|
-
class
|
53
|
-
|
57
|
+
class HTTPError < Error
|
58
|
+
JSON_CONTENT_TYPE_REGEXP: Regexp
|
59
|
+
|
60
|
+
attr_reader response : Net::HTTPResponse
|
61
|
+
attr_reader code : String
|
62
|
+
|
63
|
+
def initialize: (response: Net::HTTPResponse) -> void
|
64
|
+
|
65
|
+
private
|
66
|
+
def error_message: (Net::HTTPResponse response) -> String
|
67
|
+
def message_from_json_response: (Net::HTTPResponse response) -> String
|
68
|
+
def json?: (Net::HTTPResponse response) -> bool
|
54
69
|
end
|
55
70
|
|
56
71
|
class Forbidden < ClientError
|
@@ -77,19 +92,18 @@ module X
|
|
77
92
|
class PayloadTooLarge < ClientError
|
78
93
|
end
|
79
94
|
|
80
|
-
class ServerError <
|
95
|
+
class ServerError < HTTPError
|
81
96
|
end
|
82
97
|
|
83
98
|
class ServiceUnavailable < ServerError
|
84
99
|
end
|
85
100
|
|
86
|
-
class TooManyRedirects <
|
101
|
+
class TooManyRedirects < Error
|
87
102
|
end
|
88
103
|
|
89
104
|
class TooManyRequests < ClientError
|
90
105
|
@response: Net::HTTPResponse
|
91
106
|
|
92
|
-
def initialize: (String msg, Net::HTTPResponse response) -> void
|
93
107
|
def limit: -> Integer
|
94
108
|
def remaining: -> Integer
|
95
109
|
def reset_at: -> Time
|
@@ -154,19 +168,19 @@ module X
|
|
154
168
|
attr_reader connection: Connection
|
155
169
|
attr_reader request_builder: RequestBuilder
|
156
170
|
attr_reader max_redirects: Integer
|
157
|
-
def initialize: (
|
158
|
-
def handle: (response: Net::HTTPResponse, request: Net::HTTPRequest, base_url: String, ?redirect_count: Integer) -> Net::HTTPResponse
|
171
|
+
def initialize: (connection: Connection, request_builder: RequestBuilder, ?max_redirects: Integer) -> void
|
172
|
+
def handle: (response: Net::HTTPResponse, request: Net::HTTPRequest, base_url: String, ?authenticator: Authenticator, ?redirect_count: Integer) -> Net::HTTPResponse
|
159
173
|
|
160
174
|
private
|
161
175
|
def build_new_uri: (Net::HTTPResponse response, String base_url) -> URI::Generic
|
162
|
-
def build_request: (Net::HTTPRequest request, URI::Generic new_uri, Integer response_code) -> Net::HTTPRequest
|
176
|
+
def build_request: (Net::HTTPRequest request, URI::Generic new_uri, Integer response_code, Authenticator authenticator) -> Net::HTTPRequest
|
163
177
|
def send_new_request: (URI::Generic new_uri, Net::HTTPRequest new_request) -> Net::HTTPResponse
|
164
178
|
end
|
165
179
|
|
166
180
|
class ResponseParser
|
167
181
|
DEFAULT_ARRAY_CLASS: Class
|
168
182
|
DEFAULT_OBJECT_CLASS: Class
|
169
|
-
|
183
|
+
ERROR_MAP: Hash[Integer, singleton(Unauthorized) | singleton(BadRequest) | singleton(Forbidden) | singleton(InternalServerError) | singleton(NotFound) | singleton(PayloadTooLarge) | singleton(ServiceUnavailable) | singleton(TooManyRequests)]
|
170
184
|
JSON_CONTENT_TYPE_REGEXP: Regexp
|
171
185
|
|
172
186
|
attr_accessor array_class: Class
|
@@ -175,11 +189,8 @@ module X
|
|
175
189
|
def parse: (response: Net::HTTPResponse) -> untyped
|
176
190
|
|
177
191
|
private
|
178
|
-
def success?: (Net::HTTPResponse response) -> bool
|
179
192
|
def error: (Net::HTTPResponse response) -> (Unauthorized | BadRequest | Forbidden | InternalServerError | NotFound | PayloadTooLarge | ServiceUnavailable | TooManyRequests)
|
180
193
|
def error_class: (Net::HTTPResponse response) -> (singleton(Unauthorized) | singleton(BadRequest) | singleton(Forbidden) | singleton(InternalServerError) | singleton(NotFound) | singleton(PayloadTooLarge) | singleton(ServiceUnavailable) | singleton(TooManyRequests))
|
181
|
-
def error_message: (Net::HTTPResponse response) -> String
|
182
|
-
def message_from_json_response: (Net::HTTPResponse response) -> String
|
183
194
|
def json?: (Net::HTTPResponse response) -> bool
|
184
195
|
end
|
185
196
|
|
@@ -213,14 +224,15 @@ module X
|
|
213
224
|
attr_accessor redirect_handler: RedirectHandler
|
214
225
|
attr_accessor response_parser: ResponseParser
|
215
226
|
|
216
|
-
def initialize: (?
|
227
|
+
def initialize: (?api_key: String?, ?api_key_secret: String?, ?access_token: String?, ?access_token_secret: String?, ?bearer_token: String?, ?base_url: String, ?open_timeout: Float | Integer, ?read_timeout: Float | Integer, ?write_timeout: Float | Integer, ?proxy_url: URI::Generic? | String?, ?debug_output: IO, ?array_class: Class, ?object_class: Class, ?max_redirects: Integer) -> void
|
217
228
|
def get: (String endpoint, ?headers: Hash[String, String]) -> untyped
|
218
229
|
def post: (String endpoint, ?String? body, ?headers: Hash[String, String]) -> untyped
|
219
230
|
def put: (String endpoint, ?String? body, ?headers: Hash[String, String]) -> untyped
|
220
231
|
def delete: (String endpoint, ?headers: Hash[String, String]) -> untyped
|
221
232
|
|
222
233
|
private
|
223
|
-
def
|
234
|
+
def initialize_oauth: (String? api_key, String? api_key_secret, String? access_token, String? access_token_secret) -> void
|
235
|
+
def initialize_authenticator: -> Authenticator
|
224
236
|
def execute_request: (Symbol http_method, String endpoint, headers: Hash[String, String], ?body: String?) -> untyped
|
225
237
|
end
|
226
238
|
|
@@ -253,13 +265,13 @@ module X
|
|
253
265
|
private
|
254
266
|
def validate!: (file_path: String, media_category: String) -> nil
|
255
267
|
def infer_media_type: (String file_path, String media_category) -> String
|
256
|
-
def init: (Client upload_client, String file_path, String media_type, String media_category) -> untyped
|
257
268
|
def split: (String file_path, Integer chunk_size) -> Array[String]
|
258
|
-
def
|
259
|
-
def
|
260
|
-
def
|
261
|
-
def
|
262
|
-
def
|
269
|
+
def init: (upload_client: Client, file_path: String, media_type: String, media_category: String) -> untyped
|
270
|
+
def append: (upload_client: Client, file_paths: Array[String], media: untyped, media_type: String, ?boundary: String) -> Array[String]
|
271
|
+
def upload_chunk: (upload_client: Client, query: String, upload_body: String, file_path: String, ?headers: Hash[String, String]) -> Integer?
|
272
|
+
def cleanup_file: (String file_path) -> Integer?
|
273
|
+
def finalize: (upload_client: Client, media: untyped) -> untyped
|
274
|
+
def construct_upload_body: (file_path: String, media_type: String, ?boundary: String) -> String
|
263
275
|
end
|
264
276
|
|
265
277
|
class CGI
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: x
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erik Berlin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -36,6 +36,7 @@ files:
|
|
36
36
|
- lib/x/errors/forbidden.rb
|
37
37
|
- lib/x/errors/gateway_timeout.rb
|
38
38
|
- lib/x/errors/gone.rb
|
39
|
+
- lib/x/errors/http_error.rb
|
39
40
|
- lib/x/errors/internal_server_error.rb
|
40
41
|
- lib/x/errors/network_error.rb
|
41
42
|
- lib/x/errors/not_acceptable.rb
|