bitly 1.1.2 → 2.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +36 -3
- data/.rspec +3 -0
- data/.travis.yml +5 -2
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -2
- data/History.txt +18 -1
- data/LICENSE.md +1 -1
- data/README.md +151 -58
- data/Rakefile +4 -9
- data/bin/console +14 -0
- data/bin/oauth +10 -0
- data/bin/setup +8 -0
- data/bitly.gemspec +24 -29
- data/config/env.yml.example +5 -0
- data/docs/authentication.md +51 -0
- data/docs/bitlinks.md +2 -0
- data/docs/branded_short_domains.md +20 -0
- data/docs/groups.md +80 -0
- data/docs/oauth_apps.md +20 -0
- data/docs/organizations.md +74 -0
- data/docs/premium_apis.md +0 -0
- data/docs/users.md +1 -0
- data/lib/bitly.rb +6 -7
- data/lib/bitly/api.rb +16 -0
- data/lib/bitly/api/base.rb +22 -0
- data/lib/bitly/api/bitlink.rb +341 -0
- data/lib/bitly/api/bitlink/clicks_summary.rb +35 -0
- data/lib/bitly/api/bitlink/deeplink.rb +29 -0
- data/lib/bitly/api/bitlink/link_click.rb +74 -0
- data/lib/bitly/api/bitlink/paginated_list.rb +51 -0
- data/lib/bitly/api/bsd.rb +24 -0
- data/lib/bitly/api/click_metric.rb +185 -0
- data/lib/bitly/api/client.rb +588 -0
- data/lib/bitly/api/group.rb +232 -0
- data/lib/bitly/api/group/preferences.rb +73 -0
- data/lib/bitly/api/list.rb +22 -0
- data/lib/bitly/api/oauth_app.rb +26 -0
- data/lib/bitly/api/organization.rb +104 -0
- data/lib/bitly/api/shorten_counts.rb +61 -0
- data/lib/bitly/api/user.rb +107 -0
- data/lib/bitly/error.rb +33 -0
- data/lib/bitly/http.rb +6 -0
- data/lib/bitly/http/adapters/net_http.rb +27 -0
- data/lib/bitly/http/client.rb +33 -0
- data/lib/bitly/http/request.rb +116 -0
- data/lib/bitly/http/response.rb +65 -0
- data/lib/bitly/oauth.rb +109 -0
- data/lib/bitly/version.rb +3 -1
- metadata +88 -108
- data/Manifest +0 -37
- data/lib/bitly/url.rb +0 -103
- data/lib/bitly/utils.rb +0 -57
- data/lib/bitly/v3.rb +0 -14
- data/lib/bitly/v3/bitly.rb +0 -7
- data/lib/bitly/v3/country.rb +0 -13
- data/lib/bitly/v3/day.rb +0 -13
- data/lib/bitly/v3/missing_url.rb +0 -15
- data/lib/bitly/v3/oauth.rb +0 -41
- data/lib/bitly/v3/realtime_link.rb +0 -18
- data/lib/bitly/v3/referrer.rb +0 -13
- data/lib/bitly/v3/url.rb +0 -154
- data/test/bitly/test_client.rb +0 -266
- data/test/bitly/test_config.rb +0 -28
- data/test/bitly/test_url.rb +0 -167
- data/test/bitly/test_utils.rb +0 -79
- data/test/fixtures/cnn.json +0 -1
- data/test/fixtures/cnn_and_google.json +0 -1
- data/test/fixtures/expand_cnn.json +0 -1
- data/test/fixtures/expand_cnn_and_google.json +0 -1
- data/test/fixtures/google_and_cnn_info.json +0 -1
- data/test/fixtures/google_info.json +0 -1
- data/test/fixtures/google_stats.json +0 -1
- data/test/fixtures/shorten_error.json +0 -1
- data/test/test_helper.rb +0 -39
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./base"
|
3
|
+
|
4
|
+
module Bitly
|
5
|
+
module API
|
6
|
+
class ShortenCounts
|
7
|
+
include Base
|
8
|
+
|
9
|
+
def self.attributes
|
10
|
+
[:units, :facet, :unit_reference, :unit]
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader(*attributes)
|
14
|
+
attr_reader :metrics
|
15
|
+
|
16
|
+
Metric = Struct.new(:key, :value)
|
17
|
+
|
18
|
+
##
|
19
|
+
# Shorten counts by group
|
20
|
+
# [`GET /v4/groups/{group_guid}/shorten_counts`](https://dev.bitly.com/v4/#operation/getGroupShortenCounts)
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# shorten_counts = Bitly::API::ShortenCounts.by_group(client: client, group_guid: group_guid)
|
24
|
+
#
|
25
|
+
# @param client [Bitly::API::Client] An authorized API client
|
26
|
+
# @param group_guid [String] The guid of the group for which you want
|
27
|
+
# shorten counts
|
28
|
+
#
|
29
|
+
# @return [Bitly::API::ShortenCounts]
|
30
|
+
def self.by_group(client:, group_guid:)
|
31
|
+
response = client.request(path: "/groups/#{group_guid}/shorten_counts")
|
32
|
+
new(data: response.body, response: response)
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Shorten counts by organization
|
37
|
+
# [`GET /v4/organizations/{organization_guid}/shorten_counts`](https://dev.bitly.com/v4/#operation/getOrganizationShortenCounts)
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# shorten_counts = Bitly::API::ShortenCounts.by_organization(client: client, organization_guid: organization_guid)
|
41
|
+
#
|
42
|
+
# @param client [Bitly::API::Client] An authorized API client
|
43
|
+
# @param organization_guid [String] The guid of the organization for which
|
44
|
+
# you want shorten counts
|
45
|
+
#
|
46
|
+
# @return [Bitly::API::ShortenCounts]
|
47
|
+
def self.by_organization(client:, organization_guid:)
|
48
|
+
response = client.request(path: "/organizations/#{organization_guid}/shorten_counts")
|
49
|
+
new(data: response.body, response: response)
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(data:, response: nil)
|
53
|
+
assign_attributes(data)
|
54
|
+
@metrics = data["metrics"].map do |metric|
|
55
|
+
Metric.new(metric["key"], metric["value"])
|
56
|
+
end
|
57
|
+
@response = response
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./base"
|
3
|
+
|
4
|
+
module Bitly
|
5
|
+
module API
|
6
|
+
##
|
7
|
+
# A User represents the authorized user
|
8
|
+
class User
|
9
|
+
class Email
|
10
|
+
attr_reader :email, :is_verified, :is_primary
|
11
|
+
def initialize(data)
|
12
|
+
@email = data["email"]
|
13
|
+
@is_verified = data["is_verified"]
|
14
|
+
@is_primary = data["is_primary"]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
include Base
|
19
|
+
##
|
20
|
+
# Gets the authorized user from the API.
|
21
|
+
# [`GET /v4/user`](https://dev.bitly.com/v4/#operation/getUser)
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# user = Bitly::API::User.fetch(client: client)
|
25
|
+
#
|
26
|
+
# @param client [Bitly::API::Client] The authorized API client
|
27
|
+
#
|
28
|
+
# @return [Bitly::API::User]
|
29
|
+
def self.fetch(client:)
|
30
|
+
response = client.request(path: "/user")
|
31
|
+
new(data: response.body, client: client, response: response)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Array<Symbol>] The attributes the API returns for a user
|
35
|
+
def self.attributes
|
36
|
+
[:login, :is_active, :is_2fa_enabled, :name, :is_sso_user, :default_group_guid]
|
37
|
+
end
|
38
|
+
# @return [Array<Symbol>] The attributes the API returns that need to be
|
39
|
+
# converted to `Time` objects.
|
40
|
+
def self.time_attributes
|
41
|
+
[:created, :modified]
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader(*(attributes + time_attributes))
|
45
|
+
attr_reader :emails
|
46
|
+
|
47
|
+
##
|
48
|
+
# Creates a Bitly::API::User object.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# user = Bitly::API::User.new(data: user_data, client: client)
|
52
|
+
#
|
53
|
+
# @param data [Hash<String, String | Boolean>] The user data from the API
|
54
|
+
# @param client [Bitly::API::Client] The authorized API client
|
55
|
+
# @param response [Bitly::HTTP::Response] The original HTTP response
|
56
|
+
#
|
57
|
+
# @return [Bitly::API::User]
|
58
|
+
def initialize(data:, client:, response: nil)
|
59
|
+
assign_attributes(data)
|
60
|
+
@client = client
|
61
|
+
@response = response
|
62
|
+
if data["emails"]
|
63
|
+
@emails = data["emails"].map { |e| Email.new(e) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Returns the default group for the user from the default group guid
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# user.default_group
|
72
|
+
#
|
73
|
+
# @returns [Bitly::API::Group]
|
74
|
+
def default_group
|
75
|
+
@default_group ||= Group.fetch(client: @client, guid: default_group_guid)
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Allows you to update the authorized user's name or default group guid.
|
80
|
+
# If you update the default group ID and have already loaded the default
|
81
|
+
# group, it is nilled out so it can be reloaded with the correct ID.
|
82
|
+
# [`PATCH /v4/user`](https://dev.bitly.com/v4/#operation/updateUser)]
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# user.update(name: "New Name", default_group_guid: "aaabbb")
|
86
|
+
#
|
87
|
+
# @param name [String] A new name
|
88
|
+
# @param default_group_guid [String] A new default guid
|
89
|
+
#
|
90
|
+
# @return [Bitly::API::User]
|
91
|
+
def update(name: nil, default_group_guid: nil)
|
92
|
+
params = { "name" => name }
|
93
|
+
if default_group_guid
|
94
|
+
params["default_group_guid"] = default_group_guid
|
95
|
+
@default_group = nil
|
96
|
+
end
|
97
|
+
@response = @client.request(
|
98
|
+
path: "/user",
|
99
|
+
method: "PATCH",
|
100
|
+
params: params
|
101
|
+
)
|
102
|
+
assign_attributes(@response.body)
|
103
|
+
self
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/bitly/error.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bitly
|
4
|
+
##
|
5
|
+
# An error class that covers all potential errors from the Bitly API. In an
|
6
|
+
# error scenario, the API is only guaranteed to return a status_code and
|
7
|
+
# status_txt: https://dev.bitly.com/formats.html
|
8
|
+
class Error < StandardError
|
9
|
+
##
|
10
|
+
# @return [String] The status code of the failed request
|
11
|
+
attr_reader :status_code
|
12
|
+
|
13
|
+
##
|
14
|
+
# @return [String] The description of the failed request
|
15
|
+
attr_reader :description
|
16
|
+
|
17
|
+
##
|
18
|
+
# @return [Bitly::HTTP::Response] The response that caused the error
|
19
|
+
attr_reader :response
|
20
|
+
|
21
|
+
##
|
22
|
+
# Creates a new Bitly::Error object
|
23
|
+
#
|
24
|
+
# @param [Bitly::HTTP::Response] response The parsed response to the HTTP request
|
25
|
+
def initialize(response)
|
26
|
+
@response = response
|
27
|
+
@status_code = response.status
|
28
|
+
@description = response.body["description"]
|
29
|
+
@message = "[#{@status_code}] #{response.body["message"]}"
|
30
|
+
super(@message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/bitly/http.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
|
5
|
+
module Bitly
|
6
|
+
module HTTP
|
7
|
+
module Adapters
|
8
|
+
class NetHTTP
|
9
|
+
def request(request)
|
10
|
+
Net::HTTP.start(request.uri.host, request.uri.port, use_ssl: true) do |http|
|
11
|
+
method = Object.const_get("Net::HTTP::#{request.method.capitalize}")
|
12
|
+
full_path = request.uri.path
|
13
|
+
full_path += "?#{request.uri.query}" if request.uri.query
|
14
|
+
http_request = method.new full_path
|
15
|
+
http_request.body = request.body
|
16
|
+
request.headers.each do |header, value|
|
17
|
+
http_request[header] = value
|
18
|
+
end
|
19
|
+
response = http.request http_request
|
20
|
+
success = response.kind_of? Net::HTTPSuccess
|
21
|
+
return [response.code, response.body, response.to_hash, success]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bitly
|
4
|
+
module HTTP
|
5
|
+
class Client
|
6
|
+
def initialize(adapter=Bitly::HTTP::Adapters::NetHTTP.new)
|
7
|
+
@adapter = adapter
|
8
|
+
raise ArgumentError, "Adapter must have a request method." unless @adapter.respond_to?(:request)
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# The main method for the HTTP client. It receives a Bitly::HTTP::Request
|
13
|
+
# object, makes the request described and returns a Bitly::HTTP::Response.
|
14
|
+
#
|
15
|
+
# @param [Bitly::HTTP::Request] request The request that should be made
|
16
|
+
#
|
17
|
+
# @return [Bitly::HTTP::Response] The response from the request.
|
18
|
+
#
|
19
|
+
# @raise [Bitly::Error] If the response is not a successful response
|
20
|
+
# in the 2xx range, then we raise an error with the response passed as
|
21
|
+
# an argument. It is up to the application to catch this error.
|
22
|
+
def request(request)
|
23
|
+
status, body, headers, success = @adapter.request(request)
|
24
|
+
response = Bitly::HTTP::Response.new(status: status, body: body, headers: headers, request: request)
|
25
|
+
if success
|
26
|
+
return response
|
27
|
+
else
|
28
|
+
raise Bitly::Error, response
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bitly
|
4
|
+
module HTTP
|
5
|
+
class Request
|
6
|
+
# @return [String] The HTTP method that the request should be.
|
7
|
+
attr_reader :method
|
8
|
+
|
9
|
+
# @return [Hash] A hash of parameters that will be turned into query
|
10
|
+
# parameters or a request body
|
11
|
+
attr_reader :params
|
12
|
+
|
13
|
+
# @return [Hash] A hash of HTTP headers that will be included with the
|
14
|
+
# request
|
15
|
+
attr_reader :headers
|
16
|
+
|
17
|
+
##
|
18
|
+
# Creates a new Bitly::HTTP::Request object, which is to be used by the
|
19
|
+
# [Bitly::HTTP::Client].
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# request = Bitly::HTTP::Request.new(uri: URI.parse('https://api-ssl.bitly.com/v3/shorten'), method: "GET")
|
23
|
+
#
|
24
|
+
# @param [URI] uri A [URI] that you want to make the request to.
|
25
|
+
# @param [String] method The HTTP method that should be used to make the
|
26
|
+
# request.
|
27
|
+
# @param [Hash] params The parameters to be sent as part of the request.
|
28
|
+
# GET parameters will be sent as part of the query string and other
|
29
|
+
# methods will be added to the request body.
|
30
|
+
def initialize(uri: , method: "GET", params: {}, headers: {})
|
31
|
+
errors = []
|
32
|
+
@uri = uri
|
33
|
+
errors << "uri must be an object of type URI. Received a #{uri.class}" unless uri.kind_of?(URI)
|
34
|
+
@method = method
|
35
|
+
errors << "method must be a valid HTTP method. Received: #{method}." unless HTTP_METHODS.include?(method)
|
36
|
+
@params = params
|
37
|
+
errors << "params must be a hash. Received: #{params.inspect}." unless params.kind_of?(Hash)
|
38
|
+
@headers = headers
|
39
|
+
errors << "headers must be a hash. Received: #{headers.inspect}." unless headers.kind_of?(Hash)
|
40
|
+
raise ArgumentError, errors.join("\n") if errors.any?
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Returns the uri for the request. If the request is an HTTP method that
|
45
|
+
# uses a body to send data, then the uri is the one that the request was
|
46
|
+
# initialised with. If the request uses query parameters, then the
|
47
|
+
# parameters are serialised and added to the uri's query.
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# uri = URI.parse("https://api-ssl.bitly.com/v3/shorten")
|
51
|
+
# request = Bitly::HTTP::Request.new(uri: uri, params: { foo: "bar" })
|
52
|
+
# request.uri.to_s
|
53
|
+
# # => "https://api-ssl.bitly.com/v3/shorten?foo=bar"
|
54
|
+
#
|
55
|
+
# @return [URI] The full URI for the request
|
56
|
+
def uri
|
57
|
+
uri = @uri.dup
|
58
|
+
return uri if HTTP_METHODS_WITH_BODY.include?(@method)
|
59
|
+
if uri.query
|
60
|
+
existing_query = URI.decode_www_form(uri.query)
|
61
|
+
new_query = hash_to_arrays(@params)
|
62
|
+
uri.query = URI.encode_www_form((existing_query + new_query).uniq)
|
63
|
+
else
|
64
|
+
uri.query = URI.encode_www_form(@params) if @params.any?
|
65
|
+
end
|
66
|
+
uri
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Returns the body of the request if the request is an HTTP method that
|
71
|
+
# uses a body to send data. The body is a JSON string of the parameters.
|
72
|
+
# If the request doesn't use a body to send data, this returns nil.
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# uri = URI.parse("https://api-ssl.bitly.com/v3/shorten")
|
76
|
+
# request = Bitly::HTTP::Request.new(uri: uri, method: 'POST', params: { foo: "bar" })
|
77
|
+
# request.body
|
78
|
+
# # => "{\"foo\":\"bar\"}"
|
79
|
+
#
|
80
|
+
# @return [String] The request body
|
81
|
+
def body
|
82
|
+
return nil if HTTP_METHODS_WITHOUT_BODY.include?(@method)
|
83
|
+
return JSON.generate(params)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def hash_to_arrays(hash)
|
89
|
+
hash.map do |key, value|
|
90
|
+
if value.is_a?(Array)
|
91
|
+
value.map { |v| [key, v] }
|
92
|
+
else
|
93
|
+
[[key, value]]
|
94
|
+
end
|
95
|
+
end.flatten(1)
|
96
|
+
end
|
97
|
+
|
98
|
+
HTTP_METHODS_WITHOUT_BODY = [
|
99
|
+
"GET",
|
100
|
+
"HEAD",
|
101
|
+
"DELETE",
|
102
|
+
"TRACE",
|
103
|
+
"OPTIONS"
|
104
|
+
]
|
105
|
+
|
106
|
+
HTTP_METHODS_WITH_BODY = [
|
107
|
+
"POST",
|
108
|
+
"PUT",
|
109
|
+
"PATCH",
|
110
|
+
"CONNECT"
|
111
|
+
]
|
112
|
+
|
113
|
+
HTTP_METHODS = HTTP_METHODS_WITH_BODY + HTTP_METHODS_WITHOUT_BODY
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bitly
|
4
|
+
module HTTP
|
5
|
+
##
|
6
|
+
# The Response class handles generic responses from the API. It is made up
|
7
|
+
# of a status code, body and headers. The body is expected to be JSON and it
|
8
|
+
# will parse the body. The status should lie within the range 100 - 599 and
|
9
|
+
# the headers should be a hash.
|
10
|
+
class Response
|
11
|
+
|
12
|
+
# @return [String] The response's status code
|
13
|
+
attr_reader :status
|
14
|
+
|
15
|
+
# @return [Hash] The response's parsed body
|
16
|
+
attr_reader :body
|
17
|
+
|
18
|
+
# @return [Hash] The response's headers
|
19
|
+
attr_reader :headers
|
20
|
+
|
21
|
+
# @return [Bitly::HTTP::Request] The request that caused this response
|
22
|
+
attr_reader :request
|
23
|
+
|
24
|
+
##
|
25
|
+
# Creates a new Bitly::HTTP::Response object, which can be used by other
|
26
|
+
# objects in the library.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# response = Bitly::HTTP::Response.new(status: "200", body: "{}", headers: {})
|
30
|
+
#
|
31
|
+
# @param [String] status The status code of the response, which should be
|
32
|
+
# between 100 and 599
|
33
|
+
# @param [String] body The body of the response, a String that is valid
|
34
|
+
# JSON and will be parsed
|
35
|
+
# @param [Hash] headers The response headers
|
36
|
+
def initialize(status:, body:, headers:, request: nil)
|
37
|
+
errors = []
|
38
|
+
@status = status
|
39
|
+
errors << "Status must be a valid HTTP status code. Received #{status}" unless is_status?(status)
|
40
|
+
if body.nil? || body.empty?
|
41
|
+
@body = nil
|
42
|
+
else
|
43
|
+
begin
|
44
|
+
@body = JSON.parse(body)
|
45
|
+
rescue JSON::ParserError
|
46
|
+
@body = {
|
47
|
+
"message" => body
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@headers = headers
|
52
|
+
errors << "Headers must be a hash. Received #{headers}" unless headers.is_a?(Hash)
|
53
|
+
@request = request
|
54
|
+
errors << "Request must be a Bitly::HTTP::Request. Received #{request}" if request && !request.is_a?(Request)
|
55
|
+
raise ArgumentError, errors.join("\n"), caller if errors.any?
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def is_status?(status)
|
61
|
+
!!status.match(/\A[1-5][0-9][0-9]\z/)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|