net 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +11 -0
  4. data/{LICENSE.txt → MIT-LICENSE} +2 -4
  5. data/README.md +108 -17
  6. data/lib/net.rb +0 -1
  7. data/lib/net/twitter.rb +11 -0
  8. data/lib/net/twitter/api/configuration.rb +16 -0
  9. data/lib/net/twitter/api/request.rb +121 -0
  10. data/lib/net/twitter/config.rb +15 -0
  11. data/lib/net/twitter/errors.rb +4 -0
  12. data/lib/net/twitter/errors/response_error.rb +14 -0
  13. data/lib/net/twitter/errors/suspended_user.rb +11 -0
  14. data/lib/net/twitter/errors/too_many_users.rb +11 -0
  15. data/lib/net/twitter/errors/unknown_user.rb +11 -0
  16. data/lib/net/twitter/models.rb +1 -0
  17. data/lib/net/twitter/models/user.rb +80 -0
  18. data/lib/net/version.rb +1 -1
  19. data/net.gemspec +14 -7
  20. data/spec/net/twitter/models/user_spec.rb +98 -0
  21. data/spec/spec_helper.rb +21 -0
  22. data/spec/support/cassettes/Net_Twitter_Models_User/_find_by/given_a_suspended_screen_name/.yml +69 -0
  23. data/spec/support/cassettes/Net_Twitter_Models_User/_find_by/given_an_existing_case-insensitive_screen_name/returns_an_object_representing_that_user.yml +136 -0
  24. data/spec/support/cassettes/Net_Twitter_Models_User/_find_by/given_an_unknown_screen_name/.yml +69 -0
  25. data/spec/support/cassettes/Net_Twitter_Models_User/_find_by_/given_a_suspended_screen_name/.yml +69 -0
  26. data/spec/support/cassettes/Net_Twitter_Models_User/_find_by_/given_an_existing_case-insensitive_screen_name/returns_an_object_representing_that_user.yml +80 -0
  27. data/spec/support/cassettes/Net_Twitter_Models_User/_find_by_/given_an_unknown_screen_name/.yml +69 -0
  28. data/spec/support/cassettes/Net_Twitter_Models_User/_where/called_more_times_than_Twitter_s_rate_limit/waits_for_the_rate_limit_to_expire_then_continues.yml +4802 -0
  29. data/spec/support/cassettes/Net_Twitter_Models_User/_where/given_more_screen_names_than_allowed_by_Twitter/.yml +69 -0
  30. data/spec/support/cassettes/Net_Twitter_Models_User/_where/given_multiple_existing_case-insensitive_screen_names/returns_an_array_of_objects_representing_those_users.yml +87 -0
  31. data/spec/support/cassettes/Net_Twitter_Models_User/_where/given_multiple_unknown_or_suspended_screen_names/returns_an_empty_array.yml +69 -0
  32. data/spec/support/vcr.rb +22 -0
  33. metadata +138 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc39cfbf35cc2f608255699239173161bc22b946
4
- data.tar.gz: d3b90279a21b9ec08ed7bc3940d688d4589b3d62
3
+ metadata.gz: e8d47667af5750fa95def0080edb95f64ebe5ec5
4
+ data.tar.gz: d2339bd6f4b728e27de7c9d3249877c10c000474
5
5
  SHA512:
6
- metadata.gz: 0f06e52f69536e19e585dd252564328623094a755736b6a15a47221fe839cf0aba3a2218b893aae1afa4d6939f0ed078ce3828ce20b75602d2b33f959034639a
7
- data.tar.gz: 3da6df355bb145b2da134d37881d69d23ce81168c172a56af6be02c2fbfbc68c5455556dffeb6102c76d5054a015d0156f735f86c9c480d1af5053e6166092eb
6
+ metadata.gz: f076d2cf3063b019795f320cbff740ed3fbf07d01e9d4b1b7a4295f5c16bbf25cccb3982a178a6f9dfba6cb0e2c27c6f6ce610f4b2609b9c22b83f39e87f3343
7
+ data.tar.gz: c5391de7b4762d3d3e3b7073cd27bae4aac635d21fedc6b696bef465205340bc9c365b07ea5d5180ea158766a1cb59309e7de5d6e2dddf572ee144531639f741
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --fail-fast
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ For more information about changelogs, check
6
+ [Keep a Changelog](http://keepachangelog.com) and
7
+ [Vandamme](http://tech-angels.github.io/vandamme).
8
+
9
+ ## 0.1.0 - 2014-10-13
10
+
11
+ * Initial release with `Twitter::User` supporting `find_by`, `find_by!` and `where`.
@@ -1,6 +1,4 @@
1
- Copyright (c) 2014 claudiob
2
-
3
- MIT License
1
+ Copyright 2014 Fullscreen, Inc.
4
2
 
5
3
  Permission is hereby granted, free of charge, to any person obtaining
6
4
  a copy of this software and associated documentation files (the
@@ -19,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
17
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,29 +1,120 @@
1
- # Net
1
+ Net - a Ruby client for social networks API
2
+ ===========================================
2
3
 
3
- TODO: Write a gem description
4
+ Net helps you write apps that need to interact with Twitter.
4
5
 
5
- ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
7
+ After [configuring your Twitter app](#configuring-your-app), you can run commands like:
8
8
 
9
- gem 'net'
9
+ ```ruby
10
+ user = Net::Twitter::User.find_by screen_name: 'fullscreen'
11
+ user.screen_name #=> "Fullscreen"
12
+ user.followers_count #=> 48_200
13
+ ```
10
14
 
11
- And then execute:
15
+ How to install
16
+ ==============
12
17
 
13
- $ bundle
18
+ To install on your system, run
14
19
 
15
- Or install it yourself as:
20
+ gem install net
16
21
 
17
- $ gem install net
22
+ To use inside a bundled Ruby project, add this line to the Gemfile:
18
23
 
19
- ## Usage
24
+ gem 'net', '~> 0.1.0'
20
25
 
21
- TODO: Write usage instructions here
26
+ Since the gem follows [Semantic Versioning](http://semver.org),
27
+ indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
28
+ guarantees that your project won’t occur in any error when you `bundle update`
29
+ and a new version of Net is released.
22
30
 
23
- ## Contributing
31
+ Available resources
32
+ ===================
24
33
 
25
- 1. Fork it ( https://github.com/[my-github-username]/net/fork )
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create a new Pull Request
34
+ Net::Twitter::User
35
+ -----------
36
+
37
+ Use [Net::Account]() to:
38
+
39
+ * retrieve a Twitter user by screen name
40
+ * retrieve a list of Twitter users by screen names
41
+ * access the number of followers of a Twitter user
42
+
43
+ ```ruby
44
+ user = Net::Twitter::User.find_by screen_name: 'fullscreen'
45
+ user.followers_count #=> 48_200
46
+
47
+ users = Net::Twitter::User.where screen_name: ['fullscreen', 'brohemian6']
48
+ users.map(&:followers_count).sort #=> [12, 48_200]
49
+ ```
50
+
51
+ *The methods above require a configured Twitter app (see below).*
52
+
53
+ Configuring your app
54
+ ====================
55
+
56
+ In order to use Net you must create an app in the [Twitter Application Manager](https://apps.twitter.com/app/new).
57
+
58
+ Once the app is created, copy the API key and secret and add them to your
59
+ code with the following snippet of code (replacing with your own key and secret)
60
+ :
61
+
62
+ ```ruby
63
+ Net::Twitter.configure do |config|
64
+ config.apps.push key: 'abcd', secret: 'efgh'
65
+ end
66
+ ```
67
+
68
+ Configuring with environment variables
69
+ --------------------------------------
70
+
71
+ As an alternative to the approach above, you can configure your app with
72
+ variables. Setting the following environment variables:
73
+
74
+ ```bash
75
+ export TWITTER_API_KEY='abcd'
76
+ export TWITTER_API_SECRET='efgh'
77
+ ```
78
+
79
+ is equivalent to configuring your app with the initializer:
80
+
81
+ ```ruby
82
+ Net::Twitter.configure do |config|
83
+ config.apps.push key: 'abcd', secret: 'efgh'
84
+ end
85
+ ```
86
+
87
+ so use the approach that you prefer.
88
+ If a variable is set in both places, then `Net::Twitter.configure` takes precedence.
89
+
90
+ How to test
91
+ ===========
92
+
93
+ To run tests, type:
94
+
95
+ ```bash
96
+ rspec
97
+ ```
98
+
99
+ Net uses [VCR](https://github.com/vcr/vcr) so by default tests do not run
100
+ HTTP requests.
101
+
102
+ If you need to run tests against the live Twitter API,
103
+ [configure your Twitter app](#configuring-your-app) using environment variables,
104
+ erase the cassettes, then run `rspec`.
105
+
106
+
107
+ How to release new versions
108
+ ===========================
109
+
110
+ If you are a manager of this project, remember to upgrade the [Net gem](http://rubygems.org/gems/net)
111
+ whenever a new feature is added or a bug gets fixed.
112
+
113
+ Make sure all the tests are passing, document the changes in CHANGELOG.md and
114
+ README.md, bump the version, then run
115
+
116
+ rake release
117
+
118
+ Remember that the net gem follows [Semantic Versioning](http://semver.org).
119
+ Any new release that is fully backward-compatible should bump the *patch* version (0.1.x).
120
+ Any new version that breaks compatibility should bump the *minor* version (0.x.0)
data/lib/net.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "net/version"
2
2
 
3
3
  module Net
4
- # Your code goes here...
5
4
  end
@@ -0,0 +1,11 @@
1
+ require 'net/twitter/config'
2
+ require 'net/twitter/errors'
3
+ require 'net/twitter/models'
4
+
5
+ module Net
6
+ module Twitter
7
+ extend Config
8
+ include Errors
9
+ include Models
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module Net
2
+ module Twitter
3
+ module Api
4
+ class Configuration
5
+ attr_accessor :apps
6
+
7
+ def initialize
8
+ @apps = []
9
+ env_key = ENV['TWITTER_API_KEY']
10
+ env_secret = ENV['TWITTER_API_SECRET']
11
+ @apps.push key: env_key, secret: env_secret if env_key && env_secret
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,121 @@
1
+ require 'net/twitter/errors/response_error'
2
+ require 'active_support'
3
+ require 'active_support/core_ext'
4
+
5
+ module Net
6
+ module Twitter
7
+ module Api
8
+ class Request
9
+ def initialize(attrs = {})
10
+ @host = 'api.twitter.com'
11
+ @path = attrs.fetch :path, "/1.1/#{attrs[:endpoint]}.json"
12
+ @query = attrs[:params].to_param if attrs[:params]
13
+ @block = attrs.fetch :block, -> (request) {add_access_token! request}
14
+ @method = attrs.fetch :method, :get
15
+ end
16
+
17
+ def run
18
+ print "#{as_curl}\n"
19
+
20
+ case response = run_http_request
21
+ when Net::HTTPOK
22
+ JSON response.body
23
+ when Net::HTTPTooManyRequests
24
+ store_rate_limit_reset response.header["x-rate-limit-reset"].to_i
25
+ run
26
+ else
27
+ raise Errors::ResponseError, response
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def run_http_request
34
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
35
+ http.request http_request
36
+ end
37
+ end
38
+
39
+ def http_request
40
+ http_class = "Net::HTTP::#{@method.capitalize}".constantize
41
+ @http_request ||= http_class.new(uri.request_uri).tap do |request|
42
+ @block.call request
43
+ end
44
+ end
45
+
46
+ def uri
47
+ @uri ||= URI::HTTPS.build host: @host, path: @path, query: @query
48
+ end
49
+
50
+ def add_access_token!(request)
51
+ request.add_field 'Authorization', "Bearer #{access_token}"
52
+ end
53
+
54
+ def access_token
55
+ @@access_token ||= fetch_access_token
56
+ end
57
+
58
+ def fetch_access_token
59
+ path = '/oauth2/token'
60
+ block = -> (request) {add_client_credentials! request}
61
+ request = Request.new path: path, method: :post, block: block
62
+ authentication_data = request.run
63
+ authentication_data['access_token']
64
+ # rescue Errors::Suspended
65
+ # require 'pry'; binding.pry; true;
66
+ end
67
+
68
+ def add_client_credentials!(request)
69
+ request.initialize_http_header client_credentials_headers
70
+ request.add_field 'Authorization', "Basic #{credentials}"
71
+ request.set_form_data grant_type: 'client_credentials'
72
+ end
73
+
74
+ def client_credentials_headers
75
+ content_type = 'application/x-www-form-urlencoded;charset=UTF-8'
76
+ {}.tap{|headers| headers['Content-Type'] = content_type}
77
+ end
78
+
79
+ def credentials
80
+ @@app = apps.find(next_available_app) do |app|
81
+ app[:limit_reset].to_i < Time.now.to_i
82
+ end
83
+ Base64.strict_encode64 "#{@@app[:key]}:#{@@app[:secret]}"
84
+ end
85
+
86
+ def next_available_app
87
+ Proc.new do
88
+ next_limit_reset = @@apps.map{|app| app[:limit_reset]}.min
89
+ sleep_time = next_limit_reset - Time.now.to_i
90
+ puts "Sleeping for #{sleep_time}s\n"
91
+ sleep sleep_time
92
+ puts "Waking up\n"
93
+ @@apps.find{|app| app[:limit_reset] == next_limit_reset}
94
+ end
95
+ end
96
+
97
+ def store_rate_limit_reset(limit_reset)
98
+ @@app[:limit_reset] = limit_reset
99
+ @@access_token, @@app, @http_request = nil, nil, nil
100
+ end
101
+
102
+ def apps
103
+ @@apps ||= Net::Twitter.configuration.apps.map do |app|
104
+ app.tap{app[:limit_reset] = nil}
105
+ end
106
+ end
107
+
108
+ def as_curl
109
+ 'curl'.tap do |curl|
110
+ curl << " -X #{http_request.method}"
111
+ http_request.each_header do |name, value|
112
+ curl << %Q{ -H "#{name}: #{value}"}
113
+ end
114
+ curl << %Q{ -d '#{http_request.body}'} if http_request.body
115
+ curl << %Q{ "#{@uri.to_s}"}
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,15 @@
1
+ require 'net/twitter/api/configuration'
2
+
3
+ module Net
4
+ module Twitter
5
+ module Config
6
+ def configure
7
+ yield configuration if block_given?
8
+ end
9
+
10
+ def configuration
11
+ @configuration ||= Api::Configuration.new
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ require 'net/twitter/errors/response_error'
2
+ require 'net/twitter/errors/suspended_user'
3
+ require 'net/twitter/errors/too_many_users'
4
+ require 'net/twitter/errors/unknown_user'
@@ -0,0 +1,14 @@
1
+ module Net
2
+ module Twitter
3
+ module Errors
4
+ class ResponseError < StandardError
5
+ attr_reader :response
6
+
7
+ def initialize(response = {})
8
+ @response = response
9
+ super response
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module Net
2
+ module Twitter
3
+ module Errors
4
+ class SuspendedUser < StandardError
5
+ def message
6
+ 'Suspended user'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Net
2
+ module Twitter
3
+ module Errors
4
+ class TooManyUsers < StandardError
5
+ def message
6
+ 'Too many users in the request'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Net
2
+ module Twitter
3
+ module Errors
4
+ class UnknownUser < StandardError
5
+ def message
6
+ 'Unknown user'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1 @@
1
+ require 'net/twitter/models/user'
@@ -0,0 +1,80 @@
1
+ require 'net/twitter/api/request'
2
+ require 'net/twitter/errors'
3
+
4
+ module Net
5
+ module Twitter
6
+ module Models
7
+ class User
8
+ attr_reader :screen_name, :followers_count
9
+
10
+ def initialize(attrs = {})
11
+ attrs.each{|k, v| instance_variable_set("@#{k}", v) unless v.nil?}
12
+ end
13
+
14
+ # Returns the existing Twitter user matching the provided attributes or
15
+ # nil when the user is not found.
16
+ #
17
+ # @return [Net::Twitter::Models::User] when the user is found.
18
+ # @return [nil] when the user is not found or suspended.
19
+ # @param [Hash] params the attributes to find a user by.
20
+ # @option params [String] :screen_name The Twitter user’s screen name
21
+ # (case-insensitive).
22
+ def self.find_by(params = {})
23
+ find_by! params
24
+ rescue Errors::UnknownUser, Errors::SuspendedUser
25
+ nil
26
+ end
27
+
28
+ # Returns the existing Twitter user matching the provided attributes or
29
+ # raises an error when the user is not found or suspended.
30
+ #
31
+ # @return [Net::Twitter::Models::User] the Twitter user.
32
+ # @param [Hash] params the attributes to find a user by.
33
+ # @option params [String] :screen_name The Twitter user’s screen name
34
+ # (case-insensitive).
35
+ # @raise [Net::Errors::UnknownUser] if the user cannot be found.
36
+ # @raise [Net::Errors::SuspendedUser] if the user account is suspended.
37
+ def self.find_by!(params = {})
38
+ request = Api::Request.new endpoint: 'users/show', params: params
39
+ user_data = request.run
40
+ new user_data
41
+ rescue Errors::ResponseError => error
42
+ case error.response
43
+ when Net::HTTPNotFound then raise Errors::UnknownUser
44
+ when Net::HTTPForbidden then raise Errors::SuspendedUser
45
+ end
46
+ end
47
+
48
+ # Returns up to 100 existing Twitter users matching the provided
49
+ # attributes. Raises an error when requesting more than 100 users.
50
+ # Does not return users in the same order that they are requested.
51
+
52
+ # @return [Array<Net::Twitter::Models::User>] the Twitter users.
53
+ # @param [Hash] conditions The attributes to find users by.
54
+ # @option conditions [Array<String>] screen_name The Twitter user's
55
+ # screen names (case-insensitive).
56
+ # @raise [Net::Errors::TooManyUsers] when more than 100 Twitter users
57
+ # match the provided attributes.
58
+ def self.where(conditions = {})
59
+ params = to_where_params conditions
60
+ request = Api::Request.new endpoint: 'users/lookup', params: params
61
+ users_data = request.run
62
+ users_data.map{|user_data| new user_data}
63
+ rescue Errors::ResponseError => error
64
+ case error.response
65
+ when Net::HTTPNotFound then []
66
+ when Net::HTTPForbidden then raise Errors::TooManyUsers
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def self.to_where_params(conditions = {})
73
+ conditions.dup.tap do |params|
74
+ params.each{|k,v| params[k] = v.join(',') if v.is_a?(Array)}
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end