kintone のレコード詳細ページから Amazon 商品データを参照するテクニック

著者名:長井 祥和( 合同会社アクアビット (External link)

目次

はじめに

kintoneアプリで資産を管理したいという要望をよく聞きます。
その際、既存サイトのデータが参照できたら便利ですよね。
たとえば商品データを参照したい場合、Amazon APIを通して取得したいといった要件は十分考えられます。
本稿では、kintoneの詳細レコード上に、Amazon APIから取得したデータを表示する方法について紹介します。

必要な環境

本件を実現するために必要な環境は次のとおりです。

パソコン

ノートかデスクトップかの違いは問いません。
モバイルや俗にいうガラパゴス携帯、またタブレットからの利用は考慮していません。

Web ブラウザー

本件ではGoogle Chrome、Firefox、Internet Explorer 9と11で検証しています。
その他のブラウザーでは予測不可能な動作に遭遇するかもしれません。

PHP が動作するサーバー

今回の検証で使用したレンタルサーバーは、iniも変更できず、pearなどのライブラリも利用できません。
SSLも専用ではく共用SSLを使用しています。

PHPネイティブだけを頼りに構築しているので、サーバー間の動作差異はあまりないと思えます。
なお、レンタルサーバーのOSはUNIX系で、Apache 2.2で動いているものを使いました。
PHPのバージョンは5.3.2です。

kintone アプリ

最低限、次の3つのフィールドが必要です。

フィールドタイプ フィールド名 フィールドコード 説明
文字列(1 行) ISBNコード F_Isbn ISBNコード用
スペース Space_Image Amazon情報表示用スペース
スペース Large_Image 画像表示用スペース

これらのフィールドを用意して、kintoneカスタマイズを適用すると、レコード詳細画面にAmazonから取得した商品データが表示されます。

なお、kintoneアプリには上記以外のフィールドも追加して表示しています。

Amazon の認証情報

今回作成するサンプルに必要な次の3つの情報を準備します。

  • Access Key ID
  • Secret Access Key
  • トラッキングID

これらの認証情報を確認する方法は、今回割愛します。

PHP ファイル

PHP を利用した Amazon へのパラメーターの生成方法

kntoneからAmazonの情報を取得するのにphpを介さなければならないかについて、一言申し添えておかねばなりません。
このTipsをご覧の皆様は、すでに有識者の皆様が執筆された秀逸なTipsの数々にも目を通されたことでしょう。
次のTipsでは、kintoneから外部APIに接続する内容です。

私も本稿を書くにあたって参考にしました。

これらのTipsはkintoneのJavaScript APIを使用しています。
JavaScript内に外部APIのURLを直接書き込み、その結果を表示させる方法です。
一方、Amazon APIを呼び出す際のURLですが、その中には先ほど確認したAmazonの認証情報から生成した署名文字列を含める必要があります。
つまり、署名文字列を生成するにあたっては、JavaScript内に次の機密情報を埋め込まねばなりません。

  • Access Key ID
  • Secret Access Key
  • トラッキングID

この回のカスタマイズでは、それを避けるために、外部のPHPに署名文字列の生成や機密情報の扱いを任せようというのが狙いです。

kintoneに埋め込むJavaScript内で呼び出すAPIのURLは次のとおりです。

1
https://example.com/AmazonBookInfo.php?code=ISBN&no=<ISBN>

<ISBN>は、kintoneのフィールドに入力されているISBNコードです。
Amazon認証情報に関する情報の一切はJavaScriptからは省かれています。

AmazonBookInfo.php

kintoneからのリクエストは、次のPHPファイル(AmazonBookInfo.php)で処理されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
    header("Cache-Control: no-cache, must-revalidate");
    header('Access-control-allow-origin:*');
    header('Access-Control-Allow-Headers:*');
    header('Content-type: application/xml; charset=UTF-8;');
    header('p3p: CP=COR');

    require_once('SetBookByAmazon.php');

    $ISBN     = $_GET["no"];
    $BookCode = $_GET["code"];

    $return = SetBookByAmazon($ISBN, $BookCode);
    echo $return;
?>

JavaScriptに値を返すための各種ヘッダーの記述と、Amazonから情報を取得する本体のPHPの「SetBookByAmazon.php」の呼び出しが書かれています。
ヘッダーについてはGoogle ChromeとFirefox、そしてInternet Explorer 9で正常にデータが渡せることを確認できています。
これは環境によって違いますので、工夫してみてください。
特に4番目のヘッダーでapplication/xmlとなっています。これはJavaScriptに対して渡すデータの種類をXMLとして規定する部分ですから、重要です。

SetBookByAmazon.php

次に「AmazonBookInfo.php」から呼び出しされた「SetBookByAmazon.php」のコードを示します。

 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
/*
 * Amazon ISBN Data
 * Copyright (c) 2015 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */

<?php
function urlencode_rfc3986($str){
    return str_replace('%7E', '~', rawurlencode($str));
}
function SetBookByAmazon($BookId,$BookCode ){
    define('AccessKeyId','ICHIBANSAISHINNOAKUSESUKII');
    define('SecretAccessKey','DAIJINADAIJINASIIKURETTOAKUSESUKIIDESUNENN');
    define('AssociateTag','anatanoiddesu-99');

    $baseurl = 'http://ecs.amazonaws.jp/onca/xml';
    $params = array();
    $params['AWSAccessKeyId'] = AccessKeyId;
    $params['AssociateTag']   = AssociateTag;
    $params['IdType']         = $BookCode;
    $params['ItemId']         = $BookId;
    $params['Operation']      = 'ItemLookup';
    $params['ResponseGroup']  = 'ItemAttributes,Images';
    $params['SearchIndex']    = 'Books';
    $params['Service']        = 'AWSECommerceService';
    $params['Timestamp']      = gmdate('Y-m-dTH:i:sZ');
    $params['Version']        = '2009-03-31';

    ksort($params);

    $canonical_string = '';
    foreach ($params as $k => $v) {
        $canonical_string .= '&'.urlencode_rfc3986($k).'='.urlencode_rfc3986($v);
    }
    $canonical_string = substr($canonical_string, 1);

    $parsed_url = parse_url($baseurl);
    $string_to_sign = "GETn{$parsed_url['host']}n{$parsed_url['path']}n{$canonical_string}";
    $signature = base64_encode(hash_hmac('sha256', $string_to_sign, SecretAccessKey, true));

    $url = $baseurl.'?'.$canonical_string.'&Signature='.urlencode_rfc3986($signature);

    //echo $url;

    $response = file_get_contents($url);
    echo $response;
}

次の行の3つのパラメーターの値は、それぞれ Amazon の認証情報で確認した値に置き換えてください。

12
13
14
    define('AccessKeyId','ICHIBANSAISHINNOAKUSESUKII');
    define('SecretAccessKey','DAIJINADAIJINASIIKURETTOAKUSESUKIIDESUNENN');
    define('AssociateTag','anatanoiddesu-99');

動作確認

「AmazonBookInfo.php」と「SetBookByAmazon.php」をサーバーにアップロードしたら、Webブラウザーで「AmazonBookInfo.php」のパスにアクセスしてください。
XML形式で本の情報が表示されたことを確認します。

1
https://example.com/AmazonBookInfo.php?code=ISBN&no=<ISBN>

今回はISBNのみに焦点を当てて書籍情報を取得しています。
「SetBookByAmazon.php」に記述しているAmazon APIのURLに渡すパラメーター次第で、さまざまな商品情報を取得できます。
今回使用したAPIの詳細は、次のページを参照してください。
Product Advertising API | ItemLookUp (External link)

プログラムの解説

商品の情報
22
23
    $params['IdType']         = $BookCode;
    $params['ItemId']         = $BookId;

kintoneカスタマイズファイルから渡された変数が格納される場所です。

Amazonから取得する情報の種類
25
    $params['ResponseGroup']  = 'ItemAttributes,Images';

Amazonから取得する情報の種類を指定します。
それぞれ以下を示しています。

  • ItemAttributes:商品詳細
  • Images:画像情報

複数の種類を指定する場合はカンマ(,)でつなげます。

署名作成の準備
31
ksort($params);

指定した各種パラメーターをそのパラメーター名称で並び替えます。
署名作成時に必要な処理です。

各種パラメーターのエンコード
33
34
35
36
37
    $canonical_string = '';
    foreach ($params as $k => $v) {
        $canonical_string .= '&'.urlencode_rfc3986($k).'='.urlencode_rfc3986($v);
    }
    $canonical_string = substr($canonical_string, 1);

指定した各種パラメーターをエンコードします。
RFC3986 (External link) で定められた方法に準じています。

署名文字列の生成
39
40
41
    $parsed_url = parse_url($baseurl);
    $string_to_sign = "GETn{$parsed_url['host']}n{$parsed_url['path']}n{$canonical_string}";
    $signature = base64_encode(hash_hmac('sha256', $string_to_sign, SecretAccessKey, true));

ハッシュ関数を使用して、Secret Access Keyを元の文字列が推測できない文字列に変換します。

商品情報の取得(Amazon API の実行)
47
$response   = file_get_contents($url);

組み立てたAmazon APIのURLを実行します。
レスポンスボディはXML形式で返却されます。

リクエスト結果の表示と返却
48
echo $response;

APIから返却された文字列を表示します。
この表示処理によって、呼び出し元へ文字列が戻されます。

注意事項

セキュリティの観点から、2つのファイルをアップロードしたサーバーはHTTPS化してください。
kintone セキュアコーディングガイドライン | 通信に HTTPS を使用する

またkintoneのURLはhttps://sample.cybozu.com/のようにhttpsで始まるため、PHPサーバーがHTTP化していないとブラウザーによって好ましからざる動きをすることもあります。

kintone カスタマイズ

PHPサーバー側で、SBNからAmazonの書籍情報を取得する処理が完成しました。
あとはkintoneカスタマイズで受け取って加工するだけです。

kintoneアプリにカスタマイズを適用する方法はこの記事では割愛します。
詳しくは カスタマイズファイルの適用方法を確認してください。

Amazon API を実行するタイミング

このカスタマイズではレコードの詳細画面でAmazon情報を取得します。
そのため、レコード編集画面を開いたときの実装は割愛しています。

場合によっては、詳細画面からAmazon APIを呼び出したときに「Http/1.1 Service Unavailable」エラーが発生します。
レコード詳細画面を開くという操作は頻繁に行う操作のため、Amazon APIへのリクエスト回数が制限値を超過してしまうことが原因のようです。
このエラー対策については、jQueryのAjaxで、Errorハンドラーから再帰呼び出しを試しましたが、calleeとuse strictが両立できませんでした。
なので、時間をおいて再実行ということでご了承ください。

クロスドメイン制約

PHPサーバーへのリクエストを送信するとき、「sample.cybozu.com」から「example.com」といった異なるドメインのサーバーにAjaxリクエストを発行します。
しかし別ドメインへのリクエストはセキュリティリスクにつながるため、Webブラウザーでは簡単に通信できないようになっています。
これをクロスドメイン制約といいます。

このカスタマイズでは、kintoneとPHPサーバーとのやりとりにXML形式でやりとりしています。
JSONPを使えばクロスドメイン制約についても解決できるようです。

ソースコード

このカスタマイズにはjQueryを使用します。
カスタマイズを適用する際には、サイボウズが提供する Cybozu CDNのjQueryのURLを指定してください。 また、次の内容をテキストエディタに貼り付け、「AmazonCallBookInfoDetail.js」という名前で保存します。

  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
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/*
 * Amazon ISBN Data
 * Copyright (c) 2015 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */

(function() {
  'use strict';

  jQuery.support.cors = true;
  kintone.events.on('app.record.detail.show', (event) => {
    const userAgent = window.navigator.userAgent.toLowerCase();
    let Isbn = '';
    Isbn = event.record.F_Isbn.value;
    Isbn = Isbn.replace(/-/g, '');
    Isbn = Isbn.replace(/ISBN/g, '');
    const Space_Amazon = kintone.app.record.getSpaceElement('Space_Image');
    const Space_LargeImg = kintone.app.record.getSpaceElement('Large_Image');
    let errormsg;

    if ((Isbn.substr(Isbn.length - 1, 1)).toLocaleUpperCase() !== 'X' && isNaN(Isbn, 10)) {
      Space_Amazon.innerHTML = 'Isbnの値が不正な文字列または記号を含んでいます。';
      return event;
    }
    Space_LargeImg.style.width = 311;
    Space_Amazon.innerHTML = 'データを読み込んでいます。';

    $.ajax({
      type: 'POST',
      url: 'https://example.com/AmazonBookInfo.php?code=ISBN&no=' + Isbn + '&now=' + parseInt((new Date()) / 1000, 10),
      dataType: 'xml',
      cache: false,
      async: true,
      timeout: 10000,
      success: (data) => {
        let aws_ASIN; // Amazon商品コード
        let aws_DetailPageURL; // Amazon詳細ページ
        let aws_AddToWishlist; // AmazonWishリストページ
        let aws_TellAFriend; // Amazon友だちに教えるページ
        let aws_AllCustomerReviews; // Amazonカスタマーレビューページ
        let aws_AllOffers; // Amazon全ての提案ページ
        let aws_SwatchImageURL; // 見本用イメージ画像URL
        let aws_SwatchImageHeight; // 見本用イメージ縦幅
        let aws_SwatchImageWidth; // 見本用イメージ横幅
        let aws_SmallImageURL; // 小イメージ画像URL
        let aws_SmallImageHeight; // 小イメージ縦幅
        let aws_SmallImageWidth; // 小イメージ横幅
        let aws_ThumbnailImageURL; // サムネイル用イメージ画像URL
        let aws_ThumbnailImageHeight; // サムネイル用イメージ縦幅
        let aws_ThumbnailImageWidth; // サムネイル用イメージ横幅
        let aws_TinyImageURL; // 極小イメージ画像URL
        let aws_TinyImageHeight; // 極小イメージ縦幅
        let aws_TinyImageWidth; // 極小イメージ横幅
        let aws_MediumImageURL; // 中イメージ画像URL
        let aws_MediumImageHeight; // 中イメージ縦幅
        let aws_MediumImageWidth; // 中イメージ横幅
        let aws_LargeImageURL; // 大イメージ画像URL
        let aws_LargeImageHeight; // 大イメージ縦幅
        let aws_LargeImageWidth; // 大イメージ横幅
        let aws_Author; // 著者
        let aws_Creator; // 著者(編者等)
        let aws_Binding; // 装丁
        let aws_EAN; // 商品識別コード
        let aws_EANList; // 商品識別コードリスト
        let aws_IsAdultProduct; // アダルト商品フラグ
        let aws_ISBN; // ISBNコード
        let aws_Label; // レーベル
        let aws_LanguageName; // 使用言語名
        let aws_LanguageType; // 使用言語タイプ
        let aws_ListPriceAmount; // 価格
        let aws_ListPriceCurrencyCode; // 価格通貨コード
        let aws_ListPriceFormattedPrice; // 表示価格
        let aws_Manufacturer; // 生産者
        let aws_NumberOfPages; // ページ数
        let aws_PackageDimensionsHeight; // 商品サイズ縦
        let aws_PackageDimensionsLength; // 商品サイズ奥行
        let aws_PackageDimensionsWeight; // 商品重量
        let aws_PackageDimensionsWidth; // 商品サイズ横
        let aws_ProductGroup; // 商品種別
        let aws_ProductTypeName; // 商品種別名称
        let aws_PublicationDate; // 発行日
        let aws_Publisher; // 発行者
        let aws_ReleaseDate; // 発売日
        let aws_SKU; // 商品管理コード
        let aws_Studio; // スタジオ
        let aws_Title; // 題名

        $(data).find('Item').each(function() {
          if ($(this).find('ItemAttributes').find('ISBN').text()) {
            aws_ASIN = $(this).find('ASIN').text();
            aws_DetailPageURL = $(this).find('DetailPageURL').text();
            $(this).find('ItemLink').find('Description').each(function() {
              if ($(this).text() === 'Add To Wishlist') {
                aws_AddToWishlist = $(this).next().text();
              }
              if ($(this).text() === 'Tell A Friend') {
                aws_TellAFriend = $(this).next().text();
              }
              if ($(this).text() === 'All Customer Reviews') {
                aws_AllCustomerReviews = $(this).next().text();
              }
              if ($(this).text() === 'All Offers') {
                aws_AllOffers = $(this).next().text();
              }
            });
            $(this).find('ImageSet').each(function() {
              aws_SwatchImageURL = $(this).find('SwatchImage').find('URL').text();
              aws_SwatchImageHeight = $(this).find('SwatchImage').find('Height').text();
              aws_SwatchImageWidth = $(this).find('SwatchImage').find('Width').text();
              aws_SmallImageURL = $(this).find('SmallImage').find('URL').text();
              aws_SmallImageHeight = $(this).find('SmallImage').find('Height').text();
              aws_SmallImageWidth = $(this).find('SmallImage').find('Width').text();
              aws_ThumbnailImageURL = $(this).find('ThumbnailImage').find('URL').text();
              aws_ThumbnailImageHeight = $(this).find('ThumbnailImage').find('Height').text();
              aws_ThumbnailImageWidth = $(this).find('ThumbnailImage').find('Width').text();
              aws_TinyImageURL = $(this).find('TinyImage').find('URL').text();
              aws_TinyImageHeight = $(this).find('TinyImage').find('Height').text();
              aws_TinyImageWidth = $(this).find('TinyImage').find('Width').text();
              aws_MediumImageURL = $(this).find('MediumImage').find('URL').text();
              aws_MediumImageHeight = $(this).find('MediumImage').find('Height').text();
              aws_MediumImageWidth = $(this).find('MediumImage').find('Width').text();
              aws_LargeImageURL = $(this).find('LargeImage').find('URL').text();
              aws_LargeImageHeight = $(this).find('LargeImage').find('Height').text();
              aws_LargeImageWidth = $(this).find('LargeImage').find('Width').text();
            });
            aws_Author = $(this).find('ItemAttributes').find('Author').text();
            aws_Creator = $(this).find('ItemAttributes').find('Creator').text() + ' ' + $(this).find('ItemAttributes').find('Creator').attr('Role');
            aws_Binding = $(this).find('ItemAttributes').find('Binding').text();
            aws_EAN = $(this).find('ItemAttributes').find('EAN').text();
            aws_EANList = $(this).find('ItemAttributes').find('EANList').find('EANListElement').text();
            aws_IsAdultProduct = $(this).find('ItemAttributes').find('IsAdultProduct').text();
            aws_ISBN = $(this).find('ItemAttributes').find('ISBN').text();
            aws_Label = $(this).find('ItemAttributes').find('Label').text();
            aws_LanguageName = $(this).find('ItemAttributes').find('Languages').find('Language').find('Name').text();
            aws_LanguageType = $(this).find('ItemAttributes').find('Languages').find('Language').find('Type').text();
            aws_ListPriceAmount = $(this).find('ItemAttributes').find('ListPrice').find('Amount').text();
            aws_ListPriceCurrencyCode = $(this).find('ItemAttributes').find('ListPrice').find('CurrencyCode').text();
            aws_ListPriceFormattedPrice = $(this).find('ItemAttributes').find('ListPrice').find('FormattedPrice').text();
            aws_Manufacturer = $(this).find('ItemAttributes').find('Manufacturer').text();
            aws_NumberOfPages = $(this).find('ItemAttributes').find('NumberOfPages').text();
            aws_PackageDimensionsHeight = $(this).find('ItemAttributes').find('PackageDimensions').find('Height').text();
            aws_PackageDimensionsLength = $(this).find('ItemAttributes').find('PackageDimensions').find('Length').text();
            aws_PackageDimensionsWeight = $(this).find('ItemAttributes').find('PackageDimensions').find('Weight').text();
            aws_PackageDimensionsWidth = $(this).find('ItemAttributes').find('PackageDimensions').find('Width').text();
            aws_ProductGroup = $(this).find('ItemAttributes').find('ProductGroup').text();
            aws_ProductTypeName = $(this).find('ItemAttributes').find('ProductTypeName').text();
            aws_PublicationDate = $(this).find('ItemAttributes').find('PublicationDate').text();
            aws_Publisher = $(this).find('ItemAttributes').find('Publisher').text();
            aws_ReleaseDate = $(this).find('ItemAttributes').find('ReleaseDate').text();
            aws_SKU = $(this).find('ItemAttributes').find('SKU').text();
            aws_Studio = $(this).find('ItemAttributes').find('Studio').text();
            aws_Title = $(this).find('ItemAttributes').find('Title').text();
          }
        });
        if (aws_ASIN) {
          Space_Amazon.innerHTML =
                            '題名:' + aws_Title + '<br />' +
                            '著者:' + (aws_Author ? aws_Author : aws_Creator) + '<br />' +
                            'EAN:' + aws_EAN + '<br />' +
                            'EANlist:' + aws_EANList + '<br />' +
                            'ISBN:' + aws_ISBN + '<br />' +
                            '単一商品コード:' + aws_SKU + '<br />' +
                            'Amazon商品コード:' + aws_ASIN + '<br />' +
                            '装丁:' + aws_Binding + '<br />' +
                            '価格:' + aws_ListPriceFormattedPrice + '<br />' +
                            'ページ数:' + aws_NumberOfPages + '<br />' +
                            '出版日:' + aws_PublicationDate + ' 発売日:' + aws_ReleaseDate + '<br />' +
                            '出版社:' + aws_Publisher + '<br />' +
                            'レーベル:' + aws_Label + '<br />' +
                            '製作者:' + aws_Manufacturer + '<br />' +
                            'スタジオ:' + aws_Studio + '<br />' +
                            '言語:' + aws_LanguageName + '<br />' +
                            '縦:' + aws_PackageDimensionsHeight + ' 奥:' + aws_PackageDimensionsLength + ' 横:' + aws_PackageDimensionsWidth + ' 重量:' + aws_PackageDimensionsWeight + '<br />' +
                            '商品種類:' + aws_ProductGroup + ' 商品分類:' + aws_ProductTypeName + '<br />' +
                            '<a href=\'' + aws_DetailPageURL + '\' target=\'_blank\'>Amazon詳細ページ</a><br />' +
                            '<a href=\'' + aws_AddToWishlist + '\' target=\'_blank\'>AmazonWishリストページ</a><br />' +
                            '<a href=\'' + aws_TellAFriend + '\' target=\'_blank\'>Amazon友だちに教えるページ</a><br />' +
                            '<a href=\'' + aws_AllCustomerReviews + '\' target=\'_blank\'>Amazonカスタマーレビューページ</a><br />' +
                            '<a href=\'' + aws_AllOffers + '\' target=\'_blank\'>Amazon全ての提案ページ</a><br />';
          if (userAgent.indexOf('firefox') !== -1) {
            Space_LargeImg.innerHTML = '<img src=\'' + aws_LargeImageURL.replace('http:', '') + '\' width=\'' + aws_LargeImageWidth + '\' height=\'' + aws_LargeImageHeight + '\' />';
          } else {
            Space_LargeImg.innerHTML = '<img src=\'' + aws_LargeImageURL + '\' width=\'' + aws_LargeImageWidth + '\' height=\'' + aws_LargeImageHeight + '\' />';
          }
        } else {
          Space_Amazon.innerHTML = 'エラー名称:' +
                            $(data).find('Items').find('Request').find('Errors').find('Error').find('Code').text() +
                            '<br />' +
                            'エラー内容:' +
                            $(data).find('Items').find('Request').find('Errors').find('Error').find('Message').text();
        }
      },
      error: function(XMLHttpRequest, textStatus, errorThrown) {
        errormsg = '--- エラー情報 ---';
        errormsg = errormsg + '<BR>エラー番号:' + XMLHttpRequest.status;
        errormsg = errormsg + '<BR>エラー詳細:' + XMLHttpRequest.statusText;
        errormsg = errormsg + '<BR>エラー内容:' + textStatus;
        errormsg = errormsg + '<BR>エラー発生箇所:' + errorThrown;
        for (let i = 0; i < errorThrown; i++) {
          errormsg = errormsg + '<BR>error ' + i + ':' + errorThrown[i];
        }
        window.alert(errormsg);
      }
    });

    return event;
  });

})();

プログラムの解説

ブラウザー種別の判別
14
    const userAgent = window.navigator.userAgent.toLowerCase();

実行しているブラウザーの種別をここで取得しています。
呼び出し先のURLがhttpsから始まっても、画像データはhttpで返されることがあり、それを防ぐために一部ブラウザーによって画像のアドレスを変更しています。

ISBN のフォーマットチェック
23
24
25
26
    if ((Isbn.substr(Isbn.length - 1, 1)).toLocaleUpperCase() !== 'X' && isNaN(Isbn, 10)) {
      Space_Amazon.innerHTML = 'Isbnの値が不正な文字列または記号を含んでいます。';
      return event;
    }

本稿で実装する内容では、ユーザ入力データからのサニタイズ処理はあまりありません。
唯一この場所で実施しています。
数値かをチェックしていますが、ISBNの末尾一桁のチェックディジット結果がXとなる場合のみ許可しています。

Amazon API の URL の生成
32
      url: 'https://example.com/AmazonBookInfo.php?code=ISBN&no=' + Isbn + '&now=' + parseInt((new Date()) / 1000, 10),

先に紹介しましたが、codeIsbnという2つのパラメーター以外に、nowというパラメーターも追加しています。
実はnowパラメーターは以下の処理では使われていません。
投げるリクエストURLを常に変化させないとInternet Explorer上でうまく値が戻ってこないという情報から、このような記載を設けています。

キャッシュの無効化
34
      cache: false,

キャッシュをOFFにしないとInternet Explorerで値を取得できないことがあるため追記しています。

また、AmazonからのXMLデータを扱うときに気を付けるべき点があります。
それは書籍とKindle書籍の場合です。
Kindle書籍はISBN項目を持っていないのですが、Amazonの仕様でISBNに対してデータ検索結果にKindleデータも含まれます。
XML上では、2つの<Item>タグが紐付いています。
その場合は、JavaScript側で<Item>タグ内をループさせるのですが、<ISBN>タグの有無で判別しています。

だいたいのXMLデータの構造がおわかりいただけましたでしょうか。
項目については ItemAttributes レスポンスグループ (External link) で説明されています。

Amazon の商品情報の HTML の生成
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
        $(data).find('Item').each(function() {
          if ($(this).find('ItemAttributes').find('ISBN').text()) {
            // ...
            $(this).find('ImageSet').each(function() {
              // ...
            });
          }
        }
        if (aws_ASIN) {
          Space_Amazon.innerHTML = // ...
        });

XMLのデータが格納された変数dataに対し、<Item>タグの要素を検索し、さらにその中で<ImageSet>タグを検索しています。
そして必要な情報を取得した後にinnerHTMLを使用して書き込むべき文字列を生成しています。

kintone への適用

jQuery CDNのURLと「AmazonCallBookInfoDetail.js」をkintoneアプリに適用します。

スクリーンショットでは「jQuery.xdomainrequest.min.js」が適用されていますが、現在は適用不要です。
このファイルは、Internet Explorer 9以前のブラウザーでクロスドメイン制約に対処するため必要なライブラリです。
MoonScript/jQuery-ajaxTransport-XDomainRequest (External link)

全体の動作確認

ISBNを入力したレコードを開いて、次のように表示できることを確認してください。

レコードを移動すると新たなレコード先でAmazonから呼び出されたデータが表示されます。
ただし、あまり短い時間で呼び出すとAmazonからの応答が途絶えてしまいます。
その場合はF5でリロードしてください。

まとめ

外部認証が必要な複雑なAPIを呼び出す場合、外部サーバーでAPIを実行する方法がわかると、さまざまなケースでkintoneをより一層活用できます。
ただしくれぐれも過度なAmazonへのリクエストは控えてください。
1時間当たりのリクエスト回数に上限があるためです。