bitlyr 0.9.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.
- data/History.txt +146 -0
- data/LICENSE +20 -0
- data/README.md +97 -0
- data/README.rdoc +37 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/bitlyr.gemspec +154 -0
- data/lib/bitlyr/client.rb +153 -0
- data/lib/bitlyr/country.rb +10 -0
- data/lib/bitlyr/day.rb +12 -0
- data/lib/bitlyr/error.rb +13 -0
- data/lib/bitlyr/lib/core_ext/hash.rb +27 -0
- data/lib/bitlyr/lib/core_ext/string.rb +5 -0
- data/lib/bitlyr/missing_url.rb +12 -0
- data/lib/bitlyr/realtime_link.rb +16 -0
- data/lib/bitlyr/referrer.rb +12 -0
- data/lib/bitlyr/response.rb +39 -0
- data/lib/bitlyr/strategy/access_token.rb +26 -0
- data/lib/bitlyr/strategy/api_key.rb +33 -0
- data/lib/bitlyr/strategy/base.rb +26 -0
- data/lib/bitlyr/strategy/oauth.rb +50 -0
- data/lib/bitlyr/url.rb +124 -0
- data/lib/bitlyr/user.rb +100 -0
- data/lib/bitlyr.rb +39 -0
- data/test/fixtures/9uX1TE.json +1 -0
- data/test/fixtures/9uX1TEclicks.json +1 -0
- data/test/fixtures/9uX1TEclicks2.json +1 -0
- data/test/fixtures/9uX1TEinfo.json +1 -0
- data/test/fixtures/9uX1TEinfo2.json +1 -0
- data/test/fixtures/auth_fail.json +1 -0
- data/test/fixtures/auth_success.json +1 -0
- data/test/fixtures/betaworks.json +1 -0
- data/test/fixtures/betaworks2.json +1 -0
- data/test/fixtures/betaworks_jmp.json +1 -0
- data/test/fixtures/betaworks_other_user.json +1 -0
- data/test/fixtures/bitly9uX1TE.json +1 -0
- data/test/fixtures/bitly_pro_domain.json +1 -0
- data/test/fixtures/clicks_by_day.json +1 -0
- data/test/fixtures/clicks_by_day1.json +1 -0
- data/test/fixtures/clicks_by_day2.json +1 -0
- data/test/fixtures/clicks_by_minute1_url.json +1 -0
- data/test/fixtures/clicks_by_minute2_url.json +1 -0
- data/test/fixtures/clicks_by_minute_hash.json +1 -0
- data/test/fixtures/clicks_by_minute_hashes.json +1 -0
- data/test/fixtures/country_hash.json +1 -0
- data/test/fixtures/country_hash2.json +1 -0
- data/test/fixtures/country_url.json +1 -0
- data/test/fixtures/failure.json +1 -0
- data/test/fixtures/invalid_bitly_pro_domain.json +1 -0
- data/test/fixtures/invalid_credentials.json +1 -0
- data/test/fixtures/invalid_domain.json +1 -0
- data/test/fixtures/invalid_user.json +1 -0
- data/test/fixtures/invalid_x_api_key.json +1 -0
- data/test/fixtures/lookup_multiple_url.json +1 -0
- data/test/fixtures/lookup_not_real_url.json +1 -0
- data/test/fixtures/lookup_single_url.json +1 -0
- data/test/fixtures/missing_hash.json +1 -0
- data/test/fixtures/multiple_info.json +1 -0
- data/test/fixtures/multiple_url_click.json +1 -0
- data/test/fixtures/multiple_urls.json +1 -0
- data/test/fixtures/not_bitly_pro_domain.json +1 -0
- data/test/fixtures/not_found_info.json +1 -0
- data/test/fixtures/referrer_hash.json +1 -0
- data/test/fixtures/referrer_hash2.json +1 -0
- data/test/fixtures/referrer_url.json +1 -0
- data/test/fixtures/success.json +1 -0
- data/test/fixtures/url_info.json +1 -0
- data/test/fixtures/user_clicks.json +32 -0
- data/test/fixtures/user_countries.json +60 -0
- data/test/fixtures/user_realtime_links.json +15 -0
- data/test/fixtures/user_referrers.json +1 -0
- data/test/fixtures/valid_user.json +1 -0
- data/test/integration/strategy/test_api_key.rb +20 -0
- data/test/integration/strategy/test_oauth.rb +52 -0
- data/test/integration/test_client.rb +1415 -0
- data/test/integration/test_user.rb +97 -0
- data/test/test_helper.rb +54 -0
- data/test/unit/core_ext/test_hash.rb +69 -0
- data/test/unit/core_ext/test_string.rb +14 -0
- data/test/unit/strategy/test_access_token.rb +14 -0
- data/test/unit/strategy/test_api_key.rb +11 -0
- data/test/unit/strategy/test_base.rb +71 -0
- data/test/unit/strategy/test_oauth.rb +32 -0
- data/test/unit/test_bitly.rb +43 -0
- data/test/unit/test_client.rb +21 -0
- data/test/unit/test_country.rb +20 -0
- data/test/unit/test_day.rb +22 -0
- data/test/unit/test_error.rb +11 -0
- data/test/unit/test_missing.rb +34 -0
- data/test/unit/test_realtime_link.rb +30 -0
- data/test/unit/test_referrer.rb +20 -0
- data/test/unit/test_response.rb +86 -0
- data/test/unit/test_url.rb +155 -0
- data/test/unit/test_user.rb +17 -0
- metadata +233 -0
data/lib/bitlyr/error.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class Hash
|
2
|
+
def to_query
|
3
|
+
map {|k, v|"#{k}=#{v}"}.sort * '&'
|
4
|
+
end
|
5
|
+
|
6
|
+
def stringify_keys!
|
7
|
+
keys.each do |key|
|
8
|
+
self[key.to_s] = delete(key)
|
9
|
+
end
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def stringify_keys
|
14
|
+
dup.stringify_keys!
|
15
|
+
end
|
16
|
+
|
17
|
+
def symbolize_keys!
|
18
|
+
keys.each do |key|
|
19
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def symbolize_keys
|
25
|
+
dup.symbolize_keys!
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
class MissingUrl
|
3
|
+
attr_accessor :short_url, :user_hash, :long_url, :error
|
4
|
+
|
5
|
+
def initialize(options={})
|
6
|
+
@error = options['error']
|
7
|
+
@long_url = options['long_url']
|
8
|
+
@short_url = options['short_url']
|
9
|
+
@user_hash = options['hash']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
# Day objects are created by the realtime_links method of a user
|
3
|
+
class RealtimeLink
|
4
|
+
attr_reader :clicks, :user_hash
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@clicks = options['clicks']
|
8
|
+
@user_hash = options['user_hash']
|
9
|
+
end
|
10
|
+
|
11
|
+
# A convenience method to create a Bitlyr::Url from the data
|
12
|
+
def create_url(client)
|
13
|
+
Bitlyr::Url.new(client, 'user_clicks' => clicks, 'user_hash' => user_hash)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
class Referrer
|
3
|
+
attr_reader :clicks, :referrer, :referrer_app, :url
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@url = options['url']
|
7
|
+
@clicks = options['clicks']
|
8
|
+
@referrer = options['referrer']
|
9
|
+
@referrer_app = options['referrer_app']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
class Response
|
3
|
+
REASONS = { "OK" => "OK",
|
4
|
+
"INVALID_URI" => "Invalid URI" }.freeze
|
5
|
+
|
6
|
+
def initialize(response)
|
7
|
+
@response = response
|
8
|
+
end
|
9
|
+
|
10
|
+
def success?
|
11
|
+
parsed['status_code'] == 200
|
12
|
+
end
|
13
|
+
|
14
|
+
def status
|
15
|
+
parsed['status_code']
|
16
|
+
end
|
17
|
+
|
18
|
+
def reason
|
19
|
+
status = parsed['status_txt']
|
20
|
+
REASONS[ status ] || status.split('_').map(&:capitalize) * ' '
|
21
|
+
end
|
22
|
+
|
23
|
+
def body
|
24
|
+
parsed['data']
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def parsed
|
29
|
+
case @response
|
30
|
+
when OAuth2::Response
|
31
|
+
@response.parsed
|
32
|
+
when HTTParty::Response
|
33
|
+
@response.parsed_response
|
34
|
+
else
|
35
|
+
raise "Unsupported Response type: #{@response.class}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
module Strategy
|
3
|
+
class AccessToken < Base
|
4
|
+
extend Forwardable
|
5
|
+
delegate [ :client, :[] ] => :access_token
|
6
|
+
|
7
|
+
def initialize(access_token)
|
8
|
+
@access_token = access_token
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
# Make a request from the access token
|
13
|
+
def run_request(verb, method, options={})
|
14
|
+
access_token.send(verb, method, :params => authorization_params.merge(options), :parse => :json)
|
15
|
+
end
|
16
|
+
|
17
|
+
def access_token
|
18
|
+
@access_token
|
19
|
+
end
|
20
|
+
|
21
|
+
def authorization_params
|
22
|
+
{ :access_token => access_token.token }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
module Strategy
|
3
|
+
class ApiKey < Base
|
4
|
+
include HTTParty
|
5
|
+
base_uri 'http://api.bit.ly/v3/'
|
6
|
+
query_string_normalizer proc { |query|
|
7
|
+
query.map do |key, value|
|
8
|
+
case value
|
9
|
+
when Array
|
10
|
+
value.map { |v| "#{key}=#{CGI.escape( v.to_s )}" }
|
11
|
+
else
|
12
|
+
"#{key}=#{CGI.escape( value.to_s )}"
|
13
|
+
end
|
14
|
+
end.compact.join('&')
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(login, api_key)
|
18
|
+
@login = login
|
19
|
+
@api_key = api_key
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
# Make a request using the login and apiKey
|
24
|
+
def run_request(verb, method, options={})
|
25
|
+
self.class.send(verb, "/#{method}", :query => authorization_params.merge(options))
|
26
|
+
end
|
27
|
+
|
28
|
+
def authorization_params
|
29
|
+
{ :login => @login, :apiKey => @api_key }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
module Strategy
|
3
|
+
class Base
|
4
|
+
def request(*args)
|
5
|
+
response = Bitlyr::Response.new(run_request(*args))
|
6
|
+
if response.success?
|
7
|
+
response.body
|
8
|
+
else
|
9
|
+
raise BitlyrError.new(response)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Validates a login and api key
|
14
|
+
def validate(x_login, x_api_key)
|
15
|
+
response = request(:get, :validate, :x_login => x_login, :x_apiKey => x_api_key)
|
16
|
+
return response['valid'] == 1
|
17
|
+
end
|
18
|
+
alias :valid? :validate
|
19
|
+
|
20
|
+
private
|
21
|
+
def run_request(*args)
|
22
|
+
raise "Define this in the subclass"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
module Strategy
|
3
|
+
class OAuth < Base
|
4
|
+
extend Forwardable
|
5
|
+
delegate [ :run_request ] => :access_token
|
6
|
+
|
7
|
+
BASE_URL = 'https://api-ssl.bit.ly/v3/'
|
8
|
+
|
9
|
+
def initialize(consumer_token, consumer_secret)
|
10
|
+
@consumer_token = consumer_token
|
11
|
+
@consumer_secret = consumer_secret
|
12
|
+
end
|
13
|
+
|
14
|
+
# Get the url to redirect a user to, pass the url you want the user
|
15
|
+
# to be redirected back to.
|
16
|
+
def authorize_url(redirect_url)
|
17
|
+
client.auth_code.authorize_url(:redirect_uri => redirect_url)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the access token. You must pass the exact same redirect_url passed
|
21
|
+
# to the authorize_url method
|
22
|
+
def get_access_token_from_code(code, redirect_url)
|
23
|
+
access_token = client.auth_code.get_token(code, :redirect_uri => redirect_url, :parse => :query)
|
24
|
+
Bitlyr::Strategy::AccessToken.new(access_token)
|
25
|
+
end
|
26
|
+
|
27
|
+
# If you already have a user token, this method gets the access token
|
28
|
+
def get_access_token_from_token(token, params={})
|
29
|
+
params.stringify_keys!
|
30
|
+
access_token = ::OAuth2::AccessToken.new(client, token, params)
|
31
|
+
Bitlyr::Strategy::AccessToken.new(access_token)
|
32
|
+
end
|
33
|
+
|
34
|
+
# If you already have a user token, this method sets the access token
|
35
|
+
def set_access_token_from_token!(token, params={})
|
36
|
+
@access_token ||= get_access_token_from_token(token, params)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
# Get the OAuth 2 client
|
41
|
+
def client
|
42
|
+
@client ||= ::OAuth2::Client.new(@consumer_token, @consumer_secret, :site => BASE_URL, :token_url => '/oauth/access_token')
|
43
|
+
end
|
44
|
+
|
45
|
+
def access_token
|
46
|
+
@access_token
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/bitlyr/url.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
|
3
|
+
# Url objects should only be created by the client object as it collects the correct information
|
4
|
+
# from the API.
|
5
|
+
class Url
|
6
|
+
attr_reader :short_url, :long_url, :user_hash, :global_hash, :referrers, :countries
|
7
|
+
|
8
|
+
# Initialize with a bitly client and optional hash to fill in the details for the url.
|
9
|
+
def initialize(client, options = {})
|
10
|
+
@client = client
|
11
|
+
@title = options['title'] || '' if options.key?('title')
|
12
|
+
@new_hash = (options['new_hash'] == 1)
|
13
|
+
@long_url = options['long_url']
|
14
|
+
@user_hash = options['hash'] || options['user_hash']
|
15
|
+
@short_url = options['url'] || options['short_url'] || "http://bit.ly/#{@user_hash}"
|
16
|
+
@created_by = options['created_by']
|
17
|
+
@global_hash = options['global_hash']
|
18
|
+
@user_clicks = options['user_clicks']
|
19
|
+
@global_clicks = options['global_clicks']
|
20
|
+
|
21
|
+
@referrers = options['referrers'].map{|referrer| Bitlyr::Referrer.new(referrer) } if options['referrers']
|
22
|
+
@countries = options['countries'].map{|country| Bitlyr::Country.new(country) } if options['countries']
|
23
|
+
|
24
|
+
if options['clicks'] && options['clicks'][0].is_a?(Hash)
|
25
|
+
@clicks_by_day = options['clicks'].map{|day| Bitlyr::Day.new(day)}
|
26
|
+
else
|
27
|
+
@clicks_by_minute = options['clicks']
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns true if the user hash was created first for this call
|
32
|
+
def new_hash?
|
33
|
+
@new_hash
|
34
|
+
end
|
35
|
+
|
36
|
+
# If the url already has click statistics, returns the user clicks.
|
37
|
+
# IF there are no click statistics or <tt>:force => true</tt> is passed,
|
38
|
+
# updates the stats and returns the user clicks
|
39
|
+
def user_clicks(options={})
|
40
|
+
update_clicks_data if @user_clicks.nil? || options[:force]
|
41
|
+
@user_clicks
|
42
|
+
end
|
43
|
+
|
44
|
+
# If the url already has click statistics, returns the global clicks.
|
45
|
+
# IF there are no click statistics or <tt>:force => true</tt> is passed,
|
46
|
+
# updates the stats and returns the global clicks
|
47
|
+
def global_clicks(options={})
|
48
|
+
update_clicks_data if @global_clicks.nil? || options[:force]
|
49
|
+
@global_clicks
|
50
|
+
end
|
51
|
+
|
52
|
+
# If the url already has the title, return it.
|
53
|
+
# IF there is no title or <tt>:force => true</tt> is passed,
|
54
|
+
# updates the info and returns the title
|
55
|
+
def title(options={})
|
56
|
+
update_info if @title.nil? || options[:force]
|
57
|
+
@title
|
58
|
+
end
|
59
|
+
|
60
|
+
# If the url already has the creator, return it.
|
61
|
+
# IF there is no creator or <tt>:force => true</tt> is passed,
|
62
|
+
# updates the info and returns the creator
|
63
|
+
def created_by(options={})
|
64
|
+
update_info if @created_by.nil? || options[:force]
|
65
|
+
@created_by
|
66
|
+
end
|
67
|
+
|
68
|
+
# If the url already has referrer data, return it.
|
69
|
+
# IF there is no referrer or <tt>:force => true</tt> is passed,
|
70
|
+
# updates the referrers and returns them
|
71
|
+
def referrers(options={})
|
72
|
+
update_referrers if @referrers.nil? || options[:force]
|
73
|
+
@referrers
|
74
|
+
end
|
75
|
+
|
76
|
+
# If the url already has country data, return it.
|
77
|
+
# IF there is no country or <tt>:force => true</tt> is passed,
|
78
|
+
# updates the countries and returns them
|
79
|
+
def countries(options={})
|
80
|
+
update_countries if @countries.nil? || options[:force]
|
81
|
+
@countries
|
82
|
+
end
|
83
|
+
|
84
|
+
def clicks_by_minute(options={})
|
85
|
+
if @clicks_by_minute.nil? || options[:force]
|
86
|
+
full_url = @client.clicks_by_minute(@user_hash || @short_url)
|
87
|
+
@clicks_by_minute = full_url.clicks_by_minute
|
88
|
+
end
|
89
|
+
@clicks_by_minute
|
90
|
+
end
|
91
|
+
|
92
|
+
def clicks_by_day(options={})
|
93
|
+
if @clicks_by_day.nil? || options[:force]
|
94
|
+
full_url = @client.clicks_by_day(@user_hash || @short_url)
|
95
|
+
@clicks_by_day = full_url.clicks_by_day
|
96
|
+
end
|
97
|
+
@clicks_by_day
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def update_clicks_data
|
103
|
+
full_url = @client.clicks(@user_hash || @short_url)
|
104
|
+
@global_clicks = full_url.global_clicks
|
105
|
+
@user_clicks = full_url.user_clicks
|
106
|
+
end
|
107
|
+
|
108
|
+
def update_info
|
109
|
+
full_url = @client.info(@user_hash || @short_url)
|
110
|
+
@created_by = full_url.created_by
|
111
|
+
@title = full_url.title
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_referrers
|
115
|
+
full_url = @client.referrers(@user_hash || @short_url)
|
116
|
+
@referrers = full_url.referrers
|
117
|
+
end
|
118
|
+
|
119
|
+
def update_countries
|
120
|
+
full_url = @client.countries(@user_hash || @short_url)
|
121
|
+
@countries = full_url.countries
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/bitlyr/user.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Bitlyr
|
2
|
+
|
3
|
+
# A user requires an oauth access token. The flow is as follows:
|
4
|
+
#
|
5
|
+
# o = Bitlyr::Strategy::OAuth.new(consumer_token, consumer_secret)
|
6
|
+
# o.authorize_url(redirect_url)
|
7
|
+
# #=> "https://bit.ly/oauth/authorize?client_id=#{consumer_token}&type=web_server&redirect_uri=http%3A%2F%2Ftest.local%2Fbitlyr%2Fauth"
|
8
|
+
# Redirect your users to this url, when they authorize your application
|
9
|
+
# they will be redirected to the url you provided with a code parameter.
|
10
|
+
# Use that parameter, and the exact same redirect url as follows:
|
11
|
+
#
|
12
|
+
# o.get_access_token_from_code(params[:code], redirect_url)
|
13
|
+
# #=> #<Bitlyr::AccessToken ...>
|
14
|
+
#
|
15
|
+
# Then use that access token to create your user object.
|
16
|
+
#
|
17
|
+
# u=Bitlyr::User.new(o.access_token)
|
18
|
+
class User
|
19
|
+
|
20
|
+
def initialize(access_token)
|
21
|
+
@access_token = access_token
|
22
|
+
end
|
23
|
+
|
24
|
+
# OAuth 2 endpoint that provides a list of top referrers (up to 500 per
|
25
|
+
# day) for a given user’s bit.ly links, and the number of clicks per referrer.
|
26
|
+
#
|
27
|
+
# http://code.google.com/p/bitly-api/wiki/ApiDocumentation#/v3/user/referrers
|
28
|
+
def referrers(options={})
|
29
|
+
if @referrers.nil? || options.delete(:force)
|
30
|
+
@referrers = get_method(:referrers, Bitlyr::Referrer, options)
|
31
|
+
end
|
32
|
+
@referrers
|
33
|
+
end
|
34
|
+
|
35
|
+
# OAuth 2 endpoint that provides a list of countries from which clicks
|
36
|
+
# on a given user’s bit.ly links are originating, and the number of clicks per country.
|
37
|
+
#
|
38
|
+
# http://code.google.com/p/bitly-api/wiki/ApiDocumentation#/v3/user/countries
|
39
|
+
def countries(options={})
|
40
|
+
if @countries.nil? || options.delete(:force)
|
41
|
+
@countries = get_method(:countries, Bitlyr::Country, options)
|
42
|
+
end
|
43
|
+
@countries
|
44
|
+
end
|
45
|
+
|
46
|
+
# OAuth 2 endpoint that provides a given user’s 100 most popular links
|
47
|
+
# based on click traffic in the past hour, and the number of clicks per link.
|
48
|
+
#
|
49
|
+
# http://code.google.com/p/bitly-api/wiki/ApiDocumentation#/v3/user/realtime_links
|
50
|
+
def realtime_links(options={})
|
51
|
+
if @realtime_links.nil? || options.delete(:force)
|
52
|
+
result = get(:realtime_links, options)
|
53
|
+
@realtime_links = result['realtime_links'].map { |rs| Bitlyr::RealtimeLink.new(rs) }
|
54
|
+
end
|
55
|
+
@realtime_links
|
56
|
+
end
|
57
|
+
|
58
|
+
# OAuth 2 endpoint that provides the total clicks per day on a user’s bit.ly links.
|
59
|
+
#
|
60
|
+
# http://code.google.com/p/bitly-api/wiki/ApiDocumentation#/v3/user/clicks
|
61
|
+
def clicks(options={})
|
62
|
+
get_clicks(options)
|
63
|
+
@clicks
|
64
|
+
end
|
65
|
+
|
66
|
+
# Displays the total clicks returned from the clicks method.
|
67
|
+
def total_clicks(options={})
|
68
|
+
get_clicks(options)
|
69
|
+
@total_clicks
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns a Bitly Client using the credentials of the user.
|
73
|
+
def client
|
74
|
+
@client ||= Bitlyr::Client.new(@access_token)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def get_method(method, klass, options)
|
80
|
+
result = get(method, options)
|
81
|
+
result[method.to_s].map do |rs|
|
82
|
+
rs.map do |obj|
|
83
|
+
klass.new(obj)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_clicks(options={})
|
89
|
+
if @clicks.nil? || options.delete(:force)
|
90
|
+
result = get(:clicks, options)
|
91
|
+
@clicks = result['clicks'].map { |rs| Bitlyr::Day.new(rs) }
|
92
|
+
@total_clicks = result['total_clicks']
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def get(method, options)
|
97
|
+
@access_token.request(:get, "user/#{method}", options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/bitlyr.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'httparty'
|
4
|
+
require 'oauth2'
|
5
|
+
require 'cgi'
|
6
|
+
require 'forwardable'
|
7
|
+
|
8
|
+
module Bitlyr
|
9
|
+
def self.new(params = {})
|
10
|
+
params.symbolize_keys!
|
11
|
+
if params.key?(:client_id) && params.key?(:client_secret)
|
12
|
+
strategy = Bitlyr::Strategy::OAuth.new(params[:client_id], params[:client_secret])
|
13
|
+
strategy.get_access_token_from_token(params[:token]) if params[:token]
|
14
|
+
Bitlyr::Client.new strategy
|
15
|
+
elsif params.key?(:login) && params.key?(:api_key)
|
16
|
+
Bitlyr::Client.new Bitlyr::Strategy::ApiKey.new(params[:login], params[:api_key])
|
17
|
+
else
|
18
|
+
raise "requires a login and apiKey or client id and client secret"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'bitlyr/client'
|
24
|
+
require 'bitlyr/country'
|
25
|
+
require 'bitlyr/day'
|
26
|
+
require 'bitlyr/error'
|
27
|
+
require 'bitlyr/missing_url'
|
28
|
+
require 'bitlyr/realtime_link'
|
29
|
+
require 'bitlyr/referrer'
|
30
|
+
require 'bitlyr/response'
|
31
|
+
require 'bitlyr/url'
|
32
|
+
require 'bitlyr/user'
|
33
|
+
require 'bitlyr/strategy/base'
|
34
|
+
require 'bitlyr/strategy/access_token'
|
35
|
+
require 'bitlyr/strategy/api_key'
|
36
|
+
require 'bitlyr/strategy/oauth'
|
37
|
+
|
38
|
+
require 'bitlyr/lib/core_ext/hash'
|
39
|
+
require 'bitlyr/lib/core_ext/string'
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "status_code": 200, "status_txt": "OK", "data": { "expand": [ { "hash": "9uX1TE", "long_url": "http:\/\/betaworks.com\/", "user_hash": "9uX1TE", "global_hash": "18H1ET" } ] } }
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks": [{"user_clicks": 0, "global_hash": "18H1ET", "hash": "9uX1TE", "user_hash": "9uX1TE", "global_clicks": 81}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks": [{"user_clicks": 1, "global_hash": "18H1ET", "hash": "9uX1TE", "user_hash": "9uX1TE", "global_clicks": 82}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"info": [{"global_hash": "18H1ET", "hash": "9uX1TE", "user_hash": "9uX1TE", "created_by": "philnash", "title": "A title"}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"info": [{"global_hash": "18H1ET", "hash": "9uX1TE", "user_hash": "9uX1TE", "created_by": "philnash2", "title": "A New Title"}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"data":{"authenticate":{"successful": false}},"status_code": 200,"status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"data":{"authenticate":{"api_key": "R_0da49e0a9118ff35f52f629d2d71bf07","successful": true,"username": "bitlyapidemo"}},"status_code": 200,"status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "status_code": 200, "status_txt": "OK", "data": { "long_url": "http:\/\/betaworks.com\/", "url": "http:\/\/bit.ly\/9uX1TE", "hash": "9uX1TE", "global_hash": "18H1ET", "new_hash": 1 } }
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "status_code": 200, "status_txt": "OK", "data": { "long_url": "http:\/\/betaworks.com\/", "url": "http:\/\/bit.ly\/9uX1TE", "hash": "9uX1TE", "global_hash": "18H1ET", "new_hash": 0 } }
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "status_code": 200, "status_txt": "OK", "data": { "long_url": "http:\/\/betaworks.com\/", "url": "http:\/\/j.mp\/9uX1TE", "hash": "9uX1TE", "global_hash": "18H1ET", "new_hash": 0 } }
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "status_code": 200, "status_txt": "OK", "data": { "long_url": "http:\/\/betaworks.com\/", "url": "http:\/\/bit.ly\/cWJlxM", "hash": "cWJlxM", "global_hash": "18H1ET", "new_hash": 1 } }
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "status_code": 200, "status_txt": "OK", "data": { "expand": [ { "short_url": "http:\/\/bit.ly\/9uX1TE", "long_url": "http:\/\/betaworks.com\/", "user_hash": "9uX1TE", "global_hash": "18H1ET" } ] } }
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"domain": "nyti.ms", "bitly_pro_domain": true}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks_by_day": [{"global_hash": "9DguyN", "hash": "9DguyN", "user_hash": "9DguyN", "clicks": [{"clicks": 1, "day_start": 1290488400}, {"clicks": 0, "day_start": 1290402000}, {"clicks": 0, "day_start": 1290315600}, {"clicks": 1, "day_start": 1290229200}, {"clicks": 0, "day_start": 1290142800}, {"clicks": 0, "day_start": 1290056400}, {"clicks": 0, "day_start": 1289970000}]}, {"global_hash": "dpC5ns", "hash": "dvxi6W", "user_hash": "dvxi6W", "clicks": [{"clicks": 1, "day_start": 1290488400}, {"clicks": 0, "day_start": 1290402000}, {"clicks": 0, "day_start": 1290315600}, {"clicks": 0, "day_start": 1290229200}, {"clicks": 0, "day_start": 1290142800}, {"clicks": 0, "day_start": 1290056400}, {"clicks": 0, "day_start": 1289970000}]}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks_by_day": [{"global_hash": "9DguyN", "hash": "9DguyN", "user_hash": "9DguyN", "clicks": [{"clicks": 1, "day_start": 1290488400}, {"clicks": 0, "day_start": 1290402000}, {"clicks": 0, "day_start": 1290315600}, {"clicks": 1, "day_start": 1290229200}, {"clicks": 0, "day_start": 1290142800}, {"clicks": 0, "day_start": 1290056400}, {"clicks": 0, "day_start": 1289970000}]}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks_by_day": [{"global_hash": "9DguyN", "hash": "9DguyN", "user_hash": "9DguyN", "clicks": [{"clicks": 2, "day_start": 1290488400}, {"clicks": 0, "day_start": 1290402000}, {"clicks": 0, "day_start": 1290315600}, {"clicks": 1, "day_start": 1290229200}, {"clicks": 0, "day_start": 1290142800}, {"clicks": 0, "day_start": 1290056400}, {"clicks": 0, "day_start": 1289970000}]}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks_by_minute": [{"short_url": "http://j.mp/9DguyN", "global_hash": "9DguyN", "user_hash": "9DguyN", "clicks": [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks_by_minute": [{"short_url": "http://j.mp/9DguyN", "global_hash": "9DguyN", "user_hash": "9DguyN", "clicks": [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks_by_minute": [{"global_hash": "9DguyN", "hash": "9DguyN", "user_hash": "9DguyN", "clicks": [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"clicks_by_minute": [{"global_hash": "9DguyN", "hash": "9DguyN", "user_hash": "9DguyN", "clicks": [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}, {"global_hash": "dpC5ns", "hash": "dvxi6W", "user_hash": "dvxi6W", "clicks": [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"global_hash": "djZ9g4", "hash": "djZ9g4", "user_hash": "djZ9g4", "countries": [{"country": "US", "clicks": 58}, {"country": "GB", "clicks": 14}, {"country": null, "clicks": 14}, {"country": "BR", "clicks": 6}, {"country": "CA", "clicks": 6}, {"country": "FR", "clicks": 5}, {"country": "AU", "clicks": 4}, {"country": "IN", "clicks": 4}, {"country": "DE", "clicks": 3}, {"country": "TW", "clicks": 3}, {"country": "IT", "clicks": 2}, {"country": "NO", "clicks": 2}, {"country": "PL", "clicks": 2}, {"country": "BE", "clicks": 1}, {"country": "BG", "clicks": 1}, {"country": "CI", "clicks": 1}, {"country": "CZ", "clicks": 1}, {"country": "ES", "clicks": 1}, {"country": "GR", "clicks": 1}, {"country": "HK", "clicks": 1}, {"country": "KR", "clicks": 1}, {"country": "MY", "clicks": 1}, {"country": "PH", "clicks": 1}, {"country": "PT", "clicks": 1}, {"country": "SE", "clicks": 1}, {"country": "VE", "clicks": 1}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"global_hash": "djZ9g4", "hash": "djZ9g4", "user_hash": "djZ9g4", "countries": [{"country": "US", "clicks": 59}, {"country": "GB", "clicks": 14}, {"country": null, "clicks": 14}, {"country": "BR", "clicks": 6}, {"country": "CA", "clicks": 6}, {"country": "FR", "clicks": 5}, {"country": "AU", "clicks": 4}, {"country": "IN", "clicks": 4}, {"country": "DE", "clicks": 3}, {"country": "TW", "clicks": 3}, {"country": "IT", "clicks": 2}, {"country": "NO", "clicks": 2}, {"country": "PL", "clicks": 2}, {"country": "BE", "clicks": 1}, {"country": "BG", "clicks": 1}, {"country": "CI", "clicks": 1}, {"country": "CZ", "clicks": 1}, {"country": "ES", "clicks": 1}, {"country": "GR", "clicks": 1}, {"country": "HK", "clicks": 1}, {"country": "KR", "clicks": 1}, {"country": "MY", "clicks": 1}, {"country": "PH", "clicks": 1}, {"country": "PT", "clicks": 1}, {"country": "SE", "clicks": 1}, {"country": "VE", "clicks": 1}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 200, "data": {"short_url": "http://bit.ly/djZ9g4", "global_hash": "djZ9g4", "user_hash": "djZ9g4", "countries": [{"country": "US", "clicks": 58}, {"country": "GB", "clicks": 14}, {"country": null, "clicks": 14}, {"country": "BR", "clicks": 6}, {"country": "CA", "clicks": 6}, {"country": "FR", "clicks": 5}, {"country": "AU", "clicks": 4}, {"country": "IN", "clicks": 4}, {"country": "DE", "clicks": 3}, {"country": "TW", "clicks": 3}, {"country": "IT", "clicks": 2}, {"country": "NO", "clicks": 2}, {"country": "PL", "clicks": 2}, {"country": "BE", "clicks": 1}, {"country": "BG", "clicks": 1}, {"country": "CI", "clicks": 1}, {"country": "CZ", "clicks": 1}, {"country": "ES", "clicks": 1}, {"country": "GR", "clicks": 1}, {"country": "HK", "clicks": 1}, {"country": "KR", "clicks": 1}, {"country": "MY", "clicks": 1}, {"country": "PH", "clicks": 1}, {"country": "PT", "clicks": 1}, {"country": "SE", "clicks": 1}, {"country": "VE", "clicks": 1}]}, "status_txt": "OK"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "status_code": 400, "status_txt": "Bad Request", "data": { } }
|
@@ -0,0 +1 @@
|
|
1
|
+
{"status_code": 500, "data": null, "status_txt": "INVALID_DOMAIN"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "data": [ ], "status_code": 500, "status_txt": "INVALID_LOGIN" }
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "data": [ ], "status_code": 500, "status_txt": "INVALID_ARG_DOMAIN" }
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "status_code": 200, "status_txt": "OK", "data": { "valid": 0 } }
|
@@ -0,0 +1 @@
|
|
1
|
+
{ "data": [ ], "status_code": 500, "status_txt": "INVALID_X_APIKEY" }
|