LINEのFlex Messageを使った『偉人の写真入り名言』Botを作った。

## 1. はじめに
[前回](https://qiita.com/Kobe-dad/items/970f5628bcddf1e7b6fb)、契約書＆Line Botを使って、こどもが『あつ森』をしすぎないように使用ルールと報告スキームを決めたが、Botとの会話が『ゲーム開始』、『ゲーム終了』など機械的な進捗報告だけになっていたので、少し楽しい要素を入れてみた。
ITは子供に制限を課すためのものではなく、ワクワクするものであってほしい。

**実際の機械的なBotとのやり取り**
左がBot。右が10歳。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1426173/be9994dd-edec-8cee-753d-015709cd4b31.jpeg" width="250">

## 2. やったこと
単なる報告相手からbotを脱却させるために、

* botによる、『偉人の写真入り名言』の定期配信

を実装しました。※ 以下、偉人Botと呼ぶ
少しでもBotに親しみを感じてほしいなと。

## 3. 技術的要素
基本的に他の方が紹介しているコードを（基本コピペで）自分の環境で試しているだけであまり技術的に新しいことはありませんが、

* Flexメッセージの作成
* Google SpreadSheetのデータベースとしての利用

などです。
また、GAS（Google App Script）でコードは記述します。

## 4. 手順
### ① Flexメッセージ作成
名言を送付するときに単なるテキストだと味気なくて見る気が無くなる、ということで、今回はFlexメッセージのBubbleを使用することにしました。出力イメージはこれです。
**偉人の背景画像の上に、偉人の名言をテキストで重畳** というシンプルなものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1426173/393bd2ab-81de-1840-3264-1e1d81ccd1fd.jpeg" width="250">

#### Simulatorを用いたJSONの作成
Flex Messageでは画像とテキストを自由にレイアウトしてちょっとCoolなコンテンツを作成可能です。
作成には[こちらのサイト](https://www.yukibnb.com/entry/linemessagingapi_flex_message_simulator)にある通り、[Flex Simulator](https://developers.line.biz/flex-simulator/)を使ってデザインを決めた上で、JSON形式でデータを出力し、ソースコードに張り付けます。

本稿では、写真の上に文字が覆いかぶさっているものが良かったので、以下の『Hotel』のテンプレをベースにごにょごにょし、JSONとして出力しました。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1426173/24e72d49-bf51-1485-5c88-bfa7cde0e781.png)


**Simulatorの使い方は詳述しませんが、一点だけ気を付けるのは、Simulatorが出力するJSONデータの張り付け方。以下のコメント『以下、～』と『以上、～』の間に張り付けてください。**
※ ソースコードの全体像は最後に記載します。

```javascript:偉人Bot.gs抜粋
       /* message start*/
        var message = {
            "messages": [                         
                        {
                          "type": "flex",
                          "altText": "名言",                          
                          "contents":
                          //以下、Flex Message Simulatorより
                          {
                              "type": "bubble",
                              "body": {
                                "type": "box",
                                "layout": "vertical",
                               === 略 ===
                          }
                          //以上、Flex Message Simulatorより
                        }
                        ]
        };
        /* message end*/

```

### ②名言データの作成
主に[こちら](https://iyashitour.com/meigen/greatman/stevejobs)のサイトから引用させていただきました。ページ下部に名言一覧をテーブル形式で記載してくれているので、SpreadSheetにコピペで張るだけでOKです。

**SpreadSheetの構成は以下の通り。A列は発言回数をインクリメントしていくための領域です。**
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1426173/6396b781-c7b5-11c1-de34-1c90a750de25.png)
#### 背景画像へのリンクの用意
E-H列には背景画像へのリンクを記載します。各自の環境でご用意いただければOKですが、私はGoogle Driveに保存しファイルへのリンクを使用しました。（画像自体の著作権にはご注意ください）
Google Driveの通常の画像リンクはそのままでは使用できないため、以下のとおり置き換えるようにしてください。
`×  通常の画像リンク：https://drive.google.com/file/d/[画像のID]/view?usp=sharing`
`〇 置き換え後のリンク：https://drive.google.com/uc?export=view&id=[画像のID]`

### ③偉人Botのコード
整理しきれていないですが、偉人BotのGASのコードを記載します。
機能は以下の2つです。
※ 私の手元では、お仕事時間管理Bot上に本機能を実装しています。

* "名言"と話しかけるとランダムで名言を返してくれる。
* 定期的に名言をBroadcastでPush配信。

以下対応をいただければGASのエディタにコピペで動くはずです。
（Messaging APIでHookする設定等々はできている前提です。）

* 最初の4行にあるLINEトークンやGoogle SpreadSheet IDをご自身の環境に置き換える。
* 連携するSpreadSheetに"meigen","debug"という名前のシートを作成し、"meigen"に名言データを入力する。

※ 名言の数は112で直打ちしていたり、いろいろと雑なコードですが。。

```javascript:偉人Bot.gs
var channel_access_token = "[LINEのチャンネルトークン]";
var DEBUG = false;//true or false. Trueの時は、Push送信時にBroadcastではなく、to宛にのみメッセージ送信
var SPREADSHEETID = "[連携するSpreadSheetのID]";
var TO = "[DEBUG用のLINE ID]";//DEBUGがtrueの時はこのLine IDに対してのみメッセージ送信

// ボットにメッセージ送信/フォロー/アンフォローした時の処理
function doPost(e) {
  var events = JSON.parse(e.postData.contents).events;
  events.forEach(function(event) {
    if(event.type == "message") {
      reply(event);
    } else if(event.type == "follow") {
      follow(event);
    } else if(event.type == "unfollow") {
      unFollow(event);
    }
 });
}
// 入力メッセージを解析
function reply(e) {
  var postMsg = e.message.text;
  var replytext = "";
  var displayName  = getDisplayName(e);//使用しない
  var date = new Date();//使用しない
  var today = Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy/MM/dd'); //使用しない
  
  if(postMsg.match("名言"))
  {
     var replyData = makeQuoteData("reply", e.replyToken);
     try{
        var res = UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", replyData);
        /* resをSpreadsheetの"debug"シートにLog 出力する */
        var spreadsheet = SpreadsheetApp.openById(SPREADSHEETID);
        var sheet = spreadsheet.getSheetByName('debug');
        sheet.getRange(1,1).setValue(res);
    } catch(err) {
      // 例外エラー処理 ResをSpreadsheetの"debug"シートにLog 出力する
       var spreadsheet = SpreadsheetApp.openById(SPREADSHEETID);
       var sheet = spreadsheet.getSheetByName('debug');
       sheet.getRange(1,2).setValue(err);
    }
    return;
  }else{
    replyText ="偉人の名言を聞きたかったら『名言』と話しかけてね"
    var message = {
      "replyToken" : e.replyToken,
      "messages" : [
        {
          "type" : "text",
          "text" : ((e.message.type=="text") ? replyText : "テキスト以外は返せないの。ごめんね。"+displayName)
        }
      ]
    };
    var replyData = {
      "method" : "post",
      "headers" : {
        "Content-Type" : "application/json",
        "Authorization" : "Bearer " + channel_access_token
      },
      "payload" : JSON.stringify(message)
    };
  
    UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", replyData);
    return;
  }
  return;//ここには来ないはず
}

function getDisplayName(e)
{
  var userId = e.source.userId;
  var options = {"headers" : {"Authorization" : "Bearer " + channel_access_token}};
  var json = UrlFetchApp.fetch("https://api.line.me/v2/bot/profile/" + userId , options);
  return JSON.parse(json).displayName;
}
/* フォローされた時の処理 */
function follow(e) {
  
}

/* アンフォローされた時の処理 */
function unFollow(e){
  
}

///// 定期実行する関数。実行する曜日も判定 /////
function dispatch_event()
{
  var today = new Date(); 
  var dayIndex = today.getDay()
  var day = ["日", "月", "火", "水", "木", "金", "土"][dayIndex];
  if(day == "土"||day == "日")
  {
    pushQuote("push");
  }else
  {
    pushQuote("push");
  }
}
////////////////////
function pushQuote(msg)
{
  var pushData;
  pushData = makeQuoteData(msg,null);
  try {
        if(DEBUG == true)
        {
           UrlFetchApp.fetch("https://api.line.me/v2/bot/message/push", pushData);
        }else{
           UrlFetchApp.fetch("https://api.line.me/v2/bot/message/broadcast", pushData);
        }
  } catch (e) {
        Logger.log('Error:')
        Logger.log(e)
  }
  
}
function makeQuoteData(msg,repToken) {
    var spreadsheet = SpreadsheetApp.openById(SPREADSHEETID);
    var sheet = spreadsheet.getSheetByName('meigen');
    var no = Math.floor(Math.random()*112)+1;//どの行の名言を選択するか。112=Number of Quotes
    var meigen = sheet.getRange(no,3).getValue();
    var person = " - "+sheet.getRange(no,4).getValue();
    var imgno = Math.floor(Math.random()*4);//どの列の画像を使用するか4=4種類
    var imgurl = sheet.getRange(no,5+imgno).getValue();
    // 選択数のカウント。インクリメント
    sheet.getRange(no,1).setValue(sheet.getRange(no,1).getValue()+1);
    
    var textsize = "md";
    if(meigen.length>171)///文字数が多い場合ははみ出ないように表示文字を小さく
      textsize = "sm";
       /* message start*/
        var message = {
            "messages": [                         
                        {
                          "type": "flex",
                          "altText": "名言",                          
                          "contents":
                          //以下、Flex Message Simulatorより
                          {
                              "type": "bubble",
                              "body": {
                                "type": "box",
                                "layout": "vertical",
                                "contents": [
                                  {
                                    "type": "image",
                                    "url": imgurl,
                                    "size": "full",
                                    "aspectMode": "cover",
                                    "aspectRatio": "1:1",
                                    "gravity": "center"
                                  },
                                  {
                                    "type": "box",
                                    "layout": "vertical",
                                    "contents": [],
                                    "position": "absolute",
                                    "background": {
                                      "type": "linearGradient",
                                      "angle": "0deg",
                                      "endColor": "#00000000",
                                      "startColor": "#00000000"
                                    },
                                    "width": "100%",
                                    "height": "100%",
                                    "offsetBottom": "0px",
                                    "offsetStart": "0px",
                                    "offsetEnd": "0px",
                                    "backgroundColor": "#ffffff88"
                                  },
                                  {
                                    "type": "box",
                                    "layout": "horizontal",
                                    "contents": [
                                      {
                                        "type": "box",
                                        "layout": "vertical",
                                        "contents": [
                                          {
                                            "type": "box",
                                            "layout": "horizontal",
                                            "contents": [
                                              {
                                                "type": "text",
                                                "text": meigen,
                                                "size": textsize,
                                                "color": "#111111",
                                                "wrap": true
                                              }
                                            ]
                                          },
                                          {
                                            "type": "box",
                                            "layout": "horizontal",
                                            "contents": [
                                              {
                                                "type": "box",
                                                "layout": "baseline",
                                                "contents": [
                                                  {
                                                    "type": "text",
                                                    "text": person,
                                                    "color": "#222222",
                                                    "size": "md",
                                                    "flex": 0,
                                                    "align": "end"
                                                  }
                                                ],
                                                "flex": 0,
                                                "spacing": "lg"
                                              }
                                            ]
                                          }
                                        ],
                                        "spacing": "xs"
                                      }
                                    ],
                                    "position": "absolute",
                                    "offsetBottom": "0px",
                                    "offsetStart": "0px",
                                    "offsetEnd": "0px",
                                    "paddingAll": "20px"
                                  }
                                ],
                                "paddingAll": "0px"
                              }
                            }
                            ////以上、Flex Message Simulatorより
                        }
                        ]
        };
        /* message end*/
        if(DEBUG == true){
           message.to = TO;
         }
        if(msg == "push")
        {
           //do nothing
        }else if(msg == "reply")
        {
          message.replyToken = repToken;
        }
    var pushData = {
        "method": "post",
        "headers": {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + channel_access_token
        },
        "payload": JSON.stringify(message)
    };
    return pushData;
}
```

### ④定期配信設定
定期的に配信するために、GASエディタ上の"トリガー"からdispatch_event関数を実行するよう設定ください。


## 5. 効果
当初は、私の趣味で、Steve JobsとMichael Jordanの名言を160個設定していたのですが、10歳の女の子にはそこまで刺さらなかったので、リクエストに応えてナイチンゲールやキュリー夫人を追加したら、楽しんで見るようになってくれました。

また、背景画像があることで、『この人こんな顔なんだ！』というところを入り口に、言葉自体にも興味を持ってくれたりしたことが良かったです。
それでいうと、背景を鬼滅の刃とかすみっこぐらしにしておくと、もっと見てくれるのかもしれません。（権利の問題はありますが）