LINE と Google Apps Script で作成したBotの情報をkintoneに保存する

LINEボットをGoogle Apps Scriptで動作させて、LINEからの情報をkintoneに保存してみました。
ボットのフロントにLINE Messaging API を利用して、kintoneはデータベースに使います。

# 前提条件や知識など

* LINEボットの設定が出来る
* Google Apps Scriptでコードが書ける
* kintoneでアプリが作成出来る

# 概要
LINE Messaging API のWebhookの設定に、Google Apps Script(GAS)をWebアプリで公開した際のURLを設定します。LINEのメッセージリクエストをGASのdoPost関数で取得して、リクエストの内容をkintoneにレコード追加して、同時にLINEにおうむ返しで返します。

1. kintoneアプリを作成
2. LINE Messaging APIを設定
3. Google Apps Script でWebアプリを作成
4. Google Apps Script の公開URLをLINE Messaging API のWebhookに設定します

# 1. kintoneアプリを作成

<img width="755" alt="スクリーンショット 2020-06-15 19.22.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214523/6a27dfbc-5ad4-b8d2-0bc6-314f71f9982b.png">

文字列（１行）フィールドを使って、LINE UserIdとメッセージを保存するフィールドを作成します。

# 2. LINE Messaging APIを設定

LINE Messaging API の設定方法に関しては特別な設定はありませんので、下記の公式リンクを参考にしてください。

* [LINE Messaging API](https://developers.line.biz/ja/services/messaging-api/)
* [Messaging APIを利用するには](https://developers.line.biz/ja/docs/messaging-api/getting-started/#%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90)


# 3. Google Apps Script でWebアプリを作成
参考にコードを貼っておきます。

```typescript
// 環境変数のセット
const scriptProperties = PropertiesService.getScriptProperties();
const DOMAIN = scriptProperties.getProperty('DOMAIN'); // kintone ドメイン名
const APP_ID = scriptProperties.getProperty('APP_ID'); // kintone ユーザーID
const API_TOKEN = scriptProperties.getProperty('API_TOKEN'); // kintone APIトークン
const SHEET_ID = scriptProperties.getProperty('SHEET_ID'); // デバッグ用 Spreadsheet ID
const CHANNEL_ACCESS_TOKEN = scriptProperties.getProperty('CHANNEL_ACCESS_TOKEN'); // LINEチャネルアクセストークン

function doPost(e: { postData: { contents: string; }; }) {
  appendLogToSpreadsheet('--- debug start', SHEET_ID)

  let lineEvents = []
  const botUserId: string = JSON.parse(e.postData.contents).destination
  lineEvents = JSON.parse(e.postData.contents).events
  appendLogToSpreadsheet(botUserId, SHEET_ID)
  appendLogToSpreadsheet(JSON.stringify(lineEvents), SHEET_ID)
  if (!lineEvents.length) {
    return ContentService.createTextOutput(JSON.stringify({'content': '200 ok'})).setMimeType(ContentService.MimeType.JSON);
  }
  lineEvents.forEach(event => appendLogToSpreadsheet(JSON.stringify(event), SHEET_ID))
  let replyToken: string = ''
  let userMessage: string = ''
  const replyPackageId: string = '11537'
  const replyStickerId: string = '52002740' // LINEスタンプID
  let replyMessage = {}
  let kintoneResult = {}
  lineEvents.forEach( async (event) => {
    if (event.type === 'message') {
      if (event.message.type === 'text') { // テキストメッセージ
        replyToken = event.replyToken
        userMessage = event.message.text
        replyMessage = {
          type: 'text',
          text: userMessage
        }
        kintoneResult = await LoggerTokintone (userMessage, event.source.userId)
      } else if (event.message.type === 'sticker') { // LINEスタンプ
        replyToken = event.replyToken
        replyMessage = {
          type: 'sticker',
          packageId: replyPackageId,
          stickerId: replyStickerId
        }
        kintoneResult = await LoggerTokintone (`sticker packageId: ${event.message.packageId}, stickerId: ${event.message.stickerId}`, event.source.userId)
      } else {
        replyToken = event.replyToken
        userMessage = 'テキスト又はスタンプ以外が送信されました'
        replyMessage = {
          type: 'text',
          text: userMessage
        }
        kintoneResult = await LoggerTokintone (userMessage, event.source.userId)
      }
    } else {
      replyToken = event.replyToken
      userMessage = 'メッセージ以外が送信されました'
      replyMessage = {
        type: 'text',
        text: userMessage
      }
      kintoneResult = await LoggerTokintone (userMessage, event.source.userId)
    }
    appendLogToSpreadsheet('result', SHEET_ID)
    appendLogToSpreadsheet(kintoneResult, SHEET_ID)
  })
  if (typeof replyToken === 'undefined') {
    return ContentService.createTextOutput(JSON.stringify({'content': '200 ok'})).setMimeType(ContentService.MimeType.JSON);
  }

  const url: string = 'https://api.line.me/v2/bot/message/reply';
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': `Bearer ${CHANNEL_ACCESS_TOKEN}` ,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [replyMessage],
    }),
  });
  return ContentService.createTextOutput(JSON.stringify({'content': '200 ok'})).setMimeType(ContentService.MimeType.JSON);
}


// Spreadsheetにログを書き出す
function appendLogToSpreadsheet(log: string, sheetId: string, sheetName = 'sheet1') {
  const spreadSheet = SpreadsheetApp.openById(sheetId)
  spreadSheet.getSheetByName(sheetName).appendRow(
    [new Date(), log]
  );
  SpreadsheetApp.flush()
}

// kintoneにユーザーIDをログとして追加する
function LoggerTokintone (message: string, lineId: string) {
  const payload = {
    app: APP_ID,
    record: { 
      'message': {
        'value': message
      },
      'lineId': {
        'value': lineId
      },
    }
  }
  const option = {
    method: "post",
    contentType: "application/json",
    headers: { "X-Cybozu-API-Token": API_TOKEN },
    muteHttpExceptions: true,
    payload: JSON.stringify(payload)
  };
  appendLogToSpreadsheet('option', SHEET_ID)
  appendLogToSpreadsheet(JSON.stringify(option), SHEET_ID)
  
  // kintoneにレコード追加
  return new Promise((resolve, reject) => {
    try {
      const result = UrlFetchApp.fetch(
        `https://${DOMAIN}.cybozu.com/k/v1/record.json`,
        option
      )
      resolve(result)
    } catch (error) {
      reject(error)
    }
  })
}
```

デバッグログはSpreadsheetに出力しています。

# 4. Google Apps Script の公開URLをLINE Messaging API のWebhookに設定します

作成したLINE Messaging API チャネルの Webhook設定にGASの公開URLを設定して、Webhookの利用をONにします。

# 実行

LINE Messaging API のボット情報のQRコードを読み取って、友達追加します。
テキストメッセージを送るとおうむ返しでGASのBotサーバーからメッセージが送信されます。同時にkintoneにLINE UserIdとメッセージを追加します。

LINEメッセージ画面
<img width="375" alt="IMG_1158.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214523/3a95f54e-1e51-9a1d-70d1-68735048efaa.png">

kintoneレコード一覧
<img width="604" alt="スクリーンショット 2020-06-15 20.57.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214523/88c72878-da0e-8764-21f3-ec72686072c1.png">


# 参考

## LINE Messaging API

* [Messaging APIの特徴](https://developers.line.biz/ja/services/messaging-api/)
* [応答メッセージを送る](https://developers.line.biz/ja/docs/messaging-api/overview/#messaging-api%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)
* [Messaging APIの概要](https://developers.line.biz/ja/docs/messaging-api/overview/#messaging-api%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)
* [メッセージの送信方法](https://developers.line.biz/ja/docs/messaging-api/sending-messages/#methods-of-sending-message)
* [応答メッセージを送る](https://developers.line.biz/ja/reference/messaging-api/#send-reply-message)
* [Webhook](https://developers.line.biz/ja/reference/messaging-api/#webhooks)
* [Webhookイベントオブジェクト](https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects)
* [メッセージイベント](https://developers.line.biz/ja/reference/messaging-api/#message-event)
* [LINE チャネルの作成](https://developers.line.biz/ja/docs/messaging-api/getting-started/#%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90)
* [ボットを作成する](https://developers.line.biz/ja/docs/messaging-api/building-bot/#%E5%A7%8B%E3%82%81%E3%82%8B%E5%89%8D%E3%81%AB)

---
## Google Apps Script

* https://developers.google.com/apps-script/overview
* https://developers.google.com/apps-script/guides/v8-runtime
* https://developers.google.com/apps-script/guides/web
* https://developers.google.com/apps-script/guides/content
* https://codelabs.developers.google.com/codelabs/clasp/#0
* https://github.com/google/clasp

---
## 今後の展開

* kintoneからレコードを取得してLINEに返信する
* GASの代わりにBotサーバーにHerokuを利用する