# amazon.rb: Making link with image to Amazon using Amazon ECS. # # see document: #{@lang}/amazon.rb # # Copyright (C) 2005-2007 TADA Tadashi # You can redistribute it and/or modify it under GPL2 or any later version. # require 'net/http' require 'uri' require 'timeout' require 'rexml/document' # do not change these variables @amazon_subscription_id = '1CVA98NEF1G753PFESR2' @amazon_require_version = '2011-08-01' @amazon_url_hash = { 'ca' => 'http://www.amazon.ca/exec/obidos/ASIN', 'cn' => 'http://www.amazon.cn/exec/obidos/ASIN', 'de' => 'http://www.amazon.de/exec/obidos/ASIN', 'es' => 'http://www.amazon.es/exec/obidos/ASIN', 'fr' => 'http://www.amazon.fr/exec/obidos/ASIN', 'it' => 'http://www.amazon.it/exec/obidos/ASIN', 'jp' => 'http://www.amazon.co.jp/exec/obidos/ASIN', 'uk' => 'http://www.amazon.co.uk/exec/obidos/ASIN', 'us' => 'http://www.amazon.com/exec/obidos/ASIN', } @amazon_ecs_url_hash = { 'ca' => 'http://rpaproxy.tdiary.org/rpaproxy/ca/', 'cn' => 'http://rpaproxy.tdiary.org/rpaproxy/cn/', 'de' => 'http://rpaproxy.tdiary.org/rpaproxy/de/', 'es' => 'http://rpaproxy.tdiary.org/rpaproxy/es/', 'fr' => 'http://rpaproxy.tdiary.org/rpaproxy/fr/', 'it' => 'http://rpaproxy.tdiary.org/rpaproxy/it/', 'jp' => 'http://rpaproxy.tdiary.org/rpaproxy/jp/', 'uk' => 'http://rpaproxy.tdiary.org/rpaproxy/uk/', 'us' => 'http://rpaproxy.tdiary.org/rpaproxy/us/', } enable_js( 'amazon.js' ) if @conf['amazon.bitly'] and @conf['bitly.login'] and @conf['bitly.key'] then enable_js( 'amazon_bitly.js' ) add_js_setting( '$tDiary.plugin.bitly' ) add_js_setting( '$tDiary.plugin.bitly.login', "'#{@conf['bitly.login']}'" ) add_js_setting( '$tDiary.plugin.bitly.apiKey', "'#{@conf['bitly.key']}'" ) end class AmazonRedirectError < StandardError; end class AmazonItem def initialize(xml, parser = :rexml) @parser = parser if parser == :oga @doc = Oga.parse_xml(xml) @item = @doc.xpath('*/*/Item')[0] else @doc = REXML::Document::new( REXML::Source::new( xml ) ).root @item = @doc.elements.to_a( '*/Item' )[0] end end def nodes(path) if @parser == :oga if @item @item.xpath(path) else @doc.xpath(path) end else if @item @item.elements.to_a(path) else @doc.elements.to_a(path) end end end def has_item? !@item.nil? end end def amazon_fetch( url, limit = 10 ) raise ArgumentError, 'HTTP redirect too deep' if limit == 0 px_host, px_port = (@conf['proxy'] || '').split( /:/ ) px_port = 80 if px_host and !px_port res = Net::HTTP::Proxy( px_host, px_port ).get_response( URI::parse( url ) ) case res when Net::HTTPSuccess res.body when Net::HTTPRedirection, Net::HTTPFound amazon_fetch( res['location'], limit - 1 ) when Net::HTTPForbidden, Net::HTTPServiceUnavailable raise AmazonRedirectError.new( limit.to_s ) else raise ArgumentError, res.error! end end def amazon_call_ecs( asin, id_type, country ) @conf["amazon.aid.#{@amazon_default_country}"] = @conf['amazon.aid'] unless @conf['amazon.aid'].to_s.empty? aid = @conf["amazon.aid.#{country}"] || '' url = (@conf['amazon.endpoints'] || @amazon_ecs_url_hash)[country].dup url << "?Service=AWSECommerceService" url << "&SubscriptionId=#{@amazon_subscription_id}" url << "&AssociateTag=#{aid}" unless aid.empty? url << "&Operation=ItemLookup" url << "&ItemId=#{asin}" url << "&IdType=#{id_type}" url << "&SearchIndex=Books" if id_type == 'ISBN' url << "&SearchIndex=All" if id_type == 'EAN' url << "&ResponseGroup=Medium" url << "&Version=#{@amazon_require_version}" limit = 10 begin Timeout.timeout( limit ) do amazon_fetch( url ) end rescue AmazonRedirectError limit = $!.message.to_i retry rescue ArgumentError, SystemCallError, Net::HTTPExceptions @logger.error "amazon.rb: #{$!.message} by #{asin}" end end def amazon_author( item ) begin author = [] %w(Author Creator Artist).each do |elem| item.nodes( "*/#{elem}" ).each do |a| author << a.text end end @conf.to_native( author.uniq.join( '/' ), 'utf-8' ) rescue '-' end end def amazon_title( item ) @conf.to_native( item.nodes( '*/Title' )[0].text, 'utf-8' ) end def amazon_image( item ) image = {} begin size = case @conf['amazon.imgsize'] when 0; 'Large' when 2; 'Small' else; 'Medium' end if item.nodes("#{size}Image")[0] node_prefix = "#{size}Image" elsif item.nodes("ImageSets/ImageSet/#{size}Image")[0] node_prefix = "ImageSets/ImageSet/#{size}Image" end image[:src] = item.nodes("#{node_prefix}/URL")[0].text image[:src].gsub!(/http:\/\/ecx\.images-amazon\.com/, 'https://images-na.ssl-images-amazon.com') image[:height] = item.nodes("#{node_prefix}/Height")[0].text image[:width] = item.nodes("#{node_prefix}/Width")[0].text rescue base = @conf['amazon.default_image_base'] || 'https://tdiary.github.io/tdiary-theme/plugin/amazon/' case @conf['amazon.imgsize'] when 0 image[:src] = "#{base}large.png" image[:height] = 500 image[:width] = 380 when 2 image[:src] = "#{base}small.png" image[:height] = 75 image[:width] = 57 else image[:src] = "#{base}medium.png" image[:height] = 160 image[:width] = 122 end end image end def amazon_url( item ) item.nodes( 'DetailPageURL' )[0].text end def amazon_label( item ) begin @conf.to_native( item.nodes( '*/Label' )[0].text, 'utf-8' ) rescue '-' end end def amazon_price( item ) begin @conf.to_native( item.nodes( '*/LowestNewPrice/FormattedPrice' )[0].text, 'utf-8' ) rescue begin @conf.to_native( item.nodes( '*/ListPrice/FormattedPrice' )[0].text, 'utf-8' ) rescue '(no price)' end end end def amazon_detail_html( item ) author = amazon_author( item ) title = amazon_title( item ) size_orig = @conf['amazon.imgsize'] @conf['amazon.imgsize'] = 2 image = amazon_image( item ) @conf['amazon.imgsize'] = size_orig url = amazon_url( item ) <<-HTML #{h title}
#{h author}
#{h amazon_label( item )}
#{h amazon_price( item )}
HTML end def amazon_to_html( item, with_image = true, label = nil, pos = 'amazon' ) with_image = false if @mode == 'categoryview' author = amazon_author( item ) author = "(#{author})" unless author.empty? label ||= %Q|#{amazon_title( item )}#{author}| alt = '' if with_image and @conf['amazon.hidename'] || pos != 'amazon' then label, alt = alt, label end if with_image image = amazon_image( item ) unless image[:src] then img = '' else size = %Q|height="#{h image[:height]}" width="#{h image[:width]}"| img = <<-HTML #{h alt} HTML img.gsub!( /\t/, '' ) end end url = amazon_url( item ) %Q|#{img}#{h label}| end def amazon_get( asin, with_image = true, label = nil, pos = 'amazon' ) asin = asin.to_s.strip # delete white spaces asin.sub!(/\A([a-z]+):/, '') country = $1 || @conf['amazon.default_country'] || @amazon_default_country digit = asin.gsub( /[^\d]/, '' ) if digit.length == 13 then # ISBN-13 asin = digit id_type = /^97[89]/ =~ digit ? 'ISBN' : 'EAN' else id_type = 'ASIN' end begin cache = "#{@cache_path}/amazon" Dir::mkdir( cache ) unless File::directory?( cache ) begin xml = File::read( "#{cache}/#{country}#{asin}.xml" ) if xml.chomp == 'true' # ignore dirty cache raise Errno::ENOENT end rescue Errno::ENOENT xml = amazon_call_ecs( asin, id_type, country ) File::open( "#{cache}/#{country}#{asin}.xml", 'wb' ) {|f| f.write( xml )} end parser = defined?(Oga) ? :oga : :rexml item = AmazonItem.new(xml, parser) if pos == 'detail' then amazon_detail_html( item ) else amazon_to_html( item, with_image, label, pos ) end rescue Timeout::Error @logger.error "amazon.rb: Amazon API Timeouted." message = asin if @mode == 'preview' then message << %Q|(Amazon API Timeouted))| end message rescue NoMethodError message = label || asin if @mode == 'preview' then if item.has_item? then message << %Q|(#{h $!}\n#{h $@.join( ' / ' )})| else m = item.nodes( 'Items/Request/Errors/Error/Message' )[0].text message << %Q|(#{h @conf.to_native( m, 'utf-8' )})| end end message end end add_conf_proc( 'amazon', @amazon_label_conf ) do amazon_conf_proc end def amazon_conf_proc if @mode == 'saveconf' then @conf['amazon.imgsize'] = @cgi.params['amazon.imgsize'][0].to_i @conf['amazon.hidename'] = (@cgi.params['amazon.hidename'][0] == 'true') @conf['amazon.bitly'] = (@cgi.params['amazon.bitly'][0] == 'true') @conf['amazon.nodefault'] = (@cgi.params['amazon.nodefault'][0] == 'true') if @cgi.params['amazon.clearcache'][0] == 'true' then Dir["#{@cache_path}/amazon/*"].each do |cache| File::delete( cache ) end end unless @conf['amazon.hideconf'] then @conf['amazon.aid'] = @cgi.params['amazon.aid'][0] end end result = '' result << <<-HTML

#{@amazon_label_imgsize}

#{@amazon_label_title}

HTML if @options['bitly.login'] and @options['bitly.key'] then result << <<-HTML

#{@amazon_label_bitly}

HTML end result << <<-HTML

#{@amazon_label_notfound}

#{@amazon_label_clearcache}

HTML unless @conf['amazon.hideconf'] then result << <<-HTML

#{@amazon_label_aid}

#{@amazon_label_aid_desc}

HTML end result end def isbn_detail( asin ) amazon_get( asin, true, nil, 'detail' ) end def isbn_image( asin, label = nil ) amazon_get( asin, true, label ) end def isbn_image_left( asin, label = nil ) amazon_get( asin, true, label, 'left' ) end def isbn_image_right( asin, label = nil ) amazon_get( asin, true, label, 'right' ) end def isbn( asin, label = nil ) amazon_get( asin, false, label ) end # for compatibility alias isbnImgLeft isbn_image_left alias isbnImgRight isbn_image_right alias isbnImg isbn_image alias amazon isbn_image # Local Variables: # mode: ruby # indent-tabs-mode: t # tab-width: 3 # ruby-indent-level: 3 # End: