いやもう、タイトルの通りでございます。いつものAmazon to Rakutenに買い物かごをしれっと追加しました。まあ、つけた方が個人的に便利やし。そこで工夫した点など。

必要な情報

まず、実際の楽天ブックスの商品ページの買い物かごボタンを見て、どんなデータをPOSTしてやればいいのかを調べます。

 1 <form method="post" action="https://books.step.rakuten.co.jp/rms/mall/book/bs/Cart">
 2   <div>
 3     <span class="unit">個数&nbsp;</span>
 4     <input value="1" type="text" size="4" name="units" id="units">
 5     <input value="買い物かごに入れる" type="submit">
 6     <input type="hidden" value="213310" name="shop_bid">
 7     <input type="hidden" value="14034556" name="item_id" id="ScItemGet">
 8     <input type="hidden" value="1" name="inventory_flag">
 9   </div>
10 </form>

見た感じ、いじる必要がありそうなとこは、

1. 「個数」をhiddenで1個に固定
2. "item_id"を商品に合わせて変更

の2点。楽天ブックスは楽天市場の1店舗なので、"shop_bid"は全部一緒のはず。"inventory_flag"は、まあようわからんけどこのままでいいっしょ。

SPONSERD LINK

実装

ということで、実質コードを書く必要があるのは"item_id"だけ。ところが厄介なことに、"item_id"はなんとAPIに入っていないので、もっかいXMLHttpRequestを叩いて無理矢理取ってくることに。http通信2回分なんて待ってられないので、一回目のAPI通信で取得したデータはすぐ表示しつつ、続けてitem_idが取得出来次第ボタンを表示っていう仕様にしました。

 1 chrome.extension.onConnect.addListener(function(port) {
 2   console.assert(port.name == "AtoR");
 3   port.onMessage.addListener(function(msg) {
 4     if(msg.status == "start"){
 5       //ページアクションのアイコンを表示
 6       chrome.pageAction.show(port.sender.tab.id);</p>
 7 
 8       //楽天APIから商品を検索
 9       query = "http://api.rakuten.co.jp/rws/3.0/json?" +
10           "developerId=" + devId +
11           "&affiliateId=" + afiId +
12           "&operation=" + opr +
13           "&version=" + ver+
14           "&keyword=" + encodeURI(msg.title);</p>
15 
16       api.open("GET",query,true);
17       api.onreadystatechange = sourceGet(port);
18       api.send(null);
19     }
20     else if(msg.status == "null"){
21       content = "このページでは使用できません。";
22     }
23     else{
24       sourceGet(port);
25     }
26   });
27 });
28 
29 //タブが変更された時の処理
30 chrome.tabs.onSelectionChanged.addListener(function(tabid){
31   chrome.tabs.getSelected(null, function(tab) {
32     ・・・
33   });
34 });
35 
36 function sourceGet(port){
37   if (api.readyState == 4 && api.status == 200){
38     response = eval('[' + api.responseText + ']')[0];</p>
39 
40     if(response['Header']['Status'] == 'Success'){
41       items = response['Body']['BooksTotalSearch']['Items']['Item'];</p>
42 
43       //商品データを1つずつhtmlに出力
44       content = '<table width="300">';
45       for(i=0;i<items.length;i++){
46         content = content+'<tr><td><a href="'+items[i]['affiliateUrl']+'"><img src="'+items[i]['mediumImageUrl']+'" /></a></td><td style="width:200px;vertical-align:top;">タイトル:<a href="'+items[i]['affiliateUrl']+'" target="_blank">'+items[i]['title']+'</a><br />著者:'+items[i]['author']+'<br />価格(税込):'+setComma(items[i]['itemPrice'])+'円<br />ポイント:'+Math.floor(items[i]['itemPrice']/1.05/100)+'ポイント<br /><img src="img/star_';
47         if(items[i]['reviewAverage'] == 0) content = content + '0';
48         else if(items[i]['reviewAverage'] > 0 && items[i]['reviewAverage'] < 1) content = content + '0.5';
49         else if(items[i]['reviewAverage'] == 1) content = content + '1';
50         else if(items[i]['reviewAverage'] > 1 && items[i]['reviewAverage'] < 2) content = content + '1.5';
51         else if(items[i]['reviewAverage'] == 2) content = content + '2';
52         else if(items[i]['reviewAverage'] > 2 && items[i]['reviewAverage'] < 3) content = content + '2.5';
53         else if(items[i]['reviewAverage'] == 3) content = content + '3';
54         else if(items[i]['reviewAverage'] > 3 && items[i]['reviewAverage'] < 4) content = content + '3.5';
55         else if(items[i]['reviewAverage'] == 4) content = content + '4';
56         else if(items[i]['reviewAverage'] > 4 && items[i]['reviewAverage'] < 5) content = content + '4.5';
57         else if(items[i]['reviewAverage'] == 5) content = content + '5';
58         content = content + '.png" />('+items[i]['reviewCount']+')<br />買い物かごに入れる</td></tr>';
59 
60         rakuten.open("GET",items[i]["itemUrl"],true);
61         rakuten.onreadystatechange = function(){
62           if (rakuten.readyState == 4 && rakuten.status == 200){
63             rr = rakuten.responseText;
64             rr.match(/value="([^"]*)"[^>]*id="ScItemGet"/);
65             rr = RegExp.$1;
66 
67             //買い物かご
68             cart = '<form method="post" action="https://books.step.rakuten.co.jp/rms/mall/book/bs/Cart" target="_blank">';
69             cart = cart + '<div>';
70             cart = cart + '<input value="1" type="hidden" name="units" id="units">';
71             cart = cart + '<input value="買い物かごに入れる" type="submit">';
72             cart = cart + '<input type="hidden" value="213310" name="shop_bid">';
73             cart = cart + '<input type="hidden" value="'+rr+'" name="item_id" id="ScItemGet">';
74             cart = cart + '<input type="hidden" value="1" name="inventory_flag">'
75             cart = cart + '</div>'
76             cart = cart + '</form>'
77 
78             content = content.replace("買い物かごに入れる", cart);
79             viewContent();
80           }
81         };
82         rakuten.send(null);
83       }
84       content = content+'</table>';
85       viewContent();
86     }
87     ・・・
88   }
89   ・・・
90 }

61行目で、まずベタ文字で「買い物かごに入れる」って表示しつつ、63行目以下で2度目の通信を行ってます。item_idは67行目の正規表現で強引にゲット。正規表現が一発で決まると気持ちいい。
無事にitem_idが取れたらベタ文字をformタグで置換。めでたしめでたし。