kintoneとLINEを連携してkintoneからメッセージを送受信してみよう！

# はじめに

kintone上で LINE メッセージの送受信ができる仕組みを構築する手順をまとめてみました！

構成は以下の通りです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/3d5ba3a0-86e9-45dd-3d91-5c7634c59b54.png" width="600" />

LINEからのメッセージ受信については、Webhook をトリガーとして AWS で構築した API を呼び出して、AWS から kintone にメッセージを登録する流れとなります。

また、LINE へのメッセージ送信については、kintone から LINE Messaging API を実行してメッセージを送信する流れとなります。

# 動作イメージ

初回のメッセージのやり取りは LINE 側から行います。
友だち追加した LINE アカウントからメッセージを送信すると、kintone にメッセージが届きます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/ed10e98f-7431-0036-dc6e-73a6b8759e23.png" width="300" />

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/27e5ac3e-b7ad-2123-9d3c-3609bf40e6f5.png" width="350" />

届いたメッセージにLINEユーザーIDが含まれているので、友だちとしてLINEユーザーIDを登録します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/ef8e60ec-d6cf-3e0c-62aa-208f03daf2b5.png" width="300" />

LINEユーザーIDが保存できたら kintone 側からのメッセージ送信が可能となります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/cd749208-fe6e-cbe5-9306-4faf38c2e330.png" width="300" />

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/89320dbf-a911-93e6-dbda-444db42dca10.png" width="300" />

以降は kintone 上で LINE のメッセージをやり取りすることが可能です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/f2386acb-4f65-151d-f3ab-9b24ffe18b06.png" width="300" />

# 構築手順

## 1. kintone アプリの作成

"送受信したメッセージの履歴を記録するアプリ" と "メッセージを送受信する友達を管理するアプリ" の2つを作成します。

### 1.1 「メッセージ履歴」アプリの作成
送受信したメッセージの履歴を記録するための「メッセージ履歴」アプリを作成します。
フォームのフィールドは以下の3つを用意してください。

* LINEユーザーID（文字列1行）　※必須
LINE のユーザーを一意に識別するためのユーザーIDを保存します。

* 送信メッセージ（文字列複数行）
kintone から LINE へ送信したメッセージを保存します。

* 受信メッセージ（文字列複数行）
LINE から受信したメッセージを保存します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/73f58e6c-fbc2-6103-e92b-13a3adafc829.png" width="600" />

「メッセージ履歴」アプリを作成したら、レコード追加権限のみを付与した API トークンを発行しておいてください。後ほど使用いたします。

### 1.2 「友だちリスト」アプリの作成

メッセージを送受信する友達を管理するための「友だちリスト」アプリを作成します。
フォームのフィールドは以下の2つを用意してください。

* LINEユーザーID（文字列1行）　※必須
LINE のユーザーを一意に識別するためのユーザーIDを保存します。

* メッセージ履歴（関連レコード一覧）
「メッセージ履歴」アプリからLINEユーザーIDが一致するレコードを取得して表示します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/cfb5c653-f1d6-2bae-7bf6-363b827974ea.png" width="500" />

## 2. LINE Bot の作成

LINE Developers のドキュメントに従って、Messaging API を利用するための準備を行ってください。

https://developers.line.biz/ja/docs/messaging-api/getting-started/

* LINE公式アカウントを作成する
* Messaging APIを有効にする
* 新規プロバイダーを作成する

また、LINE Official Account Manager の応答設定から以下の設定を行ってください。

* あいさつメッセージを無効にする
* 応答メッセージを無効にする

## 3. AWS Lambda で関数を作成

LINE からメッセージを受信して kintone にメッセージを登録する処理を作成します。

Lambda で新たに関数を作成してください。今回はランタイムとして Node.js を利用いたしました。作成したコードは以下の通りです。

```javascript
import { Client, validateSignature } from '@line/bot-sdk';
import { KintoneRestAPIClient } from '@kintone/rest-api-client';

export const handler = async (req) => {
  // 署名の検証（LINEからの接続であるか）
  const signature = req.headers['x-line-signature'];
  const bool = validateSignature(req.body, process.env.LINE_CHANNEL_SECRET, signature);
  if (!bool) throw new Error('invalid signature');

  // LINEから受信したイベントを処理
  const body = JSON.parse(req.body);
  for await (const event of body.events) {
    // テキストメッセージイベント以外はスキップ
    if (event.type !== 'message' || event.message.type !== 'text') {
      return;
    }
    // メッセージ、またはユーザーIDが取得できなければスキップ
    const message = event.message.text || '';
    const userId = event.source.userId || '';
    if (message === '' || userId === '') {
      return;
    }
    // kintoneにレコード登録
    const kintoneClient = new KintoneRestAPIClient({
      baseUrl: process.env.KINTONE_BASE_URL,
      auth: { apiToken: process.env.KINTONE_API_TOKEN },
    });
    const response = await kintoneClient.record.addRecord({
      app: process.env.KINTONE_APP_ID,
      record: {
        LINEユーザーID: { value: userId },
        受信メッセージ: { value: message },
      },
    });
  }

  return { statusCode: 200 };
};
```

上記の関数を実行するためにはレイヤーの作成と環境変数の設定を行う必要があります。

### 3.1 AWS Lambda レイヤーの作成

LINE Messaging API SDK と kintone REST API Client を用いるため、AWS Lambdaでレイヤーを作成します。レイヤーの作成手順は省略しますが、インストールしたライブラリを Zip に圧縮してアップロードするだけです。作成できたら、先ほど作成した関数にレイヤーを追加してください。

**LINE Messaging API SDK**

https://github.com/line/line-bot-sdk-nodejs

```bash
npm install @line/bot-sdk --save
```

**kintone REST API Client**

https://github.com/kintone/js-sdk/tree/main/packages/rest-api-client

```bash
npm install @kintone/rest-api-client
```

### 3.2 AWS Lambda 環境変数の設定

4つの環境変数を設定します。今回は以下のような変数名で管理しております。

* KINTONE_BASE_URL
利用している kintone のドメインを含めたURL

* KINTONE_APP_ID
「メッセージ履歴」アプリのID

* KINTONE_API_TOKEN
「メッセージ履歴」アプリで発行したAPIトークン

* LINE_CHANNEL_SECRET
LINE Developers のコンソールから参照可能なチャネルシークレット

## 4. Amazon API Gateway で HTTP API を作成

Lambda 関数を LINE から実行するために HTTP API を作成します。

1. API タイプの選択
HTTP API を選択してください。
2. API の作成
「Lambda 統合」で先ほど作成した Lambda 関数を選択してください。
3. ルートを設定
メソッドは「POST」を選択してください。リソースパスは任意の値で構いません。
4. ステージを定義 
特に変更せず、初期値のまま設定してください。

API の設定が完了したら、作成したAPIのURLを LINE に登録します。
LINE Developers のコンソールにある Messaging API の設定画面を開いて、「Webhook URL」に作成したAPIのURLを設定してください。また、併せて「Webhook の利用」を有効にしてください。

以上で、LINE からメッセージを受信して kintone にメッセージを登録する処理は作成完了となります。

## 5. kintone プラグイン開発環境の整備

ここからは、kintone から LINE へメッセージ送信をする処理の作成を行っていきます。

kintone プラグインを作成するため、cybozu developer network に記載された手順に従って、create-plugin と webpack-plugin-kintone-plugin をインストールします。

https://cybozu.dev/ja/kintone/sdk/development-environment/create-plugin/

https://cybozu.dev/ja/kintone/sdk/development-environment/webpack-plugin-kintone-plugin/

次に利用するライブラリをインストールします。今回は送信するメッセージの入力フォームをアラートで表示するため、SweetAlert2 をインストールしてください。

```bash
npm install sweetalert2
```

ライブラリをインストールが完了したら、`npm run build` コマンドを実行してください。`dist` ディレクトリの下に `plugin.zip` が生成されていれば準備完了です。

:::note warn
**なぜ、アプリカスタマイズではなく、プラグインを作成するのか？**

作成したアプリをカスタマイズして、LINE にメッセージ送信することは技術的には可能です。しかし、LINE Messaging API を実行するためにはチャネルアクセストークンが必要で、**アプリカスタマイズで実現すると秘匿すべきチャネルアクセストークンが流出してしまう可能性があります。**

したがって、プラグインを用いて秘匿すべきチャネルアクセストークンは隠した状態でLINE Messaging API を実行するように構築しています。
:::

## 6. メッセージ送信機能の作成

「友だちリスト」のレコード詳細画面でメッセージ送信を可能とする機能を作成します。`src\js` ディレクトリの `desktop.js` に以下のコードを記述してください。

<details>
<summary>コード全文はこちら</summary>
<div>

```javascript:desktop.js
import Swal from 'sweetalert2';

(function (PLUGIN_ID) {
  kintone.events.on('app.record.detail.show', () => {
    const sendButton = document.createElement('button');
    sendButton.textContent = 'メッセージ送信';
    sendButton.className = 'kintoneplugin-button-dialog-ok';
    sendButton.style = 'margin:16px 0 0 16px';
    sendButton.onclick = showAlert;

    const headerMenuSpace = kintone.app.record.getHeaderMenuSpaceElement();
    headerMenuSpace.appendChild(sendButton);
  });

  /**
   * メッセージ送信アラート表示
   */
  const showAlert = () => {
    Swal.fire({
      title: 'LINEメッセージ送信',
      text: '送信するメッセージを入力してください。',
      input: 'textarea',
      showCancelButton: true,
      cancelButtonText: 'キャンセル',
      confirmButtonText: '送信',
      preConfirm: async (message) => {
        // 入力チェック
        if (message === '') {
          Swal.showValidationMessage('メッセージを入力してください。');
          return;
        }
        try {
          const { record } = kintone.app.record.get();
          const userId = record['LINEユーザーID'].value;
          await sendMessage(userId, message);
          await createHistory(userId, message);
        } catch (error) {
          console.error(error);
          Swal.showValidationMessage('メッセージの送信に失敗しました。');
        }
      },
      allowOutsideClick: () => !Swal.isLoading(),
    }).then((result) => {
      if (result.isConfirmed) {
        Swal.fire({
          title: '送信完了',
          text: 'メッセージが送信されました。',
          icon: 'success',
        }).then(() => location.reload());
      }
    });
  };

  /**
   * LINEにメッセージ送信
   */
  const sendMessage = async (userId, message) => {
    const [, status] = await kintone.plugin.app.proxy(
      PLUGIN_ID,
      'https://api.line.me/v2/bot/message/push',
      'POST',
      {},
      {
        to: userId,
        messages: [{ type: 'text', text: message }],
      },
    );
    if (status !== 200) {
      throw new Error('メッセージの送信に失敗しました。');
    }
  };

  /**
   * メッセージ履歴の登録
   */
  const createHistory = async (userId, message) => {
    const appId = kintone.app.getRelatedRecordsTargetAppId('メッセージ履歴');
    await kintone.api(kintone.api.url('/k/v1/record.json', true), 'POST', {
      app: appId,
      record: {
        LINEユーザーID: { value: userId },
        送信メッセージ: { value: message },
      },
    });
  };
})(kintone.$PLUGIN_ID);
```
</div>
</details>

記述したコードの内容について説明します。
まず最初に、レコード詳細画面が表示されたときのイベントで、メニューの上側の要素を取得してボタンを配置するようにしています。このボタンを押下することで送信するメッセージを入力するあらーが表示されます。

```javascript
  kintone.events.on('app.record.detail.show', () => {
    const sendButton = document.createElement('button');
    sendButton.textContent = 'メッセージ送信';
    sendButton.className = 'kintoneplugin-button-dialog-ok';
    sendButton.style = 'margin:16px 0 0 16px';
    sendButton.onclick = showAlert;

    const headerMenuSpace = kintone.app.record.getHeaderMenuSpaceElement();
    headerMenuSpace.appendChild(sendButton);
  });
```

次に、ボタンを押下したときに実行される処理について記述しています。SweetAlert2 を用いて表示したアラートにテキストエリアを設け、送信するメッセージを入力可能としています。
また、アラート上の「送信」ボタンを押下すると LINE にメッセージを送信した後、送信したメッセージを履歴として kintone に登録する処理を行っております。

```javascript
  const showAlert = () => {
    Swal.fire({
      title: 'LINEメッセージ送信',
      text: '送信するメッセージを入力してください。',
      input: 'textarea',
      showCancelButton: true,
      cancelButtonText: 'キャンセル',
      confirmButtonText: '送信',
      preConfirm: async (message) => {
        // 入力チェック
        if (message === '') {
          Swal.showValidationMessage('メッセージを入力してください。');
          return;
        }
        try {
          const { record } = kintone.app.record.get();
          const userId = record['LINEユーザーID'].value;
          await sendMessage(userId, message);
          await createHistory(userId, message);
        } catch (error) {
          console.error(error);
          Swal.showValidationMessage('メッセージの送信に失敗しました。');
        }
      },
      allowOutsideClick: () => !Swal.isLoading(),
    }).then((result) => {
      if (result.isConfirmed) {
        Swal.fire({
          title: '送信完了',
          text: 'メッセージが送信されました。',
          icon: 'success',
        }).then(() => location.reload());
      }
    });
  };
```

以下のコードは「送信」ボタンを押下したときに実行される LINE へメッセージを送信する処理です。`kintone.plugin.app.proxy()` を用いることで、秘匿情報であるチャネルアクセストークンを隠したまま、Messaging API を呼び出すことを可能としています。

```javascript
  const sendMessage = async (userId, message) => {
    const [, status] = await kintone.plugin.app.proxy(
      PLUGIN_ID,
      'https://api.line.me/v2/bot/message/push',
      'POST',
      {},
      {
        to: userId,
        messages: [{ type: 'text', text: message }],
      },
    );
    if (status !== 200) {
      throw new Error('メッセージの送信に失敗しました。');
    }
  };
```

以下のコードは LINE へメッセージを送信した後に、履歴として kintone に保存する処理です。`kintone.api()` を用いて、「メッセージ履歴」アプリに1件のレコードを新規登録しております。

```javascript
  const createHistory = async (userId, message) => {
    const appId = kintone.app.getRelatedRecordsTargetAppId('メッセージ履歴');
    await kintone.api(kintone.api.url('/k/v1/record.json', true), 'POST', {
      app: appId,
      record: {
        LINEユーザーID: { value: userId },
        送信メッセージ: { value: message },
      },
    });
  };
```

## 7. プラグイン設定画面の作成

Messaging API で用いるチャネルアクセストークンをプラグインに保存するため、プラグイン設定画面を作成していきます。

まずは、プラグイン設定画面の HTML を作成します。`plugin\html` ディレクトリの `config.html` に以下の内容を記述してください。

```html:config.html
<section class="settings">
  <form class="js-submit-settings">
    <div class="kintoneplugin-row">
      <label for="channel-access-token" class="kintoneplugin-label">
        チャネルアクセストークン<span class="kintoneplugin-require">*</span>
      </label>
      <p class="kintoneplugin-title">
        Messaging API を実行するために LINE Developers
        から取得したチャネルアクセストークンを設定してください。
      </p>
      <input
        type="text"
        id="channel-access-token"
        class="js-channel-access-token kintoneplugin-input-text"
      />
    </div>
    <div class="kintoneplugin-row">
      <button
        type="button"
        class="js-cancel-button kintoneplugin-button-dialog-cancel"
      >
        キャンセル
      </button>
      <button class="kintoneplugin-button-dialog-ok">保存</button>
    </div>
  </form>
</section>
```

また、見た目を整えるため、`plugin\css` ディレクトリの `config.css` に以下のコードを追記してください。

```css:config.css
.kintoneplugin-input-text {
  width: 50em;
  display: block;
  margin-top: 1em;
}
```

次に、画面に入力されたチャネルアクセストークンを保存する処理を `src\js` ディレクトリの `config.js` に記述してください。

```javascript:config.js
(function (PLUGIN_ID) {
  const formEl = document.querySelector('.js-submit-settings');
  const cancelButtonEl = document.querySelector('.js-cancel-button');
  const inputEl = document.querySelector('.js-channel-access-token');
  if (!(formEl && cancelButtonEl && inputEl)) {
    throw new Error('必要なフィールドが存在しません。');
  }

  formEl.addEventListener('submit', (e) => {
    e.preventDefault();
    kintone.plugin.app.setProxyConfig(
      'https://api.line.me',
      'POST',
      {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${inputEl.value}`,
      },
      {},
      () => {
        window.location.href = '../../flow?app=' + kintone.app.getId();
      },
    );
  });

  cancelButtonEl.addEventListener('click', () => {
    window.location.href = '../../' + kintone.app.getId() + '/plugin/';
  });
})(kintone.$PLUGIN_ID);
```

最後に、`plugin` ディレクトリの `manifest.json` を編集して、デフォルトで設定されているプラグイン設定の必須項目を削除してください。

```diff_json:manifest.json
  "config": {
    "html": "html/config.html",
    "js": [
      "js/config.js"
    ],
    "css": [
      "css/51-modern-default.css",
      "css/config.css"
    ],
    "required_params": [
-      "message"
    ]
  },
```

プラグイン設定画面が作成できたら、`npm run build` コマンドを実行してください。`dist` ディレクトリの下に生成された `plugin.zip` を kintone にアップロードして、「友だちリスト」アプリにプラグインを追加してください。

## 8. チャネルアクセストークンの保存

プラグインの利用準備が整ったので、プラグインの設定を行います。
LINE Developers のコンソールから長期のチャネルアクセストークンを発行してください。

:::note info
チャネルアクセストークンにはいくつか種類があり、セキュリティを考慮すると有効期間が決められているものが望ましいですが、今回は発行が簡単な「長期のチャネルアクセストークン」を用います。
:::

https://developers.line.biz/ja/docs/basics/channel-access-token/#channel-access-token-types

発行できたらプラグインの設定画面を開いて、発行したチャネルアクセストークンを入力して保存してください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/baadf5af-e31c-b728-6eaa-dc6d4075ae91.png" width="700" />

以上で、LINE メッセージの送受信ができる仕組みの構築が完了となります。

# 補足

kintone から LINE にメッセージを送信するプラグインのプログラムを参考までに載せておきます。

https://github.com/falya128/send-line-message-plugin