iT邦幫忙

第 11 屆 iThome 鐵人賽

3
Software Development

Line Bot 心得分享 LineMessagingApi + LUIS + BotFramework系列 第 15

[Day15] LINE Bot 取得用戶上傳的內容 - Message Event

今天要介紹如何取得用戶上傳的內容,到目前為止 Line Bot 只能接收文字訊息,那如果使用者傳的是圖片或影片呢,這篇就來看 Line Bot 還可以處理哪些訊息格式。

Message event:

  • Text 文字訊息
  • Image 圖片訊息
  • Video 影片訊息
  • Audio 音訊訊息
  • File 檔案訊息
  • Location 位置訊息
  • Sticker 貼圖訊息

Message event 外層結構

  • type: 類型 message
  • replyToken: 回覆訊息用的 token,時效為 30 秒,只能使用一次
  • message: 訊息物件。

1. Text 文字訊息

  • id: 訊息 ID。
  • type: 類型 text
  • text: 文字訊息。

來源官方文件: #wh-text

程式碼:

protected override async Task OnMessageAsync(MessageEvent ev)
{
    switch (ev.Message.Fix())
    {
        //文字訊息
        case TextEventMessage textMessage:
            {
                await _messagingClient.ReplyMessageAsync(ev.ReplyToken, 
                    $"收到的是文字訊息,內容: {textMessage.Text}");
            }
            break;
    }
}

結果:

https://ithelp.ithome.com.tw/upload/images/20200116/20106865QtRPjv2Ki0.jpg


2. Image 圖片訊息

  • id: 訊息 ID。
  • type: 類型 image
  • contentProvider.type: 訊息提供者分為兩類
    line: 訊息由使用者上傳,可以從 content 取得檔案。
    external: 訊息由 liff.sendMessages() 方法送出,可從下列屬性取得檔案。
  • contentProvider.originalContentUrl: 內容超連結。
  • contentProvider.previewImageUrl: 預覽圖片超連結。

來源官方文件: #wh-image

程式碼:

protected override async Task OnMessageAsync(MessageEvent ev)
{
    switch (ev.Message.Fix())
    {
        //圖片訊息
        case ImageEventMessage imageMessage:
        {
            if (imageMessage.ContentProvider.Type != ContentProviderType.Line)
                break;

            //取得檔案串流
            using (var stream = 
                await _messagingClient.GetContentStreamAsync(imageMessage.Id))
            {
                //取得副檔名
                var ext = GetFileExtension(
                    stream.ContentHeaders.ContentType.MediaType);

                //儲存檔案
                using (var fs = 
                    File.Create($@"D:\home\site\wwwroot\App_Data\image{ext}"))
                {
                    stream.CopyTo(fs);
                }

                await _messagingClient.ReplyMessageAsync(ev.ReplyToken,
                    $"收到的是圖片訊息\n" +
                    $"檔案名稱: image{ext}\n" +
                    $@"儲存路徑: D:\home\site\wwwroot\App_Data\image{ext}");
            }

        }
        break;
    }
}

沒辦法取得使用者上傳的檔名,只有 MediaType 可以用。

string GetFileExtension(string mediaType)
{
    switch (mediaType)
    {
        case "image/jpeg":
            return ".jpg";
        case "audio/aac":
            return ".aac";
        case "video/mp4":
            return ".mp4";
        default:
            return "";
    }
}

結果:

https://ithelp.ithome.com.tw/upload/images/20200116/20106865HJazAeqZhh.jpg


3. Video 影片訊息

  • id: 訊息 ID。
  • type: 類型 video
  • duration: 時間長度 (milliseconds)。
  • contentProvider.type: 訊息提供者分為兩類
    line: 訊息由使用者上傳,可以從 content 取得檔案。
    external: 訊息由 liff.sendMessages() 方法送出,可從下列屬性取得檔案。
  • contentProvider.originalContentUrl: 內容超連結。
  • contentProvider.previewImageUrl: 預覽圖片超連結。

來源官方文件: #wh-video

程式碼:

protected override async Task OnMessageAsync(MessageEvent ev)
{
    switch (ev.Message.Fix())
    {
        //影片訊息
        case VideoEventMessage videoMessage:
        {
            if (videoMessage.ContentProvider.Type != ContentProviderType.Line)
                break;

            //取得檔案串流
            using (var stream = 
                await _messagingClient.GetContentStreamAsync(videoMessage.Id))
            {
                //取得副檔名
                var ext = GetFileExtension(
                    stream.ContentHeaders.ContentType.MediaType);

                //儲存檔案
                using (var fs = 
                    File.Create($@"D:\home\site\wwwroot\App_Data\video{ext}"))
                {
                    stream.CopyTo(fs);
                }

                await _messagingClient.ReplyMessageAsync(ev.ReplyToken,
                    $"收到的是影片訊息\n" +
                    $"Duration: {videoMessage.Duration}\n" +
                    $"檔案名稱: video{ext}\n" +
                    $@"儲存路徑: D:\home\site\wwwroot\App_Data\video{ext}");
            }
        }
        break;
    }
}

結果:

https://ithelp.ithome.com.tw/upload/images/20200116/20106865dJdHrHGcfH.jpg


4. Audio 音訊訊息

  • id: 訊息 ID。
  • type: 類型 audio
  • duration: 時間長度 (milliseconds)。
  • contentProvider.type: 訊息提供者分為兩類
    line: 訊息由使用者上傳,可以從 content 取得檔案。
    external: 訊息由 liff.sendMessages() 方法送出,可從下列屬性取得檔案。
  • contentProvider.originalContentUrl: 內容超連結。

來源官方文件: #wh-audio

程式碼:

protected override async Task OnMessageAsync(MessageEvent ev)
{
    switch (ev.Message.Fix())
    {
        //音訊訊息
        case AudioEventMessage audioMessage:
        {
            if (audioMessage.ContentProvider.Type != ContentProviderType.Line)
                break;

            //取得檔案串流
            using (var stream = 
                await _messagingClient.GetContentStreamAsync(audioMessage.Id))
            {
                //取得副檔名
                var ext = GetFileExtension(
                    stream.ContentHeaders.ContentType.MediaType);

                //儲存檔案
                using (var fs = 
                    File.Create($@"D:\home\site\wwwroot\App_Data\audio{ext}"))
                {
                    stream.CopyTo(fs);
                }

                await _messagingClient.ReplyMessageAsync(ev.ReplyToken,
                    $"收到的是音訊訊息\n" +
                    $"Duration: {audioMessage.Duration}\n" +
                    $"檔案名稱: audio{ext}\n" +
                    $@"儲存路徑: D:\home\site\wwwroot\App_Data\audio{ext}");
            }
        }
        break;
    }
}

結果:

https://ithelp.ithome.com.tw/upload/images/20200116/20106865m1RvmeGUO1.jpg


5. File 檔案訊息

可以從 content 取得檔案。

  • id: 訊息 ID。
  • type: 類型 file
  • fileName: 檔案名稱。
  • fileSize: 檔案大小。

來源官方文件: #wh-file

程式碼:

protected override async Task OnMessageAsync(MessageEvent ev)
{
    switch (ev.Message.Fix())
    {
        //檔案訊息
        case FileEventMessage fileMessage:
        {
            //取得檔案串流
            using (var stream = 
                await _messagingClient.GetContentStreamAsync(fileMessage.Id))
            {
                //取得副檔名
                var ext = GetFileExtension(
                    stream.ContentHeaders.ContentType.MediaType);

                //儲存檔案
                using (var fs = 
                    File.Create($@"D:\home\site\wwwroot\App_Data\{fileMessage.FileName}"))
                {
                    stream.CopyTo(fs);
                }

                await _messagingClient.ReplyMessageAsync(ev.ReplyToken,
                    $"收到的是檔案訊息。\n" +
                    $"FileName: {fileMessage.FileName}\n" +
                    $"FileSize: {fileMessage.FileSize}\n" +
                    $@"儲存路徑: D:\home\site\wwwroot\App_Data\{fileMessage.FileName}");
            }
            
        }
        break;
    }
}

結果:

https://ithelp.ithome.com.tw/upload/images/20200116/20106865aeguQTudzO.jpg


6. Location 位置訊息

可以從 content 取得檔案。

  • id: 訊息 ID。
  • type: 類型 location
  • title: 標題。
  • address: 地址。
  • latitude: 緯度。
  • longitude: 經度。

來源官方文件: #wh-location

程式碼:

protected override async Task OnMessageAsync(MessageEvent ev)
{
    switch (ev.Message.Fix())
    {
        //位置訊息
        case LocationEventMessage locationMessage:
            {
                await _messagingClient.ReplyMessageAsync(ev.ReplyToken,
                    $"收到的是位置訊息。\n" +
                    $"Title: {locationMessage.Title}\n" +
                    $"Address: {locationMessage.Address}\n" +
                    $"Latitude: {locationMessage.Latitude}\n" +
                    $"Longitude: {locationMessage.Longitude}");
            }
            break;
    }
}

結果:

https://ithelp.ithome.com.tw/upload/images/20200116/20106865C73dZCNBPy.jpg


7. Sticker 貼圖訊息

可以使用的 Line 貼圖清單

  • id: 訊息 ID。
  • type: 類型 sticker
  • packageId: 包裝代碼。
  • stickerId: 貼圖代碼。
  • stickerResourceType: 貼圖類型。
    STATICANIMATIONSOUNDANIMATION_SOUND
    POPUPPOPUP_SOUNDNAME_TEXT

來源官方文件: #wh-sticker

目前 SDK 沒有 stickerResourceType 屬性可以用,如果自己加需要改很多地方,所以就等作者更新吧。

程式碼:

protected override async Task OnMessageAsync(MessageEvent ev)
{
    switch (ev.Message.Fix())
    {
        //貼圖訊息
        case StickerEventMessage stickerMessage:
            {
                await _messagingClient.ReplyMessageAsync(ev.ReplyToken,
                    $"收到的是貼圖訊息。\n" +
                    $"Package: {stickerMessage.PackageId}\n" +
                    $"Sticker: {stickerMessage.StickerId}");
            }
            break;
    }
}

結果:

https://ithelp.ithome.com.tw/upload/images/20200116/20106865MirnqyixXE.jpg


程式中 ev.Message.Fix() 的作用是?

不知道什麼原因作者把 ImageEventAudioEventVideoEvent 這三個物件,
合併成一個 MediaEvent,所以目前 GitHub 上的寫法是沒辦法使用的。

https://ithelp.ithome.com.tw/upload/images/20200116/20106865kf4iYxv6KW.jpg

來源: https://github.com/pierre3/LineMessagingApi

我自己寫了一個 Fix() 方法修正這件事。

/*
MIT License

Copyright (c) 2017 pierre3

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

namespace FixMessageEvent
{
    public static class MessageEventExtension
    {
        public static EventMessage Fix(this EventMessage eventMessage)
        {
            switch (eventMessage)
            {
                case MediaEventMessage media:
                    {
                        if (media.Type == EventMessageType.Image)
                            return new ImageEventMessage(
                                media.Type, 
                                media.Id, 
                                media.ContentProvider, 
                                media.Duration);
                        if (media.Type == EventMessageType.Video)
                            return new VideoEventMessage(
                                media.Type, 
                                media.Id, 
                                media.ContentProvider, 
                                media.Duration);
                        if (media.Type == EventMessageType.Audio)
                            return new AudioEventMessage(
                                media.Type, 
                                media.Id, 
                                media.ContentProvider, 
                                media.Duration);
                    }
                    break;
            }
            return eventMessage;
        }
    }

    public class ImageEventMessage : MediaEventMessage
    {
        public ImageEventMessage(
            EventMessageType type, 
            string id,
            ContentProvider contentProvider = null, 
            int? duration = null
            ) : base(type, id, contentProvider, duration)
        {
        }
    }

    public class VideoEventMessage : MediaEventMessage
    {
        public VideoEventMessage(
            EventMessageType type,
            string id,
            ContentProvider contentProvider = null,
            int? duration = null
            ) : base(type, id, contentProvider, duration)
        {
        }
    }

    public class AudioEventMessage : MediaEventMessage
    {
        public AudioEventMessage(
            EventMessageType type,
            string id,
            ContentProvider contentProvider = null,
            int? duration = null
            ) : base(type, id, contentProvider, duration)
        {
        }
    }
}

如果怕麻煩,也可以直接判斷 Message.Type,作者的範例中大多使用這種寫法。

protected override async Task OnMessageAsync(MessageEvent ev)
{
    switch (ev.Message.Type)
    {
        case EventMessageType.Text:
            var textMessage = ev.Message as TextEventMessage;
            break;
        case EventMessageType.Image:
            var imageMessage = ev.Message as MediaEventMessage;
            break;
        case EventMessageType.Audio:
            var audioMessage = ev.Message as MediaEventMessage;
            break;
        case EventMessageType.Video:
            var videoMessage = ev.Message as MediaEventMessage;
            break;
        case EventMessageType.File:
            var fileMessage = ev.Message as FileEventMessage;
            break;
        case EventMessageType.Location:
            var locationMessage = ev.Message as LocationEventMessage;
            break;
        case EventMessageType.Sticker:
            var stickerMessage = ev.Message as StickerEventMessage;
            break;
    }
}

結語

下一篇要介紹 LINE Bot 的邀請處理,今天就到這裡,感謝大家觀看。 (´・ω・`)


上一篇
[Day14] LINE Bot 的動作類型 - Action Objects
下一篇
[Day16] LINE Bot 的邀請處理 - Webhook Event
系列文
Line Bot 心得分享 LineMessagingApi + LUIS + BotFramework27
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
eric19740521
iT邦新手 1 級 ‧ 2020-01-19 02:58:36

寫的範例很棒

謝謝 ╰( ̄▽ ̄)╭

0
熊熊工程師
iT邦研究生 3 級 ‧ 2022-04-19 09:13:10

感謝整理,非常實用

我要留言

立即登入留言