Yuki's bnb blog

こんにちは!Yukiといいます。本業のかたわら大阪で2016年夏から民泊運営のお手伝いをしています。民泊業務に関する様々なことを自動化・効率化したいと思い日々活動しています。 お気軽にコメント・お問い合わせください :) TOEICスコア 985

【シリーズ第6話】スプレッドシートの語句を返信するLINE BOTをGASで作成する方法(解説編: GAS)

f:id:yukibnb:20200506120233p:plain

こんにちは!
Yuki (@yukibnb) です。

LINE Messaging APIとGoogle Apps Script(GAS)を使用してスプレッドシートに記載された語句を返信するBOTを作成する方法シリーズ記事の第6話です。

今回がシリーズ最終話です!

f:id:yukibnb:20200316214556g:plain

 
前回の第5話ではLINE Messaging APIとGASを取り巻く全体像を解説しました。

www.yukibnb.com

 
今回はシリーズ最終話として、第4話で記述したGASの解説を行います。

www.yukibnb.com

第5話で全体像を理解したことで、今回の解説がより理解しやすくなっていると思います。

では見ていきましょう!

 

GASへメッセージ情報を送信する

POST送信について

f:id:yukibnb:20200505174650p:plain

上記画像の(2)、LINE Messaging APIからGASにメッセージ情報を送信するところから見てみましょう。

第4話WebhookとはGASの住所のようなもの、そしてLINE Messaging APIからGASに送られるメッセージ情報はJSON文字列という形式だと紹介しました。

僕たちが日常生活で誰かに品物を送る場合、「届け先住所」と「品物」だけでなく、「どのような方法で」送付するかも決める必要があります。

例えばヤマト、佐川、郵便などです。

同じようにコンピューターの世界でも、目的地にデータを送信する方法は様々です。

そしてLINE Messaging APIからGASへはPOST送信という方法でデータ(JSON文字列)が送られます。※単純にPOSTと呼ばれたり、POSTメソッド、POSTリクエストとも呼ばれたりします。

WebhookとPOST送信、JSON文字列の関係は以下のようなイメージです。

f:id:yukibnb:20200505200632p:plain
 
現実世界では「田中さん宅へ商品をヤマト宅急便で送る」と言いますが、上記のような場合「WebhookにJSON文字列をPOSTする」と言ったりします。

図を見ると理解しやすいですね。

 

GASの徹底解説!

f:id:yukibnb:20200504012337p:plain

ではここからは第4話で記述したGASの解説を行います。

大きく3点に分けて見ていきましょう。

  • a. メッセージ情報を解析
  • b. 返信語句・宛先を決定
  • c. メッセージ形式を変換

 

memo

記述したGASの全文は第4話をご覧ください。

 

a. メッセージ情報を解析

doPost(e)でPOST送信されたデータを受信する

f:id:yukibnb:20200505194907p:plain

POST送信されたデータをGASで受信するにはdoPost(e)というイベントハンドラを使用します。

イベントハンドラとは何かイベントが発生した時に自動的に処理を実行するものです。doPost(e)とはGASがPOST送信を受信した時に自動的に処理を実行します。

そしてdoPost(e)eにはPOST送信されたデータが入ります。今回ではJSON文字列がeに該当します。

doPost(e)を使うことでLINE Messaging APIからJSON文字列、つまりメッセージ情報を受信することができます。

そして受信したメッセージ情報はdoPost(e){ ..... }{ ..... }で自由に使うことができます。

現実世界に置き換えると以下のようなイメージです。

f:id:yukibnb:20200505192616p:plain

炒めものロボに材料を渡すと自動的に材料を切り、炒めてお皿に盛り付けまでしてくれます。

毎回材料が変わっても炒めものロボの作業自体は変わりません。

同じように毎回ユーザーが異なるLINEメッセージを送っても、doPost(e)は受け取ったメッセージであるeに対して同じ処理を実行してくれます。

 

JSON.parseでJSON文字列を解析する

f:id:yukibnb:20200505194235p:plain

第4話JSON文字列をGASが取り扱える形式に解析することを「JSONをパースする」と言うと紹介しました。

JSONをパースするのは簡単です。JSON.parseメソッドを使うだけでOKです。

細かく見ていきましょう。

//LINE Messaging APIからPOST送信を受けたときに起動する
// e はJSON文字列
function doPost(e){
  if (typeof e === "undefined"){
    //動作を終了する
    return;
  } else {
    //JSON文字列をパース(解析)し、変数jsonに格納する
    var json = JSON.parse(e.postData.contents);

    //変数jsonを関数replyFromSheetに渡し、replyFromSheetを実行する
    replyFromSheet(json)
  }
}

 
LINE Messaging APIからGASのWebhookに対してJSON文字列がPOST送信されます。するとdoPost(e)が起動します。

//LINE Messaging APIからPOST送信を受けたときに起動する
// e はJSON文字列
function doPost(e){

 
以下の箇所はJSON文字列が有効でない場合、その時点で処理を終了させるために記述しています。

  if (typeof e === "undefined"){
    //動作を終了する
    return;

 
JSON文字列が有効な場合、JSON.parseメソッドでJSON文字列をパースします。

この時JSON.parse(e)ではなくJSON.parse(e.postData.contents)とする必要があります。

JSON.parse(e.postData.contents)を実行することでJSON文字列をパース完了し、GASで扱えるデータになりました。

そしてそのデータを変数jsonに格納します。

    //JSON文字列をパース(解析)し、変数jsonに格納する
    var json = JSON.parse(e.postData.contents);

 
次に関数replyFromSheetを実行します。

replyFromSheet(json)と書くことでreplyFromSheetに変数jsonを渡すことができます。doPost(e)のeにJSON文字列を渡すのと同じイメージです。

    //変数jsonを関数replyFromSheetに渡し、replyFromSheetを実行する
    replyFromSheet(json)

 

b. 返信語句・宛先を決定

関数replyFromSheetを実行する

f:id:yukibnb:20200505231022p:plain

今回関数doPostと関数replyFromSheetを分割して記述しました。

そして関数doPostの中で関数replyFromSheetを呼び出しています。

関数replyFromSheetは変数jsonを受け取り、変数jsonの内容に対して処理を行い返信語句と宛先を決定し、LINE Messaging APIにデータを送信します。

 

  • 関数doPost: JSON文字列をパースし、関数replyFromSheetを実行(起動)する
  • 関数replyFromSheet: パースされたJSON文字列を処理し、返信語句・宛先を決定し、LINE Messaging APIにデータを送信する

 
分割して記述することで後々メンテナンスが必要になった場合、問題箇所の特定や修正の手間が削減できるためです。

現実世界に置き換えて考えてみると以下のようなイメージです。

f:id:yukibnb:20200505234009p:plain

炒めものを調理しテーブルまで運んでくれる単一のロボットを開発するのはすごく大変で複雑です。

また完成した後に機能を変更することや、故障個所の特定や修理も大変です。

上記画像のように炒めものロボと配達ロボを開発し、炒めものロボに「お皿に盛り付けが完了したら配達ロボを呼び出す」というプログラムを記述しておけば、盛り付けが完了すると配達ロボが自動的にキッチンからテーブルまでお皿を運んでくれます。

故障が発生した場合、故障個所の特定や修理もしやすくなります。

また配達ロボは料理以外を運ばせることもできるので、新聞をテーブルまで運ばせたり応用がききます。

 
このような理由で関数doPostと関数replyFromSheetを分割して記述しています。

replyFromSheet(data)のdataには変数jsonが渡されていますので、関数replyFromSheet内で変数jsonを処理したい場合、dataを処理することになります。

具体的に関数replyFromSheetの中を見てみましょう。

 

受信語句・返信語句一覧を取得する

f:id:yukibnb:20200505233219p:plain

まずスプレッドシートの受信語句・返信語句一覧を取得します。

変数wordListには一覧データが二次元配列として格納されています。

  //シートの最終行を取得する
  var lastRow = sh.getLastRow();
   
  //シートの全受信語句と返信語句を二次元配列で取得する
  var wordList = sh.getRange(1,1,lastRow,2).getValues();

 

data(変数json)から必要なデータを取得する

f:id:yukibnb:20200506092800p:plain

dataの中から必要な情報を2点取得します。

  • 返信用の宛先 (reply token)
  • ユーザーが送信した語句

 
第5話でも紹介しましたが、「返信用の宛先」とはLINE Messaging APIの専門用語でreply token(応答トークン)と言います。

ユーザーに返信するにはreply tokenが必ず必要となります。

data.events[0]を記述すると、data(変数json)内のメッセージ情報を取得できます。取得した情報は以下のような形になっています。
f:id:yukibnb:20200506094917p:plain

 
この画像内に黄色でハイライトした箇所がreply tokenとユーザーが送信した語句です。

それぞれ以下のように記述すると取り出すことができます。
f:id:yukibnb:20200506130436p:plain

 
記述したGASで該当する箇所は以下です。

変数reply_tokenにreply tokenを、変数textにはユーザーが送信した語句を格納しました。

  //受信したメッセージ情報を変数に格納する
  var reply_token = data.events[0].replyToken; //reply token
  var text = data.events[0].message.text; //ユーザーが送信した語句

 

返信語句を取得する

f:id:yukibnb:20200503223725j:plain

f:id:yukibnb:20200506101352p:plain

ユーザーが送信した語句(変数text)がわかったので、それをもとに返信語句を取得します。

スプレッドシートのA列(受信語句)にあたる要素を二次元配列wordListに対してfor文でひとつひとつチェックし、A列に入力されている語句が変数textと等しい場合、B列の語句(返信語句)を空配列replyTextListに格納します。

memo

二次元配列のループ処理順序の解説は以下記事を参照ください
【初心者向けGAS】二次元配列のループの処理順序を徹底解説

またユーザーが送信した語句がA列と合致しない場合、この時点で処理を終了します。(ユーザーには何も返信されません)

  //返信語句を格納するための空配列を宣言する
  var replyTextList = [];
  
  //LINEで受信した語句がシートの受信語句と同じ場合、返信語句をreplyTextにpushする
  for(var i = 1; i < wordList.length; i++) {
    if(wordList[i][0] == text) {
     replyTextList.push(wordList[i][1]);
    }
  }
  

  //LINEで受信した語句がシートの受信語句と一致しない場合、関数を終了する
  if(replyTextList.length < 1) {
    return;

 

吹き出しの数を最大5つに調整する

第3話でも紹介しましたが、LINE Messaging APIの仕様上、一度に返信できる吹き出し数は5つです。(2020/5/6現在)

そのため配列replyTextListの要素数が5より多い場合、あふれた分を切り捨てて吹き出しが5つだけユーザーに返信されるようにします。

そのための処理を以下で行います。

  //replyTextListのLengthが5より大きい場合、messageLengthを5にする
  //※※一度に最大5つの吹き出ししか返信できないためです※※
  } else if(replyTextList.length > 5) {
    var messageLength = 5;
  } else {
    var messageLength = replyTextList.length;
  }

 
これで無事ユーザーへの返信語句(配列replyTextList)と宛先(reply token)を決定することができました。

 

c. メッセージ形式を変換

(a)でJSON文字列をパースすることで、受信したメッセージ情報をGASが取り扱える形式に変換しました。

そして変換した情報をもとに(b)で返信語句(配列replyTextList)と宛先(reply token)を決定できました。

ここからは最後の仕上げとして、LINE Messaging APIに送り返すためにメッセージ形式を変換して整えます。

ほとんど定型文のようなものなので、深く考えず「そういうものだ」ぐらいの認識で大丈夫です。

 

配列replyTextListを変換する

f:id:yukibnb:20200506105049p:plain

配列replyTextListはそのままではLINE Messaging APIに送信できないため、上記画像のようにLINE Messaging APIが取り扱える形式に変換する必要があります。

以下箇所で、変換した上で空配列messageArrayに格納しました。

もし配列replyTextListに5つより多くの要素が入っていたとしても、空配列messageArrayには最大5つしか要素は追加されないようにしています。

  //"messages"に渡す配列を格納するための空配列を宣言する
  //[{"type": "text", "text": "返信語句その1"},{"type": "text", "text": "返信語句その2"}....]
  var messageArray = [];
  
  //replyTextListに格納されている返信語句を最大5つ、messageArrayにpushする
  for(var j = 0; j < messageLength; j++) {
    messageArray.push({"type": "text", "text": replyTextList[j]});
  }

 

チャネルアクセストークンを指定する

f:id:yukibnb:20200506105707p:plain

第4話でGASからLINE Messaging APIに情報を送る際に、チャネルアクセストークンを用いることで正しいLINE Messaging API(どのLINE BOTか)を指定することができると紹介しました。

そのチャネルアクセストークンを以下箇所で指定しています。

なおチャネルアクセストークンはGASの最初に変数LINE_ACCESS_TOKENに格納していますので、ここでは変数LINE_ACCESS_TOKENを呼び出しています。

変数headersに送付先住所であるチャネルアクセストークンを格納することができました。

  var headers = {
    "Content-Type": "application/json; charset=UTF-8",
    "Authorization": "Bearer " + LINE_ACCESS_TOKEN,
  };

 

返信語句とreply tokenをさらに整える

返信語句は配列messageArrayに整った状態で格納されています。

そしてreply tokenは変数reply_tokenに格納されています。

これらは例えるなら段ボールに詰める前の品物です。配列messageArrayと変数reply_tokenはそれぞれ単体では整っていますが、LINE Messaging APIに送信するには段ボール詰めが必要です。

その作業を以下で行っています。

  var postData = {
    "replyToken": reply_token,
    "messages": messageArray
  };

 

optionを指定する

f:id:yukibnb:20200506111503p:plain

ここでは最後の仕上げを行います。

先程段ボール詰めと例えましたが、段ボール詰めの後は送り状を用意しないといけません。

ヤマト、佐川、郵便と言った配送手段を決めたり、送り状に宛先や内容品を記載する必要があります。

その作業を以下で行っています。

また第4話で紹介した、JSON文字列に変換する(戻す)作業をここで実施しています。

  var options = {
    "method" : "post",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
  };

 
これで無事LINE Messaging APIにデータを送信する準備が整いました。

変数optionsには送付方法(method)、送付先住所(headers)、内容品(payload)が含まれています

 

LINE Messaging APIにデータを送信する

準備がすべて整いましたのでデータをLINE Messaging APIに送信しましょう。

以下1文で完了します。

  //LINE Messaging APIにデータを送信する
  UrlFetchApp.fetch(replyUrl, options);
}

 
変数replyUrlはGASの中ほどでhttps://api.line.me/v2/bot/message/replyを格納しましたので、今回それを呼び出しています。

チャネルアクセストークンが住所だとすると、変数replyUrlは国のようなものです。

LINE Messaging APIに返信するには大きな国としてhttps://api.line.me/v2/bot/message/replyを指定した上で、細かい送付先住所などを変数optionsで指定するイメージです。

送付先住所は毎回変わっても、国は常にhttps://api.line.me/v2/bot/message/replyから変更ありません。

これで無事LINE Messaging APIにデータを送信できました。

LINE Messaging APIはGASからデータを受信すると、その中にあるreply token宛てに返信語句を自動的に送信します。

f:id:yukibnb:20200503234932j:plain

 

まとめ

今回は第4話で記述したGASの解説を行いました。

第4話のGASそのものにもコメントを多く付与しましたが、つまづきやすいポイントをかみ砕いて解説しました。

なじみのないカタカナ語や英語がたくさん出てきますが、現実世界に置き換えてイメージしてみると理解しやすくなります。

 
これでLINE Messaging APIとGoogle Apps Script(GAS)を使用してスプレッドシートに記載された語句を返信するBOTを作成する方法シリーズ記事は完結です。

本記事だけでもかなり長い解説となりましたが、6記事に渡り最後までお読み頂きありがとうございました!引き続き当ブログの最新記事もご期待ください。
 

 
【シリーズ目次】
《基本編》
【シリーズ第1話】LINE Messaging APIのプロバイダーとチャネルを作成する方法
【シリーズ第2話】LINE Messaging APIのチャネルアクセストークンを発行しよう
【シリーズ第3話】LINE BOTに返信させる語句をスプレッドシートに入力する
【シリーズ第4話】コピペでOK!スプレッドシートの語句を返信するLINE BOTをGASで作成する方法(実践編)
【シリーズ第5話】スプレッドシートの語句を返信するLINE BOTをGASで作成する方法(解説編: 全体像)
・※本記事※【シリーズ第6話】スプレッドシートの語句を返信するLINE BOTをGASで作成する方法(解説編: GAS)
 
《応用編》
【応用編第1話】スプレッドシートを使ってLINE BOTにスタンプや画像を返信させる方法 (シート準備編)
【応用編第2話】コピペでOK!スプレッドシートを使ってLINE BOTにスタンプや画像を返信させる方法 (実践編)
【応用編第3話】コピペでOK!スプレッドシートを使ってLINE BOTにスタンプや画像を返信させる方法 (解説編: GAS)