/* * [BStore.java] * * Summary: Facts about various online bookstores. * * Copyright: (c) 2011-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2011-09-28 initial version */ package com.mindprod.stores; import com.mindprod.aws.jax.AWSECommerceService; import com.mindprod.aws.jax.AWSECommerceServicePortType; import com.mindprod.common18.Misc; import com.mindprod.common18.ST; import com.mindprod.entities.DeEntifyStrings; import com.mindprod.fastcat.FastCat; import com.mindprod.htmlmacros.macro.CountryFlag; import com.mindprod.http.Get; import com.mindprod.isbn.ISBNValidate; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import static com.mindprod.stores.BStoreConstants.*; import static com.mindprod.stores.Hint.*; import static com.mindprod.stores.StockStatus.NOTCARRIED; import static java.lang.System.*; /** * Facts about various online bookstores. *

* Amazon, except Amazon.es gets info via AWS SOAP interface. * Others get in by screenscraping. * BooksInStock.fireup triggers the check of bookstores to find out which books are in stock. * To add bookstore: * Book -- display link to store in HTML * BStore -- detail of links and probes, analysis of probe results. * BulkProber -- extra SET parms to control which stores probed. * Stock -- file to track in stock for this store. * e:\mindprod\embellishment\xxxxstock.dat track what stores have in inventory * * @author Roedy Green, Canadian Mind Products * @version 1.0 2011-09-28 initial version * @see Book * @see BulkProber for checkxxxx defininitions * @see Stock#reportUnusedClues * @since 2011-09-28 */ public enum BStore implements OnlineStore { ABEANZ( true /* alive */, "abe books anz", "Abe Books Australia and New Zealand", "https://", false, "www.abebooks.com", false /* pp */, false /* not for ebooks */, CountryFlag.Australia, null /* account */, INTERPROBE_DELAY_NONE, /* dummy probes need no delay */ /* no clues, use abe.com */ 0, null, 0, 24_000 ) { public String buildProductLink( final String isbn13 ) { validateIsbn13( isbn13 ); return buildAbeProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAbeQueryLink( title, author ); } /** * is a given product in stock? * * @param isbn13 product number * @param probe * @return true if the product is in stock. */ public StockStatus getProductStatus( String isbn13, final boolean probe ) { validateIsbn13( isbn13 ); // we don't maintain a separate inventory, we borrow AEBCOM's return ABECOM.getProductStatus( isbn13, true ); } /** * Probe this bookstore to see if it carries the given isbn/product. * * @param isbn13 ISBN of book to check * * @return does bookstore carry this book 0=no 1=yes 2=don't know 3=store is refusing probes. */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); out.println( " dummy probe" ); return ABECOM.getProductStatus( isbn13, true ); } }, ABECA( true, "abe books.ca", "Abe Books Canada", "https://", false, "www.abebooks.com", false, false /* not for ebooks */, CountryFlag.Canada, null, INTERPROBE_DELAY_NONE, /* dummy probes need no delay */ 0, null, /* no clues, use USA status */ 0, 24_000 ) { public String buildProductLink( final String isbn13 ) { return buildAbeProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAbeQueryLink( title, author ); } /** * is a given product in stock? * * @param isbn13 product number * * @param probe * @return status of the product stock */ public StockStatus getProductStatus( String isbn13, final boolean probe ) { // we don't maintain a separate inventory, we borrow AEBCOM's return ABECOM.getProductStatus( isbn13, true ); } /** * Probe this bookstore to see if it carries the given isbn/product. * * @param isbn13 ISBN of book to check * * @return does bookstore carry this book 0=no 1=yes 2=don't know 3=store is refusing probes. */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); out.println( " dummy probe" ); return ABECOM.getProductStatus( isbn13, true ); } }, ABECOM( true, "abe books.com", "Abe Books USA", "https://", false, "www.abebooks.com", false, false /* not for ebooks */, CountryFlag.USA, null, INTERPROBE_DELAY_FOR_ABE, 0, new Clue[] { new Clue( MUST, "\"price\">US$ " ), // >Quantity Available: > 20< >Quantity Available: 10< new Clue( MUST, "regex:>Quantity Available:\\s*(?:>)?\\s*([1-9]|[1-9][0-9])<" ), new Clue( UNLISTED, ">We're sorry, no results were found for the search terms.<" ), }, 0, 24_000 ) { public String buildProductLink( final String isbn13 ) { return buildAbeProductLink( isbn13 ); } public String buildProbeLink( final String isbn13 ) { return buildAbeProbeLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAbeQueryLink( title, author ); } /** Probe abe store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, ABEDE( true, "abe books.de", "Abe Books Germany", "https://", false, "www.abebooks.de", false, false /* not for ebooks */, CountryFlag.Germany, null, INTERPROBE_DELAY_FOR_ABE_DE, 0, new Clue[] { new Clue( IN, "regex:>Anzahl: [1-9][0-9]*<" ), new Clue( MUST, "\"price\">EUR " ), new Clue( UNLISTED, ">Keine Suchergebnisse<" ), new Clue( UNLISTED, "Leider konnten keine Treffer f\u00fcr folgende Suchbegriffe gefunden werden" ), }, 0, 23_000 ) { public String buildProductLink( final String isbn13 ) { return buildAbeProductLink( isbn13 ); } public String buildProbeLink( final String isbn13 ) { return buildAbeProbeLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAbeQueryLink( title, author ); } /** Probe abede store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, ABEES( true, "iberlibro.com", "Iberlibro, Abe Books Spain", "https://", false, "www.iberlibro.com", false, false /* not for ebooks */, CountryFlag.Spain, null, INTERPROBE_DELAY_FOR_ABE, 0, new Clue[] { new Clue( IN, "ISBN 13: EUR " ), }, 0, 23_000 )// zanox rejected us { public String buildProductLink( final String isbn13 ) { return buildAbeProductLink( isbn13 ); } public String buildProbeLink( final String isbn13 ) { return buildAbeProbeLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAbeQueryLink( title, author ); } /** Probe abees store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, ABEFR( true, "abe books.fr", "Abe Books France", "https://", false, "www.abebooks.fr", false, false /* not for ebooks */, CountryFlag.France, null, INTERPROBE_DELAY_FOR_ABE, 0, new Clue[] { new Clue( MUST, "\"price\">EUR " ), new Clue( UNLISTED, "Nous sommes d\u00e9sol\u00e9s ; aucun r\u00e9sultat n'a " + "\u00e9t\u00e9 trouv\u00e9 pour les termes que vous avez saisis" ), }, 0, 23_0000 ) // zanox rejected us { public String buildProductLink( final String isbn13 ) { return buildAbeProductLink( isbn13 ); } public String buildProbeLink( final String isbn13 ) { return buildAbeProbeLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAbeQueryLink( title, author ); } /** Probe abefr store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, ABEIT( true, "abe books.it", "Abe Books Italy", "https://", false, "www.abebooks.it", false, false /* not for ebooks */, CountryFlag.Italy, null, INTERPROBE_DELAY_FOR_ABE, 0, new Clue[] { new Clue( MUST, "\"price\">EUR " ), new Clue( UNLISTED, "Siamo spiacenti, non ci sono risultati per i criteri di ricerca " + "inseriti" ), }, 0, 23_000 ) // zanox rejected us { public String buildProductLink( final String isbn13 ) { return buildAbeProductLink( isbn13 ); } public String buildProbeLink( final String isbn13 ) { return buildAbeProbeLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAbeQueryLink( title, author ); } public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, ABEUK( true, "abe books.co.uk", "Abe Books UK", "https://", false, "www.abebooks.co.uk", false, false /* not for ebooks */, CountryFlag.UK, null, INTERPROBE_DELAY_FOR_ABE, 0, new Clue[] { new Clue( IN, "Book Description:" ), new Clue( MUST, "price\">\u00a3 " ), new Clue( UNLISTED, "no results were found" ), }, 0, 23_000 ) { public String buildProductLink( final String isbn13 ) { return buildAbeProductLink( isbn13 ); } public String buildProbeLink( final String isbn13 ) { return buildAbeProbeLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAbeQueryLink( title, author ); } /** Probe abeuk store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, AMAZONCA( true, "amazon.ca", "Amazon Canada", "https://", true, "www.amazon.ca", false, false /* not for ebooks */, CountryFlag.Canada, CONFIGURATION.getAccountForAmazonCa(), INTERPROBE_DELAY_FOR_AMAZON_CA, 330641, null, 0, 15121 ) { public String buildProductLink( final String isbn13 ) { return buildAmazonProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortCA(); } /** * Is product in stock? * @param product isbn or asin * * @param probe * @return true if in stock. */ public StockStatus getProductStatus( final String product, final boolean probe ) { return getAmazonProductStatus( product ); } public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.awsProbe( this, asin ); } }, AMAZONCOM( true, "amazon.com", "Amazon USA", "https://", true, "www.amazon.com", false, false /* not for ebooks */, CountryFlag.USA, CONFIGURATION.getAccountForAmazonCom(), BStoreConstants.INTERPROBE_DELAY_FOR_AMAZON_COM, 9325, null, 0, 1789 ) { public String buildProductLink( final String isbn13 ) { return buildAmazonProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePort(); } /** * Is product in stock? * @param product isbn or asin * * @param probe * @return true if in stock. */ public StockStatus getProductStatus( final String product, final boolean probe ) { return getAmazonProductStatus( product ); } public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.awsProbe( this, asin ); } }, AMAZONDE( true, "amazon.de", "Amazon Germany", "https://", true, "www.amazon.de", false, false /* not for ebooks */, CountryFlag.Germany, CONFIGURATION.getAccountForAmazonDe(), BStoreConstants.INTERPROBE_DELAY_FOR_AMAZON_GENERIC, 6742, null, 0, 1638 ) { public String buildProductLink( final String isbn13 ) { return buildAmazonProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortDE(); } /** * Is product in stock? * @param product isbn or asin * * @param probe * @return true if in stock. */ public StockStatus getProductStatus( final String product, final boolean probe ) { return getAmazonProductStatus( product ); } public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.awsProbe( this, asin ); } }, AMAZONES( true, "amazon.es", "Amazon Spain", "https://", true, "www.amazon.es", false, false /* not for ebooks */, CountryFlag.Spain, CONFIGURATION.getAccountForAmazonEs(), BStoreConstants.INTERPROBE_DELAY_FOR_AMAZON_GENERIC, 6746, null, 0, 1624 ) { public String buildProductLink( final String isbn13 ) { return buildAmazonProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortES(); } /** * Is product in stock? * @param product isbn or asin * * @param probe * @return true if in stock. */ public StockStatus getProductStatus( final String product, final boolean probe ) { return getAmazonProductStatus( product ); } public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.awsProbe( this, asin ); } }, AMAZONFR( true, "amazon.fr", "Amazon France", "https://", true, "www.amazon.fr", false, false /* not for ebooks */, CountryFlag.France, CONFIGURATION.getAccountForAmazonFr(), BStoreConstants.INTERPROBE_DELAY_FOR_AMAZON_GENERIC, 6746, null, 0, 1642 ) { public String buildProductLink( final String isbn13 ) { return buildAmazonProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortFR(); } /** * Is product in stock? * @param product isbn or asin * * @param probe * @return true if in stock. */ public StockStatus getProductStatus( final String product, final boolean probe ) { return getAmazonProductStatus( product ); } public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.awsProbe( this, asin ); } }, AMAZONIT( true, "amazon.it", "Amazon Italy", "https://", true, "www.amazon.it", false, false /* not for ebooks */, CountryFlag.Italy, CONFIGURATION.getAccountForAmazonIt(), BStoreConstants.INTERPROBE_DELAY_FOR_AMAZON_GENERIC, 23322, null, 0, 3370 ) { public String buildProductLink( final String isbn13 ) { return buildAmazonProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortIT(); } /** * Is product in stock? * @param product isbn or asin * * @param probe * @return true if in stock. */ public StockStatus getProductStatus( final String product, final boolean probe ) { return getAmazonProductStatus( product ); } public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.awsProbe( this, asin ); } }, AMAZONUK( true, "amazon.co.uk", "Amazon UK", "https://", true, "www.amazon.co.uk", false, false /* not for ebooks */, CountryFlag.UK, CONFIGURATION.getAccountForAmazonUk(), BStoreConstants.INTERPROBE_DELAY_FOR_AMAZON_GENERIC, 6738, null, 0, 1634 ) { public String buildProductLink( final String isbn13 ) { return buildAmazonProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortUK(); } /** * Is product in stock? * @param product isbn or asin * * @param probe * @return true if in stock. */ public StockStatus getProductStatus( final String product, final boolean probe ) { return getAmazonProductStatus( product ); } public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.awsProbe( this, asin ); } }, JUNGLEE( /* amazon india, affiliates for Indians only */ true, "junglee.com", "Junglee (Amazon in India)", "https://", false, "www.junglee.com", false, false /* not for ebooks */, CountryFlag.India, CONFIGURATION.getAccountForJungleeCom(), BStoreConstants.INTERPROBE_DELAY_FOR_JUNGLEE, /* Theoretically supports SOAP AWS, but cannot get it to work */ /* shares stock with other Amazon stores. Stock in ASIN not ISBN */ 0, new Clue[] { new Clue( CHOP_TAIL, "Explore More Items Similar" ), new Clue( IN, "class=\"formatHeader\"" ), new Clue( IN, "class=\"offer-price-lowest-price mrs\"" ), new Clue( IN, "class=\"offerPrice\"" ), new Clue( MUST, "class=\"whole-price\"" ), new Clue( OUT5, "This product is not available from any of our online sellers" ), }, 55_000, 0 ) { public String buildProbeLink( final String asin ) { return buildAmazonManualProbeLink( asin ); } public String buildProductLink( final String isbn13 ) { return buildAmazonProductLink( isbn13 ); } public String buildQueryLink( final String title, final String author ) { return buildAmazonQueryLink( title, author ); } /** not used. Will if India AWS starts working */ public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return service.getAWSECommerceServicePortIN(); } /** * Is product in stock? * @param product isbn or asin * * @param probe * @return true if in stock. Internally stored as asin. */ public StockStatus getProductStatus( final String product, final boolean probe ) { return getAmazonProductStatus( product ); } /** Probe junglee store to see if product in stock, don't use AWS * * @param asin of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String asin ) { validateAsin( asin ); return BulkProber.screenScrapeProbe( this, asin, buildProbeLink( asin ) ); } }, BN( true, "Barnes & Noble", "Barnes & Noble", "https://", true, "www.barnesandnoble.com", true /* paypal */, false, CountryFlag.USA, CONFIGURATION.getAccountForBarnesAndNoble(), BStoreConstants.INTERPROBE_DELAY_FOR_BN, 0, new Clue[] { // partly duplicated in NOOK new Clue( CHOP_LEAD, "" ), new Clue( CHOP_TAIL, "Customers Who Bought This Item Also Bought" ), new Clue( FREEZE, "Yikes! We’re having technical difficulties. Please try again shortly." ), new Clue( IN, ">Item is available through our marketplace sellers and in stores.<" ), new Clue( IN, ">Item is available through our marketplace sellers.<" ), new Clue( IN, "Add to Bag" ), new Clue( IN, "\"outOfStock\":false" ), new Clue( IN, "regex:

\\s*" + DOLLAR_PRICE + "" ), new Clue( IN, "regex:[1-9][0-9]? New & Used from\\s*" + DOLLAR_PRICE + "" ), new Clue( LISTED, "ean=xxx\">Audiobook" ), new Clue( LISTED, "ean=xxx\">Hardcover" ), new Clue( LISTED, "ean=xxx\">Paperback" ), new Clue( LISTED, "regex:itemprop=\"price\">\\s*" + DOLLAR_PRICE + "" ), new Clue( LISTED, "regex:itemscope itemtype=\"http://schema.org/Offer\">\\s*" + DOLLAR_PRICE + "" ), new Clue( OUT5, "Temporarily Out of Stock Online" ), new Clue( UNLISTED2, "0 results" ), // has schema, but not Instock/OutOfStock }, 0, 93_000 ) { /** Barnes & Noble quick probe * * @param isbn13 Isbn * @return URL to probe with */ public String buildProbeLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/w/?ean=", isbn13 ); return sb.toString(); } /** * linksynergy stopped working * @param isbn13 isbn of book * @return link for book macro */ public String buildProductLink( String isbn13 ) { return buildProbeLink( isbn13 ); } /** * query link, for general query for Barnes & Noble * @param title title of book * @param author author of this book * @return HTML for URL to link. */ public String buildQueryLink( final String title, final String author ) { final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/s/" ); sb.append( prepareQueryField( title + " by " + author ) ); return sb.toString(); } /** Probe bn store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, CHAPTERS( true, "Chapters Indigo", "Chapters Indigo", "https://", true, "www.chapters.indigo.ca", true, false /* not for ebooks */, CountryFlag.Canada, CONFIGURATION.getAccountForChaptersIndigo(), BStoreConstants.INTERPROBE_DELAY_NONE, 0, new Clue[] { // clues are case-sensitive, unless the regex specifies otherwise. new Clue( IN, "Available for download" ), new Clue( IN, "Ships within 1-2 weeks" ), new Clue( IN, "http://schema.org/LimitedAvailability" ), new Clue( IN2, "In stock online" ), new Clue( IN2, "regex:We found (?:[1-9]|[1-9][0-9]) results? for “[\\d]+”" ), new Clue( OUT2, ">Sold Out<" ), new Clue( OUT2, "Pre-order online" ), new Clue( OUT2, "http://schema.org/OutOfStock" ), new Clue( OUT2, "http://schema.org/PreOrder" ), new Clue( OUT5, "Out of stock online" ), }, 0, 290_000 ) { /** * probe for chapters * * @param isbn13 INbn * @return code for link */ public String buildProbeLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 5 ); sb.append( getProtocol(), getHost() ); sb.append( "/en-ca/books/search/?keywords=" ); sb.append( isbn13 ); sb.append( "&os=Books&isFallback=true" ); return sb.toString(); } /** build link to product from web page * * @param isbn13 book number * @return URL tc go to that page of Chapters site */ public String buildProductLink( String isbn13 ) { return buildProbeLink( isbn13 ); } /** * query link, for general query for Chapters Indigo * @param title title of book * @param author author of this book * @return HTML for URL to link. */ public String buildQueryLink( final String title, final String author ) { final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/en-ca/home/search?SearchBoxKeywords=" ); sb.append( prepareQueryField( title + " by " + author ) ); return sb.toString(); } /** Probe chapters store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, CHAPTERSEBOOKS( true, "Chapters Indigo eBooks", "Chapters Indigo eBooks", "https://", true, "www.chapters.indigo.ca", true /* takes paypal */, true /* for ebooks */, CountryFlag.Canada, CONFIGURATION.getAccountForChaptersIndigo(), BStoreConstants.INTERPROBE_DELAY_NONE, 0, new Clue[] { // clues are case-sensitive, unless the regex specifies otherwise. new Clue( IN, "Available for download" ), new Clue( IN, "Ships within 1-2 weeks" ), new Clue( IN, "http://schema.org/LimitedAvailability" ), new Clue( IN2, "In stock online" ), new Clue( IN2, "regex:We found (?:[1-9]|[1-9][0-9]) results? for “[\\d]+”" ), new Clue( LISTED, "buy eBook" ), new Clue( MUST3, ">Kobo ebook<" ), new Clue( MUST3, ">buy ebook<" ), new Clue( OUT2, ">Sold Out<" ), new Clue( OUT2, "Pre-order online" ), new Clue( OUT2, "http://schema.org/OutOfStock" ), new Clue( OUT2, "http://schema.org/PreOrder" ), new Clue( OUT5, "Out of stock online" ), }, 0, 290_000 ) { /** * probe for chapters * * @param isbn13 INbn * @return code for link */ public String buildProbeLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 5 ); sb.append( getProtocol(), getHost() ); sb.append( "/en-ca/home/search/?keywords=" ); sb.append( isbn13 ); sb.append( "&os=Books&isFallback=true" ); return sb.toString(); } /** build link to product from web page * * @param isbn13 book number * @return URL tc go to that page of Chapters site */ public String buildProductLink( String isbn13 ) { return buildProbeLink( isbn13 ); } /** * query link, for general query for Chapters Indigo * @param title title of book * @param author author of this book * @return HTML for URL to link. */ public String buildQueryLink( final String title, final String author ) { final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "en-ca/home/search?SearchBoxKeywords=" ); sb.append( prepareQueryField( title + " by " + author ) ); return sb.toString(); } /** Probe chapters store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, // used to be books.google.com. Now play.google.com // sells ebooks listed by paperback ISBN. GOOGLE( true, "Google play", "Google Play", "https://", false, "play.google.com", false /* no paypal */, true /* for ebooks */, CountryFlag.USA, null /* account */, BStoreConstants.INTERPROBE_DELAY_FOR_GOOGLE, 0, new Clue[] { new Clue( CHOP_LEAD, "" + DOLLAR_PRICE + "" ), new Clue( UNLISTED, "We couldn't find anything for your search" ), }, 0, 1_300 ) { /** * fast probe link to google online book * * @param isbn13 book number * @return url */ public String buildProbeLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 5 ); sb.append( getProtocol(), getHost() ); sb.append( "/store/search?q=", isbn13 ); sb.append( "&c=books" ); return sb.toString(); } /** * monetised link to Google book * * @param isbn13 book numer * @return url */ public String buildProductLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 5 ); sb.append( getProtocol(), getHost() ); sb.append( "/store/search?q=", isbn13 ); sb.append( "&c=books" ); return sb.toString(); } /** * query link, for general query for Google play * @param title title of book * @param author author of this book * @return HTML for URL to link. */ public String buildQueryLink( final String title, final String author ) { final FastCat sb = new FastCat( 4 ); // https://play.google.com/store/search?q=The+Secret+Life+of+Plants+by+Peter+Tompkins+and+Christopher+Bird sb.append( getProtocol(), getHost() ); sb.append( "/store/search?q=" ); sb.append( prepareQueryField( title + " by " + author ) ); return sb.toString(); } /** Probe google store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }, KOBO( true, "Kobo", "Kobo (eBooks only)", "https://", true, "store.kobobooks.com", true, true /* for ebooks */, CountryFlag.USA, CONFIGURATION.getAccountForKobo(), BStoreConstants.INTERPROBE_DELAY_FOR_KOBO, 0, new Clue[] { new Clue( IN, "regex:

\\s*" + DOLLAR_PRICE + "\\s*

" ), new Clue( IN, "
  • ISBN: xxx
  • " ), new Clue( IN, "regex:" ), new Clue( IN2, "" ), new Clue( UNLISTED, ">0 results for" ), new Clue( UNLISTED, "Sorry! Your search for" ), new Clue( UNLISTED, "did not return any results." ), }, 0, 135_000 ) { /** * fast probe link to kobo book * * @param isbn13 book number * @return url */ public String buildProbeLink( final String isbn13 ) { // can start with 123. Do not validate. final FastCat sb = new FastCat( 4 ); // https://store.kobobooks.com/en-ca/Search/Query?q=9780060000738 sb.append( getProtocol(), getHost(), "/en-ca/Search/Query?q=", isbn13 ); return sb.toString(); } /** * monetised link to kobo book * * @param isbn13 book numer * @return url */ public String buildProductLink( final String isbn13 ) { // can start with 123. Do not validate. final FastCat sb = new FastCat( 4 ); // Deep Linking probe embeded in probe: // https://store.kobobooks.com/en-ca/Search/Query?q=9780060000738 // http://click.linksynergy.com/fs-bin/click?id=vSCiQT/Cnuo&subid=&offerid=360582.1&type=10&tmpid=9387 // &RD_PARM1=https%3A%2F%2Fstore.kobobooks.com%2Fen-ca%2FSearch%2FQuery%3Fq%3D9780060000738 sb.append( "http://click.linksynergy.com/fs-bin/click?id=" ); sb.append( getAccount() ); sb.append( "&subid=&offerid=360582.1&type=10&tmpid=9387&" + "RD_PARM1=https%3A%2F%2Fstore.kobobooks.com%2Fen-ca%2FSearch%2FQuery%3Fq%3D" ); sb.append( isbn13 ); return sb.toString(); } /** * query link, for general query for Kobo * @param title title of book * @param author author of this book * @return HTML for URL to link. */ public String buildQueryLink( final String title, final String author ) { // e.g. http://store.kobobooks.com/Search?Query=20%2C001+ final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost(), "/Search?Query=" ); sb.append( prepareQueryField( title + " by " + author ) ); return sb.toString(); } /** Probe Kobo store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } /** * do we need to suppress cookie errors * @return true if need to suppress cookie errors */ public boolean suppressCookieErrors() { return true; } }, NOOK( true, "Nook at Barnes & Noble", "Nook at Barnes & Noble", "https://", true, "www.barnesandnoble.com", true /* paypal */, true /* for ebooks */, CountryFlag.USA, CONFIGURATION.getAccountForBarnesAndNoble(), BStoreConstants.INTERPROBE_DELAY_FOR_BN, 0, new Clue[] { // partly duplicated in BN // only ebooks will be filtered here // new Clue( IN, "\"outOfStock\":false" ), cannot be trusted for ebooks // new Clue( OUT5, "\"outOfStock\":true" ), cannot be trusted for ebooks // we are trying to weed out most of the books and take the ebooks. // If we accept a book, nothing awful happens. It will just not be used. new Clue( CHOP_LEAD, "" ), new Clue( CHOP_TAIL, "Customers Who Bought This Item Also Bought" ), new Clue( FREEZE, "Yikes! We’re having technical difficulties. Please try again shortly." ), new Clue( IN, "
    xxx
    " ), new Clue( IN, "

    NOOK Book

    " ), new Clue( IN, ">Item is available through our marketplace sellers and in stores.<" ), new Clue( IN, ">Item is available through our marketplace sellers.<" ), new Clue( IN, "Add to Bag" ), new Clue( IN, "regex:

    \\s*" + DOLLAR_PRICE + "" ), new Clue( IN, "regex:itemprop=\"price\">\\s*" + DOLLAR_PRICE + "" ), new Clue( IN2, "ean=xxx\">NOOK Book" ), new Clue( UNLISTED2, "0 results" ), }, 0, 93_000 ) { /** nook quick probe * * @param isbn13 Isbn * @return URL to probe with */ public String buildProbeLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/w/?ean=", isbn13 ); return sb.toString(); } /** * linksynergy stopped working * @param isbn13 isbn of book * @return link for book macro */ public String buildProductLink( String isbn13 ) { return buildProbeLink( isbn13 ); } /** * query link, for general query for Barnes & Noble * @param title title of book * @param author author of this book * @return HTML for URL to link. */ public String buildQueryLink( final String title, final String author ) { final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/s/" ); sb.append( prepareQueryField( title + " by " + author ) ); return sb.toString(); } /** Probe bn store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book */ public StockStatus probe( final String isbn13 ) { // this is an ebook. It can't be outofstock final StockStatus stockStatus = BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); return ( stockStatus == StockStatus.OUTOFSTOCK ) ? StockStatus.INSTOCK : stockStatus; } }, POWELLS( true, "Powells", "Powells", "http://", false, "www.powells.com", true, false /* not for ebooks */, CountryFlag.USA, CONFIGURATION.getAccountForPowells(), BStoreConstants.INTERPROBE_DELAY_FOR_POWELLS, 0, new Clue[] { new Clue( LISTED, "regex:Ships in [\\p{Digit}]+ to [\\p{Digit}]+ days" ), new Clue( MUST, "regex:

    \\s+" + DOLLAR_PRICE + "
    " ), new Clue( OUT2, "regex:
    \\s+0.00
    " ), // schema.org possibilities no longer used. }, 0, 150_000 ) { public String buildProbeLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 5 ); // http://www.powells.com/book/-9781583672129/?p_isbn new format sb.append( getProtocol(), getHost() ); sb.append( "/book/-", isbn13, "?p_isbn" ); return sb.toString(); } public String buildProductLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 7 ); // http://www.powells.com/book/-9781583672129/?p_isbn&partnerid=28995 new format sb.append( getProtocol(), getHost() ); sb.append( "/book/-", isbn13, "?p_isbn" ); sb.append( "&partnerid=", getAccount() ); return sb.toString(); } /** * query link, for general query for Powells * @param title title of book * * @param author author of this book * @return HTML for URL to link. */ public String buildQueryLink( String title, final String author ) { // Powells chokes on ’ title = title.replace( '\u2019' /* ’ */, '\'' ); final FastCat sb = new FastCat( 9 ); // split author into its own field // http://www.powells.com/SearchResults?kw=any_words:The+End+of+the+line&au=Joe+Smith&bi=Dvd sb.append( getProtocol(), getHost() ); sb.append( "/PartnerSearchResult.aspx?p_kw" ); sb.append( "&partnerID=", this.getAccount(), "&kw=any_words:" ); sb.append( prepareQueryField( title ) ); sb.append( "&au=" ); sb.append( prepareQueryField( author ) ); return sb.toString(); } /** Probe Kobo store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); // this is an ebook. It can't be outofstock final StockStatus stockStatus = BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); return ( stockStatus == StockStatus.OUTOFSTOCK ) ? StockStatus.INSTOCK : stockStatus; } }, SAFARI( // tracks Safari when probed with isbn13 IN PROCESS OF FLIPPING TO SSL. MAY NOT WORK EITHER WAY // https: is not working true, "O’Reilly Safari", "O’Reilly Safari", "https://", true, "www.safaribooksonline.com", false, false /* not for ebooks */, CountryFlag.USA, null, BStoreConstants.INTERPROBE_DELAY_NONE, 0, new Clue[] { new Clue( IN, "ISBN-10:" ), new Clue( IN, "ISBN-13:" ), new Clue( IN, "Overview:" ), new Clue( IN, "Print ISBN:" ), new Clue( IN, "Start Reading" ), new Clue( IN, "Web ISBN:" ), new Clue( MUST, "$" ), new Clue( NEWER, ">Most Recent Edition<" ), }, 0, 34_000 ) { /** Probe O'Reilly Safari with 13-char ISBN * * @param isbn13 isbn * @return url to probe a book */ public String buildProductLink( final String isbn13 ) { // http://my.safaribooksonline.com/9781449311520 validateIsbn13( isbn13 ); return buildProbeLink( isbn13 ); } public String buildProbeLink( final String isbn13 ) { validateIsbn13( isbn13 ); final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/", isbn13 ); return sb.toString(); } /** * query link, for general query for Safari 13 * @param title title of book * * @param author author of this book * @return HTML for URL to link. */ public String buildQueryLink( final String title, final String author ) { return buildSafariQueryLink( title, author ); } /** Probe SAFARI store to see if product in stock * * @param isbn13 of product * * @return does bookstore carry this book 0=no 1=yes 2=don't know */ public StockStatus probe( final String isbn13 ) { validateIsbn13( isbn13 ); return BulkProber.screenScrapeProbe( this, isbn13, buildProbeLink( isbn13 ) ); } }; /** * all the abe stores */ static final EnumSet ABE_BOOKSTORES = EnumSet.range( BStore.ABEANZ, BStore.ABEUK ); /** * all the Amazon bookstores */ static final EnumSet AMAZON_BOOKSTORES = EnumSet.range( BStore.AMAZONCA, BStore.JUNGLEE ); /** * all the non Amazon non Abe bookstores, the third range. */ static final EnumSet OTHER_BOOKSTORES = EnumSet.range( BStore.BN, BStore.SAFARI ); /** * max number of refused probes in a row from a store before we give up on it for this session */ private static final int MAX_ALLOWABLE_PROBES_REFUSED = 20; /** * max number stalls for a store before we give up restarting it. */ private static final int MAX_ALLOWABLE_STALLS = 9; /** * only applies to electronic stores. Just a field needed. Who knows why. */ private final int camp; /** * only applies to electronic stores. Just a field needed. Who knows why. */ private final int creative; /** * how much time we must allow between probes to this bookstore, in ms */ private final long interProbeTime; /** * our account id with this bookstore used to mark our sales */ private final String account; /** * true if this store takes paypal */ private final boolean takesPayPal; /** * bookstoreName of host with entities */ private final String decoratedBookstoreName; /** * domain to go to, without leading http:// ee.g. www.amazon.com */ private final String host; /** * either http:// or https:// */ private final String protocol; /** * bookstoreName of host without entities */ private final String undecoratedBookstoreName; /** * text appearing on a page that carries this book for this bookstore */ private final Clue[] clues; /** * flag to use to represent the bookstore */ private final CountryFlag flag; /** * books we will probe for this bookstore to see if they are instock */ private final HashSet productsYetToProbe = new HashSet<>( 2_000 ); private final int minimumResponseLength; /** * true if this store sells only ebooks **/ private final boolean onlyEbooks; private final boolean isAlive; private final boolean sni; /** * count of books recently added to this store */ private int productsAdded = 0; /** * count of books recently dropped from this store */ private int productsDropped = 0; /** * count of refused probes */ private volatile int refused = 0; // /declarations /** * 1=should check this store, 0=should not check, -1= don't know yet */ private int shouldCheck = -1; /** * number 1..31 assigned by stock to indicate this store. */ private int stockOrdinal; /** * time stamp of when last probe completed. */ private long lastProbeCompleted = 0; /** * which stock product/numbering-inventory to use */ private Stock stock; /** * Get connection in progress, null if none */ private Get getInProgress; /** * countdown of how many stalls/restarts. When hit 0, we give up */ private int stallsToGo = MAX_ALLOWABLE_STALLS; /** * last product probed, not necessary completed. */ private String lastProductProbed; /** * track how complete probe is 0 .. 7 */ private int probeCompletionStage = 0; /** * constructor * * @param forEbook true if this store sells only eBooks. * @param isAlive true if this store is functioning * @param decoratedBookstoreName name of store with entities * @param undecoratedBookstoreName name of store without entities * @param protocol either http:// or https:// * @param sni sni true/false * @param host URL of host we link to, without leading http:// * @param takesPaypal class of link * @param flag which flag tco display * @param account account used to track sales. * @param interProbeTime time between probes to this bookstore in ms * @param creative mysterious Amazon link code * @param markers clues whose presence indicates book in in or out of stock * @param minimumResponseLength minumum acceptable response before we parse * @param camp mysterious Amazon link code */ BStore( final boolean isAlive, final String decoratedBookstoreName, final String undecoratedBookstoreName, final String protocol, final boolean sni, final String host, final boolean takesPaypal, final boolean onlyEbooks, final CountryFlag flag, final String account, final long interProbeTime, final int creative, final Clue[] markers, final int minimumResponseLength, final int camp ) { this.isAlive = isAlive; this.decoratedBookstoreName = decoratedBookstoreName; assert undecoratedBookstoreName != null : "null undecoratedBookstoreName "; this.undecoratedBookstoreName = undecoratedBookstoreName; this.protocol = protocol; this.sni = sni; this.host = host; this.takesPayPal = takesPaypal; this.onlyEbooks = onlyEbooks; this.flag = flag; this.account = account; this.interProbeTime = interProbeTime; this.clues = markers == null ? new Clue[ 0 ] : markers; this.minimumResponseLength = minimumResponseLength; this.camp = camp; this.creative = creative; } /** * prepare title, author, description for use in a general query * * @param description title or author of the book * * @return endocoded field */ private static String prepareQueryField( final String description ) { try { return URLEncoder.encode( DeEntifyStrings.deEntifyHTML( description, ' ' ), "UTF-8" ); } catch ( UnsupportedEncodingException e ) { err.println( "UTF-8 not supported" ); System.exit( 2 ); return null; } } /** * print count of how many books each store has in stock. This helps detect faulty hints */ private static void printCountsOfItemsInStock() { if ( !Misc.parseBoolean( System.getenv( "showcountsofbooksinstock" ), false ) ) { return; } out.println( "B O O K S I N S T O C K" ); Stock.printCountsOfItemsInStock( BStore.values() ); out.println( "Use to update E:\\mindprod\\jgloss\\snippet\\storeranks.txt\n" ); }// /method /** * Report unused clues. This helps detect faulty hints() */ private static void reportUnusedClues() { if ( Misc.parseBoolean( System.getenv( "reportunusedclues" ), false ) || Misc.parseBoolean( System.getenv( "checkstores" ), false ) || Misc.parseBoolean( System.getenv( "checkbookstores" ), false ) ) { out.println( "U N U S E D B O O K C L U E S" ); Stock.reportUnusedClues( BStore.values() ); } }// /method /** * check that a string is a asin * * @param asin number */ private static void validateAsin( String asin ) { // kindles start with B00 // books start with 0 // audio start with 1 // some kindles start with 0 assert !ST.isEmpty( asin ) && asin.length() == 10 : "bad asin [" + asin + "]"; }// /method /** * check that a string is a isbn13 * * @param isbn13 book number */ private static void validateIsbn13( String isbn13 ) { assert !ST.isEmpty( isbn13 ) && isbn13.length() == 13 && ( isbn13.startsWith( "123" ) || ISBNValidate.isISBN13CheckDigitValid( isbn13 ) ) : "bad isbn13 [" + isbn13 + "]"; }// /method String buildAbeProbeLink( String isbn13 ) { validateIsbn13( isbn13 ); assert this.name().startsWith( "ABE" ) : "BStore.buildAbeProbeLink used with non Abe bookstore " + this.name(); final FastCat sb = new FastCat( 5 ); sb.append( getProtocol(), getHost() ); sb.append( "/servlet/SearchResults?isbn=", isbn13 ); sb.append( "&sortby=93&sts=t&x=0&y=0" ); return sb.toString(); }// /method String buildAbeProductLink( final String isbn13 ) { assert this.name().startsWith( "ABE" ) : "BStore.buildAbeProductLink used with non Abe bookstore " + this.name(); validateIsbn13( isbn13 ); if ( account == null ) { final FastCat sb = new FastCat( 5 ); sb.append( getProtocol(), getHost() ); sb.append( "/servlet/SearchResults?isbn=", isbn13 ); sb.append( "&sortby=93&sts=t&x=0&y=0" ); return sb.toString(); } final FastCat sb = new FastCat( 6 ); sb.append( "http://clickserve.cc-dt.com/link/tplclick?lid=" ); sb.append( "41000000024289215" ); sb.append( "&pubid=", getAccount() ); sb.append( "&redirect=" ); try { // we armour the entire URL converting it to one parm. sb.append( URLEncoder.encode( getProtocol() + getHost() + "/servlet/SearchResults?isbn=" + isbn13 + "&sortby=93&sts=t&x=0&y=0", "UTF-8" /* can't be Charset */ ) ); } catch ( UnsupportedEncodingException e ) { throw new IllegalArgumentException( "Program bug: problems generating AbeBooks " + this.name() + " referral" ); } return sb.toString(); }// /method /** * query link, for general query for Abe stores * * @param title title of book * @param author author of this book * * @return HTML for URL to link. */ String buildAbeQueryLink( @SuppressWarnings( "UnusedParameters" ) String title, final String author ) { assert this.name().startsWith( "ABE" ) : "buildAbeQueryLink used with non Abe bookstore " + this.name(); // Abe queries all use POST. We cannot build a canned URL to them. final FastCat sb = new FastCat( 2 ); sb.append( getProtocol(), getHost() ); return sb.toString(); // ??? does nothing }// /method /** * flag link for a referral to Amazon store, manual probe * * @param asin asin,. * * @return code for link without or flag. */ String buildAmazonManualProbeLink( final String asin ) { validateAsin( asin ); final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/dp/", asin ); return sb.toString(); }// /method /** * default build link to refer to product. Default works for Amazon * * @param isbn13 book number or asin10 * * @return HTML for URL to link. */ String buildAmazonProductLink( final String isbn13 ) { final String asin; if ( isbn13.length() == 10 ) { asin = isbn13; } else { validateIsbn13( isbn13 ); asin = ISBNToASIN.isbnToAsin( isbn13 ); // we link using asin, not ISBN-10 as we used to. } if ( asin == null ) { return ""; // dummy link } final String account = this.getAccount(); if ( account == null ) { final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/dp/", asin ); return sb.toString(); } else { final int camp = this.getCamp(); final int creative = this.getCreative(); final FastCat sb = new FastCat( 14 ); sb.append( getProtocol(), getHost() ); sb.append( "/gp/product/", asin ); sb.append( "/ref=as_li_qf_br_asin_tl?ie=UTF8" ); sb.append( "&tag=", account ); sb.append( "&linkCode=as2" ); sb.append( "&camp=", camp ); sb.append( "&creative=", creative ); sb.append( "&creativeASIN=", asin ); return sb.toString(); } } /** * query link, for general query for Amazon stores * * @param title title of book * @param author author of this book * * @return HTML for URL to link. */ String buildAmazonQueryLink( final String title, final String author ) { final FastCat sb = new FastCat( 4 ); sb.append( getProtocol(), getHost() ); sb.append( "/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords=" ); sb.append( prepareQueryField( title + " by " + author ) ); return sb.toString(); }// /method" /** * query link, for general query for Safari 10/13 stores * * @param title title of book * @param author author of this book * * @return HTML for URL to link. */ String buildSafariQueryLink( @SuppressWarnings( "UnusedParameters" ) final String title, final String author ) { // does not have a search, until you pay. Even then, done with post final FastCat sb = new FastCat( 2 ); sb.append( getProtocol(), getHost() ); return sb.toString(); }// /method int getCamp() { return camp; }// /method int getCreative() { return creative; }// /method /** * is this an ebook store * * @return true if this store sells only ebooks */ boolean isOnlyEbooks() { return this.onlyEbooks; } /** * summary reports */ public static void summary() { printCountsOfItemsInStock(); reportUnusedClues(); }// /method /** * clear counts of products added and dropped */ public void clearStats() { productsAdded = 0; productsDropped = 0; }// /method /** * configure this bookstore to use a particular stock (numbering system, inventory) * * @param stock Stack that tracks products for this store. * @param ordinal number 0 2 4 ... assigned by Stack to indicate this store. */ public void configureStock( Stock stock, int ordinal ) { this.stock = stock; this.stockOrdinal = ordinal; }// /method /** * enque a product to be probed for a given book for this bookstore * * @param product isbn13 */ public void enqueueProductToProbe( final String product ) { synchronized ( productsYetToProbe ) { productsYetToProbe.add( product ); } }// /method /** * forgive past string of refusals because we had a success */ public void forgiveRefusals() { refused = 0; } public String getAccount() { return account; }// /method /** * get all products this store potentially has in stock. * * @return Set to all products */ public String[] getAllSortedProducts() { // returns same set for all stores in the stock. final Set allProducts = stock.allProducts(); final String[] products = allProducts.toArray( new String[ allProducts.size() ] ); Arrays.sort( products ); return products; }// /method /** * is a given product in stock? Uses, cache, lookup, or aws probe, or manual probe. * If this is made private, it thinks calls are static. * * @param product product number, either ISBN or ASIN * * @return true if the product is in stock. */ public StockStatus getAmazonProductStatus( final String product ) { if ( ST.isEmpty( product ) ) { return StockStatus.NOTCARRIED; } final String asin; if ( product.length() == 13 ) { asin = ISBNToASIN.isbnToAsin( product ); if ( asin == null ) { return NOTCARRIED; } } else { asin = product; } validateAsin( asin ); synchronized ( stock ) { return stock.getProductStatus( asin, stockOrdinal, true ); } }// /method /** * get strings that indicate item is in or out of stock. * * @return clues array */ public Clue[] getClues() { return clues; }// /method /** * how many products we still have to probe */ public int getCountOfProductsYetToProbe() { synchronized ( productsYetToProbe ) { return productsYetToProbe.size(); } } /** * name of store, with entities * * @return name of store */ public String getDecoratedStoreName() { return decoratedBookstoreName; }// /method /** * make enum.name() visible. Don't change to name. * * @return enum name */ public String getEnumName() { return this.name(); }// /method /** * get the flag for this bookstore */ public CountryFlag getFlag() { return flag; }// /method /** * get the Get connection in progress * * @return Get connection in progress, null if none */ public Get getGetInProgress() { return this.getInProgress; } /** * Get bookstore host * * @return domain to go to, without leading http:// */ public String getHost() { return host; }// /method /** * milliseconds min between probing the same store. */ public long getInterProbeTime() { return interProbeTime; // read-only so need not be synchronized }// /method /** * record last product probed for this store * * @return last product probed, not necessary completed. */ public String getLastProductProbed() { return this.lastProductProbed; } /** * record last product probed for this store * * @param product product id */ public void setLastProductProbed( String product ) { this.lastProductProbed = product; } /** * miminum length of response from store to parse */ public int getMinimumResponseLength() { return this.minimumResponseLength; } /** * get store ordinal */ public int getOrdinal() { return ordinal(); } /** * get the port for the server for that country * * @param service the soap service * * @return the port for that country. */ public AWSECommerceServicePortType getPort( AWSECommerceService service ) { return null; }// /method @Override /** * return stage of probe completion 0 .. 7 */ public int getProbeCompletionStage() { return probeCompletionStage; } @Override /** * record whether probe completed * * @param completionStage 0..7 */ public void setProbeCompletionStage( final int completionStage ) { this.probeCompletionStage = completionStage; } /** * is a given product in stock at this store? * * @param product product number * @param probe true if should probe to get status if one not on file. * * @return StockStatus enum. */ public StockStatus getProductStatus( final String product, final boolean probe ) { // Override errors in product database final StockStatus override = CONFIGURATION.stockStatusOverride( this /* store */, product ); if ( override != null ) { return override; } synchronized ( stock ) { // normal case return stock.getProductStatus( product, stockOrdinal, probe ); } }// /method /** * get count of products recently added * * @return count of products recently added */ public int getProductsAddedCount() { return productsAdded; }// /method /** * get count of products recently dropped * * @return count of products recently dropped */ public int getProductsDroppedCount() { return productsDropped; }// /method /** * Get bookstore protocol eg. http:// or https:// * * @return domain to go to, without leading http:// */ public final String getProtocol() { return protocol; }// /method /** * get all products in HashSet of products for this store, and sort them. */ public String[] getSortedProductsToProbe() { synchronized ( productsYetToProbe ) { final String[] products = productsYetToProbe.toArray( new String[ productsYetToProbe.size() ] ); Arrays.sort( products ); return products; } }// /method /** * get timestamp of when previous probe was completed * * @return timestamp in millis since 1970 */ public long getTimeLastProbeCompleted() { return lastProbeCompleted == 0 ? System.currentTimeMillis() : lastProbeCompleted; }// /method /** * name of store, without entities * * @return name of store */ public String getUndecoratedStoreName() { return undecoratedBookstoreName; }// /method /** * true if still probing this store, it has not refused too many probes */ public synchronized boolean isAcceptingProbes() { return refused <= MAX_ALLOWABLE_PROBES_REFUSED; }// /method /** * is this store functioning. * If false, show with under construction icon, without any links * * @return true if store working */ public boolean isAlive() { return isAlive; } /** * Should we give up reviving this stall store? * * @return true if this store has stalled many times and should not be revived, * false if store should be revived from stall yet again; */ public boolean isHopelesslyStalled() { return stallsToGo > 0; } /** * restart the counter of refused probes, if not already hit the limit * * @param product isbn, asin, upc just probed successfully. * @param straggler true if we are processing random stragglers. false if processing bulk sorted products. */ public void noteCompletedProbe( final String product, final boolean straggler ) { synchronized ( productsYetToProbe ) { productsYetToProbe.remove( product ); } lastProbeCompleted = System.currentTimeMillis(); if ( !straggler ) { stock.noteHighestProbe( product, stockOrdinal ); } }// /method /** * increment count of products added on this probe */ public void noteProductAdded() { productsAdded++; }// /method /** * increment count of products dropped on this probe */ public void noteProductDropped() { productsDropped++; }// /method /** * increment the count of refused probes */ public void noteRefusedProbe( final String product ) { synchronized ( productsYetToProbe ) { productsYetToProbe.remove( product ); } refused++; if ( refused <= MAX_ALLOWABLE_PROBES_REFUSED ) { err.println( this + " refusing probes " + refused ); } else { err.println( this + " refusing probes " + refused + " giving up probing" ); } lastProbeCompleted = System.currentTimeMillis(); }// /method /** * record fact this store has stalled again */ public void noteStall() { stallsToGo--; } /** * what this store sells * * @return "book" */ public String productCategory() { return "book"; }// /method /** * record the Get connection in progress * * @param get Get connection */ public void recordGetInProgress( Get getInProgress ) { this.getInProgress = getInProgress; } /** * set single stockItem status * * @param product product number * @param status 0=not in stock 1=in stock 2=unknown 3=store is refusing probes. */ public void setProductStockStatus( String product, StockStatus status ) { synchronized ( stock ) { stock.setProductStockStatus( product, stockOrdinal, status ); } }// /method /** * true if this store's inventory should be checked, based on corresponding set env parm. * * @return true if should check this store. */ public boolean shouldCheck() { // cache the result of probing the environment switch ( shouldCheck ) { case -1: String name = this.name().toLowerCase(); // trim trailing 10 or 13 name = ST.chopTrailingString( name, "10" ); name = ST.chopTrailingString( name, "13" ); // deal with suppressing ABECA and ABENZ in DulkProber whatToProbe if ( Misc.parseBoolean( System.getenv( "check" + name ), false ) ) { shouldCheck = 1; return true; } else { shouldCheck = 0; return false; } case 0: return false; case 1: return true; default: throw new IllegalArgumentException( "bug in shouldCheck case" ); } }// /method /** * do we need to suppress cookie errors * * @return true if need to suppress cookie errors */ public boolean suppressCookieErrors() { return false; }// /method /** * @return true if this store takes PayPal */ public boolean takesPayPal() { return takesPayPal; } /** * Was this item successfully probed on a recent run? * * @param product product number, may be null or empty. * * @return true if recently probed already. */ public boolean wasRecentlyProbed( String product ) { return stock.wasRecentlyProbed( product, stockOrdinal ); } }