1日の摂取栄養素を管理するチャットボットをGoogle Apps ScriptとLINE BOTで作る

この記事を読むのに必要な時間は約 23 分です。

この記事を読むのに必要な時間は約 23 分です。

今回はGoogle Apps ScriptとLINE BOTでチャットボットを作成する方法についてまとめてみました。

 

今回サンプルで作ったチャットボットは、食べ物に詳しく、

 

「食べ物に含まれる栄養素(たんぱく質、炭水化物、脂質)を調べてくれる」

「今日摂取した栄養素を記録してくれる」

 

そんなチャットボットを作ってみました。

 

今回作ったラインボットは、

今日摂取した栄養素を記録することができたり、

 

 

食べ物の栄養素を調べたり、

1日の残りの目標栄養素摂取量を確認できます。

 

 

もし、食べ物以外のことなど、

チャットボットが理解できない内容を質問すると、、、

理解できる内容をLINEしてもらうように誘導します。

 

 

Google Apps ScriptとLINE BOTの連携イメージ

 

 

Botに必要なテーブルをSpread Sheetで作成

 

 

 

今回はデータベースの代わりにGoogle Spread Sheetを利用してテーブルを作成します。

SpreadSheetは以下の3つを用意します。

 

食品マスタシート

食品の栄養素(タンパク質、脂質、炭水化物)の情報を管理するスプレッドシートです。

LINE BOTアプリは食品の栄養素を質問されたらこのシートを参照して値を返却します。

 

スプレッドシートは以下を参考に作成してください。

食品マスタ

 

 

摂取栄養素管理テーブルシート

摂取した食品の栄養素(タンパク質、脂質、炭水化物)の情報を記録しておくスプレッドシートです。

LINE BOTアプリは1日の栄養摂取量記録を依頼されたら、このシートを更新します。

また、更新すると、合計値が自動的に算出されるようにしておき、

1日の残りの栄養摂取量を聞かれたら答えられるようにしておきます。

 

 

スプレッドシートは以下を参考に作成してください。

摂取栄養素管理テーブル(更新用)

 

 

摂取栄養素管理テーブル(合計値計算用)

 

会話ステータス管理テーブルシート

会話の状態を記録しておくスプレッドシートです。

会話の流れを制御することを目的として使います。

 

以下の図のように、会話が単発で終わらないものに関してはステータスで会話の流れを制御するようにアプリを作成しています。

 

 

スプレッドシートは以下を参考に作成してください。

会話ステータス管理テーブル

 

以上で、アプリに必要なスプレッドシート(テーブル)の準備は完了です。

 

 

Google Apps Scriptアプリ作成・デプロイ

 

 

次にGoogle Apps Scriptのアプリ作成、デプロイを行います。

 

ひとまず、以下を参考にアプリが動くところまでの設定をしてください。

【Google Apps Script入門(1)】GASで動くものを作ってみる

設定が完了したら、

デプロイしたアプリのURLを控えておいてください。

 

アプリが動くことを確認したら、

ソースコードをLINE BOT用に更新していきます。

 

作成したソースコードは以下です。

(LINE BOTのアクセストークン、スプレッドシートIDは差し替える必要があります。)

 

Google Apps Script
/******定数定義******/
/* CHANNEL_ACCESS_TOKEN */
var CHANNEL_ACCESS_TOKEN = 'LINE BOTのアクセストークンを設定'; 

/* LINE BOT APIのURL */
var line_endpoint = 'https://api.line.me/v2/bot/message/reply';

/* スプレッドシートID */
SPREAD_SHEET = {
  MST_FOOD: "スプレッドシートのIDを設定", // 食品マスタ
  TRN_NUTRITION: "スプレッドシートのIDを設定", //栄養素テーブル
  TRN_STATUS: "スプレッドシートのIDを設定" // 会話ステータステーブル
};

/* スプレッドシート名 */
MST_FOOD = {
  SHEET_NAME: "MST_FOOD"
};
TRN_NUTRITION = {
  SHEET_NAME: "TRN_NUTRITION",
  CALC_SHEET_NAME: "CALCULATION"
};
TRN_STATUS = {
  SHEET_NAME: "TRN_STATUS"
};

// スプレッドシートインスタンスを生成する
// 会話ステータステーブルシート
var statussheet = SpreadsheetApp.openById(SPREAD_SHEET.TRN_STATUS).getSheetByName(TRN_STATUS.SHEET_NAME);
// 食品マスタシート
var foodsheet = SpreadsheetApp.openById(SPREAD_SHEET.MST_FOOD).getSheetByName(MST_FOOD.SHEET_NAME);
// 栄養素テーブルシート
var nutritionsheet = SpreadsheetApp.openById(SPREAD_SHEET.TRN_NUTRITION).getSheetByName(TRN_NUTRITION.SHEET_NAME);
// 栄養素テーブルシート(計算シート)
var nutritionCalcsheet = SpreadsheetApp.openById(SPREAD_SHEET.TRN_NUTRITION).getSheetByName(TRN_NUTRITION.CALC_SHEET_NAME);
// 会話ステータス取得
var status = statussheet.getRange('A2').getValue();


/* Rest API(Post) */
function doPost(e) {
  // JSONをパースする
  var json = JSON.parse(e.postData.contents);

  // 返信するためのトークン取得
  var reply_token= json.events[0].replyToken;
  if (typeof reply_token === 'undefined') {
    return;
  }
  Logger.log("reply_token:" + reply_token);
  
  
  // 送られたLINEメッセージを取得
  var user_message = json.events[0].message.text;  
  // 返信メッセージ
  var reply_messages;
  
  // 会話ステータスが栄養素成分調査モードの場合
  if (status == "1") {
    // LINEメッセージで食品マスタから栄養素成分を取得して調査結果を返却する
    var searchedSelectList = searchFood(user_message);
    if (searchedSelectList.length > 0) {
      reply_messages = [searchedSelectList[0][0] + 'の栄養素詳細\n【タンパク質】:' + searchedSelectList[0][1] + '[g]\n【脂質】:' + searchedSelectList[0][2] + '[g]\n【炭水化物】:'+ searchedSelectList[0][3] + "[g]"];
    }else {
      reply_messages = ["栄養素を調べましたが分かりませんでした。"];
    }
    statussheet.getRange('A2').setValue('0');
  // 会話ステータスが1日の栄養素成分登録モードの場合
  }else if (status == "2") {
    // LINEメッセージで食品マスタから栄養素成分を取得し、取得できたら栄養素テーブルに成分を登録する
    var searchedSelectList = searchFood(user_message);    
    if (searchedSelectList.length > 0) {
      var registData = [];
      registData.push(searchedSelectList[0][0]);
      registData.push(searchedSelectList[0][1]);
      registData.push(searchedSelectList[0][2]);
      registData.push(searchedSelectList[0][3]);
      nutritionsheet.appendRow(registData);
      reply_messages = ["今日の栄養素摂取量を更新しておきました。\n" + searchedSelectList[0][0] + 'の栄養素詳細\n【タンパク質】:' + searchedSelectList[0][1] + '[g]\n【脂質】:' + searchedSelectList[0][2] + '[g]\n【炭水化物】:'+ searchedSelectList[0][3] + "[g]"];
    }else {
      reply_messages = ["栄養素を調べましたが分かりませんでした。"];
    }
    statussheet.getRange('A2').setValue('0');
  // 会話ステータスが通常モードの場合
  } else {
    if (user_message.match(/記録|計算|追加/)) {
      reply_messages = ["今日の栄養素を記録するので食べたものを教えて下さい"];
      statussheet.getRange('A2').setValue('2');
    }else if (user_message.match(/調べる|調べたい/)) {
      reply_messages = ["栄養素を調べるので食べ物を教えてください。"];
      statussheet.getRange('A2').setValue('1');
    } else if (user_message.match(/目標|摂取量|残り|どれくらい/)) {
      var nutrition = nutritionCalcsheet.getRange('A7:C7').getValues()[0];
        Logger.log("status = %s", nutrition);
      reply_messages = ['今日残りの栄養素摂取量\n【タンパク質】:' + nutrition[0] + '[g]\n【脂質】:' + nutrition[1] + '[g]\n【炭水化物】:'+ nutrition[2] + "[g]"];
    } else if (user_message.match(/設定/)) {
      var nutrition = nutritionCalcsheet.getRange('A3:C3').getValues()[0];
      reply_messages = ['1日の栄養素摂取量\n【タンパク質】:' + nutrition[0] + '[g]\n【脂質】:' + nutrition[1] + '[g]\n【炭水化物】:'+ nutrition[2] + "[g]"];
    } else if (user_message.match(/リセット|消す/)) {
      // 栄養素テーブルに記録した1日の栄養素摂取量をリセットする
      var lastRow = nutritionsheet.getRange("A:A").getValues().filter(String).length - 1;
      nutritionsheet.deleteRows(2, lastRow);
      reply_messages = ['1日の栄養素摂取量をリセットしました。'];
    // LINEメッセージの内容が理解できなかった場合
    } else {
      randomMessage = ["理解できませんでした。「栄養素を調べたい」と入力すると食べ物の栄養素が調べられます"
                     , "理解できませんでした。「今日摂取した栄養素を記録したい」と入力すると栄養素が記録調できます"
                     , "理解できませんでした。「目標摂取量まで残りは?」と入力すると目標の栄養素の摂取量までの残りが確認できます。"
                     , "理解できませんでした。「設定」と入力すると一日の目標摂取栄養素が確認できます。"
                     , "理解できませんでした。「今日記録した栄養素をリセットする」と入力すると記録している栄養素がリセットできます。"
                       ]
      reply_messages = [randomMessage[Math.floor( Math.random() * randomMessage.length )]];
    }
  }

  // 返却メッセージを作成する
  var messages = reply_messages.map(function (v) {
    return {'type': 'text', 'text': v};    
  });    
  UrlFetchApp.fetch(line_endpoint, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': messages,
    }),
  });
  
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}


/* 食品マスタを検索して最初にヒットした食品を返却する */
var searchFood = function(user_message) {
  var lastRow = foodsheet.getRange("A:A").getValues().filter(String).length - 1;
  var allSelectList = foodsheet.getRange(2, 1, lastRow, 4).getValues();
  var searchedSelectList = new Array();
    
  for(var i in allSelectList) {
    var record = allSelectList[i];
    var regexp = new RegExp(user_message)
    if(record[0].match(regexp)) {
      searchedSelectList.push(record);
      Logger.log("record = %s", allSelectList[i]);
      break;
    }
  }
  return searchedSelectList;
};

 

ソースコードの解説

そもそも解説するほど難しいプログラムではないですが。。。

 

大きく、

「status(会話ステータス)」と「user_message(ユーザが発言したメッセージ)」によって処理を分岐しています。

 

例えば会話ステータスが、1(栄養素成分調査モード)の状態の時は、

user_message(ユーザが発言したメッセージ)をキーに食品マスタから該当の栄養素成分を取得して返答します。

 

また、会話ステータスが、0(通常会話モード)の状態の時は、

user_message(ユーザが発言したメッセージ)に事前に用意しているキーワード(調べる、追加、計算など)がメッセージに含まれるかによって返答するメッセージを決定しています。

(なので、「BOTが自ら学習して・・・」や、「文脈を解釈して・・」など高度なことはできません。)

 

 

 

LINE BOT設定手順

 

 

次にLINE BOTの設定を行います。

 

LINE BOT環境構築に関する詳細な手順は、以下の環境構築手順にまとめてありますので参照ください。

HerokuとLINE BOTでチャットボットを作る

 

設定が完了したら、Google Apps Scriptのアプリのアクセストークを

アクセストークン(ロングターム)で差し替え、

 

WebhookURLに、控えておいたGoogle Apps ScriptのアプリのURLを設定します。

 

以上で、LINE BOTの設定は完了です。

 

 

さいごに

 

 

今回はGoogle Apps ScriptとLINE BOTでチャットボットを作成しました。

 

 

ユーザが発言したメッセージの文脈の解析等を行わず、キーワードの有無で会話を判定するBotを作成したので、

賢いBotとは言えませんが、、、

 

Google Spread Sheetと連携したり他のGoogle APIと連携すれば、

面白いものが作れそうだということは分かりました。

 

 

今回のサンプルは会話の文脈や意味を全く理解出来ませんが、

会話の流れを把握したり、会話から学習できるようなBotはどうすれば作成できるか?

については今後調査、サンプリング検証してみたいと思います。

 

個人的には、それよりも今回作成したLINE BOTで生活習慣を管理していくことを最優先に取り組みたいです。。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください