x 0.19.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +9 -0
- data/CHANGELOG.md +648 -15
- data/README.md +506 -73
- data/SECURITY.md +66 -0
- data/UPGRADING.md +670 -0
- data/lib/x/version.rb +16 -1
- data/lib/x.rb +13 -1
- data/sig/manifest.yaml +5 -0
- data/sig/x.rbs +5 -361
- metadata +57 -49
- data/bin/console +0 -10
- data/bin/setup +0 -6
- data/lib/x/account_uploader.rb +0 -168
- data/lib/x/authenticator.rb +0 -21
- data/lib/x/bearer_token_authenticator.rb +0 -36
- data/lib/x/client.rb +0 -191
- data/lib/x/client_credentials.rb +0 -208
- data/lib/x/connection.rb +0 -183
- data/lib/x/errors/bad_gateway.rb +0 -6
- data/lib/x/errors/bad_request.rb +0 -6
- data/lib/x/errors/client_error.rb +0 -6
- data/lib/x/errors/connection_exception.rb +0 -6
- data/lib/x/errors/error.rb +0 -4
- data/lib/x/errors/forbidden.rb +0 -6
- data/lib/x/errors/gateway_timeout.rb +0 -6
- data/lib/x/errors/gone.rb +0 -6
- data/lib/x/errors/http_error.rb +0 -84
- data/lib/x/errors/internal_server_error.rb +0 -6
- data/lib/x/errors/invalid_media_type.rb +0 -6
- data/lib/x/errors/network_error.rb +0 -6
- data/lib/x/errors/not_acceptable.rb +0 -6
- data/lib/x/errors/not_found.rb +0 -6
- data/lib/x/errors/payload_too_large.rb +0 -6
- data/lib/x/errors/server_error.rb +0 -6
- data/lib/x/errors/service_unavailable.rb +0 -6
- data/lib/x/errors/too_many_redirects.rb +0 -6
- data/lib/x/errors/too_many_requests.rb +0 -58
- data/lib/x/errors/unauthorized.rb +0 -6
- data/lib/x/errors/unprocessable_entity.rb +0 -6
- data/lib/x/media_upload_validator.rb +0 -36
- data/lib/x/media_uploader.rb +0 -234
- data/lib/x/oauth2_authenticator.rb +0 -169
- data/lib/x/oauth_authenticator.rb +0 -182
- data/lib/x/rate_limit.rb +0 -89
- data/lib/x/redirect_handler.rb +0 -109
- data/lib/x/request_builder.rb +0 -93
- data/lib/x/response_parser.rb +0 -81
- data/lib/x/stream_parser.rb +0 -75
data/lib/x/redirect_handler.rb
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
require "net/http"
|
|
2
|
-
require "uri"
|
|
3
|
-
require_relative "authenticator"
|
|
4
|
-
require_relative "connection"
|
|
5
|
-
require_relative "errors/too_many_redirects"
|
|
6
|
-
require_relative "request_builder"
|
|
7
|
-
|
|
8
|
-
module X
|
|
9
|
-
# Handles HTTP redirects for API requests
|
|
10
|
-
# @api public
|
|
11
|
-
class RedirectHandler
|
|
12
|
-
# Default maximum number of redirects to follow
|
|
13
|
-
DEFAULT_MAX_REDIRECTS = 10
|
|
14
|
-
|
|
15
|
-
# The maximum number of redirects to follow
|
|
16
|
-
# @api public
|
|
17
|
-
# @return [Integer] the maximum number of redirects to follow
|
|
18
|
-
# @example Get or set the maximum redirects
|
|
19
|
-
# handler.max_redirects = 5
|
|
20
|
-
attr_accessor :max_redirects
|
|
21
|
-
|
|
22
|
-
# The connection for making requests
|
|
23
|
-
# @api public
|
|
24
|
-
# @return [Connection] the connection for making requests
|
|
25
|
-
# @example Get the connection
|
|
26
|
-
# handler.connection
|
|
27
|
-
attr_reader :connection
|
|
28
|
-
|
|
29
|
-
# The request builder for creating requests
|
|
30
|
-
# @api public
|
|
31
|
-
# @return [RequestBuilder] the request builder for creating requests
|
|
32
|
-
# @example Get the request builder
|
|
33
|
-
# handler.request_builder
|
|
34
|
-
attr_reader :request_builder
|
|
35
|
-
|
|
36
|
-
# Initialize a new RedirectHandler
|
|
37
|
-
#
|
|
38
|
-
# @api public
|
|
39
|
-
# @param connection [Connection] the connection for making requests
|
|
40
|
-
# @param request_builder [RequestBuilder] the request builder for creating requests
|
|
41
|
-
# @param max_redirects [Integer] the maximum number of redirects to follow
|
|
42
|
-
# @return [RedirectHandler] a new instance
|
|
43
|
-
# @example Create a redirect handler
|
|
44
|
-
# handler = X::RedirectHandler.new(connection: conn, request_builder: builder)
|
|
45
|
-
def initialize(connection: Connection.new, request_builder: RequestBuilder.new,
|
|
46
|
-
max_redirects: DEFAULT_MAX_REDIRECTS)
|
|
47
|
-
@connection = connection
|
|
48
|
-
@request_builder = request_builder
|
|
49
|
-
@max_redirects = max_redirects
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Handle redirects for an HTTP response
|
|
53
|
-
#
|
|
54
|
-
# @api public
|
|
55
|
-
# @param response [Net::HTTPResponse] the HTTP response to handle
|
|
56
|
-
# @param request [Net::HTTPRequest] the original HTTP request
|
|
57
|
-
# @param base_url [String] the base URL for the request
|
|
58
|
-
# @param authenticator [Authenticator] the authenticator for requests
|
|
59
|
-
# @param redirect_count [Integer] the current redirect count
|
|
60
|
-
# @return [Net::HTTPResponse] the final HTTP response after following redirects
|
|
61
|
-
# @raise [TooManyRedirects] if the maximum number of redirects is exceeded
|
|
62
|
-
# @example Handle a response
|
|
63
|
-
# response = handler.handle(response: resp, request: req, base_url: url)
|
|
64
|
-
def handle(response:, request:, base_url:, authenticator: Authenticator.new, redirect_count: 0)
|
|
65
|
-
if response.is_a?(Net::HTTPRedirection)
|
|
66
|
-
raise TooManyRedirects, "Too many redirects" if redirect_count > max_redirects
|
|
67
|
-
|
|
68
|
-
new_uri = build_new_uri(response, base_url)
|
|
69
|
-
|
|
70
|
-
new_request = build_request(request, new_uri, Integer(response.code), authenticator)
|
|
71
|
-
new_response = connection.perform(request: new_request)
|
|
72
|
-
|
|
73
|
-
handle(response: new_response, request: new_request, base_url:, redirect_count: redirect_count + 1)
|
|
74
|
-
else
|
|
75
|
-
response
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
# Build a new URI from the redirect response
|
|
82
|
-
# @api private
|
|
83
|
-
# @param response [Net::HTTPResponse] the redirect response
|
|
84
|
-
# @param base_url [String] the base URL
|
|
85
|
-
# @return [URI] the new URI
|
|
86
|
-
def build_new_uri(response, base_url)
|
|
87
|
-
location = response.fetch("location")
|
|
88
|
-
# If location is relative, it will join with the original base URL, otherwise it will overwrite it
|
|
89
|
-
URI.join(base_url, location)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Build a new request for the redirect
|
|
93
|
-
# @api private
|
|
94
|
-
# @param request [Net::HTTPRequest] the original request
|
|
95
|
-
# @param uri [URI] the new URI
|
|
96
|
-
# @param response_code [Integer] the HTTP response code
|
|
97
|
-
# @param authenticator [Authenticator] the authenticator
|
|
98
|
-
# @return [Net::HTTPRequest] the new request
|
|
99
|
-
def build_request(request, uri, response_code, authenticator)
|
|
100
|
-
http_method = :get
|
|
101
|
-
if [307, 308].include?(response_code)
|
|
102
|
-
http_method = request.method.downcase.to_sym
|
|
103
|
-
body = request.body
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
request_builder.build(http_method:, uri:, body:, authenticator:)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
data/lib/x/request_builder.rb
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
require "net/http"
|
|
2
|
-
require "uri"
|
|
3
|
-
require_relative "authenticator"
|
|
4
|
-
require_relative "version"
|
|
5
|
-
|
|
6
|
-
module X
|
|
7
|
-
# Builds HTTP requests for the X API
|
|
8
|
-
# @api public
|
|
9
|
-
class RequestBuilder
|
|
10
|
-
# Default headers for API requests
|
|
11
|
-
DEFAULT_HEADERS = {
|
|
12
|
-
"Content-Type" => "application/json; charset=utf-8",
|
|
13
|
-
"User-Agent" => "X-Client/#{VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION} (#{RUBY_PLATFORM})"
|
|
14
|
-
}.freeze
|
|
15
|
-
# Mapping of HTTP method symbols to Net::HTTP classes
|
|
16
|
-
HTTP_METHODS = {
|
|
17
|
-
get: Net::HTTP::Get,
|
|
18
|
-
post: Net::HTTP::Post,
|
|
19
|
-
put: Net::HTTP::Put,
|
|
20
|
-
delete: Net::HTTP::Delete
|
|
21
|
-
}.freeze
|
|
22
|
-
|
|
23
|
-
# Build an HTTP request
|
|
24
|
-
#
|
|
25
|
-
# @api public
|
|
26
|
-
# @param http_method [Symbol] the HTTP method (:get, :post, :put, :delete)
|
|
27
|
-
# @param uri [URI] the request URI
|
|
28
|
-
# @param body [String, nil] the request body
|
|
29
|
-
# @param headers [Hash] additional headers for the request
|
|
30
|
-
# @param authenticator [Authenticator] the authenticator for the request
|
|
31
|
-
# @return [Net::HTTPRequest] the built HTTP request
|
|
32
|
-
# @raise [ArgumentError] if the HTTP method is not supported
|
|
33
|
-
# @example Build a GET request
|
|
34
|
-
# builder.build(http_method: :get, uri: URI("https://api.x.com/2/users/me"))
|
|
35
|
-
def build(http_method:, uri:, body: nil, headers: {}, authenticator: Authenticator.new)
|
|
36
|
-
request = create_request(http_method:, uri:, body:)
|
|
37
|
-
add_headers(request:, headers:)
|
|
38
|
-
add_authentication(request:, authenticator:)
|
|
39
|
-
request
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
# Create an HTTP request
|
|
45
|
-
# @api private
|
|
46
|
-
# @param http_method [Symbol] the HTTP method
|
|
47
|
-
# @param uri [URI] the request URI
|
|
48
|
-
# @param body [String, nil] the request body
|
|
49
|
-
# @return [Net::HTTPRequest] the created request
|
|
50
|
-
def create_request(http_method:, uri:, body:)
|
|
51
|
-
http_method_class = HTTP_METHODS[http_method]
|
|
52
|
-
|
|
53
|
-
raise ArgumentError, "Unsupported HTTP method: #{http_method}" unless http_method_class
|
|
54
|
-
|
|
55
|
-
escaped_uri = escape_query_params(uri)
|
|
56
|
-
request = http_method_class.new(escaped_uri)
|
|
57
|
-
request.body = body
|
|
58
|
-
request
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Add authentication to a request
|
|
62
|
-
# @api private
|
|
63
|
-
# @param request [Net::HTTPRequest] the request
|
|
64
|
-
# @param authenticator [Authenticator] the authenticator
|
|
65
|
-
# @return [void]
|
|
66
|
-
def add_authentication(request:, authenticator:)
|
|
67
|
-
authenticator.header(request).each do |key, value|
|
|
68
|
-
request[key] = value
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Add headers to a request
|
|
73
|
-
# @api private
|
|
74
|
-
# @param request [Net::HTTPRequest] the request
|
|
75
|
-
# @param headers [Hash] additional headers
|
|
76
|
-
# @return [void]
|
|
77
|
-
def add_headers(request:, headers:)
|
|
78
|
-
DEFAULT_HEADERS.merge(headers).each do |key, value|
|
|
79
|
-
request[key] = value
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Escape query parameters in a URI
|
|
84
|
-
# @api private
|
|
85
|
-
# @param uri [URI] the URI
|
|
86
|
-
# @return [URI] the URI with escaped query parameters
|
|
87
|
-
def escape_query_params(uri)
|
|
88
|
-
URI(uri).tap do |u|
|
|
89
|
-
u.query = URI.encode_www_form(URI.decode_www_form(u.query)).gsub("%2C", ",") if u.query
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
data/lib/x/response_parser.rb
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require "net/http"
|
|
3
|
-
require_relative "errors/bad_gateway"
|
|
4
|
-
require_relative "errors/bad_request"
|
|
5
|
-
require_relative "errors/connection_exception"
|
|
6
|
-
require_relative "errors/http_error"
|
|
7
|
-
require_relative "errors/forbidden"
|
|
8
|
-
require_relative "errors/gateway_timeout"
|
|
9
|
-
require_relative "errors/gone"
|
|
10
|
-
require_relative "errors/internal_server_error"
|
|
11
|
-
require_relative "errors/not_acceptable"
|
|
12
|
-
require_relative "errors/not_found"
|
|
13
|
-
require_relative "errors/payload_too_large"
|
|
14
|
-
require_relative "errors/service_unavailable"
|
|
15
|
-
require_relative "errors/too_many_requests"
|
|
16
|
-
require_relative "errors/unauthorized"
|
|
17
|
-
require_relative "errors/unprocessable_entity"
|
|
18
|
-
|
|
19
|
-
module X
|
|
20
|
-
# Parses HTTP responses from the X API
|
|
21
|
-
# @api public
|
|
22
|
-
class ResponseParser
|
|
23
|
-
# Mapping of HTTP status codes to error classes
|
|
24
|
-
ERROR_MAP = {
|
|
25
|
-
400 => BadRequest,
|
|
26
|
-
401 => Unauthorized,
|
|
27
|
-
403 => Forbidden,
|
|
28
|
-
404 => NotFound,
|
|
29
|
-
406 => NotAcceptable,
|
|
30
|
-
409 => ConnectionException,
|
|
31
|
-
410 => Gone,
|
|
32
|
-
413 => PayloadTooLarge,
|
|
33
|
-
422 => UnprocessableEntity,
|
|
34
|
-
429 => TooManyRequests,
|
|
35
|
-
500 => InternalServerError,
|
|
36
|
-
502 => BadGateway,
|
|
37
|
-
503 => ServiceUnavailable,
|
|
38
|
-
504 => GatewayTimeout
|
|
39
|
-
}.freeze
|
|
40
|
-
|
|
41
|
-
# Parse an HTTP response
|
|
42
|
-
#
|
|
43
|
-
# @api public
|
|
44
|
-
# @param response [Net::HTTPResponse] the HTTP response to parse
|
|
45
|
-
# @param array_class [Class, nil] the class for parsing JSON arrays
|
|
46
|
-
# @param object_class [Class, nil] the class for parsing JSON objects
|
|
47
|
-
# @return [Hash, Array, nil] the parsed response body
|
|
48
|
-
# @raise [HTTPError] if the response is not successful
|
|
49
|
-
# @example Parse a response
|
|
50
|
-
# parser.parse(response: response)
|
|
51
|
-
def parse(response:, array_class: nil, object_class: nil)
|
|
52
|
-
raise error(response) unless response.is_a?(Net::HTTPSuccess)
|
|
53
|
-
|
|
54
|
-
return if response.instance_of?(Net::HTTPNoContent)
|
|
55
|
-
|
|
56
|
-
begin
|
|
57
|
-
JSON.parse(response.body, array_class:, object_class:)
|
|
58
|
-
rescue JSON::ParserError
|
|
59
|
-
nil
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
# Create an error from a response
|
|
66
|
-
# @api private
|
|
67
|
-
# @param response [Net::HTTPResponse] the HTTP response
|
|
68
|
-
# @return [HTTPError] the error
|
|
69
|
-
def error(response)
|
|
70
|
-
error_class(response).new(response:)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Get the error class for a response
|
|
74
|
-
# @api private
|
|
75
|
-
# @param response [Net::HTTPResponse] the HTTP response
|
|
76
|
-
# @return [Class] the error class
|
|
77
|
-
def error_class(response)
|
|
78
|
-
ERROR_MAP[Integer(response.code)] || HTTPError
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
data/lib/x/stream_parser.rb
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require "net/http"
|
|
3
|
-
require_relative "response_parser"
|
|
4
|
-
|
|
5
|
-
module X
|
|
6
|
-
# Handles streaming responses from the X API
|
|
7
|
-
# @api public
|
|
8
|
-
class StreamParser
|
|
9
|
-
# Line delimiter for streaming responses
|
|
10
|
-
LINE_DELIMITER = "\r\n".freeze
|
|
11
|
-
|
|
12
|
-
# Process a streaming response and yield parsed JSON objects
|
|
13
|
-
#
|
|
14
|
-
# @api public
|
|
15
|
-
# @param response [Net::HTTPResponse] the HTTP response to stream
|
|
16
|
-
# @param response_parser [ResponseParser] the response parser for error handling
|
|
17
|
-
# @param array_class [Class, nil] the class for parsing JSON arrays
|
|
18
|
-
# @param object_class [Class, nil] the class for parsing JSON objects
|
|
19
|
-
# @yield [Hash, Array] each parsed JSON object from the stream
|
|
20
|
-
# @return [void]
|
|
21
|
-
# @raise [HTTPError] if the response is not successful
|
|
22
|
-
# @example Process a streaming response
|
|
23
|
-
# handler.process(response: response, response_parser: parser) { |json| puts json }
|
|
24
|
-
def process(response:, response_parser:, array_class: nil, object_class: nil, &block)
|
|
25
|
-
response_parser.parse(response:) unless response.is_a?(Net::HTTPSuccess)
|
|
26
|
-
|
|
27
|
-
buffer = +""
|
|
28
|
-
response.read_body do |chunk|
|
|
29
|
-
buffer << chunk
|
|
30
|
-
process_buffer(buffer:, array_class:, object_class:, &block)
|
|
31
|
-
end
|
|
32
|
-
process_remaining(buffer:, array_class:, object_class:, &block)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
# Process complete lines from the buffer
|
|
38
|
-
# @api private
|
|
39
|
-
# @param buffer [String] the accumulated data buffer
|
|
40
|
-
# @param array_class [Class, nil] the class for parsing JSON arrays
|
|
41
|
-
# @param object_class [Class, nil] the class for parsing JSON objects
|
|
42
|
-
# @yield [Hash, Array] each parsed JSON object
|
|
43
|
-
# @return [void]
|
|
44
|
-
def process_buffer(buffer:, array_class:, object_class:, &)
|
|
45
|
-
while (line_end = buffer.index(LINE_DELIMITER))
|
|
46
|
-
line = buffer.slice!(0, line_end) # : String
|
|
47
|
-
buffer.delete_prefix!(LINE_DELIMITER)
|
|
48
|
-
yield_json(line:, array_class:, object_class:, &) unless line.empty?
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Process any remaining data after the stream ends
|
|
53
|
-
# @api private
|
|
54
|
-
# @param buffer [String] the remaining data buffer
|
|
55
|
-
# @param array_class [Class, nil] the class for parsing JSON arrays
|
|
56
|
-
# @param object_class [Class, nil] the class for parsing JSON objects
|
|
57
|
-
# @yield [Hash, Array] the parsed JSON object
|
|
58
|
-
# @return [void]
|
|
59
|
-
def process_remaining(buffer:, array_class:, object_class:, &)
|
|
60
|
-
buffer.strip!
|
|
61
|
-
yield_json(line: buffer, array_class:, object_class:, &) unless buffer.empty?
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Parse a line as JSON and yield the result
|
|
65
|
-
# @api private
|
|
66
|
-
# @param line [String] the JSON line to parse
|
|
67
|
-
# @param array_class [Class, nil] the class for parsing JSON arrays
|
|
68
|
-
# @param object_class [Class, nil] the class for parsing JSON objects
|
|
69
|
-
# @yield [Hash, Array] the parsed JSON object
|
|
70
|
-
# @return [void]
|
|
71
|
-
def yield_json(line:, array_class:, object_class:)
|
|
72
|
-
yield JSON.parse(line, array_class:, object_class:)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|