ぼくは昔からずっとamazonユーザーだったんですが、打倒アマゾンを掲げる楽天入社を機に、本とかCDとかは楽天ブックスで買うようになりました。
ところが、RSSに多数登録してる書評ブログから商品をクリックすると、大半がamazonにジャンプしてしまいます。
仕方がないので別タブで楽天ブックスを開き、本のタイトルをコピペして検索をかけ、やっとその商品の詳細ページに辿り着けるというめんどくさいステップを踏んでました。
これをなんとか楽にしたいという1楽天ブックスユーザーの熱い思いを、今回Google Chromeの拡張機能という形にしてみました。

Amazon to Rakuten

これで自分は便利になるし打倒アマゾンにも一役買えるんじゃないかというのもあるんですが、色んなとこでハマったので忘れないうちにその辺書いときます。

大枠

実はこれだいぶ前に作ろうとしたんですが、全然やり方がわからず放置してました。
ところが先日うちのインターン生でChrome Extensionを作るという女の子が。色々聞いてみて「これならできそう!」ってなってやってみたらできたっていう感じです。

流れはこんな感じ。

1. chrome.tabs.getSelectedで今開いてるamazonのページのURLを取得
2. XMLHttpRequestで取得したURLのHTMLを取ってきて、本のタイトルを正規表現で取得
3. 楽天ブックス総合検索APIに取得したタイトルを投げる
4. 検索結果をPopupに表示。表示されたやつをクリックすると無事楽天ブックスの商品詳細ページにジャンプ

まあ正直4.はPopup使わずに一番上の検索結果に自動的に飛ばそうかと思ったんですが、運悪く違うページに飛んじゃったらどうしようもないので1クッション挟みました。

この流れを見て、「なんでContent Script使わへんの?」と思った方、鋭い。実はほぼ完成してからぼーっとAPI見てたらContent Scriptの存在に気づいて、ちょっとやってみたけどメッセージの送受信がうまくいかずとりあえず上の流れで公開したっていう感じです。そのうち修正したい。

というわけで、以下具体的に。

SPONSERD LINK

1. 今開いているページのURLを取得

 1 /* manifest.json */
 2 {
 3   ・・・
 4   "browser_action": {
 5     "default_icon": "icon.png",
 6     "popup": "popup.html"
 7   },
 8   "permissions": [
 9 		"tabs",
10 		・・・
11   ],
12 	・・・
13 }

まずはmanifest.jsonにchrome.tabsの使用を宣言しなければなりません。permissionsの中にtabsを足します。

1 /* popup.html */
2 chrome.tabs.getSelected(null, function(tab) {
3   var url = tab.url;
4   ・・・
5 });

すると、上記のようにchrome.tabs.getSelectedでタブ情報を取ってこれます。tabの中に各種情報が入っていて、tab.urlで現在開いているタブのURLが取れるという具合です。

2. 取得したURLのHTMLデータを取得

 1 /* popup.html */
 2 var req = new XMLHttpRequest();
 3 var loading = false;
 4 
 5 chrome.tabs.getSelected(null, function(tab) {
 6   var url = tab.url;
 7   req.open("GET",url,true);
 8   req.onreadystatechange = sorceget;
 9   req.send(null);
10 });
11 
12 function sorceget(){
13   if (req.readyState == 4 && req.status == 200){
14     //読み込みが成功した時の処理
15     ・・・
16   }
17   else{
18     //読み込み中、もしくは読み込みに失敗した時の処理
19     if(loading == false){
20       //ロード中画像を表示
21       loadingImg = document.createElement('img');
22       loadingImg.id = "loading";
23       loadingImg.src = "loading.gif";
24 
25       var objBody = document.getElementsByTagName("body").item(0);
26       objBody.appendChild(loadingImg);
27       loading = true;
28     }
29   }
30 }

今度は先程取得したURLの中身をXMLHttpRequestで引っ張ってきます。
req.openの第一引数は取得のmethod、第二引数は対象のURL、第三引数はとりあえず省略orTRUEでOK。
req.onreadystatechangeは、読み込みのステータスが変わる度に呼ばれるfunctionです。if (req.readyState == 4 && req.status == 200)で読み込み完了かつ読み込み成功の時の処理を書きます。elseにロード中の画像を表示するスクリプトを書いとくといい感じな気がします。
req.sendはURLにGETかPOSTで値を渡すのに使うらしい。渡さなくてもnullでsendしとかんとあかんらしい。
その他詳細はこちら

あとはこれで持ってきたHTMLデータから正規表現でタイトルを抜き取れば第一段階完了。ただamazonがちょっとタイトル部分のソースを変えたらアウト。

3. 楽天ブックス総合検索APIに取得したタイトルを投げる

まあ、ここはいいでしょう。XMLHttpRequestで別オブジェクトを作ってクエリを投げれば結果が返ってきます。
APIの詳細はこちら

1 /* popup.html */
2 items = eval('[' + api.responseText + ']')[0]['Body']['BooksTotalSearch']['Items']['Item'];

ただ、返ってきたJSONをオブジェクトに変換する方法を知らなかったのでメモ。
ちなみにこのコードでapi.responseTextに入ってた検索結果の商品がitemsに配列で入るので、forしてitems[i]['title']でタイトルが取れたり、そんな感じです。
これを一行で済ませてしまわずに検索結果が空やった時の分岐とかを本来は作らないといけないとは思う。まあそのうち。

4. 楽天APIの検索結果を表示

今回は、popup.htmlにdocument.writeでガリガリHTML書いてその中にitemsのデータをいちいち埋め込んでってやりました。
スマートじゃないけど、とりあえず表示できればOK。
中にtarget="_blank"のリンクを埋め込んだら普通に新規タブで開いてくれました。
現在のタブに表示させようとするとちょっとめんどいっぽい?

これからやりたいこと

そんな感じで、なんとか動くものができました。やるかどうかはわからんけどこれからやりたいことを。

1. やっぱりContent ScriptとMessageを使いこなしたい。
2. 検索結果が無かった時の分岐。
3. 2の結果が空やった時、もしくは違う商品しか表示されなかった時のために「お探しの商品が見つかりませんか?」的なメッセージと共に検索ワードを手で変えて検索し直せるようにしたい。
4. ブラウザアクションじゃなくページアクションで。
5. 商品ジャンルも取ってきてAPIを使い分ければ本やCD以外の商品も楽天市場APIとかで取ってこれるかも。
6. デザインを綺麗に。

とりあえずこんな感じかなあ。
1.は言わずもがな。HTML通信を一回減らせるのは大きい。今回はbackground_pageを使ってないので、amazonのページが表示された瞬間に裏側でContent ScriptがDOM解析でタイトルを取ってきて、裏側で検索かけつつ、アイコンをクリックしたら一瞬でpopupに表示されるとかできたら素敵。できるんかな。
2.は、今ロード中画像がぐるぐる出続けるだけやし。バグと言っていいぐらい。
3.は実際取ってこれへん商品もちょこちょこあるし、余計な文言を省いて検索し直せたらもっと便利。
4.はよくわからずにブラウザアクション(右側にアイコンがずっと出てるやつ)で作ったけど、amazonの詳細ページでしか使えないのでページアクション(特定のサイトを見てる時だけアドレスバーの中の右端にアイコンが出るやつ)の方がいいかも。
5.は、今はブックスのAPIしか使ってませんがamazonも色んな商品置いてるので、使える幅が増えたらいいなあ。
6.は、誰かデザインして・・・

はじめてのChrome拡張ということでまだまだ基本機能ができただけですが、ちまちまアップデートしていくかもしれないのでよかったら使ってみてください。

Amazon to Rakuten