LINE Botの友達情報をkintoneへ登録してみた

# はじめに

LINE Botのプッシュメッセージ※を利用するためには **「userID」** が必要です。

userIDはアカウントごとに一意の値であり、
1つのBot(正確には1つのプロバイダー)から見たら、同じアカウントは同じuserIDとなります。
-> このuserIDでアカウントを特定することができる！！
-> -> 「Bot A にメッセージを送信すると、別のBot Bから返事が返ってくる」ってこともできます！！

しかし、
このuserIDは「友達登録時」「ユーザーからのメッセージ受信時」といったように、ユーザーが何かアクションをしないと取得できません :bomb:

ということで、
任意のタイミングでメッセージを送るためには、あらかじめuserIDをためておくDBが必要になります!

<br>

（ってことで、）

<br>

今回は友達登録された際に **kintone** へuserIDを登録する方法を書きます！!

※プッシュメッセージについては [こちら](https://developers.line.biz/ja/reference/messaging-api/#send-push-message) をご覧ください

# 仕組み・フロー

ユーザーの操作が「友達登録」なのか「メッセージ送信」なのかは Webhookで受け取れるイベントの中にある`type`で判断することができます。

`event.body.events[].type`

このtypeは、

- `follow` -> 友達登録 (とブロック解除)
- `unfollow` -> ブロック
- `message` -> メッセージ送信

という感じに判断できます。なのであとはいい感じに分岐すれば良いだけです！
![flow.png](https://qiita-image-store.s3.amazonaws.com/0/153051/2af0a71d-9826-5ca4-fa91-3e95dbaa970d.png)

ちょっとまじめに流れを書いてみると、

1. Webhookを受け取る
2. イベントオブジェクトからtypeとuserIDを取得する
3. typeが`follow`か`unfollow`なら、userIDをクエリとしてkintoneのレコードを取得する
4. `follow`かつ該当レコードがなければ、userIDをkintoneへ登録する <br> （ついでにアカウント名なども一緒に）
5. `follow`かつ該当レコードがあれば、「ブロック解除」の処理をする <br> -> 取得したレコードを更新する
6. `unfollow`であれば、該当レコードを「ブロック中」と更新する

こんな感じかと。（4へは最初の1回だけ。あとは5or6への処理を繰り返す）

# kintone側の設定

DBとしてkintoneを使います！！！ （S3やDynamoでもいいじゃんは :ng: ですｗ）

## フィールド

kintoneのフィールドは以下のようにしました

| フィールド | フィールドコード | 用途 |
|:-:|:-:|:-:|
| 作成日時 | create_time | Botを追加した日時 |
| 文字列（1行） | line_name | LINEのアカウント名 |
| 文字列（1行） | line_id | LINEのuserID |
| チェックボックス | line_block | Botのブロック判断 |

フィールドを配置するとこんな感じです。

<img width="600" alt="スクリーンショット 2019-03-20 20.14.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/153051/62f79cf1-ee83-42da-8bbe-0c23d5de7ad5.png">

## APIトークン

`設定 > APIトークン` から `閲覧/追加/編集`にそれぞれチェックをつけてトークンを発行してください。

# コード

[GitHub](https://github.com/RyBB/LINE_FriendsRegist_kintone) においてあります。cloneすればとりあえず動くとは思います。

```bash
// 作業用ディレクトリへ移動
$ cd work
// リポジトリのクローン
$ git clone https://github.com/RyBB/LINE_FriendsRegist_kintone.git

// ディレクトリの移動
$ cd LINE_FriendsRegist_kintone
// node_moduleのインストール
$ npm install
```

中間サーバとしてLambda（とAPI Gateway）を利用しています。AWS側の細かい設定は省略！
(AWSの設定は[こちら](https://developer.cybozu.io/hc/ja/articles/360015747791#step4)を参考にすると良いです)

```javascript:index.js
const kintone = require('kintone-nodejs-sdk');
const line = require('@line/bot-sdk');

const kin_common = {
  url: process.env.KINTONE_DOMAIN,
  apiToken: process.env.KINTONE_APITOKEN,
  appId: process.env.KINTONE_APPID,
  fCode1: 'line_name',
  fCode2: 'line_id',
  fCode3: 'line_block',
};

const kintoneAuth = new kintone.Auth();
kintoneAuth.setApiToken(kin_common.apiToken);
const kintoneConnection = new kintone.Connection(kin_common.url, kintoneAuth);
const kintoneRecord = new kintone.Record(kintoneConnection);

// kintoneのレコードをクエリで条件取得する処理
const getRecords = (LINE_USER_ID) => {
  const query = kin_common.fCode2 + ' = "' + LINE_USER_ID + '"';
  return kintoneRecord.getRecords(kin_common.appId, query, ['$id'], true);
};

// kintoneのレコードを1件登録する処理
const postRecord = (LINE_USER_ID, LINE_NAME) => {
  const params = {
    [kin_common.fCode1]: {
      'value': LINE_NAME
    },
    [kin_common.fCode2]: {
      'value': LINE_USER_ID
    },
  };
  return kintoneRecord.addRecord(kin_common.appId, params);
};

// kintoneのレコードを1件更新する処理
const putRecord = (LINE_USER_ID, recordid, follow) => {
  const params = {
    [kin_common.fCode2]: {
      'value': LINE_USER_ID
    },
    [kin_common.fCode3]: {
      'value': follow === 'follow' ? [] : ['ブロック中']
    },
  };
  return kintoneRecord.updateRecordById(kin_common.appId, recordid, params);
};

exports.handler = async (event) => {
  const res = JSON.parse(event.body);
  const line_userId = res.events[0].source.userId;

  const client = new line.Client({
    channelAccessToken: process.env.LINE_ACCESS_TOKEN
  });

  let resp, line_resp;
  switch(res.events[0].type) { // メッセージのタイプを判断
    case 'follow': {
      resp = await getRecords(line_userId);
      if (resp.totalCount !== '0') {
        console.log('ブロック解除された');
        await putRecord(line_userId, resp.records[0].$id.value, 'follow');
        break;
      }
      console.log('新規登録された');
      line_resp = await client.getProfile(line_userId); // LINEのアカウント名を取得する
      await postRecord(line_resp.userId, line_resp.displayName);
      break;
    }
    case 'unfollow': {
      resp = await getRecords(line_userId);
      if (resp.totalCount !== '0') {
        console.log('ブロックされた');
        await putRecord(line_userId, resp.records[0].$id.value, 'unfollow');
      }
      break;
    }
    default: {
      // それ以外。何もしない
    }
  }
};
```
Lambda上の環境変数はこんなかんじ。
![setting.png](https://qiita-image-store.s3.amazonaws.com/0/153051/defaad48-6a49-4706-7633-1bf93bf93072.png)

## ちょっと解説

コードをちょっとだけ解説します！

- kintone Node.js SDK

今回はkintoneへのリクエストが多い （GET/POST/PUT） ので、SDKを利用しました。
最初にコネクションを設定しないといけないですが、それ以外はかなり楽に記述できます （メソッドとか）

詳しい書き方は [こちら](https://developer.cybozu.io/hc/ja/articles/360000511243-kintone-API-SDK-%CE%B2-for-Node-js) 。

```javascript:kintone-nodejs-sdk_connection
// コネクション
const kintoneAuth = new kintone.Auth();
kintoneAuth.setApiToken(kin_common.apiToken);
const kintoneConnection = new kintone.Connection(kin_common.url, kintoneAuth);
const kintoneRecord = new kintone.Record(kintoneConnection);

// メソッド (GET/POST/PUT それぞれあります)
kintoneRecord.XXXX();
```

- typeによる分岐

typeが`follow`か`unfollow`か`それ以外`かで分岐させています。

```javascript:switch
  switch(res.events[0].type) { // メッセージのタイプを判断
    case 'follow': {
      resp = await getRecords(line_userId);
      if (resp.totalCount !== '0') {
        console.log('ブロック解除された');
        await putRecord(line_userId, resp.records[0].$id.value, 'follow');
        break;
      }
      console.log('新規登録された');
      line_resp = await client.getProfile(line_userId); // LINEのアカウント名を取得する
      await postRecord(line_resp.userId, line_resp.displayName);
      break;
    }
    case 'unfollow': {
      resp = await getRecords(line_userId);
      if (resp.totalCount !== '0') {
        console.log('ブロックされた');
        await putRecord(line_userId, resp.records[0].$id.value, 'unfollow');
      }
      break;
    }
    default: {
      // それ以外。何もしない
    }
  }
```

- kintoneのレコード取得

kintoneのuserIDを格納するフィールド（line_id）と友達登録時（ブロック操作時）に受け取ったLINEのuserIDがマッチするレコードのみ取得するようにします。
もしマッチするレコードがあれば、すでにそのアカウントはkintoneへ登録済みってことになります！

```javascript:kintone_getRecords
const getRecords = (LINE_USER_ID) => {
  const query = kin_common.fCode2 + ' = "' + LINE_USER_ID + '"';
  return kintoneRecord.getRecords(kin_common.appId, query, ['$id'], true);
};
```

- kintoneのレコード更新

typeが`follow`かつレコードが存在すれば、ブロック解除したことになるので、
kintoneのチェックボックスを空にします。
逆に、typeが`unfollow`であればチェックボックスに「ブロック中」と入れます。

```javascript:kintone_putRecord
const putRecord = (LINE_USER_ID, recordid, follow) => {
  const params = {
    [kin_common.fCode2]: {
      'value': LINE_USER_ID
    },
    [kin_common.fCode2]: {
      'value': follow === 'follow' ? [] : ['ブロック中']
    }
  };
  return kintoneRecord.updateRecordById(kin_common.appId, recordid, params);
};
```

# 完成イメージ

こんな感じでkintoneにuserIDとアカウント名とブロック状況が登録されます。
![line-kin.png](https://qiita-image-store.s3.amazonaws.com/0/153051/e561aa8b-8e5d-a0fa-8f99-cd00331c1494.png)

# おわりに

あとは任意のタイミングでkintoneからuserIDを取得してメッセージを送るだけです。
アカウント名も`client.getProfile(line_userId);`で取得しているのでデータとして扱いやすいかと！！

このくらいのDB管理であれば、むしろkintoneのほうがフォーム作ったりが簡単なのでおすすめです！
（きちんと運用する場合は「ブロックされたらレコードを削除する」などの処理のほうが良いですよ〜）

それでは！≧(+･` ཀ･´)≦