3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[LINE] For C#er (ASP.NET Core), Line Botの作成 - Part 2 - メッセージの返信

Last updated at Posted at 2019-09-14

LINEからデータ(テキスト)を受取る方法は前回の記事、「For C#er (ASP.NET Core), Line Botの作成 - Part 1」をご参照ください。

ではでは、今日はLINEで受信したテキストに対して返信する仕組みを書きます。

メッセージの返信

次はユーザーからきたメッセージに対して返信をします。シンプルにテキストを返すケースもあればよりリッチなコンテンツを返すケースもあります。まずはテキストの返信から。

ユーザーに返信したい場合は、下記のエンドポイントに対してPOSTリクエストを送信します。

POST https://api.line.me/v2/bot/message/reply

ヘッダーは下記のとおりです。

リクエストヘッダー 説明
Content-Type application/json
Authorization Bearer {channel access token}

ボディーは下記のとおりです。

プロパティ タイプ 必須 説明
replyToken String 必須 Webhookで受信する応答トークン
messages メッセージオブジェクトの配列 必須 送信するメッセージ、最大件数:5
notificationDisabled Boolean 任意 true or false、デフォルト値はfalseです。
  • notificationDisabled
  • true:メッセージ送信時に、ユーザーに通知されない。
  • false: メッセージ送信時に、ユーザーに通知される。ただし、LINEで通知をオフにしている場合は通知されません。
    public class Message
    {
        public string id { get; set; }
        public string type { get; set; }
        public string text { get; set; }
    }

    public class LineTextReplyObject
    {
        public string replyToken { get; set; }
        public List<Message> messages { get; set; }
        public bool notificationDisabled { get; set; }
    }

##メッセージオブジェクトの種類

返信メッセージは下記のタイプがあります

  • テキストメッセージ
  • 画像メッセージ
  • 動画メッセージ
  • 音声メッセージ
  • 位置情報メッセージ
  • スタンプメッセージ
  • イメージマップメッセージ
  • テンプレートメッセージ
  • Flex Message

そしてすべてのメッセージに共通してクイックリプライを付随することができます。 今日はテキストメッセージ、画像メッセージ、動画メッセージ、クイックリプライを使ってみます。

では早速テキストから、

テキストメッセージで返信する

まずは、受け取ったメッセージをそのまま返すという、意味のない機能を実装してみます。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using LineApiApp.Models;
using System.Text;
using System.Security.Cryptography;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;

namespace LineApiApp
{
    public class LineReply
    {

        private const string _secret = "";
        private const string _token = "";
        private readonly HttpClient _httpClient;

        public LineReply()
        {
            _httpClient = new HttpClient();
        }

        [FunctionName("ReplyToMessage")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            // Get headear value (signature)
            req.Headers.TryGetValue("X-Line-Signature", out var xlinesignature);

            // Get body
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            // Check for signature
            if (IsSingatureOk(xlinesignature, requestBody, _secret))
            {

                // Deserialize
                var data = JsonConvert.DeserializeObject<LineWebhookMessageObject>(requestBody);

                // Log
                log.LogInformation("Type is : " + data.events[0].type);

                // Let's make sure I am responding to text msg only 
                if (data.events[0].type == "message")
                {
                    // Log
                    log.LogInformation("M/D is : " + data.events[0].message.text + " / " + data.destination);

                    // Reply
                    await SendTextReplyAsync(data.events[0].replyToken, data.events[0].message.text);

                    // OK!
                    return new OkResult();
                }

                // Log
                log.LogInformation("Data type was not appropriate");

                // I am not yet able to handle your request
                return new BadRequestResult();
            }

            // Log
            log.LogInformation("Signature verification fail");

            // Uh.. NOT OK
            return new BadRequestResult();

        }

        /// <summary>
        /// Return if signature matches
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="text"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private bool IsSingatureOk(string signature, string text, string key)
        {

            var textBytes = Encoding.UTF8.GetBytes(text);
            var keyBytes = Encoding.UTF8.GetBytes(key);

            using (HMACSHA256 hmac = new HMACSHA256(keyBytes))
            {
                var hash = hmac.ComputeHash(textBytes, 0, textBytes.Length);
                var hash64 = Convert.ToBase64String(hash);

                return signature == hash64;
            }
        }

        /// <summary>
        /// Send text back
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        private async Task SendTextReplyAsync(string replyToken, string message)
        {
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);

            var response = await _httpClient.PostAsJsonAsync<LineTextReplyObject>("https://api.line.me/v2/bot/message/reply", new LineTextReplyObject()
            {
                replyToken = replyToken,
                messages = new List<Message>()
                {
                    new Message(){
                        type = "text",
                        text = message
                    } 
                }
            });

            response.EnsureSuccessStatusCode();
        }
    }
}

結果

image.png

次はクイックリプライに挑戦。

クイックリプライで返信する

テキスト以外のフォーマットでも返信をしてみます。まずはクイックリプライ。クイックリプライの詳細に関してはこちらを参照。
https://developers.line.biz/ja/docs/messaging-api/using-quick-reply/

クイックリプライの定義はこちら

プロパティ タイプ 必須 説明
quickReply Object 任意 itemsオブジェクト

Itemsオブジェクト

プロパティ タイプ 必須 説明
items Objectの配列 必須 クイックリプライボタンオブジェクト。最大オブジェクト数:13

クイックリプライボタンオブジェクト

プロパティ タイプ 必須 説明
imageUrl String 任意 ボタンの先頭に表示するアイコンのURL、最大文字数:1000、URLスキーム:https、画像フォーマット:PNG、アスペクト比:1:1、最大データサイズ:1MB、画像サイズに制限はありません。、actionプロパティに指定するアクションがカメラアクション、カメラロールアクション、または位置情報アクションで、imageUrlプロパティが未指定の場合、デフォルトのアイコンが表示されます。
action Object 必須 タップされたときのアクション。アクションオブジェクトを指定します。

クイックリプライではチャット画面下部分に小さなアイコン(ボタン)が表示されます、
ボタンをクリックしたときのアクションに、

  • ポストバックアクション
  • メッセージアクション
  • 日時選択アクション
  • カメラアクション
  • カメラロールアクション
  • 位置情報アクション

のいずれかを指定することができます。

全種類表示してみます。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using LineApiApp.Models;
using System.Text;
using System.Security.Cryptography;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;

namespace LineApiApp
{
    public class LineReply
    {

        private const string _secret = "";
        private const string _token = "";
        private readonly HttpClient _httpClient;

        public LineReply()
        {
            _httpClient = new HttpClient();
        }

        [FunctionName("ReplyToMessage")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            // Get headear value (signature)
            req.Headers.TryGetValue("X-Line-Signature", out var xlinesignature);

            // Get body
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            // Check for signature
            if (IsSingatureOk(xlinesignature, requestBody, _secret))
            {

                // Deserialize
                var data = JsonConvert.DeserializeObject<LineWebhookMessageObject>(requestBody);

                // Log
                log.LogInformation("Type is : " + data.events[0].type);

                // Let's make sure I am responding to text msg only 
                if (data.events[0].type == "message")
                {

                    // Reply
                    await SendQuickReply(data.events[0].replyToken);

                    // OK!
                    return new OkResult();
                }

                // Log
                log.LogInformation("Data type was not appropriate");

                // I am not yet able to handle your request
                return new BadRequestResult();
            }

            // Log
            log.LogInformation("Signature verification fail");

            // Uh.. NOT OK
            return new BadRequestResult();

        }

        /// <summary>
        /// Return if signature matches
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="text"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private bool IsSingatureOk(string signature, string text, string key)
        {

            var textBytes = Encoding.UTF8.GetBytes(text);
            var keyBytes = Encoding.UTF8.GetBytes(key);

            using (HMACSHA256 hmac = new HMACSHA256(keyBytes))
            {
                var hash = hmac.ComputeHash(textBytes, 0, textBytes.Length);
                var hash64 = Convert.ToBase64String(hash);

                return signature == hash64;
            }
        }

        /// <summary>
        /// Sebd quick reply
        /// </summary>
        /// <param name="replyToken"></param>
        /// <returns></returns>
        private async Task SendQuickReply(string replyToken)
        {
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);

            var response = await _httpClient.PostAsJsonAsync<LineTextReplyObject>("https://api.line.me/v2/bot/message/reply", new LineTextReplyObject()
            {
                replyToken = replyToken,
                messages = new List<Message>()
                {
                    new Message()
                    {
                        type="text",
                        text = "クイックリプライ",
                        quickReply = new QuickReplyItems(){
                            items = new List<QuickReplyItem>()
                            {
                                new QuickReplyItem(){
                                    type = "action",
                                    action = new QuickReplyAction()
                                    {
                                        type = "postback",
                                        label = "購入",
                                        data = "action=buy",
                                        displayText = "購入をタップ"
                                    }
                                },
                                new QuickReplyItem()
                                {
                                    type = "action",
                                    action = new QuickReplyAction()
                                    {
                                        type = "message",
                                        label= "メッセージ",
                                        text= "メッセージをタップ"
                                    }
                                },
                                new QuickReplyItem(){
                                    type = "action",
                                    action = new QuickReplyAction()
                                    {
                                        type = "datetimepicker",
                                        label= "日付を選択",
                                        data = "date=date",
                                        mode = "datetime",
                                        initial = "2019-09-09t00:00",
                                        max = "2020-02-01t23:59",
                                        min = "2019-09-09t00:00"
                                    }
                                },
                                new QuickReplyItem(){
                                    type = "action",
                                    action = new QuickReplyAction()
                                    {
                                        type = "camera",
                                        label = "カメラ"
                                    }
                                },
                                new QuickReplyItem(){
                                    type = "action",
                                    action = new QuickReplyAction()
                                    {
                                        type = "cameraRoll",
                                        label = "カメラロール"
                                    }
                                },
                                new QuickReplyItem(){
                                    type = "action",
                                    action = new QuickReplyAction()
                                    {
                                        type = "location",
                                        label = "ロケーション"
                                    }
                                }
                            }
                        }
                    }
                }
            });

            response.EnsureSuccessStatusCode();
        }
    }
}

###結果

image.png

image.png

image.png

画像を返信する

画像のオブジェクトは下記

プロパティ タイプ 必須 説明
type String 必須 image
originalContentUrl String 必須 画像のURL(最大文字数:1000)、HTTPS、JPEG、最大画像サイズ:4096×4096、最大ファイルサイズ:1MB
previewImageUrl String 必須 プレビュー画像のURL(最大文字数:1000)、HTTPS、JPEG、最大画像サイズ:240×240、最大ファイルサイズ:1MB

オリジナル画像とプレビュー画像の二つを準備します。


using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using LineApiApp.Models;
using System.Text;
using System.Security.Cryptography;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;

namespace LineApiApp
{
    public class LineReply
    {

        private const string _secret = "";
        private const string _token = "";
        private readonly HttpClient _httpClient;

        public LineReply()
        {
            _httpClient = new HttpClient();
        }

        [FunctionName("ReplyToMessage")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            // Get headear value (signature)
            req.Headers.TryGetValue("X-Line-Signature", out var xlinesignature);

            // Get body
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            // Check for signature
            if (IsSingatureOk(xlinesignature, requestBody, _secret))
            {

                // Deserialize
                var data = JsonConvert.DeserializeObject<LineWebhookMessageObject>(requestBody);

                // Log
                log.LogInformation("Type is : " + data.events[0].type);

                // Let's make sure I am responding to text msg only 
                if (data.events[0].type == "message")
                {

                    // Reply
                    await SendImageReply(data.events[0].replyToken);

                    // OK!
                    return new OkResult();
                }

                // Log
                log.LogInformation("Data type was not appropriate");

                // I am not yet able to handle your request
                return new BadRequestResult();
            }

            // Log
            log.LogInformation("Signature verification fail");

            // Uh.. NOT OK
            return new BadRequestResult();

        }

        /// <summary>
        /// Return if signature matches
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="text"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private bool IsSingatureOk(string signature, string text, string key)
        {

            var textBytes = Encoding.UTF8.GetBytes(text);
            var keyBytes = Encoding.UTF8.GetBytes(key);

            using (HMACSHA256 hmac = new HMACSHA256(keyBytes))
            {
                var hash = hmac.ComputeHash(textBytes, 0, textBytes.Length);
                var hash64 = Convert.ToBase64String(hash);

                return signature == hash64;
            }
        }

        /// <summary>
        /// Send image reply
        /// </summary>
        /// <param name="replyToken"></param>
        /// <returns></returns>
        private async Task SendImageReply(string replyToken)
        {
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);

            var response = await _httpClient.PostAsJsonAsync<LineTextReplyObject>("https://api.line.me/v2/bot/message/reply", new LineTextReplyObject()
            {
                replyToken = replyToken,
                messages = new List<Message>()
                {
                    new Message()
                    {
                        type="image",
                        originalContentUrl = "https://sqlvaidccykhi26mlc.blob.core.windows.net/misc-sho-junkbox/handstand-scropion-big.jpg",
                        previewImageUrl = "https://sqlvaidccykhi26mlc.blob.core.windows.net/misc-sho-junkbox/handstand-scropion-small.jpg"
                    }
                }
            });

            response.EnsureSuccessStatusCode();
        }

    }


}

###結果

image.png

上で表示されているのがサムネールで、下がオリジナル

image.png

動画で返信

動画のオブジェクトは下記、画像と一緒ですね。

プロパティ タイプ 必須 説明
type String 必須 video
originalContentUrl String 必須 動画ファイルのURL(最大文字数:1000)、HTTPS、mp4、最大長:1分、最大ファイルサイズ:10MB、一定以上に縦長・横長の動画を送信した場合、一部の環境では動画の一部が欠けて表示される場合があります。
previewImageUrl String 必須 プレビュー画像のURL(最大文字数:1000)、HTTPS、JPEG、最大画像サイズ:240×240、最大ファイルサイズ:1MB

240 x 240 のjpgイメージと10MB以下のmp4ファイルを用意してサーバーにあげておきます。


using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using LineApiApp.Models;
using System.Text;
using System.Security.Cryptography;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;

namespace LineApiApp
{
    public class LineReply
    {

        private const string _secret = "";
        private const string _token = "";
        private readonly HttpClient _httpClient;

        public LineReply()
        {
            _httpClient = new HttpClient();
        }

        [FunctionName("ReplyToMessage")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            // Get headear value (signature)
            req.Headers.TryGetValue("X-Line-Signature", out var xlinesignature);

            // Get body
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            // Check for signature
            if (IsSingatureOk(xlinesignature, requestBody, _secret))
            {

                // Deserialize
                var data = JsonConvert.DeserializeObject<LineWebhookMessageObject>(requestBody);

                // Log
                log.LogInformation("Type is : " + data.events[0].type);

                // Let's make sure I am responding to text msg only 
                if (data.events[0].type == "message")
                {

                    // Reply
                    await SendVideoReply(data.events[0].replyToken);

                    // OK!
                    return new OkResult();
                }

                // Log
                log.LogInformation("Data type was not appropriate");

                // I am not yet able to handle your request
                return new BadRequestResult();
            }

            // Log
            log.LogInformation("Signature verification fail");

            // Uh.. NOT OK
            return new BadRequestResult();

        }

        /// <summary>
        /// Return if signature matches
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="text"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private bool IsSingatureOk(string signature, string text, string key)
        {

            var textBytes = Encoding.UTF8.GetBytes(text);
            var keyBytes = Encoding.UTF8.GetBytes(key);

            using (HMACSHA256 hmac = new HMACSHA256(keyBytes))
            {
                var hash = hmac.ComputeHash(textBytes, 0, textBytes.Length);
                var hash64 = Convert.ToBase64String(hash);

                return signature == hash64;
            }
        }

        /// <summary>
        /// Send video reply
        /// </summary>
        /// <param name="replyToken"></param>
        /// <returns></returns>
        private async Task SendVideoReply(string replyToken)
        {
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);

            var response = await _httpClient.PostAsJsonAsync<LineTextReplyObject>("https://api.line.me/v2/bot/message/reply", new LineTextReplyObject()
            {
                replyToken = replyToken,
                messages = new List<Message>()
                {
                    new Message()
                    {
                        type="video",
                        originalContentUrl = "https://sqlvaidccykhi26mlc.blob.core.windows.net/misc-sho-junkbox/coverr-kiev-architecture-1566805889945.mp4",
                        previewImageUrl = "https://sqlvaidccykhi26mlc.blob.core.windows.net/misc-sho-junkbox/coast-preview.jpg"
                    }
                }
            });

            response.EnsureSuccessStatusCode();
        }
    }
}

返信に関する参考ドキュメント
https://developers.line.biz/ja/reference/messaging-api/#send-reply-message

###結果

image.png

静止画だと表現できませんが、下が動画です。

image.png

#次

引き続き違う種類の返信タイプを試してみます。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?