require 'rubygems' require 'httparty' require 'hashie' require 'oauth' module Foursquare class OAuth def initialize(ctoken, csecret, options={}) @consumer_token, @consumer_secret = ctoken, csecret end def consumer return @consumer if @consumer @consumer = ::OAuth::Consumer.new(@consumer_token, @consumer_secret, { :site => "http://foursquare.com", :scheme => :header, :http_method => :post, :request_token_path => "/oauth/request_token", :access_token_path => "/oauth/access_token", :authorize_path => "/oauth/authorize", :proxy => (ENV['HTTP_PROXY'] || ENV['http_proxy']) }) end def set_callback_url(url) clear_request_token request_token(:oauth_callback => url) end def request_token(options={}) @request_token ||= consumer.get_request_token(options) end def authorize_from_request(request_token, request_secret, verifier) request_token = ::OAuth::RequestToken.new(consumer, request_token, request_secret) access_token = request_token.get_access_token(:oauth_verifier => verifier) @atoken, @asecret = access_token.token, access_token.secret end def access_token @access_token ||= ::OAuth::AccessToken.new(consumer, @atoken, @asecret) end def authorize_from_access(atoken, asecret) @atoken, @asecret = atoken, asecret end private def clear_request_token @request_token = nil end end class Base BASE_URL = 'http://api.foursquare.com/v1' FORMAT = 'json' attr_accessor :oauth def initialize(oauth) @oauth = oauth end # # Foursquare API: http://groups.google.com/group/foursquare-api/web/api-documentation # # .test # api test method # => {'response': 'ok'} # .checkin = {:shout => 'At home. Writing code'} # post new check in # => {...checkin hash...} # .history # authenticated user's checkin history # => [{...checkin hashie...}, {...another checkin hashie...}] # .send('venue.flagclosed=', {:vid => 12345}) # flag venue 12345 as closed # => {'response': 'ok'} # .venue_flagclosed = {:vid => 12345} # => {'response': 'ok'} # # Assignment methods(POSTs) always return a hash. Annoyingly Ruby always returns what's on # the right side of the assignment operator. So there are some wrapper methods below # for POSTs that make sure it gets turned into a hashie # # def method_missing(method_symbol, params = {}) # method_name = method_symbol.to_s.split(/\.|_/).join('/') # # if (method_name[-1,1]) == '=' # method = method_name[0..-2] # result = post(api_url(method), params) # params.replace(result[method] || result) # else # result = get(api_url(method_name, params)) # result[method_name] || result # end # end # # def api(method_symbol, params = {}) # Hashie::Mash.new(method_missing(method_symbol, params)) # end # def api_url(method_name, options = nil) params = options.is_a?(Hash) ? to_query_params(options) : options params = nil if params and params.blank? url = BASE_URL + '/' + method_name.split('.').join('/') url += ".#{FORMAT}" url += "?#{params}" if params url end def parse_response(response) raise_errors(response) Hashie::Mash.new(Yajl::Parser.parse(response.body)) end def to_query_params(options) options.map{|key, value| "#{key}=#{value}"}.join('&') end def get(url, params) parse_response(@oauth.access_token.get(api_url(url, params))) end def post(url, body) parse_response(@oauth.access_token.post(api_url(url), body)) end # API method wrappers # Check in methods def checkins(params = {}) get('/checkins', params).checkins end def checkin(params = {}) post('/checkin', params).checkin end def history(params = {}) get('/history', params).checkins end # User methods def user(params = {}) get('/user', params).user end def friends(params = {}) get('/friends', params).friends end # Venue methods def venues(params = {}) get('/venues', params).venues end def venue(params = P) get('/venue', params).venue end def categories get('/categories', params).categories end def add_venue(params = {}) post('/addvenue', params).venue end def venue_propose_edit(params = {}) post('/venue/proposeedit', params) end def venue_flag_closed(params = {}) post('/venue/flagclosed', params) end # Tip methods def tips(params = {}) get('/tips', params).tips end def add_tip(params = {}) post('/addtip', params).tip end def tip_mark_todo(params = {}) post('/tip/marktodo', params).tip end def tip_mark_done(params = {}) post('/tip/markdone', params).tip end # Friend methods def friend_requests get('/friend/requests').requests end def friend_approve(params = {}) post('/friend/approve', params).user end def friend_deny(params = {}) post('/friend/deny', params).user end def friend_send_request(params = {}) post('/friend/sendrequest', params).user end def friends_by_name(params = {}) post('/findfriends/byname', params).users end def friends_by_phone(params = {}) get('/findfriends/byphone', params).users end def friends_by_twitter(params = {}) get('/findfriends/bytwitter', params).users end def settings_set_pings(params = {}) post('/settings/setpings', params).settings end def test(params = {}) get('/test', params) end private def raise_errors(response) message = "(#{response.code}): #{response.message} - #{response.inspect} - #{response.body}" case response.code.to_i when 400 raise BadRequest, message when 401 raise Unauthorized, message when 403 raise General, message when 404 raise NotFound, message when 500 raise InternalError, "Foursquare had an internal error. Please let them know in the group.\n#{message}" when 502..503 raise Unavailable, message end end end class BadRequest < StandardError; end class Unauthorized < StandardError; end class General < StandardError; end class Unavailable < StandardError; end class InternalError < StandardError; end class NotFound < StandardError; end end