本の情報を表示するHangouts Chat用ボットのつくり方

2019.07.19

フジワラ
Hangouts Chatで、ISBNを渡すと書誌情報を返してくれるボットをGoogle Apps Script(GAS)でつくってみました。

はじめに

ニューロマジックでは社内でやりとりするチャットツールとしてG SuiteのHangouts Chatを使用しています。

Hangouts Chatで本を紹介しようとAmazonのURLを貼り付けると、以下のように書影以外の画像が表示されたり、

以下のように別の本の書影が表示されることがあります。

ちょっとわかりづらくて不便なので、ISBN(10 or 13)を渡すと書誌情報を返してくれるボットをGoogle Apps Script(GAS)でつくってみました。

つくり方

Hangouts Chat APIガイド内のGoogle Apps Script botや、Google Developers CodelabsのHangouts Chat bot with Apps Script を参考につくっていきます。

書誌情報の取得はAmazonからスクレイピングするのが一番カンタンですが、規約的に問題があるため、カーリルと版元ドットコムが提供しているopenDBを利用します。

1. コードの記述

Google Apps Script Editorを開いてコードを記述し、プロジェクト名(ここではBookDataBot-demo)を付けて保存します。

  • 入力メッセージは onMessage イベントで取得します。
  • 入力メッセージはDMかチャットルームかでメンション(@{ボット名})の有無などの違いがあるため、多くの場合は判別が必要です。が、このボットはISBNの文字列(0-9の数字とX)以外を無視する仕様のため判別を省略しています。
  • メッセージの返却方法にはSimple text messagesとCard Formatting Messagesがありますが、このボットではCard Formatting Messagesを使用します。
  • openDBの仕様はOpenBD 書誌APIデータ仕様JPO出版情報登録センター ONIXデータ仕様第4版を参照します。
  • 他のユーザーがボットにメッセージを送って利用できるようにするには、Google Apps Scriptの共有設定で閲覧権限を付与する必要があります。
  • この記事では取り扱いませんが、GASでの本格的な開発の際はバージョン管理などができるclaspを使うのがオススメです。
bookdata-bot.gs.jsvar OPEN_BD_API_PATH = 'https://api.openbd.jp/v1/get?isbn=', //OpenDBのAPIのパス
  DESCRIPTION_MAX_LENGTH = 150, //表示する概要文の最大文字数
  RESPONSE_ERROR_MESSAGE = {"text": "書籍情報は見つかりませんでした。"}; //書誌情報を取得できなかったときのメッセージ

// 入力メッセージを受け取って書誌情報を返却する
function onMessage(event) {
  var ISBN = event.message.text.replace(/[^0-9X]/g, '');
  if ( /[0-9X]{10}/.test(ISBN) || /[0-9X]{13}/.test(ISBN) ) {
    var bookData = getOpenDBData(ISBN);
    if (bookData) {
      return setMessageCard(bookData);
    } else {
      return RESPONSE_ERROR_MESSAGE;
    }
  } else {
    return RESPONSE_ERROR_MESSAGE;
  }
}

// ISBNからOpenDBのデータを取得する
function getOpenDBData(isbn) {
  var fetchURL = OPEN_BD_API_PATH + isbn;
  var response = UrlFetchApp.fetch(fetchURL, {'method' : 'get', 'contentType' : 'application/json; charset=utf-8'});
  var json = JSON.parse(response.getContentText('UTF-8'));
  if (json[0]) {
    var myOnix = json[0].onix,
      mySummary = json[0].summary,
      myBookData = {};
    myBookData['title'] = myOnix.DescriptiveDetail.TitleDetail.TitleElement.TitleText.content;
    myBookData['subtitle'] = myOnix.DescriptiveDetail.TitleDetail.TitleElement.Subtitle ? myOnix.DescriptiveDetail.TitleDetail.TitleElement.Subtitle.content : '';
    myBookData['description'] = myOnix.CollateralDetail.TextContent ? getDescription(myOnix.CollateralDetail.TextContent) : "";
    myBookData['author'] = mySummary['author'];
    myBookData['publisher'] = mySummary['publisher'];
    myBookData['isbn-13'] = mySummary['isbn'];
    myBookData['asin'] = toISBN10(myBookData['isbn-13']);
    myBookData['amazon'] = 'https://www.amazon.co.jp/dp/' + myBookData['asin'] + '/';
    myBookData['thumb'] = myOnix.CollateralDetail.SupportingResource ? myOnix.CollateralDetail.SupportingResource[0].ResourceVersion[0].ResourceLink : '';
    return myBookData;
  } else {
    return;
  }
}

// 返却する書誌情報をCard Formatting Messagesのフォーマットに整形する
function setMessageCard(bookData) {
  var headerData = {},
    sectionData = [];
  if ( bookData['title'] ) {
    headerData['title'] = bookData['title'];
    if (bookData['subtitle'])
      headerData['subtitle'] = bookData['subtitle'];
    if (bookData['thumb'])
      headerData['imageUrl'] = bookData['thumb'];
    if (bookData['description']) {
      sectionData.push({
        "widgets": [
          {
            "textParagraph": {
              "text": bookData['description']
            }
          }
        ]
      });
    }
    if (bookData['author']) {
      sectionData.push({
        "widgets": [
          {
            "keyValue": {
              "topLabel": "著者",
              "content": bookData['author']
            }
          }
        ]
      });
    }
    if (bookData['publisher']) {
      sectionData.push({
        "widgets": [
          {
            "keyValue": {
              "topLabel": "出版社",
              "content": bookData['publisher']
            }
          }
        ]
      });
    }
    sectionData.push({
      "widgets": [
        {
          "buttons": [
            {
              "textButton": {
                "text": "Amazonで見る",
                "onClick": {
                  "openLink": {
                    "url": bookData['amazon']
                  }
                }
              }
            }
          ]
        }
      ]
    });
    return {
      "cards": [
        {
          "header": headerData,
          "sections": sectionData
        }
      ]
    };
  } else {
    return RESPONSE_ERROR_MESSAGE;
  }
}

// OpenDBのデータを元に表示する概要文を調整する
function getDescription(content) {
  var textObj = {},
    description = '';
  content.forEach(function(value) {
    textObj[value['TextType']] = value['Text'];
  });
  if (textObj['03']) {
    description = textObj['03'];
  } else if (textObj['02']) {
    description = textObj['02'];
  } else if (textObj['23']) {
    description = textObj['23'];
  }
  description = description.replace(/\n{2,}/g, ' ').replace(/\n/g, '');
  if (description.length > DESCRIPTION_MAX_LENGTH)
    description = description.substr(0, DESCRIPTION_MAX_LENGTH - 1) + '…';
  return description;
}

// ISBN13をISBN10に変換する
function toISBN10(isbn) {
  var calc = 0,
    str = isbn.slice(3, 12),
    checkDigit,
    i = 0,
    j = 10;
  for(i, j; i < 9; i++, j--) {
    calc += Number(str[i]) * j;
  }
  checkDigit = 11 - calc % 11;
  if(checkDigit === 10) {
    checkDigit = 'X';
  } else if(checkDigit === 11) {
    checkDigit = 0;
  }
  return str + checkDigit;
}

2. マニフェストファイルの作成

Google Apps Script Editorのメニューから「表示」→「マニュフェスト ファイルを表示」を選択します。 appscript.json が表示されるので、以下のように書き換えます。

appscript.json{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER",
  "chat": {}
}

3. 作成したコードの共有設定を変更

Google Apps Script Editorの右上にある「共有」ボタンから、アクセスできるユーザーに「リンクを知っている{組織名}の全員」を追加します。

4. 作成したプロジェクトに対する編集権限を追加

作成したコードをボットとしてデプロイするには、プロジェクトの編集権限が必要です。2019年4月8日以降、G Suiteの一般ユーザーではこの編集権限がデフォルトでオフになっているため、G Suiteの管理者にGASで作成したプロジェクト(ここではBookDataBot-demo)に対する編集権限追加を依頼します。

5. Google Cloud ConsoleでHangouts Chat APIを有効化

Google Cloud Platformにアクセスし、左上の「プロジェクトの選択」を選択します。

「プロジェクトとフォルダを検索」の検索窓からGASで付けたプロジェクト名(ここではBookDataBot-demo)を検索し、出てきたプロジェクトを選択します。

「上位のプロジェクト」から「Google API」、または左上ハンバーガーメニューから「APIとサービス」>「ダッシュボード」を選択します。

「APIとサービスを有効化」を選択します。

「APIとサービスを検索」の検索窓から「Hangouts Chat API」を検索し、選択します。

「Hangouts Chat API」の「有効にする」ボタンを押して、APIを有効化します。

もしも以下のように「有効にする」ボタンが選択できないときは、プロジェクトの編集権限追加が必要です。G Suiteの管理者に依頼してください。

APIを有効化してHangouts Chat APIの概要ページに遷移後、左メニューから「設定」を選択します。

Hangouts Chat APIの概要ページに遷移後、左メニューの「設定」をクリックし、以下の内容を入力します。

項目(※は必須) 入力内容
ボット名 ※ Hangouts Chatで表示するボット名を入力します
(例) BookDataBot
アバターのURL ※ ボットのアイコン画像のURLを入力します
※httpsのURLが必要です
説明 ※ Hangouts Chatでボットを検索したときに表示される説明文を入力します。
(例)ISBNから書誌情報を取得するボットです。
機能 「ダイレクトメッセージ」「ルーム」両方を選択します
接続設定 「Apps Script project」を選択し、手順3で取得した「デプロイID」を入力します
権限 ボットのインストールを許可する対象を選択してメールアドレスを入力します

6. ボットを試す

作成したボットはHangouts Chatに登録されています。検索し、話しかけてみます。
初回は必ず「設定を必要としています」と表示されます。「設定」を押してアクセスを許可します。

設定完了後、ボットにISBNを渡すと、以下のように書誌情報がカード形式で表示されます。

まとめ

Hangouts ChatでのボットづくりはSlackなどと比べて制約が多い上にネット上の記事もかなり少ないため、ちょっとハードルが高い印象があります。が、カンタンなものであれば、ほぼJavaScriptの知識だけでつくることが可能です。

みなさんもボットづくりに挑戦してみてはいかがでしょうか。

参考記事

Neuromagic Labsの新着記事