bitly-oauth 0.1.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.
Files changed (88) hide show
  1. data/History.txt +154 -0
  2. data/LICENSE +20 -0
  3. data/README.md +87 -0
  4. data/README.rdoc +35 -0
  5. data/Rakefile +42 -0
  6. data/VERSION +1 -0
  7. data/lib/bitly_oauth.rb +28 -0
  8. data/lib/bitly_oauth/access_token.rb +37 -0
  9. data/lib/bitly_oauth/client.rb +149 -0
  10. data/lib/bitly_oauth/country.rb +10 -0
  11. data/lib/bitly_oauth/day.rb +12 -0
  12. data/lib/bitly_oauth/error.rb +15 -0
  13. data/lib/bitly_oauth/lib/core_ext/hash.rb +27 -0
  14. data/lib/bitly_oauth/lib/core_ext/string.rb +5 -0
  15. data/lib/bitly_oauth/missing_url.rb +12 -0
  16. data/lib/bitly_oauth/realtime_link.rb +16 -0
  17. data/lib/bitly_oauth/referrer.rb +12 -0
  18. data/lib/bitly_oauth/referring_domain.rb +11 -0
  19. data/lib/bitly_oauth/response.rb +39 -0
  20. data/lib/bitly_oauth/url.rb +118 -0
  21. data/lib/bitly_oauth/user.rb +104 -0
  22. data/test/fixtures/9uX1TE.json +1 -0
  23. data/test/fixtures/9uX1TEclicks.json +1 -0
  24. data/test/fixtures/9uX1TEclicks2.json +1 -0
  25. data/test/fixtures/9uX1TEinfo.json +1 -0
  26. data/test/fixtures/9uX1TEinfo2.json +1 -0
  27. data/test/fixtures/auth_fail.json +1 -0
  28. data/test/fixtures/auth_success.json +1 -0
  29. data/test/fixtures/betaworks.json +1 -0
  30. data/test/fixtures/betaworks2.json +1 -0
  31. data/test/fixtures/betaworks_jmp.json +1 -0
  32. data/test/fixtures/betaworks_other_user.json +1 -0
  33. data/test/fixtures/bitly9uX1TE.json +1 -0
  34. data/test/fixtures/bitly_pro_domain.json +1 -0
  35. data/test/fixtures/clicks_by_day.json +1 -0
  36. data/test/fixtures/clicks_by_day1.json +1 -0
  37. data/test/fixtures/clicks_by_day2.json +1 -0
  38. data/test/fixtures/clicks_by_minute1_url.json +1 -0
  39. data/test/fixtures/clicks_by_minute2_url.json +1 -0
  40. data/test/fixtures/clicks_by_minute_hash.json +1 -0
  41. data/test/fixtures/clicks_by_minute_hashes.json +1 -0
  42. data/test/fixtures/country_hash.json +1 -0
  43. data/test/fixtures/country_hash2.json +1 -0
  44. data/test/fixtures/country_url.json +1 -0
  45. data/test/fixtures/failure.json +1 -0
  46. data/test/fixtures/invalid_bitly_pro_domain.json +1 -0
  47. data/test/fixtures/invalid_credentials.json +1 -0
  48. data/test/fixtures/invalid_domain.json +1 -0
  49. data/test/fixtures/invalid_user.json +1 -0
  50. data/test/fixtures/invalid_x_api_key.json +1 -0
  51. data/test/fixtures/lookup_multiple_url.json +1 -0
  52. data/test/fixtures/lookup_not_real_url.json +1 -0
  53. data/test/fixtures/lookup_single_url.json +1 -0
  54. data/test/fixtures/missing_hash.json +1 -0
  55. data/test/fixtures/multiple_info.json +1 -0
  56. data/test/fixtures/multiple_url_click.json +1 -0
  57. data/test/fixtures/multiple_urls.json +1 -0
  58. data/test/fixtures/not_bitly_pro_domain.json +1 -0
  59. data/test/fixtures/not_found_info.json +1 -0
  60. data/test/fixtures/referrer_hash.json +1 -0
  61. data/test/fixtures/referrer_hash2.json +1 -0
  62. data/test/fixtures/referrer_url.json +1 -0
  63. data/test/fixtures/success.json +1 -0
  64. data/test/fixtures/url_info.json +1 -0
  65. data/test/fixtures/user_clicks.json +32 -0
  66. data/test/fixtures/user_countries.json +60 -0
  67. data/test/fixtures/user_realtime_links.json +15 -0
  68. data/test/fixtures/user_referrers.json +1 -0
  69. data/test/fixtures/valid_user.json +1 -0
  70. data/test/integration/strategy/old_test_api_key.rb +20 -0
  71. data/test/integration/test_client.rb +709 -0
  72. data/test/integration/test_user.rb +97 -0
  73. data/test/test_helper.rb +54 -0
  74. data/test/unit/core_ext/test_hash.rb +69 -0
  75. data/test/unit/core_ext/test_string.rb +14 -0
  76. data/test/unit/test_bitly_oauth.rb +19 -0
  77. data/test/unit/test_client.rb +87 -0
  78. data/test/unit/test_country.rb +19 -0
  79. data/test/unit/test_day.rb +22 -0
  80. data/test/unit/test_error.rb +11 -0
  81. data/test/unit/test_missing.rb +34 -0
  82. data/test/unit/test_realtime_link.rb +30 -0
  83. data/test/unit/test_referrer.rb +19 -0
  84. data/test/unit/test_referring_domain.rb +21 -0
  85. data/test/unit/test_response.rb +86 -0
  86. data/test/unit/test_url.rb +156 -0
  87. data/test/unit/test_user.rb +17 -0
  88. metadata +215 -0
@@ -0,0 +1,10 @@
1
+ module BitlyOAuth
2
+ class Country
3
+ attr_reader :clicks, :country
4
+
5
+ def initialize(options = {})
6
+ @clicks = options['clicks']
7
+ @country = options['country']
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module BitlyOAuth
2
+
3
+ # Day objects are created by the clicks_by_day method of a url
4
+ class Day
5
+ attr_reader :clicks, :day_start
6
+
7
+ def initialize(options = {})
8
+ @clicks = options['clicks']
9
+ @day_start = Time.at(options['day_start']) if options['day_start']
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module BitlyOAuth
2
+ class Error < StandardError
3
+ attr_reader :code, :response
4
+
5
+ alias :msg :message
6
+
7
+ def initialize(response)
8
+ @response = response
9
+ @message = response.reason
10
+ @code = response.status
11
+
12
+ super("#{@message} - '#{@code}'")
13
+ end
14
+ end
15
+ end
@@ -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,5 @@
1
+ class String
2
+ def to_a
3
+ [ self ]
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module BitlyOAuth
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 BitlyOAuth
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 BitlyOAuth::Url from the data
12
+ def create_url(client)
13
+ BitlyOAuth::Url.new(client, 'user_clicks' => clicks, 'user_hash' => user_hash)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module BitlyOAuth
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,11 @@
1
+ module BitlyOAuth
2
+ class ReferringDomain
3
+ attr_reader :clicks, :domain
4
+
5
+ def initialize(options={})
6
+ @clicks = options['clicks']
7
+ @domain = options['domain']
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,39 @@
1
+ module BitlyOAuth
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,118 @@
1
+ module BitlyOAuth
2
+
3
+ class Url
4
+ attr_reader :short_url, :long_url, :user_hash, :global_hash, :referrers, :countries
5
+
6
+ # Initialize with a bitly client and optional hash to fill in the details for the url.
7
+ def initialize(client, options = {})
8
+ @client = client
9
+ @title = options['title'] || '' if options.key?('title')
10
+ @new_hash = (options['new_hash'] == 1)
11
+ @long_url = options['long_url']
12
+ @user_hash = options['hash'] || options['user_hash']
13
+ @short_url = options['url'] || options['short_url'] || "http://bit.ly/#{@user_hash}"
14
+ @created_by = options['created_by']
15
+ @global_hash = options['global_hash']
16
+ @user_clicks = options['user_clicks']
17
+ @global_clicks = options['global_clicks']
18
+
19
+ @referrers = options['referrers'].map{|referrer| BitlyOAuth::Referrer.new(referrer) } if options['referrers']
20
+ @countries = options['countries'].map{|country| BitlyOAuth::Country.new(country) } if options['countries']
21
+
22
+ if options['clicks'] && options['clicks'][0].is_a?(Hash)
23
+ @clicks_by_day = options['clicks'].map{|day| BitlyOAuth::Day.new(day)}
24
+ else
25
+ @clicks_by_minute = options['clicks']
26
+ end
27
+ end
28
+
29
+ # Returns true if the user hash was created first for this call
30
+ def new_hash?
31
+ @new_hash
32
+ end
33
+
34
+ # If the url already has click statistics, returns the user clicks.
35
+ # IF there are no click statistics or <tt>:force => true</tt> is passed,
36
+ # updates the stats and returns the user clicks
37
+ def user_clicks(options={})
38
+ update_clicks_data if @user_clicks.nil? || options[:force]
39
+ @user_clicks
40
+ end
41
+
42
+ # If the url already has click statistics, returns the global clicks.
43
+ # IF there are no click statistics or <tt>:force => true</tt> is passed,
44
+ # updates the stats and returns the global clicks
45
+ def global_clicks(options={})
46
+ update_clicks_data if @global_clicks.nil? || options[:force]
47
+ @global_clicks
48
+ end
49
+
50
+ # If the url already has the title, return it.
51
+ # IF there is no title or <tt>:force => true</tt> is passed,
52
+ # updates the info and returns the title
53
+ def title(options={})
54
+ update_info if @title.nil? || options[:force]
55
+ @title
56
+ end
57
+
58
+ # If the url already has the creator, return it.
59
+ # IF there is no creator or <tt>:force => true</tt> is passed,
60
+ # updates the info and returns the creator
61
+ def created_by(options={})
62
+ update_info if @created_by.nil? || options[:force]
63
+ @created_by
64
+ end
65
+
66
+ # If the url already has referrer data, return it.
67
+ # IF there is no referrer or <tt>:force => true</tt> is passed,
68
+ # updates the referrers and returns them
69
+ def referrers(options={})
70
+ if @referrers.nil? || options[:force]
71
+ full_url = @client.referrers(@user_hash || @short_url)
72
+ @referrers = full_url.referrers
73
+ end
74
+ @referrers
75
+ end
76
+
77
+ # If the url already has country data, return it.
78
+ # IF there is no country or <tt>:force => true</tt> is passed,
79
+ # updates the countries and returns them
80
+ def countries(options={})
81
+ if @countries.nil? || options[:force]
82
+ full_url = @client.countries(@user_hash || @short_url)
83
+ @countries = full_url.countries
84
+ end
85
+ @countries
86
+ end
87
+
88
+ def clicks_by_minute(options={})
89
+ if @clicks_by_minute.nil? || options[:force]
90
+ full_url = @client.clicks_by_minute(@user_hash || @short_url)
91
+ @clicks_by_minute = full_url.clicks_by_minute
92
+ end
93
+ @clicks_by_minute
94
+ end
95
+
96
+ def clicks_by_day(options={})
97
+ if @clicks_by_day.nil? || options[:force]
98
+ full_url = @client.clicks_by_day(@user_hash || @short_url)
99
+ @clicks_by_day = full_url.clicks_by_day
100
+ end
101
+ @clicks_by_day
102
+ end
103
+
104
+ private
105
+
106
+ def update_clicks_data
107
+ full_url = @client.clicks(@user_hash || @short_url)
108
+ @global_clicks = full_url.global_clicks
109
+ @user_clicks = full_url.user_clicks
110
+ end
111
+
112
+ def update_info
113
+ full_url = @client.info(@user_hash || @short_url)
114
+ @created_by = full_url.created_by
115
+ @title = full_url.title
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,104 @@
1
+ module BitlyOAuth
2
+
3
+ # A user requires a BitlyOAuth::AccessToken. The flow is as follows:
4
+ #
5
+ # client = BitlyOAuth::Client.new(consumer_token, consumer_secret)
6
+ # client.authorize_url(redirect_url)
7
+ # #=> "https://bitly.com/oauth/authorize?client_id=id&type=code&redirect_uri=http%3A%2F%2Ftest.local%2Fbitly-oauth%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
+ # token = o.get_access_token_from_code(params[:code], redirect_url)
13
+ # #=> #<BitlyOAuth::AccessToken ...>
14
+ #
15
+ # Then use that access token to create your user object.
16
+ #
17
+ # user = BitlyOAuth::User.new(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, BitlyOAuth::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, BitlyOAuth::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| BitlyOAuth::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 ||= begin
75
+ client = BitlyOAuth::Client.new(@access_token.client.id, @access_token.client.secret)
76
+ client.set_access_token_from_token(@access_token.token)
77
+ client
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def get_method(method, klass, options)
84
+ result = get(method, options)
85
+ result[method.to_s].map do |rs|
86
+ rs.map do |obj|
87
+ klass.new(obj)
88
+ end
89
+ end
90
+ end
91
+
92
+ def get_clicks(options={})
93
+ if @clicks.nil? || options.delete(:force)
94
+ result = get(:clicks, options)
95
+ @clicks = result['clicks'].map { |rs| BitlyOAuth::Day.new(rs) }
96
+ @total_clicks = result['total_clicks']
97
+ end
98
+ end
99
+
100
+ def get(method, options)
101
+ @access_token.get("user/#{method}", options)
102
+ end
103
+ end
104
+ end
@@ -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"}