Repository: line/line-bot-sdk-go (go)

Issue:
Please add Message.Type() method (text, image, video, flex, ...)
I'm making LineBot webhook, after using ```linebot.ParseRequest``` 
I can only get **Event Type** by using  
```
event, err := linebot.ParseRequest(os.Getenv("LINE_CHANNEL_SECRET"), httpReqest)
event[0].Type
```
but cannot get the **message type**, 
Please consider including it in the future update.

**This is my code to check message type, may useful**
```
// some part of code from func (e *Event) MarshalJSON()
fmt.Println(EventMessageType(event[0]))  //  text, image, ...

func EventMessageType(e *linebot.Event) linebot.MessageType {
	switch e.Message.(type) {
	case *linebot.TextMessage:
		return linebot.MessageTypeText
	case *linebot.ImageMessage:
		return linebot.MessageTypeImage
	case *linebot.VideoMessage:
		return linebot.MessageTypeVideo
	case *linebot.AudioMessage:
		return linebot.MessageTypeAudio
	case *linebot.FileMessage:
		return linebot.MessageTypeFile
	case *linebot.LocationMessage:
		return linebot.MessageTypeLocation
	case *linebot.StickerMessage:
		return linebot.MessageTypeSticker
	}
	return ""
}
```
2nd way (I'm using Go Fiber Frameworks)
```
body := &struct {
		Events []struct {
			Message struct {
				Type string `json:"type"`
			} `json:"message,omitempty"`
		} `json:"events,omitempty"`
	}{}
	c.BodyParser(&body)
	fmt.Println(body.Events[0].Message.Type)
```

Pull request description:
Implement #251, add Message.Type()
Resolves #251 

Patch:
diff --git a/examples/richmenu_helper/main.go b/examples/richmenu_helper/main.go
index e2cad57..dfb1aef 100644
--- a/examples/richmenu_helper/main.go
+++ b/examples/richmenu_helper/main.go
@@ -25,8 +25,7 @@ import (
 
 func main() {
 	var (
-		mode     = flag.String("mode", "list", "mode of richmenu helper [list|create|link|unlink|bulklink|bulkunlink|get|delete|upload|download|alias_create|alias_get|alias_update|alias_delete|alias_list]")
-		aid      = flag.String("aid", "", "alias id")
+		mode     = flag.String("mode", "list", "mode of richmenu helper [list|create|link|unlink|bulklink|bulkunlink|get|delete|upload|download]")
 		uid      = flag.String("uid", "", "user id")
 		rid      = flag.String("rid", "", "richmenu id")
 		filePath = flag.String("image.path", "", "path to image, used in upload/download mode")
@@ -60,32 +59,6 @@ func main() {
 			log.Fatal(err)
 		}
 		log.Printf("Image is written to %s", *filePath)
-	case "alias_create":
-		if _, err = bot.CreateRichMenuAlias(*aid, *rid).Do(); err != nil {
-			log.Fatal(err)
-		}
-	case "alias_get":
-		if res, err := bot.GetRichMenuAlias(*aid).Do(); err != nil {
-			log.Fatal(err)
-		} else {
-			log.Printf("%v\n", res)
-		}
-	case "alias_update":
-		if _, err = bot.UpdateRichMenuAlias(*aid, *rid).Do(); err != nil {
-			log.Fatal(err)
-		}
-	case "alias_delete":
-		if _, err = bot.DeleteRichMenuAlias(*aid).Do(); err != nil {
-			log.Fatal(err)
-		}
-	case "alias_list":
-		res, err := bot.GetRichMenuAliasList().Do()
-		if err != nil {
-			log.Fatal(err)
-		}
-		for _, alias := range res {
-			log.Printf("%v\n", alias)
-		}
 	case "link":
 		if _, err = bot.LinkUserRichMenu(*uid, *rid).Do(); err != nil {
 			log.Fatal(err)
diff --git a/linebot/message.go b/linebot/message.go
index 9927906..adc1c28 100644
--- a/linebot/message.go
+++ b/linebot/message.go
@@ -38,6 +38,7 @@ const (
 // Message interface
 type Message interface {
 	Message()
+	Type() MessageType
 }
 
 // SendingMessage interface
@@ -56,6 +57,7 @@ type TextMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 
 	Mention *Mention
 }
@@ -69,7 +71,7 @@ func (m *TextMessage) MarshalJSON() ([]byte, error) {
 		Sender     *Sender          `json:"sender,omitempty"`
 		Emojis     []*Emoji         `json:"emojis,omitempty"`
 	}{
-		Type:       MessageTypeText,
+		Type:       m.messageType,
 		Text:       m.Text,
 		QuickReply: m.quickReplyItems,
 		Sender:     m.sender,
@@ -103,6 +105,7 @@ type ImageMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 }
 
 // MarshalJSON method of ImageMessage
@@ -114,7 +117,7 @@ func (m *ImageMessage) MarshalJSON() ([]byte, error) {
 		QuickReply         *QuickReplyItems `json:"quickReply,omitempty"`
 		Sender             *Sender          `json:"sender,omitempty"`
 	}{
-		Type:               MessageTypeImage,
+		Type:               m.messageType,
 		OriginalContentURL: m.OriginalContentURL,
 		PreviewImageURL:    m.PreviewImageURL,
 		QuickReply:         m.quickReplyItems,
@@ -147,6 +150,7 @@ type VideoMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 }
 
 // MarshalJSON method of VideoMessage
@@ -158,7 +162,7 @@ func (m *VideoMessage) MarshalJSON() ([]byte, error) {
 		QuickReply         *QuickReplyItems `json:"quickReply,omitempty"`
 		Sender             *Sender          `json:"sender,omitempty"`
 	}{
-		Type:               MessageTypeVideo,
+		Type:               m.messageType,
 		OriginalContentURL: m.OriginalContentURL,
 		PreviewImageURL:    m.PreviewImageURL,
 		QuickReply:         m.quickReplyItems,
@@ -191,6 +195,7 @@ type AudioMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 }
 
 // MarshalJSON method of AudioMessage
@@ -202,7 +207,7 @@ func (m *AudioMessage) MarshalJSON() ([]byte, error) {
 		QuickReply         *QuickReplyItems `json:"quickReply,omitempty"`
 		Sender             *Sender          `json:"sender,omitempty"`
 	}{
-		Type:               MessageTypeAudio,
+		Type:               m.messageType,
 		OriginalContentURL: m.OriginalContentURL,
 		Duration:           m.Duration,
 		QuickReply:         m.quickReplyItems,
@@ -229,9 +234,10 @@ func (m *AudioMessage) AddEmoji(*Emoji) SendingMessage {
 
 // FileMessage type
 type FileMessage struct {
-	ID       string
-	FileName string
-	FileSize int
+	ID          string
+	FileName    string
+	FileSize    int
+	messageType MessageType
 }
 
 // LocationMessage type
@@ -244,6 +250,7 @@ type LocationMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 }
 
 // MarshalJSON method of LocationMessage
@@ -257,7 +264,7 @@ func (m *LocationMessage) MarshalJSON() ([]byte, error) {
 		QuickReply *QuickReplyItems `json:"quickReply,omitempty"`
 		Sender     *Sender          `json:"sender,omitempty"`
 	}{
-		Type:       MessageTypeLocation,
+		Type:       m.messageType,
 		Title:      m.Title,
 		Address:    m.Address,
 		Latitude:   m.Latitude,
@@ -294,6 +301,7 @@ type StickerMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 }
 
 // MarshalJSON method of StickerMessage
@@ -307,7 +315,7 @@ func (m *StickerMessage) MarshalJSON() ([]byte, error) {
 		QuickReply          *QuickReplyItems    `json:"quickReply,omitempty"`
 		Sender              *Sender             `json:"sender,omitempty"`
 	}{
-		Type:                MessageTypeSticker,
+		Type:                m.messageType,
 		PackageID:           m.PackageID,
 		StickerID:           m.StickerID,
 		StickerResourceType: m.StickerResourceType,
@@ -341,6 +349,7 @@ type TemplateMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 }
 
 // MarshalJSON method of TemplateMessage
@@ -352,7 +361,7 @@ func (m *TemplateMessage) MarshalJSON() ([]byte, error) {
 		QuickReply *QuickReplyItems `json:"quickReply,omitempty"`
 		Sender     *Sender          `json:"sender,omitempty"`
 	}{
-		Type:       MessageTypeTemplate,
+		Type:       m.messageType,
 		AltText:    m.AltText,
 		Template:   m.Template,
 		QuickReply: m.quickReplyItems,
@@ -387,6 +396,7 @@ type ImagemapMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 }
 
 // MarshalJSON method of ImagemapMessage
@@ -401,7 +411,7 @@ func (m *ImagemapMessage) MarshalJSON() ([]byte, error) {
 		QuickReply *QuickReplyItems `json:"quickReply,omitempty"`
 		Sender     *Sender          `json:"sender,omitempty"`
 	}{
-		Type:       MessageTypeImagemap,
+		Type:       m.messageType,
 		BaseURL:    m.BaseURL,
 		AltText:    m.AltText,
 		BaseSize:   m.BaseSize,
@@ -442,6 +452,7 @@ type FlexMessage struct {
 
 	quickReplyItems *QuickReplyItems
 	sender          *Sender
+	messageType     MessageType
 }
 
 // MarshalJSON method of FlexMessage
@@ -453,7 +464,7 @@ func (m *FlexMessage) MarshalJSON() ([]byte, error) {
 		QuickReply *QuickReplyItems `json:"quickReply,omitempty"`
 		Sender     *Sender          `json:"sender,omitempty"`
 	}{
-		Type:       MessageTypeFlex,
+		Type:       m.messageType,
 		AltText:    m.AltText,
 		Contents:   m.Contents,
 		QuickReply: m.quickReplyItems,
@@ -508,10 +519,41 @@ func (*ImagemapMessage) Message() {}
 // Message implements Message interface
 func (*FlexMessage) Message() {}
 
+// Type implements Message interface
+func (m *FileMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *TextMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *ImageMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *VideoMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *AudioMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *LocationMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *StickerMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *TemplateMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *ImagemapMessage) Type() MessageType { return m.messageType }
+
+// Type implements Message interface
+func (m *FlexMessage) Type() MessageType { return m.messageType }
+
 // NewTextMessage function
 func NewTextMessage(content string) *TextMessage {
 	return &TextMessage{
-		Text: content,
+		Text:        content,
+		messageType: MessageTypeText,
 	}
 }
 
@@ -520,6 +562,7 @@ func NewImageMessage(originalContentURL, previewImageURL string) *ImageMessage {
 	return &ImageMessage{
 		OriginalContentURL: originalContentURL,
 		PreviewImageURL:    previewImageURL,
+		messageType:        MessageTypeImage,
 	}
 }
 
@@ -528,6 +571,7 @@ func NewVideoMessage(originalContentURL, previewImageURL string) *VideoMessage {
 	return &VideoMessage{
 		OriginalContentURL: originalContentURL,
 		PreviewImageURL:    previewImageURL,
+		messageType:        MessageTypeVideo,
 	}
 }
 
@@ -536,49 +580,55 @@ func NewAudioMessage(originalContentURL string, duration int) *AudioMessage {
 	return &AudioMessage{
 		OriginalContentURL: originalContentURL,
 		Duration:           duration,
+		messageType:        MessageTypeAudio,
 	}
 }
 
 // NewLocationMessage function
 func NewLocationMessage(title, address string, latitude, longitude float64) *LocationMessage {
 	return &LocationMessage{
-		Title:     title,
-		Address:   address,
-		Latitude:  latitude,
-		Longitude: longitude,
+		Title:       title,
+		Address:     address,
+		Latitude:    latitude,
+		Longitude:   longitude,
+		messageType: MessageTypeLocation,
 	}
 }
 
 // NewStickerMessage function
 func NewStickerMessage(packageID, stickerID string) *StickerMessage {
 	return &StickerMessage{
-		PackageID: packageID,
-		StickerID: stickerID,
+		PackageID:   packageID,
+		StickerID:   stickerID,
+		messageType: MessageTypeSticker,
 	}
 }
 
 // NewTemplateMessage function
 func NewTemplateMessage(altText string, template Template) *TemplateMessage {
 	return &TemplateMessage{
-		AltText:  altText,
-		Template: template,
+		AltText:     altText,
+		Template:    template,
+		messageType: MessageTypeTemplate,
 	}
 }
 
 // NewImagemapMessage function
 func NewImagemapMessage(baseURL, altText string, baseSize ImagemapBaseSize, actions ...ImagemapAction) *ImagemapMessage {
 	return &ImagemapMessage{
-		BaseURL:  baseURL,
-		AltText:  altText,
-		BaseSize: baseSize,
-		Actions:  actions,
+		BaseURL:     baseURL,
+		AltText:     altText,
+		BaseSize:    baseSize,
+		Actions:     actions,
+		messageType: MessageTypeImagemap,
 	}
 }
 
 // NewFlexMessage function
 func NewFlexMessage(altText string, contents FlexContainer) *FlexMessage {
 	return &FlexMessage{
-		AltText:  altText,
-		Contents: contents,
+		AltText:     altText,
+		Contents:    contents,
+		messageType: MessageTypeFlex,
 	}
 }


Test patch:
diff --git a/linebot/message_test.go b/linebot/message_test.go
new file mode 100644
index 0000000..b9b73f9
--- /dev/null
+++ b/linebot/message_test.go
@@ -0,0 +1,111 @@
+// Copyright 2021 LINE Corporation
+//
+// LINE Corporation licenses this file to you under the Apache License,
+// version 2.0 (the "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at:
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package linebot
+
+import (
+	"strconv"
+	"testing"
+)
+
+func TestMessageTypes(t *testing.T) {
+	type want struct {
+		Type MessageType
+	}
+	testCases := []struct {
+		Label   string
+		Message Message
+		Want    want
+	}{
+		{
+			Label:   "A text message",
+			Message: NewTextMessage("Hello, world"),
+			Want: want{
+				Type: MessageTypeText,
+			},
+		},
+		{
+			Label:   "A location message",
+			Message: NewLocationMessage("title", "address", 35.65910807942215, 139.70372892916203),
+			Want: want{
+				Type: MessageTypeLocation,
+			},
+		},
+		{
+			Label:   "A image message",
+			Message: NewImageMessage("https://example.com/original.jpg", "https://example.com/preview.jpg"),
+			Want: want{
+				Type: MessageTypeImage,
+			},
+		},
+		{
+			Label:   "A sticker message",
+			Message: NewStickerMessage("1", "1"),
+			Want: want{
+				Type: MessageTypeSticker,
+			},
+		},
+		{
+			Label:   "A audio message",
+			Message: NewAudioMessage("https://example.com/original.m4a", 1000),
+			Want: want{
+				Type: MessageTypeAudio,
+			},
+		},
+		{
+			Label: "A template message",
+			Message: NewTemplateMessage(
+				"this is a buttons template",
+				NewButtonsTemplate(
+					"https://example.com/bot/images/image.jpg",
+					"",
+					"Please select",
+					NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText"),
+				),
+			),
+			Want: want{
+				Type: MessageTypeTemplate,
+			},
+		},
+		{
+			Label: "A flex message",
+			Message: NewFlexMessage(
+				"this is a flex message",
+				&BubbleContainer{}),
+			Want: want{
+				Type: MessageTypeFlex,
+			},
+		},
+		{
+			Label: "A Imagemap message",
+			Message: NewImagemapMessage(
+				"https://example.com/bot/images/rm001",
+				"this is an imagemap",
+				ImagemapBaseSize{1040, 1040},
+				NewURIImagemapAction("example", "https://example.com/", ImagemapArea{520, 0, 520, 1040}),
+			),
+			Want: want{
+				Type: MessageTypeImagemap,
+			},
+		},
+	}
+
+	for i, tc := range testCases {
+		t.Run(strconv.Itoa(i)+"/"+tc.Label, func(t *testing.T) {
+			if tc.Message.Type() != tc.Want.Type {
+				t.Errorf("Message type mismatch: have %v; want %v", tc.Message.Type(), tc.Want.Type)
+			}
+		})
+	}
+}