// ==UserScript==
// @namespace     http://snowplow.org/martin/greasemonkey
// @name          killfile
// @description   Provides a killfile for certain blogs.  Covers livejournal, haloscan comments, most typepad blogs, most blogspot blogs, scienceblogs.com, and more as I add them.  Trolls can be killfiled with a single click.
// ==/UserScript==
//
// This is a script for greasemonkey 0.6, a plugin for the FireFox
// web browser. (http://greasemonkey.mozdev.org/)
// It likely will NOT work in earlier versions of
// greasemonkey, nor will it work in any other browser.
//
// The intention of this script is to hide the comments of commentors
// you, the reader, do not wish to hear from.  In that respect, it's
// like an old usenet killfile.  It does not affect what other
// visitors to the site will see, nor does it affect what you see
// before the page finishes loading (due to some limitations in
// Mozilla, you really don't want to rewrite HTML that's not entirely
// there yet).  This is not a tool meant for handling spam, only for
// an individual comment reader to avoid having to see comments they
// don't wish to see.
//
// This script is maintained by martin@snowplow.org (Daniel Martin)
// This script is provided under the terms of the GNU General
// Public License, version 2, which can be found at
//   http://www.gnu.org/copyleft/gpl.html
// Specifically, note that this script comes with NO guarantees,
// not even the implied guarantee of merchantibility or fitness for a 
// specific purpose.
//
// Please let me know of other blogs you'd like covered with this,
// and I welcome patches.  Future versions of this script may be
// released under a license different from the GPL.
//
// Future enhancements of this script will hopefully include a
// configuration page so that the user can add their own blog
// configurations themselves.

// Acknowledgements:
// The GreaseMonkey folks, for putting together GM.
// Stan Dyck, whose original killfile script for
//   the blog pharyngula was the inspiration for this script
//   (Although no actual code was borrowed)
//   You can see his script at
//   http://sgd.homelinux.net/greasemonkey/pharyngulakillfile.user.js
// Christina Schelin, http://christina267.wordpress.com/
//   for village voice code and wordpress updates

var ourVersion = "20091002.1";

var ourURL = 'http://snowplow.org/martin/greasemonkey/killfile.user.js';

function upgrade() {
  window.open(ourURL,"killfile");
}

GM_registerMenuCommand("Get latest killfile script", upgrade);

function checkVersion() {
  GM_xmlhttpRequest({
    method:"GET",
        url:"http://snowplow.org/martin/greasemonkey/versions.xml?killfile-"+ourVersion,
        headers:{
        "User-Agent":"Greasemonkey/0.6",
          "Accept":"application/xml,text/xml",
          },
        onload:
      function(responseDetails) {
        var parser = new DOMParser();
        var dom = parser.parseFromString(responseDetails.responseText,
                                         "application/xml");
        var programNodes = dom.getElementsByTagName('program');
        if (!programNodes) {
          GM_log("Can't find program: " + responseDetails.responseText);
        }
        for (var i=0; i < programNodes.length; i++) {
          if (programNodes[i].getAttribute('id') == 'killfile') {
            var programNode = programNodes[i];
            var version = programNode.getElementsByTagName("version")[0];
            if (version.textContent > ourVersion)
              {
                alert("There's a new version of killfile available.\n" +
                      "You are running version " + ourVersion + "\n" +
                      "The latest available version is " + version.textContent + "\n" +
                      "You can upgrade from the 'Tools->User script commands' menu");
              }
            GM_setValue('lastVersionCheckTime',Math.round((new Date()).getTime()/1000));
          }
        }
      }
    });
}

function autoCheckVersion() {
  var nowsecs = Math.round((new Date()).getTime()/1000);
  var prevcheck = GM_getValue('lastVersionCheckTime',0);
  if (nowsecs > prevcheck + 60*60*24*3) {
    checkVersion();
  }
}

function showComment(spot) {
  spot.childNodes[0].style.display = 'block';
  spot.childNodes[1].style.display = 'none';
}
function hideComment(spot) {
  spot.childNodes[0].style.display = 'none';
  spot.childNodes[1].style.display = 'block';
}

function escapeHTML(s) {
    var div = document.createElement('div');
    var text = document.createTextNode(s);
    div.appendChild(text);
    return div.innerHTML;
}

function reviewContent() {
  var trolllist = GM_getValue("Trolllist", ';');
  var snap = document.evaluate("//div[@class='dtm_killfile_commentholder']", document, null,
                               XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  for (var i=0; i < snap.snapshotLength; i++) {
    var spot = snap.snapshotItem(i);
    if (trolllist.indexOf(";" + spot.getAttribute("dtm_killfile_user") + ";") > -1) {
      hideComment(spot);
    } else {
      showComment(spot);
    }
  }
}

function addTroll(troll) {
  var trolllist = GM_getValue("Trolllist", ';');
  if (trolllist.indexOf(";" + troll + ";") < 0) {
    trolllist = trolllist + troll + ";";
    GM_setValue("Trolllist", trolllist);
  }
  reviewContent();
}

function delTroll(troll) {
  var trolllist = GM_getValue("Trolllist", ';');
  if (trolllist.indexOf(";" + troll + ";") > -1) {
    trolllist = trolllist.replace(";" + troll + ";",";");
    GM_setValue("Trolllist", trolllist);
  }
  reviewContent();
}

function handleClick(evt) {
  var holderdiv = this;
  var evtar = evt.target;
  var clazz = evtar.getAttribute('class');
  if (clazz) {
      if (clazz == "dtm_killfile_show") {
        evt.stopPropagation();
        evt.preventDefault();
        showComment(holderdiv);
      } else if (clazz == "dtm_killfile_hide") {
        evt.stopPropagation();
        evt.preventDefault();
        hideComment(holderdiv);
      } else if (clazz == "dtm_killfile_kill") {
        evt.stopPropagation();
        evt.preventDefault();
        addTroll(holderdiv.getAttribute('dtm_killfile_user'));
      } else if (clazz == "dtm_killfile_unkill") {
        evt.stopPropagation();
        evt.preventDefault();
        delTroll(holderdiv.getAttribute('dtm_killfile_user'));
      }
  }
}

// the basic scenario is essentially wordpress-like, so the
// wordpress scenarios below are pretty short
function basicScenario() {
  return {
  commenttopxpath: "//ol[contains(concat(' ',@class,' '),' commentlist ')]/li",
      sigbit: ".//cite[1]",
      replaceXpath: "child::node()",
      mangleBefore: null,
      mangleAppend: null,
      tabXpath: null,
      precedingBit: '',
      followingBit: '',
      sigUserMatch: '$2',
      sigHrefMatch: '$1',
      aHrefAttribute: 'href', // sometimes title
      get sigpat() {
        var s = ('^ *' + this.precedingBit + 
                 ' *(?:<a [^>]*?(?:\\b(?:' + this.aHrefAttribute + 
                 ') *="([^>"]*)")?[^>]*>)?(\\S[^<]*[^ <]|[^ <]) *(?:</a>)? *' + 
                 this.followingBit + '.*');
        return new RegExp(s,"");
      },
      foreachComment:
    function(loopBody) {
      if (!loopBody) {return null;}
      var snap = document.evaluate(this.commenttopxpath,
                                   document, null, 
                                   XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
      for (var i=0; i < snap.snapshotLength; i++) {
        loopBody(snap.snapshotItem(i));
      }
    },
      getUserspec:
    function(commentNode) {
      if (!commentNode) {return null;}
      var sigsnap = document.evaluate(this.sigbit, commentNode, null,
                                      XPathResult.ANY_UNORDERED_NODE_TYPE, null);
      var nd = sigsnap.singleNodeValue;
      if (nd == null) {progresslog("xpath " + this.sigbit + " gave null: " + commentNode);return null;}
      var sigHTML = nd.innerHTML;
      if (!sigHTML) {sigHTML = nd.textContent;}
      else {sigHTML = sigHTML.replace(/\&nbsp;/g, ' ');}
      sigHTML = sigHTML.replace(/\s+/g, ' ');
      sigHTML = sigHTML.replace(/\s+$/, '');
      sigHTML = sigHTML.replace(/^\s+/, '');
      // in case people match on attribute tags
      var sigre = this.sigpat;
      if (! sigre.test(sigHTML)) {
        progresslog("Didn't match: html " + sigHTML.toSource() + " and regexp " + sigre.toSource());
        return null;
      }
      var user = sigHTML.replace(sigre,this.sigUserMatch);
      var href = sigHTML.replace(sigre,this.sigHrefMatch);
//      progresslog("user: html {" + sigHTML.toSource() + "} and regexp {" + sigre.toSource() + "} gave " + escape(user) + "!" + escape(href));
      return [user, escape(user) + "!" + escape(href)];
    },
      divHTML:
    '<div class="dtm_killfile_shown"></div>' +
      '<div class="dtm_killfile_hidden" style="display:none"><p>Comment by __SHORTUSER__ blocked.' +
      ' [<a href="tag:remove%20user%20from%20killfile" class="dtm_killfile_unkill">unkill</a>]' +
      '&#8203;[<a href="tag:show%20comment" class="dtm_killfile_show">show&nbsp;comment</a>]' +
      '</p></div>',
      divHTMLuser:
    function(user, userspec) {
      return this.divHTML.replace(/__SHORTUSER__/g,escapeHTML(user)).replace(/__USER__/g,escapeHTML(userspec));
    },
      getEmptyHolder:
    function(commentNode, user, userspec) {
      var ddiv = document.createElement('div');
      ddiv.innerHTML = this.divHTMLuser(user, userspec);
      if (this.tabXpath) {
        var tabNode = document.evaluate(this.tabXpath, commentNode, null,
                                        XPathResult.ANY_UNORDERED_NODE_TYPE, null);
        tabNode = tabNode.singleNodeValue;
        if (tabNode) {
          var tabtarget = ddiv.childNodes[1].childNodes[0];
          tabtarget.insertBefore(tabNode.cloneNode(true),tabtarget.firstChild);
        }
      }
      ddiv.setAttribute("class", "dtm_killfile_commentholder");
      ddiv.setAttribute("dtm_killfile_user", userspec);
      return ddiv;
    },
      spanHTML: '[<a href="tag:killfile%20user" class="dtm_killfile_kill">kill</a>]' +
      '&#8203;[<a href="tag:hide%20comment" class="dtm_killfile_hide">hide&nbsp;comment</a>]',
      spanHTMLuser:
    function(user, userspec) {
      return this.spanHTML.replace(/__SHORTUSER__/g,escapeHTML(user)).replace(/__USER__/g,escapeHTML(userspec));
    },
      mangleCommentContent:
    function(contentNode, user, userspec) {
      if (!contentNode) {return null;}
      var snap3 = null;
      var useBefore = true;
      if (this.mangleBefore) {
        snap3 = document.evaluate(this.mangleBefore, contentNode, null,
                                  XPathResult.ANY_UNORDERED_NODE_TYPE, null);
      } else if (this.mangleAppend) {
        snap3 = document.evaluate(this.mangleAppend, contentNode, null,
                                  XPathResult.ANY_UNORDERED_NODE_TYPE, null);
        useBefore = false;
      }
      if (snap3 && snap3.singleNodeValue) {
        var target = snap3.singleNodeValue;
        var cspan = document.createElement('span');
        cspan.innerHTML = this.spanHTMLuser(user, userspec);
        if (useBefore) {
          target.parentNode.insertBefore(cspan,target);
        } else {
          target.appendChild(cspan);
        }
        return contentNode;
      }
      progresslog("Can't find insertion point for kill button");
      progresslog(contentNode);
      progresslog(contentNode.childNodes[0]);
      return null;
    },
      insertCommentHolder:
    function(commentNode, holderDiv) {
      if (! (commentNode && holderDiv)) {return null;}
      var snap2 = document.evaluate(this.replaceXpath, commentNode, null,
                                    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
      if (snap2.snapshotLength == 0) {return null;}
      snap2.snapshotItem(0).parentNode.insertBefore(holderDiv,snap2.snapshotItem(0));
      var contentNode = holderDiv.firstChild;
      for (var j=0; j < snap2.snapshotLength; j++) {
        var removedNode = snap2.snapshotItem(j);//.parentNode.removeChild(snap2.snapshotItem(j));
        contentNode.appendChild(removedNode);
      }
      holderDiv.addEventListener('click', handleClick, true);
      return contentNode;
    },
      handleComment:
    function(commentNode) {
      //progresslog("Comment found " + location.href);
      var us = this.getUserspec(commentNode);
      if (! us) {progresslog("Can't find user");return null;}
      var commentTwo = this.mangleCommentContent(commentNode, us[0], us[1]);
      if (!commentTwo) {progresslog("No kill button"); return null;}
      var ddiv = this.getEmptyHolder(commentTwo, us[0], us[1]);
      if (! ddiv) {progresslog("Can't get empty holder");return null;}
      var contentdiv = this.insertCommentHolder(commentTwo, ddiv);
      if (! contentdiv) {progresslog("Can't insert comment holder");return null;}
    },
      manglePage:
    function () {
      var me = this;
      this.foreachComment(function (c) {me.handleComment(c)});
    }
  };
}

var kf_debug = GM_getValue("debug",0);

function progresslog(logstr) {
  if (kf_debug) {GM_log(logstr);}
}

function wordpressScenario() {
  return {
    get mangleBefore() {return this.sigbit + '/following::*'},
      __proto__:basicScenario()
      };
}

// Thanks to Christina Schelin, http://christina267.wordpress.com
function wordpressScenario2() {
  return {
    commenttopxpath: "//ol[@id='commentlist']/li",
    sigbit: "span[@class='commentauthor']",
    __proto__:wordpressScenario()
  };
}

function riotactScenario() {
  return {
    get mangleAppend() {return this.sigbit + '/..'},
    precedingBit: 'Comment by ',
    followingBit: ' [-\u2014] ',
    commenttopxpath: "//ol[@id='commentlist']/li",
    __proto__:basicScenario()
  };
}

function pandagonNewScenario() {
  return {
  commenttopxpath: "//div[@id='content']/div[starts-with(@class,'comment-body')]",
  sigbit: "div[@class='comment-posted']",
  precedingBit: 'Comment #\\d*: ',
  followingBit: ' on \\S+ at <a',
  mangleAppend: "div[@class='comment-posted']",
      __proto__:basicScenario()
      };
}

function feministingNewScenario() {
  return {
  commenttopxpath: "//div[@id='comments']//" + 
      "div[contains(concat(' ', @class, ' '), ' comment ')]/" +
      "div[contains(concat(' ', @class, ' '), ' inner ')]",
  sigbit: ".//div[contains(concat(' ', @class, ' '), ' commentByline ')]/" +
      "span[contains(concat(' ', @class, ' '), ' author ')]",
  precedingBit: '',
  followingBit: '',
  get mangleAppend() {return this.sigbit + '/..'},
      __proto__:basicScenario()
      };
}

function feministingNewFrontPageScenario() {
  return {
  commenttopxpath: "//div[@class='mainPadding']/div[@class='entryBody']",
  replaceXpath: ".|preceding-sibling::div[1][@class='entryTitle']",
  sigbit: "div[@class='posted']",
  precedingBit: 'Posted by ',
  followingBit: ' at <a[^<>]*>[^<>]*</a> \\| in.*',
  mangleAppend: "div[@class='posted']",
      __proto__:basicScenario()
      };
}

function pandagonScenario() {
  return {
  sigbit: "span[@class='commentauthor']",
      __proto__:wordpressScenario()
      };
}

function pandagon2Scenario() {
  return {
  commenttopxpath: "//ol[@id='commentlist']/li",
  sigbit: "child::*[@class='commentauthor']",
      __proto__:wordpressScenario()
      };
}

// thanks to Christina Schelin, http://christina267.wordpress.com/
function voiceScenario() {
  return {
  commenttopxpath: "//div[@id='commentsList']/div",
  sigbit: "div[@class='comments_info']/b",
      __proto__:wordpressScenario()
      };
}

function livejournalScenario() {
  return {
  commenttopxpath: "//table[starts-with(@id,'ljcmt') and .//span[contains(concat(' ',@class,' '),' ljuser ')]]",
      sigbit: "descendant::span[contains(concat(' ',@class,' '),' ljuser ')][1]",
      replaceXpath: ".",
      get mangleBefore() {return this.sigbit + "/following::*[1]";},
      tabXpath: 'descendant::img[1]',
      precedingBit: '(?:.*?</a>)?',
      __proto__:basicScenario()
      };
}

function livejournalScenario_1a() {
  return {
  commenttopxpath: "//span[starts-with(@id,'ljcmt') and ./table[position() = 1 and @class='talk-comment']]",
      __proto__:livejournalScenario()
      };
}

function livejournalScenario2() {
  return {
  commenttopxpath: "//div[@class='comment_wrapper']",
      sigbit: ".//div[@class='comment_postedby']/*[1]",
      tabXpath: '',
      __proto__:livejournalScenario()
      };
}

function livejournalScenario3() {
  return {
  commenttopxpath: "//div[starts-with(@id,'ljcmt') and child::div[@class='entry']]",
      replaceXpath: "./h3|./div[@class='entry']|./div[@class='talklinks']",
      tabXpath: '',
      __proto__:livejournalScenario()
      };
}

function livejournalScenario4() {
  return {
  commenttopxpath: "//div[starts-with(@id,'ljcmt')]",
      replaceXpath: "child::node()",
      tabXpath: '',
      __proto__:livejournalScenario()
      };
}

function livejournalScenario5() {
  return {
    commenttopxpath: "//td[@id='content']//table[@class='heading_bar']/following-sibling::div",
    get mangleAppend() {return this.sigbit + "/parent::node()";},
    mangleBefore: null,
    __proto__:livejournalScenario4()
  };
}
function livejournalScenario6() {
  return {
    commenttopxpath: "//table[@class='entrybox'][2]/tbody/tr/td/table/tbody/tr/td/div",
    __proto__:livejournalScenario5()
  };
}

function ljfriendsScenario() {
  // If you want this, see the comments in "scenariolist" below
  return {
  commenttopxpath: "//table[not(ancestor::table) and " + 
      "(preceding::a[starts-with(@href,'http://www.livejournal.com/userinfo.bml?')]) " +
      "and (descendant::a[substring-after(@href,'.livejournal.com/')=''])]",
      sigbit: "descendant::a[contains(@href,'livejournal.com/') and " +
      "substring-after(@href,'livejournal.com/')=''][1]/@href",
      replaceXpath: ".",
      sigpat: /(http:\/\/(\w+)\..*)/,
      sigUserMatch: '$2',
      sigHrefMatch: '$1',
      mangleAppend: "descendant::a[contains(@href,'livejournal.com/') and " +
      "substring-after(@href,'livejournal.com/')=''][1]/..",
      spanHTML: '<br>[<a href="tag:killfile%20user" class="dtm_killfile_kill">kill</a>]' +
      '&#8203;[<a href="tag:hide%20comment" class="dtm_killfile_hide">hide&nbsp;comment</a>]',
      __proto__:basicScenario()
      };
}

function pandasThumbScenario() {
  return {
  commenttopxpath: "//div[@id='comment-panels']//div[contains(concat(' ', @class, ' '), ' comment ') and not(boolean(.//p[@id='comment-update-message']))]",
      sigbit: ".//div[contains(concat(' ', @class, ' '), ' comment-header ')]//span[contains(concat(' ', @class, ' '), ' author ')]",
      precedingBit: '',
      followingBit: '',
      aHrefAttribute: 'title',
      mangleBefore: ".//span[contains(concat(' ', @class, ' '), ' byline ')]",
      __proto__:basicScenario()
  };
}

function blogspotDLScenario() {
  return {
    commenttopxpath: "//dl[@id='comments-block']/dt",
      sigbit: ".",
      precedingBit: '(?: *<div class="profile-image-container">.*?</div>)? *',
      followingBit: '\\b *said\\W+',
      mangleAppend: ".",
      replaceXpath: ".|following-sibling::dd[1]",
      __proto__:basicScenario()
  };
}

function blogspotDivScenario() {
  return {
  commenttopxpath: "//div[@class='blogComments']/div[@class='blogComment']",
      sigbit: "div[@class='byline']",
      sigUserMatch: '$2$3',
      sigpat: /^ *<a[^>]*>#<\/a> posted by (?:<span[^>]*comment-icon[^>]*>.*?<\/span>)? *(?:(?:<a [^>]*?(?:\b(?:href) *="([^>"]*)")?[^>]*>)?(\S[^<]*[^ <]|[^ <]) *(?:<\/a>)?|<span class=['"]anon[^>]*>([^<]*)<\/span>) +: +[^a-zA-Z]+[AP]M[^a-zA-Z]*$/,
      mangleAppend: "div[@class='byline']",
      __proto__:basicScenario()
  };
}

function blogspotTableScenario() {
  return {
  commenttopxpath: "//table[@class='MainTable']//td[@class='MessageCell']/P",
      sigbit: "*[@class='byline']",
      sigUserMatch: '$1',
      sigHrefMatch: '$2',
      sigpat: /^ *(\S[^|]*[^<>]*?\S|\S) *\| *(?:<a href="([^\"]*)"[^>]*>\S+<\/a> *\|)?[^|]*\|[^|]*$/,
      mangleAppend: "*[@class='byline'][last()]",
      __proto__:basicScenario()
  };
}


function pharyngulaScenario() {
  return {
  commenttopxpath: "//div[@id='comments']//div[@class='commentContent']",
      sigbit: "p[@class='commentFooter']",
      precedingBit: 'Posted by:',
      followingBit: '(?:(?:<a [^>]*>)?<img [^>]*>(?:</a>)?)? *\\|[^|]*$',
      mangleAppend: "p[@class='commentFooter']",
      __proto__:basicScenario()
  };
}

function mtScenario2() {
  return {
  commenttopxpath: "//div[@class='content']/div[@class='othercomment']",
      sigbit: "p[@class='posted']",
      followingBit: '(?:<a [^>]*><img [^>]*><\\/a>)? +at +[^\\]]*\\[link\\][^\\]]*$',
      mangleAppend: "p[@class='posted']",
      __proto__:pharyngulaScenario()
  };
}

function typepadScenario() {
  return {
      commenttopxpath: "//div[@class='comments-content']/div[contains(concat(' ',@class,' '),' comment ')]",
      sigbit: "p[@class='comment-footer']",
      mangleAppend: "p[@class='comment-footer']",
      __proto__:pharyngulaScenario()
  };
}

function haloscanScenario() {
    return {
        commenttopxpath: "//table[@class='MainTable']//td[@class='MessageCell']",
        sigbit: "descendant::span[@class='byline']",
        mangleAppend: "descendant::span[@class='byline']",
        mangleBefore: null,
        replaceXpath: "child::node()[position() > 2 and position() < last()]",
        sigUserMatch: '$1',
        sigHrefMatch: '$2',
        sigpat: /^ *(\S[^|]*.*?\S|\S) *\| *(?:<a href="([^\"]*)"[^>]*>Homepage<\/a> *\|)?[^|]*\|[^|]*$/,
        __proto__:basicScenario()
  };
}

function giveemhellharryScenario1() {
  return {
  commenttopxpath: "//h3[@id='comment']/following-sibling::ol[1]/li",
      sigbit: "small[last()]",
      followingBit: ' \\w+ +\\d+,[^,]*<a',
      mangleAppend: "small[last()]",
      precedingBit: '[^<]*',
      __proto__:basicScenario()
  }
}

function giveemhellharryScenario2() {
  return {
  commenttopxpath: "//div[@class='comments']//div[@class='comment']",
      sigbit: "div[@class='commentauthor']",
      replaceXpath: "div[@class='commenttitle' or @class='commenttext' or @class='commentauthor']",
      mangleAppend: "div[@class='commentauthor']",
      precedingBit: ' *[Bb][Yy] *',
      __proto__:basicScenario()
  }
}

function soapbloxScenario1() {
  return {
  commenttopxpath: "//form[@name='rateForm']/a[@name != 'p0' and starts-with(@name,'p')]/following-sibling::div[1][starts-with(@class,'commentLevel')]",
      sigbit: "div[last()-1]",
      mangleAppend: "div[last()]",
      precedingBit: '<i>by: *',
      followingBit: ' @[^@]*',
      __proto__:basicScenario()
  }
}

function truthoutScenario() {
  return {
  commenttopxpath: "//div[@class='commenthead']",
      replaceXpath: ".|following-sibling::div[1]",
      sigbit: "following-sibling::div[1]//a[starts-with(@href,'/blog/user/')][last()]",
      mangleBefore: ".//br[last()]",
      __proto__:basicScenario()
  };
}

function athleticsNationScenario() {
  return {
      delayed: true,
      commenttopxpath: "//div[@class='cx' and .//p[@class='cl']]",
      sigbit: ".//p[@class='cb']/a[1]",
      replaceXpath: ".",
      mangleAppend: ".//p[@class='cl']",
      __proto__:basicScenario()
      };
}
function athleticsNationScenario2() {
  return {
      commenttopxpath: "//div[starts-with(@id,'comment_item_')]/div[starts-with(@id,'comment_inner_')]",
      sigbit: "./p[@class='byline' or @class='by']/a[1]",
      mangleAppend: "./p[@class='byline' or @class='by']",
      __proto__:athleticsNationScenario()
      };
}
function athleticsNationOldScenario() {
  return {
      commenttopxpath: "//a[@name='commenttop'][1]/following-sibling::form//table",
      sigbit: "./tbody/tr[last()]//a[1]",
      replaceXpath: ".",
      mangleBefore: "descendant::br[last()]",
      __proto__:basicScenario()
      };
}

function tnrScenario() {
    return {
      commenttopxpath: "//div[@class='discuss-header']",
      replaceXpath: ".|following-sibling::div[1]",
      sigbit: "b[1]",
      mangleAppend: ".",
      __proto__:basicScenario()
    };
}

// One of the many Moveable Type templates
function mtScenario1() {
  return {
      commenttopxpath: "//div[@class='comments-body']",
      sigbit: "span[@class='comments-post']",
      precedingBit: 'Posted by:',
      followingBit: 'at <a [^>]*>[^<>]*</a> *$',
      mangleAppend: "span[@class='comments-post']",
      __proto__:basicScenario()
  };
}

function freeperScenario() {
  return {
      commenttopxpath: "//div[@class='b2'][1]/following-sibling::div[@class='n2']",
      sigbit: "preceding-sibling::*[@class='a2'][1]",
      replaceXpath: ".|preceding-sibling::node()[position() < 7][self::div or self::text()]",
      precedingBit: '<a .*</a> posted on <b>.*?</b> by',
      followingBit: '',
      mangleAppend: ".",
      __proto__:basicScenario()
  };
}

function nytimesBlogsScenario() {
  return {
      commenttopxpath: "//ul[@class='commentlist']/li",
      sigbit: ".//p[last()]/cite",
      replaceXpath: ".",
      precedingBit: '\\W* Posted by ',
      followingBit: '',
      mangleAppend: ".//p[last()]",
      __proto__:basicScenario()
  };
}

// fetlife.com
// Added thanks to xtina@twilite.org
function fetScenario() {
    return {
        commenttopxpath: "//div[contains(@class,'group_comment')]",
        sigbit: "./*[contains(@class,'span-14')]/div[@class='quiet']",
        precedingBit: '<a href="/users/[0-9]*">',
        followingBit: '</a> responded .*',
        __proto__:wordpressScenario()
    };
}

var scenariolist = {
  pamshouseblend:[{scenario:soapbloxScenario1,hrefpat:"^[^/]*//[^/]*/showDiary.do"}],
  escapepod:[{scenario:wordpressScenario,hrefpat:"^[^/]*//[^/]*/[0-9]"}],
  ravelry:[{scenario:wordpressScenario,hrefpat:"^[^/]*//blog\.[^/]*/[0-9]"}],
  blogsome:[{scenario:pandagon2Scenario,hrefpat:"^[^/]*//pandagon[.][^/]*/[0-9]"}],
  pandagon:[{scenario:pandagonScenario,hrefpat:"^[^/]*//[^/]*/[0-9]"},
            {scenario:pandagonNewScenario,hrefpat:"^[^/]*//[^/]*/.*\\bsite/comments/"}],
  fauxrealtho:[{scenario:pandagon2Scenario,hrefpat:"^[^/]*//[^/]*/[0-9]"}],
  billcara:[{scenario:mtScenario2,hrefpat:"^[^/]*//[^/]*/archives/"}],
  // also thanks to http://christina267.wordpress.com
  scalzi:[{scenario:wordpressScenario,hrefpat:"^[^/]*//[^/]*/whatever"}],
  philliesnation:[{scenario:wordpressScenario,hrefpat:"^[^/]*//[^/]*/archives/"}],
  feministe:[{scenario:wordpressScenario,hrefpat:"^[^/]*//[^/]*/blog/archives/"}],
  powweb:[{scenario:wordpressScenario,hrefpat:"^[^/]*//feministe[.][^/]*/blog/archives/"}],
  amptoons:[{scenario:wordpressScenario,hrefpat:"^[^/]*//[^/]*/blog/archives/"}],
  'the-riotact':[{scenario:riotactScenario, // wordpress 1.5 ?
   xpath:"//ol[@id='commentlist']/li[1]//cite"}],
  wordpress:[
  {scenario:wordpressScenario, // wordpress 1.5 ?
   xpath:"//ol[contains(concat(' ',@class,' '),' commentlist ')]/li[1]/descendant::*[self::cite or self::span][1][self::cite]"
   },
  {scenario:pandagonScenario,  // wordpress 2 ?
   xpath:"//ol[contains(concat(' ',@class,' '),' commentlist ')]/li[1]/span[@class='commentauthor']"},
  {scenario:wordpressScenario2,
   xpath:"//ol[@id='commentlist']/li[1]/span[@class='commentauthor']"},
  ],
  blogger:[
  {scenario:blogspotDLScenario,
   hrefpat:"^[^/]*//[^/]*/comment[.]g[?]",
   xpath:"//dl[@id='comments-block']/dt"},
  ],
  blogspot:[
  {scenario:blogspotDLScenario,
   xpath:"//dl[@id='comments-block']/dt"},
  {scenario:blogspotDivScenario,
    xpath:"//div[@class='blogComments']/div[@class='blogComment']"},
  {scenario:blogspotTableScenario,
    xpath:"//table[@class='MainTable']//td[@class='MessageCell']"}
  ],
  livejournal:[
  {scenario:livejournalScenario2,
   hrefpat:"^[^/]*//((syndicated|community)\\.[^/]*/)?[^/]*/[0-9]",
   xpath:"//div[contains(concat(' ',@class,' '),' comment_wrapper ')]",
  },
  {scenario:livejournalScenario3,
   xpath: "//div[@class='box' and starts-with(@id,'ljcmt')]",
   hrefpat:"^[^/]*//((syndicated|community)\\.[^/]*/)?[^/]*/[0-9]",
  },
  {scenario:livejournalScenario4,
   xpath: "//div[starts-with(@id,'ljcmt') and descendant::span[contains(concat(' ',@class,' '),' ljuser ')]]",
   hrefpat:"^[^/]*//((syndicated|community)\\.[^/]*/)?[^/]*/[0-9]",
  },
  {scenario:livejournalScenario5,
   xpath: "//td[@id='content']//table[@class='heading_bar']/following-sibling::div",
   hrefpat:"^[^/]*//((syndicated|community)\\.[^/]*/)?[^/]*/[0-9]",
  },
  {scenario:livejournalScenario6,
   xpath: "//table[@class='entrybox'][2]/tbody/tr/td/table/tbody/tr/td/div",
   hrefpat:"^[^/]*//((syndicated|community)\\.[^/]*/)?[^/]*/[0-9]",
  },
  {scenario:livejournalScenario_1a,
   xpath:"//span[starts-with(@id,'ljcmt')]/table[@class='talk-comment']",
   hrefpat:"^[^/]*//((syndicated|community)\\.[^/]*/)?[^/]*/[0-9]",
  },
  {scenario:livejournalScenario,
   hrefpat:"^[^/]*//((syndicated|community)\\.[^/]*/)?[^/]*/[0-9]",
  },
  // Commented out because I think it's ugly.  Your taste may vary, so you can
  // uncomment this next line to kill/unkill people on your lj friends list.
  // Intended primarily for communities you like mostly, but one or two
  // posters get on your nerves.
  // {scenario:ljfriendsScenario,hrefpat:"^[^/]*//[^/]*/friends"},
  ],
  journalfen:   // Basically, old livejournal code
  [
  {scenario:livejournalScenario2,
   hrefpat:".*/[0-9]*\\.html",
   xpath:"//div[@class='comment_wrapper']",
  },
  {scenario:livejournalScenario3,
   xpath: "//div[@class='box' and starts-with(@id,'ljcmt')]",
   hrefpat:".*/[0-9]*\\.html",
  },
  {scenario:livejournalScenario4,
   xpath: "//div[starts-with(@id,'ljcmt') and descendant::span[@class='ljuser']]",
   hrefpat:".*/[0-9]*\\.html",
  },
  {scenario:livejournalScenario5,
   xpath: "//td[@id='content']//table[@class='heading_bar']/following-sibling::div",
   hrefpat:".*/[0-9]*\\.html",
  },
  {scenario:livejournalScenario6,
   xpath: "//table[@class='entrybox'][2]/tbody/tr/td/table/tbody/tr/td/div",
   hrefpat:".*/[0-9]*\\.html",
  },
  {scenario:livejournalScenario,
   hrefpat:".*/[0-9]*\\.html",
  },
  // Commented out because I think it's ugly.  Your taste may vary, so you can
  // uncomment this next line to kill/unkill people on your lj friends list.
  // Intended primarily for communities you like mostly, but one or two
  // posters get on your nerves.
  // {scenario:ljfriendsScenario,hrefpat:".*/friends\\b"},
  ],
  scienceblogs:
  [
  {scenario:pharyngulaScenario,hrefpat:"^[^/]*//[^/]*/\\w+/[0-9]"},
  // Do all blogs at scienceblogs.com fit this format?
  ],
  feministing:
  [
  {scenario:feministingNewScenario,
   xpath:"//div[@id='comments']//div[@class='commentByline']/span[contains(concat(' ', @class, ' '), ' author ')]"},
  {scenario:typepadScenario,hrefpat:"^[^/]*//[^/]*/archives/[0-9]"},
  {scenario:feministingNewFrontPageScenario,hrefpat:"^[^/]*//community\\.[^/]*/[^/]*$"},
  ],
  typepad:
  [
  {scenario:typepadScenario,hrefpat:"^[^/]*//[^/]*/\\w+/[0-9]",
   xpath:"//div[@class='comments-content'][1]/div[contains(concat(' ',@class,' '),' comment ')][1]/p[@class='comment-footer']"},
  // There are some other typepad pages with a VERY BAD comment structure
  // (no surrounding element to grab ahold of, so that you have to
  // divide up the comment stream where-ever you see P's of a certain class)
  // Not that I won't get to them eventually, but it's going to take some
  // serious xpath voodoo
  ],
  pandasthumb:[
  {scenario:pandasThumbScenario,hrefpat:"^[^/]*//[^/]*/archives/"}
  ],
  haloscan:[
  {scenario:haloscanScenario,
   xpath:"//body/table[@class='MainTable']//td[@class='MessageCell']"}
  ],
  giveemhellharry:[
  {scenario:giveemhellharryScenario1, hrefpat:"^[^/]*//[^/]*/blog/"},
  {scenario:giveemhellharryScenario2, hrefpat:"^[^/]*//[^/]*/page/community/",
   xpath: "//div[@class='comments']"}
  ],
  truthout:[
  {scenario:truthoutScenario,
   hrefpat:"^https?://forum.truthout.org/blog/story/",}
  ],
  athleticsnation:[
  {scenario:athleticsNationScenario,
  xpath:"//div[@class='cx']//p[@class='cl']",
  hrefpat:"^[^?]*$"
  },
  {scenario:athleticsNationScenario2,
  xpath:"//div[@id='comments_list']/div[starts-with(@id,'comment_item_')]/div[starts-with(@id,'comment_inner_')]",
  hrefpat:"^[^?]*$",
  },
  {scenario:athleticsNationOldScenario,
  xpath:"//a[@name='commenttop']/following-sibling::form//table[1]",
  }  
  ],
  tnr:[{scenario:tnrScenario,hrefpat:"^[^/]*//[^/]*/doc_posts",}],
  smalldeadanimals:[{scenario:mtScenario1,hrefpat:"^[^/]*//[^/]*/\\w*/[0-9]",}],
  freerepublic:[{scenario:freeperScenario, hrefpat:"^[^/]*//[^/]*/focus/f-(news|chat)/[0-9]*/(replies|posts)",}],
  nytimes:[{scenario:nytimesBlogsScenario, hrefpat:"^[^/]*//[^/]*\\.blogs\\.[^/]*/([0-9]*/){3}[\\w-]+/",}],
  villagevoice:[{scenario:voiceScenario,hrefpat:"^[^/]*//[^/]*/news/[0-9]",}],
  fetlife:[{scenario:fetScenario,hrefpat:"[^/]*//[^/]*/groups/[0-9]*/group_posts/"}],
};

// sbNation.com is really a family of related blogs
var sbNation =
   ("beyondtheboxscore buffalorumblings minorleagueball halosheaven lookoutlanding "+
    "lonestarball bluebirdbanter draysbay camdenchat overthemonster pinstripealley letsgotribe " +
    "royalsreview blessyouboys twinkietown southsidesox azsnakepit truebluela mccoveychronicles " +
    "gaslampball purplerow talkingchop fishstripes amazinavenue federalbaseball thegoodphight " +
    "crawfishboxes brewcrewball vivaelbirdos bleedcubbieblue bucsdugout redreporter blogabull " +
    "clipsnation sactownroyalty mavsmoneyball poundingtherock blazersedge goldenstateofmind badlefthook " +
    "sundaymorningqb cornnation burntorangenation crimsonandcreammachine dawgsports rollbamaroll " +
    "swampball andthevalleyshook rockytoptalk aseaofblue conquestchronicles bruinsnation udubdish " +
    "buildingthedam aroundtheoval blackshoediaries schembechlerhall blacksburgbeacon tomahawknation " +
    "carolinamarch ramblinracket provopride blocku rakesofmallow podiumcafe faketeams bloggingtheboys " +
    "bleedinggreennation hogshaven windycitygridiron prideofdetroit dailynorseman fieldgulls patspulpit " +
    "cincyjungle dawgsbynature behindthesteelcurtain stampedeblue bigcatcountry musiccitymiracles " +
    "arrowheadpride milehighreport globalfutbol").split(" ");
for (c in sbNation) {
  scenariolist[sbNation[c]] = scenariolist['athleticsnation'];
}

function findScenario() {
  var domain = location.host.replace(/^(?:.*[.])?((?!www\.)[a-zA-Z0-9-]+)(?:\.\w{3}|(?:\.\w{2})+)$/,'$1');
  domain = domain.toLowerCase();
  var matchl = scenariolist[domain];
  var re;
  if (matchl) {
    var matchind;
    for (matchind in matchl) {
      var match = matchl[matchind];
      var found = true;
      if (found && match.hrefpat) {
        re = new RegExp(match.hrefpat);
        found = re.test(location.href);
      }
      if (found && match.xpath) {
        found = document.evaluate(match.xpath, document, null, XPathResult.BOOLEAN_TYPE,null);
        if (found) {found = found.booleanValue;}
        if (found) {progresslog("matched. " + location.href);} else {progresslog("Not matched.  " + location.href);}
      }
      if (found) {
        return match.scenario();
      }
    }
  }
  return null;
}
// main.  i.e. "onload"
var model = findScenario();
if (model) {
  if (model.delayed) {
    window.addEventListener("load", function () {model.manglePage();}, false);
  } else {
    model.manglePage();
  }
  window.addEventListener("load", reviewContent, false);
  window.addEventListener("load", autoCheckVersion, false);
}


/// Local Variables: ///
/// mode: C++ ///
/// tab-width: 4 ///
/// indent-tabs-mode: nil ///
/// c-basic-offset: 2 ///
/// End: ///
