Google Readerにはてなブックマーク件数を表示する

Google x はてブ(Greasemonkey版)を参考に作ってみた。

// ==UserScript==
// @name          GR+?B
// @namespace     http://d.hatena.ne.jp/nozom/
// @description   show ?B count in Google Reader
// @include       http://www.google.com/reader/view/*
// ==/UserScript==

// original author is id:kusigahama
// see http://d.hatena.ne.jp/kusigahama/20051207#p1

(function() {
  var timerID;
  var busy = false;
  var url2count = new Object();

  String.prototype.htmlescape = function() {
    return this.replace(/&/, "&amp;").replace(/</g, "&lt;");
  }

  function setBookmarkCount(targetNode, href, count) {
    var str = (count > 0 ? "" + count : "no") + " user" + (count > 1 ? "s" : "");
    var a = document.createElement("a");
    a.setAttribute('href', "http://b.hatena.ne.jp/entry/" + href);
    a.setAttribute('target', '_blank');
    a.appendChild(document.createTextNode(str));
    with (a.style) {
      fontSize = "0.9em";
      textDecoration = "none";
      if (count >= 5) {
        fontWeight = "bold";
        backgroundColor = "#fff0f0";
        color = "#f66";
      }
      if (count >= 10) {
        backgroundColor = "#ffcccc";
        color = "red";
      }
    }

    with (targetNode) {
      appendChild(document.createTextNode(" ("));
      appendChild(a);
      appendChild(document.createTextNode(") "));
    }
  }

  function setBookmarkCounts(titleArray) {
    for (var i = 0; i < titleArray.length; i++) {
      var href = titleArray[i].href;
      var title = titleArray[i].node;
      var count = url2count[href];
      if (count != null) {
        var node = document.createElement('span');
        node.className = 'hatena-bookmark-count';
        if (count > 0)
          setBookmarkCount(node, href, count);
        title.insertBefore(node, title.childNodes[1]);
      }
    }
  }

  function callXmlrpc(requestbody, titleArray) {
    const endpoint = "http://b.hatena.ne.jp/xmlrpc";
    function onload(response) {
      if (response.responseText.match(/<fault>/)) {
        clearInterval(timerID);
        alert("xmlrpc call failed: " + response.responseText + "\n" + "request: " + requestbody);
      } else {
        var pattern = /<name>([^<]+)<\/name>\s*<value><int>(\d+)/g;
        var m;
        while (m = pattern.exec(response.responseText)) {
          url2count[m[1]] = m[2];
        }
        setBookmarkCounts(titleArray);
      }
      busy = false;
    }

    // alert('xmlrpc call');
    GM_xmlhttpRequest({ method: "POST", url: endpoint, data: requestbody, onload: onload });
  }

  function greader_add_bookmark_count() {
    if (busy) return;

    var titles = document.evaluate('//h2[@class="entry-title"]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
    if (! titles.snapshotLength) return;

    busy = true;

    var titleArray = new Array();
    var reqUrlArray = new Array();
    var reqUrlHash = new Object();
    for (var i = 0; i < titles.snapshotLength; i++) {
      var title = titles.snapshotItem(i);
      var nodes = title.childNodes;
      if ((nodes == null) ||
          (nodes[1] == null) ||
          (nodes[1].tagName != 'SPAN') ||
          (nodes[1].className != 'hatena-bookmark-count')) {
        var link = null;
        if (title.firstChild.tagName == 'A') {
          // entry-container (Expanded view or Collapsed item)
          link = title.firstChild;
        } else {
          // entry (List view)
          link = title.parentNode.parentNode.firstChild;
          if (link.tagName != 'A') link = null;
        }
        if (link != null) {
          titleArray.push({ node: title, href: link.href });
          if ((url2count[link.href] == null) &&
              (! reqUrlHash[link.href])) {
            reqUrlHash[link.href] = true;
            reqUrlArray.push(link.href);
          }
        }
      }
    }
    if (titleArray.length == 0) {
      busy = false;
      return;
    }

    if (reqUrlArray.length == 0) {
      // all items are found in the cache
      setBookmarkCounts(titleArray);
      busy = false;
    } else {
      var request = '<?xml version="1.0"?>\n<methodCall>\n<methodName>bookmark.getCount</methodName>\n<params>\n';
      for (var i = 0; i < reqUrlArray.length; i++) {
        // avoid xmlrpc call failure in 'too many params' error
        if (i > 20) break;
        var href = reqUrlArray[i];
        request += "<param><value><string>" + href.htmlescape() + "</string></value></param>\n";
      }
      request += "</params>\n</methodCall>\n";
      callXmlrpc(request, titleArray);
    }
  }

  // be careful not to be too busy
  timerID = setInterval(greader_add_bookmark_count, 3000);
})();

installする

Google Readerがバージョンアップして使いやすくなったので、これまでメインで使っていたはてなRSSから移行することにした。それで一つ困ったのが、はてなRSSにあったはてなブックマーク件数を表示する機能が使えなくなること。そのためいくつかのフィードについては移行できないでいたのだが、これで完全に移行できる。

ちなみに、Google Readerの使い方を色々試してみて、今はShow all + List View + Sort by newestに落ち着いた。j,kで読み進めていって、気になったエントリはvで元ページを表示するといった感じ。これが一番Gmailっぽい使い方ができる。