# RelatedAmazonProducts.rb # # This is a direct port of my RelatedAmazonProduct pyBlosxom implementation. It's a little # cludgy because Ruby doesn't have native XML parsing capability as of yet, and I don't want # to rely on external libs that may or may not be maintained - I parse the REST response from # Amazon using some regex trickery instead. # # The only difference from my pyBlosxom plugin is that I don't rely on the images response # from amazon - I just use Amazon's image server and product ASINs. # # Uses your Amazon Web Services account to query products related to your Folksonomy Tags. This plugin # does not require Folksonomy, however if you are using Folksonomy, please get the latest version of it, # as there were some potential conlicts over the 'tags' metadata entry. # # Obtains a REST response from the following URL: # # http://webservices.amazon.com/onca/xml?Service=AWSECommerceService # &AWSAccessKeyId=config['aws_access_key'] # &AssociateTag=config['aws_associates_id'] # &SearchIndex=config['aws_product_type'] # &Operation=ItemSearch # &Keywords= # &Availability=Available # &Merchant=Amazon # [&Publisher=config['aws_favorite_pub']] # [&BrowseNode=config['amazon_browse_node']] # # Specify your AWS Access Key and the type of product to search for in your config.py, # like so (defaults are as shown here): # # config['aws_access_key']= # Your AWS Access key # config['aws_associates_id']= # Your Amazon Associates ID # config['aws_product_type']=Books # The type of product to search on. # config['aws_favorite_pub']= # Restricts selections to a particular publisher # config['story_product_count']=4 # The number of books to show on a page. # # I like to use MediumImage and then scale it down using CSS. The SmallImage returns a very low-res # picture from Amazon, and is useful if you don't have a lot of bandwidth. # # aws_product_type, aws_favorite_pub, and amazon_browse_node can be overridden on a per-story basis by defining # metadata with the same key name in your story. So for instance, if your config.py specifies an aws_product_type of "Books", # but you just posted about DVDs, you could override this setting for your story by doing: # # My Post on DVDs # #tags batman,superman,mighty mouse # #aws_product_type DVD #

DVDs are awesome.

# # Valid aws_product_type values are [ "Books","Music","DVD","Toys","Video Games","Software","Software Video Games", # "Electronics","Tools","Sporting Goods","Art Supplies","Kitchen","Gourmet Food", # "Apparel","PC Hardware","VHS" ] # # aws_product_type defautls to "Books" # # For a list of valid amazon browse nodes, please see: # http://www.amazon.com/gp/aws/sdk/main.html/002-5817805-8594421?s=AWSEcommerceService&v=2005-03-23&p=ApiReference/BrowseNodeValuesArticle # # My site uses BrowseNode 5, which is the code for Computing and Internet under Books in the US. # # Populates a varaible $relatedproducts for use in your story template, that will contain HTML like: # #
# # #
# # If you do not want to generate related products for a particular entry, add a "noproducts" metadata entry: # # My Entry with No Products # #noproducts require 'cgi' require 'net/http' class Product def initialize( asin,title,link,img,relevance ) @dict = {} @dict['asin'] = asin @dict['title'] = title @dict['link'] = link @dict['img'] = img @dict['relevance'] = relevance end def []( name ) @dict[name] end @dict end def cb_prepare( app ) config = app.getConfiguration return if not app.hasValue?('aws_access_key') app['story_product_count'] = 5 if not app.hasValue?('story_product_count') itemSearchUrl = "http://webservices.amazon.com/" itemSearchPath = "/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=" + config['aws_access_key'] + "&Operation=ItemSearch&Merchant=Amazon&ResponseGroup=Small&Sort=relevancerank" if app.hasValue?('aws_associates_id') itemSearchPath = itemSearchPath + "&AssociateTag=" + app.getValue('aws_associates_id') end app['itemsearch'] = itemSearchUrl app['itemsearchpath'] = itemSearchPath end # Looks up related products at amazon.com if we're viewing a product directly (i.e. through # it's /item/id path). def cb_story( app ) entry = app['entry'] config = app['config'] pathinfo = app['PATH_INFO'] return if not app.isPermalink?( pathinfo ) return if not app.hasValue?('aws_access_key',entry) or app.getValue('aws_access_key',entry).length == 0 return if entry.getTags.length == 0 or entry.getMetadata( 'noproducts' ) tags = entry.getTags() producttype = app.getValue( 'aws_product_type', entry ) producttype = 'Books' if not producttype publisher = app.getValue( 'aws_favorite_pub', entry ) publisher = "" if not publisher browseNode = app.getValue( 'amazon_browse_node', entry ) browseNode = "-1" if not browseNode products = [] tags.each{ |tag| products.concat( getProductsForTag( app, tag, producttype, publisher, browseNode ) ) } products = sortProducts( products ) relatedproductsheader = app.getValue('related_products_header',entry) relatedproductsheader = entry['related_products_header'] if entry['related_products_header'] if products and products.length > 0 products = products.slice(0, [products.length, app.getValue('story_product_count',entry)].min) relatedproducts = "
\n

"+relatedproductsheader+"

\n

" products.each{ |p| relatedproducts += "\n" % [ p['link'], p['img'] ] } relatedproducts += "

\n
" app['relatedproducts'] = relatedproducts end end # This is the meat and potatoes of how we determine which products to # show. We first sort based on how many tags a product appeared in. If # there is no one product that appeared in more than one tag, we resort # the whole shebang based on search relevance. def sortProducts( products ) ranked = {} products.each{ |p| if ranked.keys.member?(p['asin']) ranked[p['asin']] = [ ranked[p['asin']][0] + 1, p ] else ranked[p['asin']] = [ 1, p ] end } counts = [] ranked.each{ |asin,pair| counts << pair[0] if pair[0] } return if counts.min == nil if (2 > counts.min) ranked = {} products.each{ |p| ranked[p['asin']] = [ p['relevance'].to_i, p ] } ranked = ranked.values.sort{|a,b| a[0] - b[0]} else ranked = ranked.values.sort{|a,b| b[0] - a[0]} end products = [] ranked.each{ |r| products << r[1] } products end def getProductsForTag( app, tag, producttype, publisher, browsenode ) config = app.getConfiguration itemSearchUrl = app['itemsearch'] itemSearchPath = app['itemsearchpath'] + "&Keywords="+CGI::escape(tag)+"&SearchIndex="+producttype if ( publisher.length > 0 ): itemSearchPath = itemSearchPath + "&Publisher=" + publisher end if ( browsenode.to_i >= 0 ): itemSearchPath = itemSearchPath + "&BrowseNode=" + browsenode.to_s end url = URI::parse( itemSearchUrl ) res = Net::HTTP.start(url.host, url.port) {|http| http.get(itemSearchPath)} products = [] relevance = 0 xmlresp = res.body.gsub( "", "\n" ).gsub( "", "\n" ) xmlresp.each{ |line| asin = "" title = "" link = "" img = "" line.scan( /(.*)<\/ASIN>/ ).each{ |x| asin = x[0] } line.scan( /(.*)<\/Title>/ ).each{ |x| title = x[0] } line.scan( /<DetailPageURL>(.*)<\/DetailPageURL>/ ).each{ |x| link = x[0] } img = "http://images.amazon.com/images/P/" + asin + ".01.TZZZZZZZ.jpg" p = Product::new( asin,title,link,img,relevance ) if asin and asin.strip.length > 0 products << p relevance += 1 end } if xmlresp products end