San Francisco Bay Area Libraries - Books lookup on all sites

By Junk Blocker Last update Jul 26, 2011 — Installed 11,770 times.

There are 6 previous versions of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name        San Francisco Bay Area Libraries - Books lookup on all sites
// @description Display the availability of books in San Francisco Bay Area libraries on all sites
// @namespace   http://loonyone.livejournal.com
// @include     *
// @creator     Manpreet Singh <junkblocker@yahoo.com>
// @source      http://userscripts.org/scripts/show/1072
// @identifier  http://userscripts.org/scripts/source/1072.user.js
// @version     4.0
// @date        2011-07-25
// ==/UserScript==

/* Copyright (c) 2006-2011, Manpreet Singh <junkblocker@yahoo.com>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

// TODO: LibraryThing has FRAMES on wishlist pages!!!!! ARGGGGHHHH!!!!!

// Credits:
//  * Initially based on Palo Alto Library lookup script and Tony Hirst's Generic OU Library Linky script
//  * Individual Library Linking code for book pages from Peninsula Library System Google Print Lookup
//  * xISBN lookup code via Jon Udell
//  * Configuration form logic from YousableTube userscript by Mindeye.

// Changelog
// 4.0 - Made it (mostly) work with Chrome 13 beta which has cross-site xhr support
//       Added the preferences button on the titlebar
//       Fixed alternate ISBNs caching
// 3.9 - Added support for peninusla library system's paginated status pages
//       Fixed peninsula library system title extraction and linking in corner cases
//       Updated links to point to new library URLs
//       More caching option to save bandwidth
//       Added Damaged status
//       Added an experimental skin
//       Grey title in case no books are available
//       Ability to collapse all status types individually
//       Slightly better links to amazon.com
//       Added "Lost and Paid" status type
//       Minor bug fixes
// 3.8 - Support additional libraries
//       Major functionality revamp
//       Renamed
// 3.7 - Added real algorithmic validation for ISBN-10 and ISBN-13
//       Bumped up zIndex
// 3.6 - Fixed oversight when partial results weren't return in case of error
//       Google books works better now
// 3.5 - Added additional pattern to match on plsinfo.org page
// 3.4 - Fixed Alternate ISBN lookup for ISBN-13
// 3.3 - Lookup alternate ISBNs via xISBN web service
//       Link individual librarys on plsinfo.org book page
//       Added toggle menu
//       Added Mending status

// TODO:
// Google Books
// frameset/frames

(function () {
  var gStyles = [
    { // 0
      name: 'Plain Orange',
      frame_title_bg: '#F63',
      lib_title_bg_color: '#333',
      lib_title_text_color: 'white',
      type_group_bg_css: '',
      type_group_title_css: "font-weight: normal; \n" +
        "background: #F96; \n",
      book_bg_css: "background: #ffffcc; \n",
    },
    { // 1
      name: 'Smooth Green',
      frame_title_bg: '#d9ef92',
      lib_title_bg_color: '#f3ffce',
      lib_title_text_color: 'black',
      type_group_bg_css:
        "background: #d9ef92; /* Old browsers */ \n" +
        "background: -moz-linear-gradient(top, #d9ef92 0%, #f3ffce 100%); /* FF3.6+ */ \n" +
        "background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#d9ef92), color-stop(100%,#f3ffce)); /* Chrome,Safari4+ */ \n" +
        "background: -webkit-linear-gradient(top, #d9ef92 0%,#f3ffce 100%); /* Chrome10+,Safari5.1+ */ \n" +
        "background: -o-linear-gradient(top, #d9ef92 0%,#f3ffce 100%); /* Opera11.10+ */ \n" +
        "background: -ms-linear-gradient(top, #d9ef92 0%,#f3ffce 100%); /* IE10+ */ \n" +
        "filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#d9ef92', endColorstr='#f3ffce',GradientType=0 ); /* IE6-9 */ \n" +
        "background: linear-gradient(top, #d9ef92 0%,#f3ffce 100%); /* W3C */ \n",
      type_group_title_css: "font-weight: bold; \n" +
        "background: #f3ffce; /* Old browsers */ \n" +
        "background: -moz-linear-gradient(top, #f3ffce 0%, #d9ef92 100%); /* FF3.6+ */ \n" +
        "background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f3ffce), color-stop(100%,#d9ef92)); /* Chrome,Safari4+ */ \n" +
        "background: -webkit-linear-gradient(top, #f3ffce 0%,#d9ef92 100%); /* Chrome10+,Safari5.1+ */ \n" +
        "background: -o-linear-gradient(top, #f3ffce 0%,#d9ef92 100%); /* Opera11.10+ */ \n" +
        "background: -ms-linear-gradient(top, #f3ffce 0%,#d9ef92 100%); /* IE10+ */ \n" +
        "filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f3ffce', endColorstr='#d9ef92',GradientType=0 ); /* IE6-9 */ \n" +
        "background: linear-gradient(top, #f3ffce 0%,#d9ef92 100%); /* W3C */ \n",
      book_bg_css:
        "background: transparent none no-repeat scroll 0 0; \n",
    },
  ];

  function isSafari() {
    return navigator.appVersion.search("Safari") != -1
  }

  function isChrome() {
    // or Chromium etc.
    return navigator.appVersion.search("Chrome") != -1
  }

  function log(args) {
    var o = Array.prototype.concat.apply([], arguments);

    if (typeof console != 'undefined' && console.log) {
      console.log(o.join(", "));
      if (typeof GM_log != 'undefined') GM_log(o.join(", "));
      return true;
    } else if (typeof GM_log != 'undefined') {
      GM_log(o.join(", "));
      return true;
    } else if (window.opera) {
      opera.postError(o.join(", "));
    }
    return false;
  }

  if (typeof GM_registerMenuCommand != 'undefined') {
    registerMenuCommand = GM_registerMenuCommand;
  } else {
    registerMenuCommand = function(){};
  }

  function addStyle(css) {
    if (typeof GM_addStyle != 'undefined') return GM_addStyle(css);

    if (heads = document.getElementsByTagName('head')) {
      root = heads[0];
    } else {
      root = document.body;
    }
    var style = document.createElement('style');
    try {
      style.innerHTML = css;
    } catch(x) {
      style.innerText = css;
    }
    style.type = 'text/css';
    root.appendChild(style);
  }

  function openInTab(url) {
    if (typeof GM_openInTab != 'undefined') {
      GM_openInTab(url);
    } else {
      window.open(url);
    }
  }

  function getStyle(elm, prop) {
    return window.getComputedStyle(elm, null).getPropertyValue(prop);
  }

  // *************************************************************************
  // Typed storage variables
  // *************************************************************************
  var _REG_IGNORE = false;
  var _REG = {};

  var isGM = (typeof GM_getValue != 'undefined' && typeof GM_getValue('a', 'b') != 'undefined');

  var getValue = function(name, defaultValue) {
    if (!_REG_IGNORE && !_REG[name]) log('ERROR: ' + name + ' accessed before registering');
    var value = isGM ? GM_getValue(name) : localStorage.getItem(name);
    if (!value) return defaultValue;

    var type = value[0];
    value = value.substring(1);
    switch (type) {
      case 'b': return value == 'true';
      case 'n': return Number(value);
      case 'o':
        try {
          return JSON.parse(value);
        } catch (e) {
          log(e);
          return defaultValue;
        }
      default: return value;
    }
  };

  var setValue = function(name, value) {
    if (!_REG[name]) {
      if (!_REG_IGNORE) log('ERROR: ' + name + ' assigned before registering');
    } else {
      var x = getValue(name);
      if (typeof x != 'undefined' && typeof x != typeof value) log('Saved type for ' + name + ' ' + x + ' ' + typeof x + ' differs from the type being assigned ' + value + ' ' + typeof value);
    }
    var type  = (typeof value)[0];
    if (type == 'o') {
      try {
        value = type + JSON.stringify(value);
      } catch (e) {
        log(e);
        return;
      }
    } else {
      value = type + value;
    }
    isGM ? GM_setValue(name, value) : localStorage.setItem(name, value);
  };

  var deleteValue = isGM ? GM_deleteValue : function(name) {localStorage.removeItem(name)};
  
  var listValues = isGM ? GM_listValues : function() {
    var ret = new Array();
    for (var i = 0; i < localStorage.length; i++) {
      ret.push(localStorage.key(i));
    }
    return ret;
  };

  function reg(name, def, values) {
    //log('Registering ' + name);
    if (_REG[name]) log(name + ' is already registered.');
    if (typeof values != 'undefined' && typeof values == 'string' && typeof def != 'string') {
      log('Invalid registration param types for ' + name);
    }
    _REG_IGNORE = true;
    var x = getValue(name);
    _REG_IGNORE = false;
    if (typeof values == 'undefined') {
      _REG[name] = true;
      if (typeof x != typeof def) {
        log('Fixing ' + name + ' to ' + def + ' type(' + typeof def + ')');
        setValue(name, def);
      }
      return;
    } else if (typeof values == 'object') {
      for (var i = 0, l = values.length ; i < l; i++) {
        //log(x, typeof x, values[i], typeof values[i]);
        if (x === values[i]) {
          _REG[name] = true;
          return;
        }
      }
    } else if (typeof values == 'string') {
      if (typeof x == values) {
        _REG[name] = true;
        return;
      }
    } else {
      log('Invalid param values for ' + name);
      return;
    }
    _REG[name] = true;
    log('Fixing ' + name + ' to ' + def + ' type(' + typeof def + ')');
    setValue(name, def);
  }
  // *************************************************************************
  reg('CHECKING', 0);
  reg('CacheAltISBNs?', true);
  reg('CacheBookStatus?', true);
  reg('DEVELOPER', false);
  reg('LAST_CHECKED', 0);
  reg('NEXT_XHR_AT', 0);
  reg('Palo Alto Library', true);
  reg('Peninsula Library System', true);
  reg('Scrape?', true);
  reg('Shop', 'amazon', ['amazon', 'borders', 'powells', 'barnesandnoble']);
  reg('ShowDELAYED', 'expanded', ['expanded', 'collapsed', 'no']);
  reg('ShowELECTRONIC', 'expanded', ['expanded', 'collapsed', 'no']);
  reg('ShowERROR', 'expanded', ['expanded', 'collapsed', 'no']);
  reg('ShowINSTANT', 'expanded', ['expanded', 'collapsed', 'no']);
  reg('ShowNOPE', 'expanded', ['expanded', 'collapsed', 'no']);
  reg('Style', 0, [0, 1]);
  reg('alt_isbns_cache', {});
  reg('book_status_cache', {});
  reg('xISBN?', true);

  function type(o) {
    return !!o && Object.prototype.toString.call(o).match(/(\w+)\]/)[1];
  }

  function $xp(exp, node) { // xpath helper
    if (!node||node=='') node = document;
    var i, arr = [], r = document.evaluate(exp, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (i = 0, l = r.snapshotLength; i < l; i++) arr.push(r.snapshotItem(i));
    return arr;
  }

  // Returns only the first element of the array returned by $x (or null if the array was empty)
  function $xp1(exp, node) {
    if (!node||node=='') node = document;
    return document.evaluate(exp, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  }

  function removeEventHandler(target, eventName, eventHandler) {
    if (target.addEventListener) {
      target.removeEventListener(eventName, eventHandler, true);
    } else if (target.attachEvent) {
      target.detachEvent('on' + eventName, eventHandler);
    }
  }

  function addEventHandler(target, eventName, eventHandler, scope) {
    var f = scope ? function(){ eventHandler.apply( scope, arguments ); } : eventHandler;
    if (target.addEventListener)  {
      target.addEventListener(eventName, f, true);
    } else if (target.attachEvent) {
      target.attachEvent('on' + eventName, f);
    }
    return f;
  }

  // Creates a new node with the given attributes and properties (be careful with XPCNativeWrapper limitations)
  function createNode(type, attributes, props) {
    var node = document.createElement(type);
    if (attributes) {
      for (var attr in attributes) {
        if (! attributes.hasOwnProperty(attr)) continue;
        node.setAttribute(attr, attributes[attr]);
      }
    }
    if (props) {
      for (var prop in props) {
        if (! props.hasOwnProperty(prop)) continue;
        if (prop in node) node[prop] = props[prop];
      }
    }
    return node;
  }

  // Adds !important to CSS rules of any type
  String.prototype.makeImportant = function() {

    var Selector, DeclarationBlock, CssArray = this.match(/([^{]+)({[^{}]+})/);
    if (CssArray === null) {
      // Inline CSS rule (e.g. "display: none") or scripting rule (e.g. element.style.display = "none")
      Selector = "";
      DeclarationBlock = this;
    }
    else {
      // Complete CSS rule (e.g. ".nd {display: none}")
      Selector = CssArray[1];
      DeclarationBlock = CssArray[2];
    }

    // Adds !important to each rule
    if (DeclarationBlock.indexOf(":") !== -1) {
      DeclarationBlock = DeclarationBlock.replace(/[^:;{}]+:[^:;{}]+/g, "$& !important");
      // Unmangle url(data:image/gif;base64,...) type declarations
      if (DeclarationBlock.indexOf('url(data !important') !== -1) {
        DeclarationBlock = DeclarationBlock.replace(/url\(data !important/, 'url(data');
        DeclarationBlock = DeclarationBlock.replace(/url\(data[^\)]+\)[^;}]+/, "$& !important");
      }
    }
    else {
      // No structure could be recognized, so we'll just add !important
      DeclarationBlock += " !important";
    }
    // Remove any !important duplicates
    DeclarationBlock = DeclarationBlock.replace(/(?:\s*!important\s*){2,}/gi, " !important");

    return Selector + DeclarationBlock;

  };

  function configureScript() {
    if (top !== self || document.body.tagName.toLowerCase() === 'frameset') return;

    // Gets the layers
    var maskLayer = document.getElementById("gsmaskLayer");
    var dialogLayer = document.getElementById("gsdialogLayer");
    // Checks the layers state
    // Creates the layers if they don't exist or displays them if they are hidden
    if ((maskLayer) && (dialogLayer)) {
      if ((maskLayer.style.display === "none") && (dialogLayer.style.display === "none")) {
        maskLayer.style.display = "";
        dialogLayer.style.display = "";
      }
      dialogLayer.focus();
    } else {
      createLayers();
    }

    return;

    // Creates the configuration layers
    function createLayers() {
      // Creates a layer to mask the page during configuration
      maskLayer = createNode("div", {id: "gsmaskLayer", title: "Click here to return to the page"});

      // Creates a layer for the configuration dialog
      dialogLayer = createNode("div", {id: "gsdialogLayer"});

      var currentShowINSTANT = getValue('ShowINSTANT', 'expanded');
      var currentShowNOPE = getValue('ShowNOPE', 'expanded');
      var currentShowDG = getValue('ShowDELAYED', 'expanded');
      var currentShowEA = getValue('ShowELECTRONIC', 'expanded');
      var currentShowER = getValue('ShowERROR', 'expanded');
      var currentShop = getValue('Shop', 'amazon');
      var currentStyle = getValue('Style', 0);

      // Creates the configuration dialog HTML
      dialogLayer.innerHTML = "<div id='gsconfTitle'>San Francisco Bay Area Libraries Configuration</div>" +
        "<ul>" +
        "<li>Which libraries should be enabled?</li>" +
        "<input type='checkbox' id='gsconfPeninsulaLibrarySystem'" + (getValue("Peninsula Library System", true)?" checked='checked'":"") + ">Peninsula Library System<br>" +
        "<input type='checkbox' id='gsconfPaloAltoLibrary'" + (getValue("Palo Alto Library", true)?" checked='checked'":"") + ">Palo Alto Library</li>" +
        "<li>How should the books immediately available in the library be shown?</li>" +
        "<select id='gsconfShowINSTANT' size='1'>" +
        "<optgroup label='Show Not Available'>" +
        "<option id='gsconfShowINSTANTNo' value='no'" + ((currentShowINSTANT === "no") ? " selected='selected'" : "") + ">No</option>" +
        "<option id='gsconfShowINSTANTExpanded' value='expanded'" + ((currentShowINSTANT === "expanded") ? " selected='selected'" : "") + ">Expanded</option>" +
        "<option id='gsconfShowINSTANTCollapsed' value='collapsed'" + ((currentShowINSTANT === "collapsed") ? " selected='selected'" : "") + ">Collapsed</option>" +
        "</optgroup>" +
        "</select>" +
        "<li>How should the books not available in the library be shown?</li>" +
        "<select id='gsconfShowNOPE' size='1'>" +
        "<optgroup label='Show Not Available'>" +
        "<option id='gsconfShowNOPENo' value='no'" + ((currentShowNOPE === "no") ? " selected='selected'" : "") + ">No</option>" +
        "<option id='gsconfShowNOPEExpanded' value='expanded'" + ((currentShowNOPE === "expanded") ? " selected='selected'" : "") + ">Expanded</option>" +
        "<option id='gsconfShowNOPECollapsed' value='collapsed'" + ((currentShowNOPE === "collapsed") ? " selected='selected'" : "") + ">Collapsed</option>" +
        "</optgroup>" +
        "</select>" +
        "<li>How should the books not immediately available in the library be shown?</li>" +
        "<select id='gsconfShowDG' size='1'>" +
        "<optgroup label='Show Not Available'>" +
        "<option id='gsconfShowDGNo' value='no'" + ((currentShowDG === "no") ? " selected='selected'" : "") + ">No</option>" +
        "<option id='gsconfShowDGExpanded' value='expanded'" + ((currentShowDG === "expanded") ? " selected='selected'" : "") + ">Expanded</option>" +
        "<option id='gsconfShowDGCollapsed' value='collapsed'" + ((currentShowDG === "collapsed") ? " selected='selected'" : "") + ">Collapsed</option>" +
        "</optgroup>" +
        "</select>" +
        "<li>How should the electronic edition be shown?</li>" +
        "<select id='gsconfShowEA' size='1'>" +
        "<optgroup label='Show Not Available'>" +
        "<option id='gsconfShowEANo' value='no'" + ((currentShowEA === "no") ? " selected='selected'" : "") + ">No</option>" +
        "<option id='gsconfShowEAExpanded' value='expanded'" + ((currentShowEA === "expanded") ? " selected='selected'" : "") + ">Expanded</option>" +
        "<option id='gsconfShowEACollapsed' value='collapsed'" + ((currentShowEA === "collapsed") ? " selected='selected'" : "") + ">Collapsed</option>" +
        "</optgroup>" +
        "</select>" +
        "<li>Should we show any errors encountered?</li>" +
        "<select id='gsconfShowER' size='1'>" +
        "<optgroup label='Show Not Available'>" +
        "<option id='gsconfShowERNo' value='no'" + ((currentShowER === "no") ? " selected='selected'" : "") + ">No</option>" +
        "<option id='gsconfShowERExpanded' value='expanded'" + ((currentShowER === "expanded") ? " selected='selected'" : "") + ">Expanded</option>" +
        "<option id='gsconfShowERCollapsed' value='collapsed'" + ((currentShowER === "collapsed") ? " selected='selected'" : "") + ">Collapsed</option>" +
        "</optgroup>" +
        "</select>" +
        "<li>Should we try to find alternate ISBNs for the same book?</li>" +
        "<input type='checkbox' id='gsconfXISBN'" + (getValue("xISBN?", true)?" checked='checked'":"") + ">Try alternate ISBNs</li>" +
        "<li>Should we try really hard to find ISBNs in the page?<br><em>*</em> Note: This might be slow.</li>" +
        "<input type='checkbox' id='gsconfScrape'" + (getValue('Scrape?', true)?" checked='checked'":"") + ">Scrape hard for ISBNs</li>" +
        "<li>Should we remember book availability status for upto one hour to improve performance during heavy browsing?<br><em>*</em> Note: Recommended!</li>" +
        "<input type='checkbox' id='gsconfCacheBookStatus'" + (getValue("CacheBookStatus?", true)?" checked='checked'":"") + ">Cache book status</li>" +
        "<li>Should we remember alternate book ISBNs for upto one hour to improve performance during heavy browsing?<br><em>*</em> Note: Recommended!</li>" +
        "<input type='checkbox' id='gsconfCacheAltISBNs'" + (getValue("CacheAltISBNs?", true)?" checked='checked'":"") + ">Cache alternate ISBNs</li>" +
        "<li>Select the search results link site:</li>" +
        "<select id='gsconfShop' size='1'>" +
        "<optgroup label='Book Stores'>" +
        "<option id='gsconfShopAmazon' value='amazon'" + ((currentShop === "amazon") ? " selected='selected'" : "") + ">Amazon.com</option>" +
        "<option id='gsconfShopBarnesandnoble' value='barnesandnoble'" + ((currentShop === "barnesandnoble") ? " selected='selected'" : "") + ">Barnes & Noble</option>" +
        "<option id='gsconfShopBorders' value='borders'" + ((currentShop === "borders") ? " selected='selected'" : "") + ">Borders Books</option>" +
        "<option id='gsconfShopPowells' value='powells'" + ((currentShop === "powells") ? " selected='selected'" : "") + ">Powell's Books</option>" +
        "</optgroup>" +
        "<optgroup label='Price Comparison Sites'>" +
        "<option id='gsconfShopBookfinder' value='bookfinder'" + ((currentShop === "bookfinder") ? " selected='selected'" : "") + ">BookFinder.com</option>" +
        "<option id='gsconfShopISBNdb' value='isbndb'" + ((currentShop === "bookfinder") ? " selected='selected'" : "") + ">ISBNdb.com</option>" +
        "<option id='gsconfShopMuseophile' value='museophile'" + ((currentShop === "museophile") ? " selected='selected'" : "") + ">Museophile</option>" +
        "<option id='gsconfShopShopWiki' value='shopwiki'" + ((currentShop === "shopwiki") ? " selected='selected'" : "") + ">ShopWiki</option>" +
        "</optgroup>" +
        "</select>" +
        "<li>Select the display style:</li>" +
        "<select id='gsconfStyle' size='1'>" +
        "<optgroup label='Display Style'>" +
        "<option id='gsconfStyle0' value='0'" + ((currentStyle === "0") ? " selected='selected'" : "") + ">" + gStyles[0].name + "</option>" +
        "<option id='gsconfStyle1' value='1'" + ((currentStyle === "1") ? " selected='selected'" : "") + ">" + gStyles[1].name + "</option>" +
        "</optgroup>" +
        "</select>" +
        "</ul>" +
        "</div>" +
        "<div id='gsconfButDiv'>" +
        "<input type='button' id='gsconfResetBut' value='Reset' title='Reset all script configuration'>" +
        "<input type='button' id='gsconfCancelBut' value='Cancel' title='Return to the page without saving'>" +
        "<input type='button' id='gsconfOKBut' value='OK' title='Save the current configuration'>" +
        "</div>";

      // Appends the layers to the document
      document.body.appendChild(maskLayer);
      document.body.appendChild(dialogLayer);

      // Adds the necessary event listeners
      addEventHandler(maskLayer, "click", hideLayers);
      addEventHandler(document.getElementById("gsconfResetBut"), "click", resetConfig);
      addEventHandler(document.getElementById("gsconfCancelBut"), "click", hideLayers);
      addEventHandler(document.getElementById("gsconfOKBut"), "click", saveConfiguration);

      // Initiates an array with CSS styles for the script
      [
        // Adds styles and classes for the configuration layers and its contents
        "#gsmaskLayer {background-color: black; opacity: 0.7; z-index: 2147483645; " +
          "position: fixed; left: 0px; top: 0px; width: 100%; height: 100%}",
        "#gsdialogLayer {background-color: white; overflow: auto; padding: 20px; z-index: 2147483646; " +
          "outline: black solid thin; position: fixed; left: 30%; top: 7.5%; width: 40%; height: 85%; text-align: left; " +
          "font-family: 'Times New Roman',Times,serif; text-shadow: 0 0 1px #ccc}",
        "#gsdialogLayer > * {margin: 20px 0px}",
        "#gsdialogLayer li {margin: 15px 0px 7px; line-height: 1.5em; font-style: italic; color: #333; display: list-item; list-style-type: none; font-size: 14px; " +
          "font-family: Arial,Helvetica,FreeSans,\"Luxi-sans\",\"Nimbus Sans L\",sans-serif; " +
          "background: url(data:image/gif;base64,R0lGODlhAQACAIAAAMncpv///yH5BAAAAAAALAAAAAABAAIAAAICRAoAOw==) left bottom repeat-x; " +
          "border: none}",
        "#gsdialogLayer input, #gsdialogLayer select {vertical-align: bottom; margin-right: 0.5em; color: #333; line-height: 14px; border: 1px solid #333;}",
        "#gsconfTitle {cursor: default; font-size: 20px; line-height: 24px; font-weight: bold; text-align: center; color: #333; margin: 20px; " +
          "font-family: 'Times New Roman',Times,serif}",
        "#gsconfButDiv {text-align: center}",
        "#gsconfButDiv input {margin: 5px}",
        "#gsdialogLayer ul {list-style-type: none; line-height: 16px; padding-left: 40px; padding-right: 40px; color: #333; list-style-image: none; display: list-item; font-size: 14px; " +
          "font-family: Arial,Helvetica,FreeSans,\"Luxi-sans\",\"Nimbus Sans L\",sans-serif; " +
          "background: none; border: none}",
        "#gsdialogLayer em {font-weight: bold; font-style: normal; color: red}"

      // Adds the styles from the style array to the page, making them important
      ].forEach(function(s) {addStyle(s.makeImportant());});

    }

    // Changes the enabled state of all input/select fields of the dialog layer. If newState is undefined or not boolean, it does nothing
    // It is a nested function
    function setDialogInputState(newState) {
      if (typeof(newState) !== "boolean") return;
      var allInputs = $xp(".//input|.//select", dialogLayer);
      allInputs.forEach(function(i) {i.disabled = !newState;});
    }

    // Exits the configuration by hiding the layers
    // It is called by the Cancel button and the maskLayer event listeners
    // It is a nested function
    function hideLayers(evt) {
      dialogLayer.style.display = "none";
      maskLayer.style.display = "none";
    }

    // Checks and saves the configuration to the configuration variables
    // It is called by the Ok button event listener
    // It is a nested function
    function saveConfiguration(evt) {

      // Disables the input/select fields
      setDialogInputState(false);

      // Sets other configuration variables
      setValue("Peninsula Library System", document.getElementById("gsconfPeninsulaLibrarySystem").checked);
      setValue("Palo Alto Library", document.getElementById("gsconfPaloAltoLibrary").checked);
      setValue("ShowNOPE", document.getElementById("gsconfShowNOPE").value);
      setValue("ShowDELAYED", document.getElementById("gsconfShowDG").value);
      setValue("ShowELECTRONIC", document.getElementById("gsconfShowEA").value);
      setValue("ShowERROR", document.getElementById("gsconfShowER").value);
      setValue("xISBN?", document.getElementById("gsconfXISBN").checked);
      setValue('Scrape?', document.getElementById("gsconfScrape").checked);
      setValue('CacheBookStatus?', document.getElementById("gsconfCacheBookStatus").checked);
      setValue('CacheAltISBNs?', document.getElementById("gsconfCacheAltISBNs").checked);
      setValue('Shop', document.getElementById("gsconfShop").value);
      setValue("Style", parseInt(document.getElementById("gsconfStyle").value));

      // Reloads page and script
      window.location.reload();

    }
    function resetConfig(evt) {
      // Disables the input/select fields
      setDialogInputState(false);

      // Sets other configuration variables
      setValue("Peninsula Library System", true);
      setValue("Palo Alto Library", true);
      setValue("ShowINSTANT", 'expanded');
      setValue("ShowNOPE", 'expanded');
      setValue("ShowDELAYED", 'expanded');
      setValue("ShowELECTRONIC", 'expanded');
      setValue("ShowERROR", 'expanded');
      setValue("xISBN?", true);
      setValue('Scrape?', true);
      setValue('CacheBookStatus?', true);
      setValue('CacheAltISBNs?', true);
      setValue('Shop', 'amazon');
      setValue('Style', 0);
      setValue('book_status_cache', {});
      setValue('alt_isbns_cache', {});

      // Reloads page and script
      window.location.reload();
    }
  }
  // Registers the configuration menu command
  registerMenuCommand("San Francisco Bay Area Libraries Configuration", configureScript, null, null, "S");

  var gIcons = {
    close: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABiklEQVQ4EaWTz0rDQBDGZxupRdRLjx4kUAXR9igIfQBBq4IXD+LNR1Dw4rUH9QkUCt4Va9WjIPQBKiiiB4N4lAimqUmRZN1vZCIW/xT8YJid2d98u00Ton9KYX617C6Soi3SZP/pp8gx3NruRvYgYZc3H+8v70LdjcCBl+EeLHzft/O5IYrjWPo/5nwuzbwAKSw874WHoyii0elDQu4M6eMQ8CI2aDa9ZODqeJbGZqpcA4IRavTFFLyIDVqt12TAsiy6OZ2nibkTZpBRow/BBLyIDdrtN95QSvFPwTWvayU+GRk1AvswAC9iA61TDIRhyACg8VKNT0aWq2MfRuBFvLKsDENBEJgH5FFh4Yzq+0VyXZczavSxDzPwIjZIp/uTU4ordbqoTHENCAOo0ZebgBfxe5DJDJpraQbO9ya/DAsofXDgRWLgNG61XRjpZSPZ7Mx4iIaDgSN7MMg+O9XtcmVpnZQ1TOaj+FU6enhyjnYwZ8IF3WdiwMTHH20WXSgyTNPE5wvRxdC3yDtmSTFP/cHSgQAAAABJRU5ErkJggg==',
    minus: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABW0lEQVQ4EaWTv0rDUBTGz03MH0RdOjpIoA4O9UEUJxcHVx9BwcW1g/oEDj6B0OKLqOCgg0EcpYJJahLyz/sdOG1Mayz4weE09/udr/fepkT/lML8UX+0T4rOqSLvzzxFvuaOr047NxP28Ozt5f45qRYROPAyvIQPURR5ve46lWUp67/2XtdmXgAOCIJPHl4kwDAMAi/igDAMqCiKmR2YpsnrAqPrYxJ4EQeMx18MKqUIQ3XVn/ElKPAiDkjTjA3LsmhrdyjeTH+83aM8zwm8iAOqyuDtJ0lCD4Md8QiBWTaF4WOX4EUcYJou7yBNU4rjWLy53XEcfUx34nGAba9wAM5X17xLBANexAGuu8a32wxoPmMIvwJ4kQT4d0+Vt73pMCBms+P8mkOALx4COh/+8KJ/fXBCytwg/adoVVW8vvuDS8zpGoFe1rWq6+cLoBdahMsKdU1fiBa41foGXSbJroM1THIAAAAASUVORK5CYII=',
    plus: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABbElEQVQ4EaWTz0rDQBDGZxvzB1EvPXqQQhU8tEefQRRB8OLBq4+g4MVrD+oTKPgESovg0WdQwYMeDOJRWjBpbYJN1/0Gpk1SjYIfLJOd+c2X2bAh+qcU+ncb7S1SdESaKr/6KfINt3d6UL4YsTuHr893T5H+i8CBl+YpPHS73UqtOk/D4VDyP8Za1WFegBIeguCdm5MkofRaWr3M7FHDS8CLeIIwDBj8bgI0pWWOSeBFbNDrfbCBUoosy5Iax/RepgMvYoM4/mQD27Zpeb0lNY7p/cPVBg0GAwIvYgOtS3y2KIrovrkmNapvXmf2qGNK8CI2sCyPJ4jjmPr9vtQ4BsH4vEi4rmuO6Y0YNnCcGTbIfzBQ+Rz24EVs4HlzZiw9Ad+crUzkwIEXiYF/+6gr9UWXjaSYjzi/4WDgSw0G5Y7fOm6cb++TshbI/BSF0snLm988QZ9ZbdDTZs2alb0AJlEg3K7QrPGFKIALS19dlefzXsRgTQAAAABJRU5ErkJggg==',
    spin: 'data:image/gif;base64,R0lGODlhEAAQAPIAAP9mMwAAAMJNJkIaDQAAAGInE4I0GpI6HSH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQACgABACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkEAAoAAgAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkEAAoAAwAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkEAAoABAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQACgAFACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQACgAGACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAAKAAcALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==',
    prefs: 'data:image/png;base64,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'
  };

  var gNow = new Date().getTime();
  var ONE_DAY = 24 * 60 * 60 * 1000;
  var SIX_HOURS = 6 * 60 * 60 * 1000;
  var gXHRCount = 0;

  function downXHRCount(f) {
    return function(results) {
      gXHRCount--;
      return f(results);
    }
  }

  function xhr(u, f) {
    var now = new Date().getTime();
    var next_request = getValue('NEXT_XHR_AT', now);
    // maintain about 2 parallel XHRs (1 is too few)
    if (next_request > (now + 100) && gXHRCount > 2) { // the > condition prevents livelock
      setTimeout(xhr, NEXT_XHR_AT - now, u, f);
      return;
    }
    setValue('NEXT_XHR_AT', now + 100);
    gXHRCount++;
    setTimeout(GM_xmlhttpRequest, 100, {method: 'GET', url: u, onload: downXHRCount(f)});
  }

  // ********** Auto update the script
  function autoUpdateFromUserscriptsDotOrg(SCRIPT) {
    // Note: Version numbers must be in x.y float format
    try {
      if (typeof getValue == 'undefined') return;
      // avoid a flood of dialogs e.g. when opening a browser with multiple tabs set to homepage and a script with * includes
      var DoS_PREVENTION_TIME = 2 * 60 * 1000;
      var isSomeoneChecking = getValue('CHECKING', null);
      setValue('CHECKING', gNow);
      if (isSomeoneChecking && (gNow - isSomeoneChecking) < DoS_PREVENTION_TIME) return;
      var lastChecked = getValue('LAST_CHECKED', null);

      if (lastChecked && (gNow - lastChecked) < SIX_HOURS) return;

      xhr(
        SCRIPT.url + '?source', // don't increase the 'installed' count just for checking
        function(result) {
          if (result.status !== 200) return;
          if (!result.responseText.match(/@version\s+([\d.]+)/)) return;
          var theOtherVersion = parseFloat(RegExp.$1);
          if (theOtherVersion <= parseFloat(SCRIPT.version)) return;
          if (window.confirm('A new version ' + theOtherVersion + ' of greasemonkey script "' + SCRIPT.name + '" is available.\nYour installed version is ' + SCRIPT.version + ' .\n\nUpdate now?\n')) {
            openInTab(SCRIPT.url);
          }
        });
      setValue('LAST_CHECKED', gNow);
    } catch(e2) {}
  }
  autoUpdateFromUserscriptsDotOrg({
    name: 'San Francisco Bay Area Libraries - Books lookup on all sites',
    url: 'http://userscripts.org/scripts/source/1072.user.js',
    version: '4.0'
  });

  // ********** Link individual libraries on plsinfo.org book page
  // This code directly from 'Peninsula Library System Google Print Lookup'
  if (location.href.match(/^http:\/\/catalog\.plsinfo\.org\/(search|record)/i)) {
    try {

      // library name and id hash Array - for building the url later
      // Note: The order is important
      var theLibraryIDs = [];

      theLibraryIDs["Atherton"] = 55;
      theLibraryIDs["Belmont"] = 56;
      theLibraryIDs["Bookmobile"] = 72;
      theLibraryIDs["Brisbane"] = 73;
      theLibraryIDs["Burlingame[, -]+Easton"] = 52;
      theLibraryIDs["Burlingame"] = 53;
      theLibraryIDs["CaƱada College"] = 61;
      theLibraryIDs["College of San Mateo"] = 58;
      theLibraryIDs["Daly City[, -]+Bayshore"] = 86;
      theLibraryIDs["Daly City[, -]+John [D. ]*Daly"] = 63;
      theLibraryIDs["Daly City[, -]+Serramonte"] = 85;
      theLibraryIDs["Daly City[, -]+Westlake"] = 62;
      theLibraryIDs["East Palo Alto"] = 60;
      theLibraryIDs["Foster City"] = 59;
      theLibraryIDs["Half Moon Bay"] = 74;
      theLibraryIDs["Menlo Park[, -]+Belle Haven"] = 66;
      theLibraryIDs["Menlo Park"] = 65;
      theLibraryIDs["Millbrae"] = 64;
      theLibraryIDs["Pacifica[, -]+Sanchez"] = 75;
      theLibraryIDs["Pacifica[, -]+Sharp Park"] = 76;
      theLibraryIDs["Portola Valley"] = 77;
      theLibraryIDs["Redwood City[, -]+Fair Oaks"] = 68;
      theLibraryIDs["Redwood City[, -]+Redwood Shores"] = 69;
      theLibraryIDs["Redwood City[, -]+Schaberg"] = 70;
      theLibraryIDs["Redwood City[, -]+Downtown"] = 67;
      theLibraryIDs["Redwood City"] = 27;
      theLibraryIDs["San Bruno"] = 54;
      theLibraryIDs["San Carlos"] = 78;
      theLibraryIDs["San Mateo[, -]+Hillsdale"] = 81;
      theLibraryIDs["San Mateo[, -]+Marina"] = 82;
      theLibraryIDs["San Mateo[, -]+Main"] = 80;
      theLibraryIDs["San Mateo"] = 31;
      theLibraryIDs["Skyline College"] = 57;
      theLibraryIDs["South San Francisco[, -]+Grand Avenue"] = 83;
      theLibraryIDs["South San Francisco[, -]+Community Learning Center"] = 28;
      theLibraryIDs["South San Francisco"] = 84;
      theLibraryIDs["Woodside"] = 79;

      var xpathLibraryNameCells = "//tr[@class='bibItemsEntry']/td[1]";
      var libraryNameElems = document.evaluate(xpathLibraryNameCells, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

      for (var k = 0; k < libraryNameElems.snapshotLength; k++) { // iterate thru each library name table cell
        var theLibraryNameElem = libraryNameElems.snapshotItem(k);
        var theLibraryName = theLibraryNameElem.innerHTML;
        var theNamePattern;
        for (var theNameKey in theLibraryIDs) {
          if (! theLibraryIDs.hasOwnProperty(theNameKey)) continue;
          theNamePattern = new RegExp(theNameKey);
          var result;
          if ((result = theNamePattern.exec(theLibraryName)) !== null) {
            var theLibName = result[0];
            var theNewName = theLibraryName.replace(theNamePattern, '<a href="http://www.plsinfo.org/library-hours?tid=' + theLibraryIDs[theNameKey] + '" style="background-color:#FFC" target="_blank">' + theLibName + "</a>");
            theLibraryNameElem.removeChild(theLibraryNameElem.firstChild);
            theLibraryNameElem.innerHTML = theNewName;

            break;
          }
        }
      }
    } catch (e3) {}
  }

  Array.prototype.addUnique = function (elem) {
    for (var i = 0, len = this.length; i < len; i++) {
      if (this[i] === elem) {
        return;
      }
    }
    this.push(elem);
  }

  var ISBN = {
    checkISBN : function (aISBN) { // somewhat lax validation of ISBN
      var theISBN = aISBN;
      try {
        theISBN = theISBN.replace(/-+/g, '').replace(/x/g, "X");
        if (! theISBN.match(/^\d{9,12}[\dX]$/) || (theISBN.length !== 10 && theISBN.length !== 13)) { // a multiline string with isbn appended can still trigger get ignored without this check
          //if (getValue('DEVELOPER', false)) {
          //unsafeWindow.confirm("Not a valid ISBN (length): " + aISBN + "\n" + theISBN);
          //}
          return null;
        }
        var checksum = 0;
        if (theISBN.length === 10) {
          for (var i = 0; i < 9; i++) {
            checksum += parseInt(theISBN.charAt(i)) * (10 - i);
          }
          if (theISBN.charAt(9) === "X") {
            checksum += 10;
          } else {
            checksum += parseInt(theISBN.charAt(9));
          }
          if ((checksum % 11) === 0) {
            return theISBN;
          } else {
            //if (getValue('DEVELOPER', false)) {
            //unsafeWindow.confirm("Not a valid ISBN (10): " + aISBN + "\n" + theISBN);
            //}
            return null;
          }
        } else {
          for (var i = 0; i < 12; i++) {
            checksum += parseInt(theISBN.charAt(i)) * (i % 2 === 0?1:3);
          }
          checksum = 10 - (checksum % 10);
          if (checksum === 10) {
            checksum = 0;
          }
          if (parseInt(theISBN.charAt(12)) !== checksum) {
            //if (getValue('DEVELOPER', false)) {
            //unsafeWindow.confirm("Not a valid ISBN (13 X but not 0): " + aISBN + "\n" + theISBN);
            //}
            return null;
          } else {
            return theISBN;
          }
        }
      } catch(e4) {
        if (getValue('DEVELOPER', false)) {
          log("Error validating ISBN: " + aISBN + "\n\n" + e4);
        }
        return null;
      }
    },

    findGenericISBN : function(theArray, badass) {
      var isbn_re_1 = /isbn(?:-?1[03])?[=\/+:]((\d{3}-?)?\d{9}[\dX]|\d\d-\d{6}-\d-\d|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{5}-\d{3}-\d|\d-\d{3}-\d{5}-\d)/i;
      var isbn_re_2 = /isbn(?:-?1[03])?[=\/+:]+((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{5}-\d{3}-\d|\d-\d{3}-\d{5}-\d)/i;
      try {
        var bodyhtml = document.body.innerHTML;
        var found;
        try {
          theArray.addUnique(location.href.match(isbn_re_1)[1]);
        } catch (e5){}
        try {
          theArray.addUnique(document.title.match(isbn_re_2)[1]);
        } catch (e6){}
        if ( !document.links ) {
          document.links = document.getElementsByTagName("a");
        }
        var links = document.links;
        for (var a = 0, len = links.length; a < len; a++) {
          if (links[a].href.match(/\b(\d{9,12}[x0-9])\b/i)) {
            theArray.addUnique(RegExp.$1);
          }
        }
        if (badass) {
          var badass_isbn_re_1 = /ISBN[\s:-]?(?:1[03])?(?:\s|<[^<]*>)*[\n\r]*(?:(?:\s|<^>]*>)*[\n\r]*)*(?:\s|<[^<]*>)?([\dX-]{10,})(.*[\n\r]*)*/i;
          var badass_isbn_re_2 = /\b((?:\d{3}-?)?(?:\d{9}[\dX]|\d\d-\d{6}-\d-\d|\d{3}-\d{4}-\d{5}-\d|[\d-]{8,}[\dx]|\d{4}-\d{5}-\d|\d-\d{5}-\d{3}-\d|\d-\d{3}-\d{5}-\d))([\s\n\r,\dX-]*)/i;
          while (bodyhtml.match(badass_isbn_re_1)) {
            var maybeMultiple = RegExp.$1;
            bodyhtml = RegExp.$2;
            while (maybeMultiple.match(badass_isbn_re_2)) {
              found = RegExp.$1;
              maybeMultiple = RegExp.$2;
              theArray.addUnique(found);
            }
          }
        }
      } catch (e7) {}
    },

    getISBNs : function() {
      var results = [];
      var url = location.href;
      try {
        if (url.match(/^https?:\/\/(www\.)?librarything\.com/)) {
          var lta = $xp("//div[@class='isbn']/a");
          for (var lti = 0, len = lta.length; lti < len; lti++) {
            var a = lta[lti];
            if (a.innerHTML.match(/ISBN\s+((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)) {
              results.addUnique(RegExp.$1);
            }
          }
        } else if (url.match(/^https?:\/\/(www\.)?amazon\./)) {
          try {
            results.addUnique(url.match(/[\/=]((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)(\/|%|\&|;|\?|$)/i)[1]);
          } catch (e8) {}
          var c = document.body.innerHTML;
          var idx = c.indexOf('ISBN-10:</b>');
          if (idx !== -1 && c.substring(idx+12).match(/^[\n\r\s]*((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)) {
            results.addUnique(RegExp.$1);
          }
          idx = c.indexOf('ISBN-13:</b>');
          if (idx !== -1 && c.substring(idx+12).match(/^[\n\r\s]*((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)) {
            results.addUnique(RegExp.$1);
          }
        } else if (url.match(/abebooks\.com/)) {
          try {
            results.addUnique(document.title.match(/ISBN ((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)[1]);
          } catch (e9) {}
          try {
            results.addUnique(document.body.innerHTML.match(/class="isbn">((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)[1]);
          } catch (e10) {}
          var links = document.links;
          for (var a = 0, len = links.length; a < len; a++) {
            if (links[a].href.match(/bi=(\d{9,12}[x0-9])\b/i)) {
              results.addUnique(RegExp.$1);
            }
          }
        } else if (url.match(/\baddall\.com/)) {
          try {
            results.addUnique(url.match(/query=((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)[1]);
          } catch (e11) {};
          var links = document.links;
          for (var a = 0, len = links.length; a < len; a++) {
            if (links[a].href.match(/detail\/(\d{9,12}[x0-9])\.html/i)) {
              results.addUnique(RegExp.$1);
            }
          }
        } else if (url.match(/\bpowells\.com/)) {
          var dts = document.getElementsByTagName('dt');
          for (var i=0; i < dts.length; i++) {
            if (dts[i].innerHTML.match('ISBN([\s-]?1[03])?:')) {
              results.addUnique(dts[i].nextSibling.firstChild.text);
            }
          }
          var links = document.links;
          for (var a = 0, len = links.length; a < len; a++) {
            if (links[a].href.match(/\b(\d{9,12}[x0-9])\b/i)) {
              results.addUnique(RegExp.$1);
            }
            if (links[a].href.match(/\/biblio\/\d+-(\d{9,12}[x0-9])-\d+\b/i)) {
              results.addUnique(RegExp.$1);
            }
          }
        } else if (url.match('(alibris|half.ebay).com')) {
          var bs = document.getElementsByTagName('b');
          for (var i=0; i<bs.length; i++) {
            if (bs[i].innerHTML.match('ISBN([\s-]?1[03])?:')) {
              results.addUnique(bs[i].nextSibling.nextSibling.text);
            }
          }
          var links = document.links;
          for (var a = 0, len = links.length; a < len; a++) {
            if (links[a].href.match(/isbn\/(\d{9,12}[x0-9])\b/i)) {
              results.addUnique(RegExp.$1);
            }
          }
        } else if (url.match('books.google.com')) {
          try {
            results.addUnique(url.match(/v?id=ISBN((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)[1]);
          } catch (e11a) {}
          var links = document.links;
          for (var a = 0, len = links.length; a < len; a++) {
            if (links[a].href.match(/isbn(?:\%3D)?(\d{9,12}[x0-9])/i)) {
              results.addUnique(RegExp.$1);
            }
            if (links[a].href.match(/(?:ean=|keyword=|\/)(\d{9,12}[x0-9])/i)) {
              results.addUnique(RegExp.$1);
            }
          }
        } else if (url.match(/bookmooch\.com|searchbox.org/)) {
          try {
            results.addUnique(url.match(/detail\/((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)[1]);
          } catch (e11b) {}
        } else if (url.match(/worldcat(libraries)?\.org/)) {
          var c1 = document.body.innerHTML;
          var idx = c1.indexOf('<strong>ISBN: </strong>');
          if (idx !== -1) {
            if (c1.substring(idx+23).match(/[\n\r\s]*((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)) {
              results.addUnique(RegExp.$1);
            }
            if (c1.substring(idx+23).match(/[\n\r\s]*\d+\s+((\d{3}-?)?\d{9}[\dX]|\d{3}-\d{4}-\d{5}-\d|\d{4}-\d{5}-\d|\d-\d{3}-\d{5}-\d)/i)) {
              results.addUnique(RegExp.$1);
            }
          }
        }
      } catch (e12) {
        if (getValue('DEVELOPER', false)) {
          log("Exception: " + e12);
        }
      }
      this.findGenericISBN(results, getValue('Scrape?', true));
      return results; // sorting doesn't matter as search requests finish out of order
    }
  };

  // Don't need to define the rest of  the stuff if no ISBNs found
  var gFoundISBNs = ISBN.getISBNs();
  if (!gFoundISBNs || gFoundISBNs.length <= 0) return;

  // ********** Drag and Drop
  var Drag = function() {
    this.init.apply( this, arguments );
  };

  Drag.fixE = function( e ) {
    if ( typeof e === 'undefined' ) e = window.event;
    if ( typeof e.layerX === 'undefined' ) e.layerX = e.offsetX;
    if ( typeof e.layerY === 'undefined' ) e.layerY = e.offsetY;
    return e;
  };

  Drag.prototype.MAX_INDEX = 2147483644; // max int minus 1 for maskLayer minus 1 for dialogLayer

  Drag.prototype.zIndices = [];

  Drag.prototype.init = function( handle, dragdiv ) {
    this.div = dragdiv || handle;
    this.handle = handle;
    //if ( isNaN(parseInt(this.div.style.left)) ) this.div.style.left  = '0px';
    //if ( isNaN(parseInt(this.div.style.top)) ) this.div.style.top = '0px';
    this.onDragStart = function(){};
    this.onDragEnd = function(){};
    this.onDrag = function(){};
    this.onClick = function(){};
    this.mouseDown = addEventHandler(this.handle, 'mousedown', this.start, this);
    this.div.style.zIndex = this.MAX_INDEX - this.zIndices.length;
    this.zIndices.push(this.div);
    this.div.style.left = (((this.zIndices.length - 1)* 30 ) + 100) + 'px';
    this.div.style.top = ((this.zIndices.length - 1)* 30 ) + 'px';

    this.raise();
  };

  Drag.prototype.raise = function() {
    // raise to the top cheaply
    // Assumes that the div aren't destroyed once created
    if (this.div.style.zIndex !== this.MAX_INDEX) {
      var otherdiv = this.zIndices[0];
      otherdiv.style.zIndex = this.div.style.zIndex;
      this.div.style.zIndex = this.MAX_INDEX;
      this.zIndices[0] = this.div;
      this.zIndices[this.MAX_INDEX - otherdiv.style.zIndex] = otherdiv;
    }
  }

  Drag.prototype.start = function(e) {
    e = Drag.fixE(e);

    this.started = new Date();
    var y = this.startY = parseInt(this.div.style.top);
    var x = this.startX = parseInt(this.div.style.left);

    this.raise();
    this.div.style.opacity = 0.85;

    this.onDragStart(x, y);
    this.lastMouseX = e.clientX;
    this.lastMouseY = e.clientY;
    this.documentMove = addEventHandler(document, 'mousemove', this.drag, this);
    this.documentStop = addEventHandler(document, 'mouseup', this.end, this);
    if (e.preventDefault) e.preventDefault();
    return false;
  };

  Drag.prototype.drag = function(e) {
    e = Drag.fixE(e);
    var ey = e.clientY;
    var ex = e.clientX;

    // prevent the title from becoming unreachable
    if (ex < 0) ex = 0;
    if (ey < 0) ey = 0;

    var y = parseInt(this.div.style.top);
    var x = parseInt(this.div.style.left);
    var nx = ex + x - this.lastMouseX;
    var ny = ey + y - this.lastMouseY;
    this.div.style.left = nx + 'px';
    this.div.style.top  = ny + 'px';

    this.lastMouseX = ex;
    this.lastMouseY = ey;
    this.onDrag(nx, ny);
    if (e.preventDefault) {
      e.preventDefault();
    }
    return false;
  };

  Drag.prototype.end = function() {
    removeEventHandler(document, 'mousemove', this.documentMove);
    removeEventHandler(document, 'mouseup', this.documentStop);
    var time = (new Date()) - this.started;
    var x = parseInt(this.div.style.left),  dx = x - this.startX;
    var y = parseInt(this.div.style.top), dy = y - this.startY;
    this.onDragEnd( x, y, dx, dy, time );
    if ( (dx*dx + dy*dy) < (4*4) && time < 1e3 ) {
      this.onClick( x, y, dx, dy, time );
    }
    this.div.style.opacity = 1.0;
  };

  var fitString = function(s, w) {
    var filler = '...';
    var f = filler.length;
    if (w < 0) return s;
    if (w === 0) return '';
    if (s.length <= w) return s;
    if (w <= f) return s.substring(0, w);
    return s.substring(0, Math.floor(w / 2) - Math.floor(f/2)) + filler + s.substring(s.length - Math.floor((w+1)/2) + Math.floor((f+1)/2), s.length);
  };

  var gAltISBNs = {};

  var gFrame;

  var gLibDivs = {};

  var LIBS = ['Peninsula Library System', 'Palo Alto Library'];

  var LCFG = {
    'Peninsula Library System': {
      searchURL : 'http://catalog.plsinfo.org/search/i?SEARCH=%s',
      selfURLRegex : '^http:\/\/catalog\.plsinfo\.org\/.*$',
      titleFunc : function(page) {
        var doc = document.createElement('div');
        doc.innerHTML = page;
        if (isSafari() || isChrome()) {
          document.getElementsByTagName('body')[0].appendChild(doc);
        }
        return $xp1('//*[contains(concat( " ", @class, " " ), concat( " ", "bibInfoData", " " ))]//strong', doc);
      }
    },
    'Palo Alto Library': {
      searchURL : 'http://webcat.cityofpaloalto.org/ipac20/ipac.jsp?index=ISBNEX&term=%s',
      selfURLRegex : '^https?:\/\/webcat\.cityofpaloalto\.org\/.*$',
      titleFunc : function(page) {
        var doc = document.createElement('div');
        doc.innerHTML = page;
        if (isSafari() || isChrome()) {
          document.getElementsByTagName('body')[0].appendChild(doc);
        }
        return $xp1("//td[@colspan='3' and @width='100%']/a[@class='boldBlackFont2'][1]", doc);
      }
    }
  };

  var ll = {
    func_or_regex : function(f_or_r, content) {
      if (type(f_or_r) == 'Function') {
        return f_or_r(content)
      } else {
        return f_or_r.test(content)
      }
    },
    libraryAvailability : /\bIn Library|Check Shelf|>Checked in<|>Display Main</i,
    libraryRecentlyReturned : /Recently Returned|Recent Return/i,
    libraryRecentAvailability : /Recent Checkin/i,
    libraryCheckedOut : /\bChecked out\b/i,
    libraryStorage : /Storage/i,
    libraryOnHold : /(On Hold Shelf|[Bb]eing [Hh]eld|holds? on \w+ copy returned of \d+ cop|In transit \+\d+ hold)/i,
    libraryProcessing : /IN PROCESS(ING)?/i,  // yeah yeah I know
    libraryTransit : /(In )?Transit\b|>\s*Transit request\s*</i,
    libraryDueBack : /(\d{2} [A-Z][A-Z][A-Z] \d{2}|DUE \d{2}-\d{2}-\d{2})/i,
    libraryInternet : /INTERNET|electronic resource/,
    libraryAudio : /sound recording/,
    libraryVideo: /videorecording/,
    libraryWhereIsIt : /Where is it\?/,
    libraryOrdered : /COPY ORDERED|ITEM HAS BEEN ORDERED|On Order/i,
    libraryReordered : /REORDERED/i,
    libraryMissing : /MISSING/i,
    libraryLostAndPaid : /LOST AND PAID/i,
    libraryDefective : />Defective<|Damaged/i,
    libraryUseOnly : /LIB\. USE ONLY/i,
    libraryReference : />Reference Shelves</i,
    libraryNotFound : function(content) { // TODO : The title can be extractable in the second case
      return /No matches found; nearby STANDARD NOS are|Sorry, could not find anything matching/.test(content) ||
        (! /Call No./.test(content) && ! /Copy.Holding information/.test(content));
    },
    libraryNoItemInformation : /No Item Information/,
    libraryMending : /MENDING/i,
    libraryMendingReview : /Mends Review/i,
    libraryWithdrawn : /Withdrawn/i,
    libraryMultiple : /STANDARD NOS \(|titles matched/,
    xisbnAlternatesQueryOld : 'http://old-xisbn.oclc.org/webservices/xisbn/%s',
    xisbnAlternatesQueryNew : 'http://xisbn.worldcat.org/webservices/xid/isbn/%s?method=getEditions&format=xml',
    //xisbnMetadataQuery1 : 'http://xisbn.worldcat.org/webservices/xid/isbn/',
    //xisbnMetadataQuery2 : '?method=getMetadata&format=xml&fl=*',
    isbnREplain : /(\d{7,12}[\d|X])/ig,
    titleREplain : /title=["']([^"']+)["']/ig,

    makeAltQueryUrl : function(isbn, method) {
      var qurl;
      method = method || 1;
      if (method === 1) {
        return ll.xisbnAlternatesQueryNew.replace(/%s/g, isbn);
      } else {
        return ll.xisbnAlternatesQueryOld.replace(/%s/g, isbn);
      }
    },

    shouldStop : function(library) {
      return !!(!getValue(library, true) || location.href.match(LCFG[library].selfURLRegex));
    },

    stat_types : ['INSTANT', 'ELECTRONIC', 'DELAYED', 'NOPE', 'ERROR'],

    stat_labels : {
      INSTANT : 'Instant Gratification',
      ELECTRONIC : 'Electronic Editions',
      DELAYED : 'Delayed Gratification',
      NOPE : 'Not available',
      ERROR : 'Error Encountered'
    },

    stat_colors : {
      INSTANT : 'green',
      ELECTRONIC : '#3399CC',
      DELAYED : '#FF7633',
      NOPE : '#666666',
      ERROR : 'red'
    },

    makeLibraryWindow : function(library) {
      var libraryid = library.replace(/[^a-zA-Z0-9]/g, '');

      var body = document.getElementsByTagName('body')[0];
      if (!body) return;

      if (!gFrame) {
        gFrame = document.createElement('div');
        gFrame.id = 'sfbal_frame';
        gFrame.className += 'sfbal_frame';
        gFrame.state = 'collapsed';

        var theFrameTitle = document.createElement('div');
        theFrameTitle.id = 'sfbal_frame_title';
        theFrameTitle.className += 'sfbal_frame_title';
        if (ll.hopeCount) {
          theFrameTitle.style.background = gStyles[getValue('Style', 0)].frame_title_bg;
        } else {
          theFrameTitle.style.background = '#ccc';
        }
        theFrameTitle.appendChild(document.createTextNode(' San Francisco Bay Area Libraries '));

        ///////////////////////////////////////////////////////////////////////
        // PREFERENCES BUTTON
        var thePrefsImg = document.createElement( 'img' );
        thePrefsImg.src = gIcons.prefs;

        thePrefsImg.style.right = '22px';
        thePrefsImg.className += 'mini_button';

        thePrefsImg.setAttribute('alt', 'P');
        thePrefsImg.setAttribute('title', 'Click to change preferences');
        addEventHandler(thePrefsImg, 'click', configureScript);
        theFrameTitle.appendChild(thePrefsImg);
        // COLLAPSE BUTTON
        ///////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////
        // COLLAPSE BUTTON
        var theCollapseImg = document.createElement( 'img' );
        theCollapseImg.src = gIcons.plus;

        theCollapseImg.style.right = '3px';
        theCollapseImg.className += 'mini_button';

        theCollapseImg.setAttribute('alt', '+/-');
        theCollapseImg.setAttribute('title', 'Click to collapse');
        addEventHandler(theCollapseImg, 'click', function() {
          try{
            var newdisplay;
            if (gFrame.state === 'collapsed') {
              newdisplay = 'block';
              gFrame.state = 'expanded';
              theCollapseImg.src = gIcons.minus;
            } else {
              newdisplay = 'none';
              gFrame.state = 'collapsed';
              theCollapseImg.src = gIcons.plus;
            }
            var c = gFrame.firstChild;
            while (c) {
              if (c.getAttribute('class') === 'sfbal_lib') {
                c.style.display = newdisplay;
              }
              c = c.nextSibling;
            }
          } catch(e14) {
            log(e14);
          }
        });

        theFrameTitle.appendChild(theCollapseImg);
        // COLLAPSE BUTTON
        ///////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////
        // CLOSE BUTTON
        var theCloseImg = document.createElement( 'img' );
        theCloseImg.src = gIcons.close;
        theCloseImg.style.left = '3px';
        theCloseImg.className += 'mini_button';

        theCloseImg.setAttribute('alt', 'X');
        theCloseImg.setAttribute('title', 'Click to remove');
        addEventHandler(theCloseImg, 'click', function(){ try{gFrame.style.display="none";}catch(e14a){} } );

        theFrameTitle.appendChild(theCloseImg);
        // CLOSE BUTTON
        ///////////////////////////////////////////////////////////////////////

        theFrameTitle.drag = new Drag(theFrameTitle, gFrame);
        gFrame.appendChild(theFrameTitle);
        body.appendChild(gFrame);
      }

      if (gLibDivs[libraryid]) return;

      gLibDivs[libraryid] = document.createElement('div');
      gLibDivs[libraryid].id = libraryid;

      var tmp = gLibDivs[libraryid].style;
      gLibDivs[libraryid].className += 'sfbal_lib';
      if (gFrame.state === 'expanded') {
        // not !important to enable hiding later
        gLibDivs[libraryid].style.display = 'block';
      } else {
        // not !important to enable hiding later
        gLibDivs[libraryid].style.display = 'none';
      }
      var libtitle = document.createElement('div');
      libtitle.className += 'sfbal_lib_title';
      libtitle.appendChild(document.createTextNode(' ' + library + ' '));
      gLibDivs[libraryid].appendChild(libtitle);

      gFrame.appendChild(gLibDivs[libraryid]);

      for (var sti = 0, len = ll.stat_types.length; sti < len; sti++) {
        var the_stat_type = ll.stat_types[sti];
        var etype = getValue('Show' + the_stat_type, 'expanded');
        if (etype == 'no') continue;

        var the_group_title = document.createElement('div');
        the_group_title.id = libraryid + "_" + the_stat_type + "_title";
        the_group_title.className += 'sfbal_type_group_title';
        the_group_title.style.display = 'none';
        the_group_title.alt = the_group_title.title = 'Click to collapse or expand';

        var the_type_group = document.createElement('div');
        the_type_group.id = libraryid + "_" + the_stat_type;
        the_type_group.className += 'sfbal_type_group';

        if (etype == 'expanded') {
          the_group_title.innerHTML = '&uarr; ' + ll.stat_labels[the_stat_type];
          the_type_group.style.display = 'block';
        } else {
          the_group_title.innerHTML = '&darr; ' + ll.stat_labels[the_stat_type];
          the_type_group.style.display = 'none';
        }
        addEventHandler(the_group_title, 'click', function(){
          try {
            var the_group_ID = this.id.replace(/_title$/, "");
            var the_group = document.getElementById(the_group_ID);
            if (the_group.style.display === 'none') {
              the_group.style.display = "block";
              this.innerHTML = '&uarr; ' + this.innerHTML.substring(2);
            } else {
              the_group.style.display = "none";
              this.innerHTML = '&darr; ' + this.innerHTML.substring(2);
            }
          } catch(e15){}
        });
        gLibDivs[libraryid].appendChild(the_group_title);
        gLibDivs[libraryid].appendChild(the_type_group);
      }
    },

    shop_urls : {
      // 'amazon' : 'http://www.amazon.com/exec/obidos/ISBN=%s',
      'amazon' : 'http://www.amazon.com/exec/obidos/ASIN/%s/',
      'bookfinder' : 'http://www.bookfinder.com/search/?isbn=%s&st=xl&ac=qr',
      'isbndb': 'http://isbndb.com/search-all.html?kw=%s',
      'museophile': 'http://www.museophile.com/cgi/archive/isbn?%s',
      'shopwiki': 'http://www.shopwiki.com/search/%s',
      'barnesandnoble': 'http://search.barnesandnoble.com/booksearch/isbninquiry.asp?isbn=%s',
      'borders': 'http://www.borders.com/online/store/SearchResults?keyword=%s&type=0&simple=1',
      'ebay': 'http://books.search.ebay.com/%s',
      'powells': 'http://www.powells.com/cgi-bin/biblio?isbn=%s'
    },

    insertLinks: function(details, library) {
      var libraryid = library.replace(/[^a-zA-Z0-9]/g, '');
      for (var i = 0, len = details.haz.length; i < len; i++) {
        var the_stat_type = details.haz[i][1];
        if (getValue('Show' + the_stat_type, 'expanded') == 'no') continue;

        ll.makeLibraryWindow(library);

        var bkid = details.isbn + "_" + libraryid + "_" + the_stat_type;
        if (document.getElementById(bkid)) continue;
        if (the_stat_type !== 'NOPE') ll.hopeCount++;
        var book = document.createElement('div');
        book.id = bkid;
        book.className += 'sfbal_book';

        var ltlink = document.createElement('a');
        ltlink.href = 'http://www.librarything.com/isbn/' + details.isbn;
        ltlink.target = '_blank';
        ltlink.alt = 'This book on LibraryThing';
        ltlink.title = 'This book on LibraryThing';
        ltlink.textContent = 'LT';
        ltlink.className += 'sfbal_book_link';
        ltlink.style.color = ll.stat_colors[the_stat_type];

        book.appendChild(ltlink);

        if (details.orig_isbn) {
          var aalink = document.createElement('a');
          aalink.href = ll.shop_urls[getValue('Shop', 'amazon')].replace(/%s/, details.orig_isbn);
          aalink.target = '_blank';
          aalink.alt = aalink.title = 'This book on ' + getValue('Shop', 'amazon');
          aalink.textContent = 'OISBN';
          aalink.className += 'sfbal_book_link';
          aalink.style.color = ll.stat_colors[the_stat_type];

          book.appendChild(document.createTextNode(' | '));
          book.appendChild(aalink);
        }

        var alink = document.createElement('a');
        alink.href = ll.shop_urls[getValue('Shop', 'amazon')].replace(/%s/, details.isbn);
        alink.target = '_blank';
        alink.alt = alink.title = 'This book on ' + getValue('Shop', 'amazon');
        alink.className += 'sfbal_book_link';
        alink.style.color = ll.stat_colors[the_stat_type];

        if (details.title) {
          alink.textContent = fitString(details.title, 100)
          alink.alt = alink.title = details.title + ' on ' + getValue('Shop', 'amazon');
        } else {
          alink.textContent = details.isbn;
          alink.alt = 'This book on ' + getValue('Shop', 'amazon');
          alink.title = alink.alt;
        }
        book.appendChild(document.createTextNode(' | '));
        book.appendChild(alink);

        var hrefTitle = details.haz[i][0];
        var thislink = document.createElement('a');
        with (thislink) {
          href = LCFG[library].searchURL.replace(/%s/g, details.isbn);
          alt = hrefTitle;
          title = hrefTitle;
          target = '_blank';
          textContent = 'LIB';
          className += "sfbal_book_link";
        }
        thislink.style.color = ll.stat_colors[the_stat_type];

        book.insertBefore(document.createTextNode(' | '), book.firstChild);
        book.insertBefore(thislink, book.firstChild);

        var status_type_block = document.getElementById(libraryid + "_" + the_stat_type);
        status_type_block.appendChild(book);
        var status_type_block_title = document.getElementById(libraryid + "_" + the_stat_type + "_title");
        status_type_block_title.style.display = 'block';
        if (status_type_block_title.innerHTML.match(/ \[(\d+)\]$/)) {
          var count = parseInt(RegExp.$1) + 1;
          status_type_block_title.innerHTML = status_type_block_title.innerHTML.replace(/ \[\d+\]$/, " [" + count + "]");
        } else {
          status_type_block_title.innerHTML += ' [1]';
          status_type_block_title.style.display = 'block';
          //status_type_block.style.display = 'block';
        }
      }
      ll.updateRemaining();
    },

    lookupDoneFor : {},

    titleFoundFor : {},

    makeBookStatusCallback : function(details, library) {
      return function(results) {
        ll.xhrCount--;
        page = results.responseText;

        // plsinfo loves pagination now >:/
        if (library == 'Peninsula Library System' && page.indexOf("View additional copies or search for a specific volume/copy") != -1) {
          var doc = document.createElement('div');
          doc.innerHTML = page;
          if (isSafari() || isChrome()) {
            document.getElementsByTagName('body')[0].appendChild(doc);
          }
          var more = $xp1('//input[@value="View additional copies or search for a specific volume/copy"]/..', doc);
          if (more && more.action) {
            ll.xhrCount++;
            xhr(more.action, ll.makeBookStatusCallback(details, library));
          }
        }

        var availTypes;
        try {
          availTypes = ll.BookStatusCache[details.isbn][library].haz;
        } catch(e_no_status_cache) {
          availTypes = [];
        }

        var error = false;

        // NOTE : Always set it to false only later on
        var title_extractable = true;

        if (ll.func_or_regex(ll.libraryNotFound, page)) {
          availTypes.addUnique(["Not available." , "NOPE"]);
          title_extractable = false;
        } else {
          if (ll.func_or_regex(ll.libraryMultiple, page)) {
            availTypes.addUnique(["Multiple matches" , "INSTANT"]);
            title_extractable = false;
          }
          if (ll.func_or_regex(ll.libraryAvailability, page)) {
            availTypes.addUnique(["On the shelf now!" , "INSTANT"]);
          }
          if (ll.func_or_regex(ll.libraryRecentAvailability, page)) {
            availTypes.addUnique(["Recent Checkin" , "INSTANT"]);
          }
          if ( ll.func_or_regex(ll.libraryRecentlyReturned, page) ) {
            availTypes.addUnique(["Recently Returned" , "INSTANT"]);
          }
          if ( ll.func_or_regex(ll.libraryReference, page) ) {
            availTypes.addUnique(["Reference Shelves" , "INSTANT"]);
          }
          if ( ll.func_or_regex(ll.libraryInternet, page) && ll.func_or_regex(ll.libraryAudio, page) ) {
            availTypes.addUnique(["Electronic Audiobook Available" , "ELECTRONIC"]);
          } else if ( ll.func_or_regex(ll.libraryInternet, page) ) {
            availTypes.addUnique(["eBook available" , "ELECTRONIC"]);
          }
          if ( ll.func_or_regex(ll.libraryCheckedOut, page) ) {
            availTypes.addUnique(["Checked out" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryStorage, page) ) {
            availTypes.addUnique(["In Storage" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryProcessing, page) ) {
            availTypes.addUnique(["In Processing" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryTransit, page) ) {
            availTypes.addUnique(["In Transit" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryOnHold, page) ) {
            availTypes.addUnique(["Being Held" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryDueBack, page) ) {
            var due = page.match(ll.libraryDueBack)[1];
            availTypes.addUnique(["Due back" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryMending, page) ) {
            availTypes.addUnique(["Mending" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryMendingReview, page) ) {
            availTypes.addUnique(["Mending Review" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryWithdrawn, page) ) {
            availTypes.addUnique(["Withdrawn" , "NOPE"]);
          }
          if ( ll.func_or_regex(ll.libraryInternet, page) && ll.func_or_regex(ll.libraryVideo, page) ) {
            availTypes.addUnique(["Electronic Video Available" , "ELECTRONIC"]);
          } else if ( ll.func_or_regex(ll.libraryVideo, page) ) {
            availTypes.addUnique(["Video Recording" , "INSTANT"]);
          }
          if ( ll.func_or_regex(ll.libraryOrdered, page) ) {
            availTypes.addUnique(["Ordered" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryReordered, page) ) {
            availTypes.addUnique(["Reordered" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryMissing, page) ) {
            availTypes.addUnique(["Missing" , "NOPE"]);
          }
          if ( ll.func_or_regex(ll.libraryLostAndPaid, page) ) {
            availTypes.addUnique(["Lost and Paid" , "NOPE"]);
          }
          if ( ll.func_or_regex(ll.libraryDefective, page) ) {
            availTypes.addUnique(["Defective" , "NOPE"]);
          }
          if ( ll.func_or_regex(ll.libraryUseOnly, page) ) {
            availTypes.addUnique(["Library Use Only" , "DELAYED"]);
          }
          if ( ll.func_or_regex(ll.libraryNoItemInformation, page) ) {
            if (! ll.func_or_regex(ll.libraryInternet, page)) {
              availTypes.addUnique(["No Item Information" , "DELAYED"]);
            }
          }
          if (!availTypes.length && ll.func_or_regex(ll.libraryWhereIsIt, page) ) { // maybe multiple editions
            availTypes.addUnique(["Click for details" , "DELAYED"]);
          }
          if (!availTypes.length) {
            error = true;
            title_extractable = false;
            if (library === 'Peninsula Library system') {
              var tpage = page;
              while (tpage.match(/td\s+width.*22.*>(.+)<\/td>((.*[\n\r]*)*)/i)) {
                var extraType = RegExp.$1;
                tpage = RegExp.$2;
                extraType = extraType.replace(/<!--.*-->/g,'')
                  .replace(/.nbsp;/g, ' ')
                  .replace(/^\s+/, '')
                  .replace(/\s+$/, '');
                availTypes.addUnique([extraType, "ERROR"]);
              }
            } else if (library === 'Palo Alto Library') {
              var tpage = page;
              while (tpage.match(/(?:white|FCFDC|ormation)">(.+)<\/a>((.*[\n\r]*)*)/i)) {
                var extraType = RegExp.$1;
                tpage = RegExp.$2;
                extraType = extraType.replace(/<!--.*-->/g,'')
                  .replace(/.nbsp;/g, ' ')
                  .replace(/^\s+/, '')
                  .replace(/\s+$/, '');
                availTypes.addUnique([extraType, "ERROR"]);
              }
            }
            if (!availTypes.length) {
              title_extractable = false;
              availTypes.addUnique(["Error: Click for library page. You can check by searching manually." , "ERROR"]);
            }
          }
        }
        if (availTypes.length && title_extractable && !ll.titleFoundFor[details.isbn]) {
          var title = LCFG[library].titleFunc(page)
          if (title) ll.titleFoundFor[details.isbn] = title.innerHTML;
        }
        if (ll.titleFoundFor[details.isbn]) {
          details.title = ll.titleFoundFor[details.isbn];
        }
        if (availTypes.length) {
          details.haz = availTypes;
          if (!error && getValue('CacheBookStatus?', true)) {
            details.saved = gNow;
            ll.BookStatusCache[details.isbn] = {};
            ll.BookStatusCache[details.isbn][library] = details;
            setValue('book_status_cache', ll.BookStatusCache);
          }
          ll.insertLinks (details, library);
        }
        return;
      };
    },

    BookStatusCache : getValue('CacheBookStatus?', true)?getValue('book_status_cache', {}):{},
    
    AltISBNsCache : getValue('CacheAltISBNs?', true)?getValue('alt_isbns_cache', {}):{},

    xhrCount : 0,

    hopeCount : 0,

    updateRemaining : function() {
      // if (ll.xhrCount) log(ll.xhrCount + " pending");
      var f = document.getElementById('sfbal_frame_title');
      if (!f) return; // one library or nothing
      f = f.firstChild;
      if (ll.hopeCount) {
        f.textContent = f.textContent.replace(/^(\d+: )?/, "" + ll.hopeCount + ": ");
        f.parentNode.style.backgroundColor = gStyles[getValue('Style', 0)].frame_title_bg;
      }
      if (ll.xhrCount > 0) {
        if (f.textContent.match(/ \[checking \d+\.\.\.\]$/)) {
          f.textContent = f.textContent.replace(/ \[checking \d+\.\.\.\]$/, ' [checking ' + ll.xhrCount + '...]');
        } else {
          f.textContent += ' [checking ' + ll.xhrCount + '...]';
        }
        setTimeout(arguments.callee, 5000);
        return;
      } else {
        if (f.textContent.match(/ \[checking \d+\.\.\.\]$/)) {
          f.textContent = f.textContent.replace(/ \[checking \d+\.\.\.\]$/, "");
        }
      }
    },


    crossFillBookTitles : function() {
      // When running with multiple libraries enabled, one library might provide a title
      // for an ISBN which another library may not have. At the end of all fetches, we
      // spread those found titles to the other libraries

      ll.updateRemaining();
      if (ll.xhrCount <= 0) {

        var libs = [];

        for (var li = 0, len = LIBS.length; li < len; li++) {
          if (ll.shouldStop(LIBS[li])) continue;

          libs.push(LIBS[li]);
        }

        if (libs.length < 2) return;

        for (var i = 0, len = libs.length; i < len; i++) {
          var lib = libs[i];
          var libraryid = lib.replace(/[^a-zA-Z0-9]/g, '');

          for (var sti = 0, lenx = ll.stat_types.length; sti < lenx; sti++) {
            var the_stat_type = ll.stat_types[sti];
            if (getValue('Show' + the_stat_type, 'expanded') == 'no') continue;

            for (var isbn in ll.titleFoundFor) {
              if (! ll.titleFoundFor.hasOwnProperty(isbn)) continue;

              var bkid = isbn + "_" + libraryid + "_" + the_stat_type;

              var elem = document.getElementById(bkid);
              if (!elem) continue;

              if (elem.lastChild.textContent === isbn) {
                log("Fixing " + isbn + " for " + bkid + " to " + ll.titleFoundFor[isbn]);
                elem.lastChild.textContent = ll.titleFoundFor[isbn];
              }
            }
          }
        }
      }
    },

    makeAltCallback: function(org_isbn) {
      return function(results) {
        ll.xhrCount--;

        var xisbns = results.responseText.match(ll.isbnREplain);
        ll.AltISBNsCache[org_isbn] = {
          'saved': gNow,
          'isbns': xisbns||[]
        };
        setValue('alt_isbns_cache', ll.AltISBNsCache);
        if (xisbns && xisbns.length > 0) {
          ll.doLookup(xisbns, org_isbn);
        }
      };
    },

    doLookup : function (isbns, orig_isbn) {

      for (var ix = 0; ix < isbns.length ; ix++) {
        var the_isbn = ISBN.checkISBN(isbns[ix]);

        if (!the_isbn || ll.lookupDoneFor[the_isbn]) continue;

        ll.lookupDoneFor[the_isbn] = true;
        for (var i = 0, len = LIBS.length; i < len; i++) {
          var lib = LIBS[i];

          if (ll.shouldStop(lib)) continue;

          try {
            var saved = ll.BookStatusCache[the_isbn][lib]["saved"];
            if (saved && (gNow - saved) < SIX_HOURS) {
              ll.insertLinks(ll.BookStatusCache[the_isbn][lib], lib);
            } else {
              // rough purge of cache
              for (var cached in ll.BookStatusCache) {
                if (! ll.lookCache.hasOwnProperty(cached)) continue;
                if ((gNow - ll.BookStatusCache[cached][lib]['saved']) >= SIX_HOURS) {
                  delete ll.BookStatusCache[cached][lib];
                }
              }
              setValue('book_status_cache', ll.BookStatusCache);
            }
          } catch (e16) {}

          ll.xhrCount++;
          if (! orig_isbn) {
            xhr(LCFG[lib].searchURL.replace(/%s/g, the_isbn), ll.makeBookStatusCallback({'isbn': the_isbn}, lib));
          } else {
            xhr(LCFG[lib].searchURL.replace(/%s/g, the_isbn), ll.makeBookStatusCallback({'isbn': the_isbn, 'orig_isbn': orig_isbn}, lib));
          }
        }

        if (!getValue('xISBN?', true) || orig_isbn) continue;

        if (ll.AltISBNsCache[the_isbn] && ll.AltISBNsCache[the_isbn]['saved']) {
          if ((gNow - ll.AltISBNsCache[the_isbn]['saved']) < SIX_HOURS) {
            ll.doLookup(ll.AltISBNsCache[the_isbn]['isbns']);
            continue;
          } else {
            // time to purge old cached entries
            for (var cached in ll.AltISBNsCache) {
              if (! ll.AltISBNsCache.hasOwnProperty(cached)) continue;
              if ((gNow - ll.AltISBNsCache[cached]['saved']) >= SIX_HOURS) {
                delete ll.AltISBNsCache[cached];
              }
            }
            setValue('alt_isbns_cache', ll.AltISBNsCache);
          }
        }
        ll.xhrCount++;
        xhr(ll.makeAltQueryUrl(the_isbn), ll.makeAltCallback(the_isbn));
      } // for
      // calling it indirectly because if setTimeout fails due to restrictions, the
      // rescheduling will fail from ll.crossFillBookTitles() also, which will leave a
      // [x remaining]
      // showing forever in the title
      ll.crossFillBookTitles();
    }
  }; // var ll =


  try {
    // prevent the library from automatically going back to the main page
    // from our search result after a timeout
    try {
      if (document.location.href.match(/^http:\/\/webcat\.cityofpaloalto\.org/i)) {
        unsafeWindow.Timer = function(){};
        if (document.location.href.match(/^http:\/\/webcat\.cityofpaloalto\.org\/ipac20\/ipac\.jsp\?index=ISBNEX.term=(\d{9,12}[0-9xX])/)) {
          var isbn = RegExp.$1;
          $xp("//td[@colspan='3' and @width='100%']/a[@class='boldBlackFont2'][1]").forEach(function(s) {
            s.innerHTML = '<a href="' + ll.shop_urls[getValue('Shop', 'amazon')].replace(/%s/, isbn) + '" style="background-color:#EEC">' + s.innerHTML + '</a>';
          });
        }
      } else if (document.location.href.match(/^http:\/\/catalog\.plsinfo\.org\/(search|record)/i)) {
        unsafeWindow.timeout_url = document.location.href;
        if (document.location.href.match(/\/search\/i\?(?:SEARCH=)?(\d{9,12}[\dx])$/i)) {
          var isbn = RegExp.$1;
          $xp('//*[contains(concat( " ", @class, " " ), concat( " ", "bibInfoData", " " ))]//strong').forEach(function(s) {
            s.innerHTML = '<a href="' + ll.shop_urls[getValue('Shop', 'amazon')].replace(/%s/, isbn) + '" style="background-color:#EEC">' + s.innerHTML + '</a>';
          });
        } else {
          var isbn_inserted = 0;
          $xp('//*[contains(concat( " ", @class, " " ), concat( " ", "bibInfoData", " " ))]').forEach(function(s) {
            if (isbn_inserted) return;
            var isbn = s.innerHTML.replace(/[^0-9X]/, '');
            if (ISBN.checkISBN(isbn)) {
              s.innerHTML = '<a href="' + ll.shop_urls[getValue('Shop', 'amazon')].replace(/%s/, isbn) + '" style="background-color:#EEC">' + s.innerHTML + '</a>';
              isbn_inserted = 1;
              $xp('//*[contains(concat( " ", @class, " " ), concat( " ", "bibInfoData", " " ))]//strong').forEach(function(title) {
                title.innerHTML = '<a href="' + ll.shop_urls[getValue('Shop', 'amazon')].replace(/%s/, isbn) + '" style="background-color:#EEC">' + title.innerHTML + '</a>';
              });
            }
          });
        }
      }
    } catch (e0) {}

    ll.doLookup(gFoundISBNs);
    [
      "a.sfbal_book_link { " +
        "font-weight: bold; " +
        "text-decoration: none; " +
        "font-family: Arial,Helvetica,FreeSans,'Luxi-sans','Nimbus Sans L',sans-serif; " +
        "background: transparent none no-repeat scroll 0 0; " +
        "padding: 0px; " +
        "margin: 0px; " +
        "width: auto; " +
      "}",
      "a:hover.sfbal_book_link {color: black}",
      ".sfbal_frame { " +
        "border: 1px solid black; " +
        "position: absolute; " +
        "text-align: left; " +
        "font-size: 8pt; " +
        "line-height: 1.1em; " +
        "margin: 0px; " +
        "padding: 0px; " +
        "width: auto; " +
      "}",
      ".sfbal_frame_title { " +
        "border: solid; " +
        "border-width: 0px; " +
        "cursor: move; " +
        "font-family: Arial,Helvetica,FreeSans,'Luxi-sans','Nimbus Sans L',sans-serif; " +
        "font-weight: bold; " +
        "padding: 6px 47px 6px 27px; " +
        "text-align: left; " +
        "color: black; " +
        "margin: 0px; " +
        "width: auto; " +
      "}",
      ".sfbal_lib { " +
        "border: 0px; " +
        "text-align: left; " +
        "font-size: 8pt; " +
        "line-height: 1.1em; " +
        "margin: 0px; " +
        "padding: 0px; " +
      "}",
      ".sfbal_lib_title { " +
        "background: " + gStyles[getValue('Style', 0)].lib_title_bg_color + "; /* Old browsers */ " +
        "border-width: 0px 0px 1px 0px; " +
        "border: none; " +
        "color: " + gStyles[getValue('Style', 0)].lib_title_text_color + "; " +
        "font-family: Arial,Helvetica,FreeSans,'Luxi-sans','Nimbus Sans L',sans-serif; " +
        "font-weight: bold; " +
        "margin: 0px; " +
        "padding: 5px; " +
        "text-align: right; " +
        "width: auto; " +
      "}",
      ".sfbal_type_group { " +
        "color: #120; " +
        "font-family: Arial,Helvetica,FreeSans,'Luxi-sans','Nimbus Sans L',sans-serif; " +
        "margin: 0px; " +
        "padding: 0px; " +
        "text-align: left; " +
        "width: auto; " +
        gStyles[getValue('Style', 0)].type_group_bg_css +
      "}",
      ".sfbal_type_group_title { " +
        gStyles[getValue('Style', 0)].type_group_title_css +
        "border-bottom: 0px 0px 0px 1px solid orange; " +
        "color: black; " +
        "font-family: Arial,Helvetica,FreeSans,'Luxi-sans','Nimbus Sans L',sans-serif; " +
        "margin: 0px; " +
        "padding: 3px 3px; " +
        "text-align: left; " +
        "cursor: pointer; " +
        "width: auto; " +
      "}",
      ".sfbal_book { " +
        "display: block; " +
        "text-align: left; " +
        "font-family: Arial,Helvetica,FreeSans,'Luxi-sans','Nimbus Sans L',sans-serif; " +
        gStyles[getValue('Style', 0)].book_bg_css +
        "padding: 3px 3px; " +
        "margin: 0px; " +
        "border: none; " +
        "width: auto; " +
        "color: #120; " +
      "}",
      ".mini_button { " +
          "margin: 0 0 0 0; " +
          "position: absolute; " +
          "top: 5px; " +
          "border: none; " +
          "cursor: pointer; " +
      "}"
    ].forEach(function(s) {addStyle(s.makeImportant());});
  } catch (e18) {
    if (getValue('DEVELOPER', false)) {
      unsafeWindow.confirm(e18);
    }
    return;
  }
}
)();

// vim: set et fdl=2 fdm=indent fenc=utf-8 ff=unix ft=javascript sts=0 sw=2 ts=2 nowrap :