Skip to content

Commit

Permalink
Initial release with Twitter::User
Browse files Browse the repository at this point in the history
Supports `find_by`, `find_by!` and `where`.
  • Loading branch information
jcohenho authored and claudiob committed Oct 13, 2014
0 parents commit 88101b5
Show file tree
Hide file tree
Showing 34 changed files with 6,174 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
*.bundle
*.so
*.o
*.a
mkmf.log
3 changes: 3 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--format documentation
--color
--fail-fast
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog

All notable changes to this project will be documented in this file.

For more information about changelogs, check
[Keep a Changelog](http://keepachangelog.com) and
[Vandamme](http://tech-angels.github.io/vandamme).

## 0.1.0 - 2014-10-13

* Initial release with `Twitter::User` supporting `find_by`, `find_by!` and `where`.
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in net.gemspec
gemspec
20 changes: 20 additions & 0 deletions MIT-LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright 2014 Fullscreen, Inc.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
120 changes: 120 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
Net - a Ruby client for social networks API
===========================================

Net helps you write apps that need to interact with Twitter.


After [configuring your Twitter app](#configuring-your-app), you can run commands like:

```ruby
user = Net::Twitter::User.find_by screen_name: 'fullscreen'
user.screen_name #=> "Fullscreen"
user.followers_count #=> 48_200
```

How to install
==============

To install on your system, run

gem install net

To use inside a bundled Ruby project, add this line to the Gemfile:

gem 'net', '~> 0.1.0'

Since the gem follows [Semantic Versioning](http://semver.org),
indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
guarantees that your project won’t occur in any error when you `bundle update`
and a new version of Net is released.

Available resources
===================

Net::Twitter::User
-----------

Use [Net::Account]() to:

* retrieve a Twitter user by screen name
* retrieve a list of Twitter users by screen names
* access the number of followers of a Twitter user

```ruby
user = Net::Twitter::User.find_by screen_name: 'fullscreen'
user.followers_count #=> 48_200

users = Net::Twitter::User.where screen_name: ['fullscreen', 'brohemian6']
users.map(&:followers_count).sort #=> [12, 48_200]
```

*The methods above require a configured Twitter app (see below).*

Configuring your app
====================

In order to use Net you must create an app in the [Twitter Application Manager](https://apps.twitter.com/app/new).

Once the app is created, copy the API key and secret and add them to your
code with the following snippet of code (replacing with your own key and secret)
:

```ruby
Net::Twitter.configure do |config|
config.apps.push key: 'abcd', secret: 'efgh'
end
```

Configuring with environment variables
--------------------------------------

As an alternative to the approach above, you can configure your app with
variables. Setting the following environment variables:

```bash
export TWITTER_API_KEY='abcd'
export TWITTER_API_SECRET='efgh'
```

is equivalent to configuring your app with the initializer:

```ruby
Net::Twitter.configure do |config|
config.apps.push key: 'abcd', secret: 'efgh'
end
```

so use the approach that you prefer.
If a variable is set in both places, then `Net::Twitter.configure` takes precedence.

How to test
===========

To run tests, type:

```bash
rspec
```

Net uses [VCR](https://github.com/vcr/vcr) so by default tests do not run
HTTP requests.

If you need to run tests against the live Twitter API,
[configure your Twitter app](#configuring-your-app) using environment variables,
erase the cassettes, then run `rspec`.


How to release new versions
===========================

If you are a manager of this project, remember to upgrade the [Net gem](http://rubygems.org/gems/net)
whenever a new feature is added or a bug gets fixed.

Make sure all the tests are passing, document the changes in CHANGELOG.md and
README.md, bump the version, then run

rake release

Remember that the net gem follows [Semantic Versioning](http://semver.org).
Any new release that is fully backward-compatible should bump the *patch* version (0.1.x).
Any new version that breaks compatibility should bump the *minor* version (0.x.0)
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require "bundler/gem_tasks"

4 changes: 4 additions & 0 deletions lib/net.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
require "net/version"

module Net
end
11 changes: 11 additions & 0 deletions lib/net/twitter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'net/twitter/config'
require 'net/twitter/errors'
require 'net/twitter/models'

module Net
module Twitter
extend Config
include Errors
include Models
end
end
16 changes: 16 additions & 0 deletions lib/net/twitter/api/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Net
module Twitter
module Api
class Configuration
attr_accessor :apps

def initialize
@apps = []
env_key = ENV['TWITTER_API_KEY']
env_secret = ENV['TWITTER_API_SECRET']
@apps.push key: env_key, secret: env_secret if env_key && env_secret
end
end
end
end
end
121 changes: 121 additions & 0 deletions lib/net/twitter/api/request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require 'net/twitter/errors/response_error'
require 'active_support'
require 'active_support/core_ext'

module Net
module Twitter
module Api
class Request
def initialize(attrs = {})
@host = 'api.twitter.com'
@path = attrs.fetch :path, "/1.1/#{attrs[:endpoint]}.json"
@query = attrs[:params].to_param if attrs[:params]
@block = attrs.fetch :block, -> (request) {add_access_token! request}
@method = attrs.fetch :method, :get
end

def run
print "#{as_curl}\n"

case response = run_http_request
when Net::HTTPOK
JSON response.body
when Net::HTTPTooManyRequests
store_rate_limit_reset response.header["x-rate-limit-reset"].to_i
run
else
raise Errors::ResponseError, response
end
end

private

def run_http_request
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.request http_request
end
end

def http_request
http_class = "Net::HTTP::#{@method.capitalize}".constantize
@http_request ||= http_class.new(uri.request_uri).tap do |request|
@block.call request
end
end

def uri
@uri ||= URI::HTTPS.build host: @host, path: @path, query: @query
end

def add_access_token!(request)
request.add_field 'Authorization', "Bearer #{access_token}"
end

def access_token
@@access_token ||= fetch_access_token
end

def fetch_access_token
path = '/oauth2/token'
block = -> (request) {add_client_credentials! request}
request = Request.new path: path, method: :post, block: block
authentication_data = request.run
authentication_data['access_token']
# rescue Errors::Suspended
# require 'pry'; binding.pry; true;
end

def add_client_credentials!(request)
request.initialize_http_header client_credentials_headers
request.add_field 'Authorization', "Basic #{credentials}"
request.set_form_data grant_type: 'client_credentials'
end

def client_credentials_headers
content_type = 'application/x-www-form-urlencoded;charset=UTF-8'
{}.tap{|headers| headers['Content-Type'] = content_type}
end

def credentials
@@app = apps.find(next_available_app) do |app|
app[:limit_reset].to_i < Time.now.to_i
end
Base64.strict_encode64 "#{@@app[:key]}:#{@@app[:secret]}"
end

def next_available_app
Proc.new do
next_limit_reset = @@apps.map{|app| app[:limit_reset]}.min
sleep_time = next_limit_reset - Time.now.to_i
puts "Sleeping for #{sleep_time}s\n"
sleep sleep_time
puts "Waking up\n"
@@apps.find{|app| app[:limit_reset] == next_limit_reset}
end
end

def store_rate_limit_reset(limit_reset)
@@app[:limit_reset] = limit_reset
@@access_token, @@app, @http_request = nil, nil, nil
end

def apps
@@apps ||= Net::Twitter.configuration.apps.map do |app|
app.tap{app[:limit_reset] = nil}
end
end

def as_curl
'curl'.tap do |curl|
curl << " -X #{http_request.method}"
http_request.each_header do |name, value|
curl << %Q{ -H "#{name}: #{value}"}
end
curl << %Q{ -d '#{http_request.body}'} if http_request.body
curl << %Q{ "#{@uri.to_s}"}
end
end
end
end
end
end
15 changes: 15 additions & 0 deletions lib/net/twitter/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'net/twitter/api/configuration'

module Net
module Twitter
module Config
def configure
yield configuration if block_given?
end

def configuration
@configuration ||= Api::Configuration.new
end
end
end
end
4 changes: 4 additions & 0 deletions lib/net/twitter/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
require 'net/twitter/errors/response_error'
require 'net/twitter/errors/suspended_user'
require 'net/twitter/errors/too_many_users'
require 'net/twitter/errors/unknown_user'
14 changes: 14 additions & 0 deletions lib/net/twitter/errors/response_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Net
module Twitter
module Errors
class ResponseError < StandardError
attr_reader :response

def initialize(response = {})
@response = response
super response
end
end
end
end
end
11 changes: 11 additions & 0 deletions lib/net/twitter/errors/suspended_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Net
module Twitter
module Errors
class SuspendedUser < StandardError
def message
'Suspended user'
end
end
end
end
end
Loading

0 comments on commit 88101b5

Please sign in to comment.