Report abuse

require 'cgi'
require 'net/http'
require 'xmlsimple' # gem install xml-simple
require 'digest/md5'
# Hyves API client class.
# v0.1 (Joost Hietbrink, joost@yelloyello.com) MIT License
#
# Usage:
#  h = Hyves.new(key, secret)
#  h.authorize_token_url(:callback_url => 'http://localhost:3000/hyves/')
#  # Now go to the url, login and get an requesttoken
#  h.requesttoken = # requesttoken from the url
#  h.auth_accesstoken # Sets the access token in h.accesstoken
#  h.users_getLoggedin
#  friend_uids = h.friends_get['uid'] # Gets the friends for logged-in user
#  friends = h.users_get(:uid => friend_uids)
#  pids = friends['user'].collect{|friend| friend['pid']}
#  friend_avatars = h.photos_get(:pid => pids)
#  h.friends_getDistance(:uid => 1)
#
# When you have errors, use h.debug = true to get some info.
class Hyves

  class HyvesException < StandardError;end

  attr_accessor :api_key, :shared_secret, :version, :host, :debug

  # Set the requesttoken you got via the authorize_token_url
  attr_accessor :requesttoken
  attr_accessor :accesstoken # Hash with all accesstoken attributes: {"expiredate"=>"2007-12-11 10:57:56", "accesstoken"=>"OTA3X_74MOE1NkfqY6-CFRAqjhI=", "uid"=>"1019902"}
  attr_reader :output

  # Replace this API key & secret with your own
  def initialize(api_key='your key here', shared_secret='and a secret here')
    @api_key = api_key
    @shared_secret = shared_secret
    @host = 'http://hyves-api.nl'
    @version = '2007.9.4'
    @output = 'xml' # 'json'
    @debug = false
  end

  # def request(method, *params)
  def request(url)
    response = XmlSimple.xml_in(http_get(url), { 'ForceArray' => false })
    log "url: #{url}"
    log response.inspect
    raise HyvesException, "#{response['error_message']} (errorcode: #{response['error_code']})" if response['error_code']
    response
  end

  # Does an HTTP GET on a given URL and returns the response body
  def http_get(url)
    Net::HTTP.get_response(URI.parse(url)).body.to_s
  end

  def api_call(options = {})
    time = Time.now.to_i # FIXME: Should this be Time.now.utc?
    params_hash = {:format => output, :v => version, :api_key => api_key, :ts => time}
    params_hash[:accesstoken] = accesstoken['accesstoken'] unless accesstoken.blank? # Add accesstoken if we have one!
    params_hash.merge!(options) # Overwrite the defaults with special options
    params_hash.stringify_keys!
    # NOTE: sorting is needed to generate unique (same) sig as server
    # NOTE: oauth signature is based on "param1=a¶m2=b", Hyves signature expects "param1=aparam2=b"
    md5_params = to_url_params(params_hash, :seperator => '', :sort => true, :encode => false)
    url_params = to_url_params(params_hash)
    signature = Digest::MD5.hexdigest(md5_params+shared_secret)
    url = "http://hyves-api.nl/?#{url_params}&sig=#{signature}"
    return request(url)
  end

  # For Desktop apps. Webapps should use the authorize_token_url to get this!
  # Returns the latest requesttoken OR gets a new one using the Desktop method.
  def auth_requesttoken
    requesttoken || @requesttoken = self.api_call(:method => 'auth.requesttoken')['requesttoken']
  end

  # Returns the latest accesstoken OR retreivces new one using the most recent requesttoken.
  def auth_accesstoken
    accesstoken || @accesstoken = self.api_call(:method => 'auth.accesstoken', :requesttoken => self.auth_requesttoken)
  end

  # options:
  #  callback_url = http://www.example.com/callback/
  #  seperator = '?'
  #  infinite = 'false'
  def authorize_token_url(options = {})
    raise ArgumentError, 'Please supply a callback_url!' if options[:callback_url].blank?
    url_params = to_url_params(options)
    url = "http://www.hyves.nl/authorizeToken/?api_key=#{api_key}"
    url += "&#{url_params}" unless url_params.blank?
  end

private

  # This method defines all api calls
  def method_missing(meth, *args)
    hyves_method = meth.to_s.gsub('_', '.')
    options = {:method => hyves_method}
    options.merge!(args.first) if args.first.is_a?(Hash)
    api_call(options)
  end

  def to_url_params(hash, options={})
    options.reverse_merge!({:seperator => '&', :sort => false, :encode => true}) # Default options
    keys = (options[:sort] ? hash.keys.sort : hash.keys) # Sort the hash (needed for md5)
    keys.collect do |key|
      value = hash[key]
      value = value.join(',') if value.is_a?(Array)
      param = "#{key}=#{value}"
      options[:escape] ? URI.escape(param) : param # return escaped / non-escaped param to collect array
    end.join(options[:seperator])
  end

  def log(msg)
    puts msg if @debug
  end

end