【LINE Bot】Railsで明日のシフトを通知するBotを作りました

## はじめに
現在，自作アプリの「minshif」というサービスを開発しています．minshifはシフトを一元管理するアプリで，シフト提出依頼，シフト作成，シフト変更を行えます．機能の一つとしてLINEでシフト通知の自動送信機能を作成した過程を紹介します．

（現在は，バックエンドを停止しています）

https://www.minshif.com/

### 概要
LINE Botからプッシュメッセージを送るためには`userId`を保存しておく必要があります．
`userId`はユーザごとに固定されているため，それを使うことで**ユーザへ個別にメッセージを送信**できます．
この機能を利用して明日にシフトが入っている場合は自動的にお知らせする機能を作りました．
Sidekiqを使うことでスケジューリングされた時刻に定期実行するよう実装しました．

:::note info
お急ぎの方は`案2：認証コードを発行する`から読み進めてください
:::


### やること
- LINEの`userId`とアプリのユーザ情報の紐付け方法
- LINE Botを使って，ユーザごとに個別対応する必要があるケースの参考事例紹介

### やらないこと
- Sidekiq＋Redisを使った定期実行のやり方
    - [こちら](https://qiita.com/lemonade_37/items/296bc211cf3e781c5600)のリンクを参考にさせていただきました



## 背景
この機能を作った背景です。

（私事なので読み飛ばしていただいて結構です。）

<details><summary>詳細</summary>
---------------------------------------- ここから -------------------------------------------

読んでくださるのですね．ありがとうございます😊

私は塾講師で4年勤務していました．その間，特に季節講習で急なシフト変更が多発していました．
1. 生徒の時間変更依頼
2. 講師のシフト変更依頼（長期休暇のかなり前(1~2ヶ月前)にシフトを作成するため）

その変更は（塾長によって違いましたが），口頭で伝える時もあればシフト表を紙で印刷していただける時もありました．

しかし，口頭の場合は変更のメモを取り忘れてしまうことやオーバーブッキングをしてしまうことがあり，紙の場合は何度も変更が起こることによってどれが最新版かわからなくなることがありました．どれもヒューマンエラー的な要素ですが無視できないものだと思います．

それにより，講師がシフトを忘れてしまう場合や休みなのに出勤してしまう場合（自分w）がありました．塾長は一時的な解決策として毎日，明日のシフトに入っている人に対してLINEで連絡することを行なってくださりました．

しかし，授業の変更がない人に対しても連絡をしていただいていたため，返信する講師側にも負担になっていました．したがって講師の中にはその必要性に疑問を持つ人もいました．

毎回の授業連絡の必要性についてヒアリングを行うと，最近は生徒の事情で授業変更が多いため実施をしていると御回答いただきました．加えて，授業の変更がなくても混乱を避けるために送る必要があると御回答いただきました．
それでも，数人に対して毎日連絡するのはかなり大変だと感じました．

そこで明日のシフトに入っている人に対して自動的に連絡するLINE Botを作成するようにしました．

---------------------------------------- ここまで -------------------------------------------
</details>














## やりたいこと

今回やりたいことは，ユーザが所属している店舗で明日シフトに入っている場合，そのユーザへLINE Botから通知を送ることです．







## 課題
前提として，LINEからメッセージを送るためには`userId`を取得する必要があります．
そのためにはユーザが以下のアクションを取る必要があります．
- **友だち追加時**
- ブロック時
- **ユーザからのメッセージ受信時**
- **LINEログイン時**

上記アクションを行うと以下のデータ構造がWebhookを通してアプリに送信されます．
（[公式doc](https://developers.line.biz/ja/docs/messaging-api/getting-user-ids/#get-user-ids-in-webhook)より抜粋）
```json
{
  "destination": "xxxxxxxxxx",
  "events": [
    {
      "type": "follow",
      "timestamp": 1462629479859,
      "source": {
        // ユーザーIDはsourceオブジェクトのuserIdプロパティから取得できる
        "type": "user",
        "userId": "U8189cf6745fc0d808977bdb0b9f22995"
      },
      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
      "mode": "active",
      "webhookEventId": "01FZ74A0TDDPYRVKNK77XKC3ZR",
      "deliveryContext": {
        "isRedelivery": false
      }
    }
  ]
}
```

ユーザを識別せずBotを使用する場合は，友だち追加時に送信される`userId`を保存することで実装できそうです．

しかし，**今回のようにユーザを識別する必要があるBotは，この情報だけで識別することは（自分が考えた限り）できない**と思います．
したがって「LINEの`userId`」と「アプリのユーザ情報」を**紐付ける方法**を検討しました．










## 案１：シフト通知機能をLINEログインしたユーザに限定する

ユーザがLINEのプロバイダでログインしたときも，`userId`を取得できます．
したがってログイン時にuserIdをDBへ保存しておく方法が挙げられます．

![スクリーンショット 2024-11-03 13.43.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2665507/e02ac448-4e9b-ff22-f61e-a4c07fe61f6a.png)

### 利点
1. LINE Bot利用時に，ユーザは友達追加するだけで使用できる
1. LINEログイン以外のプロバイダもこのサービス以外はそのまま利用できて影響を与えない


### 欠点
1. LINE以外のプロバイダからログインしたいユーザにサービスを提供できない
1. 他プロバイダを利用していて，Botのサービスを使用したくなった場合，LINEで再度ログインが必要






## 案２：認証コードを発行する
### 準備
1. アプリで認証コードを生成して，DBへ保存しておく
    a. このときDBに「認証コード」「有効期限」「アプリのユーザID」を保存
1. ユーザに友だち追加QRコードを提供する
1. ユーザがそれを基に友だち追加する

![スクリーンショット 2024-11-03 13.53.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2665507/9edfe91e-a36e-406b-7057-fe10966ffca7.png)



### 認証フロー
1. ユーザは，アプリで生成された認証コードをコピーしておく
1.  **ユーザがLINE Botに対して認証コードを送信**
    a. 認証できた場合は，ユーザと紐付け
    b. 認証できない場合（有効期限切れ or 認証コード不一致 or 認証コード形式誤り）は，
認証コードを再発行するメッセージを送信




|![success.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2665507/0a701fdc-9ad5-bf60-5456-511221492b5c.png)|![スクリーンショット 2024-11-03 13.45.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2665507/0bc8ee89-7fe3-55f4-3ba1-4e9ca05aac05.png)|
|---|---|
|認証成功時|認証失敗時|





### 利点
1. 案一と比較して，どのプロバイダでもLINE Botからの通知機能を利用できる
1. すべて一律のサービス提供ができる


### 欠点
1. ユーザの負担が増加
1. 認証コードを送り忘れていた場合，利用できない














## 採用案

今回は案2を採用しました．
理由としては**多くのユーザへサービス提供ができる**点と，ユーザ負担が増加するとしても一度のみ認証コードを送信するだけなので許容範囲だと考えているからです．



## 結果
毎朝9時に明日のシフトを通知してくれるようにしました．
実際に使っていただくと非常に好評で，8割近い講師の方々からわかりやすいという意見をいただくことができました．
「9時よりも18時などの夕方に通知してくれる方がありがたい」や「カスタムできるともっと良くなる」という意見もあったためUX向上に向けて更なる開発に取り組みたいと考えています．

![スクリーンショット 2024-11-16 12.16.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2665507/c79f0bdb-2c27-b192-b979-4b1a63e239f1.png)



## 学び
アプリの方針を決定する際に重要なことは，ユーザにとって価値が高い方を選択することだと再認識しました．案1の方がユーザへの負担が軽減されるものの，LINEログインをしたユーザのみに限定してしまうと機能を使用していただくユーザ数が減少してしまいます．より多くのユーザに価値を提供することに重点を置いて考え抜く必要があると学びました．


技術的には、定期実行できるSidekiqの強さを実感しました。これによって定期的に行っている業務(今回は明日のシフト連絡)を自動化させることができました。ITの力によって単純作業を自動化・効率化することで、新たな価値やいいサービスを作るための時間を増やすことができるようにすることが自分のやりがいにつながりました。




## 最後に

今回はLINE Botでユーザごとに個別対応する必要があったためこのようなフローを考えました．問題でも述べましたが，個別対応の必要がない場合は友だち追加時に`userId`を保存する方法でいいと思います．

最後に，LINE Botからマークダウン形式でメッセージを送信したいのですが，PCしか対応していないためスマホなどのiOSにも対応してほしいと願っています．(2024/11/16時点)


## 参考

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

https://qiita.com/RyBB/items/23221bafd8231579b396

https://qiita.com/fromage-blanc/items/1815992793b239a6d017