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

必要な情報

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

1
2
3
4
5
6
7
8
9
10
<form method="post" action="https://books.step.rakuten.co.jp/rms/mall/book/bs/Cart">
  <div>
    <span class="unit">個数&nbsp;</span>
    <input value="1" type="text" size="4" name="units" id="units">
    <input value="買い物かごに入れる" type="submit">
    <input type="hidden" value="213310" name="shop_bid">
    <input type="hidden" value="14034556" name="item_id" id="ScItemGet">
    <input type="hidden" value="1" name="inventory_flag">
  </div>
</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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
chrome.extension.onConnect.addListener(function(port) {
  console.assert(port.name == "AtoR");
  port.onMessage.addListener(function(msg) {
    if(msg.status == "start"){
      //ページアクションのアイコンを表示
      chrome.pageAction.show(port.sender.tab.id);</p>

      //楽天APIから商品を検索
      query = "http://api.rakuten.co.jp/rws/3.0/json?" +
          "developerId=" + devId +
          "&affiliateId=" + afiId +
          "&operation=" + opr +
          "&version=" + ver+
          "&keyword=" + encodeURI(msg.title);</p>

      api.open("GET",query,true);
      api.onreadystatechange = sourceGet(port);
      api.send(null);
    }
    else if(msg.status == "null"){
      content = "このページでは使用できません。";
    }
    else{
      sourceGet(port);
    }
  });
});

//タブが変更された時の処理
chrome.tabs.onSelectionChanged.addListener(function(tabid){
  chrome.tabs.getSelected(null, function(tab) {
    ・・・
  });
});

function sourceGet(port){
  if (api.readyState == 4 && api.status == 200){
    response = eval('[' + api.responseText + ']')[0];</p>

    if(response['Header']['Status'] == 'Success'){
      items = response['Body']['BooksTotalSearch']['Items']['Item'];</p>

      //商品データを1つずつhtmlに出力
      content = '<table width="300">';
      for(i=0;i<items.length;i++){
        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_';
        if(items[i]['reviewAverage'] == 0) content = content + '0';
        else if(items[i]['reviewAverage'] > 0 && items[i]['reviewAverage'] < 1) content = content + '0.5';
        else if(items[i]['reviewAverage'] == 1) content = content + '1';
        else if(items[i]['reviewAverage'] > 1 && items[i]['reviewAverage'] < 2) content = content + '1.5';
        else if(items[i]['reviewAverage'] == 2) content = content + '2';
        else if(items[i]['reviewAverage'] > 2 && items[i]['reviewAverage'] < 3) content = content + '2.5';
        else if(items[i]['reviewAverage'] == 3) content = content + '3';
        else if(items[i]['reviewAverage'] > 3 && items[i]['reviewAverage'] < 4) content = content + '3.5';
        else if(items[i]['reviewAverage'] == 4) content = content + '4';
        else if(items[i]['reviewAverage'] > 4 && items[i]['reviewAverage'] < 5) content = content + '4.5';
        else if(items[i]['reviewAverage'] == 5) content = content + '5';
        content = content + '.png" />('+items[i]['reviewCount']+')<br />買い物かごに入れる</td></tr>';

        rakuten.open("GET",items[i]["itemUrl"],true);
        rakuten.onreadystatechange = function(){
          if (rakuten.readyState == 4 && rakuten.status == 200){
            rr = rakuten.responseText;
            rr.match(/value="([^"]*)"[^>]*id="ScItemGet"/);
            rr = RegExp.$1;

            //買い物かご
            cart = '<form method="post" action="https://books.step.rakuten.co.jp/rms/mall/book/bs/Cart" target="_blank">';
            cart = cart + '<div>';
            cart = cart + '<input value="1" type="hidden" name="units" id="units">';
            cart = cart + '<input value="買い物かごに入れる" type="submit">';
            cart = cart + '<input type="hidden" value="213310" name="shop_bid">';
            cart = cart + '<input type="hidden" value="'+rr+'" name="item_id" id="ScItemGet">';
            cart = cart + '<input type="hidden" value="1" name="inventory_flag">'
            cart = cart + '</div>'
            cart = cart + '</form>'

            content = content.replace("買い物かごに入れる", cart);
            viewContent();
          }
        };
        rakuten.send(null);
      }
      content = content+'</table>';
      viewContent();
    }
    ・・・
  }
  ・・・
}

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