misc/plugin/amazon.rb in tdiary-5.0.14 vs misc/plugin/amazon.rb in tdiary-5.1.0

- old
+ new

@@ -1,46 +1,15 @@ # amazon.rb: Making link with image to Amazon using Amazon ECS. # # see document: #{@lang}/amazon.rb # -# Copyright (C) 2005-2007 TADA Tadashi <sho@spc.gr.jp> +# Copyright (C) 2005-2019 TADA Tadashi <t@tdtds.jp> # You can redistribute it and/or modify it under GPL2 or any later version. # - -require 'net/http' -require 'uri' +require 'aws/pa_api' 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' ) @@ -48,125 +17,33 @@ 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 - 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 +def amazon_author(item) 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' ) + author = item["ItemInfo"]["ByLineInfo"]["Contributors"][0]["Name"] rescue '-' end end -def amazon_title( item ) - @conf.to_native( item.nodes( '*/Title' )[0].text, 'utf-8' ) +def amazon_title(json) + json["ItemInfo"]["Title"]["DisplayValue"] end -def amazon_image( item ) +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 + image[:src] = item["Images"]["Primary"][size]["URL"] + image[:height] = item["Images"]["Primary"][size]["Height"] + image[:width] = item["Images"]["Primary"][size]["Width"] 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" @@ -183,73 +60,69 @@ end end image end -def amazon_url( item ) - item.nodes( 'DetailPageURL' )[0].text +def amazon_url(item) + item["DetailPageURL"] end def amazon_label( item ) begin - @conf.to_native( item.nodes( '*/Label' )[0].text, 'utf-8' ) + item["ItemInfo"]["ByLineInfo"]["Manufacturer"]["DisplayValue"] rescue '-' end end -def amazon_price( item ) +def amazon_price(item) begin - @conf.to_native( item.nodes( '*/LowestNewPrice/FormattedPrice' )[0].text, 'utf-8' ) + item["Offers"]["Listings"][0]["Price"]["DisplayAmount"] rescue - begin - @conf.to_native( item.nodes( '*/ListPrice/FormattedPrice' )[0].text, 'utf-8' ) - rescue - '(no price)' - end + '(no price)' end end -def amazon_detail_html( item ) - author = amazon_author( item ) - title = amazon_title( item ) +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 ) + image = amazon_image(item) @conf['amazon.imgsize'] = size_orig - url = amazon_url( item ) + url = amazon_url(item) <<-HTML <a class="amazon-detail" href="#{url}"><span class="amazon-detail"> <img class="amazon-detail left" src="#{h image[:src]}" height="#{h image[:height]}" width="#{h image[:width]}" alt=""> <span class="amazon-detail-desc"> <span class="amazon-title">#{h title}</span><br> <span class="amazon-author">#{h author}</span><br> - <span class="amazon-label">#{h amazon_label( item )}</span><br> - <span class="amazon-price">#{h amazon_price( item )}</span> + <span class="amazon-label">#{h amazon_label(item)}</span><br> + <span class="amazon-price">#{h amazon_price(item)}</span> </span> </span></a> HTML end -def amazon_to_html( item, with_image = true, label = nil, pos = 'amazon' ) +def amazon_to_html(item, with_image = true, label = nil, pos = 'amazon') with_image = false if @mode == 'categoryview' - author = amazon_author( item ) + author = amazon_author(item) author = "(#{author})" unless author.empty? - label ||= %Q|#{amazon_title( item )}#{author}| + 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 ) + image = amazon_image(item) unless image[:src] then img = '' else size = %Q|height="#{h image[:height]}" width="#{h image[:width]}"| img = <<-HTML @@ -258,57 +131,67 @@ HTML img.gsub!( /\t/, '' ) end end - url = amazon_url( item ) + url = amazon_url(item) %Q|<a href="#{h url}">#{img}#{h label}</a>| 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' +def amazon_get(asin, with_image = true, label = nil, pos = 'amazon') + asin = asin.to_s.strip.gsub(/-/, '') + country, item_id = asin.scan(/\A(..):(.*)/).flatten + unless country + country = @conf['amazon.default_country'] || @amazon_default_country + item_id = asin end begin cache = "#{@cache_path}/amazon" Dir::mkdir( cache ) unless File::directory?( cache ) begin - xml = File::read( "#{cache}/#{country}#{asin}.xml" ) + json = JSON.parse(File::read("#{cache}/#{country}#{item_id}.json")) rescue Errno::ENOENT - xml = amazon_call_ecs( asin, id_type, country ) - File::open( "#{cache}/#{country}#{asin}.xml", 'wb' ) {|f| f.write( xml )} + access_key = @conf['amazon.access_key'] + secret_key = @conf['amazon.secret_key'] + partner_tag = @conf['amazon.aid'] + paapi = AWS::PAAPI.new(access_key, secret_key, partner_tag) + json = paapi.get_items(item_id, country.to_sym) + File::open("#{cache}/#{country}#{item_id}.json", 'wb'){|f| f.write(json)} end - parser = defined?(Oga) ? :oga : :rexml - item = AmazonItem.new(xml, parser) + item = json["ItemsResult"]["Items"][0] if pos == 'detail' then - amazon_detail_html( item ) + amazon_detail_html(item) else - amazon_to_html( item, with_image, label, pos ) + amazon_to_html(item, with_image, label, pos) end + rescue Net::HTTPUnauthorized + p country, item_id + @logger.error "amazon.rb: Amazon API Unauthorized." + message = asin + if @mode == 'preview' then + message << %Q|<span class="message">(Amazon API Unauthorized))</span>| + end + message rescue Timeout::Error - @logger.error "amazon.rb: Amazon API Timeouted." + @logger.error "amazon.rb: PA-API Timeouted." message = asin if @mode == 'preview' then - message << %Q|<span class="message">(Amazon API Timeouted))</span>| + message << %Q|<span class="message">(PA-API Timeouted))</span>| end message + rescue Net::HTTPResponse, Net::HTTPExceptions => e + @logger.error "amazon.rb: #{e.message}" + message = label || asin + if @mode == 'preview' then + message << %Q|<span class="message">(#{h e.message})</span>| + end + message rescue NoMethodError + @logger.error "amazon.rb: #{json["Errors"][0]["Message"]}" message = label || asin if @mode == 'preview' then - if item.has_item? then - message << %Q|<span class="message">(#{h $!}\n#{h $@.join( ' / ' )})</span>| - else - m = item.nodes( 'Items/Request/Errors/Error/Message' )[0].text - message << %Q|<span class="message">(#{h @conf.to_native( m, 'utf-8' )})</span>| - end + message << %Q|<span class="message">(#{h json["Errors"][0]["Message"]})</span>| end message end end