require 'rubygems' require 'active_support' require 'time' require 'net/https' require 'rexml/document' require 'cgi' module Delicious def self.authenticate(options={}) API.auth.update(options) end module Cache class Basic < Hash attr_reader :created_on def initialize(*args) @created_on = Time.now end def cache(uri, data) self[uri] = data end def expire(pattern) each { |uri, val| delete uri if uri =~ pattern } end end end class APIError < StandardError; end class API @@auth = {} @@host = 'api.del.icio.us' @@base_url = '/v1' @@user_agent = 'Del.icio.us Ruby API 0.1' @@cache_class = Cache::Basic @@cache = @@cache_class.new @@expire_cache_every = 1.days @@auto_expire_cache = true cattr_accessor :auth, :host, :base_url, :cache, :user_agent, :cache_class, :expire_cache_every cattr_accessor :auto_expire_cache def initialize(props={}) props.each do |key, val| setter = "#{key}=".intern self.send setter, val if respond_to? setter end end class << self def resource_name name.split('::').last.downcase.pluralize end def call(meth, parameters={}) uri = "#{base_url}/#{resource_name}/#{meth}?#{build_params(parameters)}" parse(request(uri)) rescue raise APIError, "Request failed with error: #{$!}" end protected def request(uri) check_cache if auto_expire_cache return cache[uri] if cache.has_key? uri http = Net::HTTP.new(host, 443) http.use_ssl = true http.start do |http| req = Net::HTTP::Get.new(uri, "User-Agent" => user_agent) req.basic_auth(auth[:username], auth[:password]) cache.cache(uri, http.request(req).body) end end def parse(response) doc = REXML::Document.new(response) if doc.root.name == 'result' doc.root.attributes['code'] || doc.root.text else doc.root.elements.collect { |element| get_object(element) } end end def get_object(element) klass = Module.const_get("Delicious").const_get(element.name.capitalize) klass.new element.attributes rescue raise ObjectNotSupported, "Delicious::#{element.name.capitalize}" end def build_params(parameters) parameters.collect { |key, value| "#{key.to_s}=#{CGI::escape(value.to_s)}" if value }.compact.join "&" end def check_cache self.cache = cache_class.new if Time.now > (cache.created_on + expire_cache_every) end def method_missing(meth, *args) if args.empty? || args.size == 1 && args.first.is_a?(Hash) call(meth, args.first || {}) else super end end end end class Post < API attr_accessor :href, :description, :extended, :tag, :time, :has, :others def tags tag.split ' ' end def tags=(val) tag = val.join ' ' end def add(replace=false, shared=false) result = self.class.call(:add, :url => href, :tags => tag, :description => description, :extended => extended, :replace => replace ? 'yes' : 'no', :shared => shared ? 'yes' : 'no' ) raise APIError, "Post add failed with error: #{result}" unless result == 'done' return true end end class Tag < API attr_accessor :count, :tag end class Bundle < API attr_accessor :name, :tags end class Date < API attr_accessor :date, :count end end