あったらいいなーって思い、作ってみました。機能概要は以下の通りです。
機能
導入するとこんな感じに、左下にお気に入りユーザの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 勉強してないです。
spidermonkey とか、コマンドラインツールがあるとさらに便利だと思いますが、今回はインストールでこけて使いませんでした(ノ∀`)
- 他のグリモンのソース
- AutoPagerize for Greasemonkey
- XHR のレスポンスをXPath で解析するには、AutoPegerize のcreateHTMLDocumentByString が使えます
- キャッシュの使い方も参考になります
- Motsu Tabetai for Greasemonkey
- 書き方を参考にしました。グローバルを汚染しないために、無名関数で覆うことで、全体をこの関数のスコープ内に閉じ込めています
- 初めてのGreasemonkey の作り方(Slimtimer Report to Ticket) - Slow Dance
- 僕が$X の使い方、Array.forEach 等を勉強した際の記録
- AutoPagerize for Greasemonkey
ソースには反映していませんが、以下のコードも参考にしました。
こんな感じです。特に重要なのは他のグリモンのソースです。グリモンは、「ソースが短い」、「他のライブラリとかなしに動く」という点でソースを読みながら勉強するスタイルが適していると感じます。
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回目のリクエストからになています。それまでのリクエストはキャッシュの生成に使われてしまい、値は得られないようになっている。
// 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 のコンソールに内容が表示されるようになる。