第12 屆iT邦幫忙鐵人賽系列文章 (Day4)
本篇開始我們要來逐步實踐我們的婚禮 Chatbot 了!
我們預期在加入好友的時候 (OnFollow) 事件時回傳歡迎詞:Hi UserName, 感謝您加入 Kyle 的婚禮小助理!
記得上一篇講 Webhook Event 時的結構嗎? 裡面有個 userId
查看文件後,可以透過 access token 和 userId 來取得用戶資訊
我們建立一個 Class 來實作取得使用者相關程式,讓未來不同的情境可以調用,以下是文件取得使用者範例的Sample
curl -v -X GET https://api.line.me/v2/bot/profile/{userId} \
-H 'Authorization: Bearer {channel access token}'
有個小技巧可以把 curl 轉成 C# 的 HttpClinet ,將以上的資訊貼到 https://curl.olsh.me/ 即可轉換完成,再做一些微調就好了
轉換後如下:
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("GET"), "[https://api.line.me/v2/bot/profile/{userId](https://api.line.me/v2/bot/profile/{userId)}"))
{
request.Headers.TryAddWithoutValidation("Authorization", "Bearer {channel access token}");
var response = await httpClient.SendAsync(request);
}
}
access token 在幾天前的文章,我們已經定義在 appsetting.json 了,在建構的時候我們注入進來
新增一個 GetUserProfile function,實作如下
public async Task<UserProfile> GetUserProfile(string userId)
{
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("GET"), $"{lineMessageApiBaseUrl}/{userId}"))
{
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
var response = await httpClient.SendAsync(request);
if(response.StatusCode != HttpStatusCode.OK)
{
// 這邊未來應該要 log 起來
throw new Exception("get_profile_error");
}
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<UserProfile>(result);
}
}
}
UserProfile.cs 定義如下
public class UserProfile
{
public string userId { get; set; }
public string displayName { get; set; }
public string pictureUrl { get; set; }
public string statusMessage { get; set; }
}
JSON.NET 轉換
在 .NET Core 3.1 其實有內建 System.Text.Json 來做JSON的序列化跟反序列化處理,但個人還是習慣用 JSON.NET
JsonConvert.DeserializeObject<UserProfile>(result)
您可在專案以下打開 Nuget 套件管理
安裝 JSON.NET 回來
在 OnFollow 事件取得使用者資訊
我們建立一個 Class 來實作回覆使用者相關程式,以下是官方文件回覆使用者範例的Sample
curl -v -X POST https://api.line.me/v2/bot/message/reply \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-d '{
"replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
"messages":[
{
"type":"text",
"text":"Hello, user"
},
{
"type":"text",
"text":"May I help you?"
}
]
}'
用 https://curl.olsh.me/ 轉成 C#
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://api.line.me/v2/bot/message/reply"))
{
request.Headers.TryAddWithoutValidation("Authorization", "Bearer {channel access token}");
request.Content = new StringContent("{\n \"replyToken\":\"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA\",\n \"messages\":[\n {\n \"type\":\"text\",\n \"text\":\"Hello, user\"\n },\n {\n \"type\":\"text\",\n \"text\":\"May I help you?\"\n }\n ]\n}");
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var response = await httpClient.SendAsync(request);
}
}
做點調整:將 StringContent 變成class,並用 JSON.NET序列化,完整實作如下:
private readonly string accessToken;
private static string lineMessageApiBaseUrl = "[https://api.line.me/v2/bot/message/reply](https://api.line.me/v2/bot/message/reply)";
public LineReplyMessageUtility(IOptions<LineSetting> lineSetting)
{
accessToken = lineSetting.Value.ChannelAccessToken;
}
public async Task ReplyMessageAsync(string replyToken, params string[] messages)
{
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("POST"), $"{lineMessageApiBaseUrl}"))
{
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
LineMessageReq req = new LineMessageReq();
req.ReplyToken = replyToken;
foreach (var message in messages)
{
req.Messages.Add(new TextMessage()
{
Text = message
});
}
var postJson = JsonConvert.SerializeObject(req, new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy() //轉小寫
},
Formatting = Formatting.Indented
});
request.Content = new StringContent(postJson);
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var response = await httpClient.SendAsync(request);
}
}
}
LineMessageReq
這邊定義一個 IMessage 的介面,之後不同的訊息類型,將會以此來繼承
public class LineMessageReq
{
public string ReplyToken { get; set; }
public List<IMessage> Messages { get; set; } = new List<IMessage>();
}
TextMessage Class
public class TextMessage : IMessage
{
public string Text { get; set; }
public LineMessageType Type => LineMessageType.text;
}
於 OnFollowAsync 加入 Reply
protected virtual async Task OnFollowAsync(Event ev)
{
// 取得使用者的資訊
var user = await lineProfileUtility.GetUserProfile(ev.source.userId);
// 回傳歡迎詞
await lineMessageUtility.ReplyMessageAsync(ev.replyToken, $@"Hi {user.displayName}, 感謝您加入婚禮小助理!")
}
如何測試加入好友事件呢? 其實只要封鎖再解除封鎖就好了
實作效果
如何取得 Line 使用者資訊
https://developers.line.biz/en/docs/social-api/getting-user-profiles/
如何回覆 Line 使用者訊息
https://developers.line.biz/en/reference/messaging-api/#send-reply-message
如何把 curl 轉成 C# HttpClient
https://curl.olsh.me/
C# 的 介面
interface (C# Reference)
JSON.NET
https://www.newtonsoft.com/json
本篇文章同步發佈於我的 Medium 如果這篇文章對你有幫助,就大力追蹤和拍手鼓掌下去吧 !
請問大大為什麼我在LineMessageReq的class裡面 IMessage會報錯
The type or namespace name 'IMessage' could not be found (are you missing a using directive or an assembly reference?)