x 0.7.1 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +21 -3
- data/Rakefile +1 -1
- data/lib/x/bearer_token_authenticator.rb +14 -0
- data/lib/x/client.rb +43 -17
- data/lib/x/connection.rb +64 -15
- data/lib/x/errors/error.rb +4 -12
- data/lib/x/errors/too_many_redirects_error.rb +5 -0
- data/lib/x/errors/too_many_requests_error.rb +1 -4
- data/lib/x/oauth_authenticator.rb +95 -0
- data/lib/x/redirect_handler.rb +56 -0
- data/lib/x/request_builder.rb +11 -6
- data/lib/x/response_handler.rb +22 -6
- data/lib/x/version.rb +1 -1
- data/sig/x.rbs +85 -49
- metadata +7 -6
- data/lib/x/authenticator.rb +0 -82
- data/lib/x/client_defaults.rb +0 -14
- data/lib/x/errors/errors.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81a5b12497937f06eb22779e6c78ef9dc18d5e9c5b049c96b4da461b058c6998
|
4
|
+
data.tar.gz: 96f4e02f93df2e39589095f12d5a1e790f17159f932cc1ab72330e301815bda4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8540864206f44e6863653ec538d076d4f1c696dab4fff2b8eeb33b587603301d2d62faedb163c59cc9366439b28a29acff4068876b4013a05bf88090c61042f
|
7
|
+
data.tar.gz: ef425363ab320f922e9f05f28f4d21a8090b763ef0d2468dfcd0ff7b9d0c456efa7f0a21b034fc750e5d927aafd5c4ae1b9407696d242512a44c7114e4efda4a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.8.1] - 2023-09-20
|
4
|
+
|
5
|
+
- Fix bug where setting Connection#base_uri= doesn't update the HTTP client (d5a89db)
|
6
|
+
|
7
|
+
## [0.8.0] - 2023-09-14
|
8
|
+
|
9
|
+
- Add (back) bearer token authentication (62e141d)
|
10
|
+
- Follow redirects (90a8c55)
|
11
|
+
- Parse error responses with Content-Type: application/problem+json (0b697d9)
|
12
|
+
|
3
13
|
## [0.7.1] - 2023-09-02
|
4
14
|
|
5
15
|
- Fix bug in X::Authenticator#split_uri (ebc9d5f)
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
# X
|
1
|
+
# A [Ruby](https://www.ruby-lang.org) interface to the [X API](https://developer.x.com)
|
2
2
|
|
3
|
-
|
3
|
+
## Follow
|
4
|
+
|
5
|
+
For updates and announcements, follow [this gem](https://x.com/gem) and [its creator](https://x.com/sferik) on X.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -14,9 +16,11 @@ Or, if Bundler is not being used to manage dependencies:
|
|
14
16
|
|
15
17
|
## Usage
|
16
18
|
|
17
|
-
First, obtain X credentails from https://developer.x.com
|
19
|
+
First, obtain X credentails from <https://developer.x.com>.
|
18
20
|
|
19
21
|
```ruby
|
22
|
+
require "x"
|
23
|
+
|
20
24
|
x_credentials = {
|
21
25
|
api_key: "INSERT YOUR X API KEY HERE",
|
22
26
|
api_key_secret: "INSERT YOUR X API KEY SECRET HERE",
|
@@ -70,6 +74,20 @@ The tests for the previous version of this library executed in about 2 seconds.
|
|
70
74
|
|
71
75
|
This code is not littered with comments that are intended to generate documentation. Rather, this code is intended to be simple enough to serve as its own documentation. If you want to understand how something works, don’t read the documentation—it might be wrong—read the code. The code is always right.
|
72
76
|
|
77
|
+
## Sponsorship
|
78
|
+
|
79
|
+
The X gem is free to use, but with X API pricing tiers, it actually costs money to develop and maintain. By contributing to the project, you help us:
|
80
|
+
|
81
|
+
1. Maintain the library: Keeping it up-to-date and secure.
|
82
|
+
2. Add new features: Enhancements that make your life easier.
|
83
|
+
3. Provide support: Faster responses to issues and feature requests.
|
84
|
+
|
85
|
+
⭐️ Bonus: Sponsors will get priority support and influence over the project roadmap. We will also list your name or your company's logo on our GitHub page.
|
86
|
+
|
87
|
+
Building and maintaining an open-source project like this takes a considerable amount of time and effort. Your sponsorship can help sustain this project. Even a small monthly donation makes a huge difference!
|
88
|
+
|
89
|
+
[Click here to sponsor this project.](https://github.com/sponsors/sferik)
|
90
|
+
|
73
91
|
## Development
|
74
92
|
|
75
93
|
1. Checkout and repo:
|
data/Rakefile
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
module X
|
2
|
+
# Handles bearer token authentication
|
3
|
+
class BearerTokenAuthenticator
|
4
|
+
attr_accessor :bearer_token
|
5
|
+
|
6
|
+
def initialize(bearer_token)
|
7
|
+
@bearer_token = bearer_token
|
8
|
+
end
|
9
|
+
|
10
|
+
def header(_request)
|
11
|
+
"Bearer #{bearer_token}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/x/client.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require "forwardable"
|
2
|
-
require_relative "
|
3
|
-
require_relative "
|
2
|
+
require_relative "bearer_token_authenticator"
|
3
|
+
require_relative "oauth_authenticator"
|
4
4
|
require_relative "connection"
|
5
|
+
require_relative "redirect_handler"
|
5
6
|
require_relative "request_builder"
|
6
7
|
require_relative "response_handler"
|
7
8
|
|
@@ -9,25 +10,36 @@ module X
|
|
9
10
|
# Main public interface
|
10
11
|
class Client
|
11
12
|
extend Forwardable
|
12
|
-
include ClientDefaults
|
13
13
|
|
14
|
-
def_delegators :@authenticator, :api_key, :api_key_secret, :access_token, :access_token_secret
|
15
|
-
def_delegators :@authenticator, :api_key=, :api_key_secret=, :access_token=, :access_token_secret=
|
16
|
-
def_delegators :@connection, :
|
17
|
-
def_delegators :@connection, :
|
14
|
+
def_delegators :@authenticator, :bearer_token, :api_key, :api_key_secret, :access_token, :access_token_secret
|
15
|
+
def_delegators :@authenticator, :bearer_token=, :api_key=, :api_key_secret=, :access_token=, :access_token_secret=
|
16
|
+
def_delegators :@connection, :base_uri, :open_timeout, :read_timeout, :write_timeout, :debug_output
|
17
|
+
def_delegators :@connection, :base_uri=, :open_timeout=, :read_timeout=, :write_timeout=, :debug_output=
|
18
18
|
def_delegators :@request_builder, :content_type, :user_agent
|
19
19
|
def_delegators :@request_builder, :content_type=, :user_agent=
|
20
20
|
def_delegators :@response_handler, :array_class, :object_class
|
21
21
|
def_delegators :@response_handler, :array_class=, :object_class=
|
22
22
|
|
23
|
-
def initialize(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
23
|
+
def initialize(bearer_token: nil,
|
24
|
+
api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
|
25
|
+
base_url: Connection::DEFAULT_BASE_URL,
|
26
|
+
open_timeout: Connection::DEFAULT_OPEN_TIMEOUT,
|
27
|
+
read_timeout: Connection::DEFAULT_READ_TIMEOUT,
|
28
|
+
write_timeout: Connection::DEFAULT_WRITE_TIMEOUT,
|
29
|
+
content_type: RequestBuilder::DEFAULT_CONTENT_TYPE,
|
30
|
+
user_agent: RequestBuilder::DEFAULT_USER_AGENT,
|
31
|
+
debug_output: nil,
|
32
|
+
array_class: ResponseHandler::DEFAULT_ARRAY_CLASS,
|
33
|
+
object_class: ResponseHandler::DEFAULT_OBJECT_CLASS,
|
34
|
+
max_redirects: RedirectHandler::DEFAULT_MAX_REDIRECTS)
|
35
|
+
|
36
|
+
initialize_authenticator(bearer_token, api_key, api_key_secret, access_token, access_token_secret)
|
37
|
+
@connection = Connection.new(base_url: base_url, open_timeout: open_timeout, read_timeout: read_timeout,
|
38
|
+
write_timeout: write_timeout, debug_output: debug_output)
|
39
|
+
@request_builder = RequestBuilder.new(content_type: content_type, user_agent: user_agent)
|
40
|
+
@redirect_handler = RedirectHandler.new(@authenticator, @connection, @request_builder,
|
41
|
+
max_redirects: max_redirects)
|
42
|
+
@response_handler = ResponseHandler.new(array_class: array_class, object_class: object_class)
|
31
43
|
end
|
32
44
|
|
33
45
|
def get(endpoint)
|
@@ -48,10 +60,24 @@ module X
|
|
48
60
|
|
49
61
|
private
|
50
62
|
|
63
|
+
def initialize_authenticator(bearer_token, api_key, api_key_secret, access_token, access_token_secret)
|
64
|
+
@authenticator = if bearer_token
|
65
|
+
BearerTokenAuthenticator.new(bearer_token)
|
66
|
+
elsif api_key && api_key_secret && access_token && access_token_secret
|
67
|
+
OauthAuthenticator.new(api_key, api_key_secret, access_token, access_token_secret)
|
68
|
+
else
|
69
|
+
raise ArgumentError,
|
70
|
+
"Client must be initialized with either a bearer_token or " \
|
71
|
+
"an api_key, api_key_secret, access_token, and access_token_secret"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
51
75
|
def send_request(http_method, endpoint, body = nil)
|
52
|
-
|
76
|
+
uri = URI.join(base_uri.to_s, endpoint)
|
77
|
+
request = @request_builder.build(@authenticator, http_method, uri, body: body)
|
53
78
|
response = @connection.send_request(request)
|
54
|
-
@
|
79
|
+
final_response = @redirect_handler.handle_redirects(response, request, base_uri)
|
80
|
+
@response_handler.handle(final_response)
|
55
81
|
end
|
56
82
|
end
|
57
83
|
end
|
data/lib/x/connection.rb
CHANGED
@@ -1,29 +1,40 @@
|
|
1
1
|
require "forwardable"
|
2
2
|
require "net/http"
|
3
3
|
require "uri"
|
4
|
-
require_relative "errors/errors"
|
5
4
|
require_relative "errors/network_error"
|
6
5
|
|
7
6
|
module X
|
8
7
|
# Sends HTTP requests
|
9
8
|
class Connection
|
10
9
|
extend Forwardable
|
11
|
-
include Errors
|
12
10
|
|
13
|
-
|
11
|
+
DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze
|
12
|
+
DEFAULT_HOST = "https://api.twitter.com".freeze
|
13
|
+
DEFAULT_PORT = 443
|
14
|
+
DEFAULT_OPEN_TIMEOUT = 60 # seconds
|
15
|
+
DEFAULT_READ_TIMEOUT = 60 # seconds
|
16
|
+
DEFAULT_WRITE_TIMEOUT = 60 # seconds
|
17
|
+
NETWORK_ERRORS = [
|
18
|
+
Errno::ECONNREFUSED,
|
19
|
+
Net::OpenTimeout,
|
20
|
+
Net::ReadTimeout
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
attr_reader :base_uri, :http_client
|
14
24
|
|
15
25
|
def_delegators :@http_client, :open_timeout, :read_timeout, :write_timeout
|
16
26
|
def_delegators :@http_client, :open_timeout=, :read_timeout=, :write_timeout=
|
17
27
|
def_delegator :@http_client, :set_debug_output, :debug_output=
|
18
28
|
|
19
|
-
def initialize(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
def initialize(base_url: DEFAULT_BASE_URL, open_timeout: DEFAULT_OPEN_TIMEOUT,
|
30
|
+
read_timeout: DEFAULT_READ_TIMEOUT, write_timeout: DEFAULT_WRITE_TIMEOUT, debug_output: nil)
|
31
|
+
self.base_uri = base_url
|
32
|
+
apply_http_client_settings(
|
33
|
+
open_timeout: open_timeout,
|
34
|
+
read_timeout: read_timeout,
|
35
|
+
write_timeout: write_timeout,
|
36
|
+
debug_output: debug_output
|
37
|
+
)
|
27
38
|
end
|
28
39
|
|
29
40
|
def send_request(request)
|
@@ -32,15 +43,53 @@ module X
|
|
32
43
|
raise NetworkError, "Network error: #{e.message}"
|
33
44
|
end
|
34
45
|
|
35
|
-
def
|
36
|
-
|
37
|
-
raise ArgumentError, "Invalid base URL" unless
|
46
|
+
def base_uri=(base_url)
|
47
|
+
base_uri = URI(base_url)
|
48
|
+
raise ArgumentError, "Invalid base URL" unless base_uri.is_a?(URI::HTTPS) || base_uri.is_a?(URI::HTTP)
|
38
49
|
|
39
|
-
@
|
50
|
+
@base_uri = base_uri
|
51
|
+
update_http_client_settings
|
40
52
|
end
|
41
53
|
|
42
54
|
def debug_output
|
43
55
|
@http_client.instance_variable_get(:@debug_output)
|
44
56
|
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def apply_http_client_settings(open_timeout:, read_timeout:, write_timeout:, debug_output:)
|
61
|
+
@http_client.open_timeout = open_timeout
|
62
|
+
@http_client.read_timeout = read_timeout
|
63
|
+
@http_client.write_timeout = write_timeout
|
64
|
+
@http_client.set_debug_output(debug_output) if debug_output
|
65
|
+
end
|
66
|
+
|
67
|
+
def current_http_client_settings
|
68
|
+
{
|
69
|
+
open_timeout: @http_client.open_timeout,
|
70
|
+
read_timeout: @http_client.read_timeout,
|
71
|
+
write_timeout: @http_client.write_timeout,
|
72
|
+
debug_output: debug_output
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def update_http_client_settings
|
77
|
+
conditionally_apply_http_client_settings do
|
78
|
+
host = @base_uri.host || DEFAULT_HOST
|
79
|
+
port = @base_uri.port || DEFAULT_PORT
|
80
|
+
@http_client = Net::HTTP.new(host, port)
|
81
|
+
@http_client.use_ssl = @base_uri.scheme == "https"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def conditionally_apply_http_client_settings
|
86
|
+
if @http_client
|
87
|
+
settings = current_http_client_settings
|
88
|
+
yield
|
89
|
+
apply_http_client_settings(**settings)
|
90
|
+
else
|
91
|
+
yield
|
92
|
+
end
|
93
|
+
end
|
45
94
|
end
|
46
95
|
end
|
data/lib/x/errors/error.rb
CHANGED
@@ -1,24 +1,16 @@
|
|
1
1
|
require "json"
|
2
2
|
require "net/http"
|
3
|
-
require_relative "../client_defaults"
|
4
3
|
|
5
4
|
module X
|
6
5
|
# Base error class
|
7
6
|
class Error < ::StandardError
|
8
|
-
|
7
|
+
JSON_CONTENT_TYPE_REGEXP = %r{application/(problem\+|)json}
|
8
|
+
|
9
9
|
attr_reader :object
|
10
10
|
|
11
|
-
def initialize(msg, response
|
12
|
-
if
|
13
|
-
@object = JSON.parse(response.body, array_class: array_class, object_class: object_class)
|
14
|
-
end
|
11
|
+
def initialize(msg, response:)
|
12
|
+
@object = JSON.parse(response.body || "{}") if JSON_CONTENT_TYPE_REGEXP.match?(response["content-type"])
|
15
13
|
super(msg)
|
16
14
|
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def json_response?(response)
|
21
|
-
response.is_a?(Net::HTTPResponse) && response.body && response["content-type"] == DEFAULT_CONTENT_TYPE
|
22
|
-
end
|
23
15
|
end
|
24
16
|
end
|
@@ -1,12 +1,9 @@
|
|
1
1
|
require_relative "client_error"
|
2
|
-
require_relative "../client_defaults"
|
3
2
|
|
4
3
|
module X
|
5
4
|
# Rate limit error
|
6
5
|
class TooManyRequestsError < ClientError
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(msg, response:, array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
|
6
|
+
def initialize(msg, response:)
|
10
7
|
@response = response
|
11
8
|
super
|
12
9
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "cgi"
|
3
|
+
require "json"
|
4
|
+
require "openssl"
|
5
|
+
require "securerandom"
|
6
|
+
require "uri"
|
7
|
+
|
8
|
+
module X
|
9
|
+
# Handles OAuth authentication
|
10
|
+
class OauthAuthenticator
|
11
|
+
OAUTH_VERSION = "1.0".freeze
|
12
|
+
OAUTH_SIGNATURE_METHOD = "HMAC-SHA1".freeze
|
13
|
+
OAUTH_SIGNATURE_ALGORITHM = "sha1".freeze
|
14
|
+
|
15
|
+
attr_accessor :api_key, :api_key_secret, :access_token, :access_token_secret
|
16
|
+
|
17
|
+
def initialize(api_key, api_key_secret, access_token, access_token_secret)
|
18
|
+
@api_key = api_key
|
19
|
+
@api_key_secret = api_key_secret
|
20
|
+
@access_token = access_token
|
21
|
+
@access_token_secret = access_token_secret
|
22
|
+
end
|
23
|
+
|
24
|
+
def header(request)
|
25
|
+
method, url, query_params = parse_request(request)
|
26
|
+
build_oauth_header(method, url, query_params)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def parse_request(request)
|
32
|
+
uri = request.uri
|
33
|
+
query_params = parse_query_params(uri.query.to_s)
|
34
|
+
[request.method, uri_without_query(uri), query_params]
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_query_params(query_string)
|
38
|
+
URI.decode_www_form(query_string).to_h
|
39
|
+
end
|
40
|
+
|
41
|
+
def uri_without_query(uri)
|
42
|
+
uri.to_s.chomp("?#{uri.query}")
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_oauth_header(method, url, query_params)
|
46
|
+
oauth_params = default_oauth_params
|
47
|
+
all_params = query_params.merge(oauth_params)
|
48
|
+
oauth_params["oauth_signature"] = generate_signature(method, url, all_params)
|
49
|
+
format_oauth_header(oauth_params)
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_oauth_params
|
53
|
+
{
|
54
|
+
"oauth_consumer_key" => api_key,
|
55
|
+
"oauth_nonce" => SecureRandom.hex,
|
56
|
+
"oauth_signature_method" => OAUTH_SIGNATURE_METHOD,
|
57
|
+
"oauth_timestamp" => Time.now.utc.to_i.to_s,
|
58
|
+
"oauth_token" => access_token,
|
59
|
+
"oauth_version" => OAUTH_VERSION
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def generate_signature(method, url, params)
|
64
|
+
base_string = signature_base_string(method, url, params)
|
65
|
+
hmac_signature(base_string)
|
66
|
+
end
|
67
|
+
|
68
|
+
def hmac_signature(base_string)
|
69
|
+
digest = OpenSSL::Digest.new(OAUTH_SIGNATURE_ALGORITHM)
|
70
|
+
hmac = OpenSSL::HMAC.digest(digest, signing_key, base_string)
|
71
|
+
Base64.strict_encode64(hmac)
|
72
|
+
end
|
73
|
+
|
74
|
+
def signature_base_string(method, url, params)
|
75
|
+
"#{method}&#{encode(url)}&#{encode(encode_params(params))}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def encode_params(params)
|
79
|
+
params.sort.map { |k, v| "#{k}=#{encode(v.to_s)}" }.join("&")
|
80
|
+
end
|
81
|
+
|
82
|
+
def signing_key
|
83
|
+
"#{encode(api_key_secret)}&#{encode(access_token_secret)}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def format_oauth_header(params)
|
87
|
+
"OAuth #{params.sort.map { |k, v| "#{k}=\"#{encode(v.to_s)}\"" }.join(", ")}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def encode(value)
|
91
|
+
# TODO: Replace CGI.escape with CGI.escapeURIComponent when support for Ruby 3.1 is dropped
|
92
|
+
CGI.escape(value.to_s).gsub("+", "%20")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
require_relative "connection"
|
4
|
+
require_relative "errors/too_many_redirects_error"
|
5
|
+
|
6
|
+
module X
|
7
|
+
# Handles HTTP redirects
|
8
|
+
class RedirectHandler
|
9
|
+
DEFAULT_MAX_REDIRECTS = 10
|
10
|
+
|
11
|
+
attr_reader :authenticator, :connection, :request_builder, :max_redirects
|
12
|
+
|
13
|
+
def initialize(authenticator, connection, request_builder, max_redirects: DEFAULT_MAX_REDIRECTS)
|
14
|
+
@authenticator = authenticator
|
15
|
+
@connection = connection
|
16
|
+
@request_builder = request_builder
|
17
|
+
@max_redirects = max_redirects
|
18
|
+
end
|
19
|
+
|
20
|
+
def handle_redirects(response, original_request, original_base_url, redirect_count = 0)
|
21
|
+
if response.is_a?(Net::HTTPRedirection)
|
22
|
+
raise TooManyRedirectsError.new("Too many redirects", response: response) if redirect_count >= max_redirects
|
23
|
+
|
24
|
+
new_uri = build_new_uri(response, original_base_url)
|
25
|
+
new_request = build_request(original_request, new_uri)
|
26
|
+
new_response = send_new_request(new_uri, new_request)
|
27
|
+
|
28
|
+
handle_redirects(new_response, new_request, original_base_url, redirect_count + 1)
|
29
|
+
else
|
30
|
+
response
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def build_new_uri(response, original_base_url)
|
37
|
+
location = response["location"].to_s
|
38
|
+
new_uri = URI.parse(location)
|
39
|
+
new_uri = URI.join(original_base_url.to_s, location) if new_uri.relative?
|
40
|
+
new_uri
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_request(original_request, new_uri)
|
44
|
+
http_method = original_request.method.downcase.to_sym
|
45
|
+
body = original_request.body if original_request.body
|
46
|
+
request_builder.build(authenticator, http_method, new_uri, body: body)
|
47
|
+
end
|
48
|
+
|
49
|
+
def send_new_request(new_uri, new_request)
|
50
|
+
@connection = Connection.new(base_url: new_uri, open_timeout: connection.open_timeout,
|
51
|
+
read_timeout: connection.read_timeout, write_timeout: connection.write_timeout,
|
52
|
+
debug_output: connection.debug_output)
|
53
|
+
connection.send_request(new_request)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/x/request_builder.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "net/http"
|
2
2
|
require "uri"
|
3
|
+
require_relative "version"
|
3
4
|
|
4
5
|
module X
|
5
6
|
# Creates HTTP requests
|
@@ -10,16 +11,20 @@ module X
|
|
10
11
|
put: Net::HTTP::Put,
|
11
12
|
delete: Net::HTTP::Delete
|
12
13
|
}.freeze
|
14
|
+
DEFAULT_CONTENT_TYPE = "application/json; charset=utf-8".freeze
|
15
|
+
DEFAULT_USER_AGENT = "X-Client/#{VERSION} Ruby/#{RUBY_VERSION}".freeze
|
16
|
+
AUTHORIZATION_HEADER = "Authorization".freeze
|
17
|
+
CONTENT_TYPE_HEADER = "Content-Type".freeze
|
18
|
+
USER_AGENT_HEADER = "User-Agent".freeze
|
13
19
|
|
14
20
|
attr_accessor :content_type, :user_agent
|
15
21
|
|
16
|
-
def initialize(content_type, user_agent)
|
22
|
+
def initialize(content_type: DEFAULT_CONTENT_TYPE, user_agent: DEFAULT_USER_AGENT)
|
17
23
|
@content_type = content_type
|
18
24
|
@user_agent = user_agent
|
19
25
|
end
|
20
26
|
|
21
|
-
def build(authenticator, http_method,
|
22
|
-
url = URI.join(base_url.to_s, endpoint)
|
27
|
+
def build(authenticator, http_method, url, body: nil)
|
23
28
|
request = create_request(http_method, url, body)
|
24
29
|
add_authorization(request, authenticator)
|
25
30
|
add_content_type(request)
|
@@ -40,15 +45,15 @@ module X
|
|
40
45
|
end
|
41
46
|
|
42
47
|
def add_authorization(request, authenticator)
|
43
|
-
authenticator.
|
48
|
+
request.add_field(AUTHORIZATION_HEADER, authenticator.header(request))
|
44
49
|
end
|
45
50
|
|
46
51
|
def add_content_type(request)
|
47
|
-
request.add_field(
|
52
|
+
request.add_field(CONTENT_TYPE_HEADER, content_type) if content_type
|
48
53
|
end
|
49
54
|
|
50
55
|
def add_user_agent(request)
|
51
|
-
request.add_field(
|
56
|
+
request.add_field(USER_AGENT_HEADER, user_agent) if user_agent
|
52
57
|
end
|
53
58
|
end
|
54
59
|
end
|
data/lib/x/response_handler.rb
CHANGED
@@ -1,16 +1,32 @@
|
|
1
1
|
require "json"
|
2
2
|
require "net/http"
|
3
|
-
require_relative "errors/
|
3
|
+
require_relative "errors/bad_request_error"
|
4
|
+
require_relative "errors/authentication_error"
|
5
|
+
require_relative "errors/forbidden_error"
|
6
|
+
require_relative "errors/not_found_error"
|
7
|
+
require_relative "errors/too_many_requests_error"
|
8
|
+
require_relative "errors/server_error"
|
9
|
+
require_relative "errors/service_unavailable_error"
|
4
10
|
|
5
11
|
module X
|
6
12
|
# Process HTTP responses
|
7
13
|
class ResponseHandler
|
8
|
-
|
9
|
-
|
14
|
+
DEFAULT_ARRAY_CLASS = Array
|
15
|
+
DEFAULT_OBJECT_CLASS = Hash
|
16
|
+
ERROR_CLASSES = {
|
17
|
+
400 => BadRequestError,
|
18
|
+
401 => AuthenticationError,
|
19
|
+
403 => ForbiddenError,
|
20
|
+
404 => NotFoundError,
|
21
|
+
429 => TooManyRequestsError,
|
22
|
+
500 => ServerError,
|
23
|
+
503 => ServiceUnavailableError
|
24
|
+
}.freeze
|
25
|
+
JSON_CONTENT_TYPE_REGEXP = %r{application/(problem\+|)json}
|
10
26
|
|
11
27
|
attr_accessor :array_class, :object_class
|
12
28
|
|
13
|
-
def initialize(array_class, object_class)
|
29
|
+
def initialize(array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
|
14
30
|
@array_class = array_class
|
15
31
|
@object_class = object_class
|
16
32
|
end
|
@@ -22,13 +38,13 @@ module X
|
|
22
38
|
|
23
39
|
error_class = ERROR_CLASSES[response.code.to_i] || Error
|
24
40
|
error_message = "#{response.code} #{response.message}"
|
25
|
-
raise error_class.new(error_message, response: response
|
41
|
+
raise error_class.new(error_message, response: response)
|
26
42
|
end
|
27
43
|
|
28
44
|
private
|
29
45
|
|
30
46
|
def successful_json_response?(response)
|
31
|
-
response.is_a?(Net::HTTPSuccess) && response.body && response["content-type"]
|
47
|
+
response.is_a?(Net::HTTPSuccess) && response.body && JSON_CONTENT_TYPE_REGEXP.match?(response["content-type"])
|
32
48
|
end
|
33
49
|
end
|
34
50
|
end
|
data/lib/x/version.rb
CHANGED
data/sig/x.rbs
CHANGED
@@ -1,44 +1,44 @@
|
|
1
1
|
module X
|
2
2
|
VERSION: Gem::Version
|
3
3
|
|
4
|
-
class
|
4
|
+
class BearerTokenAuthenticator
|
5
|
+
attr_accessor bearer_token: String
|
6
|
+
def initialize: (String bearer_token) -> void
|
7
|
+
def header: (Net::HTTPRequest _request) -> String
|
8
|
+
end
|
9
|
+
|
10
|
+
class OauthAuthenticator
|
5
11
|
OAUTH_VERSION: String
|
6
12
|
OAUTH_SIGNATURE_METHOD: String
|
13
|
+
OAUTH_SIGNATURE_ALGORITHM: String
|
7
14
|
|
8
15
|
attr_accessor api_key: String
|
9
16
|
attr_accessor api_key_secret: String
|
10
17
|
attr_accessor access_token: String
|
11
18
|
attr_accessor access_token_secret: String
|
12
19
|
def initialize: (String api_key, String api_key_secret, String access_token, String access_token_secret) -> void
|
13
|
-
def
|
20
|
+
def header: (Net::HTTPRequest request) -> String
|
14
21
|
|
15
22
|
private
|
16
|
-
def
|
17
|
-
def
|
23
|
+
def parse_request: (Net::HTTPRequest request) -> [String, String, Hash[String, String]]
|
24
|
+
def parse_query_params: (String query_string) -> Hash[String, String]
|
25
|
+
def uri_without_query: (URI::Generic uri) -> String
|
26
|
+
def build_oauth_header: (String method, String url, Hash[String, String] query_params) -> String
|
18
27
|
def default_oauth_params: -> Hash[String, String]
|
19
|
-
def generate_signature: (String method, String
|
20
|
-
def
|
28
|
+
def generate_signature: (String method, String url, Hash[String, String] params) -> String
|
29
|
+
def hmac_signature: (String base_string) -> String
|
30
|
+
def signature_base_string: (String method, String url, Hash[String, String] params) -> String
|
21
31
|
def encode_params: (Hash[String, String] params) -> String
|
22
32
|
def signing_key: -> String
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
module ClientDefaults
|
27
|
-
DEFAULT_BASE_URL: String
|
28
|
-
DEFAULT_CONTENT_TYPE: String
|
29
|
-
DEFAULT_OPEN_TIMEOUT: Integer
|
30
|
-
DEFAULT_READ_TIMEOUT: Integer
|
31
|
-
DEFAULT_WRITE_TIMEOUT: Integer
|
32
|
-
DEFAULT_USER_AGENT: String
|
33
|
-
DEFAULT_ARRAY_CLASS: Class
|
34
|
-
DEFAULT_OBJECT_CLASS: Class
|
33
|
+
def format_oauth_header: (Hash[String, String] params) -> String
|
34
|
+
def encode: (String value) -> String
|
35
35
|
end
|
36
36
|
|
37
37
|
class Error < StandardError
|
38
|
-
|
38
|
+
JSON_CONTENT_TYPE_REGEXP: Regexp
|
39
39
|
|
40
|
-
attr_reader object: untyped
|
41
|
-
def initialize: (String msg, response: Net::HTTPResponse
|
40
|
+
attr_reader object: Hash[String, untyped]
|
41
|
+
def initialize: (String msg, response: Net::HTTPResponse) -> void
|
42
42
|
|
43
43
|
private
|
44
44
|
def json_response?: (Net::HTTPResponse response) -> bool
|
@@ -59,11 +59,13 @@ module X
|
|
59
59
|
class NotFoundError < ClientError
|
60
60
|
end
|
61
61
|
|
62
|
+
class TooManyRedirectsError < ClientError
|
63
|
+
end
|
64
|
+
|
62
65
|
class TooManyRequestsError < ClientError
|
63
|
-
include ClientDefaults
|
64
66
|
@response: Net::HTTPResponse
|
65
67
|
|
66
|
-
def initialize: (String msg, response: Net::HTTPResponse
|
68
|
+
def initialize: (String msg, response: Net::HTTPResponse) -> void
|
67
69
|
def limit: -> Integer
|
68
70
|
def remaining: -> Integer
|
69
71
|
def reset_at: -> Time
|
@@ -76,49 +78,82 @@ module X
|
|
76
78
|
class ServiceUnavailableError < ServerError
|
77
79
|
end
|
78
80
|
|
79
|
-
module Errors
|
80
|
-
ERROR_CLASSES: Hash[Integer, singleton(AuthenticationError) | singleton(BadRequestError) | singleton(ForbiddenError) | singleton(NotFoundError) | singleton(ServerError) | singleton(ServiceUnavailableError) | singleton(TooManyRequestsError)]
|
81
|
-
NETWORK_ERRORS: Array[(singleton(::Errno::ECONNREFUSED) | singleton(::Net::OpenTimeout) | singleton(::Net::ReadTimeout))]
|
82
|
-
end
|
83
|
-
|
84
81
|
class NetworkError < Error
|
85
82
|
end
|
86
83
|
|
87
84
|
class Connection
|
85
|
+
DEFAULT_BASE_URL: String
|
86
|
+
DEFAULT_HOST: String
|
87
|
+
DEFAULT_PORT: Integer
|
88
|
+
DEFAULT_OPEN_TIMEOUT: Integer
|
89
|
+
DEFAULT_READ_TIMEOUT: Integer
|
90
|
+
DEFAULT_WRITE_TIMEOUT: Integer
|
91
|
+
NETWORK_ERRORS: Array[(singleton(::Errno::ECONNREFUSED) | singleton(::Net::OpenTimeout) | singleton(::Net::ReadTimeout))]
|
88
92
|
extend Forwardable
|
89
|
-
include Errors
|
90
93
|
@http_client: Net::HTTP
|
91
94
|
|
92
|
-
attr_reader
|
93
|
-
|
95
|
+
attr_reader base_uri: URI::Generic
|
96
|
+
attr_reader open_timeout : Float | Integer
|
97
|
+
attr_reader read_timeout : Float | Integer
|
98
|
+
attr_reader write_timeout : Float | Integer
|
99
|
+
def initialize: (?base_url: URI::Generic | String, ?open_timeout: Float | Integer, ?read_timeout: Float | Integer, ?write_timeout: Float | Integer, ?debug_output: IO?) -> void
|
94
100
|
def send_request: (Net::HTTPRequest request) -> Net::HTTPResponse
|
95
|
-
def
|
101
|
+
def base_uri=: (URI::Generic | String base_url) -> void
|
96
102
|
def debug_output: -> IO?
|
103
|
+
|
104
|
+
private
|
105
|
+
def apply_http_client_settings: (open_timeout: Float | Integer, read_timeout: Float | Integer, write_timeout: Float | Integer, debug_output: IO?) -> untyped
|
106
|
+
def current_http_client_settings: -> {open_timeout: Float | Integer, read_timeout: Float | Integer, write_timeout: Float | Integer, debug_output: IO?}
|
107
|
+
def update_http_client_settings: -> untyped
|
108
|
+
def conditionally_apply_http_client_settings: { -> untyped } -> untyped
|
97
109
|
end
|
98
110
|
|
99
111
|
class RequestBuilder
|
100
112
|
HTTP_METHODS: Hash[::Symbol, (singleton(::Net::HTTP::Get) | singleton(::Net::HTTP::Post) | singleton(::Net::HTTP::Put) | singleton(::Net::HTTP::Delete))]
|
113
|
+
DEFAULT_CONTENT_TYPE: String
|
114
|
+
DEFAULT_USER_AGENT: String
|
115
|
+
AUTHORIZATION_HEADER: String
|
116
|
+
CONTENT_TYPE_HEADER: String
|
117
|
+
USER_AGENT_HEADER: String
|
101
118
|
|
102
119
|
attr_accessor content_type: String
|
103
120
|
attr_accessor user_agent: String
|
104
|
-
def initialize: (
|
105
|
-
def build: (
|
121
|
+
def initialize: (?content_type: String, ?user_agent: String) -> void
|
122
|
+
def build: (BearerTokenAuthenticator | OauthAuthenticator authenticator, Symbol http_method, URI::Generic url, ?body: String?) -> (Net::HTTPRequest)
|
106
123
|
|
107
124
|
private
|
108
|
-
def create_request: (
|
109
|
-
def add_authorization: (Net::HTTPRequest request,
|
125
|
+
def create_request: (Symbol http_method, URI::Generic url, String? body) -> (Net::HTTPRequest)
|
126
|
+
def add_authorization: (Net::HTTPRequest request, BearerTokenAuthenticator | OauthAuthenticator authenticator) -> void
|
110
127
|
def add_content_type: (Net::HTTPRequest request) -> void
|
111
128
|
def add_user_agent: (Net::HTTPRequest request) -> void
|
112
129
|
end
|
113
130
|
|
131
|
+
class RedirectHandler
|
132
|
+
DEFAULT_MAX_REDIRECTS: Integer
|
133
|
+
|
134
|
+
attr_reader authenticator: BearerTokenAuthenticator | OauthAuthenticator
|
135
|
+
attr_reader connection: Connection
|
136
|
+
attr_reader request_builder: RequestBuilder
|
137
|
+
attr_reader max_redirects: Integer
|
138
|
+
def initialize: (BearerTokenAuthenticator | OauthAuthenticator authenticator, Connection connection, RequestBuilder request_builder, ?max_redirects: Integer) -> void
|
139
|
+
def handle_redirects: (Net::HTTPResponse response, Net::HTTPRequest original_request, URI::Generic | String original_base_url, ?Integer redirect_count) -> Net::HTTPResponse
|
140
|
+
|
141
|
+
private
|
142
|
+
def build_new_uri: (Net::HTTPResponse response, URI::Generic | String original_base_url) -> URI::Generic
|
143
|
+
def build_request: (Net::HTTPRequest original_request, URI::Generic new_uri) -> Net::HTTPRequest
|
144
|
+
def send_new_request: (URI::Generic new_uri, Net::HTTPRequest new_request) -> Net::HTTPResponse
|
145
|
+
end
|
146
|
+
|
114
147
|
class ResponseHandler
|
115
|
-
|
116
|
-
|
148
|
+
DEFAULT_ARRAY_CLASS: Class
|
149
|
+
DEFAULT_OBJECT_CLASS: Class
|
150
|
+
ERROR_CLASSES: Hash[Integer, singleton(AuthenticationError) | singleton(BadRequestError) | singleton(ForbiddenError) | singleton(NotFoundError) | singleton(ServerError) | singleton(ServiceUnavailableError) | singleton(TooManyRequestsError)]
|
151
|
+
JSON_CONTENT_TYPE_REGEXP: Regexp
|
117
152
|
|
118
153
|
attr_accessor array_class: Class
|
119
154
|
attr_accessor object_class: Class
|
120
|
-
def initialize: (
|
121
|
-
def handle: (Net::HTTPResponse response) -> untyped
|
155
|
+
def initialize: (?array_class: Class, ?object_class: Class) -> void
|
156
|
+
def handle: (Net::HTTPResponse response) -> Hash[String, untyped]
|
122
157
|
|
123
158
|
private
|
124
159
|
def successful_json_response?: (Net::HTTPResponse response) -> bool
|
@@ -126,20 +161,21 @@ module X
|
|
126
161
|
|
127
162
|
class Client
|
128
163
|
extend Forwardable
|
129
|
-
|
130
|
-
@authenticator: Authenticator
|
164
|
+
@authenticator: BearerTokenAuthenticator | OauthAuthenticator
|
131
165
|
@connection: Connection
|
132
166
|
@request_builder: RequestBuilder
|
167
|
+
@redirect_handler: RedirectHandler
|
133
168
|
@response_handler: ResponseHandler
|
134
169
|
|
135
|
-
attr_reader
|
136
|
-
def initialize: (api_key: String
|
137
|
-
def get: (String endpoint) -> untyped
|
138
|
-
def post: (String endpoint, ?nil body) -> untyped
|
139
|
-
def put: (String endpoint, ?nil body) -> untyped
|
140
|
-
def delete: (String endpoint) -> untyped
|
170
|
+
attr_reader base_uri: URI::Generic
|
171
|
+
def initialize: (?bearer_token: String?, ?api_key: String?, ?api_key_secret: String?, ?access_token: String?, ?access_token_secret: String?, ?base_url: URI::Generic | String, ?content_type: String, ?user_agent: String, ?open_timeout: Float | Integer, ?read_timeout: Float | Integer, ?write_timeout: Float | Integer, ?debug_output: IO?, ?array_class: Class, ?object_class: Class, ?max_redirects: Integer) -> void
|
172
|
+
def get: (String endpoint) -> Hash[String, untyped]
|
173
|
+
def post: (String endpoint, ?nil body) -> Hash[String, untyped]
|
174
|
+
def put: (String endpoint, ?nil body) -> Hash[String, untyped]
|
175
|
+
def delete: (String endpoint) -> Hash[String, untyped]
|
141
176
|
|
142
177
|
private
|
143
|
-
def
|
178
|
+
def initialize_authenticator: (String? bearer_token, String? api_key, String? api_key_secret, String? access_token, String? access_token_secret) -> (BearerTokenAuthenticator | OauthAuthenticator)
|
179
|
+
def send_request: (Symbol http_method, String endpoint, ?nil body) -> Hash[String, untyped]
|
144
180
|
end
|
145
181
|
end
|
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.8.1
|
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-09-
|
11
|
+
date: 2023-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -25,21 +25,22 @@ files:
|
|
25
25
|
- Rakefile
|
26
26
|
- Steepfile
|
27
27
|
- lib/x.rb
|
28
|
-
- lib/x/
|
28
|
+
- lib/x/bearer_token_authenticator.rb
|
29
29
|
- lib/x/client.rb
|
30
|
-
- lib/x/client_defaults.rb
|
31
30
|
- lib/x/connection.rb
|
32
31
|
- lib/x/errors/authentication_error.rb
|
33
32
|
- lib/x/errors/bad_request_error.rb
|
34
33
|
- lib/x/errors/client_error.rb
|
35
34
|
- lib/x/errors/error.rb
|
36
|
-
- lib/x/errors/errors.rb
|
37
35
|
- lib/x/errors/forbidden_error.rb
|
38
36
|
- lib/x/errors/network_error.rb
|
39
37
|
- lib/x/errors/not_found_error.rb
|
40
38
|
- lib/x/errors/server_error.rb
|
41
39
|
- lib/x/errors/service_unavailable_error.rb
|
40
|
+
- lib/x/errors/too_many_redirects_error.rb
|
42
41
|
- lib/x/errors/too_many_requests_error.rb
|
42
|
+
- lib/x/oauth_authenticator.rb
|
43
|
+
- lib/x/redirect_handler.rb
|
43
44
|
- lib/x/request_builder.rb
|
44
45
|
- lib/x/response_handler.rb
|
45
46
|
- lib/x/version.rb
|
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
69
|
- !ruby/object:Gem::Version
|
69
70
|
version: '0'
|
70
71
|
requirements: []
|
71
|
-
rubygems_version: 3.4.
|
72
|
+
rubygems_version: 3.4.19
|
72
73
|
signing_key:
|
73
74
|
specification_version: 4
|
74
75
|
summary: A Ruby interface to the X API.
|
data/lib/x/authenticator.rb
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
require "base64"
|
2
|
-
require "cgi"
|
3
|
-
require "json"
|
4
|
-
require "openssl"
|
5
|
-
require "securerandom"
|
6
|
-
require "uri"
|
7
|
-
|
8
|
-
module X
|
9
|
-
# Handles OAuth authentication
|
10
|
-
class Authenticator
|
11
|
-
attr_accessor :api_key, :api_key_secret, :access_token, :access_token_secret
|
12
|
-
|
13
|
-
OAUTH_VERSION = "1.0".freeze
|
14
|
-
OAUTH_SIGNATURE_METHOD = "HMAC-SHA1".freeze
|
15
|
-
|
16
|
-
def initialize(api_key, api_key_secret, access_token, access_token_secret)
|
17
|
-
@api_key = api_key
|
18
|
-
@api_key_secret = api_key_secret
|
19
|
-
@access_token = access_token
|
20
|
-
@access_token_secret = access_token_secret
|
21
|
-
end
|
22
|
-
|
23
|
-
def sign!(request)
|
24
|
-
method = request.method
|
25
|
-
uri, query_params = split_uri(request.uri)
|
26
|
-
request.add_field("Authorization", oauth_header(method, uri, query_params))
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def split_uri(uri)
|
32
|
-
query_string = uri.query.to_s
|
33
|
-
uri_base = uri.to_s.chomp("?#{query_string}")
|
34
|
-
query_params = URI.decode_www_form(query_string).to_h
|
35
|
-
[uri_base, query_params]
|
36
|
-
end
|
37
|
-
|
38
|
-
def oauth_header(method, uri, query_params)
|
39
|
-
oauth_params = default_oauth_params
|
40
|
-
all_params = query_params.merge(oauth_params)
|
41
|
-
oauth_params["oauth_signature"] = generate_signature(method, uri, all_params)
|
42
|
-
formatted_oauth_header(oauth_params)
|
43
|
-
end
|
44
|
-
|
45
|
-
def default_oauth_params
|
46
|
-
{
|
47
|
-
"oauth_consumer_key" => @api_key,
|
48
|
-
"oauth_nonce" => SecureRandom.hex,
|
49
|
-
"oauth_signature_method" => OAUTH_SIGNATURE_METHOD,
|
50
|
-
"oauth_timestamp" => Time.now.utc.to_i.to_s,
|
51
|
-
"oauth_token" => @access_token,
|
52
|
-
"oauth_version" => OAUTH_VERSION
|
53
|
-
}
|
54
|
-
end
|
55
|
-
|
56
|
-
def generate_signature(method, uri, params)
|
57
|
-
Base64.encode64(OpenSSL::HMAC.digest(
|
58
|
-
OpenSSL::Digest.new("sha1"),
|
59
|
-
signing_key,
|
60
|
-
signature_base_string(method, uri, params)
|
61
|
-
)).chomp
|
62
|
-
end
|
63
|
-
|
64
|
-
def signature_base_string(method, uri, params)
|
65
|
-
encoded_params = encode_params(params)
|
66
|
-
"#{method}&#{CGI.escape(uri)}&#{CGI.escape(encoded_params)}"
|
67
|
-
end
|
68
|
-
|
69
|
-
def encode_params(params)
|
70
|
-
# TODO: Replace CGI.escape with CGI.escapeURIComponent when support for Ruby 3.1 is dropped
|
71
|
-
params.sort.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&").gsub("+", "%20")
|
72
|
-
end
|
73
|
-
|
74
|
-
def signing_key
|
75
|
-
"#{CGI.escape(@api_key_secret)}&#{CGI.escape(@access_token_secret)}"
|
76
|
-
end
|
77
|
-
|
78
|
-
def formatted_oauth_header(params)
|
79
|
-
"OAuth #{params.sort.map { |k, v| "#{k}=\"#{CGI.escape(v.to_s)}\"" }.join(", ")}"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
data/lib/x/client_defaults.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require_relative "version"
|
2
|
-
|
3
|
-
module X
|
4
|
-
module ClientDefaults
|
5
|
-
DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze
|
6
|
-
DEFAULT_CONTENT_TYPE = "application/json; charset=utf-8".freeze
|
7
|
-
DEFAULT_ARRAY_CLASS = Array
|
8
|
-
DEFAULT_OBJECT_CLASS = Hash
|
9
|
-
DEFAULT_OPEN_TIMEOUT = 60 # seconds
|
10
|
-
DEFAULT_READ_TIMEOUT = 60 # seconds
|
11
|
-
DEFAULT_WRITE_TIMEOUT = 60 # seconds
|
12
|
-
DEFAULT_USER_AGENT = "X-Client/#{VERSION} Ruby/#{RUBY_VERSION}".freeze
|
13
|
-
end
|
14
|
-
end
|
data/lib/x/errors/errors.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
require "net/http"
|
2
|
-
require_relative "bad_request_error"
|
3
|
-
require_relative "authentication_error"
|
4
|
-
require_relative "forbidden_error"
|
5
|
-
require_relative "not_found_error"
|
6
|
-
require_relative "too_many_requests_error"
|
7
|
-
require_relative "server_error"
|
8
|
-
require_relative "service_unavailable_error"
|
9
|
-
|
10
|
-
module X
|
11
|
-
module Errors
|
12
|
-
ERROR_CLASSES = {
|
13
|
-
400 => BadRequestError,
|
14
|
-
401 => AuthenticationError,
|
15
|
-
403 => ForbiddenError,
|
16
|
-
404 => NotFoundError,
|
17
|
-
429 => TooManyRequestsError,
|
18
|
-
500 => ServerError,
|
19
|
-
503 => ServiceUnavailableError
|
20
|
-
}.freeze
|
21
|
-
|
22
|
-
NETWORK_ERRORS = [
|
23
|
-
Errno::ECONNREFUSED,
|
24
|
-
Net::OpenTimeout,
|
25
|
-
Net::ReadTimeout
|
26
|
-
].freeze
|
27
|
-
end
|
28
|
-
end
|