twitterの画面に被はてブ数を表示するグリモン

twitterの画面に被はてブ数を表示するグリモン(greasemonkey)を書いてみた。


http://twitter.com/home

とか、

http://twitter.com/yamashiro

とかを表示すると、
各エントリーの最後に(12users)みたいに、そのエントリーをブクマしてる人数がわかる。



2番煎じかもしれないけど、グリモン初心者なので、
車輪の再実装。


autopagerize 対応どうやるんだろう。対応した。

// ==UserScript==
// @name           twitterHatebuCount
// @namespace      twitterHatebuCount
// @include        http://twitter.com/*
// ==/UserScript==
 
function() {
  //どうもリクエストした順に、はてブのAPIがカウントすう返してくれるわけじゃないので、UPLとHTML要素をマップする
  //という意図と、AutoPagerize に対応したので、一度調べたエントリーは調べないようにする
  var urlToStatusMap = {};

  var hatebuFunc = function() {
      function log() {if(console) console.log.apply(console, Array.slice(arguments));}

      //指定されたノードから指定された XPath から最初の要素を取得する
      function findNode(root, xpath) {
	  var result = document.evaluate(xpath, root, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	  if (! result.snapshotLength) return null;
	  return result.snapshotItem(0);
      }

      //「user」って指定されたノードに入れる関数
      function insertBookmarkCount(targetNode, url, count) {
	  var a = document.createElement('a');
	  a.setAttribute('href', "http://b.hatena.ne.jp/entry/" + url);

	  var str = (count > 0 ? "" + count : "no") + " user" + (count != 1 ? "s" : "");
	  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";
	      }
	  }

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

      //status のノードから url を取得する
      function getStatusUrl(status) {
	  var permanentLink = findNode(status, ".//a[@class='entry-date']");
	  return permanentLink.href;
      }

      var statusList = document.evaluate(
	  "//*[@class='status-body']",
	  document,
	  null,
	  XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

      if (statusList.snapshotLength == 0) {
	  //status が一個もないってのは、タイムライン表示してない場合・・・に違いない
	  return;
      }

      var request = '<?xml version="1.0"?>\n<methodCall>\n<methodName>bookmark.getCount</methodName>\n<params>\n';
      var status;
      var url;
      for (var i = 0 ; i < statusList.snapshotLength; i++) {
	  status = statusList.snapshotItem(i);
	  url = getStatusUrl(status);
	  if(!urlToStatusMap[url]) { //まだはてブ数調べてなかったら
	      request += "<param><value><string>" + url + "</string></value></param>\n";
	      urlToStatusMap[url] = status;
	  }
      }
      request += "</params>\n</methodCall>\n";


      const endpoint = "http://b.hatena.ne.jp/xmlrpc";
      GM_xmlhttpRequest(
	  { method: "POST",
	    url: endpoint,
	    data: request,
	    onload:
	    function(response) {
		console.info("hoge");
		if (response.responseText.match(/<fault>/)) { //エラーがあった。とりあえずスルー
		    alert("xmlrpc call failed: " + response.responseText + "\n" + "request: " + request);
		} else {
		    var pattern = /<name>([^<]+)<\/name>\s*<value><int>(\d+)/g;
		    var match;
		    var i = 0;
		    var status;
		    while (match = pattern.exec(response.responseText)) {
			status = urlToStatusMap[match[1]];
			i++;
			insertBookmarkCount(status, getStatusUrl(status), match[2]);
		    }
		}

	    }
	  });
  }

  if (window.AutoPagerize && window.AutoPagerize.addFilter){
      window.AutoPagerize.addFilter(hatebuFunc);
  } 
  hatebuFunc();

}();


追記:
なんかtwitterのhtmlが変わってたので修正した。具体的にはxpath
//div
ってなってたのを
//*
にした。