Azure FunctiosでLINE BOTを作成する

Azure

AzureのサーバーレスアーキテクチャであるAzure Functionsを利用し、LINE BOTを作成してみたいと思います。

今回はAzure Portal上で完結できるC#スクリプトを利用し、送ったメッセージをそのまま返信するオウム返しBOTを作成します。

事前準備

LINE BOTで利用するMessaging APIを利用するため、LINE Developersのアカウントを登録します。※登録にはLINEアカウントが必要です。

以下URLにアクセスします。

LINE Developers
LINE Developersサイトは開発者向けのポータルサイトです。LINEのさまざまな開発者向けプロダクトを利用するための、管理ツールやドキュメントを利用できます。LINEログインやMessaging APIを活用して、アプリやサービスをもっと便利に。

トップページが表示されたらログインをクリックします。

LINEアカウントに紐づいているメールアドレスとパスワードでログインします。

開発者名、メールアドレス、規約の同意にチェックを入れ、確認画面へ進むをクリックします。

ログイン直後はプロバイダーが存在しないため、右上にある「新規プロバイダー作成」よりプロバイダーの作成を行います。

プロバイダー名を入力し、確認するボタンをクリックします。

確認画面で作成するをクリックします。

プロバイダーが作成されたらチャネルを作成します。LINE BOTの開発はMessaging APIとなります。

必要なパラメータを入力します。注意が必要なのはプランの部分で、それぞれ制約があります。

  • Developer Trial→Push Messageを利用できるが友達は50人に制限される。
  • フリー→友達人数に制限はないが、Push Messageが利用できない

また、Developer Trialから通常プランへの切り替えはできないので注意が必要です。

ポップアップが表示されるので同意します。

画面下部に規約に関する同意のチェックボックスが表示されるので、チェックを入れ作成をクリックします。

以下の画面が表示されたらチャネルの作成は完了です。クリックするとチャネルの設定ができます。後ほど設定を行います。

アーキテクチャ

Azure Queueストレージを利用して非同期に処理を行います。リクエストを一度Queueに格納することで、大量のメッセージが送られた場合でも、確実に返答することが可能となります。以下の記事を参考にさせていただきました。

大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャ - Qiita
3月24日に発表になったLINEのBOT API Trial Accountが、いよいよ4月7日から実際に試せるようになりました。既に多くのBOTが開発者の手によって作られ始めたようですね。QiitaにもいくつかBOTの作り方が投稿さ...

かなり簡略化していますが、流れの概要は以下の通りです。

LINEから送られてくるメッセージをHTTP Triggerで受け取り、メッセージ内容をQueueに格納します。Queue TriggerはQueueが格納され次第処理が動き、Queue内のメッセージを元にメッセージの返信を行います。

具体的な実装を紹介します。

実装

Azure Functionsリソース作成

Azure ポータル上のリソースの作成をクリックし、Serverless Function Appをクリックします。

Function Appのパラメータを入力し作成をクリックします。OSはWindowsでランタイムスタックを.NETとします。ランタイムスタックをJavaScriptとすると、Node.jsでのコーディングが可能となります。

作成が完了したら、左ブレードのFunction AppからFunctions Appの関数を選択し、新しい関数をクリックします。

テンプレートにHTTP triggerを選択します。

関数名を入力し、作成をクリックします。

自動でrun.csxが作成されました。関数のURLの取得をクリックします。

表示された関数のURLをコピーします。

LINE Developersのチャネル設定

LINE Developersサイトのチャネルの設定を開きます。

Webhook送信を利用するに変更し、Webhook URLに先ほどコピーした関数のURLを設定します。この時、動作確認用に末尾に「&name=test」と追加してください。※デフォルトで作成されるコードの仕様上、nameパラメータがないとBad Requestを返すため

接続確認をクリックし、成功しましたと表示されることを確認します。確認出来たら「&name=test」は削除しても問題ありません。

以下、Function Appで利用するキーを取得しておきます。同じ画面にあるChannel Secretの値を控えてください。

続いてアクセストークンです。はじめは以下の画像になっているかと思います。再発行ボタンをクリックします。

以下ポップアップが表示されたら再発行をクリックします。

表示されたアクセストークンを控えます。

Function Appの設定

続いてFunction App側の設定を行います。

作成したFunctionを選択し、アプリケーション設定をクリックします。

アプリ設定名に「CHANNEL_SECRET」「CHANNEL_ACCESS_TOKEN」を追加し、値に先ほど控えたものを追加します。

Http Triggerの構成

HTTP Triggerを構成します。まずはQueueストレージへのバインディングを追加します。Http Trigger1の統合より、新しい出力をクリックします。

Azure Queue Storageをクリックし、選択をクリックします。

拡張機能のインストールが求められるのでインストールをクリックします。

インストールが完了したらキュー名を「linequeue」にし、ストレージアカウント接続で新規をクリックし、Function App作成時に作成したストレージアカウントを選択します。(表示名がストレージアカウント名と異なりますが問題ありません)

変更したら上部にある保存ボタンをクリックしてください。

続いてHttp Trigger1に以下のソースコードを貼り付け保存をクリックします。

#r "Newtonsoft.Json"

using System.Net;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task<IActionResult> Run(HttpRequest req, 
                                            ICollector<string> outputQueueItem, 
                                            ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    // アプリケーションの設定よりCHANNEL_SECRETの値を取得する
    var channelSecret = System.Environment.GetEnvironmentVariable("CHANNEL_SECRET");
    // log.LogInformation(channelSecret);

    // リクエストの内容を読み込む
    var requestBody = await new StreamReader(req.Body).ReadToEndAsync();

    // リクエストのヘッダーを読み込む
    var xLineSignature = req.Headers["X-Line-Signature"];

    // ChannelSecretキーとリクエストの内容をByte配列に変換
    var key = Encoding.UTF8.GetBytes(channelSecret);
    var body = Encoding.UTF8.GetBytes(requestBody);

    // ChannelSecretをキーにしてHMAC-SHA256でリクエスト内容のダイジェスト値を取得する
    HMACSHA256 hmac = new HMACSHA256(key);
    var hash = hmac.ComputeHash(body, 0, body.Length);

    // ダイジェスト値をBASE64に変換
    var hash64 = Convert.ToBase64String(hash);
    
    // X-LINE-Signatureヘッダとダイジェスト値が一致しない場合BadRequestを返す
    if (xLineSignature != hash64)
    {
       log.LogInformation($"ダイジェスト値が一致しません");
       return new BadRequestObjectResult("Signature verification failed");
    };

    // リクエストの内容をQueueストレージに格納する
    outputQueueItem.Add(requestBody);

    return (ActionResult)new OkObjectResult("");
}

LINE DevelopersのWebhook URLの「&name=test」を削除し接続確認が成功することを確認します。

設定したストレージアカウントをStorage Explorerで確認すると、linequeueにキューが格納されているのが確認できるかと思います。

ソースコードについては細かくコメントを入れてますが、ポイントは2つです。

一つ目は署名を検証し、メッセージがLINEプラットフォームから送信されているか確認することです。正しい署名が付与されているメッセージのみ処理するようにしないと、不正なアプリケーションなどからのメッセージも受け付ける仕様となってしまいます。

以下リファレンスからの引用となります。

X-Line-Signatureリクエストヘッダーに含まれる署名を検証して、リクエストがLINEプラットフォームから送信されたことを確認する必要があります。

検証の手順は以下のとおりです。

チャネルシークレットを秘密鍵として、HMAC-SHA256アルゴリズムを使用してリクエストボディのダイジェスト値を取得します。
ダイジェスト値をBase64エンコードした値とリクエストヘッダーにある署名が一致することを確認します。

引用元:Messaging APIリファレンス

2つ目は可能な限り余計な処理を入れないことです。リクエストが来たら、署名の検証のみ行い、問題なければQueueに格納してさっさとStatus 200を返すことです。ここがLINEメッセージを受け付けるエントリポイントとなるため、過負荷になると処理が上手く回らない可能性が出てきます。

Queue Triggerの構成

HTTP Triggerで追加されたQueueを検知し、処理を実行するQueue Triggerを構成します。新しい関数をクリックします。

Azure Queue Storage triggerをクリックします。

キュー名をHTTP Triggerのバインディングで設定したキュー名・ストレージアカウント接続に変更し、作成をクリックします。

Queueトリガーが作成されました。

以下のソースコードを貼り付けて保存をクリックします。※エラーハンドリング等一切していません。。テキスト以外のメッセージが来るとエラーが返ります。

#r "Newtonsoft.Json"

using System;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

public static async void Run(string myQueueItem, ILogger log)
{
    //log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");

    // Queueをデシリアライズし変数へ格納
    string jsonContent = myQueueItem;
    dynamic data = JsonConvert.DeserializeObject(jsonContent);
    var result = data.events[0];

    // リプライトークンとチャンネルアクセストークンを変数へ格納
    var repToken = result.replyToken;
    var channelAccessToken = System.Environment.GetEnvironmentVariable("CHANNEL_ACCESS_TOKEN");

    // リプライメッセージ作成
    ReplyMessage rm = new ReplyMessage
    {
        replyToken = repToken,
        messages = new List<Message>()
        {
            new Message(){
                type="text",
                // 送られてきたメッセージそのままtextへ格納
                text=result.message.text
            }
        }
    };

    // リプライメッセージをシリアライズ
    string json = JsonConvert.SerializeObject(rm, Formatting.Indented);

    // Line Messaging APIへのリクエストを作成する
    var apiUrl = "https://api.line.me/v2/bot/message/reply";
    var req = new HttpRequestMessage(HttpMethod.Post, apiUrl);
    
    // ヘッダーにチャンネルアクセストークンを追加する
    req.Headers.Add(@"Authorization", @"Bearer {" + channelAccessToken + "}");

    // リクエストをJSON形式にシリアライズする
    req.Content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json");
    
    // リクエストを送信する
    using (var client = new HttpClient())
    {
        var response = await client.SendAsync(req);
        log.LogInformation($"{response}");
    }
}

public class ReplyMessage
{
    public string replyToken { get; set; }
    public List<Message> messages { get; set; }
}

public class Message
{
    public string type { get; set; }
    public string text { get; set; }
}

リプライメッセージには以下の内容を含める必要があります。

  • チャンネルアクセストークン(Header)
  • ReplyToken(Body)

チャンネルアクセストークンは環境変数から、ReplyTokenはメッセージ内より取得します。その他のメッセージ仕様についてはリファレンスを確認してください。

LINE Developers

動作確認

LINE Developersのチャネル設定内のQRコードをLINEで読み取ります。

友達追加画面になるので追加をします。

トーク画面に移行します。メッセージを送ってみます。自動応答がありますが、その下に送ったメッセージがそのまま返信されてくるかと思います。

友達追加時のメッセージや自動応答メッセージはLINE Developersのチャネル設定内で変更可能です。

長くなりましたが、Azure Functionsを利用したLINE BOTの紹介でした。

今回はテキストのオウム返しのみですが、様々なメッセージを送ることもできます。いずれCognitive Servicesと連携してAIを利用した画像判定等にチャレンジしたいと思います。

注)C# 初学者のため、記法に誤りがある可能性があります。コメント等でご指摘いただけると大変うれしいです。(C#むずかしい・・)

コメント