プリザンターにLINE BOTのWebhookを追加する

本エントリはOSSのノーコード・ローコード開発ツール「プリザンター」 Advent Calendar 2024の9日目の記事です。

https://qiita.com/advent-calendar/2024/pleasanter

# はじめに
この記事は[プリザンターに独自のエンドポイントを追加する](https://qiita.com/pierusan2010/items/9c7bd6245db2d18f619e)の続編となります。

今回は、前回ご紹介した拡張コントローラを使った例として、プリザンターにLINE BOTのwebhookのエンドポイントを実装してLINEと連携する方法について解説します。

# LINEへの通知に必要なLINEのユーザーIDを取得するBOT
プリザンターの通知機能では、通知種別に「LINE」「LINEグループ」を指定することでLINEにPush通知を送信できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/d305b49d-ae2b-0618-58ca-0a19f161bf87.png)

## 通知先のユーザーIDを取得する方法

アドレス欄にはLINEのユーザーIDを指定する必要がありますが、取得するにはLINE BOT用のサーバーを用意して[WebhookからユーザーIDを取得](https://developers.line.biz/ja/docs/messaging-api/getting-user-ids/#get-user-ids-in-webhook)する必要があります。

WebhookはAWS LambdaやAzure Functionsなどのクラウドサービスに用意しても良いですが、ユーザーIDを取得するためだけに使うには少々大袈裟です。
そこで、拡張コントローラを使ってプリザンターにWebhookのエンドポイントを生やしてしまおう！という訳です。

## 事前準備

### LINE Messaging APIを準備する
LINEの公式ドキュメントを参考に、LINE公式アカウントの作成とMessaging APIの利用設定を実施しておきます。

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

https://developers.line.biz/ja/docs/messaging-api/building-bot/

#### Webhook URLの設定

「ボットを作成する」の手順では、[WebhookのURLを指定する](https://developers.line.biz/ja/docs/messaging-api/building-bot/#setting-webhook-url)必要があります。

ここではサービス（プリザンター）のWebhook用に追加したURLを指定しますが、開発時はVisual Studionの[開発トンネル](https://learn.microsoft.com/ja-jp/aspnet/core/test/dev-tunnels?view=aspnetcore-9.0)を使うと便利です。

#### 開発者トンネルの利用
- デバッグ用のプリザンターをVisual Studioで開き、デバッグ実行用のドロップダウンボタン（下図参照）で表示されるメニューから「Dev Tunnels」-「トンネルを作成...」を選択します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/550ee495-2c1e-3583-0064-2258dbde0b2b.png)

- トンネルの種類：永続的、アクセス：公開用 として「OK」をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/bbe7130d-dc62-b9ee-5f8d-d6fb95cddbfe.png)

- デバッグ用のプリザンターをVisual Studioからデバッグ実行すると、下記の様に払い出されたトンネルURLでブラウザが立ち上がります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/a370fbe1-a9b2-0143-7e81-3d6952f353e0.png)

このURLのパスをLINE webhook用のエンドポイント `/webhook/line` に変更したものをLINE DeveloperコンソールのWebhook URLに設定します。

- 例: `https://35t2b8b8-59802.asse.devtunnels.ms/webhook/line`

## サンプルを試してみよう
前回の記事でCloneしてきた[PleasanterWebHook](https://github.com/pleasanter-developer-community/PleasanterWebHook)のプロジェクトをVisual Studioで開きます。

### LINE BOTアカウントのチャンネル情報を設定
プロジェクト内にある`webhooksettings.json`を開き、作成したLINE BOTアカウントのChannelAccessTokenとChannnelSecretを設定します。

```json
{
  "LineChannelSecret": "fda50xxxxxxxxxxxxxxxxxxxxxxxxxx",
  "LineChannelAccessToken": "W66U66Idu5UMZTxxxxxxxxxxxxxxxxxxxx"
}
```
### 拡張コントローラの再配置
- PleasanterWebHookのプロジェクトをビルドします
- ビルドしたモジュールをデバッグ用プリザンター側に再度「公開」します（公開の手順、公開先などの設定は前回の記事と同様です）

### プリザンターの実行
- デバッグ用プリザンターを開発者トンネルを有効にしてデバッグ実行します

### LINE BOTアカウントを友達登録する
LINEアプリがインストールされている端末でLINE BOTアカウントを友達登録します。
1. [LINE Developer コンソール](https://developers.line.biz/console/)でBOTアカウントを開き「Messaging API設定」タブに表示されるQRコードを確認します
1. LINEアプリでQRコードを読み取り、友達登録します

登録が完了後、トークルームに下記のメッセージが送られて来れば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/33957a07-7c5d-5fd2-40ec-90f16ca29b71.png)

### プリザンターから通知を受け取る
受け取ったメッセージに記載されているUserIDを使って、プリザンターから通知を受け取れるようにしてみましょう。

1. 通知元となるテーブルの「テーブルの管理」画面を開き、「通知」タブで「新規作成」をクリックします
1. 下記の設定を行います
    - 通知種別: LINE
    - アドレス：取得したLINEのUser ID
    - トークン: BOTアカウントのチャンネルアクセストークン  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/d305b49d-ae2b-0618-58ca-0a19f161bf87.png)

ここまで出来たら準備完了です。通知の設定をしたテーブルにレコードを作成してみましょう。
下記の様な通知が届いたら成功です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/8fff1c03-a90d-0f68-bc11-84240f2fda32.png)


## 拡張コントローラの実装
拡張コントローラの実装についても少し見てみましょう。

LINEのWebhookの処理には、LINE Messaging APIのC#ライブラリ [LineDC.Messaging](https://www.nuget.org/packages/LineDC.Messaging) を利用しています。

### ソースコード
全体のソースコードは下記をご確認ください。

https://github.com/pleasanter-developer-community/PleasanterWebHook/blob/master/WebHookController.cs

### LineBotAppの生成

`WebHookController`クラスの静的コンストラクタ内でLINE BOTの処理を行うクラス`LineBotApp`を生成しています。
- `webhooksettings.json`ファイルからLINEのチャンネルアクセストークンとチャンネルシークレットの値を取得
- その値を基に、`LineBotApp`のインスタンスを作成し、`WebHookController`クラスの静的フィールドに設定

```cs
private static readonly LineBotApp _app;
static WebHookController()
{
    //webhooksettings.jsonからLINEの設定情報を取得
    var dir = Path.GetDirectoryName(typeof(WebHookController).Assembly.Location)
        ?? AppContext.BaseDirectory;
    var netcoreEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")?.ToLower() ?? "";
    var settingsPath = Path.Combine(dir, "webhooksettings.json");
    var devSettingsPath = Path.Combine(dir, $"webhooksettings.{netcoreEnv}.json");
    var confBuilder = new ConfigurationBuilder();
    if (System.IO.File.Exists(settingsPath))
    {
        confBuilder.AddJsonFile(settingsPath);
    }
    if (System.IO.File.Exists(devSettingsPath))
    {
        confBuilder.AddJsonFile(devSettingsPath);
    }
    var config = confBuilder.Build();
    var settings = config.Get<WebhookSettings>()
        ?? new WebhookSettings();
            
    //設定情報を元に、LINE Messaging APIを処理するLineBotAppクラスを生成
    var lineClient = LineMessagingClient.Create(new HttpClient(), settings.LineChannelAccessToken);
    _app = new LineBotApp(lineClient, settings.LineChannelSecret);
}
```

### コントローラのActionを定義

Webhook用のエンドポイント`/webhook/line`用のメソッドを`WebHookController`内に定義します。

- ここでは、Webhookの処理を実行する `LineBotApp`のメソッド`RunAsync()`を呼び出すだけです
- その際、署名検証用のシグネチャをヘッダから取得して渡します

```cs
//POST: /webhook/line
[HttpPost("line")]
public async Task<IActionResult> Line()
{
    //LINE BOTのイベントを処理する
    var reader = new StreamReader(Request.Body);
    var body = await reader.ReadToEndAsync();
    var xLineSignature = Request.Headers["x-line-signature"];
    await _app.RunAsync(xLineSignature, body);
    return Ok();
}
```

### `LineBotApp`の実装

受け取ったメッセージの処理は`WebhookApplication`クラスを継承した`LineBotApp`に実装しています。

メッセージは発生状況に応じた[Webhookのイベントタイプ](https://developers.line.biz/ja/docs/messaging-api/receiving-messages/#webhook-event-types)に分類されます。`LineBotApp`では、応答を返したいイベントに対応したメソッドをオーバーライドして処理を実装します。
今回は、送信元の情報からユーザーIDやグループIDを取得して、その値を返答する処理となります。

```cs
class LineBotApp : WebhookApplication
{
    public LineBotApp(ILineMessagingClient client, string channelSecret)
        : base(client, channelSecret)
    { }

    //BOTアカウントがチャットルームやグループに追加された場合に実行されるイベント
    protected async override Task OnJoinAsync(JoinEvent ev)
    {
        await ReplyIDAsync(ev);
    }

    //BOTアカウントを友達追加した場合に実行されるイベント
    protected async override Task OnFollowAsync(FollowEvent ev)
    {
        await ReplyIDAsync(ev);
    }

    //ユーザからのメッセージを受信した際に実行されるイベント
    protected override async Task OnMessageAsync(MessageEvent ev)
    {
        await Client.ReplyMessageAsync(ev.ReplyToken, $"あなたのユーザIDは \"{ev.Source.UserId}\" です。");
    }

    //送信元に応じてIDをリプライする関数
    private async Task ReplyIDAsync(ReplyableEvent ev)
    {
        await Client.ReplyMessageAsync(ev.ReplyToken, ev.Source.Type switch
        {
            EventSourceType.Group => $$"""
            ようこそ！このグループのIDはこちらです。
            Group ID: {{ev.Source.Id}}
            """,
            EventSourceType.Room => $$"""
            ようこそ！このトークルームのIDはこちらです。
            Room ID: {{ev.Source.Id}}
            """,
            _ => $$"""
            ようこそ！あなたのIDはこちらです。
            User ID: {{ev.Source.UserId}}
            """
        });
    }
}
```

# おわりに
今回は、LINEのユーザID／グループIDを取得するだけの機能でしたが、プリザンター内のデータと連携したBOTサービスなども作れそうですね。
この記事で取り上げたサンプルコードは[GitHub上で公開](https://github.com/pleasanter-developer-community/PleasanterWebHook)していますので、ぜひ一度Cloneして動かしてみてください！