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();
}
}
}
結果
次はクイックリプライに挑戦。
クイックリプライで返信する
テキスト以外のフォーマットでも返信をしてみます。まずはクイックリプライ。クイックリプライの詳細に関してはこちらを参照。
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();
}
}
}
###結果
画像を返信する
画像のオブジェクトは下記
プロパティ | タイプ | 必須 | 説明 |
---|---|---|---|
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();
}
}
}
###結果
上で表示されているのがサムネールで、下がオリジナル
動画で返信
動画のオブジェクトは下記、画像と一緒ですね。
プロパティ | タイプ | 必須 | 説明 |
---|---|---|---|
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
###結果
静止画だと表現できませんが、下が動画です。
#次
引き続き違う種類の返信タイプを試してみます。