Skip to content

Commit

Permalink
Merge fae8f99 into f741b5d
Browse files Browse the repository at this point in the history
  • Loading branch information
tdtds committed Nov 22, 2019
2 parents f741b5d + fae8f99 commit ab8257c
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 62 deletions.
65 changes: 65 additions & 0 deletions lib/aws/pa_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require 'aws/sig_v4'
require 'json'
require 'net/http'

module AWS
class PAAPI
Market = Struct.new(:host, :region)
MARKETS = {
au: Market.new('webservices.amazon.com.au', 'us-west-2'),
br: Market.new('webservices.amazon.com.br' 'us-east-1'),
ca: Market.new('webservices.amazon.ca', 'us-east-1'),
fr: Market.new('webservices.amazon.fr', 'eu-west-1'),
de: Market.new('webservices.amazon.de', 'eu-west-1'),
in: Market.new('webservices.amazon.in', 'eu-west-1'),
it: Market.new('webservices.amazon.it', 'eu-west-1'),
jp: Market.new('webservices.amazon.co.jp', 'us-west-2'),
mx: Market.new('webservices.amazon.com.mx', 'us-east-1'),
es: Market.new('webservices.amazon.es', 'eu-west-1'),
tr: Market.new('webservices.amazon.com.tr', 'eu-west-1'),
ae: Market.new('webservices.amazon.ae', 'eu-west-1'),
uk: Market.new('webservices.amazon.co.uk', 'eu-west-1'),
us: Market.new('webservices.amazon.com', 'us-east-1'),
}.freeze

def initialize(access_key, secret_key, partner_tag)
@access_key, @secret_key, @partner_tag = access_key, secret_key, partner_tag
end

def get_items(asin, locale)
payload = {
"PartnerTag" => @partner_tag,
"PartnerType" => "Associates",
"Marketplace" => MARKETS[locale].host.sub('webservices', 'www'),
"ItemIds" => [asin],
"Resources" => [
"Images.Primary.Small",
"Images.Primary.Medium",
"Images.Primary.Large",
"ItemInfo.ByLineInfo",
"ItemInfo.Title",
"Offers.Listings.Price"
]
}.to_json
time_stamp = Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
signature = AWS::SigV4.signature(@secret_key, time_stamp, MARKETS[locale].region, MARKETS[locale].host, payload)
authorization = "AWS4-HMAC-SHA256 Credential=#{@access_key}/#{time_stamp[0,8]}/#{MARKETS[locale].region}/ProductAdvertisingAPI/aws4_request, SignedHeaders=content-encoding;host;x-amz-content-sha256;x-amz-date;x-amz-target, Signature=#{signature}"

headers = {
"X-Amz-Target" => "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.GetItems",
"Content-Encoding" => "amz-1.0",
"Host" => MARKETS[locale].host,
"X-Amz-Date" => time_stamp,
"X-Amz-Content-Sha256" => OpenSSL::Digest::SHA256.hexdigest(payload),
"Authorization" => authorization,
"Content-Type" => "application/json; charset=utf-8"
}
uri = URI("https://#{MARKETS[locale].host}/paapi5/getitems")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.post(uri.path, payload, headers)
response.value # raise on errors
return response.body
end
end
end
45 changes: 45 additions & 0 deletions lib/aws/sig_v4.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# A Simple impl of AWS Signature V4 for PA-API GetItems
#
require 'openssl'

module AWS
module SigV4
def self.canonical_request(host, payload, time_stamp)
headers = {
'Content-Encoding' => 'amz-1.0',
'Host' => host,
'X-Amz-Content-Sha256' => OpenSSL::Digest::SHA256.hexdigest(payload),
'X-Amz-Date' => time_stamp,
'X-Amz-Target' => "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.GetItems",
}
[
"POST",
"/paapi5/getitems",
'',
headers.keys.sort.map{|k|"#{k.downcase}:#{headers[k]}"}.join("\n") + "\n",
headers.keys.sort.map{|k|k.downcase}.join(';'),
OpenSSL::Digest::SHA256.hexdigest(payload)
].join("\n")
end
def self.string2sign(host, payload, time_stamp)
[
'AWS4-HMAC-SHA256',
time_stamp,
"#{time_stamp[0,8]}/us-west-2/ProductAdvertisingAPI/aws4_request",
OpenSSL::Digest::SHA256.hexdigest(canonical_request(host, payload, time_stamp))
].join("\n")
end
def self.signature(secret, time_stamp, region, host, payload)
k_secret = secret
k_date = hmac("AWS4" + k_secret, time_stamp[0,8])
k_region = hmac(k_date, region)
k_service = hmac(k_region, 'ProductAdvertisingAPI')
k_credential = hmac(k_service, "aws4_request")
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), k_credential, string2sign(host, payload, time_stamp))
end
def self.hmac(key, value)
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
end
end
end
119 changes: 57 additions & 62 deletions misc/plugin/amazon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
# You can redistribute it and/or modify it under GPL2 or any later version.
#

require 'aws/pa_api'
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'
Expand Down Expand Up @@ -130,41 +130,29 @@ def amazon_call_ecs( asin, id_type, country )
end
end

def amazon_author( item )
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']
Expand All @@ -185,40 +173,36 @@ def amazon_image( item )
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]}"
Expand All @@ -227,27 +211,27 @@ def amazon_detail_html( item )
<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
Expand All @@ -260,11 +244,11 @@ def amazon_to_html( item, with_image = true, label = nil, pos = 'amazon' )
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' )
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
Expand All @@ -280,37 +264,48 @@ def amazon_get( asin, with_image = true, label = nil, pos = 'amazon' )
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
json = JSON.parse(File::read("#{cache}/#{country}#{asin}.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(asin, country.to_sym)
p json
File::open("#{cache}/#{country}#{asin}.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
@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
Expand Down

0 comments on commit ab8257c

Please sign in to comment.