[Day 5] 讓 C# 也可以很 Social -.NET 6 C# 與 Line Services API 開發 — Webhook events 介紹(一)

APPX
appxtech

--

Hihi 大家好,上一篇我們建立了 line bot 的 webhook url 連線,這一篇將會介紹 Line 提供的 webhook events 並建立接收對應行為~。

<<回顧上一篇<<

今天文章的內容,主要參考的來源當然是 官方文件,考量文章的長度,這邊就以常見的幾種事件(event)來做說明,如果各位發現有其它需求,一樣也可以從官方文件找到相關的資訊及範例。

本篇大綱:

一、
Event Class 宣告-Webhook Request Body

二、Webhook event
1.
Message Event
2.
Unsend event
3.
Follow event
4.
Unfollow event
5.
Join event
6.
Leave event

三、
完整的 WebhookEventsDto.cs
四、
處理事件接收
五、
結論/腦筋急轉彎/範例程式碼

Event Class 宣告

Webhook Request Body

  • 從文件中,我們看到官方列出在傳送request的 資料結構/欄位說明,代表如果 LINE bot 平台如果收到訊息時,就會依照 webhook url的設定,將相關參數轉呼叫到我們手上的 API (handler)。因此我們會需要建立C# class的結構,透過 Controller API的機制(binding)來接收request裏相關的欄位資訊(參數值)。
  • 建立 Dtos/Webhook 資料夾並新增類別 WebhookEventsDto.cs
  • 並且宣告新的 class WebhookEventDto 然後將文件中的 common property 加入至class裏面,如 Type, Mode, TimeStamp 等等。後續就可以用物件裏面的屬性來直接對應到使用者輸入的資料(參數)。(*為了方便大家閱讀及一次呈現,這裏將其它常見的其它屬性也一併納入 如 SourceDto, DeliverycontextDto 類別等)
namespace LineBotMessage.Dtos
{
public class WebhookEventDto
{
// -------- 以下 common property --------
public string Type { get; set; } // 事件類型
public string Mode { get; set; } // Channel state : active | standby
public long Timestamp { get; set; } // 事件發生時間 : event occurred time in milliseconds
public SourceDto Source { get; set; } // 事件來源 : user | group chat | multi-person chat
public string WebhookEventId { get; set; } // webhook event id - ULID format
public DeliverycontextDto DeliveryContext { get; set; } // 是否為重新傳送之事件 DeliveryContext.IsRedelivery : true | false
}


// -------- 以下 common property --------

public class SourceDto
{
public string Type { get; set; }
public string? UserId { get; set; }
public string? GroupId { get; set; }
public string? RoomId { get; set; }
}

public class DeliverycontextDto
{
public bool IsRedelivery { get; set; }

}
}
  • 新增 WebhookRequestBodyDto.cs
    上述的WebhookEventDto 類別,主要是定義出 對應到 各種情境的參數模型,這樣子我們寫的程式才好接受從 LINE 傳過來的各種資訊/參數。
    而現在新增的 WebhookRequestBodyDto ,從官方文件提供的範例得知,它是對應到LINE訊息的完整結構,同時也是最外層的結構,如下圖

因此我們這邊才會定義出 WebhookRequestBodyDto 類別,並在的宣告使用 Destination 字串,以及 WebhookEventDto的串列(List)。

這裏的對應(Mapping)觀念很重要!!!! 很重要!!! 很重要!!! 關係著要看懂官方文件裏的JSON範例,以及理解 C# 是如何將JSON結構 對應 成物件結構

程式碼如下,方便參考

namespace LineBotMessage.Dtos
{
public class WebhookRequestBodyDto
{
public string? Destination { get; set; }
public List<WebhookEventDto> Events { get; set; }
}
}

目前,新增的資料夾及檔案 如下圖,

一、Message Event

使用者向 Line Bot 傳送以下 7 種訊息時會產生 Message Event ,Line 再透過上一篇建立好的 webhook url 將其傳送到伺服器。

  • Message event 文件內容,需要宣告的屬性有 ReplyToken & Message,而 Message 分成 7 種 object,我們來一個一個帶過並宣告
  • 不過建議搭配官方文件 一起服用,在對其定義格式了解後 再跟著我一起宣告

1. Text (文字)

宣告 class

  • 下圖是收到 Text message 時 Line 會傳送過來的 json 格式,文字訊息的內容包含了文字、emoji、還有 Line 在群組中的 Tag 訊息。
  • 在 WebhookEventsDto.cs 內宣告以下幾個新的 Class 。
public class MessageEventDto
{
public string Id { get; set; }
public string Type { get; set; }

// Text Message Event
public string? Text { get; set; }
public List<TextMessageEventEmojiDto>? Emojis { get; set; }
public TextMessageEventMentionDto? Mention { get; set; }
}

public class TextMessageEventEmojiDto
{
public int Index { get; set; }
public int Length { get; set; }
public string ProductId { get; set; }
public string EmojiId { get; set; }
}

public class TextMessageEventEmojiDto
{
public int Index { get; set; }
public int Length { get; set; }
public string ProductId { get; set; }
public string EmojiId { get; set; }
}

public class TextMessageEventMentionDto
{
public List<TextMessageEventMentioneeDto> Mentionees { get; set; }
}

public class TextMessageEventMentioneeDto
{
public int Index { get; set; }
public int Length { get; set; }
public string UserId { get; set; }
}
}
  • WebhookEventDto 中宣告新的
public string? ReplyToken { get; set; } // 回覆此事件所使用的 token
public MessageEventDto? Message { get; set; } // 收到訊息的事件,可收到 text、sticker、image、file、video、audio、location 訊息

測試

  • 訊息內容

2. Image (圖片)

  • Image Message Event Json 格式,一起宣告前也記得詳讀官方文件
  • 在 WebhookEventsDto.cs 下宣告以下幾個新的 Class。
public class ContentProviderDto
{
public string Type { get; set; }
public string? OriginalContentUrl { get; set; }
public string? PreviewImageUrl { get; set; }
}

public class ImageMessageEventImageSetDto
{
public string Id { get; set; }
public string Index { get; set; }
public string Total { get; set; }
}
  • MessageEventDto 中宣告新變數
public ContentProviderDto? ContentProvider { get; set; }
public ImageMessageEventImageSetDto? ImageSet { get; set; }

測試

  • 訊息內容

3. Video (影片)

  • Video Message Event Json 格式,一起宣告前也記得詳讀官方文件
  • 觀察過後可以發現,Video 與剛宣告完的 Image 比對後只多了 duration 的屬性,其中 ContentProvider 格式都一樣,所以我們只需要在 MessageEventDto 中新宣告 Duration 變數即可。
public int? Duration { get; set; } // 影片 or 音檔時長(單位:豪秒)

測試

  • 訊息內容

4. Audio (音源)

  • 會先看官方文件的你應該發現了,Audio 用到的屬性也跟宣告過的 Video 重複了,所以共用同一個就好了,不用再宣告任何東西~

測試

  • 訊息內容

5. File (檔案)

  • File 只需要多宣告兩個屬性即可 !
  • MessageEventDto 中宣告新變數
//File Message Event
public string? FileName { get; set; }
public int? FileSize { get; set; }

測試

  • 訊息內容

6. Location (位置)

  • Location 也不用宣吿 Class,只需要宣告 4 個變數。
  • MessageEventDto 中宣告新變數
//Location Message Event
public string? Title { get; set; }
public string? Address { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }

測試

  • 訊息內容

7. Sticker (貼圖)

  • Sticker 也不用宣告新 Class,只需要宣告 4 個變數因為其中一個 Text 變數跟 Text message event 共用到了。
  • MessageEventDto 中宣告新變數
//Location Message Event
public string? Title { get; set; }
public string? Address { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }

測試

  • 訊息內容

二、Unsend Event

  • 當機器人與使用者在同一個聊天室而使用者收回了他先前傳送的訊息,則Unsend event 會被觸發。
  • 新增以下 class 放進 WebhookEventsDto.cs 中。
public class UnsendEventObjectDto
{
public string messageId { get; set; }
}
  • 在 WebhookEventDto 中宣告新的變數。
public UnsendEventDto? Unsend { get; set; } //使用者“收回”訊息事件
}

測試

  • 收回訊息無法在與 Line Bot 的一對一聊天室使用,只可在群組中使用。
  • 訊息內容

三、Follow Event

  • 有使用者將機器人加為好友/解除封鎖時會觸發 Follow event。
  • 這個 evnet 所有用到的屬性都已經宣告過了~所以不用新宣告任何東西。

測試

  • 因為加入好友的動作無法重現,所以用解除封鎖來替代一樣可以接收到事件

四、Unfollow Event

  • 使用者將機器人封鎖時會觸發 Unfollow event。
  • 除了 common property 之外沒有多的 property,所以不用宣告任何東西~

測試

  • 封鎖

五、Join Event

  • 當機器人被邀請至 群組聊天室或是多人聊天室時會觸發 Join event。
  • 屬性都宣告過了~所以也不用多做什麼~

測試

  • 加入群組

六、Leave Event

  • 當機器人被從 群組聊天室或是多人聊天室踢出時會觸發 Leave event。
  • 除了 common property 之外沒有多的 property,所以不用宣告任何東西~

測試

  • 離開群組

完整的 WebhookEventsDto.cs

namespace LineBotMessage.Dtos
{

public class WebhookEventDto
{
// -------- 以下 common property --------
public string Type { get; set; } // 事件類型
public string Mode { get; set; } // Channel state : active | standby
public long Timestamp { get; set; } // 事件發生時間 : event occurred time in milliseconds
public SourceDto Source { get; set; } // 事件來源 : user | group chat | multi-person chat
public string WebhookEventId { get; set; } // webhook event id - ULID format
public DeliverycontextDto DeliveryContext { get; set; } // 是否為重新傳送之事件 DeliveryContext.IsRedelivery : true | false
// -------- 以下 event properties--------
public string? ReplyToken { get; set; } // 回覆此事件所使用的 token
public MessageEventDto? Message { get; set; } // 收到訊息的事件,可收到 text、sticker、image、file、video、audio、location 訊息
public UnsendEventDto? Unsend { get; set; } //使用者“收回”訊息事件
}


// -------- 以下 common property --------

public class SourceDto
{
public string Type { get; set; }
public string? UserId { get; set; }
public string? GroupId { get; set; }
public string? RoomId { get; set; }
}

public class DeliverycontextDto
{
public bool IsRedelivery { get; set; }

}


// -------- 以下 message event --------

public class MessageEventDto
{
public string Id { get; set; }
public string Type { get; set; }

// Text Message Event
public string? Text { get; set; }
public List<TextMessageEventEmojiDto>? Emojis { get; set; }
public TextMessageEventMentionDto? Mention { get; set; }

// Image & Video & Audio Message Event
public ContentProviderDto? ContentProvider { get; set; }
public ImageMessageEventImageSetDto? ImageSet { get; set; }
public int? Duration { get; set; }

//File Message Event
public string? FileName { get; set; }
public int? FileSize { get; set; }

//Location Message Event
public string? Title { get; set; }
public string? Address { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }

// Sticker Message Event
public string? PackageId { get; set; }
public string? StickerId { get; set; }
public string? StickerResourceType { get; set; }
public List<string>? Keywords { get; set; }
}
public class TextMessageEventEmojiDto
{
public int Index { get; set; }
public int Length { get; set; }
public string ProductId { get; set; }
public string EmojiId { get; set; }
}
public class TextMessageEventMentionDto
{
public List<TextMessageEventMentioneeDto> Mentionees { get; set; }
}
public class TextMessageEventMentioneeDto
{
public int Index { get; set; }
public int Length { get; set; }
public string UserId { get; set; }
}
public class ContentProviderDto
{
public string Type { get; set; }
public string? OriginalContentUrl { get; set; }
public string? PreviewImageUrl { get; set; }
}
public class ImageMessageEventImageSetDto
{
public string Id { get; set; }
public string Index { get; set; }
public string Total { get; set; }
}

// -------- 以下 unsend event --------
public class UnsendEventDto
{
public string messageId { get; set; }
}

}

處理事件接收

本篇要介紹的 events 都建好了,那我們就添加一個 LineBotService.cs 並寫出一個可以 handle 不同 event 接受時的事件吧。

  • 建立 Enum 資料夾並新增 WebhookEventTypeEnum.cs
  • 因為 C# 的 Enum 不能直接使用 string 作值,所以使用 class 代替其功能。
namespace LineBotMessage.Enum
{
public static class WebhookEventTypeEnum
{
public const string Message = "message";
public const string Unsend = "unsend";
public const string Follow = "follow";
public const string Unfollow = "unfollow";
public const string Join = "join" ;
public const string Leave = "leave";
}
}
  • 新增 Domain 資料夾並且再新增 LineBotService.cs
using System;
using LineBotMessage.Dtos;
using LineBotMessage.Enum;

namespace LineBotMessage.Services
{
public class LineBotService
{

// (將 LineBotController 裡宣告的 ChannelAccessToken & ChannelSecret 移到 LineBotService中)
// 貼上 messaging api channel 中的 accessToken & secret
private readonly string channelAccessToken = "Your Channel Access Token";
private readonly string channelSecret = "Your Channel Secret";

public LineBotService()
{
}

public void ReceiveWebhook(WebhookRequestBodyDto requestBody)
{
foreach(var eventObject in requestBody.Events)
{
switch (eventObject.Type)
{
case WebhookEventTypeEnum.Message:
Console.WriteLine("收到使用者傳送訊息!");
break;
case WebhookEventTypeEnum.Unsend:
Console.WriteLine($"使用者{eventObject.Source.UserId}在聊天室收回訊息!");
break;
case WebhookEventTypeEnum.Follow:
Console.WriteLine($"使用者{eventObject.Source.UserId}將我們新增為好友!");
break;
case WebhookEventTypeEnum.Unfollow:
Console.WriteLine($"使用者{eventObject.Source.UserId}封鎖了我們!");
break;
case WebhookEventTypeEnum.Join:
Console.WriteLine("我們被邀請進入聊天室了!");
break;
case WebhookEventTypeEnum.Leave:
Console.WriteLine("我們被聊天室踢出了");
break;
}
}
}
}
}
  • 修改 LineBotController.cs
using LineBotMessage.Dtos;
using LineBotMessage.Domain;
using Microsoft.AspNetCore.Mvc;

namespace LineBotMessage.Controllers
{
[Route("api/[Controller]")]
[ApiController]
public class LineBotController : ControllerBase
{
// 宣告 service
private readonly LineBotService _lineBotService;
// constructor
public LineBotController()
{
_lineBotService = new LineBotService();
}

[HttpPost("Webhook")]
public IActionResult Webhook(WebhookRequestBodyDto body)
{
_lineBotService.ReceiveWebhook(body); // 呼叫 Service
return Ok();
}

}
}

測試

都改好後就可以使用自己的Line與機器人互動,測試看看接收功能是否正常,當然也可以透過 ngrok -> http://127.0.0.1:4040/ 的位置去查看收到的各種封包內容,有錯誤時也可以在這邊查找問題~

結語

今天介紹了 6 種的 webhook events , 下一篇再介紹一部份事件後就會進入訊息推播的內容了~下一篇見。

腦筋急轉彎

  • 今天這篇,因為內容比較多,過程中其實我自己也想了好久,所以將遇到的情境分享給各位參考 ~ 如有建議,歡迎留言讓我知道~
  1. 為什麼有這麼多種類的 Webhook Events 和 Message Events 但不使用多型的方式製作呢 ?
  2. 另外 Service 要使用依賴注入的方式使用的話要怎麼做 ?

範例程式碼

如果想要參考今天範例程式碼的部份,下面是 Git Repo 連結,方便大家參考。

Day5_WebhookEventOnjects

>>下一篇>>

--

--