現在閲覧しているページをはてブしている「お気に入りユーザ」を表示するGreasemonkey

あったらいいなーって思い、作ってみました。機能概要は以下の通りです。

機能

  • 現在閲覧しているページをはてブしている「お気に入りユーザ」を表示する
  • ついでに、自分がはてブしているかも表示する


導入するとこんな感じに、左下にお気に入りユーザのfavicon が表示されます。

メリット

  • なんとなくググったページを「お気に入りユーザ」がはてブしていると分かることで、有益なページを逃しにくくなる
  • はてブしようとしたら以前はてブしてた」っていうあの敗北感から脱出できる

インストール

always_hatebu_favorites.user.js からインストールして下さい。ソースはgithub に置いてあります。

使い方

使うための条件は以下の通りです。

  • はてブにログインしていること
  • はてブを公開していること
  • お気に入りを公開していること


以下のコマンドが使えます。

  • update favorites
    • お気に入りユーザのキャッシュを更新する
    • 手動で更新しなくても、1日毎にキャッシュを最新情報で更新します
  • clear cache
    • 自分のユーザ名, お気に入りのキャッシュをクリアします
    • 普通は使わない

作った感想

グリモン作りは、初心者がjs の入り口にするのにとても良い

今回のグリモンは欲しいなベースで作ったのですが、js の知識があまりない僕でも、12時間くらいである程度の形になりました。
小規模のグリモンなら、ちょっとしたjs の知識と、ツールを使えば作れると思います。但し、ソースが汚いですし、関数の使い方とかが間違ってる可能性は大きいです。あくまで動くレベルです。でも、勉強につかう材料としては持ってこいです!
参考までに今回のグリモンを書くために必要になったものを記録します。

  • js の知識
    • alert(1); で警告をだすことができる
    • var とかで変数を宣言できることを知っている
    • 関数宣言の仕方を知っている
    • String とかArray とかの型があることを知っている
    • XPath で要素をとってこれる

このくらいで十分です。this についてとか知らなくてもできます。僕もまだthis 勉強してないです。

  • ツール
    • Firebug
      • エラー内容の表示/確認と、XPath を調べる際に使います。必須です。


spidermonkey とか、コマンドラインツールがあるとさらに便利だと思いますが、今回はインストールでこけて使いませんでした(ノ∀`)

ソースには反映していませんが、以下のコードも参考にしました。


こんな感じです。特に重要なのは他のグリモンのソースです。グリモンは、「ソースが短い」、「他のライブラリとかなしに動く」という点でソースを読みながら勉強するスタイルが適していると感じます。


1. 毎回お気に入りをparse しに行きたくないなー
2. グリモンってキャッシュとかできんのかな?
3. そういえばAutopegerize のwedata 関連のトラブルの時にキャッシュが云々って言ってたな
4. 「greasemonkey」「キャッシュ」でググる
5. GM_setValue, GM_getValue っていうのがあると知る
6. Autopegerize のソースの中でこれらを使っているところを探す
7. そこのソース読む。Date と組み合わせればキャッシュの有効期限の設定ができると知る
8. 分からない関数がでてきたらググる


という流れで自分のグリモンを作って行くと、勉強になると思いました。

今回覚えたテクニック

今回のグリモン作成を通じて、学んだことを、「Always Hatebu Favorites」のソースの中にコメントを書く形式で記録します。

他のページのデータをパースする
// $X を使うためにrequire
// @require      http://gist.github.com/3242.txt    

// AJAX リクエストを送る
GM_xmlhttpRequest({method: "GET",
      	           url: url,
		   onload: function(res) { // コールバック関数の引数res にリクエストした結果のレスポンスが格納される
		     // レスポンスのテキストからdocument オブジェクトのようなものを生成
                     // createHTMLDocumentByString 関数はAutopegerize からコピーする
                     var html = createHTMLDocumentByString(res.responseText);

                     // XPath 関数でパース
		     var username = $X('//div[@id="navigation"]//a', html)[0].firstChild.title;
		     if(username) {
		       GM_setValue('username', username);
		     }
	           }
});
キャッシュ
  • 値をキャッシュする
// 第一引数にkey, 第二引数にキャッシュするオブジェクトを指定
// 文字列以外のオブジェクトを格納する際にはtoSource() で文字列化して格納する

GM_setValue('favorites', favorites.toSource());
  • キャッシュした値を参照する
// GM_getValue にkey を渡すと値が得られる。第二引数にはデフォルト値を指定可能
// 文字列以外のオブジェクトはeval によって文字列から元のオブジェクトに戻す(要は、シリアライズ)

var favorites = eval(GM_getValue('favorites'));
  • キャッシュの有効期限を設定する
// 有効期間の設定。以下では1日に設定している
var CACHE_EXPIRE = 24 * 60 * 60 * 1000;

onload: function(res) {
  // 現在の時刻に有効期間を足す
  var expire = new Date(new Date().getTime() + CACHE_EXPIRE);
  
  // キャッシュするオブジェクトと一緒に格納
  var favorites = {
    favorites: [],
    expire: expire
  };

  GM_setValue('favorites', favorites.toSource());
}

// キャッシュした値を取得
var favorites = eval(GM_getValue('favorites'));

// favorites がundefined(まだキャッシュされていない)か
// 有効期限を過ぎていたらキャッシュを更新する
if(!favorites || favorites.expire < new Date()) {
  updateFavorites();
}
  • キャッシュをクリアする
// クリア用の関数
function clearCache() {
  GM_setValue('favorites', '');
  GM_setValue('username', '');
}

// クリア用のコマンド。グリモンのメニューから実行できるコマンドとして定義する
GM_registerMenuCommand('AlwaysHatebuFavorites - clear cache', clearCache);


キャッシュについては、setValue でセットした値を、同一リクエスト内でgetValue で取得することはできないみたい。getValue で取得できる値は、セットされる前の値。セットした値は次のリクエストから取得できるようになる。
その関係で、Always Hatebu Favorites が実際に機能するのはインストールした後の3回目のリクエストからになています。それまでのリクエストはキャッシュの生成に使われてしまい、値は得られないようになっている。

  • JSON を返すようなAPI の使い方
// API を呼ぶ。showFavorites 関数をコールバックに指定することで、API のレスポンスをこの関数に渡すことができる
GM_xmlhttpRequest({method: "GET",
		   url: 'http://b.hatena.ne.jp/entry/json/' + location.href.replace(/#/g, "%23"),
		   onload: showFavorites});


// json データをオブジェクト化する
if(data = eval("(" + json.responseText + ")")){

// Hash 形式になっていたのでb.user でユーザを参照
Array.forEach(data.bookmarks, function(b){
  bm_users.push(b.user)
});
画像を表示する
// 画像を貼る用にdiv を用意
var div = document.createElement("div");

// グリモンの名前をid として、他の要素と区別できるようにする
div.setAttribute("id", "gm_always_hatebu_favorites");

// position:fixed; bottom:0px; left:0px;
// で、左下に配置、スクロールに関係なく表示されるようにfixed
// display:block; width:" + 16 * icons_per_line + "px; text-align:left;"
// で、一列にicons_per_line 個のファビコンを並ばせる。列が変わった際にファビコンが左によるように、text-align で強制
// z-index:500;
// で、画像が最前面にくるようにする
div.setAttribute("style", "display:block; position:fixed; bottom:0px; left:0px; z-index:500;  width:" + 16 * icons_per_line + "px; text-align:left;");

arrAnd(users, bm_users).forEach(function(u){
  var img = document.createElement("img");

  img.setAttribute("src", "http://www.hatena.ne.jp/users/" + u.substr(0, 2) + "/" + u + "/profile.gif");
  img.setAttribute("height", "16");
  img.setAttribute("width", "16");
  img.setAttribute("title", u);
  img.setAttribute("alt", u);

  // 画像をdiv 下に配置する
  div.appendChild(img);
});

// div をbody 下に配置する
document.body.appendChild(div);
グリモンのデバッグ

GM_log 関数を使う。いまさらだけどGM_logの使い方。console.logも使えるようになった! - Cherenkovの暗中模索にっきの設定を行うと、Firebug のコンソールに内容が表示されるようになる。

画像のキャッシュ

今回のグリモンは、はてブのファビコンを取りに行くので、キャッシュしないとヤバイなって思っていました。でも、画像自体のキャッシュはブラウザで行ってくれるみたいです。



js とっても楽しいです!今まで主にruby を勉強していたのですが、来年はjs に挑戦してみようかなと思います丶(´▽`)ノ