Repository: line/line-bot-sdk-go
Pull request #306 (2022-03-16 18:18:22)

Support VideoComponent in Flex message
#305 
- Add `FlexVideoAspectRatioType` for some common video aspect ratios (and the inverse ones) 
- Add `VideoComponent` which is also a `FlexComponent`


Patch:
diff --git a/linebot/flex.go b/linebot/flex.go
index 9072e70..06e84f1 100644
--- a/linebot/flex.go
+++ b/linebot/flex.go
@@ -44,6 +44,7 @@ const (
 	FlexComponentTypeSpacer    FlexComponentType = "spacer"
 	FlexComponentTypeSpan      FlexComponentType = "span"
 	FlexComponentTypeText      FlexComponentType = "text"
+	FlexComponentTypeVideo     FlexComponentType = "video"
 )
 
 // FlexBubbleSizeType type
@@ -142,6 +143,22 @@ const (
 	FlexImageAspectModeTypeFit   FlexImageAspectModeType = "fit"
 )
 
+// FlexVideoAspectRatioType type
+type FlexVideoAspectRatioType string
+
+// FlexVideoAspectRatioType constants
+const (
+	FlexVideoAspectRatioType1to1   FlexVideoAspectRatioType = "1:1"
+	FlexVideoAspectRatioType4to3   FlexVideoAspectRatioType = "4:3"
+	FlexVideoAspectRatioType16to9  FlexVideoAspectRatioType = "16:9"
+	FlexVideoAspectRatioType20to13 FlexVideoAspectRatioType = "20:13"
+	FlexVideoAspectRatioType21to9  FlexVideoAspectRatioType = "21:9"
+	FlexVideoAspectRatioType3to4   FlexVideoAspectRatioType = "3:4"
+	FlexVideoAspectRatioType9to16  FlexVideoAspectRatioType = "9:16"
+	FlexVideoAspectRatioType13to20 FlexVideoAspectRatioType = "13:20"
+	FlexVideoAspectRatioType9to21  FlexVideoAspectRatioType = "9:21"
+)
+
 // FlexBoxLayoutType type
 type FlexBoxLayoutType string
 
@@ -874,6 +891,35 @@ func (c *TextComponent) MarshalJSON() ([]byte, error) {
 	})
 }
 
+// VideoComponent type
+type VideoComponent struct {
+	Type        FlexComponentType
+	URL         string
+	PreviewURL  string
+	AltContent  FlexComponent
+	AspectRatio FlexVideoAspectRatioType
+	Action      *URIAction
+}
+
+// MarshalJSON method of VideoComponent
+func (c *VideoComponent) MarshalJSON() ([]byte, error) {
+	return json.Marshal(&struct {
+		Type        FlexComponentType        `json:"type"`
+		URL         string                   `json:"url"`
+		PreviewURL  string                   `json:"previewUrl"`
+		AltContent  FlexComponent            `json:"altContent"`
+		AspectRatio FlexVideoAspectRatioType `json:"aspectRatio,omitempty"`
+		Action      *URIAction               `json:"action,omitempty"`
+	}{
+		Type:        FlexComponentTypeVideo,
+		URL:         c.URL,
+		PreviewURL:  c.PreviewURL,
+		AltContent:  c.AltContent,
+		AspectRatio: c.AspectRatio,
+		Action:      c.Action,
+	})
+}
+
 // FlexComponent implements FlexComponent interface
 func (*BoxComponent) FlexComponent() {}
 
@@ -900,3 +946,6 @@ func (*SpanComponent) FlexComponent() {}
 
 // FlexComponent implements FlexComponent interface
 func (*TextComponent) FlexComponent() {}
+
+// FlexComponent implements FlexComponent interface
+func (*VideoComponent) FlexComponent() {}
diff --git a/linebot/flex_unmarshal.go b/linebot/flex_unmarshal.go
index 39c14a6..06f52d1 100644
--- a/linebot/flex_unmarshal.go
+++ b/linebot/flex_unmarshal.go
@@ -85,6 +85,8 @@ func (c *rawFlexComponent) UnmarshalJSON(data []byte) error {
 		component = &SpacerComponent{}
 	case FlexComponentTypeText:
 		component = &TextComponent{}
+	case FlexComponentTypeVideo:
+		component = &VideoComponent{}
 	default:
 		return errors.New("invalid flex component type")
 	}
@@ -197,3 +199,19 @@ func (c *TextComponent) UnmarshalJSON(data []byte) error {
 	c.Action = raw.Action.Action
 	return nil
 }
+
+// UnmarshalJSON method for VideoComponent
+func (c *VideoComponent) UnmarshalJSON(data []byte) error {
+	type alias VideoComponent
+	raw := struct {
+		AltContent rawFlexComponent `json:"altContent"`
+		*alias
+	}{
+		alias: (*alias)(c),
+	}
+	if err := json.Unmarshal(data, &raw); err != nil {
+		return err
+	}
+	c.AltContent = raw.AltContent.Component
+	return nil
+}


Test patch:
diff --git a/linebot/flex_test.go b/linebot/flex_test.go
index 552c911..1f78f61 100644
--- a/linebot/flex_test.go
+++ b/linebot/flex_test.go
@@ -766,6 +766,49 @@ func TestUnmarshalFlexMessageJSON(t *testing.T) {
 				},
 			},
 		},
+		{
+			JSON: []byte(`{
+	"type": "bubble",
+	"hero": {
+    	"type": "video",
+    	"url": "https://example.com/video.mp4",
+    	"previewUrl": "https://example.com/video_preview.png",
+    	"altContent": {
+      		"type": "image",
+      		"size": "full",
+      		"aspectRatio": "20:13",
+      		"aspectMode": "cover",
+      		"url": "https://example.com/image.png"
+    	},
+    	"action": {
+      		"type": "uri",
+      		"label": "More information",
+      		"uri": "http://linecorp.com/"
+    	},
+    	"aspectRatio": "20:13"
+  	}
+}`),
+			Want: &BubbleContainer{
+				Type: FlexContainerTypeBubble,
+				Hero: &VideoComponent{
+					Type:       FlexComponentTypeVideo,
+					URL:        "https://example.com/video.mp4",
+					PreviewURL: "https://example.com/video_preview.png",
+					AltContent: &ImageComponent{
+						Type:        FlexComponentTypeImage,
+						URL:         "https://example.com/image.png",
+						Size:        FlexImageSizeTypeFull,
+						AspectRatio: FlexImageAspectRatioType20to13,
+						AspectMode:  FlexImageAspectModeTypeCover,
+					},
+					Action: &URIAction{
+						Label: "More information",
+						URI:   "http://linecorp.com/",
+					},
+					AspectRatio: FlexVideoAspectRatioType20to13,
+				},
+			},
+		},
 	}
 	for i, tc := range testCases {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {