module Rumblr class Client include Singleton # The default set of headers for each request. DEFAULT_HEADER = { 'User-Agent' => 'Rumblr', 'Accept' => 'application/xml' } def authenticate(email,password) raise ArgumentError unless (email && password) user_credentials = { :email => email, :password => password } uri = URI::HTTP.build({:path => '/api/authenticate'}) request = Net::HTTP::Post.new(uri.request_uri, DEFAULT_HEADER) request.set_form_data(user_credentials) user_attributes = nil complete_request(request) do |response_body| user_attributes = parse_user_attributes_from(response_body) user_attributes.merge!(user_credentials) end user = User.new(user_attributes) user.tumblelogs.each{ |tumblelog| tumblelog.user = user } user end def read(options={}) tumblelog, posts = nil, [] # initialize the return targets uri = URI::HTTP.build({:path => '/api/read'}) request = Net::HTTP::Post.new(uri.request_uri, DEFAULT_HEADER) request.set_form_data(options) raise(ArgumentError) unless (options && options[:url] && !options[:url].empty?) request_url_host = URI.parse(options[:url]).host complete_request(request,host=request_url_host) do |response_body| parser = XML::Parser.string(response_body) doc = parser.parse # parse and map tumblelog tumblelog_element = doc.find_first('//tumblr/tumblelog') tumblelog_attrs = cleanup_hash(tumblelog_element.attributes.to_h) tumblelog_attrs.merge!(:url => options[:url]) tumblelog = Tumblelog.new(tumblelog_attrs) # parse and map posts posts = doc.find('//tumblr/posts/post').inject([]) do |array, element| post_attrs = cleanup_hash(element.attributes.to_h) # inner elements => sublcass-specific attribute hash subclass_attrs = element.children.inject({'tags' => []}) do |hash, child| hash['tags'] << child.content if (child.name[/tag/]) hash[child.name] = child.content hash end # merge hashes, clean out cruft inner_attrs = cleanup_hash(subclass_attrs) inner_attrs.delete(:text) inner_attrs.delete(:tag) post_attrs.merge!(inner_attrs) # turn attributes into proper model klass = Rumblr.const_get(Post::TYPES[post_attrs[:type]]) raise 'unknown post type' unless klass post = klass.new(post_attrs) post.tumblelog = tumblelog array << post array end end return tumblelog, posts end def write(post, creds) raise(ArgumentError) unless post && post.is_a?(Post) raise(ArgumentError) unless creds uri = URI::HTTP.build(:path => "/api/write") request = Net::HTTP::Post.new(uri.request_uri, DEFAULT_HEADER) request.set_form_data(creds.merge(:type => Post::TYPES.invert[post.class]).merge(post.attribute_hash)) request_url_host = URI.parse(uri.request_uri).host complete_request(request) end protected # Starts and completes the given request. Returns or yields the response body. def complete_request(request, host = URI.parse(Rumblr.api_url).host) http = Net::HTTP.new(host) # Set to debug http output. # http.set_debug_output $stderr response = http.start { |http| http.request(request) } case response when Net::HTTPSuccess then yield response.body if block_given? when Net::HTTPBadRequest then raise Rumblr::RequestError, parse_error_message(response) when Net::HTTPForbidden then raise Rumblr::AuthorizationError, parse_error_message(response) when Net::HTTPNotFound then raise Rumblr::MissingResourceError, parse_error_message(response) when Net::HTTPMovedPermanently then raise Rumblr::MovedResourceError, parse_error_message(response) when Net::HTTPServerError then raise Rumblr::ServerError, connection_error_message else raise "Received an unexpected HTTP response: #{response}" end response.body end # Extracts the error message from the response for the exception. def parse_error_message(response) response.body end def connection_error_message "There was a problem connecting to Tumblr" end private def parse_user_attributes_from(response_body) parser = XML::Parser.string response_body doc = parser.parse tumblelogs = doc.find('//tumblr/tumblelog').inject([]) do |array, element| tumblelog_attrs = cleanup_hash(element.attributes.to_h) array << Tumblelog.new(tumblelog_attrs) array end raw_user_attributes = doc.find('//tumblr/user').first.attributes.to_h user_attributes = cleanup_hash(raw_user_attributes) user_attributes.merge!(:tumblelogs => tumblelogs) end def cleanup_hash(attrs={}) clean_attrs = attrs.inject({}) do |hash,(key,value)| mapped_key = key.gsub(/-/,'_').to_sym mapped_value = case value when /($1^|$yes^)/ then true when /($0^|$no^)/ then false else value end hash[mapped_key] = mapped_value hash end end end end