【GAS】LINEbotでテンプレートメッセージを活用する

##概要
先日作成したLINEbotからのレスポンスを、テンプレートメッセージにしてみた。

Qiita記事：[【GAS】位置情報を送ると近場の深夜営業レストランを教えてくれるLINEbot](https://qiita.com/YasumiYasumi/items/2fc531d6dfd62f3a260a)

今回LINEbotを刷新するにあたって、テンプレートメッセージの使い方を覚えたのが嬉しくて記事を作成。

##テンプレートメッセージによる見た目の変化
もともとはテキストメッセージで食べログのURLを5個送信しただけなので、表示はこんな感じ。
![Screenshot_20201211-031818-01-01-01-01.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/6995ca9e-a5bb-7238-0ab7-bff1c8796862.jpeg)
LINEはURLから自動でリンクを作成してくれるので、レストランの雰囲気もつかめて良い感じ。

このままでも良かったんだけど、テンプレートメッセージを試してみたら更に良い感じにヾ(*´∀`*)ﾉ URLの他、店舗名・サムネイル画像・距離・ジャンル・評価・価格帯をスクレイピングしてテンプレートを作成している。
![Screenshot_20201214-095850.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/39958cc3-fc1b-b90e-0e8f-5d1fc4c993d4.jpeg)
こちらはカラムを横スクロールで複数表示できる**カルーセルテンプレート**を使用したところ。テキストだった時は画面の収まりを考えて検索結果の表示を5件までにしていたけど、カルーセルテンプレートはカラムを10個まで表示できるので、レストランの選択肢も倍増^^)b

位置情報以外をLINEbotに送った場合のメッセージにもテンプレートを活用。
![Screenshot_20201214-101853.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/9a6ff71d-2b93-e699-00d6-07cba2d0c245.png)
こちらは**ボタンテンプレート**を使用。「位置情報を送る」の部分をタップすると位置情報画面が開き、位置情報を送信できる。

##コード

<details><summary>LINEに投稿があった際に動く部分</summary><div>

```javascript

function doPost(event) {

  //ApiTokenと返信用のURLを設定
  const TOKEN = 'xxxxxxxxxxxxxxxxxxxxxxxxx';
  const reply = "https://api.line.me/v2/bot/message/reply";

  //投稿を取得
  var json = JSON.parse(event.postData.contents);
  var replyToken = json.events[0].replyToken;
  var userMessage = json.events[0].message.text;
  var type = json.events[0].message.type;
  
  //投稿タイプを判定
  if(type==='location'){
    
    //緯度経度を取得
    var lat = json.events[0].message.latitude;
    var lng = json.events[0].message.longitude;
    
    //緯度経度からカルーセルテンプレートのメッセージを作成
    var payload = carousel(lat, lng, replyToken);
    
    if(payload==='該当なし'){
    
      //該当なしなら以下のメッセージを送信  
      var botMessage = '指定の条件に該当するお店は見つかりませんでした。';
      var payload = JSON.stringify({
          "replyToken": replyToken,
          "messages": [{
            "type": "text",
            "text": botMessage
          }]
      });
      UrlFetchApp.fetch(reply, {
            "headers": {
            "Content-Type": "application/json; charset=UTF-8",
            "Authorization": "Bearer " + TOKEN,
          },
          "method": "post",
          "payload": payload
      });
      
    }else{
  
      //作成されたテンプレートメッセージ送信
      UrlFetchApp.fetch(reply, {
            "headers": {
            "Content-Type": "application/json; charset=UTF-8",
            "Authorization": "Bearer " + TOKEN,
          },
          "method": "post",
          "payload": payload
      });
    }
    
  }else{
  
    //投稿タイプが位置情報でなければ位置情報を求めるボタンテンプレートを送信
    var botMessage = '位置情報を送ってね。\n近くの始発までやってるレストランを調べるよ^^)b';
    var payload = JSON.stringify({
        "replyToken": replyToken,
        "messages": [{
          "type": "template",
          "altText": "This is a buttons template",
          "template": {
              "type": "buttons",
              "text": botMessage,
              "actions": [
                  {
                    "type": "uri",
                    "label": "位置情報を送る",
                    "uri": "https://line.me/R/nv/location/"
                  }
              ]
          }
        }]
    });
    UrlFetchApp.fetch(reply, {
          "headers": {
          "Content-Type": "application/json; charset=UTF-8",
          "Authorization": "Bearer " + TOKEN,
        },
        "method": "post",
        "payload": payload
    });
  }
}
```
</div></details>

<details><summary>カルーセルテンプレートメッセージ作成部分</summary><div>

```javascript

function carousel(lat, lng, replyToken) {

  //食べログ「500m圏内＆始発まで営業（画像あり）」の絞り込み検索URLを生成 ソースを取得 ※１
  var searchURL = 'http://m.tabelog.com/mobile_gps/RC////A/0/6/l/rstlstgps/?narrow=1&RdoCosTp=2&LstCos=0&LstCosT=0&lat=' + lat + '&lon=' + lng + '&SrtT=trend&LstSitu=&sw=&img_on=1';
  var data = UrlFetchApp.fetch(searchURL).getContentText();
  
  //ライブラリ「TextPicker」にソースを渡す ※２
  TextPicker.open(data);
  
  //該当するお店がない場合
  if(TextPicker.pickUp('指定の条件に','見つかりませんでした')==='該当するお店は'){
    return '該当なし';
  }
  
  //ヒット件数取得
  var num = TextPicker.pickUp('</span>/全','件');
  num = Number(num);
  if(num>10){num=10;} //※３
  
  //カルーセルテンプレートのカラムを格納する配列を宣言
  var columns = [];
  
  TextPicker.skipTo('</span>/全');
  
  //件数分の繰り返し
  for(var i=0; i<num; i++){
  
    var URL = TextPicker.pickUp('http://m.tabelog.com/','">');
    TextPicker.skipTo(URL);
    
    //クーポンページと中間リンクをコンティニュー
    if(URL.slice(-5)==='="red'){i--; continue;}
    if(URL==='billing_mobile/register_mymenu?msgid=6'){i--; continue;}
    
    //必要情報の取得
    var title = TextPicker.pickUp('">','</a>');                  //店舗名
    var thumbnailHead = TextPicker.pickUp('<img src="','50x50'); //サムネイルURL前半
    var thumbnailFoot = TextPicker.pickUp('50x50','" ');         //サムネイルURL後半
    TextPicker.skipTo('style="color:#666666;">(');
    var cuisine = TextPicker.pickUp('/',')</span>');             //ジャンル
    TextPicker.skipTo(';"');
    var blStar = TextPicker.pickUp('>','</span>');               //★★★ ※４
    var whStar = TextPicker.pickUp(';">','</span>');             //☆☆☆
    var rating = TextPicker.pickUp('color:#ff0000;">','</span>');//点数
    var price = TextPicker.pickUp('夜:','<br />');                //夜の価格帯
    var distance = TextPicker.pickUp('現在地から','</span>');      //位置情報からの距離

    //トップページ 基本情報 地図ページのURLを作成
    var shopURL = 'http://m.tabelog.com/' + URL;
    var dataURL = shopURL + '#rstdtl_data_info';
    var mapURL = shopURL + 'dtlmap/';
    
    //サムネイルのURLを作成
    var thumbnailURL = thumbnailHead + '150x150' + thumbnailFoot; //※５
    
    //タイトル カラム用のテキストを作成
    title = title.substr(0, 33) + '(' + distance + ')'; //※６
    var price = '\n夜価格帯:' + price;
    var replaceWord = /\\/g;
    price = price.replace(replaceWord, "￥"); //※７
    var text = cuisine.substr(0, 28) + '\n' + blStar + whStar + rating + price; //※８
    
    //カラム作成
    var column = {
                    "thumbnailImageUrl": thumbnailURL,
                    "imageBackgroundColor": "#FFFFFF",
                    "title": title,
                    "text": text,
                    "defaultAction": {
                        "type": "uri",
                        "label": "View detail",
                        "uri": shopURL
                    },
                    "actions": [
                        {
                            "type": "uri",
                            "label": "基本情報を見る",
                            "uri": dataURL
                        },
                        {
                            "type": "uri",
                            "label": "地図を見る",
                            "uri": mapURL
                        }
                    ]
                  }
    
    //カラムを配列に格納
    columns[i] = column;
  }
  
  //作成したカラムを元にメッセージを作成
  var payload = JSON.stringify({
      "replyToken": replyToken,
      "messages": [{
        "type": "template",
        "altText": "this is a carousel template",
        "template": {
            "type": "carousel",
            "columns": columns,
            "imageAspectRatio": "rectangle",
            "imageSize": "cover"
        }
      }]
  });
  
  return payload;
}

//※１：{/RC////A/0/6/l/}部分 Aなら500ｍ範囲 Bなら1km範囲になる
//※２：プロジェクトID「Ms1_ywyxDUyXZlf1HE1E2_ydpxDUCDjPE」
//※３：カルーセルテンプレートのカラムは最大10個まで 検索結果が10より多い場合は10に丸める
//※４：☆はレートによって色が変わるためカラーコード部分は目印にならない
//※５：ソースページの画像は 50×50 なので 150x150 に書き換える
//※６：titleは最大40文字 (0000m) の7文字分を残して店名を33文字までに制限
//※７：半角￥が \ になるので全角￥に置き換え
//※８：textは最大60文字 他の部分を残してジャンル名を28文字までに制限
```
</div></details>

コードの説明はコメントアウトを参照。コードの最後で※印の補足を記載。
LINEbotとしての導入方法などは割愛。

今回はテンプレートメッセージについての記事なので端折っているけど、コードやスクレイピングに使用したライブラリ「TetPicker」の説明などは、前の記事にあり<(_ _)>

##普通のテキストを送信する場合
テンプレートを使用せずに普通のテキストを送信する場合はこんな感じ。

```javascript

//Webhookでメッセージを受信
function doPost(event) {

  //１．ApiTokenとメッセージ送信用のHTTPリクエストを設定
  const TOKEN = 'xxxxxxxxxxxxxxxxxxxxxxxxx';
  const reply = "https://api.line.me/v2/bot/message/reply";

  //２．受け取ったメッセージから返信用のTokenを取得
  var json = JSON.parse(event.postData.contents);
  var replyToken = json.events[0].replyToken;

  //３．メッセージタイプと内容を記述
  var payload = JSON.stringify({
      "replyToken": replyToken,
      "messages": [{
        "type": "text",
        "text": "送信したいメッセージ"
      }]
  });

  //４．送信
  UrlFetchApp.fetch(reply, {
      "headers": {
        "Content-Type": "application/json; charset=UTF-8",
        "Authorization": "Bearer " + TOKEN,
      },
      "method": "post",
      "payload": payload
  });
}
```

これが基本形。１，２，４はそのままで、３の`"messages": [{   }]`の括弧内を変えることで、テンプレートなど別タイプのメッセージを送信することができる。

##カルーセルテンプレート
前述のテキストメッセージと基本形は同じで、`"messages": [{   }]`の`{}`に記述していく。記述例は次の通り。

```json

{
  "type": "template",
  "altText": "通知などに表示される文字列",
  "template": {
      "type": "carousel",
      "columns": [
          {
            "thumbnailImageUrl": "画像のURL",
            "imageBackgroundColor": "#FFFFFF",
            "title": "タイトルの文字列（最大40文字）",
            "text": "メッセージ部分（最大120文字or60文字）",
            "defaultAction": {
                "type": "uri",
                "label": "View detail",
                "uri": "画像 タイトル テキストをタップした時のURL"
            },
            "actions": [
                {
                    "type": "uri",
                    "label": "アクション部分のテキスト",
                    "uri": "アクション部分をタップした時のURL"
                }
            ]
          }
      ],
      "imageAspectRatio": "rectangle",
      "imageSize": "cover"
  }
}
```

textとactions以外は任意なので省略可能。例えばthumbnailImageUrlを省略すれば画像が表示されないし、defaultActionを省略すればtext部分をタップしても何も起こらない。

`"columns": [   ]`内の`{   }`が１つのカラムを表しているので、`[{},{},{}]`と言う形で連ねればカラムを最大10個まで作れる。

`"action": [   ]`も同様で、`[{},{},{}]`とすることで最大3個まで設定できる。

textの最大文字数が2種類あるのは、画像とタイトルを省略した場合は最大120文字、どちらかを入れると最大60文字となるため。

実際のLINEbotのコードでは、for文で繰り返しスクレイピングしながら`{カラム}`を作り、配列`columns=[]`に格納。JSON部分を`"columns": columns`としている。

##ボタンテンプレート
カルーセルテンプレート同様、`"messages": [{   }]`の`{}`が変わるだけ。

```json
{
  "type": "template",
  "altText": "通知などに表示される文字列",
  "template": {
      "type": "buttons",
      "thumbnailImageUrl": "画像のURL",
      "imageAspectRatio": "rectangle",
      "imageSize": "cover",
      "imageBackgroundColor": "#FFFFFF",
      "title": "タイトルの文字列（最大40文字）",
      "text": "メッセージ部分（最大160文字or60文字）",
      "defaultAction": {
          "type": "uri",
          "label": "View detail",
          "uri": "画像 タイトル テキストをタップした時のURL"
      },
      "actions": [
          {
            "type": "uri",
            "label": "アクション部分のテキスト",
            "uri": "アクション部分をタップした時のURL"
          }
      ]
  }
}
```

記述方法はカルーセルテンプレートとほぼ同じ。actionsも3つまで設定可能。

同じくtextとactions以外は省略できるため、実際のLINEbotでは次のように記述している。

```javascript
var botMessage = '位置情報を送ってね。\n近くの始発までやってるレストランを調べるよ^^)b';
```

```json
"messages": [{
          "type": "template",
          "altText": "This is a buttons template",
          "template": {
              "type": "buttons",
              "text": botMessage,
              "actions": [
                  {
                    "type": "uri",
                    "label": "位置情報を送る",
                    "uri": "https://line.me/R/nv/location/"
                  }
              ]
          }
        }]
```

`"uri": "https://line.me/R/nv/location/"`のURLは、LINE URLスキームと言って、各種スキームのURLへリンクすることでカメラロールを開いたり、テキストメッセージを送ったりできる。この場合は位置情報画面を開くことができる。

[LINEで利用できるURLスキーム](https://developers.line.biz/ja/docs/messaging-api/using-line-url-scheme/#line%E3%81%A6%E3%82%99%E5%88%A9%E7%94%A8%E3%81%A6%E3%82%99%E3%81%8D%E3%82%8Burl%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%A0)

##その他のテンプレートやメッセージタイプ
上記以外のテンプレートや、画像やスタンプなどのメッセージタイプもあり、記述方法がLINEデベロッパーズのページで紹介されている。

[LINEbotのメッセージタイプ](https://developers.line.biz/ja/docs/messaging-api/message-types/)

###おしまい