# The Twitter4R API provides a nicer Ruby object API to work with
# instead of coding around the REST API.
require('net/https')
require('uri')
require('json')
# Encapsules the Twitter4R API.
module Twitter
module ClassUtilMixin #:nodoc:
def self.included(base)
base.send(:include, InstanceMethods)
end
module InstanceMethods #:nodoc:
def initialize(params = {})
params.each do |key,val|
self.send("#{key}=", val) if self.respond_to? key
end
end
end
end # ClassUtilMixin
# Error subclass raised when there is an error encountered upon
# querying or posting to the REST API.
class RESTError < Exception
include ClassUtilMixin
attr_accessor :code, :message, :uri
def to_s
"HTTP #{code}: #{message} at #{uri}"
end
end # RESTError
# Represents a user of Twitter
class User
include ClassUtilMixin
attr_accessor :id, :name, :description, :location, :screen_name, :url
def eql?(obj)
[:id, :name, :description, :location,
:screen_name, :url].each do |att|
return false unless self.send(att).eql?(obj.send(att))
end
true
end
end # User
# Represents a status posted to Twitter by a user of Twitter.
class Status
include ClassUtilMixin
attr_accessor :id, :text, :created_at, :user
def eql?(obj)
[:id, :text, :created_at, :user].each do |att|
return false unless self.send(att).eql?(obj.send(att))
end
true
end
end # Status
# Used to query or post to the Twitter REST API to simplify code.
class Client
include ClassUtilMixin
attr_accessor :login, :password
@@CONF = {
:host => 'twitter.com',
:port => 80,
:ssl => false,
}
@@URIS = {:public => '/statuses/public_timeline.json',
:friends => '/statuses/friends_timeline.json',
:friends_statues => '/statuses/friends.json',
:followers => '/statuses/followers.json',
:update => '/statuses/update.json',
:send_direct_message => '/direct_messages/new.json', }
class << self
# The following configuration options can be passed in a hash to this method:
# * host - host of the REST server, defaults to twitter.com. Should only be changed for integration testing.
# * port - port of the REST server, defaults to 443.
# * ssl - SSL flag, defaults to false. Do not use :use_ssl anymore.
# * proxy_host - proxy host. No default.
# * proxy_port - proxy port. No default.
# * proxy_user - proxy username. No default.
# * proxy_pass - proxy password. No default.
def config(conf)
@@CONF.merge!(conf)
end
# Mostly helper method for irb shell prototyping
# TODO: move this out as class extension for twitter4r console script
def from_config(config_file, env = 'test')
yaml_hash = YAML.load(File.read(config_file))
self.new yaml_hash[env]
end
end # class << self
def timeline(type = :public)
http = create_http_connection
http.start do |http|
timeline_request(type, http)
end # http.start block
end # timeline
def public_timeline
timeline :public
end
def friend_timeline
timeline :friends
end
def friend_statuses
timeline :friends_statuses
end
def follower_statuses
timeline :followers
end
def update(message)
uri = @@URIS[:update]
http = create_http_connection
http.start do |http|
request = Net::HTTP::Post.new(uri)
request.basic_auth(@login, @password)
response = http.request(request, "status=#{URI.escape(message)}")
handle_rest_response(response, uri)
end
end
def send_direct_message(user, message)
login = user.respond_to?(:screen_name) ? user.screen_name : user
uri = @@URIS[:send_direct_message]
http = create_http_connection
http.start do |http|
request = Net::HTTP::Post.new(uri)
request.basic_auth(@login, @password)
response = http.request(request, "user=#{login}&text=#{URI.escape(message)}")
handle_rest_response(response, uri)
end
end
protected
attr_accessor :login, :password, :host, :port
def unmarshall_statuses(status_array)
status_array.collect do |status|
Status.new(:id => status['id'],
:text => status['text'],
:user => unmarshall_user(status['user']),
:created_at => Time.parse(status['created_at'])
)
end
end
def unmarshall_user(map)
User.new(:id => map['id'], :name => map['name'],
:screen_name => map['screen_name'],
:description => map['description'],
:location => map['location'],
:url => map['url'])
end
def timeline_request(type, http)
uri = @@URIS[type]
request = Net::HTTP::Get.new(uri)
request.basic_auth(@login, @password)
response = http.request(request)
handle_rest_response(response, uri)
unmarshall_statuses(JSON.parse(response.body))
end
def create_http_connection
conn = Net::HTTP.new(@@CONF[:host], @@CONF[:port],
@@CONF[:proxy_host], @@CONF[:proxy_port],
@@CONF[:proxy_user], @@CONF[:proxy_pass])
conn.use_ssl = @@CONF[:ssl]
conn.verify_mode = OpenSSL::SSL::VERIFY_NONE if @@CONF[:ssl]
conn
end
def raise_rest_error(response, uri = nil)
raise RESTError.new(:code => response.code,
:message => response.message,
:uri => uri)
end
def handle_rest_response(response, uri)
unless ["200", "201"].member?(response.code)
raise_rest_error(response, uri)
end
end
end
end